Read my book

I wrote books about Webpack and React. Check them out!

Friday, July 30, 2010

HTML5 Canvas Gradients - Radial Gradient Revisited

In this post I'm going to show you how to generate a radial gradient using HTML5 canvas pixel-wise API. As mentioned before the API actually includes a way to generate it without any fuss. My implementation differs a bit from the native gradient. It handles certain boundary cases in a different manner and is more generic. The approach could easily be extended to allow more complicated gradients to be rendered.

This time I will start out by explaining the math behind my approach first and move to the code after that.


Math

I decided to mimic the API of the original gradient as I found it quite versatile. This means that it should be possible to define a radial gradient by defining it using two circles and their colors. The gradient is then drawn using this information. The following image shows the basic idea:

In order to come up with a gradient, there has to be some way to interpolate between values. In linear gradients seen before this was quite easy. In the simplest case all we had to do was to vary interpolation factor based on some coordinate (ie. in order to come up with a horizontal gradient, the factor may be derived based on the x coordinate of the current pixel and its relation to the image width).

In this case the interpolation is done between two circles, innerCircle and outerCircle. "point" represents the location of current pixel being processed. I use this information to come up with intersection points (innerIntersection, outerIntersection) and then figure out the interpolation factor based on the point's relation to inner and the whole distance between those two intersection points.

I will show how this idea translates to some actual code next:

Code


As before I used a couple of library files and extended them to fit the demands of this particular task. You may find them here: graphics.js and math.js .

Here's the basic algorithm:



In addition to the idea above I decided to crop the gradient so that it's contained within the outer circle. Hence the check for containment (!outerCircle.contains(point)).

There is also a check that figures out which intersection points to use for figuring out the interpolation ratio. In the case of the image there are actually four. I devised simple logic for coming up with the right pair. Just picking the closest intersection of the inner circle and the farthest intersection of the outer circle compared to that does the trick.

Here's a sample render of the result:


Conclusion


Compared to the native radial gradient this implementation is slow. It does allow several interesting tweaks to be made, however. The basic idea should work with other shapes as well.