An affine transform is a special 3x3 matrix used to apply translation, rotation, shearing or skew, and scaling to coordinate systems in two dimensional graphic contexts.
An affine transform is a special 3x3 matrix used to apply translation, rotation, shearing or skew, and scaling to coordinate systems in two dimensional graphic contexts. Parallel lines will remain parallel so perspective cannot be applied. The angle between lines that are not parallel may be changed by skew operations.
a c x
b d y
0 0 1
The bottom row of an affine transform matrix is always 0 0 1
, so an affine transform is described by only six values. Transforming a point is the basis of all operations with affine transforms.
new.x = old.x * at.a + old.y * at.c + at.x
new.y = old.x * at.b + old.y * at.d + at.y
The identity transform will transform a point to itself.
1 0 0
0 1 0
0 0 1
The x and y values (often referred to as tx and ty) will translate a point. The a b c d values apply rotation, shear and scale to a point. The following affine transform will rotate a point by r
radians around the origin of the coordinate space.
cos(r) sin(r) 0
-sin(r) cos(r) 0
0 0 1
To apply shear or skew by rx
and ry
radians use the following matrix. With a positive rx
value vertical lines will appear to lean to the left and with a positive ry
value horizontal lines will appear to slide down towards the right.
1 tan(rx) 0
tan(ry) 1 0
0 0 1
Scaling is applied more directly. To scale by sx
and sy
use the following matrix. Using a negative scale will flip or reflect the coordinate system around an axis. For example, using -1 for sy
in this matrix will flip a coordinate system upside down.
sx 0 0
0 sy 0
0 0 1
To apply several effects at once, several affine transform matrices can be concatenated together. The order of concatenation matters. The order of least surprise is translate, rotate, skew then scale. That is also how the transform will be perceived, regardless of the original order of operations.
In order to rotate around a point other than the origin, a rotate operation is bracketed by translate operations. The second translate will be in the direction of the rotate, since the translate will be concatenated after the rotate.
translate(-x,-y) rotate(r) translate(x,y)
In contexts where the transform is related to a specific visual element, such as css or CALayer, there can be a transform origin that implicitly translates the affine transform. Be aware of this if you must translate between coordinate spaces.
To concatenate two affine transforms u
and v
into a new
transform do the following.
new.a = u.a * v.a + u.c * v.b
new.b = u.b * v.a + u.d * v.b
new.c = u.a * v.c + u.c * v.d
new.d = u.b * v.c + u.d * v.d
new.x = u.a * v.x + u.c * v.y + u.x
new.y = u.b * v.x + u.d * v.y + u.y
The inverse of a matrix can be calculated if the determinant is not zero. Transforming a point by an affine transform then by the inverse of the affine transform will restore the original point.
determinant = a*d - b*c
inverse.a = d/determinant
inverse.b = -b/determinant
inverse.c = -c/determinant
inverse.d = a/determinant
inverse.x = (c*y - d*x)/determinant
inverse.y = (b*x - a*y)/determinant
The translation of an affine transform is always available as the x
and y
values. Other inputs can also be extracted. For an affine transform that is known to have no skew, the rotation and scale can be calculated as follows.
r = atan2( c , d )
sx = sqrt( a*a + b*b )
sy = sqrt( d*d + c*c )
The rotation, skew and scale may be calculated for an arbitrary affine transform. These will be the perceptual values in the order of least surprise, not the input values. When an affine transform is rotated and skewed, or skewed along both axis, there will be two rotation values. The least of these will be the perceptual rotation and the difference is the skew.
if ( a*d < 0 ) {
if ( a < 0 ) { flip = 1; a = -a; b = -b; }
else { flip = 2; d = -d; c = -c; }
} else {
flip = 0
}
rx = atan2( -b , a )
ry = atan2( c , d )
if ( abs( rx ) < abs( ry ) ) {
r = rx
skew_rx = 0
skew_ry = ry - rx
scale_x = sqrt( a*a + b*b )
scale_y = d / (cos(r) - sin(r)*tan(skew_ry))
} else {
r = ry
skew_ry = 0
skew_rx = ry - rx
scale_y = sqrt( d*d + c*c )
scale_x = a / (cos(r) + sin(r)*tan(skew_rx))
}
if ( 1 == flip ) scale_x = -scale_x;
if ( 2 == flip ) scale_y = -scale_y;
The original matrix will equal the concatenation of the following effects.
translate(x,y) rotate(r) skew(skew_rx,skew_ry) scale(scale_x,scale_y)
This can be used to verify that a transform will be perceived as intended. When rotate, skew and scale operations are all concatenated, the scaling may be distorted if the skew and rotation are not complementary.
Affine transform documentation for some popular platforms: