Status: ED ED: http://dev.w3.org/csswg/css-transforms/ TR: http://www.w3.org/TR/css3-transforms/ Previous Version: http://www.w3.org/TR/2012/WD-css3-transforms-20120911/ Previous Version: http://www.w3.org/TR/2012/WD-css3-transforms-20120403/ Shortname: css-transforms Link Defaults: svg (property) to/stroke/fill, css-flexbox-1 (property) display, css-masking-1 (property) clip/clip-path, filters-1 (property) filter Level: 1 Group: fxtf Editor: Simon Fraser, Apple Inc, simon.fraser@apple.com Editor: Dean Jackson, Apple Inc, dino@apple.com Editor: Edward O'Connor, Apple Inc, eoconnor@apple.com Editor: Dirk Schulze, Adobe Systems Inc, dschulze@adobe.com Former Editor: David Hyatt, Apple Inc, hyatt@apple.com Former Editor: Chris Marrin, Apple Inc, cmarrin@apple.com Former Editor: Aryeh Gregor, Mozilla, ayg@aryeh.name Abstract: CSS transforms allows elements styled with CSS to be transformed in two-dimensional or three-dimensional space. This specification is the convergence of the CSS 2D transforms, CSS 3D transforms and SVG transforms specifications. !Issues List: in Bugzilla
In general, a coordinate system defines locations and distances on the current canvas. The current local coordinate system (also user coordinate system) is the coordinate system that is currently active and which is used to define how coordinates and lengths are located and computed, respectively, on the current canvas.
For elements that have an associated CSS layout box, the current user coordinate system has its origin at the top-left of a reference box and one unit equals one CSS pixel. The viewport for resolving percentage values is defined by the width and height of the reference box. For transforms, the reference box is the bounding box.
If the element does not have an associated CSS layout box and is in the http://www.w3.org/2000/svg namespace, the current user coordinate system has its origin at the top-left corner of the element's nearest viewport.
3x3 matrix for two-dimensional transformations.
Authors can easily provide a fallback if UAs do not provide support for three-dimensional transforms. The following example has two property definitions for 'transform'. The first one consists of two two-dimensional transform functions. The second one has a two-dimensional and a three-dimensional transform function.
div {
transform: scale(2) rotate(45deg);
transform: scale(2) rotate3d(0, 0, 1, 45deg);
}
With 3D support, the second definition will override the first one. Without 3D support, the second definition is invalid and a UA falls back to the first definition.
Demonstration of the initial coordinate space.
The transformation matrix is computed from the 'transform' and 'transform-origin' properties as follows:
div {
transform: translate(100px, 100px);
}
This transform moves the element by 100 pixels in both the X and Y directions.
div {
height: 100px; width: 100px;
transform-origin: 50px 50px;
transform: rotate(45deg);
}
The 'transform-origin' property moves the point of origin by 50 pixels in both the X and Y directions. The transform rotates the element clockwise by 45° about the point of origin. After all transform functions were applied, the translation of the origin gets translated back by -50 pixels in both the X and Y directions.
div {
height: 100px; width: 100px;
transform: translate(80px, 80px) scale(1.5, 1.5) rotate(45deg);
}
This transform moves the element by 80 pixels in both the X and Y directions, then scales the element by 150%, then rotates it 45° clockwise about the Z axis. Note that the scale and rotation operate about the center of the element, since the element has the default transform-origin of ''50% 50%''.
Note that an identical rendering can be obtained by nesting elements with the equivalent transforms:
<div style="transform: translate(80px, 80px)">
<div style="transform: scale(1.5, 1.5)">
<div style="transform: rotate(45deg)"></div>
</div>
</div>
Is this effect on ''position: fixed'' necessary? If so, need to go into more detail here about why fixed positioned objects should do this, i.e., that it's much harder to implement otherwise. See Bug 16328.
Fixed backgrounds on the root element are affected by any transform specified for that element. For all other elements that are effected by a transform (i.e. have a transform applied to them, or to any of their ancestor elements), a value of ''fixed'' for the 'background-attachment' property is treated as if it had a value of ''scroll''. The computed value of 'background-attachment' is not affected. Note: If the root element is transformed, the transformation applies to the entire canvas, including any background specified for the root element. Since the background painting area for the root element is the entire canvas, which is infinite, the transformation might cause parts of the background that were originally off-screen to appear. For example, if the root element's background were repeating dots, and a transformation of ''scale(0.5)'' were specified on the root element, the dots would shrink to half their size, but there will be twice as many, so they still cover the whole viewport.This description does not exactly match what WebKit implements. Perhaps it should be changed to match current implementations? See Bug 19637.
This example shows the effect of three-dimensional transform applied to an element.
<style>
div {
height: 150px;
width: 150px;
}
.container {
border: 1px solid black;
}
.transformed {
transform: rotateY(50deg);
}
</style>
<div class="container">
<div class="transformed"></div>
</div>
The transform is a 50° rotation about the vertical, Y axis. Note how this makes the blue box appear narrower, but not three-dimensional.
Diagrams showing how scaling depends on the 'perspective' property and Z position. In the top diagram, Z is half of d. In order to make it appear that the original circle (solid outline) appears at Z (dashed circle), the circle is scaled up by a factor of two, resulting in the light blue circle. In the bottom diagram, the circle is scaled down by a factor of one-third to make it appear behind the original position.
Diagram showing the effect of moving the perspective origin upward.
The perspective matrix is computed as follows:
This example shows how perspective can be used to cause three-dimensional transforms to appear more realistic.
<style>
div {
height: 150px;
width: 150px;
}
.container {
perspective: 500px;
border: 1px solid black;
}
.transformed {
transform: rotateY(50deg);
}
</style>
<div class="container">
<div class="transformed"></div>
</div>
The inner element has the same transform as in the previous example, but its rendering is now influenced by the perspective property on its parent element. Perspective causes vertices that have positive Z coordinates (closer to the viewer) to be scaled up in X and Y, and those further away (negative Z coordinates) to be scaled down, giving an appearance of depth.
<style>
div {
height: 150px;
width: 150px;
}
.container {
perspective: 500px;
border: 1px solid black;
}
.transformed {
transform: rotateY(50deg);
background-color: blue;
}
.child {
transform-origin: top left;
transform: rotateX(40deg);
background-color: lime;
}
</style>
<div class="container">
<div class="transformed">
<div class="child"></div>
</div>
</div>
This example shows how nested 3D transforms are rendered in the absence of ''transform-style: preserve-3d''. The blue div is transformed as in the previous example, with its rendering influenced by the perspective on its parent element. The lime element also has a 3D transform, which is a rotation about the X axis (anchored at the top, by virtue of the transform-origin). However, the lime element is being rendered into the plane of its parent because it is not a member of a 3D rendering context; the parent is "flattening".
The final value of the transform used to render an element in a 3D rendering context is computed by accumulating an accumulated 3D transformation matrix as follows:
<style>
div {
height: 150px;
width: 150px;
}
.container {
perspective: 500px;
border: 1px solid black;
}
.transformed {
transform-style: preserve-3d;
transform: rotateY(50deg);
background-color: blue;
}
.child {
transform-origin: top left;
transform: rotateX(40deg);
background-color: lime;
}
</style>
This example is identical to the previous example, with the addition of ''transform-style: preserve-3d'' on the blue element. The blue element now establishes a 3D rendering context, of which the lime element is a member. Now both blue and lime elements share a common three-dimensional space, so the lime element renders as tilting out from its parent, influenced by the perspective on the container.
<style>
.container {
background-color: rgba(0, 0, 0, 0.3);
transform-style: preserve-3d;
perspective: 500px;
}
.container > div {
position: absolute;
left: 0;
}
.container > :first-child {
transform: rotateY(45deg);
background-color: orange;
top: 10px;
height: 135px;
}
.container > :last-child {
transform: translateZ(40px);
background-color: rgba(0, 0, 255, 0.75);
top: 50px;
height: 100px;
}
</style>
<div class="container">
<div></div>
<div></div>
</div>
This example shows show elements in a 3D rendering context can intersect. The container element establishes a 3D rendering context for itself and its two children. The children intersect with each other, and the orange element also intersects with the container.
This wording needs clarification; 'backface-visibility' works per-3D rendering context, not relative to the root.
This is a first pass at an attempt to precisely specify how exactly to transform elements using the provided matrices. It might not be ideal, and implementer feedback is encouraged. See bug 15605.
<style>
.transformed {
height: 100px;
width: 100px;
background: lime;
transform: perspective(50px) translateZ(100px);
}
</style>
All of the box's corners have z-coordinates greater than the perspective. This means that the box is behind the viewer and will not display. Mathematically, the point (x, y) first becomes (x, y, 0, 1), then is translated to (x, y, 100, 1), and then applying the perspective results in (x, y, 100, −1). The w-coordinate is negative, so it does not display. An implementation that doesn't handle the w < 0 case separately might incorrectly display this point as (−x, −y, −100), dividing by −1 and mirroring the box.
<style>
.transformed {
height: 100px;
width: 100px;
background: radial-gradient(yellow, blue);
transform: perspective(50px) translateZ(50px);
}
</style>
Here, the box is translated upward so that it sits at the same place the viewer is looking from. This is like bringing the box closer and closer to one's eye until it fills the entire field of vision. Since the default transform-origin is at the center of the box, which is yellow, the screen will be filled with yellow.
Mathematically, the point (x, y) first becomes (x, y, 0, 1), then is translated to (x, y, 50, 1), then becomes (x, y, 50, 0) after applying perspective. Relative to the transform-origin at the center, the upper-left corner was (−50, −50), so it becomes (−50, −50, 50, 0). This is transformed to something very far to the upper left, such as (−5000, −5000, 5000). Likewise the other corners are sent very far away. The radial gradient is stretched over the whole box, now enormous, so the part that's visible without scrolling should be the color of the middle pixel: yellow. However, since the box is not actually infinite, the user can still scroll to the edges to see the blue parts.
WebKit doesn't render this box unless the translateZ() is < 50px.
<style>
.transformed {
height: 50px;
width: 50px;
background: lime;
border: 25px solid blue;
transform-origin: left;
transform: perspective(50px) rotateY(-45deg);
}
</style>
The box will be rotated toward the viewer, with the left edge staying fixed while the right edge swings closer. The right edge will be at about z = ''70.7px'', which is closer than the perspective of ''50px''. Therefore, the rightmost edge will vanish ("behind" the viewer), and the visible part will stretch out infinitely far to the right.
Mathematically, the top right vertex of the box was originally (100, −50), relative to the transform-origin. It is first expanded to (100, −50, 0, 1). After applying the transform specified, this will get mapped to about (70.71, −50, 70.71, −0.4142). This has w = −0.4142 < 0, so we need to slice away the part of the box with w < 0. This results in the new top-right vertex being (50, −50, 50, 0). This is then mapped to some faraway point in the same direction, such as (5000, −5000, 5000), which is up and to the right from the transform-origin. Something similar is done to the lower right corner, which gets mapped far down and to the right. The resulting box stretches far past the edge of the screen.
Again, the rendered box is still finite, so the user can scroll to see the whole thing if he or she chooses. However, the right part has been chopped off. No matter how far the user scrolls, the rightmost ''30px'' or so of the original box will not be visible. The blue border was only ''25px'' wide, so it will be visible on the left, top, and bottom, but not the right.
The same basic procedure would apply if one or three vertices had w < 0. However, in that case the result of truncating the w < 0 part would be a triangle or pentagon instead of a quadrilateral.
Name: transform Value: none | <Any computed value other than ''none'' for the transform results in the creation of both a stacking context and a containing block. The object acts as a containing block for fixed positioned descendants.> Initial: none Applies to: transformable elements Inherited: no Percentages: refer to the size of bounding box Computed value: As specified, but with relative lengths converted into absolute lengths. Media: visual Animatable: as transform
<transform-list> = <>+
Name: transform-origin Value: [ left | center | right | top | bottom | <The default value for SVG elements without associated CSS layout box is ''0 0''. The values of the 'transform' and 'transform-origin' properties are used to compute the transformation matrix, as described above. If only one value is specified, the second value is assumed to be center. If one or two values are specified, the third value is assumed to be ''0px''. If two or more values are defined and either no value is a keyword, or the only used keyword is center, then the first value represents the horizontal position (or offset) and the second represents the vertical position (or offset). A third value always represents the Z position (or offset) and must be of type <> | < > ]
|
[ left | center | right | <> | < > ]
[ top | center | bottom | <> | < > ] < >?
|
[ center | [ left | right ] ] && [ center | [ top | bottom ] ] <>? Initial: 50% 50% Applies to: transformable elements Inherited: no Percentages: refer to the size of bounding box Computed value: For < > the absolute value, otherwise a percentage Media: visual Animatable: as simple list of length, percentage, or calc
A length value gives a fixed length as the offset. The value for the horizontal and vertical offset represent an offset from the top left corner of the bounding box.
For SVG elements without an associated CSS layout box the horizontal and vertical offset represent an offset from the point of origin of the element's local coordinate space.
Name: transform-style Value: flat | preserve-3d Initial: flat Applies to: transformable elements Inherited: no Percentages: N/A Computed value: Same as specified value. Media: visual Animatable: noA value of ''preserve-3d'' for 'transform-style' establishes a stacking context.
Keyword description missing. Description of flattening behavior is missing. Does rendering operate in 'z-index' order? Do we still create a 3D context and let planes intersect? See Bug 22427 Bug 23015.
The following CSS property values require the user agent to create a flattened representation of the descendant elements before they can be applied, and therefore override the behavior of ''transform-style: preserve-3d'':Name: perspective Value: none | <Where <> Initial: none Applies to: transformable elements Inherited: no Percentages: N/A Computed value: Absolute length or "none". Media: visual Animatable: as length
Distance to the center of projection.
Verify that projection is the distance to the center of projection.
Name: perspective-origin Value: [ left | center | right | top | bottom | <The values of the 'perspective' and 'perspective-origin' properties are used to compute the perspective matrix, as described above. If only one value is specified, the second value is assumed to be center. If at least one of the two values is not a keyword, then the first value represents the horizontal position (or offset) and the second represents the vertical position (or offset). The values for 'perspective-origin' represent an offset of the perspective origin from the top left corner of the bounding box.> | < > ]
|
[ left | center | right | <> | < > ]
[ top | center | bottom | <> | < > ]
|
[ center | [ left | right ] ] && [ center | [ top | bottom ] ] Initial: 50% 50% Applies to: transformable elements Inherited: no Percentages: refer to the size of the bounding box Computed value: For <> the absolute value, otherwise a percentage. Media: visual Animatable: as simple list of length, percentage, or calc
A percentage for the horizontal perspctive offset is relative to the width of the bounding box. A percentage for the vertical offset is relative to height of the bounding box. The value for the horizontal and vertical offset represent an offset from the top left corner of the bounding box.
A length value gives a fixed length as the offset. The value for the horizontal and vertical offset represent an offset from the top left corner of the bounding box.
Name: backface-visibility Value: visible | hidden Initial: visible Applies to: transformable elements Inherited: no Percentages: N/A Computed value: Same as specified value. Media: visual Animatable: noThe visibility of an element with ''backface-visibility: hidden'' is determined as follows:
Backface-visibility cannot be tested by only looking at m33. See Bug 23014.
Note: The reasoning for this definition is as follows. Assume elements are rectangles in the x–y plane with infinitesimal thickness. The front of the untransformed element has coordinates like (x, y, ε), and the back is (x, y, −ε), for some very small ε. We want to know if after the transformation, the front of the element is closer to the viewer than the back (higher z-value) or further away. The z-coordinate of the front will be m13x + m23y + m33ε + m43, before accounting for perspective, and the back will be m13x + m23y − m33ε + m43. The first quantity is greater than the second if and only if m33 > 0. (If it equals zero, the front and back are equally close to the viewer. This probably means something like a 90-degree rotation, which makes the element invisible anyway, so we don't really care whether it vanishes.)This example shows the combination of the 'transform' style property and the 'transform' presentation attribute.
<svg xmlns="http://www.w3.org/2000/svg">
<style>
.container {
transform: translate(100px, 100px);
}
</style>
<g class="container" transform="translate(200 200)">
<rect width="100" height="100" fill="blue" />
</g>
</svg>
Because of the participation to the CSS cascade, the 'transform' style property overrides the 'transform' presentation attribute. Therefore the container gets translated by ''100px'' in both the horizontal and the vertical directions, instead of ''200px''.
rotate() = rotate( <> [, < >, < >]? )
The 'transform-origin' property on the pattern in the following example specifies a ''50%'' translation of the origin in the horizontal and vertical dimension. The 'transform' property specifies a translation as well, but in absolute lengths.
<svg xmlns="http://www.w3.org/2000/svg">
<style>
pattern {
transform: rotate(45deg);
transform-origin: 50% 50%;
}
</style>
<defs>
<pattern id="pattern-1">
<rect id="rect1" width="100" height="100" fill="blue" />
</pattern>
</defs>
<rect width="200" height="200" fill="url(#pattern-1)" />
</svg>
An SVG 'pattern element' element doesn't have a bounding box. The bounding box of the referencing 'rect element' element is used instead to solve the relative values of the 'transform-origin' property. Therefore the point of origin will get translated by 100 pixels temporarily to rotate the user space of the 'pattern element' elements content.
| Data type | Additive? | 'animate element' | 'set element' | 'animateColor element' | 'animateTransform' | Notes |
|---|---|---|---|---|---|---|
| < |
yes | yes | yes | no | yes | Additive for 'animateTransform element' means that a transformation is post-multiplied to the base set of transformations. |
A by animation with a by value vb is equivalent to the same animation with a values list with 2 values, the neutral element for addition for the domain of the target attribute (denoted 0) and vb, and ''additive="sum"''. [[SMIL3]]
<rect width="100" height="100"> <animateTransform attributeName="transform" attributeType="XML" type="scale" by="1" dur="5s" fill="freeze"/> </rect>
The neutral element for addition when performing a by animation with ''type="scale"'' is the value 0. Thus, performing the animation of the example above causes the rectangle to be invisible at time 0s (since the animated transform list value is ''scale(0)''), and be scaled back to its original size at time 5s (since the animated transform list value is ''scale(1)'').
In this example the gradient transformation of the linear gradient gets animated.
<linearGradient gradientTransform="scale(2)"> <animate attributeName="gradientTransform" from="scale(2)" to="scale(4)" dur="3s" additive="sum"/> <animate attributeName="transform" from="translate(0, 0)" to="translate(100px, 100px)" dur="3s" additive="sum"/> </linearGradient>
The 'linearGradient element' element specifies the 'linearGradient/gradientTransform' presentation attribute. The two 'animate element' elements address the target attribute 'linearGradient/gradientTransform' and 'transform'. Even so all animations apply to the same gradient transformation by taking the value of the 'linearGradient/gradientTransform' presentation attribute, applying the scaling of the first animation and applying the translation of the second animation one after the other.
Note that the behavior of ''skew()'' is different from multiplying ''skewX()'' with ''skewY()''. Implementations must support this function for compatibility with legacy content.
Note that the rotation is clockwise as one looks from the end of the vector toward the origin.
<div style="transform:translate(-10px,-20px) scale(2) rotate(45deg) translate(5px,10px)"/>is functionally equivalent to:
<div style="transform:translate(-10px,-20px)">
<div style="transform:scale(2)">
<div style="transform:rotate(45deg)">
<div style="transform:translate(5px,10px)">
</div>
</div>
</div>
</div>
That is, in the absence of other styling that affects position and dimensions, a nested set of transforms is equivalent to a single list of transform functions, applied from the outside in. The resulting transform is the matrix multiplication of the list of transforms.
If a transform function causes the current transformation matrix (CTM) of an object to be non-invertible, the object and its content do not get displayed.
The object in the following example gets scaled by 0.
<style>
.box {
transform: scale(0);
}
</style>
<div class="box">
Not visible
</div>
The scaling causes a non-invertible CTM for the coordinate space of the div box. Therefore neither the div box, nor the text in it get displayed.
For example, if from-transform is ''scale(2)'' and to-transform is ''none'' then the value ''scale(1)'' will be used for to-transform and animation will proceed using the next rule. Similarly, if from-transform is ''none'' and to-transform is ''scale(2) rotate(50deg)'' then the animation will execute as if from-transform is ''scale(1) rotate(0)''.
For example, if from-transform is ''scale(1) translate(0)'' and to-transform is ''translate(100px) scale(2)'' then ''scale(1)'' and ''translate(100px)'' as well as ''translate(0)'' and ''scale(2)'' don't share a common primitive and therefore can not get interpolated following this rule.
For derived transform functions that have a two-dimensional primitive and a three-dimensional primitive, the context decides about the used primitive. See Interpolation of primitives and derived transform functions.
The two transform functions ''translate(0)'' and ''translate(100px)'' are of the same type, have the same number of arguments and therefore can get interpolated numerically. ''translateX(100px)'' is not of the same type and ''translate(100px, 0)'' does not have the same number of arguments, therefore these transform functions can not get interpolated without a former conversion step.
The following example describes a transition from ''translateX(100px)'' to ''translateY(100px)'' in 3 seconds on hovering over the div box. Both transform functions derive from the same primitive ''translate()'' and therefore can be interpolated.
div {
transform: translateX(100px);
}
div:hover {
transform: translateY(100px);
transition: transform 3s;
}
For the time of the transition both transform functions get transformed to the common primitive. ''translateX(100px)'' gets converted to ''translate(100px, 0)'' and ''translateY(100px)'' gets converted to ''translate(0, 100px)''. Both transform functions can then get interpolated numerically.
In this example a two-dimensional transform function gets animated to a three-dimensional transform function. The common primitive is ''translate3d()''.
div {
transform: translateX(100px);
}
div:hover {
transform: translateZ(100px);
transition: transform 3s;
}
First ''translateX(100px)'' gets converted to ''translate3d(100px, 0, 0)'' and ''translateZ(100px)'' to ''translate3d(0, 0, 100px)'' respectively. Then both converted transform functions get interpolated numerically.
In the following example the element gets translated by 100 pixel in both the X and Y directions and rotated by 1170° on hovering. The initial transformation is 45°. With the usage of transition, an author might expect a animated, clockwise rotation by three and a quarter turns (1170°).
<style>
div {
transform: rotate(45deg);
}
div:hover {
transform: translate(100px, 100px) rotate(1215deg);
transition: transform 3s;
}
</style>
<div></div>
The number of transform functions on the source transform ''rotate(45deg)'' differs from the number of transform functions on the destination transform ''translate(100px, 100px) rotate(1125deg)''. According to the last rule of Interpolation of Transforms, both transforms must be interpolated by matrix interpolation. With converting the transformation functions to matrices, the information about the three turns gets lost and the element gets rotated by just a quarter turn (90°).
To achieve the three and a quarter turns for the example above, source and destination transforms must fulfill the third rule of Interpolation of Transforms. Source transform could look like ''translate(0, 0) rotate(45deg)'' for a linear interpolation of the transform functions.
Supporting functions (point is a 3 component vector, matrix is a 4x4 matrix, vector is a 4 component vector):
double determinant(matrix) returns the 4x4 determinant of the matrix
matrix inverse(matrix) returns the inverse of the passed matrix
matrix transpose(matrix) returns the transpose of the passed matrix
point multVecMatrix(point, matrix) multiplies the passed point by the passed matrix
and returns the transformed point
double length(point) returns the length of the passed vector
point normalize(point) normalizes the length of the passed point to 1
double dot(point, point) returns the dot product of the passed points
double sqrt(double) returns the root square of passed value
double max(double y, double x) returns the bigger value of the two passed values
double dot(vector, vector) returns the dot product of the passed vectors
vector multVector(vector, vector) multiplies the passed vectors
double sqrt(double) returns the root square of passed value
double max(double y, double x) returns the bigger value of the two passed values
double min(double y, double x) returns the smaller value of the two passed values
double cos(double) returns the cosines of passed value
double sin(double) returns the sine of passed value
double acos(double) returns the inverse cosine of passed value
double abs(double) returns the absolute value of the passed value
double rad2deg(double) transforms a value in radian to degree and returns it
double deg2rad(double) transforms a value in degree to radian and returns it
Decomposition also makes use of the following function:
point combine(point a, point b, double ascl, double bscl)
result[0] = (ascl * a[0]) + (bscl * b[0])
result[1] = (ascl * a[1]) + (bscl * b[1])
result[2] = (ascl * a[2]) + (bscl * b[2])
return result
Input: matrix ; a 4x4 matrix
Output: translation ; a 2 component vector
scale ; a 2 component vector
angle ; rotation
m11 ; 1,1 coordinate of 2x2 matrix
m12 ; 1,2 coordinate of 2x2 matrix
m21 ; 2,1 coordinate of 2x2 matrix
m22 ; 2,2 coordinate of 2x2 matrix
Returns false if the matrix cannot be decomposed, true if it can
double row0x = matrix[0][0]
double row0y = matrix[0][1]
double row1x = matrix[1][0]
double row1y = matrix[1][1]
translate[0] = matrix[3][0]
translate[1] = matrix[3][1]
scale[0] = sqrt(row0x * row0x + row0y * row0y)
scale[1] = sqrt(row1x * row1x + row1y * row1y)
// If determinant is negative, one axis was flipped.
double determinant = row0x * row1y - row0y * row1x
if (determinant < 0)
// Flip axis with minimum unit vector dot product.
if (row0x < row1y)
scale[0] = -scale[0]
else
scale[1] = -scale[1]
// Renormalize matrix to remove scale.
if (scale[0])
row0x *= 1 / scale[0]
row0y *= 1 / scale[0]
if (scale[1])
row1x *= 1 / scale[1]
row1y *= 1 / scale[1]
// Compute rotation and renormalize matrix.
angle = atan2(row0y, row0x);
if (angle)
// Rotate(-angle) = [cos(angle), sin(angle), -sin(angle), cos(angle)]
// = [row0x, -row0y, row0y, row0x]
// Thanks to the normalization above.
double sn = -row0y
double cs = row0x
double m11 = row0x
double m12 = row0y
double m21 = row1x
double m22 = row1y
row0x = cs * m11 + sn * m21
row0y = cs * m12 + sn * m22
row1x = -sn * m11 + cs * m21
row1y = -sn * m12 + cs * m22
m11 = row0x
m12 = row0y
m21 = row1x
m22 = row1y
// Convert into degrees because our rotation functions expect it.
angle = rad2deg(angle)
return true
Input: translationA ; a 2 component vector
scaleA ; a 2 component vector
angleA ; rotation
m11A ; 1,1 coordinate of 2x2 matrix
m12A ; 1,2 coordinate of 2x2 matrix
m21A ; 2,1 coordinate of 2x2 matrix
m22A ; 2,2 coordinate of 2x2 matrix
translationB ; a 2 component vector
scaleB ; a 2 component vector
angleB ; rotation
m11B ; 1,1 coordinate of 2x2 matrix
m12B ; 1,2 coordinate of 2x2 matrix
m21B ; 2,1 coordinate of 2x2 matrix
m22B ; 2,2 coordinate of 2x2 matrix
// If x-axis of one is flipped, and y-axis of the other,
// convert to an unflipped rotation.
if ((scaleA[0] < 0 && scaleB[1] < 0) || (scaleA[1] < 0 && scaleB[0] < 0))
scaleA[0] = -scaleA[0]
scaleA[1] = -scaleA[1]
angleA += angleA < 0 ? 180 : -180
// Don't rotate the long way around.
if (!angleA)
angleA = 360
if (!angleB)
angleB = 360
if (abs(angleA - angleB) > 180)
if (angleA > angleB)
angleA -= 360
else
angleB -= 360
Afterwards, each component of the decomposed values translation, scale, angle, m11 to m22 of the source matrix get linearly interpolated with each corresponding component of the destination matrix.
Input: translation ; a 2 component vector
scale ; a 2 component vector
angle ; rotation
m11 ; 1,1 coordinate of 2x2 matrix
m12 ; 1,2 coordinate of 2x2 matrix
m21 ; 2,1 coordinate of 2x2 matrix
m22 ; 2,2 coordinate of 2x2 matrix
Output: matrix ; a 4x4 matrix initialized to identity matrix
matrix[0][0] = m11
matrix[0][1] = m12
matrix[1][0] = m21
matrix[1][1] = m22
// Translate matrix.
matrix[3][0] = translate[0] * m11 + translate[1] * m21
matrix[3][1] = translate[0] * m12 + translate[1] * m22
// Rotate matrix.
angle = deg2rad(angle);
double cosAngle = cos(angle);
double sinAngle = sin(angle);
// New temporary, identity initialized, 4x4 matrix rotateMatrix
rotateMatrix[0][0] = cosAngle
rotateMatrix[0][1] = sinAngle
rotateMatrix[1][0] = -sinAngle
rotateMatrix[1][1] = cosAngle
matrix = multiply(matrix, rotateMatrix)
// Scale matrix.
matrix[0][0] *= scale[0]
matrix[0][1] *= scale[0]
matrix[1][0] *= scale[1]
matrix[1][1] *= scale[1]
Input: matrix ; a 4x4 matrix
Output: translation ; a 3 component vector
scale ; a 3 component vector
skew ; skew factors XY,XZ,YZ represented as a 3 component vector
perspective ; a 4 component vector
quaternion ; a 4 component vector
Returns false if the matrix cannot be decomposed, true if it can
// Normalize the matrix.
if (matrix[3][3] == 0)
return false
for (i = 0; i < 4; i++)
for (j = 0; j < 4; j++)
matrix[i][j] /= matrix[3][3]
// perspectiveMatrix is used to solve for perspective, but it also provides
// an easy way to test for singularity of the upper 3x3 component.
perspectiveMatrix = matrix
for (i = 0; i < 3; i++)
perspectiveMatrix[i][3] = 0
perspectiveMatrix[3][3] = 1
if (determinant(perspectiveMatrix) == 0)
return false
// First, isolate perspective.
if (matrix[0][3] != 0 || matrix[1][3] != 0 || matrix[2][3] != 0)
// rightHandSide is the right hand side of the equation.
rightHandSide[0] = matrix[0][3]
rightHandSide[1] = matrix[1][3]
rightHandSide[2] = matrix[2][3]
rightHandSide[3] = matrix[3][3]
// Solve the equation by inverting perspectiveMatrix and multiplying
// rightHandSide by the inverse.
inversePerspectiveMatrix = inverse(perspectiveMatrix)
transposedInversePerspectiveMatrix = transposeMatrix4(inversePerspectiveMatrix)
perspective = multVecMatrix(rightHandSide, transposedInversePerspectiveMatrix)
else
// No perspective.
perspective[0] = perspective[1] = perspective[2] = 0
perspective[3] = 1
// Next take care of translation
for (i = 0; i < 3; i++)
translate[i] = matrix[3][i]
// Now get scale and shear. 'row' is a 3 element array of 3 component vectors
for (i = 0; i < 3; i++)
row[i][0] = matrix[i][0]
row[i][1] = matrix[i][1]
row[i][2] = matrix[i][2]
// Compute X scale factor and normalize first row.
scale[0] = length(row[0])
row[0] = normalize(row[0])
// Compute XY shear factor and make 2nd row orthogonal to 1st.
skew[0] = dot(row[0], row[1])
row[1] = combine(row[1], row[0], 1.0, -skew[0])
// Now, compute Y scale and normalize 2nd row.
scale[1] = length(row[1])
row[1] = normalize(row[1])
skew[0] /= scale[1];
// Compute XZ and YZ shears, orthogonalize 3rd row
skew[1] = dot(row[0], row[2])
row[2] = combine(row[2], row[0], 1.0, -skew[1])
skew[2] = dot(row[1], row[2])
row[2] = combine(row[2], row[1], 1.0, -skew[2])
// Next, get Z scale and normalize 3rd row.
scale[2] = length(row[2])
row[2] = normalize(row[2])
skew[1] /= scale[2]
skew[2] /= scale[2]
// At this point, the matrix (in rows) is orthonormal.
// Check for a coordinate system flip. If the determinant
// is -1, then negate the matrix and the scaling factors.
pdum3 = cross(row[1], row[2])
if (dot(row[0], pdum3) < 0)
for (i = 0; i < 3; i++)
scale[i] *= -1;
row[i][0] *= -1
row[i][1] *= -1
row[i][2] *= -1
// Now, get the rotations out
quaternion[0] = 0.5 * sqrt(max(1 + row[0][0] - row[1][1] - row[2][2], 0))
quaternion[1] = 0.5 * sqrt(max(1 - row[0][0] + row[1][1] - row[2][2], 0))
quaternion[2] = 0.5 * sqrt(max(1 - row[0][0] - row[1][1] + row[2][2], 0))
quaternion[3] = 0.5 * sqrt(max(1 + row[0][0] + row[1][1] + row[2][2], 0))
if (row[2][1] > row[1][2])
quaternion[0] = -quaternion[0]
if (row[0][2] > row[2][0])
quaternion[1] = -quaternion[1]
if (row[1][0] > row[0][1])
quaternion[2] = -quaternion[2]
return true
translate[0] of the source matrix and translate[0] of the destination matrix are interpolated numerically, and the result is used to set the translation of the animating element.
Quaternions of the decomposed source matrix are interpolated with quaternions of the decomposed destination matrix using the spherical linear interpolation (Slerp) as described by the pseudo code below:
Input: quaternionA ; a 4 component vector
quaternionB ; a 4 component vector
t ; interpolation parameter with 0 <= t <= 1
Output: quaternionDst ; a 4 component vector
product = dot(quaternionA, quaternionB)
// Clamp product to -1.0 <= product <= 1.0
product = max(product, 1.0)
product = min(product, -1.0)
if (product == 1.0)
quaternionDst = quaternionA
return
theta = acos(dot)
w = sin(t * theta) * 1 / sqrt(1 - product * product)
for (i = 0; i < 4; i++)
quaternionA[i] *= cos(t * theta) - product * w
quaternionB[i] *= w
quaternionDst[i] = quaternionA[i] + quaternionB[i]
return
Input: translation ; a 3 component vector
scale ; a 3 component vector
skew ; skew factors XY,XZ,YZ represented as a 3 component vector
perspective ; a 4 component vector
quaternion ; a 4 component vector
Output: matrix ; a 4x4 matrix
Supporting functions (matrix is a 4x4 matrix):
matrix multiply(matrix a, matrix b) returns the 4x4 matrix product of a * b
// apply perspective
for (i = 0; i < 4; i++)
matrix[i][3] = perspective[i]
// apply translation
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
matrix[3][i] += translation[j] * matrix[j][i]
// apply rotation
x = quaternion[0]
y = quaternion[1]
z = quaternion[2]
w = quaternion[3]
// Construct a composite rotation matrix from the quaternion values
// rotationMatrix is a identity 4x4 matrix initially
rotationMatrix[0][0] = 1 - 2 * (y * y + z * z)
rotationMatrix[0][1] = 2 * (x * y - z * w)
rotationMatrix[0][2] = 2 * (x * z + y * w)
rotationMatrix[1][0] = 2 * (x * y + z * w)
rotationMatrix[1][1] = 1 - 2 * (x * x + z * z)
rotationMatrix[1][2] = 2 * (y * z - x * w)
rotationMatrix[2][0] = 2 * (x * z - y * w)
rotationMatrix[2][1] = 2 * (y * z + x * w)
rotationMatrix[2][2] = 1 - 2 * (x * x + y * y)
matrix = multiply(matrix, rotationMatrix)
// apply skew
// temp is a identity 4x4 matrix initially
if (skew[2])
temp[2][1] = skew[2]
matrix = multiply(matrix, temp)
if (skew[1])
temp[2][1] = 0
temp[2][0] = skew[1]
matrix = multiply(matrix, temp)
if (skew[0])
temp[2][0] = 0
temp[1][0] = skew[0]
matrix = multiply(matrix, temp)
// apply scale
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
matrix[i][j] *= scale[i]
return
One translation unit on a matrix is equivalent to 1 pixel in the local coordinate system of the element.
A 2D 3x2 matrix with six parameters a, b, c, d, e and f is equivalent to the matrix:
A 2D translation with the parameters tx and ty is equivalent to a 3D translation where tz has zero as a value.
A 2D scaling with the parameters sx and sy is equivalent to a 3D scale where sz has one as a value.
A 2D rotation with the parameter alpha is equivalent to a 3D rotation with vector [0,0,1] and parameter alpha.
A 2D skew like transformation with the parameters alpha and beta is equivalent to the matrix:
A 2D skew transformation along the X axis with the parameter alpha is equivalent to the matrix:
A 2D skew transformation along the Y axis with the parameter beta is equivalent to the matrix:
A 3D translation with the parameters tx, ty and tz is equivalent to the matrix:
A 3D scaling with the parameters sx, sy and sz is equivalent to the matrix:
A 3D rotation with the vector [x,y,z] and the parameter alpha is equivalent to the matrix:
where:
A perspective projection matrix with the parameter d is equivalent to the matrix: