affine
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):
x' = a * x + c * y + e
y' = b * x + d * y + fCoordinates are screen coordinates, y pointing down, so a positive angle
turns clockwise on the screen, as CSS rotate() does.
| Function | Gives |
|---|---|
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() -> ?Affine | The 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: