<canvas> element. This time I randomly generated images resembling the cover of the album by Joy Division called “Unknown Pleasures”.
According to Wikipedia, this somewhat iconic album cover is based on radio waves. I saw a poster of it in a bar not long ago and decided to reproduce the next time I had some time to kill.
First of all let’s set up a black canvas - I used the same dimensions as the image above.
Next I manually guessed some margins.
I squinted my eyes to count the number of lines on the album cover and I believe there are 80 of them. Based on the height and the number of lines the distance between each line is simply . The same goes for the width and desired number of points on each line.
My “strategy” is to use two nested loops to iterate each line one by one. For example the following snippets draws
nLines lines evenly spaced by
dy each consisting of
nPoints points which are themselves evenly spaced by
dx. Essentially this just requires updating the and coordinates respectively by
dy. When a line ends the
x variable resets to
xMin in order to start a new line.
The randomness in the album cover comes from the coordinate. For example by adding values obtained from a uniform distribution in - this is easily done by replacing
ctx.lineTo(x, y) by
ctx.lineTo(x, y + Math.random()) - the lines start to jitter around.
Of course the noise in the album cover is not uniform at all, in fact each line has multiple “peaks” which points the way towards multimodal distributions. It seems that each line has one or more peaks, moreover some of the peaks are skewed towards the left or the right. Although the distributions don’t seem to be normal, using a mixture distribution composed of multiple normal distributions seems to be a reasonably good start.
To obtain values for we have to use the probability density function – in short PDF – of each normal distribution. Basically for each the PDF will tell us the associated value . To make the process different per line we can sample a mean and a standard deviation from two normal distributions – one for each parameter – that we can then inside another normal distribution whose PDF will be used to generate values for .
randNormal function will generate values centered around 0 with a standard deviation of 1. The reason why this technique is not popular is because the values it produces will necessarily be in range – because of the sum. The 6 is arbitrary and any value higher would increase the precision of the method while any lower value is not recommended. Because we are procedural generation we don’t really care about the precision of our methods, in fact noise and imprecision is welcome!
randNormal can be extended to sample from a normal distribution with mean
mu and standard deviation
Now we can generate a normal distribution with random parameters for each line and obtain each thanks to the PDF of the generated normal distributions.
sigma are sampled from normal distributions with manually chosen parameters.
mx is half the length of each line. The 1000 is also arbitrary and is simply there to rescale the values returned by the PDF so that they are meaningful, again this is done manually.
We’re getting somewhere! However one issue that has nothing to do with randomness is that the lines sometimes overlap whereas they do not in the album cover. One way to solve this is to draw black pixels under each line to “cover up” the previous lines. This is quite easy to do with the
fill method. At the end of each line we fill in all the area under the current line in black, then we paint the current line in white before finally moving to the next line.
This procedure gives a satisfying 3D feel to the image.
Now all we have to do is generate more resembling lines. As I mentioned the lines have multiple peaks and using a multimodal distribution could do well. To generate an arbitrary multimodal distribution one may use a mixture distribution, which is nothing more than sum over two or more distributions. For example the following figure – obtained from the Wikipedia page on mixture distributions – shows how summing three normal distributions results in a new distributions with three modes.
Producing a mixture distribution is not too complex code-wise. For each line we want to generate a multimodal distribution with a random number of modes, thus we can first of all assign a random integer to a variable called
nModes to indicate how many modes we want. Then we can generate
nModes values for and for just as we did previously. Then we can sum returned by the PDF of each distribution and use the sum as a value for .
Again we are getting closer. Now probably should work on some noise. Instead of adding some random as we did previously we could make the noise be proportionate to . After some tinkering I found that the following worked quite well.
At this moment I was quite happy with the result. However, after looking at a few runs I had a feeling that the lines oscillated too much and that they should be a bit smoother. The thing is that for each value of we produce a value for that does not take account the preceding value. I decided to use a basic smoothing function so each value would take into account the previous value . In the end I settled on doing . Implementing this rolling mean only requires the last value of at each iteration; I settled on the following code which does just so.
And that wraps it for this post, I hope you enjoyed it! As usual the final is available on GitHub with the rest of procedural generation I did.
On a side note I discovered the r/proceduralgeneration subreddit which I recommend if you’re interested in procedural generation – albeit more advanced than what I do.