kurbo/
affine.rs

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