Doodle Insights #3: Procedural Dithering (part.2)

The Doodle Insights #2 were about the basics of procedural dithering. Today we’re looking at more specific uses of this technique. It will be assumed that you read the Doodle Insights #2.

Now that you know the basics of procedural dithering, we can dive into slightly more complex practices. This Doodle Insights will be the second part on procedural dithering, before the third and last one next week.

So today we will look at how to get more refined effects by playing with random, then we will see a few performance tricks.

Last week, I wrote that procedural dithering was done by selecting random pixels on the screen and coloring their surroundings. And then I went on to describe three ways of getting data for the colors to use. But that color can also be randomized to give more complex effects.

Take this promo gif I did for Pixel Session Vol.1, based off the Red Dead Redemption 2 announcement:


If you compare the top of the screen and the lower part that is still red, you will notice that the higher you are on the screen, the less bright red pixels appear. Here, when getting any random pixel that is higher than the black ground and outside of the sun, its color is bright red. But then the following operation happens:

if rnd(2)>(y+10)/96 then

This gets a random value between 0 and 2 and then compares it to the y coordinate, reduced to a range of 0 to 1. If the random value is higher than the reduced y coordinate, the color is made darker. This way, when this happens at the top of the screen where y is nearing 0, the red is always darkened. When it happens at the bottom of the screen, where the reduced y is nearing 1, there is one chance in two that the color will be darkened. And the rest of the screen is a gradient of probability between these two states.

In yesterday’s doodle, ‘Sun’, something similar is happening in the background but this time, the distance to the sun’s center is compared to a random value instead of the y coordinate. The color chosen is then either yellow or blue.

Pico-8 Doodle #59

A much simpler application of using random to define the color is to have a small chance of making a different color happen. In this next example, when the selected pixel is outside of the text (which is stored on a second surface, the sprite-sheet) the color is defined as black, except when ‘rnd(20)<1′, and then the color is defined as red.

Pico-8 Doodle #47

(the color changing effect on that doodle is done using palette swapping at screen level, with the third parameter of the pal() function set to ‘1’)

But don’t stop at evaluating coordinates and distances, you could make your randomization depend on other elements of your creation, maybe gameplay elements if your creation is a game! The possibilities are numerous!

Now, while processing 1000 pixels independently at each frame can be working way more smoothly than one would have thought, if you complexify this processing a lot or that you need your CPU for something else, you might want to optimize this procedural dithering.

The most simple way to do this is to process less pixels each frame. I generally take exactly 1000 pixels each frame but maybe your effect will work with only 500, or even 200 each frame. You really have to try to know, sometimes it works ok, sometimes it doesn’t. However, I find 1000 to be a good value, as it will effectively treat 99% of the screen in just a few frames, while leaving the best part of the CPU for the rest of your creation. But in this next example, the background moves really slowly and other operations are very heavy on the CPU, so only 500 pixels are selected each frame.

Pico-8 Doodle #30+?

The second most simple way to optimize the dithering is to draw more pixels for each selected pixel. Last week I wrote that you should apply your color to the neighbor pixels of your selected pixel. This doesn’t have to be true.

Pico-8 Doodle #40+?

In the example above, the selected pixels are colored using ‘circfill(x,y,1,c)’.

In Charging Panic, one of the games in Pixel Session Vol.1, it is tiny squares that are drawn in the background, using ‘rect(x-1,y-1,x+1,y+1,c)’. Wider forms can also be used but the dithering will tend to appear much less subtle.

Finally, a very good way to optimize the procedural dithering is to optimize the determining of colors. Since optimization is often needed in cases where your color determining is more complex, this should be considered as your best option. Not for the lazy though.

In my experience, color-related optimization often comes with arrays. For example, if you want to use only a selection of colors in the Pico-8 palette and you are defining them with maths, that’s not very efficient. Instead you should define an array with your selection of colors and make your maths refer to the color you want in your array. Cool side-effect: you can change the colors super easily by modifying the array.

In this example, using an array to store the colors was absolutely essential:

Pico-8 Doodle #40+?

If you are using a part of your sprite-sheet as second surface which doesn’t change, and especially if you are scaling it up for your dithering (‘c=sget(x/16,y/16)’ for example), consider storing your second surface in a grid-like array construction. Here’s what I mean:

for x=0,7 do
 for y=0,7 do

Then, make your dithering refer to the correct indexes in this construction. ‘c=map[flr(x/16)][flr(y/16)]’ is much faster than ‘c=sget(x/16,y/16)’.
Optimizing your procedural dithering might take more time than you expect, but it is generally worth it. It’s easy to think that there isn’t any possibilities for further optimization but that’s definitely not true. Each situation has its unique opportunities and it is important to figure out how to use them.

Random can be used further into procedural dithering, up to the color selecting, and in a variety of ways. Be creative but stay subtle with it.
Optimization is also very possible and again in a variety of ways. But it is important that you find the opportunities of your own creation.

And that is all for this issue of the Doodle Insights! I went into more technical details in this one, so I hope I didn’t lose you halfway-through. :X

Most of the doodles here can be downloaded in the 45 Pico-8 Doodles pack and played on the Pico-8 BBS. A few of them (#46+) can only be downloaded by ‘Pico-8 lover’ patrons on Patreon for now but they will also be added to the free pack in a few weeks.

Doodle Insights #4 will be the third and last part about procedural dithering and it will be partly about mixing it with cellular automata and partly about making geometrical forms happen! And it should show up in your Patreon feed next Tuesday!

As usual, if you have comments, questions or suggestions, please leave them on the Patreon post, thank you! 😀

These Doodle Insights are here thanks to the awesome people supporting it on Patreon! Here are their names!

Joseph White, Adam M. Smith, Matthew, Tim and Alexandra, berkfrei, Nick Hughes, Christopher Mayfield, Jearl, Dave Hoffman, Thomas Wright, Morgan Jensen, Zach Bracken, Anne Le Clech, Flo Devaux, Emerson Smith, vaporstack, Dzozef, Cole Smith, Jared Butowsky, Tony Sarkees and Justin TerAvest!

Thank you for reading and enjoy the optimized dithering!


2 thoughts on “Doodle Insights #3: Procedural Dithering (part.2)

Add yours

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at

Up ↑

%d bloggers like this: