affine

tape
import affine from "affine";
import gfx from "gfx";

// a quarter turn about the image's centre, then 40 pixels right
let m = affine.rotate(90.0).around(32.0, 24.0).then(affine.translate(40.0, 0.0));
let p = m.apply(affine.PointF { x: 64.0; y: 24.0; });   // (72, 56)
screen.draw_image(&photo, m);

affine is pure and freestanding: it allocates nothing and keeps no state, so every profile can use it. An Affine is the matrix CSS and SVG write as matrix(a, b, c, d, e, f):

plaintext
x' = a * x + c * y + e
y' = b * x + d * y + f

Coordinates are screen coordinates, y pointing down, so a positive angle turns clockwise on the screen, as CSS rotate() does.

FunctionGives
identity()The transform that changes nothing
translate(tx, ty), scale(sx, sy)A move, a scale about the origin
rotate(deg), rotate_rad(rad)A turn about the origin; rotate is exact for every multiple of 90 degrees
skew_x(deg), skew_y(deg)CSS skewX and skewY
m.then(n)m, then n
multiply(m, n)The product m * n: n first, then m (the order CSS lists transforms in)
m.around(x, y)m about the point (x, y) instead of the origin (CSS transform-origin)
m.inverse() -> ?AffineThe transform that undoes m; null when m squashes the plane flat (abs(m.det()) < SINGULAR_EPSILON)
m.apply(p), m.apply_vector(v)A PointF transformed; a vector (no translation)
m.bounds(r)The smallest axis-aligned RectF holding the rectangle r transformed
m.det(), m.is_identity()The determinant; whether m is exactly the identity

Order

then reads in the order things happen: affine.translate(10.0, 0.0).then(affine.rotate(90.0)) moves a point 10 to the right and then turns it, so (1, 0) ends at (0, 11). multiply follows the matrix product, the order a CSS or SVG transform attribute lists its parts: multiply(rotate(90), translate(10, 0)) is the same transform.

Exact turns

rotate(90.0) gives the matrix with 0, 1 and -1 in it, not a cosine of 6e-17, for every multiple of 90 degrees (negative ones and several turns too). A quarter turn of an image therefore lands each pixel exactly on a pixel, and gfx’s draw_image copies them unchanged.

Drawing through a transform

gfx’s s.draw_image(&src, m) draws a surface through m, which maps the source’s pixel coordinates (0..width, 0..height) to the surface’s: smoothly sampled, with anti-aliased edges, clipped.

Last modified: