Thursday, August 1, 2013

Captcha: Drawing Text along a Bézier Spline

The following post/code will achieve something to the effect of:

Explanation of the math and why it works will come shortly. In the meantime, please see the above link for a detailed explanation.

This code assumes you have already drawn some text on your GraphicsPath. Here is the code to transform your GraphicsPath text to follow a cubic Bézier Spline:
``````
GraphicsPath BezierWarp(GraphicsPath text,Size size)
{
// Control points for a cubic Bézier spline
PointF P0 = new PointF();
PointF P1 = new PointF();
PointF P2 = new PointF();
PointF P3 = new PointF();

float shrink = 20;
float shift = 0;

P0.X = shrink;
P0.Y = shrink+shift;
P1.X = size.Width-shrink;
P1.Y = shrink;
P2.X = shrink;
P2.Y = size.Height-shrink;
P3.X = size.Width-shrink;
P3.Y = size.Height-shrink-shift;

// Calculate coefficients A thru H from the control points
float A = P3.X - 3 * P2.X + 3 * P1.X - P0.X;
float B = 3 * P2.X - 6 * P1.X + 3 * P0.X;
float C = 3 * P1.X - 3 * P0.X;
float D = P0.X;

float E = P3.Y - 3 * P2.Y + 3 * P1.Y - P0.Y;
float F = 3 * P2.Y - 6 * P1.Y + 3 * P0.Y;
float G = 3 * P1.Y - 3 * P0.Y;
float H = P0.Y;

PointF[] pathPoints = text.PathPoints;
RectangleF textBounds = text.GetBounds();

for (int i =0; i  < pathPoints.Length; i++)
{
PointF pt = pathPoints[i];
float textX = pt.X;
float textY = pt.Y;

// Normalize the x coordinate into the parameterized
// value with a domain between 0 and 1.
float t  =  textX / textBounds.Width;
float t2 = (t * t);
float t3 = (t * t * t);

// Calculate spline point for parameter t
float Sx = A * t3 + B * t2 + C * t + D;
float Sy = E * t3 + F * t2 + G * t + H;

// Calculate the tangent vector for the point
float Tx = 3 * A * t2 + 2 * B * t + C;
float Ty = 3 * E * t2 + 2 * F * t + G;

// Rotate 90 or 270 degrees to make it a perpendicular
float Px = - Ty;
float Py =   Tx;

// Normalize the perpendicular into a unit vector
float magnitude = (float)Math.Sqrt((Px*Px) + (Py*Py));
Px /= magnitude;
Py /= magnitude;

// Assume that input text point y coord is the "height" or
// distance from the spline.  Multiply the perpendicular
// vector with y. it becomes the new magnitude of the vector
Px *= textY;
Py *= textY;

// Translate the spline point using the resultant vector
float finalX = Px + Sx;
float finalY = Py + Sy;

pathPoints[i] = new PointF(finalX, finalY);
}

return new GraphicsPath(pathPoints,text.PathTypes);
}
``````