Download the full commented source code here: DitheredGradient.zip
Using the code
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:
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!