Bug: SVG radial gradients not drawn correctly (with potential fix!)


#1

I’m using JUCE fe6fa12f551357c2ba5a5cf1ca6041d084c9dee8 (latest master) and there appears to be a problem when rendering SVG radial gradients. I’m 100% certain, that this used to work correctly in earlier versions of JUCE, more precisely it worked in 90bbda1f195b4cf940f54f7f41986e0d5d68c9ec
. Here are some screenshots:

It’s supposed to look like this:
image

But it looks like this in the Projucer and my app:
image

I’m using “Normal” blending. This is the SVG:

<?xml version="1.0" encoding="UTF-8"?>
<svg width="635px" height="254px" viewBox="0 0 635 254" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
        <radialGradient cx="50.000298%" cy="50.0005245%" fx="50.000298%" fy="50.0005245%" r="50.0001489%" gradientTransform="translate(0.500003,0.500005),scale(1.000000,0.997848),translate(-0.500003,-0.500005)" id="radialGradient-1">
            <stop stop-color="#B3D3E6" offset="0%"></stop>
            <stop stop-color="#A9CDE1" offset="25.38%"></stop>
            <stop stop-color="#8EBED2" offset="68.62%"></stop>
            <stop stop-color="#76B1C5" offset="100%"></stop>
        </radialGradient>
    </defs>
    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="backdrop-outlines" transform="translate(121.000000, 129.000000)" fill="url(#radialGradient-1)" fill-rule="nonzero">
            <path d="M36.228,0 L11.07,0 C4.983,0 0,4.991 0,11.093 L0,36.307 C0,42.408 4.983,47.4 11.07,47.4 L36.228,47.4 C42.318,47.4 47.298,42.408 47.298,36.307 L47.298,11.093 C47.299,4.992 42.318,0 36.228,0 Z" id="glow"></path>
        </g>
    </g>
</svg>

It may be worth noting, that the svg is using a transformed gradient.

I tried with the CoreGraphics and the OpenGL renderer. The OpenGL Renderer yields the expected results, the CoreGraphics renderer does not. Therefore parsing is correct.

I noticed, that when parsing the SVG gradient, the FillType::transform member is set for radial gradients, but for linear gradients the transform appears to be applied to the points during parsing.

In CoreGraphicsContext::drawGradient() applyTransform() AND state->fillType.transform.transformPoints() is called. Maybe this transforms the radial gradient twice? If I change CoreGraphicsContext::drawGradient() to:

void CoreGraphicsContext::drawGradient()
{
    flip();
    applyTransform (state->fillType.transform);
    CGContextSetAlpha (context, state->fillType.getOpacity());

    const ColourGradient& g = *state->fillType.gradient;

    CGPoint p1 (convertToCGPoint (g.point1));
    CGPoint p2 (convertToCGPoint (g.point2));

    //state->fillType.transform.transformPoints (p1.x, p1.y, p2.x, p2.y);

    if (state->gradient == 0)
        state->gradient = createGradient (g, rgbColourSpace);

    if (g.isRadial)
        CGContextDrawRadialGradient (context, state->gradient, p1, 0, p1, g.point1.getDistanceFrom (g.point2),
                                     kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
    else
        CGContextDrawLinearGradient (context, state->gradient, p1, p2,
                                     kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
}

The output looks correct.

Best,
Ben

Update: The CoreGraphics and related SVG parsing code code is ancient - so not sure what’s going on here.


#2

I don’t think that the SVG parsing code is ancient. It was definitely updated in the last years.


#3

True. The version that was working was from about August 2017. I looked through the commits related to juce_SVGParser.cpp and the CoreGraphics renderer and found nothing obvious - which does not mean it’s not there of course.


#4

Update: It has nothing to do with the gradient transform. It has to do with the parent beeing transformed. For testing I use the following svg:

<?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="16 16 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
        <radialGradient cx="50%" cy="50%" fx="50%" fy="50%" r="50%" id="radialGradient">
            <stop stop-color="#ff0000" offset="0%"></stop>
            <stop stop-color="#00ff00" offset="100%"></stop>
        </radialGradient>
    </defs>
    <g transform="translate(16.000000, 16.000000)">
        <rect width="32" height="32" fill="url(#radialGradient)" />
    </g>
</svg>

Projucer:
image

Chrome / Quicklook / Sketch:
image

If I remove the transform from the parent (and shift the view box):

<?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
        <radialGradient cx="50%" cy="50%" fx="50%" fy="50%" r="50%" id="radialGradient">
            <stop stop-color="#ff0000" offset="0%"></stop>
            <stop stop-color="#00ff00" offset="100%"></stop>
        </radialGradient>
    </defs>
    <g>
        <rect width="32" height="32" fill="url(#radialGradient)" />
    </g>
</svg>

The Projucer yields the intended result:
image

@jules : do you recall why linear svg gradients points are transformed, but for radial gradients the transform is stored ?


#5

I guess this is just some subtle mistake where it’s not quite following the SVG spec in terms of how things are transformed.


#6

For me removing the if and using the else block for both cases fixes the issue. I’m a little bit worried though, that the CoreGraphics renderer and the OpenGL renderer yield different results. What do you think is the correct way to fix this ?


#7

Right… very odd. I guess the CoreGraphics renderer could be wrong, though it does seem bizarre that a bug wouldn’t have been found after so many years. It deserves some more attention though, I’ll take a look when I get chance.


#8

Can you give a hint on when you may find the time? A client is waiting for me right now. If you’re uber-bussy, I can apply my “workaround” locally and hope that nothing else breaks.

Thanks & Best,
Ben


#9

Unlikely to happen before next week, I’m a bit busy right now!


#10

The changes were added for this post: SVG linear gradient with transform problem


#11

Interesting. Thanks!


#12

@jules : any updates on this ?


#13

Sorry, haven’t had chance to look at it yet!


#14

This should be fixed in 4d4fd5e.


#15

Thanks, awesome!