1// Copyright 2018 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Affine transforms.
5
6use core::ops::{Mul, MulAssign};
7
8use crate::{Point, Rect, Vec2};
9
10#[cfg(not(feature = "std"))]
11use crate::common::FloatFuncs;
12
13/// A 2D affine transform.
14#[derive(Clone, Copy, Debug, PartialEq)]
15#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct Affine([f64; 6]);
18
19impl Affine {
20 /// The identity transform.
21 pub const IDENTITY: Affine = Affine::scale(1.0);
22
23 /// A transform that is flipped on the y-axis. Useful for converting between
24 /// y-up and y-down spaces.
25 pub const FLIP_Y: Affine = Affine::new([1.0, 0., 0., -1.0, 0., 0.]);
26
27 /// A transform that is flipped on the x-axis.
28 pub const FLIP_X: Affine = Affine::new([-1.0, 0., 0., 1.0, 0., 0.]);
29
30 /// Construct an affine transform from coefficients.
31 ///
32 /// If the coefficients are `(a, b, c, d, e, f)`, then the resulting
33 /// transformation represents this augmented matrix:
34 ///
35 /// ```text
36 /// | a c e |
37 /// | b d f |
38 /// | 0 0 1 |
39 /// ```
40 ///
41 /// Note that this convention is transposed from PostScript and
42 /// Direct2D, but is consistent with the
43 /// [Wikipedia](https://en.wikipedia.org/wiki/Affine_transformation)
44 /// formulation of affine transformation as augmented matrix. The
45 /// idea is that `(A * B) * v == A * (B * v)`, where `*` is the
46 /// [`Mul`](std::ops::Mul) trait.
47 #[inline]
48 pub const fn new(c: [f64; 6]) -> Affine {
49 Affine(c)
50 }
51
52 /// An affine transform representing uniform scaling.
53 #[inline]
54 pub const fn scale(s: f64) -> Affine {
55 Affine([s, 0.0, 0.0, s, 0.0, 0.0])
56 }
57
58 /// An affine transform representing non-uniform scaling
59 /// with different scale values for x and y
60 #[inline]
61 pub const fn scale_non_uniform(s_x: f64, s_y: f64) -> Affine {
62 Affine([s_x, 0.0, 0.0, s_y, 0.0, 0.0])
63 }
64
65 /// An affine transform representing rotation.
66 ///
67 /// The convention for rotation is that a positive angle rotates a
68 /// positive X direction into positive Y. Thus, in a Y-down coordinate
69 /// system (as is common for graphics), it is a clockwise rotation, and
70 /// in Y-up (traditional for math), it is anti-clockwise.
71 ///
72 /// The angle, `th`, is expressed in radians.
73 #[inline]
74 pub fn rotate(th: f64) -> Affine {
75 let (s, c) = th.sin_cos();
76 Affine([c, s, -s, c, 0.0, 0.0])
77 }
78
79 /// An affine transform representing a rotation of `th` radians about `center`.
80 ///
81 /// See [`Affine::rotate()`] for more info.
82 #[inline]
83 pub fn rotate_about(th: f64, center: Point) -> Affine {
84 let center = center.to_vec2();
85 Self::translate(-center)
86 .then_rotate(th)
87 .then_translate(center)
88 }
89
90 /// An affine transform representing translation.
91 #[inline]
92 pub fn translate<V: Into<Vec2>>(p: V) -> Affine {
93 let p = p.into();
94 Affine([1.0, 0.0, 0.0, 1.0, p.x, p.y])
95 }
96
97 /// An affine transformation representing a skew.
98 ///
99 /// The `skew_x` and `skew_y` parameters represent skew factors for the
100 /// horizontal and vertical directions, respectively.
101 ///
102 /// This is commonly used to generate a faux oblique transform for
103 /// font rendering. In this case, you can slant the glyph 20 degrees
104 /// clockwise in the horizontal direction (assuming a Y-up coordinate
105 /// system):
106 ///
107 /// ```
108 /// let oblique_transform = kurbo::Affine::skew(20f64.to_radians().tan(), 0.0);
109 /// ```
110 #[inline]
111 pub fn skew(skew_x: f64, skew_y: f64) -> Affine {
112 Affine([1.0, skew_y, skew_x, 1.0, 0.0, 0.0])
113 }
114
115 /// Create an affine transform that represents reflection about the line `point + direction * t, t in (-infty, infty)`
116 ///
117 /// # Examples
118 ///
119 /// ```
120 /// # use kurbo::{Point, Vec2, Affine};
121 /// # fn assert_near(p0: Point, p1: Point) {
122 /// # assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
123 /// # }
124 /// let point = Point::new(1., 0.);
125 /// let vec = Vec2::new(1., 1.);
126 /// let map = Affine::reflect(point, vec);
127 /// assert_near(map * Point::new(1., 0.), Point::new(1., 0.));
128 /// assert_near(map * Point::new(2., 1.), Point::new(2., 1.));
129 /// assert_near(map * Point::new(2., 2.), Point::new(3., 1.));
130 /// ```
131 #[inline]
132 #[must_use]
133 pub fn reflect(point: impl Into<Point>, direction: impl Into<Vec2>) -> Self {
134 let point = point.into();
135 let direction = direction.into();
136
137 let n = Vec2 {
138 x: direction.y,
139 y: -direction.x,
140 }
141 .normalize();
142
143 // Compute Householder reflection matrix
144 let x2 = n.x * n.x;
145 let xy = n.x * n.y;
146 let y2 = n.y * n.y;
147 // Here we also add in the post translation, because it doesn't require any further calc.
148 let aff = Affine::new([
149 1. - 2. * x2,
150 -2. * xy,
151 -2. * xy,
152 1. - 2. * y2,
153 point.x,
154 point.y,
155 ]);
156 aff.pre_translate(-point.to_vec2())
157 }
158
159 /// A [rotation] by `th` followed by `self`.
160 ///
161 /// Equivalent to `self * Affine::rotate(th)`
162 ///
163 /// [rotation]: Affine::rotate
164 #[inline]
165 #[must_use]
166 pub fn pre_rotate(self, th: f64) -> Self {
167 self * Affine::rotate(th)
168 }
169
170 /// A [rotation] by `th` about `center` followed by `self`.
171 ///
172 /// Equivalent to `self * Affine::rotate_about(th, center)`
173 ///
174 /// [rotation]: Affine::rotate_about
175 #[inline]
176 #[must_use]
177 pub fn pre_rotate_about(self, th: f64, center: Point) -> Self {
178 Affine::rotate_about(th, center) * self
179 }
180
181 /// A [scale] by `scale` followed by `self`.
182 ///
183 /// Equivalent to `self * Affine::scale(scale)`
184 ///
185 /// [scale]: Affine::scale
186 #[inline]
187 #[must_use]
188 pub fn pre_scale(self, scale: f64) -> Self {
189 self * Affine::scale(scale)
190 }
191
192 /// A [scale] by `(scale_x, scale_y)` followed by `self`.
193 ///
194 /// Equivalent to `self * Affine::scale_non_uniform(scale_x, scale_y)`
195 ///
196 /// [scale]: Affine::scale_non_uniform
197 #[inline]
198 #[must_use]
199 pub fn pre_scale_non_uniform(self, scale_x: f64, scale_y: f64) -> Self {
200 self * Affine::scale_non_uniform(scale_x, scale_y)
201 }
202
203 /// A [translation] of `trans` followed by `self`.
204 ///
205 /// Equivalent to `self * Affine::translate(trans)`
206 ///
207 /// [translation]: Affine::translate
208 #[inline]
209 #[must_use]
210 pub fn pre_translate(self, trans: Vec2) -> Self {
211 self * Affine::translate(trans)
212 }
213
214 /// `self` followed by a [rotation] of `th`.
215 ///
216 /// Equivalent to `Affine::rotate(th) * self`
217 ///
218 /// [rotation]: Affine::rotate
219 #[inline]
220 #[must_use]
221 pub fn then_rotate(self, th: f64) -> Self {
222 Affine::rotate(th) * self
223 }
224
225 /// `self` followed by a [rotation] of `th` about `center`.
226 ///
227 /// Equivalent to `Affine::rotate_about(th, center) * self`
228 ///
229 /// [rotation]: Affine::rotate_about
230 #[inline]
231 #[must_use]
232 pub fn then_rotate_about(self, th: f64, center: Point) -> Self {
233 Affine::rotate_about(th, center) * self
234 }
235
236 /// `self` followed by a [scale] of `scale`.
237 ///
238 /// Equivalent to `Affine::scale(scale) * self`
239 ///
240 /// [scale]: Affine::scale
241 #[inline]
242 #[must_use]
243 pub fn then_scale(self, scale: f64) -> Self {
244 Affine::scale(scale) * self
245 }
246
247 /// `self` followed by a [scale] of `(scale_x, scale_y)`.
248 ///
249 /// Equivalent to `Affine::scale_non_uniform(scale_x, scale_y) * self`
250 ///
251 /// [scale]: Affine::scale_non_uniform
252 #[inline]
253 #[must_use]
254 pub fn then_scale_non_uniform(self, scale_x: f64, scale_y: f64) -> Self {
255 Affine::scale_non_uniform(scale_x, scale_y) * self
256 }
257
258 /// `self` followed by a translation of `trans`.
259 ///
260 /// Equivalent to `Affine::translate(trans) * self`
261 ///
262 /// [translation]: Affine::translate
263 #[inline]
264 #[must_use]
265 pub fn then_translate(mut self, trans: Vec2) -> Self {
266 self.0[4] += trans.x;
267 self.0[5] += trans.y;
268 self
269 }
270
271 /// Creates an affine transformation that takes the unit square to the given rectangle.
272 ///
273 /// Useful when you want to draw into the unit square but have your output fill any rectangle.
274 /// In this case push the `Affine` onto the transform stack.
275 pub fn map_unit_square(rect: Rect) -> Affine {
276 Affine([rect.width(), 0., 0., rect.height(), rect.x0, rect.y0])
277 }
278
279 /// Get the coefficients of the transform.
280 #[inline]
281 pub fn as_coeffs(self) -> [f64; 6] {
282 self.0
283 }
284
285 /// Compute the determinant of this transform.
286 pub fn determinant(self) -> f64 {
287 self.0[0] * self.0[3] - self.0[1] * self.0[2]
288 }
289
290 /// Compute the inverse transform.
291 ///
292 /// Produces NaN values when the determinant is zero.
293 pub fn inverse(self) -> Affine {
294 let inv_det = self.determinant().recip();
295 Affine([
296 inv_det * self.0[3],
297 -inv_det * self.0[1],
298 -inv_det * self.0[2],
299 inv_det * self.0[0],
300 inv_det * (self.0[2] * self.0[5] - self.0[3] * self.0[4]),
301 inv_det * (self.0[1] * self.0[4] - self.0[0] * self.0[5]),
302 ])
303 }
304
305 /// Compute the bounding box of a transformed rectangle.
306 ///
307 /// Returns the minimal `Rect` that encloses the given `Rect` after affine transformation.
308 /// If the transform is axis-aligned, then this bounding box is "tight", in other words the
309 /// returned `Rect` is the transformed rectangle.
310 ///
311 /// The returned rectangle always has non-negative width and height.
312 pub fn transform_rect_bbox(self, rect: Rect) -> Rect {
313 let p00 = self * Point::new(rect.x0, rect.y0);
314 let p01 = self * Point::new(rect.x0, rect.y1);
315 let p10 = self * Point::new(rect.x1, rect.y0);
316 let p11 = self * Point::new(rect.x1, rect.y1);
317 Rect::from_points(p00, p01).union(Rect::from_points(p10, p11))
318 }
319
320 /// Is this map [finite]?
321 ///
322 /// [finite]: f64::is_finite
323 #[inline]
324 pub fn is_finite(&self) -> bool {
325 self.0[0].is_finite()
326 && self.0[1].is_finite()
327 && self.0[2].is_finite()
328 && self.0[3].is_finite()
329 && self.0[4].is_finite()
330 && self.0[5].is_finite()
331 }
332
333 /// Is this map [NaN]?
334 ///
335 /// [NaN]: f64::is_nan
336 #[inline]
337 pub fn is_nan(&self) -> bool {
338 self.0[0].is_nan()
339 || self.0[1].is_nan()
340 || self.0[2].is_nan()
341 || self.0[3].is_nan()
342 || self.0[4].is_nan()
343 || self.0[5].is_nan()
344 }
345
346 /// Compute the singular value decomposition of the linear transformation (ignoring the
347 /// translation).
348 ///
349 /// All non-degenerate linear transformations can be represented as
350 ///
351 /// 1. a rotation about the origin.
352 /// 2. a scaling along the x and y axes
353 /// 3. another rotation about the origin
354 ///
355 /// composed together. Decomposing a 2x2 matrix in this way is called a "singular value
356 /// decomposition" and is written `U Σ V^T`, where U and V^T are orthogonal (rotations) and Σ
357 /// is a diagonal matrix (a scaling).
358 ///
359 /// Since currently this function is used to calculate ellipse radii and rotation from an
360 /// affine map on the unit circle, we don't calculate V^T, since a rotation of the unit (or
361 /// any) circle about its center always results in the same circle. This is the reason that an
362 /// ellipse mapped using an affine map is always an ellipse.
363 ///
364 /// Will return NaNs if the matrix (or equivalently the linear map) is singular.
365 ///
366 /// First part of the return tuple is the scaling, second part is the angle of rotation (in
367 /// radians)
368 #[inline]
369 pub(crate) fn svd(self) -> (Vec2, f64) {
370 let a = self.0[0];
371 let a2 = a * a;
372 let b = self.0[1];
373 let b2 = b * b;
374 let c = self.0[2];
375 let c2 = c * c;
376 let d = self.0[3];
377 let d2 = d * d;
378 let ab = a * b;
379 let cd = c * d;
380 let angle = 0.5 * (2.0 * (ab + cd)).atan2(a2 - b2 + c2 - d2);
381 let s1 = a2 + b2 + c2 + d2;
382 let s2 = ((a2 - b2 + c2 - d2).powi(2) + 4.0 * (ab + cd).powi(2)).sqrt();
383 (
384 Vec2 {
385 x: (0.5 * (s1 + s2)).sqrt(),
386 y: (0.5 * (s1 - s2)).sqrt(),
387 },
388 angle,
389 )
390 }
391
392 /// Returns the translation part of this affine map (`(self.0[4], self.0[5])`).
393 #[inline]
394 pub fn translation(self) -> Vec2 {
395 Vec2 {
396 x: self.0[4],
397 y: self.0[5],
398 }
399 }
400
401 /// Replaces the translation portion of this affine map
402 ///
403 /// The translation can be seen as being applied after the linear part of the map.
404 #[must_use]
405 #[inline]
406 pub fn with_translation(mut self, trans: Vec2) -> Affine {
407 self.0[4] = trans.x;
408 self.0[5] = trans.y;
409 self
410 }
411}
412
413impl Default for Affine {
414 #[inline]
415 fn default() -> Affine {
416 Affine::IDENTITY
417 }
418}
419
420impl Mul<Point> for Affine {
421 type Output = Point;
422
423 #[inline]
424 fn mul(self, other: Point) -> Point {
425 Point::new(
426 self.0[0] * other.x + self.0[2] * other.y + self.0[4],
427 self.0[1] * other.x + self.0[3] * other.y + self.0[5],
428 )
429 }
430}
431
432impl Mul for Affine {
433 type Output = Affine;
434
435 #[inline]
436 fn mul(self, other: Affine) -> Affine {
437 Affine([
438 self.0[0] * other.0[0] + self.0[2] * other.0[1],
439 self.0[1] * other.0[0] + self.0[3] * other.0[1],
440 self.0[0] * other.0[2] + self.0[2] * other.0[3],
441 self.0[1] * other.0[2] + self.0[3] * other.0[3],
442 self.0[0] * other.0[4] + self.0[2] * other.0[5] + self.0[4],
443 self.0[1] * other.0[4] + self.0[3] * other.0[5] + self.0[5],
444 ])
445 }
446}
447
448impl MulAssign for Affine {
449 #[inline]
450 fn mul_assign(&mut self, other: Affine) {
451 *self = self.mul(other);
452 }
453}
454
455impl Mul<Affine> for f64 {
456 type Output = Affine;
457
458 #[inline]
459 fn mul(self, other: Affine) -> Affine {
460 Affine([
461 self * other.0[0],
462 self * other.0[1],
463 self * other.0[2],
464 self * other.0[3],
465 self * other.0[4],
466 self * other.0[5],
467 ])
468 }
469}
470
471// Conversions to and from mint
472#[cfg(feature = "mint")]
473impl From<Affine> for mint::ColumnMatrix2x3<f64> {
474 #[inline]
475 fn from(a: Affine) -> mint::ColumnMatrix2x3<f64> {
476 mint::ColumnMatrix2x3 {
477 x: mint::Vector2 {
478 x: a.0[0],
479 y: a.0[1],
480 },
481 y: mint::Vector2 {
482 x: a.0[2],
483 y: a.0[3],
484 },
485 z: mint::Vector2 {
486 x: a.0[4],
487 y: a.0[5],
488 },
489 }
490 }
491}
492
493#[cfg(feature = "mint")]
494impl From<mint::ColumnMatrix2x3<f64>> for Affine {
495 #[inline]
496 fn from(m: mint::ColumnMatrix2x3<f64>) -> Affine {
497 Affine([m.x.x, m.x.y, m.y.x, m.y.y, m.z.x, m.z.y])
498 }
499}
500
501#[cfg(test)]
502mod tests {
503 use crate::{Affine, Point, Vec2};
504 use std::f64::consts::PI;
505
506 fn assert_near(p0: Point, p1: Point) {
507 assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
508 }
509
510 fn affine_assert_near(a0: Affine, a1: Affine) {
511 for i in 0..6 {
512 assert!((a0.0[i] - a1.0[i]).abs() < 1e-9, "{a0:?} != {a1:?}");
513 }
514 }
515
516 #[test]
517 fn affine_basic() {
518 let p = Point::new(3.0, 4.0);
519
520 assert_near(Affine::default() * p, p);
521 assert_near(Affine::scale(2.0) * p, Point::new(6.0, 8.0));
522 assert_near(Affine::rotate(0.0) * p, p);
523 assert_near(Affine::rotate(PI / 2.0) * p, Point::new(-4.0, 3.0));
524 assert_near(Affine::translate((5.0, 6.0)) * p, Point::new(8.0, 10.0));
525 assert_near(Affine::skew(0.0, 0.0) * p, p);
526 assert_near(Affine::skew(2.0, 4.0) * p, Point::new(11.0, 16.0));
527 }
528
529 #[test]
530 fn affine_mul() {
531 let a1 = Affine::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
532 let a2 = Affine::new([0.1, 1.2, 2.3, 3.4, 4.5, 5.6]);
533
534 let px = Point::new(1.0, 0.0);
535 let py = Point::new(0.0, 1.0);
536 let pxy = Point::new(1.0, 1.0);
537 assert_near(a1 * (a2 * px), (a1 * a2) * px);
538 assert_near(a1 * (a2 * py), (a1 * a2) * py);
539 assert_near(a1 * (a2 * pxy), (a1 * a2) * pxy);
540 }
541
542 #[test]
543 fn affine_inv() {
544 let a = Affine::new([0.1, 1.2, 2.3, 3.4, 4.5, 5.6]);
545 let a_inv = a.inverse();
546
547 let px = Point::new(1.0, 0.0);
548 let py = Point::new(0.0, 1.0);
549 let pxy = Point::new(1.0, 1.0);
550 assert_near(a * (a_inv * px), px);
551 assert_near(a * (a_inv * py), py);
552 assert_near(a * (a_inv * pxy), pxy);
553 assert_near(a_inv * (a * px), px);
554 assert_near(a_inv * (a * py), py);
555 assert_near(a_inv * (a * pxy), pxy);
556 }
557
558 #[test]
559 fn reflection() {
560 affine_assert_near(
561 Affine::reflect(Point::ZERO, (1., 0.)),
562 Affine::new([1., 0., 0., -1., 0., 0.]),
563 );
564 affine_assert_near(
565 Affine::reflect(Point::ZERO, (0., 1.)),
566 Affine::new([-1., 0., 0., 1., 0., 0.]),
567 );
568 // y = x
569 affine_assert_near(
570 Affine::reflect(Point::ZERO, (1., 1.)),
571 Affine::new([0., 1., 1., 0., 0., 0.]),
572 );
573
574 // no translate
575 let point = Point::new(0., 0.);
576 let vec = Vec2::new(1., 1.);
577 let map = Affine::reflect(point, vec);
578 assert_near(map * Point::new(0., 0.), Point::new(0., 0.));
579 assert_near(map * Point::new(1., 1.), Point::new(1., 1.));
580 assert_near(map * Point::new(1., 2.), Point::new(2., 1.));
581
582 // with translate
583 let point = Point::new(1., 0.);
584 let vec = Vec2::new(1., 1.);
585 let map = Affine::reflect(point, vec);
586 assert_near(map * Point::new(1., 0.), Point::new(1., 0.));
587 assert_near(map * Point::new(2., 1.), Point::new(2., 1.));
588 assert_near(map * Point::new(2., 2.), Point::new(3., 1.));
589 }
590}
591