[whatwg] Canvas - createRadialGradient

For radial gradients, I've been looking into what happens when the two
circles that define the gradient are not fully overlapping (i.e. not
one entirely inside the other) - this appears to not be implemented or
specified accurately.

I'm assuming that an insignificant number of people depend on the
precise behaviour in any current browser for these radial gradients,
so it won't hurt to make changes where there's currently no
interoperability. (Please point out if I'm wrong.)


Considering the interesting implementations:

Firefox does rendering with various versions of Cairo, which seem to
differ a lot:
 Firefox 2.0.0.3 is sensible when (but only when) the start circle is
entirely inside the end circle.
 Gran Paradiso Alpha 3 is totally broken in every case.
 Gran Paradiso Alpha 4 is always understandable. [...at least in the
cases I've tested.]

Safari (2.0.4, AppleWebKit/419) is almost (except for one case) always
sensible. A recent nightly version (AppleWebKit/522+) is similar but
slightly less sensible.

Opera (9.20) is broken in every case (in at least two ways), so I'll
ignore what it does.

The spec (as of 2007-05-04) is sensible but wrong and possibly
underspecified, so I'll ignore it too.


See the examples at
http://canvex.lazyilluminati.com/misc/radial/examples.html - the start
circle is green, the end circle is blue, and the two circular outlines
show their positions. (The last case defines the two stops at 0.3 and
0.7 instead.)


Compatibility is achieved in the case of r0 < r1 with the start circle
being completely within (and not touching) the end circle; so that one
is easy.


Negative radius values don't sound very useful or sensible to me, and
they're not implemented in a useful or compatible way, so I think they
ought to be handled as either:
* Always take the absolute value of r0 and r1.
or
* Throw INDEX_SIZE_ERR when passed a negative r0 or r1 (for similarity
with arc).
I don't know which would be better. I'll guess the first for now,
since it matches Firefox.


For the touching-at-the-edge case, it is just on the edge between the
easiest case (r0 < r1, start inside end) and a weird incompatible
case. Safari and FF3 have a weird compatible behaviour that doesn't
match the cases on either side of this edge case. FF2 has a sensible
and useful behaviour, but implemented a bit buggily. Possibilities to
specify:

* Safari/FF3 behaviour - replace the entire gradient with the start
colour. Not very useful or intuitive, but it's easy enough and it
still works when the two circles are exactly the same size and
position.

* Corrected FF2 behaviour - treat it like it's nudging the smaller
circle inwards a tiny bit, so it's handled like the easy
smaller-circle-inside-larger case. But then it needs another special
rule for the same-size case (because you can never fit one circle
entirely inside the other, and there's no intuitive behaviour there,
but there is a reasonable degree of compatible behaviour already).

For now, I'll stick with the first definition since it's a bit simpler.


For the other cases, I believe Safari's behaviour is generally the
best. In particular:

* For the r0 > r1 case, Safari has a sensible and useful behaviour
(i.e. draw the gradient between the two circles, without caring which
order they were defined in) and it's compatible with FF2 too. FF3's
behaviour is not useful, but I expect it can be fixed easily (e.g. by
swapping the start/end circles before passing them to Cairo).

* For the non-totally-overlapping cases, I don't think any behaviour
is inherently obvious. There's nothing particularly wrong about
drawing an infinite cone (compared to e.g. clipping to one circle, or
clipping to the two circles plus the truncated cone joining them), and
it's easy to define, and it kind of matches the current spec. I can't
imagine FF3's behaviour being especially useful (since if you want a
linear gradient in the background, you could just draw one). Safari
and FF3 should each be able to implement the other's behaviour fairly
easily (by calculating and drawing a linear gradient, or by
calculating a triangle and clipping - I believe the geometry isn't
very complex). Only Safari has already shipped a sane behaviour, so
I'd go with that one.

~~~~

Independent of all this, there's the issue of the colour outside the
defined stops.

In old Safari, there is a solid black stop at 0 and at 1, but they are
overwritten if you define a stop at those points, and the outermost
stop colours carry on forever. In new Safari it has changed to
something similar but seemingly broken (though I haven't tried finding
out exactly what it's doing).

In Firefox (2 and 3), there are no implied stops, and the outermost
stops carry on forever.

In Opera and the spec they are transparent black just outside 0 and 1,
but I said I was ignoring both of those.

As with linear gradients
(<http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2007-April/010743.html>),
Firefox's behaviour is the most powerful and sensible (in my opinion);
and it removes the need to explicitly define different behaviour
outside the 0..1 range for radial gradients compared to linear. That
behaviour is still compatible with any Safari-only content which adds
stops at 0 and 1. I'll assume that behaviour for simplicity.

~~~~

So, I would suggest defining something like:

<<<<
Once a gradient has been created (see below), stops must be placed
along it to define how the colors are distributed along the gradient.
Between each such stop, the colors and the alpha component must be
linearly interpolated over the RGBA space to find the color to use at
that offset. Before the lowest stop, the color of the lowest stop must
be used. After the highest stop, the color of the highest stop must be
used.

[Clarified to "linearly interpolated". Changed what happens outside
the range of the stops - since this text is shared with linear
gradients, I'm assuming they are both changed to be the same.]

...

The createRadialGradient(x0, y0, r0, x1, y1, r1) method takes six
arguments, the first three representing the start circle with origin
(x0, y0) and radius r0, and the last three representing the end circle
with origin (x1, y1) and radius r1. The values are in coordinate space
units. The method must return a radial CanvasGradient initialised with
those two circles.

If either of r0 and r1 is negative, it must be replaced by its
absolute value before rendering.

[I'll assume the function's responses to non-finite values will be
addressed elsewhere.]

Radial gradients must be rendered as follows:

* If one of the start circle and end circle is enclosed within the
other circle, and their circumferences touch in at least one point,
then the gradient must be rendered as the color at offset 0.

* Otherwise, a cone or cylinder must be created from the circles, such
that at the circumference of the starting circle the color at offset 0
is used, that at the circumference of the ending circle the color at
offset 1 is used, that the circumference of a circle drawn a certain
fraction of the way along the line between the two origins with a
radius the same fraction of the way between the two radii has the
color at that offset (interpolation happening as described above) for
all offsets such that the radius of the circle at that offset is
positive; that the end circle appear to be above the start circle, and
that any points not described by the gradient are a transparent black.
>>>>

which I believe matches the implementation under the "Proposed spec"
column in the earlier example page.

I'll probably try writing some test cases using this suggested
behaviour for now, and fix them later to whatever the spec eventually
says.

-- 
Philip Taylor
excors at gmail.com

Received on Friday, 4 May 2007 13:39:33 UTC