Radial gradients have long been a staple of vector graphics. They allow for all sorts of interesting effects, like highlights. However, there are some subtleties to them. This has led to differences between SVG, Canvas, CSS and PDF. And recently there has been some discussion on what the upcoming SVG 2 specification should allow, so time to take a closer look.
First of all, what is a radial gradient? The easiest way to think about it is to imagine a whole bunch of circles being drawn. Conceptually you start at t=0 with a circle with radius r0, colour c0 and centered at (x0,y0), and end at t=1 with a circle with radius r1, colour c1 and centered at (x1,y1). These values are all linearly interpolated to yield intermediate circles:
By convention we consider circles with higher values of t to be above those with lower values of t. This can give the impression of a 3D tube or cone:
Also, in many cases there is support for extending the circles for negative values of t and values of t larger than one:
In these images the first and last circle have equal radius, resulting in a kind of tube. In the second image the tube has been extended, repeating the colour gradient for values of t outside the interval 0-1. For example, the circle for t=-0.8 has the same colour as the circle for t=0.2, and that for t=1.2.
Seems pretty straightforward, doesn’t it? Well, not quite. First of all, there is an obvious choice with respect to the kinds of extensions allowed. But also, more fundamentally, how much freedom one has in specifying the two circles between which we interpolate. There are some technical issues with not enforcing the starting circle to be fully contained in the ending circle.
Here is table showing some of the different choices that have been made:
|Format||Start outside of end||Extension|
|SVG (2)||No.||Always, using pad, reflect or repeat.|
|Canvas||Yes.||Always, using pad.|
|CSS||No (always at the center).||Always, using pad or repeat.|
|Yes.||At start, end, neither or both, using pad.|
So what is the problem with a starting circle outside the ending circle? (Or vice-versa!) First of all, it is not so much about the starting circle that needs to be contained in the ending circle, it is about the point where the radius is zero: the zero-radius point. This is most easily pictured by imagining the circles building a 3D cone (with t being the 3rd dimension), if the point where the radius is zero is inside the starting and/or ending circle we only see the top-half of the (double) cone, while if the point where the radius is zero is outside the ending circle we’re looking down on the side of the double cone (for simplicity, we will just talk about being inside or outside the ending circle, but in principle any circle on the gradient will do):
It should be noted that here no special signifigance is given to negative radii, but in most (if not all) current specifications radii are restricted to positive values. This results in just a (single) cone, instead of a double cone. In the image below, from left-to-right: a double cone with the starting radius smaller than the ending radius (both assumed positive), a double cone with the starting radius larger than the ending radius and a (single) cone restricted to positive radii (it is left as an exercise to the reader to figure out what happens when the starting radius is negative and the ending radius positive, or vice versa).
In principle the above is no problem. However, if the zero-radius point is very close to the ending circle, the gradient can quickly switch from one behaviour to the next. In other words, it is unstable:
In the left-most image we see that the zero-radius point (at the intersection of the two lines) is inside the ending circle, with negative values of t giving circles inside the starting circle and t>1 giving circles outside the ending circle. In the middle image, where the zero-radius point is on the ending circle, positive radii map to the left half of the image and negative radii map to the right half. In the third image the zero-radius point is no longer contained in the ending circle, and the plane will no longer be completely filled (you will see the outlines of the double cone).
If we imagine moving the zero-radius point from within the ending circle to outside the ending circle, we see the following:
- First the plane is mostly filled by circles with large values of t, all with positive radii.
- Just before the zero-radius point touches the ending circle the right half of the plane is filled with circles that are arbitrarily closely packed (so it makes sense to render the average colour of all circles with t>1)
- As soon as the zero-radius point is on or outside the ending circle we get into a regime where positive radii form a cone on the left half of the plane, while negative radii form a cone on the right half (initially filling both half-planes entirely).
Examples of the “inside” and “outside” regimes (spread method is repeat, the unextended gradients are superimposed over the extended gradients):
SVG appears to be the only specification dealing with this problem, and it does so by specifying that the zero-radius point must be contained within the ending circle. The advantage of this approach is that it can be specified that if the zero-radius point just touches the ending circle (or is even outside it), it should simply behave as if it was very slightly within the ending circle. On the other hand, having the zero-radius point outside the ending circle is essentially just as easy: negative radii map to a cone in one half-plane, positive radii to a cone in the other.
Several possible scenario’s for allowing the zero-radius point to not be contained within the ending circle come to mind:
- Let the author specify what behaviour should be assumed (inside or outside).
- Let renderers interpolate between the two behaviours. Conceptually the renderer would draw the same gradient many times, with slightly varying radii/positions, and take a weighted average of the results.
- Allow specification of the starting and/or ending radius (in principle the problem is “symmetric”) as a percentage/fraction of the distance between the starting center and ending circle. 100% would then correspond to the starting circle touching the ending circle exactly. Essentially this puts the responsibility
With one of the above schemes, or some other creative solution, SVG 2 and other future specifications should be able to provide a maximum amount of flexibility, while avoiding the pitfalls of allowing the zero-radius point to be on the starting/ending circle. It should be emphasized that it is the zero-radius point (and not the starting circle) that should be contained in the starting and/or ending circle (if it is contained in one, it is also contained in the other), and that the problem is not with the zero-radius point being outside the circles, but rather with it being on (or very close to) them.
Finally, it should be noted that there are some further opportunities for generalizing radial gradients. For example, CSS 4 might have conic(al) gradients, which essentially vary colour over angle rather than t. It would even be possible to create hybrid “spiral” gradients. Also, there is no particular reason to restrict yourself to circles, the mathematics are virtually just as easy for ellipses (especially with axis-aligned radii).