This specification describes a transformation matrix interface with the dimension of 3x2 and 4x4.

The transformation matrix interface replaces the SVGMatrix interface from SVG [[SVG11]]. It is a common interface used to describe 2D and 3D transformations on a graphical context for SVG, Context2D [[CANVAS-2D]], CSS3 Transforms [[CSS3-TRANSFORMS]] and WebGL [[WEBGL]].

Definitions

post-multiply
Term A post-multiplied by term B is equal to A · B.
pre-multiply
Term A pre-multiplied by term B is equal to B · A.
multiply
Multiply term A by term B is equal to A · B.

The Point dictionary

A 2D points and 3D points are represented by the following WebIDL dictionary:

double x = 0
The x coordinate of the point.
double y = 0
The y coordinate of the point.
double z = 0
The z coordinate of the point.
double w = 1
The perspective of the point.

A multiplication of a point with a matrix requires 4 values. Implementations usually normalize the point by w. It should be considered to do this here as well.

The DecomposedMatrix dictionary

The resulting values of a decomposed matrix as defined by Decomposing the matrix and used in CSS Transforms [[CSS3-TRANSFORMS]] are represented by the following DecomposedMatrix dictionary:

sequence<double> translation
Is an sequence of three double items for the translation of the matrix.
sequence<double> scale
Is an sequence of three double items for the scaling of the matrix.
sequence<double> skew
Is an sequence of three double items representing the shear of the matrix.
sequence<double> perspective
Is an sequence of four double items representing the perspective of the matrix.
sequence<double> quaternions
Is an sequence of four double items representing the quaternions for the rotation of the matrix.

It is doubtful that this can be very useful for authors. On the other hand, it allows scripted animations similar to the animations by CSS3 Transforms.

The Matrix interface

The Matrix interface represents a mathematical matrix with the purpose of describing transformations a graphical contexts. The following sections describe the details of the interface. For the full interface see Interface summary.

The constructors

A series of constructors to create a Matrix object.

Constructor()
Creates an identity matrix.
Constructor()
DOMString transformList
A DOMString of transformation functions with the syntax and specifies defined in CSS Transforms [[!CSS3-TRANSFORMS]]. One CSS pixel length maps to one unit less value in the matrix.
The DOMString must consist of a transform function list as specified by CSS Transforms.

In the following example a CSS Transform string is passed to the Matrix constructor.

