1 | // Copyright 2018 the Kurbo Authors
|
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
|
3 |
|
4 | //! Affine transforms.
|
5 |
|
6 | use core::ops::{Mul, MulAssign};
|
7 |
|
8 | use crate::{Point, Rect, Vec2};
|
9 |
|
10 | #[cfg (not(feature = "std" ))]
|
11 | use 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))]
|
17 | pub struct Affine([f64; 6]);
|
18 |
|
19 | impl 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 |
|
345 | impl Default for Affine {
|
346 | #[inline ]
|
347 | fn default() -> Affine {
|
348 | Affine::IDENTITY
|
349 | }
|
350 | }
|
351 |
|
352 | impl 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 |
|
364 | impl 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 |
|
380 | impl MulAssign for Affine {
|
381 | #[inline ]
|
382 | fn mul_assign(&mut self, other: Affine) {
|
383 | *self = self.mul(other);
|
384 | }
|
385 | }
|
386 |
|
387 | impl 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" )]
|
405 | impl 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" )]
|
426 | impl 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)]
|
434 | mod 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 | |