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

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.

3 Likes

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

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.

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 ?

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

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 ?

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.

2 Likes

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

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

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

Interesting. Thanks!

@jules : any updates on this ?

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

This should be fixed in 4d4fd5e.

2 Likes

Thanks, awesome!

This fix actually stops the radial gradient being displayed for me.


Reverting the fix lets it show correctly - although not in the projucer file explorer - in the project.

Here is the SVG:

<svg
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:cc="http://creativecommons.org/ns#"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:svg="http://www.w3.org/2000/svg"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  id="svg8"
  version="1.1"
  viewBox="0 0 87.995399 86.877441"
  height="86.877441mm"
  width="87.995399mm">
 <defs
    id="defs2">
   <linearGradient
      id="linearGradient2467">
     <stop
        id="stop2463"
        offset="0"
        style="stop-color:#f6f97f;stop-opacity:1;" />
     <stop
        id="stop2465"
        offset="1"
        style="stop-color:#feff81;stop-opacity:0" />
   </linearGradient>
   <radialGradient
      gradientUnits="userSpaceOnUse"
      gradientTransform="matrix(0.73411329,1.5698127,-3.9182804,1.8323599,520.85518,-266.56917)"
      r="43.4977"
      fy="125.44496"
      fx="112.01121"
      cy="125.44496"
      cx="112.01121"
      id="radialGradient2473"
      xlink:href="#linearGradient2467" />
   <meshgradient
      y="81.209465"
      x="52.891968"
      gradientUnits="userSpaceOnUse"
      id="meshgradient2579">
     <meshrow
        id="meshrow2593">
       <meshpatch
          id="meshpatch2595">
         <stop
            id="stop2597"
            style="stop-color:#ffffff;stop-opacity:1"
            path="c 29.3318,0  58.6636,0  87.9954,0" />
         <stop
            id="stop2599"
            style="stop-color:#f6f97f;stop-opacity:1"
            path="c 0,28.9591  0,57.9183  0,86.8774" />
         <stop
            id="stop2601"
            style="stop-color:#ffffff;stop-opacity:1"
            path="c -29.3318,0  -58.6636,0  -87.9954,0" />
         <stop
            id="stop2603"
            style="stop-color:#f6f97f;stop-opacity:1"
            path="c 0,-28.9591  0,-57.9183  -1.42109e-14,-86.8774" />
       </meshpatch>
     </meshrow>
   </meshgradient>
 </defs>
 <metadata
    id="metadata5">
   <rdf:RDF>
     <cc:Work
        rdf:about="">
       <dc:format>image/svg+xml</dc:format>
       <dc:type
          rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
       <dc:title></dc:title>
     </cc:Work>
   </rdf:RDF>
 </metadata>
 <g
    transform="translate(-52.891968,-81.209462)"
    id="layer1">
   <g
      id="g2606">
     <rect
        style="opacity:1;fill:url(#radialGradient2473);fill-opacity:1;fill-rule:nonzero;stroke:url(#meshgradient2579);stroke-width:1.53999996;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
        id="rect815"
        width="86.455399"
        height="85.33744"
        x="53.661968"
        y="81.979462"
        ry="5.7761183" />
   </g>
 </g>
</svg>```

…hmm after playing around a bit more it appears the fix works for some gradients but breaks others.