Hi Bric,

Sorry, as much as i've tried (given time constraints), I can't figure out where
you got the above 2x3 matrix instead of a 2x2.
 
Or is it a 2x3 python array, whose (top)-left 2x2 elements are the rotation
transformation? In that case, where do you get transform[0][2] and
transform[1][2] from?

Yes, it's just a 2x3 python array, whose top-left 2x2 elements are the rotation+scale+shear transformation (it's more than just rotation). In some cases, it may be 3x3 matrix, depending on what is passed in -- but the 3rd row is completely ignored either way. I'll explain about the 3rd row soon.

First, what's the 3rd column all about (i.e. transform[_][2])? It's the translation vector, and it serves a rather different purpose than the 2x2. After the rotation+scale+shear is performed, the entire point is translated by the x,y value stored in the 3rd column. This is done by simple arithemetic addition.

The extra column has the very nice property of just being part of the matrix, and "doing the right thing" with common matrix operations (including multiplication/composition & inverse, the most common). So you can represent a complex transform as a single 2x3 (or 3x3) matrix.

The 3rd row: this is primarily to make the matrix square (#rows == #columns), because only square matrices are Invertible (http://en.wikipedia.org/wiki/Invertible_matrix). Matrix Inversion is necessary to undo the effect of a matrix -- see below. The 3rd row is (virtually) always set to [0, 0, 1], unless you're really trying hard to get tricky with the maths. I've explicitly added this 3rd row in my invert_transform() function.
 
> > If you need to modify the x,y position, it gets slightly more complicated,
> but only a little. 1) transform the point as above, to get the original
> values if you need them for anything 2) calculate the new values you need.
> 3) Transform them back to the original x,y space by using the matrix
> inverse: http://en.wikipedia.org/wiki/Matrix_inverse
 
You mean, given a certain non-origin position, multiplying by the inverse would
give me that position.

Yes, roughly, I think, if I understand you correctly, except I don't think it's relevant whether the point is (0,0) or otherwise -- a transform matrix will affect all points in the same way (and furthermore, except for translation, (0,0) will always be (0,0) after transform -- a rotated/scaled/sheared origin is still the origin).

I'll try to say it less ambiguously:

A) If you multiply a point (let's call it P) by a transform matrix (let's call it T), then you get a new point (call it P'), shear/scaled/rotated/translated according to the transform matrix: P * T = P
B) If you then multiply the new point P' by the inverse of T (call that T'), then you get the original point P: P' * T' = P
In other words, the inverse of a matrix performs the exact reverse transformation that the original matrix performed.

So, if a point is specified (eg in the SVG file) in one particular "coordinate space", but a transform is applied (the transform= attribute of the path) in Inkscape before displaying it, then before you can do anything very meaningful with that point, you have to apply the same transform to it's coordinates. That's step 1 in my previous email above.

Then, after doing something meaningful with the point (which is step 2 above), you have to "un-transform" the point before you can write it back to the SVG file (step 3, applying the matrix inverse).
 
you are making me think back years ago to linear algebra class... yes, I
suspected from the getgo I might need the inverse at some point.

Yes, you can't get by without it ;-)

Hope that helps!

--
PS. Check out the Brush newsletter: Subscribe or read our previous newsletters

Bryan Hoyt, Web Development Manager  --  Brush Technology
Ph: +64 3 741 1204     Mobile: +64 21 238 7955
Web: brush.co.nz