46

I have a free clip art SVG file originally created in Inkscape which I'm making modifications to for use in a Windows 8 JavaScript game. It contains numerous instances of a path with a matrix transform applied on a surrounding group, like this:

<g transform="matrix(0.443,0.896,-0.896,0.443,589.739,-373.223)">
    <path d="M486,313s27-9,43-29l26,4,1,23-22,5s-25-6-48-3z" />
</g>

I want to flatten that transform by applying it in advance to the path in Inkscape, to reduce browser work during animation. However when I plug the 6 matrix values into the A B C D E F parameters in Inkscape and apply it, it gives the path a completely different rotation and scaling to what the IE10 engine does.

I have checked numerous times that I have the 6 values mapped correctly. What am I doing wrong?

EDIT: OK, here are before and after screenshots from IE10 and Inkscape. For the IE10 case, the SVG resides directly inside the body of an otherwise empty HTML document (the rendering is exactly the same in Firefox). In Inkscape, I simply opened the "before" SVG file which contains only the path element, selected the path, and plugged in the 6 matrix transform values into Object > Transform > Matrix. I know very little about matrices, I just want to be able to pre-apply these transformations in the same way the browser does, and ideally to understand why there is a difference in Inkscape. Thanks.

IE10 path only IE10 path with transform Inkscape path only Inkscape path with transform

Tom W Hall
  • 5,273
  • 4
  • 29
  • 35

5 Answers5

63

Short answer

When typing the transformation matrix params in Inkscape, make sure you have "Edit current matrix" checked, since if you apply a new transformation matrix to an object, you're actually multiplying this new matrix with the existing transformation matrix of the object, so make sure you edit it instead.
enter image description here

Long Answer

How to recalculate everything yourself.

First let us try and understand the transformation matrices a bit. A transformation matrix is a quick and clever tool for applying affine transformations ( transformation which preserves straight lines) to a vector.
So, if you have a vector (say, 2d coordinates) and a transformation matrix, and multiply the two together, you will end up with transformed coordinates, with the transformations defined in the transformation matrix, applied.
transformation matrix
Calculating x' and y' is done like so:

x' = a*x + c*y + e 
y' = b*x + d*y + f

Next, we need to understand the svg format a bit.
According to the w3c svg spec the matrix transform takes exactly those 6 parameters (a,b,c,d,e,f) as arguments.
Therefore, from your example,

<g transform="matrix(0.443,0.896,-0.896,0.443,589.739,-373.223)">

we have the following transformation matrix params:

a=0.443
b=0.896
c=-0.896
d=0.443
e=589.739
f=-373.223

Now, if we have the following example coordinate: x=27, y=-9, we can transform it, by using the previously defined transformation matrix like this:

x' = a*x + c*y + e 
x' = 0.443*27 + -0.896*-9 + 589.739
x' = 609.764

y' = b*x + d*y + f
y' = 0.896*27 + 0.443*-9 -373.223
y' = −353.018

Neat, huh? You can get more info here

But that is not all. We also need to understand svg path data.
According to the w3c svg path dspecification each letter in the path data represents an instruction. And each of the number pairs that follow an instruction represent a coordinate value.

From your example, we have the following path:

<path d="M486,313s27-9,43-29l26,4,1,23-22,5s-25-6-48-3z" />

Here we see that this path object uses one absolute moveto instruction (uppercase M), a relative smooth curveto cubic Bézier curve (lowercase s), a relative lineto instruction (lowercase l), and another relative smooth curveto cubic Bézier curve instruction, followed by a closepath instruction (lowercase z).

M486,313 is translated to absolute moveto x=486, y=313
s27-9,43-29 is a bit more complicated to read because some comas are omitted because they're not needed if the negative number is negative, so the minus sign acts as a coma - anyways, it translates to relative smooth bezier curveto x=27, y=-9, x=43, y=-29 (one destination point and one control point)
And so on.

So, how do we apply and remove the transformation matrix from your svg group? Like so:

// we read the transformation matrix params
// <g transform="matrix(0.443,0.896,-0.896,0.443,589.739,-373.223)">
a=0.443
b=0.896
c=-0.896
d=0.443
e=589.739
f=-373.223

// we read the path data, and transform each instruction    
// <path d="M486,313s27-9,43-29l26,4,1,23-22,5s-25-6-48-3z" />

M486,313 Absolute move to

x' = a*x + c*y + e = a*486 + c*313 + e = 524.589
y' = b*x + d*y + f = b*486 + d*313 + f = 200.892

Move to instruction is now M524.589,200.892

S27-9,43-29 - smooth curveto, repeat the same process for each coordinate, but set the e and f (translation parameters) to 0, since it's a relative instruction not an absolute.
It is now
s20.025,20.205,45.033,25.680999999999997

l26,4,1,23-22,5
will become
l7.934000000000001,25.067999999999998,-20.165,11.085,-14.226,-17.497

s-25-6-48-3
will become
s-5.698999999999999,-25.058000000000003,-18.576,-44.337

And z will remain z

So the resulting transformed path will be:

