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 /// A rotation by `th` followed by `self`.
116 ///
117 /// Equivalent to `self * Affine::rotate(th)`
118 #[inline]
119 #[must_use]
120 pub fn pre_rotate(self, th: f64) -> Self {
121 self * Affine::rotate(th)
122 }
123
124 /// A rotation by `th` about `center` followed by `self`.
125 ///
126 /// Equivalent to `self * Affine::rotate_about(th)`
127 #[inline]
128 #[must_use]
129 pub fn pre_rotate_about(self, th: f64, center: Point) -> Self {
130 Affine::rotate_about(th, center) * self
131 }
132
133 /// A scale by `scale` followed by `self`.
134 ///
135 /// Equivalent to `self * Affine::scale(scale)`
136 #[inline]
137 #[must_use]
138 pub fn pre_scale(self, scale: f64) -> Self {
139 self * Affine::scale(scale)
140 }
141
142 /// A scale by `(scale_x, scale_y)` followed by `self`.
143 ///
144 /// Equivalent to `self * Affine::scale_non_uniform(scale_x, scale_y)`
145 #[inline]
146 #[must_use]
147 pub fn pre_scale_non_uniform(self, scale_x: f64, scale_y: f64) -> Self {
148 self * Affine::scale_non_uniform(scale_x, scale_y)
149 }
150
151 /// A translation of `trans` followed by `self`.
152 ///
153 /// Equivalent to `self * Affine::translate(trans)`
154 #[inline]
155 #[must_use]
156 pub fn pre_translate(self, trans: Vec2) -> Self {
157 self * Affine::translate(trans)
158 }
159
160 /// `self` followed by a rotation of `th`.
161 ///
162 /// Equivalent to `Affine::rotate(th) * self`
163 #[inline]
164 #[must_use]
165 pub fn then_rotate(self, th: f64) -> Self {
166 Affine::rotate(th) * self
167 }
168
169 /// `self` followed by a rotation of `th` about `center.
170 ///
171 /// Equivalent to `Affine::rotate_about(th, center) * self`
172 #[inline]
173 #[must_use]
174 pub fn then_rotate_about(self, th: f64, center: Point) -> Self {
175 Affine::rotate_about(th, center) * self
176 }
177
178 /// `self` followed by a scale of `scale`.
179 ///
180 /// Equivalent to `Affine::scale(scale) * self`
181 #[inline]
182 #[must_use]
183 pub fn then_scale(self, scale: f64) -> Self {
184 Affine::scale(scale) * self
185 }
186
187 /// `self` followed by a scale of `(scale_x, scale_y)`.
188 ///
189 /// Equivalent to `Affine::scale_non_uniform(scale_x, scale_y) * self`
190 #[inline]
191 #[must_use]
192 pub fn then_scale_non_uniform(self, scale_x: f64, scale_y: f64) -> Self {
193 Affine::scale_non_uniform(scale_x, scale_y) * self
194 }
195
196 /// `self` followed by a translation of `trans`.
197 ///
198 /// Equivalent to `Affine::translate(trans) * self`
199 #[inline]
200 #[must_use]
201 pub fn then_translate(mut self, trans: Vec2) -> Self {
202 self.0[4] += trans.x;
203 self.0[5] += trans.y;
204 self
205 }
206
207 /// Creates an affine transformation that takes the unit square to the given rectangle.
208 ///
209 /// Useful when you want to draw into the unit square but have your output fill any rectangle.
210 /// In this case push the `Affine` onto the transform stack.
211 pub fn map_unit_square(rect: Rect) -> Affine {
212 Affine([rect.width(), 0., 0., rect.height(), rect.x0, rect.y0])
213 }
214
215 /// Get the coefficients of the transform.
216 #[inline]
217 pub fn as_coeffs(self) -> [f64; 6] {
218 self.0
219 }
220
221 /// Compute the determinant of this transform.
222 pub fn determinant(self) -> f64 {
223 self.0[0] * self.0[3] - self.0[1] * self.0[2]
224 }
225
226 /// Compute the inverse transform.
227 ///
228 /// Produces NaN values when the determinant is zero.
229 pub fn inverse(self) -> Affine {
230 let inv_det = self.determinant().recip();
231 Affine([
232 inv_det * self.0[3],
233 -inv_det * self.0[1],
234 -inv_det * self.0[2],
235 inv_det * self.0[0],
236 inv_det * (self.0[2] * self.0[5] - self.0[3] * self.0[4]),
237 inv_det * (self.0[1] * self.0[4] - self.0[0] * self.0[5]),
238 ])
239 }
240
241 /// Compute the bounding box of a transformed rectangle.
242 ///
243 /// Returns the minimal `Rect` that encloses the given `Rect` after affine transformation.
244 /// If the transform is axis-aligned, then this bounding box is "tight", in other words the
245 /// returned `Rect` is the transformed rectangle.
246 ///
247 /// The returned rectangle always has non-negative width and height.
248 pub fn transform_rect_bbox(self, rect: Rect) -> Rect {
249 let p00 = self * Point::new(rect.x0, rect.y0);
250 let p01 = self * Point::new(rect.x0, rect.y1);
251 let p10 = self * Point::new(rect.x1, rect.y0);
252 let p11 = self * Point::new(rect.x1, rect.y1);
253 Rect::from_points(p00, p01).union(Rect::from_points(p10, p11))
254 }
255
256 /// Is this map finite?
257 #[inline]
258 pub fn is_finite(&self) -> bool {
259 self.0[0].is_finite()
260 && self.0[1].is_finite()
261 && self.0[2].is_finite()
262 && self.0[3].is_finite()
263 && self.0[4].is_finite()
264 && self.0[5].is_finite()
265 }
266
267 /// Is this map NaN?
268 #[inline]
269 pub fn is_nan(&self) -> bool {
270 self.0[0].is_nan()
271 || self.0[1].is_nan()
272 || self.0[2].is_nan()
273 || self.0[3].is_nan()
274 || self.0[4].is_nan()
275 || self.0[5].is_nan()
276 }
277
278 /// Compute the singular value decomposition of the linear transformation (ignoring the
279 /// translation).
280 ///
281 /// All non-degenerate linear transformations can be represented as
282 ///
283 /// 1. a rotation about the origin.
284 /// 2. a scaling along the x and y axes
285 /// 3. another rotation about the origin
286 ///
287 /// composed together. Decomposing a 2x2 matrix in this way is called a "singular value
288 /// decomposition" and is written `U Σ V^T`, where U and V^T are orthogonal (rotations) and Σ
289 /// is a diagonal matrix (a scaling).
290 ///
291 /// Since currently this function is used to calculate ellipse radii and rotation from an
292 /// affine map on the unit circle, we don't calculate V^T, since a rotation of the unit (or
293 /// any) circle about its center always results in the same circle. This is the reason that an
294 /// ellipse mapped using an affine map is always an ellipse.
295 ///
296 /// Will return NaNs if the matrix (or equivalently the linear map) is singular.
297 ///
298 /// First part of the return tuple is the scaling, second part is the angle of rotation (in
299 /// radians)
300 #[inline]
301 pub(crate) fn svd(self) -> (Vec2, f64) {
302 let a = self.0[0];
303 let a2 = a * a;
304 let b = self.0[1];
305 let b2 = b * b;
306 let c = self.0[2];
307 let c2 = c * c;
308 let d = self.0[3];
309 let d2 = d * d;
310 let ab = a * b;
311 let cd = c * d;
312 let angle = 0.5 * (2.0 * (ab + cd)).atan2(a2 - b2 + c2 - d2);
313 let s1 = a2 + b2 + c2 + d2;
314 let s2 = ((a2 - b2 + c2 - d2).powi(2) + 4.0 * (ab + cd).powi(2)).sqrt();
315 (
316 Vec2 {
317 x: (0.5 * (s1 + s2)).sqrt(),
318 y: (0.5 * (s1 - s2)).sqrt(),
319 },
320 angle,
321 )
322 }
323
324 /// Returns the translation part of this affine map (`(self.0[4], self.0[5])`).
325 #[inline]
326 pub fn translation(self) -> Vec2 {
327 Vec2 {
328 x: self.0[4],
329 y: self.0[5],
330 }
331 }
332
333 /// Replaces the translation portion of this affine map
334 ///
335 /// The translation can be seen as being applied after the linear part of the map.
336 #[must_use]
337 #[inline]
338 pub fn with_translation(mut self, trans: Vec2) -> Affine {
339 self.0[4] = trans.x;
340 self.0[5] = trans.y;
341 self
342 }
343}
344
345impl Default for Affine {
346 #[inline]
347 fn default() -> Affine {
348 Affine::IDENTITY
349 }
350}
351
352impl Mul<Point> for Affine {
353 type Output = Point;
354
355 #[inline]
356 fn mul(self, other: Point) -> Point {
357 Point::new(
358 self.0[0] * other.x + self.0[2] * other.y + self.0[4],
359 self.0[1] * other.x + self.0[3] * other.y + self.0[5],
360 )
361 }
362}
363
364impl Mul for Affine {
365 type Output = Affine;
366
367 #[inline]
368 fn mul(self, other: Affine) -> Affine {
369 Affine([
370 self.0[0] * other.0[0] + self.0[2] * other.0[1],
371 self.0[1] * other.0[0] + self.0[3] * other.0[1],
372 self.0[0] * other.0[2] + self.0[2] * other.0[3],
373 self.0[1] * other.0[2] + self.0[3] * other.0[3],
374 self.0[0] * other.0[4] + self.0[2] * other.0[5] + self.0[4],
375 self.0[1] * other.0[4] + self.0[3] * other.0[5] + self.0[5],
376 ])
377 }
378}
379
380impl MulAssign for Affine {
381 #[inline]
382 fn mul_assign(&mut self, other: Affine) {
383 *self = self.mul(other);
384 }
385}
386
387impl Mul<Affine> for f64 {
388 type Output = Affine;
389
390 #[inline]
391 fn mul(self, other: Affine) -> Affine {
392 Affine([
393 self * other.0[0],
394 self * other.0[1],
395 self * other.0[2],
396 self * other.0[3],
397 self * other.0[4],
398 self * other.0[5],
399 ])
400 }
401}
402
403// Conversions to and from mint
404#[cfg(feature = "mint")]
405impl From<Affine> for mint::ColumnMatrix2x3<f64> {
406 #[inline]
407 fn from(a: Affine) -> mint::ColumnMatrix2x3<f64> {
408 mint::ColumnMatrix2x3 {
409 x: mint::Vector2 {
410 x: a.0[0],
411 y: a.0[1],
412 },
413 y: mint::Vector2 {
414 x: a.0[2],
415 y: a.0[3],
416 },
417 z: mint::Vector2 {
418 x: a.0[4],
419 y: a.0[5],
420 },
421 }
422 }
423}
424
425#[cfg(feature = "mint")]
426impl From<mint::ColumnMatrix2x3<f64>> for Affine {
427 #[inline]
428 fn from(m: mint::ColumnMatrix2x3<f64>) -> Affine {
429 Affine([m.x.x, m.x.y, m.y.x, m.y.y, m.z.x, m.z.y])
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use crate::{Affine, Point};
436 use std::f64::consts::PI;
437
438 fn assert_near(p0: Point, p1: Point) {
439 assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
440 }
441
442 #[test]
443 fn affine_basic() {
444 let p = Point::new(3.0, 4.0);
445
446 assert_near(Affine::default() * p, p);
447 assert_near(Affine::scale(2.0) * p, Point::new(6.0, 8.0));
448 assert_near(Affine::rotate(0.0) * p, p);
449 assert_near(Affine::rotate(PI / 2.0) * p, Point::new(-4.0, 3.0));
450 assert_near(Affine::translate((5.0, 6.0)) * p, Point::new(8.0, 10.0));
451 assert_near(Affine::skew(0.0, 0.0) * p, p);
452 assert_near(Affine::skew(2.0, 4.0) * p, Point::new(11.0, 16.0));
453 }
454
455 #[test]
456 fn affine_mul() {
457 let a1 = Affine::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
458 let a2 = Affine::new([0.1, 1.2, 2.3, 3.4, 4.5, 5.6]);
459
460 let px = Point::new(1.0, 0.0);
461 let py = Point::new(0.0, 1.0);
462 let pxy = Point::new(1.0, 1.0);
463 assert_near(a1 * (a2 * px), (a1 * a2) * px);
464 assert_near(a1 * (a2 * py), (a1 * a2) * py);
465 assert_near(a1 * (a2 * pxy), (a1 * a2) * pxy);
466 }
467
468 #[test]
469 fn affine_inv() {
470 let a = Affine::new([0.1, 1.2, 2.3, 3.4, 4.5, 5.6]);
471 let a_inv = a.inverse();
472
473 let px = Point::new(1.0, 0.0);
474 let py = Point::new(0.0, 1.0);
475 let pxy = Point::new(1.0, 1.0);
476 assert_near(a * (a_inv * px), px);
477 assert_near(a * (a_inv * py), py);
478 assert_near(a * (a_inv * pxy), pxy);
479 assert_near(a_inv * (a * px), px);
480 assert_near(a_inv * (a * py), py);
481 assert_near(a_inv * (a * pxy), pxy);
482 }
483}
484