Bug 40918

Summary: Radial gradients misrendered if focus is exactly on the outer circle
Product: cairo Reporter: Krzysztof Kosiński <tweenk.pl>
Component: image backendAssignee: Carl Worth <cworth>
Status: RESOLVED MOVED QA Contact: cairo-bugs mailing list <cairo-bugs>
Severity: normal    
Priority: medium    
Version: 1.10.2   
Hardware: All   
OS: All   
Whiteboard:
i915 platform: i915 features:
Attachments: Reduced test case
Output of test case with angle=0.5
Test case 2 (big circle -> focus)
Simple implementation of the tolerance-based code

Description Krzysztof Kosiński 2011-09-15 12:24:24 UTC
Created attachment 51242 [details]
Reduced test case

When the focus of a radial gradient lies almost exactly on the outer circle of the gradient, the gradient randomly switches to a different mode of rendering, which results in an abrupt change of appearance. This makes it rather hard to clip the focus point to the outer circle of the gradient as required by the SVG specification.

Attached is a reduced testcase. It should render a green surface with a red gradient extending from the edge of the outer circle regardless of the value of the "angle" constant, but most of the time it actually renders only green with some transparency. For an example of correct behavior, set "angle" to zero.
Comment 1 Krzysztof Kosiński 2011-09-15 12:26:05 UTC
Created attachment 51243 [details]
Output of test case with angle=0.5
Comment 2 Chris Wilson 2011-09-16 08:06:19 UTC
I've taken the liberty of slapping a BSD-ish licence on your example and adding it to the test-suite:

commit f09361cff16776b49e9159c60eea4e8a049865d6
Author: Krzysztof Kosinski <tweenk.pl@gmail.com>
Date:   Fri Sep 16 15:59:38 2011 +0100

    test: Add radial-outer-focus
    
    Numerical instability in the computation of gradients leads to random
    results.
    
    Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=40918


