Transformation
From
Gavin:
I think what you're going to have to do is decompose the matrix into
rotation and skew/scale. The skew matrix doesn't look like a complete
3x3 matrix, but rather just scale and perspective.
On the bright side, you
*can* omit your DOM calls to create a skew
element and just do:
this.rawNode.skew.matrix = (your matrix string)
Which seems to work OK in my samples.
Your code was searching for existing "skew" tags rather than "o:skew"
tags, and thus was creating new nodes with each setTransform.
Here's the code I found on the net for doing rotation extraction:
For a homogeneous geometrical transformation matrix, you can get the
roll, pitch and yaw angles, following the TRPY convention, using the
following formulas:
roll (rotation around z) : atan(xy, xx)
pitch (rotation around y) : -arcsin(xz)
yaw (rotation around x) : atan(yz,zz)
where the matrix is defined in the form:
[
xx, yx, zx, px;
xy, yy, zy, py;
xz, yz, zz, pz;
0, 0, 0, 1
]
(VML's matrix is a string in the form "
/sxx/,
/sxy/,
/syx/,
/syy/,
/px/,
/py/" where /s /is scale, /p /is perspective, and /x /and /y /are /x
/and /y / values. If *Offset
<http://msdn.microsoft.com/workshop/author/vml/shape/skew/skew_offset.asp>
*is in absolute units, then /px /and /py /are in emu
-1 units
<http://msdn.microsoft.com/workshop/author/vml/shape/skew/..%5Cdata%5Cunits.asp>
(otherwise they are an inverse fraction of the shape size). The default
value is "1,0,0,1,0,0".)
From
Gavin:
You've probably seen these. They're in Japanese, but the code is still
useful (particularly for doing correct radial gradient fills in VML).
http://newing.qee.jp/vml/vml_fill.html
http://newing.qee.jp/vml/vml_skew.htm
http://newing.qee.jp/vml/vml_stroke.htm
From
Gavin:
What you're seeing is conversion to "emu" units -- the smallest unit
VML can deal with. The reason stuff is looking crazy is that you weren't
specifying units for the offset value. Here's the revised method:
// set an absolute transformation matrix
setTransform: function(matrix){
this.matrix = dojo.gfx.normalizeMatrix(matrix);
// generate the attribute
if(this.matrix){
var skew = this.rawNode.skew;
var mt = "" + this.matrix.xx + "," + this.matrix.xy +
"," + this.matrix.yx + "," + this.matrix.yy +
", 0, 0";
var offset = "" + this.matrix.dx + "px," + this.matrix.dy +
"px"; // Note pixel spec on offset
dojo.debug( "mt = " + mt + " offset = " + offset );
skew.matrix = mt;
skew.offset = offset;
skew.on = "t";
dojo.debug( "skew : matrix = " + skew.matrix + " offset = " +
skew.offset );
}
// support for chaining
return this;
},
The whole file is attached.
I also notice that using
*no* offset makes the VML match the SVG better,
so that's a subject for investigation. The MSDN VML doc says:
*Remarks*
The matrix is a string in the form "
/sxx/,
/sxy/,
/syx/,
/syy/,
/px/,
/py/" where /s /is scale, /p /is perspective, and /x /and /y /are /x
/and /y / values. If *Offset
<http://msdn.microsoft.com/workshop/author/vml/SHAPE/SKEW/skew_offset.asp>
*is in absolute units, then /px /and /py /are in emu
-1 units
<http://msdn.microsoft.com/workshop/author/vml/SHAPE/SKEW/..%5Cdata%5Cunits.asp>
(otherwise they are an inverse fraction of the shape size). The default
value is "1,0,0,1,0,0".
(from
http://msdn.microsoft.com/library/default.asp?url=/workshop/author/vml/SHAPE/SKEW/skew.asp)
Finally, I had to disable the pattern test in VML because not all of the
Javascript methods were defined to make it work.
From
Gavin:
Here's the missing bit:
Offset:
Determines the amount of /x /and /y / offset from the shape's original
location. Values are defined in absolute measurement or fractional
values of a shape (ranging from -0.5 to +0.5). Default is 0,0.
If you don't specify the unit of measurement, you need to measure the
shape and convert the offset to a relative value. Ugh.
From Gavin:
http://www.developer.com/xml/article.php/10929_793961_2
Some lucid explanations of units used to transform group elements
From Gavin:
Argh. The "skew" element doesn't change the "group" element's transform,
only "shape" elements and types derived from shape.
The group element
*does* support the standard css "rotation"
translation, scale features of shape, however.
So, my modest proposal is:
1) When applying a transform matrix to a group in VML, crack out the
Rotation, Translation, and Scale components and apply them to the style
of the group element.
2) If there's anything left in the matrix (i.e., it's something besides
identity), then it's actually skewing the shapes inside the group. This
skew needs to be applied to the shapes within the group -- and if they
already have skew matrices of their own, it needs to be accumulated in
each shape.
3) Tom -- do you have the math for this handy?
4) Kun -- does this make sense?
5) Microsoft-- what
*were* you thinking?
From Eugene:
One way to handle VML cases is to decompose a matrix we set on the fly
using something like eigen decomposition
(
http://mathworld.wolfram.com/EigenDecomposition.html), which produces
two matrices. IIRC one of them is a scale matrix (diagonal one of
eigenvalues), and another is a rotation matrix (of eigenvectors) --- see
(10). I believe the 2D case of affine transformations is rather simple
to implement, and it doesn't cost too much performance-wise. It may be a
ticket for our problem without complicating the API.
Update: My post had a mistake: it should be
three matrices: rotation-scaling-rotation. It can be easily done using SVD decomposition, which is similar to eigen decomposition.
Nevertheless it doesn't help VML, because apparently it implements scaling and rotation in two steps (assuming a tree composed of embedded groups and shapes serving as leaves):
- All coordinates (coordsize/coordorigin) are calculated first without taking rotation parameter into considiration. Basically it means that all scaling/translation matrices are applied first using top-down recusion.
- All rotations are applied using bottom-up recursion. Rotations are done using a geometric center of the shape.
It appears that it is impossible to apply a rotation before a scaling/translation matrix. For example, you cannot make a diamond shape by rotating a square 45 degrees and scaling it along y axis --- it will always be scaled first, then rotated leaving you with rotated rectangle. :-(
All these mean that shapes can be transformed using
skew sub-element. (The documentation states that it is a Microsoft Office extension, but it works even when Microsoft Office is not present making it safe to use for us.) But groups cannot use skew, and restricted to transformations, which can be represented with scaling-rotating pair only. I'll continue my research to verify these findings, and possibly find a way around these restrictions.
Gradients
From
Gavin:
Also, I was thinking that what we might end up needing to do for radial
gradients is create an oval shape with a radial gradient and clip it
with the shape that is actually getting filled. That way we don't get
that boxy radial gradient fill in IE and it matches SVG on Firefox.
Take a look at the VMLFrame element to see how to do clipping. Maybe.
From Eugene:
Shape transformations do not affect gradient fills --- shape is transformed, and then filled with an original unmodivied fill.