Skip to content

Instantly share code, notes, and snippets.

@Zamlox
Last active November 10, 2016 14:18
Show Gist options
  • Save Zamlox/b835c4856058192827e0445f646e2e9d to your computer and use it in GitHub Desktop.
Save Zamlox/b835c4856058192827e0445f646e2e9d to your computer and use it in GitHub Desktop.
SVG transformations
Let's take an SVG sample:
```
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="10cm" height="10cm" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect x=".1" y=".1" width="99.8" height="99.8" fill="none" stroke="blue" stroke-width=".2" />
<!-- Start of generated content. Replaces 'use' -->
<g transform="translate(45, 10) scale(1.5) ">
<rect x="1" y="1" width="8" height="8"/>
<g transform="translate(15, 10) scale(1.5) ">
<rect x="1" y="1" width="8" height="8"/>
<circle cx="1" cy="20" r="3"/>
</g>
</g>
</svg>
```
Using reverse order of SVG transformations in ```draw``` would mean something like:
```
draw [
push [ scale 3.7878 3.7878 clip 0x0 100x100 ;-- S1
box 0x0 100x100
push [ scale 1.5 1.5 translate 45x10 ;-- S2 * T2
box 1x1 9x9
push [ scale 1.5 1.5 translate 15x10 ;-- S3 * T3
box 1x1 9x9
circle 1x20 3
]
]
]
]
```
But, this is not correct per total. While the second ```push``` transformations (scale 1.5 1.5 translate 45x10)
are reversed locally, they are actually appended to first ```push``` transformation (scale 3.7878 3.7878) and that's not good for
shape ```box 1x1 9x9``` defined in second ```push```. They should be prepended, not appended.
Similar problem is with the third ```push``` related to the second ```push```.
Mathematically, how it should be for the second ```push``` shapes:
CTM = [S2 * T2 * S1]
But in above ```draw``` sample will be:
CTM = [S1 * S2 * T2]
because the transformation matrices are cumulated by appending them.
One way how I see it could work using reversed order is something like:
```
draw [
push [ reset-matrix scale 3.7878 3.7878 clip 0x0 100x100 ;-- S1
box 0x0 100x100
push [ reset-matrix scale 1.5 1.5 translate 45x10 scale 3.7878 3.7878 ;-- S2 * T2 * S1
box 1x1 9x9
push [ reset-matrix scale 1.5 1.5 translate 15x10 scale 1.5 1.5 translate 45x10 scale 3.7878 3.7878 ;-- S3 * T3 * S2 * T2 * S1
box 1x1 9x9
circle 1x20 3
]
]
]
]
```
This might lead to unnecessary matrix operations (resetting matrix and multiplying again upper transformations each time a new inner
container is defined) which can lead to performance penalty.
I tried to avoid using ```reset-matrix``` solution but also other reverse based solutions I tried are not good enough, they lead to
complex code and possible performance penalty.
Using ```matrix-order``` solution would lead to a much cleaner and simpler ```draw``` code, like:
```
draw [
matrix-order prepend
push [ scale 3.7878 3.7878 clip 0x0 100x100 ;-- S1
box 0x0 100x100
push [ translate 45x10 scale 1.5 1.5 ;-- T2 * S2 --> S2 * T2
box 1x1 9x9
push [ translate 15x10 scale 1.5 1.5 ;-- T3 * S3 --> S3 * T3
box 1x1 9x9
circle 1x20 3
]
]
]
]
```
And because transformation matrices will be prepended, second ```push``` transformations will be prepended to first ```push```
transformation, like:
CTM = [S2 * T2 * S1]
which is correct.
Also the ```draw``` code is very similar to SVG content.
I have checked on Windows and Skia, and it is possible to specify in their API on which side should be the transformation matrix
relative to CTM (left or right). But for cairo library I haven't seen this posibility. However, for cairo it's possible to get and set
the CTM, which means it is doable but requires extra effort to manually calculate CTM based on transformations sequence.
I don't know what's the situation for Quartz.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment