tiny_skia_path/
transform.rs

1// Copyright 2006 The Android Open Source Project
2// Copyright 2020 Yevhenii Reizner
3//
4// Use of this source code is governed by a BSD-style license that can be
5// found in the LICENSE file.
6
7use crate::{NonZeroRect, Point};
8
9use crate::scalar::{Scalar, SCALAR_NEARLY_ZERO};
10
11#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
12use crate::NoStdFloat;
13
14/// An affine transformation matrix.
15///
16/// Unlike other types, doesn't guarantee to be valid. This is Skia quirk.
17/// Meaning Transform(0, 0, 0, 0, 0, 0) is ok, while it's technically not.
18/// Non-finite values are also not an error.
19#[allow(missing_docs)]
20#[derive(Copy, Clone, PartialEq, Debug)]
21pub struct Transform {
22    pub sx: f32,
23    pub kx: f32,
24    pub ky: f32,
25    pub sy: f32,
26    pub tx: f32,
27    pub ty: f32,
28}
29
30impl Default for Transform {
31    fn default() -> Self {
32        Transform {
33            sx: 1.0,
34            kx: 0.0,
35            ky: 0.0,
36            sy: 1.0,
37            tx: 0.0,
38            ty: 0.0,
39        }
40    }
41}
42
43impl Transform {
44    /// Creates an identity transform.
45    pub fn identity() -> Self {
46        Transform::default()
47    }
48
49    /// Creates a new `Transform`.
50    ///
51    /// We are using column-major-column-vector matrix notation, therefore it's ky-kx, not kx-ky.
52    pub fn from_row(sx: f32, ky: f32, kx: f32, sy: f32, tx: f32, ty: f32) -> Self {
53        Transform {
54            sx,
55            ky,
56            kx,
57            sy,
58            tx,
59            ty,
60        }
61    }
62
63    /// Creates a new translating `Transform`.
64    pub fn from_translate(tx: f32, ty: f32) -> Self {
65        Transform::from_row(1.0, 0.0, 0.0, 1.0, tx, ty)
66    }
67
68    /// Creates a new scaling `Transform`.
69    pub fn from_scale(sx: f32, sy: f32) -> Self {
70        Transform::from_row(sx, 0.0, 0.0, sy, 0.0, 0.0)
71    }
72
73    /// Creates a new skewing `Transform`.
74    pub fn from_skew(kx: f32, ky: f32) -> Self {
75        Transform::from_row(1.0, ky, kx, 1.0, 0.0, 0.0)
76    }
77
78    /// Creates a new rotating `Transform`.
79    ///
80    /// `angle` in degrees.
81    pub fn from_rotate(angle: f32) -> Self {
82        let v = angle.to_radians();
83        let a = v.cos();
84        let b = v.sin();
85        let c = -b;
86        let d = a;
87        Transform::from_row(a, b, c, d, 0.0, 0.0)
88    }
89
90    /// Creates a new rotating `Transform` at the specified position.
91    ///
92    /// `angle` in degrees.
93    pub fn from_rotate_at(angle: f32, tx: f32, ty: f32) -> Self {
94        let mut ts = Self::default();
95        ts = ts.pre_translate(tx, ty);
96        ts = ts.pre_concat(Transform::from_rotate(angle));
97        ts = ts.pre_translate(-tx, -ty);
98        ts
99    }
100
101    /// Converts `Rect` into a bounding box `Transform`.
102    #[inline]
103    pub fn from_bbox(bbox: NonZeroRect) -> Self {
104        Transform::from_row(bbox.width(), 0.0, 0.0, bbox.height(), bbox.x(), bbox.y())
105    }
106
107    /// Checks that transform is finite.
108    pub fn is_finite(&self) -> bool {
109        self.sx.is_finite()
110            && self.ky.is_finite()
111            && self.kx.is_finite()
112            && self.sy.is_finite()
113            && self.tx.is_finite()
114            && self.ty.is_finite()
115    }
116
117    /// Checks that transform is finite and has non-zero scale.
118    pub fn is_valid(&self) -> bool {
119        if self.is_finite() {
120            let (sx, sy) = self.get_scale();
121            !(sx.is_nearly_zero_within_tolerance(f32::EPSILON)
122                || sy.is_nearly_zero_within_tolerance(f32::EPSILON))
123        } else {
124            false
125        }
126    }
127
128    /// Checks that transform is identity.
129    pub fn is_identity(&self) -> bool {
130        *self == Transform::default()
131    }
132
133    /// Checks that transform is scale-only.
134    pub fn is_scale(&self) -> bool {
135        self.has_scale() && !self.has_skew() && !self.has_translate()
136    }
137
138    /// Checks that transform is skew-only.
139    pub fn is_skew(&self) -> bool {
140        !self.has_scale() && self.has_skew() && !self.has_translate()
141    }
142
143    /// Checks that transform is translate-only.
144    pub fn is_translate(&self) -> bool {
145        !self.has_scale() && !self.has_skew() && self.has_translate()
146    }
147
148    /// Checks that transform contains only scale and translate.
149    pub fn is_scale_translate(&self) -> bool {
150        (self.has_scale() || self.has_translate()) && !self.has_skew()
151    }
152
153    /// Checks that transform contains a scale part.
154    pub fn has_scale(&self) -> bool {
155        self.sx != 1.0 || self.sy != 1.0
156    }
157
158    /// Checks that transform contains a skew part.
159    pub fn has_skew(&self) -> bool {
160        self.kx != 0.0 || self.ky != 0.0
161    }
162
163    /// Checks that transform contains a translate part.
164    pub fn has_translate(&self) -> bool {
165        self.tx != 0.0 || self.ty != 0.0
166    }
167
168    /// Returns transform's scale part.
169    pub fn get_scale(&self) -> (f32, f32) {
170        let x_scale = (self.sx * self.sx + self.kx * self.kx).sqrt();
171        let y_scale = (self.ky * self.ky + self.sy * self.sy).sqrt();
172        (x_scale, y_scale)
173    }
174
175    /// Pre-scales the current transform.
176    #[must_use]
177    pub fn pre_scale(&self, sx: f32, sy: f32) -> Self {
178        self.pre_concat(Transform::from_scale(sx, sy))
179    }
180
181    /// Post-scales the current transform.
182    #[must_use]
183    pub fn post_scale(&self, sx: f32, sy: f32) -> Self {
184        self.post_concat(Transform::from_scale(sx, sy))
185    }
186
187    /// Pre-translates the current transform.
188    #[must_use]
189    pub fn pre_translate(&self, tx: f32, ty: f32) -> Self {
190        self.pre_concat(Transform::from_translate(tx, ty))
191    }
192
193    /// Post-translates the current transform.
194    #[must_use]
195    pub fn post_translate(&self, tx: f32, ty: f32) -> Self {
196        self.post_concat(Transform::from_translate(tx, ty))
197    }
198
199    /// Pre-rotates the current transform.
200    ///
201    /// `angle` in degrees.
202    #[must_use]
203    pub fn pre_rotate(&self, angle: f32) -> Self {
204        self.pre_concat(Transform::from_rotate(angle))
205    }
206
207    /// Post-rotates the current transform.
208    ///
209    /// `angle` in degrees.
210    #[must_use]
211    pub fn post_rotate(&self, angle: f32) -> Self {
212        self.post_concat(Transform::from_rotate(angle))
213    }
214
215    /// Pre-rotates the current transform by the specified position.
216    ///
217    /// `angle` in degrees.
218    #[must_use]
219    pub fn pre_rotate_at(&self, angle: f32, tx: f32, ty: f32) -> Self {
220        self.pre_concat(Transform::from_rotate_at(angle, tx, ty))
221    }
222
223    /// Post-rotates the current transform by the specified position.
224    ///
225    /// `angle` in degrees.
226    #[must_use]
227    pub fn post_rotate_at(&self, angle: f32, tx: f32, ty: f32) -> Self {
228        self.post_concat(Transform::from_rotate_at(angle, tx, ty))
229    }
230
231    /// Pre-concats the current transform.
232    #[must_use]
233    pub fn pre_concat(&self, other: Self) -> Self {
234        concat(*self, other)
235    }
236
237    /// Post-concats the current transform.
238    #[must_use]
239    pub fn post_concat(&self, other: Self) -> Self {
240        concat(other, *self)
241    }
242
243    pub(crate) fn from_sin_cos(sin: f32, cos: f32) -> Self {
244        Transform::from_row(cos, sin, -sin, cos, 0.0, 0.0)
245    }
246
247    /// Transforms a points using the current transform.
248    pub fn map_point(&self, point: &mut Point) {
249        if self.is_identity() {
250            // Do nothing.
251        } else if self.is_translate() {
252            point.x += self.tx;
253            point.y += self.ty;
254        } else if self.is_scale_translate() {
255            point.x = point.x * self.sx + self.tx;
256            point.y = point.y * self.sy + self.ty;
257        } else {
258            let x = point.x * self.sx + point.y * self.kx + self.tx;
259            let y = point.x * self.ky + point.y * self.sy + self.ty;
260            point.x = x;
261            point.y = y;
262        }
263    }
264
265    /// Transforms a slice of points using the current transform.
266    pub fn map_points(&self, points: &mut [Point]) {
267        if points.is_empty() {
268            return;
269        }
270
271        // TODO: simd
272
273        if self.is_identity() {
274            // Do nothing.
275        } else if self.is_translate() {
276            for p in points {
277                p.x += self.tx;
278                p.y += self.ty;
279            }
280        } else if self.is_scale_translate() {
281            for p in points {
282                p.x = p.x * self.sx + self.tx;
283                p.y = p.y * self.sy + self.ty;
284            }
285        } else {
286            for p in points {
287                let x = p.x * self.sx + p.y * self.kx + self.tx;
288                let y = p.x * self.ky + p.y * self.sy + self.ty;
289                p.x = x;
290                p.y = y;
291            }
292        }
293    }
294
295    /// Returns an inverted transform.
296    pub fn invert(&self) -> Option<Self> {
297        // Allow the trivial case to be inlined.
298        if self.is_identity() {
299            return Some(*self);
300        }
301
302        invert(self)
303    }
304}
305
306fn invert(ts: &Transform) -> Option<Transform> {
307    debug_assert!(!ts.is_identity());
308
309    if ts.is_scale_translate() {
310        if ts.has_scale() {
311            let inv_x = ts.sx.invert();
312            let inv_y = ts.sy.invert();
313            Some(Transform::from_row(
314                inv_x,
315                0.0,
316                0.0,
317                inv_y,
318                -ts.tx * inv_x,
319                -ts.ty * inv_y,
320            ))
321        } else {
322            // translate only
323            Some(Transform::from_translate(-ts.tx, -ts.ty))
324        }
325    } else {
326        let inv_det = inv_determinant(ts)?;
327        let inv_ts = compute_inv(ts, inv_det);
328
329        if inv_ts.is_finite() {
330            Some(inv_ts)
331        } else {
332            None
333        }
334    }
335}
336
337fn inv_determinant(ts: &Transform) -> Option<f64> {
338    let det = dcross(ts.sx as f64, ts.sy as f64, ts.kx as f64, ts.ky as f64);
339
340    // Since the determinant is on the order of the cube of the matrix members,
341    // compare to the cube of the default nearly-zero constant (although an
342    // estimate of the condition number would be better if it wasn't so expensive).
343    let tolerance = SCALAR_NEARLY_ZERO * SCALAR_NEARLY_ZERO * SCALAR_NEARLY_ZERO;
344    if (det as f32).is_nearly_zero_within_tolerance(tolerance) {
345        None
346    } else {
347        Some(1.0 / det)
348    }
349}
350
351fn compute_inv(ts: &Transform, inv_det: f64) -> Transform {
352    Transform::from_row(
353        (ts.sy as f64 * inv_det) as f32,
354        (-ts.ky as f64 * inv_det) as f32,
355        (-ts.kx as f64 * inv_det) as f32,
356        (ts.sx as f64 * inv_det) as f32,
357        dcross_dscale(ts.kx, ts.ty, ts.sy, ts.tx, inv_det),
358        dcross_dscale(ts.ky, ts.tx, ts.sx, ts.ty, inv_det),
359    )
360}
361
362fn dcross(a: f64, b: f64, c: f64, d: f64) -> f64 {
363    a * b - c * d
364}
365
366fn dcross_dscale(a: f32, b: f32, c: f32, d: f32, scale: f64) -> f32 {
367    (dcross(a as f64, b as f64, c as f64, d as f64) * scale) as f32
368}
369
370fn concat(a: Transform, b: Transform) -> Transform {
371    if a.is_identity() {
372        b
373    } else if b.is_identity() {
374        a
375    } else if !a.has_skew() && !b.has_skew() {
376        // just scale and translate
377        Transform::from_row(
378            a.sx * b.sx,
379            0.0,
380            0.0,
381            a.sy * b.sy,
382            a.sx * b.tx + a.tx,
383            a.sy * b.ty + a.ty,
384        )
385    } else {
386        Transform::from_row(
387            mul_add_mul(a.sx, b.sx, a.kx, b.ky),
388            mul_add_mul(a.ky, b.sx, a.sy, b.ky),
389            mul_add_mul(a.sx, b.kx, a.kx, b.sy),
390            mul_add_mul(a.ky, b.kx, a.sy, b.sy),
391            mul_add_mul(a.sx, b.tx, a.kx, b.ty) + a.tx,
392            mul_add_mul(a.ky, b.tx, a.sy, b.ty) + a.ty,
393        )
394    }
395}
396
397fn mul_add_mul(a: f32, b: f32, c: f32, d: f32) -> f32 {
398    (f64::from(a) * f64::from(b) + f64::from(c) * f64::from(d)) as f32
399}
400
401#[cfg(test)]
402mod tests {
403    use super::*;
404
405    #[test]
406    fn transform() {
407        assert_eq!(
408            Transform::identity(),
409            Transform::from_row(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
410        );
411
412        assert_eq!(
413            Transform::from_scale(1.0, 2.0),
414            Transform::from_row(1.0, 0.0, 0.0, 2.0, 0.0, 0.0)
415        );
416
417        assert_eq!(
418            Transform::from_skew(2.0, 3.0),
419            Transform::from_row(1.0, 3.0, 2.0, 1.0, 0.0, 0.0)
420        );
421
422        assert_eq!(
423            Transform::from_translate(5.0, 6.0),
424            Transform::from_row(1.0, 0.0, 0.0, 1.0, 5.0, 6.0)
425        );
426
427        let ts = Transform::identity();
428        assert_eq!(ts.is_identity(), true);
429        assert_eq!(ts.is_scale(), false);
430        assert_eq!(ts.is_skew(), false);
431        assert_eq!(ts.is_translate(), false);
432        assert_eq!(ts.is_scale_translate(), false);
433        assert_eq!(ts.has_scale(), false);
434        assert_eq!(ts.has_skew(), false);
435        assert_eq!(ts.has_translate(), false);
436
437        let ts = Transform::from_scale(2.0, 3.0);
438        assert_eq!(ts.is_identity(), false);
439        assert_eq!(ts.is_scale(), true);
440        assert_eq!(ts.is_skew(), false);
441        assert_eq!(ts.is_translate(), false);
442        assert_eq!(ts.is_scale_translate(), true);
443        assert_eq!(ts.has_scale(), true);
444        assert_eq!(ts.has_skew(), false);
445        assert_eq!(ts.has_translate(), false);
446
447        let ts = Transform::from_skew(2.0, 3.0);
448        assert_eq!(ts.is_identity(), false);
449        assert_eq!(ts.is_scale(), false);
450        assert_eq!(ts.is_skew(), true);
451        assert_eq!(ts.is_translate(), false);
452        assert_eq!(ts.is_scale_translate(), false);
453        assert_eq!(ts.has_scale(), false);
454        assert_eq!(ts.has_skew(), true);
455        assert_eq!(ts.has_translate(), false);
456
457        let ts = Transform::from_translate(2.0, 3.0);
458        assert_eq!(ts.is_identity(), false);
459        assert_eq!(ts.is_scale(), false);
460        assert_eq!(ts.is_skew(), false);
461        assert_eq!(ts.is_translate(), true);
462        assert_eq!(ts.is_scale_translate(), true);
463        assert_eq!(ts.has_scale(), false);
464        assert_eq!(ts.has_skew(), false);
465        assert_eq!(ts.has_translate(), true);
466
467        let ts = Transform::from_row(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
468        assert_eq!(ts.is_identity(), false);
469        assert_eq!(ts.is_scale(), false);
470        assert_eq!(ts.is_skew(), false);
471        assert_eq!(ts.is_translate(), false);
472        assert_eq!(ts.is_scale_translate(), false);
473        assert_eq!(ts.has_scale(), true);
474        assert_eq!(ts.has_skew(), true);
475        assert_eq!(ts.has_translate(), true);
476
477        let ts = Transform::from_scale(1.0, 1.0);
478        assert_eq!(ts.has_scale(), false);
479
480        let ts = Transform::from_skew(0.0, 0.0);
481        assert_eq!(ts.has_skew(), false);
482
483        let ts = Transform::from_translate(0.0, 0.0);
484        assert_eq!(ts.has_translate(), false);
485    }
486
487    #[test]
488    fn concat() {
489        let mut ts = Transform::from_row(1.2, 3.4, -5.6, -7.8, 1.2, 3.4);
490        ts = ts.pre_scale(2.0, -4.0);
491        assert_eq!(ts, Transform::from_row(2.4, 6.8, 22.4, 31.2, 1.2, 3.4));
492
493        let mut ts = Transform::from_row(1.2, 3.4, -5.6, -7.8, 1.2, 3.4);
494        ts = ts.post_scale(2.0, -4.0);
495        assert_eq!(ts, Transform::from_row(2.4, -13.6, -11.2, 31.2, 2.4, -13.6));
496    }
497}