DEV Community

Bohdan Stupak
Bohdan Stupak

Posted on • Edited on

Crafting generative art with javascript generators

Apart from career programming and management tasks, I spend my free time doing creative coding. Most of it is programming music with SuperCollider but recently I was also involved in some visual art.

Initial project structure

The idea behind the project is to study the relation between colors by randomly morphing them into each other. The program which does this is the array of program frames which are the color and number of frame executions. In the initial iteration, the capacity of the array is 5.

function generateProgram() {
    var numberOfFrames = 500;
    var numberOfIterations = 5000;
    var previousIteration = 0;
    var program = [];
    for (var i = 0; i < numberOfFrames; i++) {
        var currentIteration = randomInt(numberOfIterations) + previousIteration;
        program[i] = {
            color: randomHsl(),
            latestIteration: currentIteration
        }
        previousIteration = currentIteration;
    }
    return program;
}
Enter fullscreen mode Exit fullscreen mode

We're generating colors in HSL format since random colors in RGB look pretty similar and the result wouldn't be as pleasing aesthetically.

Program execution is the infinite loop that selects a couple of random points on the canvas and blends them with the color of the current program frame. Once we've reached the target number of iterations we switch to the next program frame.

function executeProgram(program) {
    var points = getInitialCanvas(program);
    drawOnCanvas(points);
    var iteration = 0;
    var currentProgramFrameIndex = 0;
    var currentProgramFrame = program[currentProgramFrameIndex];
    function run() {
        setTimeout(function() {
            for (var k = 0; k < 10; k++) {
                window.requestAnimationFrame(run);
                iteration++;
                var i = randomInt(height)-1;
                var j = randomInt(width)-1;
                points[i][j] = blendColors(points[i][j], currentProgramFrame.color);
                if (iteration == currentProgramFrame.latestIteration
                        && currentProgramFrameIndex !== program.length - 1) {
                    currentProgramFrameIndex++;
                    currentProgramFrame = program[currentProgramFrameIndex];
                }
                drawPointOnCanvas(i, j, points[i][j]);
            }
        }, 1);
    }
    run();
}
Enter fullscreen mode Exit fullscreen mode

We can observe that we're instructing the browser to schedule animation using window.requestAnimationFrame method. This method accepts a callback which will be executed when it is time to paint the animation. So by calling run method as the callback we're effectively creating the infinite loop.

In order for our infinite loop not to block repainting of the canvas, we call each iteration inside the setTimeout function.

Using generators

The obvious downside of this approach is that we have a limited amount of frames. Instead, we want our animation to last forever. One of the possible ways is to provide each new program frame via generators

Changes are pretty straightforward. Instead of generating the program as an array of frames, we define a generator function that will return a new frame when requested.

function* generateProgram()  {
    while (true) {
        var numberOfIterations = 5000;
        var program = [];
        var currentIteration = randomInt(numberOfIterations)
        program = {
            color: randomHsl(),
            latestIteration: currentIteration
        }
        previousIteration = currentIteration;
        yield program;
    }
}
Enter fullscreen mode Exit fullscreen mode

To request the frame we instantiate the generator object by calling the function and then calling next method.

var programGenerator = generateProgram()
var program = programGenerator.next().value;
Enter fullscreen mode Exit fullscreen mode

Now we can request each program frame, once the previous is completed.

function executeProgram(program) {
    var points = getInitialCanvas(program);
    drawOnCanvas(points);
    var iteration = 0;
    function run() {
        setTimeout(function() {
            for (var k = 0; k < 10; k++) {
                window.requestAnimationFrame(run);
                iteration++;
                var i = randomInt(height)-1;
                var j = randomInt(width)-1;
                points[i][j] = blendColors(points[i][j], program.color);
                if (iteration == program.latestIteration) {
                    program = programGenerator.next().value
                }
                drawPointOnCanvas(i, j, points[i][j]);
            }
        }, 1);
    }
    run();

}
Enter fullscreen mode Exit fullscreen mode

Sidenote: Fixing the memory leak

When you pay attention to this program long enough you might notice that it starts consuming more and more memory.

Upon examining memory profile in my Firefox devtools I can notice that the primary source of allocation is setTimeout function.

Memory profile

Turns out that calls to requestAnimationFrame are stacked to map of animation frame callbacks. And if the browser is not keeping up with the growth of callback's map it's starts growing up in size consuming more and more memory.

So the fix is as simple as increasing timeout duration.

Top comments (0)