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!
Dan,
I love your generative artwork except that you beat me to the punch with your cubic-spline shaded braids. (I was oh-so proud of myself for a dirt-simple translation of a scrolling 10 PRINT to canvas/javascript.) Yeah, yours is only prettier, smoother, and cleverer, which is OK if you like that sort of thing. 😉
About dithering: Floyd-Steinberg dithering is a decent solution for your smooth gradient and it was all the rage back in the 2-bit to 8-bit graphic days of PCs, especially on the early B&W Macs: http://i.udm4.com/screenshots_u4mac/87/87328_2.png. The best explanation I’ve heard for it is that it spreads the current pixel’s error to nearby pixels to preserve overall tone of the local area. (http://www.visgraf.impa.br/Courses/ip00/proj/Dithering1/floyd_steinberg_dithering.html)
I’m wondering, though, if F-S is overkill here. Couldn’t you do a simple ordered dither or random dither (see reference above)? By definition, your neighboring gradient colors are close together so any checkboard pattern would be hard to see but still break up the gradient bands.
Also, if you wanted to implement transparency I’d bet you don’t need to dither alpha at all. Just dither the RGB and bands in alpha wouldn’t really be noticeable, I’m guessing.
February 19, 2013 @ 10:53 pm
|Hi S!
Thanks for your comments!
Well, this is probably a very impractical example…I guess I just do these coding experiments and put them out there without always thinking about applicability!
You know way more about this than I do…now that I think of it, I’m not sure how I happened upon Floyd-Steinberg as a solution. Probably did some googling and wiki reading or found a discussion of it used for a gradient somewhere and then thought I’d give it a try. Now that you pointed me towards to those other methods I’m eager to give them a try. When I have some more time I’ll do more experimenting.
One thing I did try was to compute a dithered gradient in one thin slice, and tile this across the canvas. It worked pretty well. So I think I’ll update this at some point. I also worked out the computation for a radial gradient and I’ll share that when I get a chance.
And I’m glad you like my work and the braids. I’m sure I’m not the first one to think of drawing braids like I did…I was even afraid to google it before I got started, knowing that it must already be out there somewhere! Of course, I kind of ruined the minimalist aspect of the 10 Print idea by drawing gradients, storing information in arrays to avoid certain crossing pairs, etc…I couldn’t help myself.
Thanks again for stopping by, and for sharing some thoughts on dithering. I’ll be sure to look into these algorithms more deeply.
February 19, 2013 @ 11:52 pm
|Hi,
Your work is really beautiful! Most of all I love your jellyfish.
Is there maybe any tutorial of it, how it is to create (or maybe download)?
March 1, 2016 @ 2:27 pm
|Hi Sandro,
I’m really glad you like my work! The Jellyfish is based on the code here: http://rectangleworld.com/blog/archives/538. But I would like to keep the particulars of the jellyfish to myself. In short, I use some mathematical tricks to warp the lines into the jellyfish shape. But you might have some fun modifying the code for other results.
Thanks for your note!
March 25, 2016 @ 10:02 pm
|