SVG linear gradient with transform problem


#1

When linear gradients with scaling applied to them are loaded from an SVG, the drawing of them is not in line with other SVG renderers. The gradient points are transformed properly, however the actual coordinate system used when the gradient is drawn should also be transformed.

If you load an SVG in Inkscape or something with a transformed linear gradient, you’ll see that the gradient drawn won’t actually match with the points because of the coordinate system change.

What JUCE renders:
[attachment=2]juce gradient render.png[/attachment]

What Chrome renders:
[attachment=1]chrome gradient render.png[/attachment]

What the points look like in Inkscape:
[attachment=0]inkscape gradient render.png[/attachment]

I’ve figured out how to create a new gradient that will match what the transformed gradient is supposed to look like. There might be an easier way to do it, but this works:

Line 673 - juce_SVGParser.cpp

[code]FillType type (gradient);

if (gradient.isRadial == true)
{
type.transform = parseTransform (fillXml->getStringAttribute (“gradientTransform”))
.followedBy (transform);
}

else
{
// get the transformation for the gradient so we can use it
juce::AffineTransform gradientTransform = parseTransform (fillXml->getStringAttribute (“gradientTransform”))
.followedBy (transform);

// get the vector perpendicular to the vector of the gradient
juce::Point<float> perpendicular (gradient.point2.getY() - gradient.point1.getY(),
                                    -(gradient.point2.getX() - gradient.point1.getX()));

// get the gradient transform without the translation so we can apply it to
// the perpendicular vector
juce::AffineTransform noTranslate = gradientTransform;
noTranslate.mat02 = 0.0f;
noTranslate.mat12 = 0.0f;

// transform the perpendicular vector into the new coordinate space for the gradient
// this vector is now the slope of the linear gradient as it should appear in the new
// coordinate space
perpendicular.applyTransform (noTranslate);

// get the gradient points in the new coordinate space
juce::Point<float> gradPoint1Trans = gradient.point1.transformedBy (gradientTransform);
juce::Point<float> gradPoint2Trans = gradient.point2.transformedBy (gradientTransform);

// get the gradient's vector in the new coordinate space
juce::Point<float> point2FromPoint1 (gradPoint2Trans.getX() - gradPoint1Trans.getX(),
                                        gradPoint2Trans.getY() - gradPoint1Trans.getY());

// project the transformed gradient vector onto the transformed slope of the linear
// gradient as it should appear in the new coordinate space
float dotProduct = perpendicular.getX() * point2FromPoint1.getX() +
                    perpendicular.getY() * point2FromPoint1.getY();

dotProduct /= perpendicular.getX() * perpendicular.getX() +
                perpendicular.getY() * perpendicular.getY();

juce::Point<float> pointProject (perpendicular.getX() * dotProduct,
                                    perpendicular.getY() * dotProduct);

// set the new transformed gradient points
type.gradient->point1 = gradPoint1Trans;
type.gradient->point2 = gradPoint2Trans - pointProject;

}

return type;[/code]

As far as I can tell, radial gradients aren’t affected by this.


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

Hmm, that feels over-complicated to me, and I can’t really understand why your fix would work… Do you have an example of a problematic SVG file I could try?


#3

I can’t seem to get the file to upload. “Sorry, the board attachment quota has been reached.”

[code]<?xml version="1.0" encoding="UTF-8" standalone="no"?>









<sodipodi:namedview
id=“base"
pagecolor=”#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="240.13075"
inkscape:cy="254.29705"
inkscape:document-units="px"
inkscape:current-layer="svg2"
showgrid=“false"
inkscape:window-width=“1920"
inkscape:window-height=“1137"
inkscape:window-x=”-8"
inkscape:window-y=”-8"
inkscape:window-maximized=“1"
inkscape:snap-page=“false” />

rdf:RDF
<cc:Work
rdf:about=””>
dc:formatimage/svg+xml</dc:format>
<dc:type
rdf:resource=“http://purl.org/dc/dcmitype/StillImage” />
<dc:title />
</cc:Work>
</rdf:RDF>


[/code]

The code I wrote does seem over-complicated, but it’s the only way I could come up with to get a proper gradient.

Transforming the gradient points doesn’t seem to give the right angle of the gradient. The proper thing to do is to actually scale the coordinate system that the gradient is drawn in. So if you have a square with a 45 deg gradient in it and you scale it by 0.5 in the Y axis, the gradient will have half the slope it does and will be closer to a horizontal line. Transforming the actual points used to create the gradient would result in the points having a smaller slope as well, but since the gradient is perpendicular to the points, the slope of the drawn gradient would actually increase in slope.

What I did in that code is found the non-transformed slope of the actual drawn gradient (i.e. perpendicular to the gradient points), then applied the transformation to that. This gives the correct slope of the gradient as it should be drawn after the transformation. After that I recreate gradient points so that they will draw the gradient with this slope. The projection used in the code is what makes it seem overly complicated I think. It’s used to find the new gradient point. It isn’t easy to explain without images.


#4

Ok, thanks!

I’ve checked in a boiled-down version, trying to simplify it as much as possible, and it seems to work. I think you’re probably quite right, but it still feels wrong to me, somehow…!