Search code examples
svgmatrixtransformdrawradial-gradients

SVG - How to apply a transform matrix to a radial gradient?


I'm writing a c++ application, which is able to open several SVG files and draw them on the screen. Some of them contain a radial gradient, in which a transform matrix is provided. I need to calculate the final gradient values to apply before performing the drawing, but I have some trouble to do that.

For example, consider the following SVG:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
   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"
   version="1.1"
   id="svg2"
   viewBox="0 0 250 250"
   height="250"
   width="250">
  <defs
     id="defs4">
    <linearGradient
       osb:paint="gradient"
       id="linearGradient4162">
      <stop
         id="stop4164"
         offset="0"
         style="stop-color:#d4ca67;stop-opacity:1" />
      <stop
         id="stop4166"
         offset="1"
         style="stop-color:#64caea;stop-opacity:1;" />
    </linearGradient>
    <radialGradient
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(0.43766035,0.50852999,-0.28201922,0.24271652,332.30951,640.28863)"
       spreadMethod="reflect"
       r="125.35714"
       fy="938.15851"
       fx="14.381515"
       cy="938.15851"
       cx="14.381515"
       id="radialGradient4172"
       xlink:href="#linearGradient4162" />
  </defs>
  <g
     transform="translate(0,-802.36216)"
     id="layer1">
    <path
       id="rect4136"
       d="m 0.45358449,802.63 250.71427551,0 0,250.3572 -250.71427551,0 z"
       style="opacity:1;fill:url(#radialGradient4172);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
  </g>
</svg>

Once drawn, this SVG should give the following result:

enter image description here

In my application, I can draw the SVG as long as I ignore his provided transform matrix. In this case I have a coherent result, but of course, not conform to the expected result.

enter image description here

Now I try to apply the transform matrix to the radial gradient values. I get a completely inconsistent result.

enter image description here

Here is a pseudo-code of what I'm trying to do in my c++ code, in order to get the correct transformed gradient values:

Vector2 brushRadius(brush.Radius.Width, brush.Radius.Height);
Vector2 brushCenter(brush.Center.X, brush.Center.Y);
Vector2 brushFocus(brush.Focus.X, brush.Focus.Y);

brushRadius = brush.Matrix.Transform(brushRadius);
brushCenter = brush.Matrix.Transform(brushCenter);
brushFocus  = brush.Matrix.Transform(brushFocus);

brush.m_Radius = Size(brushRadius.X, brushRadius.Y);
brush.m_Center = Point(brushCenter.X, brushCenter.Y);
brush.m_Focus  = Point(brushFocus.X, brushFocus.Y);

Obviously this does not work.

So can someone explain to me what I'm doing wrong, and how to apply the transform matrix to the radial gradient values to get the correct result?


Solution

  • Consider the way Inkscape represents the radialGradient in its UI:

    enter image description here

    (I've changed your example so the focus isn't identical to the center, it is shown the as the cross below the center.)

    You can manipulate the gradient with this UI in a way that immediately applies a gradientTransform, without knowing its exact parameters. What you see is the center and two vectors, or, if you want, the points defining a non-degenerate parallelogram. The vectors are scaled such that their length differs, but they are also rotated. While in this case they are perpendicular, they could in addition be skewed.

    What you will need in the end to draw the gradient are the coordinates of all three points (plus the focus point). That would be eight independent values, while your current definition only sets six.

    Setting four 2D coordinates is mathematically identical to setting two plus some extra independent values (following your attempt probably width, height, rotate and skew). That will depend on how you are drawing. Conversion between the representation is a purely mathematical problem. But as you should know from linear algebra: you'll always need eight independent values.