Has this happened to you? You want to paint a very subtle gradient in the background of your canvas application over a large area, and it ends up looking “banded” with obvious stepped changes in colors from start to finish. But you wanted something smoother, and you know you’ve seen smoother gradients on a computer screen before! What you need is a dithered gradient. I’ve created a JavaScript class for this purpose, which makes use of the Floyd-Steinberg dithering algorithm. Click here or on the screenshot below to see a much larger dithered gradient generated in real time, next to a standard canvas gradient for the sake of comparison. Then grab the code below for your own canvas applications, and read my comments below for more information.

Download

Download the full commented source code here: DitheredGradient.zip

Using the code

The code which creates the smooth gradient is contained in a file DitheredLinearGradientRGB.js, and it is easy to use in your own applications. You can download the above zip file which contains both this JavaScript file along with an example html file showing the gradient in use. Here is the code in the main html file which creates and displays the gradient. As you can see, it is very similar to the standard method of creating a gradient in the canvas (see here for a reference for standard canvas gradients.)

var dgrad = new DitheredLinearGradient(0,0,displayWidth,0); dgrad.addColorStop(0,40,40,40); dgrad.addColorStop(0.8,0,0,0); dgrad.addColorStop(1,30,15,5); dgrad.fillRect(displayContext,0,0,displayWidth,displayHeight/2-1);

Note that a color stop is added to the gradient with a ratio and RGB color values as follows:

dgrad.addColorStop(ratio,red,green,blue);

Unlike a standard canvas gradient, this DitheredLinearGradient cannot be set as the fillStyle for the canvas. Presently the only way to draw the gradient to the canvas is to fill a rectangle with it using the fillRect method of this class. If you want a different shape you can accomplish this by first filling a rectangle, then cut out the shape you want by drawing more elements to the canvas using the "destination-in" composite operation.

Since the gradient is computed pixel-by-pixel, use of this code is not recommended when speed is of great concern. But it is fast enough to generate a gradient once for a background.

About the code

This issue came up recently for me when I was creating some generative art from a previous experiment. I reshaped some of my earlier sweeping fractal lines images into abstract jellyfish images (see the results here, although the jpg compression damaged them a bit!), and I decided I wanted a subtle dark blue-to-black gradient behind them. But it was too subtle; the color changes were too small over a wide area. A gradient going from #000022 to #000000 only involves 34 different colors, and if this is spread out over a wide area, you can definitely see the individual colors of the gradient in wide bands. I did a little searching on the internet about dithering, and found that a frequently used error diffusion dithering algorithm is Floyd-Steinberg dithering.

I can’t say I understand all of the perceptual science behind the precise parameters of this algorithm, but it was easy enough to implement in code. The dithering algorithm is applied when an image needs to be downsampled to a color palette with fewer colors than the original. In my code for the smooth gradients, a “pure” float-based gradient is created within the code, then each color in this pure gradient is converted to the nearest available 8-bit color, while applying Floyd-Steinberg dithering to make the transition between colors more seamless.

More to come!

These gradients do not allow for alpha variation. The dithering algorithm just doesn’t produce nice gradients if alpha is also changing over the gradient. Perhaps this will require a little more investigation and experimentation.

It is rather simple to modify the code to create radial gradients; one simply needs to change the way the colors in the float-based gradient are computed based on coordinates. And since the colors are computed based on coordinates, this opens up the possibility for lots of creative nonlinear gradients. Watch this blog for more smooth gradient examples!