<path d="M524.589,200.892s20.025,20.205,45.033,25.680999999999997l7.934000000000001,25.067999999999998,-20.165,11.085,-14.226,-17.497s-5.698999999999999,-25.058000000000003,-18.576,-44.337z" />

I hope this makes sense to you.

ArtBIT
  • 3,931
  • 28
  • 39
  • 1
    That's an excellent answer, thanks! It'll take me a while to digest the process for applying the transformations manually. In the meantime, when I set the "Edit current matrix" flag in Inkscape, it now does give the correct visual result - however, when I save the SVG file (either as standard Inkscape or optimized) it saves with the transform as a separate attribute instead of applied to the path values. I have "Store transformation" set to Optimized in Preferences. How do I get it to collapse the transformation into the path? Thanks! – Tom W Hall Feb 27 '13 at 20:21
  • 3
    @TomHall make sure you ungroup your objects (Object->Ungroup or ) and save as optimized .svg – ArtBIT Feb 28 '13 at 07:02
  • 2
    But the SVG file I am operating on as a test only has a single path node directly inside the SVG node (the path from my question). So the process is: I open it, apply the transform with Edit current matrix ticked and Store transformation set to Optimized, then try doing Ungroup, then save as an optimized SVG. But it saves with the transform added like so: Sorry to be obtuse, what am I doing wrong? – Tom W Hall Feb 28 '13 at 09:06
  • @TomHall if your object only has a single path node directly inside the SVG node, in inkscape, group the object first, edit the transformation matrix, ungroup the object (this will apply the matrix onto the path) and save as optimized svg – ArtBIT Feb 28 '13 at 09:48
  • Thanks, I'll give this a go tonight or tomorrow morning. – Tom W Hall Feb 28 '13 at 22:38
  • Argh I'm sorry, I misunderstood the bounty system and should have read it better - I thought because you'd answered within the bounty period I could accept it anytime. If I start a new bounty (it has to be 100 this time, but what the hell, your answer is great) is your existing answer eligible for me to award that straight to you? – Tom W Hall Mar 01 '13 at 20:57
  • I contacted the StackExchange team and asked if I can start a new bounty and award it to you, but apparently I can't :-( Really sorry, my fault and I should've understood the bounty system better. I upvoted you anyhow. – Tom W Hall Mar 04 '13 at 00:56
  • 4
    Note that you cannot generally carry the transform from the element into its data (even for a ``) if you want the final appearance to be the same. The presence of `skew` in particular alters the appearance of the stroke non-uniformly in a way that cannot be represented with just path commands. – Phrogz Aug 31 '13 at 23:07
  • 1
    that is the best explaination of the svg matrix in short words, i have seen, yet. this helped me a lot – halfbit Dec 25 '13 at 22:59
  • In summary, suppose you have this code: ... This is equivalent to: ... or to: ... – verdy_p Nov 17 '16 at 13:45
  • Be carefull with images (linked or embedded). When you import one and modify its scale, there's no "transform" node added in XML. Instead of that, Inkscape only changes its width and height. So... there's no way to get the image back to its original scale. To avoid this, you have to group the image (alone) before importing it and then change that group's scale. – Mario Mey Dec 26 '16 at 15:30
23

You can bake the coords selecting the path then using Path -> Union (CTRL++). Hope this helps

parameciostudio
  • 570
  • 5
  • 12
23

Paste In Place can help you:

  1. Double click the group in Inkscape, to enter it.
  2. Select all the contents of the group by pressing Ctrl+A, and copy them with Ctrl+C.
  3. Double click outside the group to leave the group.
  4. Edit > Paste In Place (Ctrl+Alt+V) – at this point, group transformations are applied to the obects you paste.
  5. Group the objects again (Ctrl+G)
  6. Move the new group to the same depth as the original, and delete the original group. (This is probably easier with the XML editor, Ctrl+Shift+X.)
andraaspar
  • 796
  • 6
  • 10
18

Following the answer of @andraaspar, you can also try ungrouping (Ctrl-U) and grouping again (Ctrl-G). It worked for me.

btel
  • 5,563
  • 6
  • 37
  • 47
1

Thanks ArtBIT for all the info ! I had some issues about this on a PHP app, and wrote a library that manipulates font data (from SVG file) and does any sort of transformation on it. Anyone else interested may give it a try from the GitHub :

https://github.com/kartsims/easysvg

Usage example :

require 'easySVG.php';
$svg = new EasySVG();
$svg->setFont("paris-bold-webfont.svg", 100, "#000000");
$svg->addText("Simple text display");
$svg->addAttribute("width", "800px");
$svg->addAttribute("height", "100px");
echo $svg->asXML();

SVG data manipulation example :

$def = 'YOUR SVG DEFINITION HERE';
$easySVG = new EasySVG();
// rotate by 40°
$rotated_def = $easySVG->defRotate($def, 40)
// rotate by 40° with center at (200,100)
$rotated_def2 = $easySVG->defRotate($def, 40, 200, 100)
// scale transform : width*4
$scaled_def = $easySVG->defScale($def, 4)
kartsims
  • 1,001
  • 9
  • 16