Andrea, it looks like numerical instability, can you take a closer look?
Comment 3 Andrea Canciani 2011-09-16 08:47:40 UTC
(In reply to comment #0)
> Created an attachment (id=51242) [details]
> Reduced test case
> 
> When the focus of a radial gradient lies almost exactly on the outer circle of
> the gradient, the gradient randomly switches to a different mode of rendering,
> which results in an abrupt change of appearance. This makes it rather hard to
> clip the focus point to the outer circle of the gradient as required by the SVG
> specification.

In order to get consistent results ad to be able to follow the SVG
specification you probably want to render it using CAIRO_EXTEND_NONE or you
could cheat a little bit and move the focus slightly inside.
(I would rather go for CAIRO_EXTEND_NONE, but it's probably much more work
because you might have to recompute the big circle and the color stops)

> 
> Attached is a reduced testcase. It should render a green surface with a red
> gradient extending from the edge of the outer circle regardless of the value of
> the "angle" constant, but most of the time it actually renders only green with
> some transparency. For an example of correct behavior, set "angle" to zero.

(In reply to comment #2)
> I've taken the liberty of slapping a BSD-ish licence on your example and adding
> it to the test-suite:
> 
> commit f09361cff16776b49e9159c60eea4e8a049865d6
> Author: Krzysztof Kosinski <tweenk.pl@gmail.com>
> Date:   Fri Sep 16 15:59:38 2011 +0100
> 
>     test: Add radial-outer-focus
> 
>     Numerical instability in the computation of gradients leads to random
>     results.
> 
>     Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=40918
> 
> 
> Andrea, it looks like numerical instability, can you take a closer look?

The very definition of radial gradient is not well-behaved when its focus point is on the outer circle and the repeat mode is not NONE. This happens because when the focus point is (slightly) inside the outer circle, the radial gradient colors the whole plane, when it is exactly on the circle it colors exactly an half-plane and when it is outside it colors the region in an angle.
The focus point can obviously go from inside to outside and vice versa even moving by a very small amount (example: rounding error) when it is supposed to be exactly on the outer circle.
Unfortunately ill-conditioned problems and rounding errors hate each other :(
Comment 4 Andrea Canciani 2011-09-16 08:54:09 UTC
Created attachment 51265 [details]
Test case 2 (big circle -> focus)

Sorry, I just realized that maybe what you're actually reporting is that sometimes you do not see the red part, not that sometimes the gradient covers an half-plane, sometimes the whole plane.

This is caused by the order in which you specified the start and end circle.

If this is the case, can you have a look at the attachment?
Comment 5 Bill Spitzak 2011-09-16 10:33:51 UTC
Would it violate the SVG spec too badly if repeats other than NONE 
filled the entire rest of the plane with the repeated color all the 
time? Ie when the point is outside the circle all the points outside the 
cone shape are always colored the same as when the point is inside the 
circle.

bugzilla-daemon@freedesktop.org wrote:
> https://bugs.freedesktop.org/show_bug.cgi?id=40918
> 
> --- Comment #3 from Andrea Canciani <ranma42@gmail.com> 2011-09-16 08:47:40 PDT ---
> (In reply to comment #0)
>> Created an attachment (id=51242)
>  --> (https://bugs.freedesktop.org/attachment.cgi?id=51242)
>> Reduced test case
>>
>> When the focus of a radial gradient lies almost exactly on the outer circle of
>> the gradient, the gradient randomly switches to a different mode of rendering,
>> which results in an abrupt change of appearance. This makes it rather hard to
>> clip the focus point to the outer circle of the gradient as required by the SVG
>> specification.
> 
> In order to get consistent results ad to be able to follow the SVG
> specification you probably want to render it using CAIRO_EXTEND_NONE or you
> could cheat a little bit and move the focus slightly inside.
> (I would rather go for CAIRO_EXTEND_NONE, but it's probably much more work
> because you might have to recompute the big circle and the color stops)
> 
>> Attached is a reduced testcase. It should render a green surface with a red
>> gradient extending from the edge of the outer circle regardless of the value of
>> the "angle" constant, but most of the time it actually renders only green with
>> some transparency. For an example of correct behavior, set "angle" to zero.
> 
> (In reply to comment #2)
>> I've taken the liberty of slapping a BSD-ish licence on your example and adding
>> it to the test-suite:
>>
>> commit f09361cff16776b49e9159c60eea4e8a049865d6
>> Author: Krzysztof Kosinski <tweenk.pl@gmail.com>
>> Date:   Fri Sep 16 15:59:38 2011 +0100
>>
>>     test: Add radial-outer-focus
>>
>>     Numerical instability in the computation of gradients leads to random
>>     results.
>>
>>     Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=40918
>>
>>
>> Andrea, it looks like numerical instability, can you take a closer look?
> 
> The very definition of radial gradient is not well-behaved when its focus point
> is on the outer circle and the repeat mode is not NONE. This happens because
> when the focus point is (slightly) inside the outer circle, the radial gradient
> colors the whole plane, when it is exactly on the circle it colors exactly an
> half-plane and when it is outside it colors the region in an angle.
> The focus point can obviously go from inside to outside and vice versa even
> moving by a very small amount (example: rounding error) when it is supposed to
> be exactly on the outer circle.
> Unfortunately ill-conditioned problems and rounding errors hate each other :(
>
Comment 6 Krzysztof Kosiński 2011-09-16 11:53:21 UTC
(In reply to comment #3)
> In order to get consistent results ad to be able to follow the SVG
> specification you probably want to render it using CAIRO_EXTEND_NONE or you
> could cheat a little bit and move the focus slightly inside.
> (I would rather go for CAIRO_EXTEND_NONE, but it's probably much more work
> because you might have to recompute the big circle and the color stops)

I'm not sure if I understood that but using CAIRO_EXTEND_NONE would mean I'd have to paint the shape twice: once to give it the outer color of the gradient and the second time to actually paint the gradient. And on top of that the inner part (e.g. the red circle) could still disappear.

The main reason I'm reporting this is because I have to "cheat" by a substantial amount (multiplying the radius by 1 - 1e-6 is not sufficient) to prevent this, and the required amount appears to depend on the pixel size of the gradient after rendering.
Comment 7 Krzysztof Kosiński 2011-09-16 12:04:48 UTC
(In reply to comment #4)
> Sorry, I just realized that maybe what you're actually reporting is that
> sometimes you do not see the red part, not that sometimes the gradient covers
> an half-plane, sometimes the whole plane.

This looks better, but the transparent area at the top right is still a problem. I would have to fill it with a separate paint operation. Additionally, I would need to reverse the order of stops.

Why is the order of circles not a problem for infocus* gradients, but suddenly starts to matter for outfocus ones? Is there actually a situation where the rendering from my testcase is preferred over the one from yours?

By the way - the problem could be solved without any magic by just filling the half-plane which is now left transparent with the outer color of the gradient. There would then be a smooth transition between outfocus and infocus gradients.

* - I invented "outfocus" as an abbreviation for "gradient with focus / inner circle outside the outer circle" and "infocus" for "gradient with focus / inner circle inside the outer circle"
Comment 8 Andrea Canciani 2011-09-16 14:33:17 UTC
(In reply to comment #7)
> (In reply to comment #4)
> > Sorry, I just realized that maybe what you're actually reporting is that
> > sometimes you do not see the red part, not that sometimes the gradient covers
> > an half-plane, sometimes the whole plane.
> 
> This looks better, but the transparent area at the top right is still a
> problem. I would have to fill it with a separate paint operation. Additionally,
> I would need to reverse the order of stops.
> 
> Why is the order of circles not a problem for infocus* gradients, but suddenly
> starts to matter for outfocus ones? Is there actually a situation where the
> rendering from my testcase is preferred over the one from yours?

Radial gradients are drawn following the PDF specification, i.e. along the direction from the start circle to the end one, using the [0,1] parameter range for NONE extend and the (-inf, +inf) for other modes (with the additional condition that circles with negative radiuses are not drawn).
The rendering from your testcase is preferred whenever it is the desired one (example: when doing PDF gradients instead of SVG ones, that is the correct rendering for your input data).

> 
> By the way - the problem could be solved without any magic by just filling the
> half-plane which is now left transparent with the outer color of the gradient.
> There would then be a smooth transition between outfocus and infocus gradients.

In this case you can simply paint the plane with solid color (the "outer color") and then draw the radial gradient. This should ensure that no part stays clear.

> 
> * - I invented "outfocus" as an abbreviation for "gradient with focus / inner
> circle outside the outer circle" and "infocus" for "gradient with focus / inner
> circle inside the outer circle"
Comment 9 Krzysztof Kosiński 2011-09-16 17:07:17 UTC
(In reply to comment #8)
> In this case you can simply paint the plane with solid color (the "outer
> color") and then draw the radial gradient. This should ensure that no part
> stays clear.

In general this is not possible. If any stop of the gradient is not fully opaque, the result would be incorrect. Painting with the SOURCE operator might fix this, but then every shape with a radial gradient would have to be rendered to a separate group.

Isn't it possible to round the focus coordinates towards the center of the outer circle rather than in some arbitrary direction? Maybe provide a new extend mode which would paint the half-plane which is now left transparent with the color of the outer circle?
Comment 10 Andrea Canciani 2011-09-16 21:01:47 UTC
(In reply to comment #9)
> (In reply to comment #8)
> > In this case you can simply paint the plane with solid color (the "outer
> > color") and then draw the radial gradient. This should ensure that no part
> > stays clear.
> 
> In general this is not possible. If any stop of the gradient is not fully
> opaque, the result would be incorrect. Painting with the SOURCE operator might
> fix this, but then every shape with a radial gradient would have to be rendered
> to a separate group.

Yes, this looks like the right thing to do.

> 
> Isn't it possible to round the focus coordinates towards the center of the
> outer circle rather than in some arbitrary direction? Maybe provide a new

It is possible, but the application should be doing it, not cairo (cairo has no means to know whether you wanted the focus point to be inside or outside the circle, it just tries to respect the input).
The application could move the focus point slightly inside the outer circle in order to (try to) avoid the rounding issues, but the problem is ill-conditioned when the focus is almost on the outer circle, so the behavior you see is actually the expected one.

> extend mode which would paint the half-plane which is now left transparent with
> the color of the outer circle?

This would be possible, but it would be quite pointless, because you would need an API change and every backend (except maybe image with a future pixman) would have to fallback and perform the separate group trick. I see no advantage in extending the API to support this ad-hoc extend mode given that it can easily be reproduced using existing gradients.

Anyway, could you please try to describe the extend mode you would like to add and what the behavior for a radial gradient in generic position would be? Would this new extend mode also provide a new rasterization type for other pattern types as well?
Comment 11 Andrea Canciani 2011-09-17 08:43:53 UTC
(In reply to comment #10)
> (In reply to comment #9)
> > (In reply to comment #8)
> > > In this case you can simply paint the plane with solid color (the "outer
> > > color") and then draw the radial gradient. This should ensure that no part
> > > stays clear.
> > 
> > In general this is not possible. If any stop of the gradient is not fully
> > opaque, the result would be incorrect. Painting with the SOURCE operator might
> > fix this, but then every shape with a radial gradient would have to be rendered
> > to a separate group.
> 
> Yes, this looks like the right thing to do.
> 
> > 
> > Isn't it possible to round the focus coordinates towards the center of the
> > outer circle rather than in some arbitrary direction? Maybe provide a new
> 
> It is possible, but the application should be doing it, not cairo (cairo has no
> means to know whether you wanted the focus point to be inside or outside the
> circle, it just tries to respect the input).
> The application could move the focus point slightly inside the outer circle in
> order to (try to) avoid the rounding issues, but the problem is ill-conditioned
> when the focus is almost on the outer circle, so the behavior you see is
> actually the expected one.
> 
> > extend mode which would paint the half-plane which is now left transparent with
> > the color of the outer circle?
> 
> This would be possible, but it would be quite pointless, because you would need
> an API change and every backend (except maybe image with a future pixman) would
> have to fallback and perform the separate group trick. I see no advantage in
> extending the API to support this ad-hoc extend mode given that it can easily
> be reproduced using existing gradients.
> 
> Anyway, could you please try to describe the extend mode you would like to add
> and what the behavior for a radial gradient in generic position would be? Would
> this new extend mode also provide a new rasterization type for other pattern
> types as well?

An alternative approach might be to provide an API to choose a background color for patterns (which has an almost obvious meaning for every pattern type).
Most backends would still have to perform the fallback, but PDF and PS would be able to support this natively.
Comment 12 Andrea Canciani 2011-09-17 09:02:31 UTC
If somebody has a link to document which specifies the aspect of SVG radial gradients?
(http://www.w3.org/TR/SVG/pservers.html#RadialGradients doesn't do this, it only specifies how to reduce the parameters to a valid range)

All the other radial specifications I found so far (PS, PDF, HTML canvas) seem to describe the same "cone" as cairo, so maybe the transparent black part of the radial gradient might actually be expected in SVG as well.
Comment 13 Krzysztof Kosiński 2011-09-17 16:58:57 UTC
(In reply to comment #10)
> It is possible, but the application should be doing it, not cairo (cairo has no
> means to know whether you wanted the focus point to be inside or outside the
> circle, it just tries to respect the input).
> The application could move the focus point slightly inside the outer circle in
> order to (try to) avoid the rounding issues, but the problem is ill-conditioned
> when the focus is almost on the outer circle, so the behavior you see is
> actually the expected one.

In order to reliably do this, I have to know how large the gradient will be when rendered - I have to move the focus point inside the outer circle by about half of a screen pixel. But I do not know the pixel size of the gradient when creating it.

I would much prefer if the behavior of Cairo was explicitly defined in this case, rather effectively left undefined.

> Anyway, could you please try to describe the extend mode you would like to add
> and what the behavior for a radial gradient in generic position would be? Would
> this new extend mode also provide a new rasterization type for other pattern
> types as well?

The new extend mode would be identical to CAIRO_EXTEND_PAD for all patterns except radial gradients and it would fill the half-plane which is now left transparent with the color of the outer circle.
Comment 14 Krzysztof Kosiński 2011-09-17 17:35:39 UTC
(In reply to comment #12)
> If somebody has a link to document which specifies the aspect of SVG radial
> gradients?
> (http://www.w3.org/TR/SVG/pservers.html#RadialGradients doesn't do this, it
> only specifies how to reduce the parameters to a valid range)

http://dev.w3.org/SVG/profiles/1.1F2/test/harness/htmlSVGWeb/pservers-grad-13-b.html

This is the closest I could find. Unfortunately the outermost color in this gradient is transparent.
Comment 15 Andrea Canciani 2011-09-17 18:27:57 UTC
(In reply to comment #13)
> (In reply to comment #10)
> > It is possible, but the application should be doing it, not cairo (cairo has no
> > means to know whether you wanted the focus point to be inside or outside the
> > circle, it just tries to respect the input).
> > The application could move the focus point slightly inside the outer circle in
> > order to (try to) avoid the rounding issues, but the problem is ill-conditioned
> > when the focus is almost on the outer circle, so the behavior you see is
> > actually the expected one.
> 
> In order to reliably do this, I have to know how large the gradient will be
> when rendered - I have to move the focus point inside the outer circle by about
> half of a screen pixel. But I do not know the pixel size of the gradient when
> creating it.

Do you know the pattern matrix and the target context?
If you know both of them, it should be possible to do this by transforming the center of the outer circle and the focus point to device space and moving the transformed focus towards the center of 2*cairo_get_tolerance () units (I would avoid moving by half a pixel, because it might easily be too much when rendering to non-raster surfaces).

> 
> I would much prefer if the behavior of Cairo was explicitly defined in this
> case, rather effectively left undefined.

It is not left undefined. The behavior follows the PDF specification, which is also consistent with PS and HTML canvas and AFAICT with SVG, too. The documentation should describe the behavior of gradients, but that's true for linear gradients as well.

> 
> > Anyway, could you please try to describe the extend mode you would like to add
> > and what the behavior for a radial gradient in generic position would be? Would
> > this new extend mode also provide a new rasterization type for other pattern
> > types as well?
> 
> The new extend mode would be identical to CAIRO_EXTEND_PAD for all patterns
> except radial gradients and it would fill the half-plane which is now left
> transparent with the color of the outer circle.

Would this provide a big advantage over choosing the background color?
It looks like the semantic would be the same as PAD using the outer circle color as the background, which means that it would be much less generic.

(In reply to comment #14)
> (In reply to comment #12)
> > If somebody has a link to document which specifies the aspect of SVG radial
> > gradients?
> > (http://www.w3.org/TR/SVG/pservers.html#RadialGradients doesn't do this, it
> > only specifies how to reduce the parameters to a valid range)
> 
> http://dev.w3.org/SVG/profiles/1.1F2/test/harness/htmlSVGWeb/pservers-grad-13-b.html
> 
> This is the closest I could find. Unfortunately the outermost color in this
> gradient is transparent.

AFAICT it seems safe to assume that nobody specified the rendering aspect of radial gradients.
The only reason why anybody would choose a different definition from PDF seems to be that they wanted continuity even when the focus is on the outer circle.
Comment 16 Andrea Canciani 2011-09-18 08:36:53 UTC
Created attachment 51315 [details]
Simple implementation of the tolerance-based code

This files contains an example of what the code to create a radial gradient (given the center, radius, focus, pattern->userspace and target cairo_t) would look.

Can you please check if it is sufficient to fix your problem?
(Can you also check if my understanding of the SVG specification about the pattern transform matrix being the inverse of cairo pattern matrices is correct?)

If this functions works as expected, we can add it to the cookbook for anybody wanting only "infocus" radial gradients.
Comment 17 Andrea Canciani 2011-10-01 09:49:26 UTC
Ping! Does the tolerance-based code work for your testcases?
Shall we add it to cairo recipes and close this report?
Comment 18 Krzysztof Kosiński 2011-10-01 17:07:41 UTC
(In reply to comment #17)
> Ping! Does the tolerance-based code work for your testcases?
> Shall we add it to cairo recipes and close this report?

Sorry for the lag. It looks like the code should work, but I didn't have the time to test it yet. I will report the results once I test it in Inkscape, hopefully during the next week.
Comment 19 Krzysztof Kosiński 2011-11-14 11:03:09 UTC
Sorry for 1.5 months of lag.

I implemented your suggestion in Inkscape. I have some doubts whether it will work for all transform matrices, but in basic testing it seems to behave correctly.

I thought about your proposal of implementing a background color for patterns, and it looks like a good idea. It would be sufficient to get rid of the tolerance-based code in your example, so I wouldn't need access to the target context in order to create the gradient. The background color should of course fill the areas where the pattern is undefined (e.g. outside of surface for surface patterns, beyond the terminal points for gradients with CAIRO_EXTEND_NONE, etc.) as opposed to areas where the gradient is transparent black.
Comment 20 GitLab Migration User 2018-08-25 13:42:02 UTC
-- GitLab Migration Automatic Message --

This bug has been migrated to freedesktop.org's GitLab instance and has been closed from further activity.

You can subscribe and participate further through the new bug through this link to our GitLab instance: https://gitlab.freedesktop.org/cairo/cairo/issues/146.

Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.