I'm working on a larger generative art project, and one thing that I need for this is an abstract representation of something that could conceivably be eaten by an ant. While ants will apparently eat almost anything, I decided to use flowers to represent their food since pictures of decomposing fruit didn't end up looking all that appealing.

This post is a quick tutorial on how I generated these flowers using simplex noise. The code to generate this ended up being just a couple of lines of Typescript, and a whole range of different abstract flowers can be generated by changing just a few parameters. I've included the code below along with an interactive visualization to explain how this all works.

Even though the code is pretty simple, the output still looks pretty cool:

All the code is up on GitHub if you want to dive deeper into this, but I'm going to go through all the interesting bits here.

Randomly Deformed Circles

The key idea here is to represent a flower by a bunch of randomly deformed overlapping circles, with each circle being randomly modified using simplex noise.

Simplex noise and Perlin noise booth smoothly interpolate between certain points with each of these points getting a value chosen at random. Perlin noise does this by picking these points on a grid, but simplex noise instead uses a series of n-dimensional triangles. This looks like a grid of triangles in a two-dimensional space, a grid of triangular pyramids in a three-dimensional space and so on. Simplex noise ends up being faster to compute than Perlin noise, while also avoiding some of the artifacts that can be found in Perlin noise.

To do these deformations, I'm just sampling a bunch of points at regular intervals around each circle. At each point, I generate a random value using the open-simplex-noise package and then add that value to the radius of the circle at that point.

An implementation in TypeScript that draws these deformed circles onto an HTML canvas context looks like:

import OpenSimplexNoise from "open-simplex-noise";

const noise = new OpenSimplexNoise(40);

export function drawDeformedCircle(ctx: CanvasRenderingContext2D,
                                   circle: {x: number, y: number, radius: number},
                                   frequency: number,
                                   magnitude: number,
                                   seed: number = 0): void {

        // Sample points evenly around the circle
        const samples = Math.floor(4 * circle.radius + 20);
        for (let j = 0; j < samples + 1; ++j) {
            const angle = (2 * Math.PI * j) / samples;

            // Figure out the x/y coordinates for the given angle
            const x = Math.cos(angle);
            const y = Math.sin(angle);

            // Randomly deform the radius of the circle at this point
            const deformation = noise.noise3D(x * frequency,
                                              y * frequency,
                                              seed) + 1;
            const radius = circle.radius * (1 + magnitude * deformation);

            // Extend the circle to this deformed radius
            ctx.lineTo(circle.x + radius * x,
                       circle.y + radius * y);

Since we want the circle to tile properly, I'm passing the x/y coordinates instead of the angle into the simplex noise function. Each individual circle only requires 2D noise because of this; however, I'm using 3D noise here to allow for each circle in the flower to get its own slightly different random disturbances.

There are two parameters here that control how the output looks. The frequency parameter scales the coordinates passed to the noise3D call, with larger values leading to more random peaks. The magnitude parameter controls how big each of these peaks are.

By tweaking these two parameters you can control how deformed a single circle will be:

While this is all sort of interesting, the real fun begins when you start stacking multiple circles on top of each other.

Stacking Circles into Flowers

To generate the final image, I'm just calling that drawDeformedCircle function in a loop - passing a slightly different radius and seed value each time.

export function drawFlower(ctx: CanvasRenderingContext2D,
                           circle: {x: number, y: number, radius: number},
                           frequency: number = 2.0, magnitude: number = 0.5,
                           independence: number = 0.1, spacing: number = 0.01,
                           count: number = 200): void {
    // adjust the radius so will have roughly the same size irregardless of magnitude
    let current = {...circle};
    current.radius /= (magnitude + 1);

    for (let i = 0; i < count; ++i) {
        // draw a circle, the final parameter controlling how similar it is to
        // other circles in this image
        drawDeformedCircle(ctx, current,
                           frequency, magnitude,
                           i * independence);

        // shrink the radius of the next circle
        current.radius *= (1 - spacing);

There are two extra parameters there. The spacing parameter controls how close each circle is to one another. Spacing is done by exponential decay, with larger values indicating that the circles should be farther apart. The independence parameter is passed to the noise3D function and controls how similar each circle is to another: passing a value of 0 means that each circle will have exactly the same random deformation applied to it and leads to a series of concentric circles being drawn out, while passing in larger values will have each circle change more from the circles next to it.

With only these 4 parameters you can generate a whole bunch of different possible looking graphs:

The code here is pretty simple but I still like how the output of this all looks. I've added everything here to GitHub which also has the code for the interactive visualizations, as well as the code to generate the sample image at the top of this post.

Published on 22 March 2018

Get new posts by email!

Enter your email address to get an email whenever I write a new post:

  • Follow me on: