Handwriting Effect with Catmull-Rom Curve

The requirement

Given a rasterized image of a handwriting (e.g. "Thank you"), we must create a handwriting animation effect.
Thank you Image courtesy: tutsplus.com

The solution

In reality, the tip of the pen leaves behind the trail of ink on paper. In computer graphics, we must apply the masking/unmasking technique to reveal parts of the words "Thank you".

It would be tempting to try an OCR approach. To me, that is an overkill for a small advertisement.

Recipes

First, we kindly ask the designer to separate the word "Thank you" from the background.

Then, we trace the coordinates of each of the characters. We should store these ordinates on a spreadsheet (preferably Google Sheet) in the exact same order as we would write the letters. The width of the curve at each of the coordinates should also be recorded.

Each character can have one or more splines. Each spline is consisted of multiple Catmull-Rom1 segments. Each segment requires four controlling points.

p1p2p3p4 Curve
p[-1]bcd Segment 1
bcde Segment 2
...
p[n-2]p[n-1]p[n]p[n+1] Segment n

If the "T" needs 10 segments, we would need 13 controlling points. The more controlling points, the closer the curve approximation becomes.

Then, apply Catmull-Rom parametric curve to interpolate positions along the recorded coordinates.

Techniques

To generate a smooth handwriting effect, we must be able to control two factors:

  1. The deviation between the tracing mask and the image
  2. The rate at which the image is revealed

Create the tracing mask

For each of the segments in the spline, find the interpolated coordinates by evaluate the parametric function C(t), whereas t is incremented from 0 to 1 with a small stepping.

FOREACH segment IN spline:  
  FOR t = 0 to 1 stepping 0.01
    point = segment(t)
    drawPoint(p)

Controlling the revealing rate

For a smooth playback, a list of frames should be prepared. It is essential to maintain a constant rate at which characters are revealed. That rate is measured in distance revealed per frame.

In order words, we keep on drawPoint(p) and whenever p - p0 >= rate we take a snapshot of the mask.

RATE = 0.1  
delta = 0

FOREACH segment IN spline:  
  FOR t = 0 to 1 stepping 0.01
    point = segment(t)
    drawPoint(p)

    if p - p0 > RATE:
      snapshot()

    p0 = p

Finally - the animation

Assuming that we use HTML5 Canvas, it is convenient to have a cheat sheet 2. The key to success here is Canvas API supports globalCompositeOperation, which allows us to control how images are composited. Masking/Unmasking is implemented primarily on this concept.

frameIndex = 0;

function frame() {  
  ctx.save();

  ctx.clearRect(0, 0, WIDTH, HEIGHT);  
  ctx.putImageData(snapshots[frameIndex], 0, 0);
  ctx.globalCompositeOperation = 'source-in';
  ctx.drawImage(handwriting, 0, 0);

  ctx.restore();

  setTimeout(function () {
    frameIndex++;
    requestAnimationFrame(frame);
  }, 1000/60); // Sixty frames per second
}

Conclusion

The solution described here could be overwhelming to some and over-simplistic to the rest. The good news is: I have implemented the code. I will create a pen and embed it here. Nothing would beat a real working piece of code anyways!