Generative Bad Handwriting
Show this tweet to a doctor

So.. I made a popular tweet last week in the #つぶやきProcessing circles.

I’ll try to explain how this script worked and how I was able to fit the whole thing into 280 characters. If you’re new to p5.js, just try pasting the tweet text to https://editor.p5js.org/ to get a similar output.

Story and Previous works

All of this started when last week when I was experimenting with the p5js curve() function. Internally this is an implementation of the Centripetal Catmull–Rom spline. I tried generating a bunch of 8 legged water spiders for fun :)

I admit this looks pretty stupid. The aim was to generate an animation a whole bunch of water spiders with the camera panning around. But then, for me to understand how exactly the Catmull-Rom spline worked, I decided to randomly plot a bunch of curves on a 2D canvas and it somehow resembled handwriting from my native language (Malayalam).

Actual Malayalam handwriting sample.

Reducing character spacing.. Do you see the similarity now?

Also, at this time I was playing the PC remaster of Journey (2012). Journey has a very beautiful blocky scriptures all over the temples in the game.

I guessed this is pretty easy generate. I made a few attempts to reproduce the approximate style using p5.js

and even an infinite scrolling version

This script had some serious performance issues(you can see it slowing down towards the end). Later I learned that this style of meaningless writing is a thing in the art community known as Generative Asemic Writing. According to Wikipedia:

Asemic writing is a wordless open semantic form of writing. The word asemic means “having no specific semantic content”. With the nonspecificity of asemic writing there comes a vacuum of meaning which is left for the reader to fill in and interpret.

I decided to combine the two and make an infinite generator of malayalam-esque asemic writing. I’ve seen curve generated asemic before. So, What I did is not something new.. however, maybe the way I made it infinite scrolling was something new(?). I’ll try to explain how the code works.

Code

I lost the original script in the minifying process, but I managed to unminify the tweet somehow.

var yOffset = 24;
var scrollPosition = 0;
var canvasWidth = 800;
var margin = 90

function setup() {
    createCanvas(canvasWidth, canvasWidth)
    noFill();
}

function deterministicRandom(index) {
  return 1000 / 3 * noise(index) - 90
}

function draw() {
    background('#fd7');
    translate(0, scrollPosition--);

    for (i = 0, y = 0; y < canvasWidth - scrollPosition; y += yOffset)
        for (x = 90; x < canvasWidth - margin;) {
            if (y + margin > -scrollPosition) {
                curve(
                  deterministicRandom(i++) + x, 
                  deterministicRandom(i++) + y, 
                  x, 
                  y + yOffset, 
                  x + 9, 
                  y + yOffset, 
                  deterministicRandom(i++) + x, 
                  deterministicRandom(i++) + y
                )
            } else {
                i += 4
            }
            x += (9 + deterministicRandom(i++) % 9)
            if (noise(x * y) < .13)
            {
              y += 2*yOffset
              x = margin
            }
        }

}

The most important part of the code is the function deterministicRandom() which is used a lot of times in the sketch. It’s basically noise() but mapped to range [243, -90]. p5 js curve() takes in 2 control point and 2 physical point coordinate to determine the location and shape of the curve. Each character is is thus a set of 4 deterministically random numbers for control points + 4 constants for physical points. All of these points are offset by a base <x,y> coordinate to place the curve in a line. Because it’s deterministically random, the shapes and location of the curves are preserved in every frame.. making the infinite scroll effect work.

The 2 loops iterate over x and y, at a constant rate. x by 9 pixels and y by 24 pixels. But, inside the loop, based on deterministic random, x is randomly incremented by up to 9 pixels to simulate the randomness in spaces between characters. Also, if for a random condition with somewhat low probability (noise(x * y) < .13), a line-break is added. Which means, y is incremented thrice in that loop and x is reset to a margin value (90).

Camera motion and line by line rendering

The infinite scroll effect is basically done using translate(0, scrollPosition--). The loop termination clause is adjusted such that only lines within the frame are rendered (between y = scrollPosition to scrollPosition+canvasHeight). The condition y + margin > -scrollPosition directly inside the loop checks for this. This also offsets the random number index to the one needed by the lines being rendered in the else case. Here’s a version of the script that shows lines being rendered as the script runs:

And that’s basically it. The initial version I designed rendered every line from the first scroll position to the last in every frame, even if those lines were not visible. This is terrible for performance and the if condition inside the loop fixed this.

Minifying

Step one of minifying was converting all the functions to arrow functions. This took up way less space. Then I moved all the setup() stuff to draw(). p5 does not re-execute createCanvas even if you place it in draw(). Then I had to cut down number of variables as much as I can. 2 of them were reused: canvasWidth(w) and margin(k) were also used as a coefficient in deterministicRandom(). Finally spaces were removed and long names were truncated to single characters.

Conclusion

This script was written in about 2-3 hours. Looking back, I can see a lot of places where I’d try to reduce repeated code and make it smoother. I never thought this would go so popular, so I never really cared to optimize so much. But there you go.. a simple way to generate bad handwriting :)



Written by Atul Vinayak on 18 September 2020