var matrix = new Matrix("translate(20px,20px), scale(2,3), rotate(45deg)"

The resulting matrix is equal to the following sequence of method calls:


var matrix = new Matrix();
matrix.translateBy(20,20);
matrix.scaleNonUniformBy(2,3);
matrix.rotateBy(45);
              

Should it be postponed to avoid CSS3 Transforms dependency?

Should unit-less values (like for SVG transform presentation attribute) be allowed too? "translate(20,20)"

Constructor()
Matrix other
All element values of the current matrix are set to the element values of the other matrix.
Constructor()
DecomposedMatrix decomposedValues
Recomposes the decomposed values as specified in Recomposing the matrix and sets the element values of the current matrix.
Constructor()
Float32array array
Create an identity matrix first. An Float32array [[!TYPED-ARRAYS]] of 6 items sets the element values a to f. An array of 16 items sets the element values m11 to m44.
Constructor()
Float64array array
Create an identity matrix first. An Float64array [[!TYPED-ARRAYS]] of 6 items sets the element values a to f. An array of 16 items sets the element values m11 to m44.
Constructor()
sequence<double> numberSequence
Create an identity matrix first. A sequence of 6 items sets the element values a to f. A sequence of 16 items sets the element values m11 to m44.

Two-dimensional attributes

If a Matrix just consists of 2D transformations the 6 values a to f can represent the transformation matrix. If the Matrix object is immutable, a DOMException of type NoModificationAllowedError must be thrown on setting the attributes.

The following attributes a to f are aliases to the two-dimensional elements of the 4x4 matrix.

// These attributes are simple aliases for certain elements of the 4x4 matrix
attribute double a

Corresponds to the attribute m11 of the Matrix interface.

attribute double b

Corresponds to the attribute m12 of the Matrix interface.

attribute double c

Corresponds to the attribute m21 of the Matrix interface.

attribute double d

Corresponds to the attribute m22 of the Matrix interface.

attribute double e

Corresponds to the attribute m41 of the Matrix interface.

attribute double f

Corresponds to the attribute m42 of the Matrix interface.

Three-dimensional attributes

The following attributes m11 to m44 represent the elements of the 4x4 matrix. The coordinates are in column-major order. If the Matrix object is immutable, a DOMException of type NoModificationAllowedError must be thrown on setting the attributes.

attribute double m11

The value of the element in column 1, row 1 of the matrix.

attribute double m12

The value of the element in column 1, row 2 of the matrix.

attribute double m13

The value of the element in column 1, row 3 of the matrix.

attribute double m14

The value of the element in column 1, row 4 of the matrix.

attribute double m21

The value of the element in column 2, row 1 of the matrix.

attribute double m22

The value of the element in column 2, row 2 of the matrix.

attribute double m23

The value of the element in column 2, row 3 of the matrix.

attribute double m24

The value of the element in column 2, row 4 of the matrix.

attribute double m31

The value of the element in column 3, row 1 of the matrix.

attribute double m32

The value of the element in column 3, row 2 of the matrix.

attribute double m33

The value of the element in column 3, row 3 of the matrix.

attribute double m34

The value of the element in column 3, row 4 of the matrix.

attribute double m41

The value of the element in column 4, row 1 of the matrix.

attribute double m42

The value of the element in column 4, row 2 of the matrix.

attribute double m43

The value of the element in column 4, row 3 of the matrix.

attribute double m44

The value of the element in column 4, row 4 of the matrix.

Immutable transformation methods

The following methods do not modify the current matrix and return new Matrix object.

// Immutable transform methods
Matrix translate()
double tx
Translation value along the x-axis.
double ty
Translation value along the y-axis.
optional double tz = 0
Optional translation value along the z-axis.
Post-multiplies a translation transformation on the current matrix and returns the resulting matrix. The current matrix is not modified.
Matrix scale()
double scale
Multiplier for a uniform scale transformation.
optional double originX
Transformation origin on the x-axis. Defaulting to 0.
optional double originY
Transformation origin on the y-axis. Defaulting to 0.
Post-multiplies a uniform 2D scale transformation (m11 = m22 = scale) on the current matrix with the given origin and returns the resulting matrix. The current matrix is not modified.
Matrix scale3d()
double scale
Multiplier for a uniform scale transformation.
optional double originX
Transformation origin on the x-axis. Defaulting to 0.
optional double originY
Transformation origin on the y-axis. Defaulting to 0.
optional double originZ
Transformation origin on the z-axis. Defaulting to 0.
Post-multiplies a uniform scale transformation (m11 = m22 = m33 = scale) on the current matrix with the given origin and returns the resulting matrix. The current matrix is not modified.
Matrix scaleNonUniform()
double scaleX
Multiplier for a non-uniform scale along the x-axis.
optional double scaleY
Multiplier for a non-uniform scale along the y-axis. Defaulting to 1.
optional double scaleZ
Multiplier for a non-uniform scale along the z-axis. Defaulting to 1.
optional double originX
Transformation origin on the x-axis. Defaulting to 0.
optional double originY
Transformation origin on the y-axis. Defaulting to 0.
optional double originZ
Transformation origin on the z-axis. Defaulting to 0.
Post-multiplies a non-uniform scale transformation on the current matrix with the given origin and returns the resulting matrix. The current matrix is not modified.
Matrix rotate()
double angle
Rotation angle in degrees.
optional double originX
Transformation origin on the x-axis. Defaulting to 0.
optional double originY
Transformation origin on the y-axis. Defaulting to 0.
Post-multiplies a rotation transformation on the current matrix with the given origin and returns the resulting matrix. The current matrix is not modified.
Matrix rotateFromVector(double x, double y)
double x
The x coordinate of the vector (x,y). Must not be zero.
double y
The y coordinate of the vector (x,y). Must not be zero.
Post-multiplies a rotation transformation on the current matrix and returns the resulting matrix. The rotation angle is determined by taking (+/-) atan(y/x). The direction of the vector (x, y) determines whether the positive or negative angle value is used. The current matrix is not modified.

Is there a need for a 3D rotation?

Matrix rotateAxisAngle(double x, double y, double z, double angle)
double x
The x coordinate of the vector (x,y,z).
double y
The y coordinate of the vector (x,y,z).
double z
The z coordinate of the vector (x,y,z).
double angle
Rotation angle in degrees.
Post-multiplies a rotation transformation on the current matrix and returns the resulting matrix. The rotation of the transform is applied around the given vector. The current matrix is not modified.
Matrix skewX()
double sx
Skew angle along the x-axis in degrees.
Post-multiplies a skewX transformation on the current matrix and returns the resulting matrix. The current matrix is not modified.
Matrix skewY()
double sy
Skew angle along the y-axis in degrees.
Post-multiplies a skewX transformation on the current matrix and returns the resulting matrix.
Matrix multiply()
Matrix other
Other matrix for multiplication
Performs matrix multiplication. This matrix is post-multiplied by the other matrix, returning the resulting new matrix. The current matrix is not modified.
Matrix flipX()
Post-multiplies the transformation Matrix(-1, 0, 0, 1, 0, 0) and returns the resulting matrix. The current matrix is not modified.
Matrix flipY()
Post-multiplies the transformation Matrix(1, 0, 0, -1, 0, 0) and returns the resulting matrix. The current matrix is not modified.
Matrix? inverse()
Returns the inverted matrix of the current matrix if applicable. Otherwise null. The current matrix is not modified.
DOMException of type InvalidModificationError
Raised when the current matrix is singular.

Mutable transformation methods

The following methods do modify the current matrix. If the Matrix object is immutable, a DOMException of type NoModificationAllowedError must be thrown on calling the operations below.

// Mutable transform methods
void multiplyBy()
Matrix other
The matrix that gets post-multiplied.
The other matrix gets post-multiplied to the current matrix.
void preMultiplyBy()
Matrix other
The matrix that gets pre-multiplied.
The other matrix gets pre-multiplied to the current matrix.
void translateBy()
double tx
Translation value along the x-axis.
double ty
Translation value along the y-axis.
optional double tz = 0
Optional translation value along the z-axis.
Post-multiplies a translation transformation on the current matrix.
void scaleBy()
double scale
Multiplier for a uniform scale transformation.
optional double originX
Transformation origin on the x-axis. Defaulting to 0.
optional double originY
Transformation origin on the y-axis. Defaulting to 0.
Post-multiplies a uniform 2D scale transformation (m11 = m22 = scale) on the current matrix with the given origin.
void scale3dBy()
double scale
Multiplier for a uniform scale transformation.
optional double originX
Transformation origin on the x-axis. Defaulting to 0.
optional double originY
Transformation origin on the y-axis. Defaulting to 0.
optional double originZ
Transformation origin on the z-axis. Defaulting to 0.
Post-multiplies a uniform 2D scale transformation (m11 = m22 = m33 = scale) on the current matrix with the given origin.
void scaleNonUniformBy()
double scaleX
Multiplier for a non-uniform scale along the x-axis.
optional double scaleY
Multiplier for a non-uniform scale along the y-axis. Defaulting to 1.
optional double scaleZ
Multiplier for a non-uniform scale along the z-axis. Defaulting to 1.
optional double originX
Transformation origin on the x-axis. Defaulting to 0.
optional double originY
Transformation origin on the y-axis. Defaulting to 0.
optional double originZ
Transformation origin on the z-axis. Defaulting to 0.
Post-multiplies a non-uniform scale transformation on the current matrix with the given origin.
void rotateBy()
double angle
Rotation angle in degrees.
optional double originX
Transformation origin on the x-axis. Defaulting to 0.
optional double originY
Transformation origin on the y-axis. Defaulting to 0.
Post-multiplies a rotation transformation on the current matrix with the given origin.
void rotateFromVectorBy(double x, double y)
double x
The x coordinate of the vector (x,y). Must not be zero.
double y
The y coordinate of the vector (x,y). Must not be zero.
Post-multiplies a rotation transformation on the current matrix. The rotation angle is determined by taking (+/-) atan(y/x). The direction of the vector (x, y) determines whether the positive or negative angle value is used.

Is there a need for a 3D rotation?

void rotateAxisAngleBy(double x, double y, double z, double angle)
double x
The x coordinate of the vector (x,y,z).
double y
The y coordinate of the vector (x,y,z).
double z
The z coordinate of the vector (x,y,z).
double angle
Rotation angle in degrees.
Post-multiplies a rotation transformation on the current matrix. The rotation of the transform is applied around the given vector.
void skewXBy()
double sx
Skew angle along the x-axis in degrees.
Post-multiplies a skewX transformation on the current matrix.
void skewYBy()
double sy
Skew angle along the y-axis in degrees.
Post-multiplies a skewX transformation on the current matrix.
void invert()
Inverts the current matrix.
DOMException of type InvalidModificationError
Raised when the current matrix is singular.

Helper methods

The following helper methods do not modify the Matrix object.

// Helper methods
boolean is2D()
Returns true if m13, m14, m23, m24, m31, m32, m34, m43 are equal to zero and m33, m44 are equal to one.
double determinant()
Returns the determinant of the current matrix.
Point transformPoint()
Point point
A Point dictionary.
The point is post-multiplied on the current matrix and returns the resulting point. point is not modified.
Float32Array toFloat32Array()
Returns the serialized elements of the current matrix in column-major order as Float32Array [[!TYPED-ARRAYS]].
Float64Array toFloat64Array()
Returns the serialized elements of the current matrix in column-major order as Float64Array [[!TYPED-ARRAYS]].
DecomposedMatrix decompose()
Returns the decomposed matrix values of the current matrix as a DecomposedMatrix dictionary. The decomposing follows the algorithm of Decomposing the Matrix.
void stringifier()
Returns a string in the form of a CSS Transforms matrix function if the current matrix is a 2D transform or a CSS Transforms matrix3d else. The syntax is as specified in CSS Transforms [[!CSS3-TRANSFORMS]].

Should be stringifier;. Bug in old respec tool.

In this example a matrix is created and several methods with 2D transformations are called.

var matrix = new Matrix();
matrix.scaleBy(2);
matrix.translateBy(20,20);

The matrix.toString() returns the DOM string:

"matrix(2,0,0,2,20,20)"

For 3D operations, the stringifier returns DOM string representing a 3D matrix.

var matrix = new Matrix();
matrix.scale3dBy(2);

Calling matrix.toString() after the snippet above returns the DOM string:

"matrix3d(2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,1)"

Decomposing the Matrix

The pseudocode below is based upon the "unmatrix" method in "Graphics Gems II, edited by Jim Arvo", but modified to use Quaternions instead of Euler angles to avoid the problem of Gimbal Locks.

The following pseudocode works on a 4x4 homogeneous matrix:

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

Supporting functions (point is a 3 component vector, matrix is a 4x4 matrix):
  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

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

// 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

Recomposing the Matrix

After interpolation the resulting values are used to transform the elements user space. One way to use these values is to recompose them into a 4x4 matrix. This can be done following the pseudo-code below:

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

Interface summary

Acknowledgments

Many thanks to Dean Jackson for his initial proposal to make this specification possible.