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