forma_render/math/
transform.rs

1// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{convert::TryFrom, error::Error, fmt, hash};
16
17use crate::{consts, math::Point, path::MAX_ERROR, utils::CanonBits};
18
19const MAX_SCALING_FACTOR_X: f32 = 1.0 + MAX_ERROR / consts::MAX_WIDTH as f32;
20const MAX_SCALING_FACTOR_Y: f32 = 1.0 + MAX_ERROR / consts::MAX_HEIGHT as f32;
21
22/// 2D transformation that preserves parallel lines.
23///
24/// Such a transformation can combine translation, scale, flip, rotate and shears.
25/// It is represented by a 3 by 3 matrix where the last row is [0, 0, 1].
26///
27/// ```text
28/// [ x' ]   [ u.x v.x t.x ] [ x ]
29/// [ y' ] = [ u.y v.y t.y ] [ y ]
30/// [ 1  ]   [   0   0   1 ] [ 1 ]
31/// ```
32#[derive(Copy, Clone, Debug)]
33pub struct AffineTransform {
34    pub ux: f32,
35    pub uy: f32,
36    pub vx: f32,
37    pub vy: f32,
38    pub tx: f32,
39    pub ty: f32,
40}
41
42impl AffineTransform {
43    pub(crate) fn transform(&self, point: Point) -> Point {
44        Point {
45            x: self.ux.mul_add(point.x, self.vx.mul_add(point.y, self.tx)),
46            y: self.uy.mul_add(point.x, self.vy.mul_add(point.y, self.ty)),
47        }
48    }
49
50    pub(crate) fn is_identity(&self) -> bool {
51        *self == Self::default()
52    }
53
54    pub fn to_array(&self) -> [f32; 6] {
55        [self.ux, self.uy, self.vx, self.vy, self.tx, self.ty]
56    }
57}
58
59impl Eq for AffineTransform {}
60
61impl PartialEq for AffineTransform {
62    fn eq(&self, other: &Self) -> bool {
63        self.ux == other.ux
64            && self.uy == other.uy
65            && self.vx == other.vx
66            && self.vy == other.vy
67            && self.tx == other.tx
68            && self.ty == other.ty
69    }
70}
71
72impl hash::Hash for AffineTransform {
73    fn hash<H: hash::Hasher>(&self, state: &mut H) {
74        self.ux.to_canon_bits().hash(state);
75        self.uy.to_canon_bits().hash(state);
76        self.vx.to_canon_bits().hash(state);
77        self.vy.to_canon_bits().hash(state);
78        self.tx.to_canon_bits().hash(state);
79        self.ty.to_canon_bits().hash(state);
80    }
81}
82
83impl Default for AffineTransform {
84    fn default() -> Self {
85        Self {
86            ux: 1.0,
87            vx: 0.0,
88            tx: 0.0,
89            uy: 0.0,
90            vy: 1.0,
91            ty: 0.0,
92        }
93    }
94}
95
96impl From<[f32; 6]> for AffineTransform {
97    fn from(transform: [f32; 6]) -> Self {
98        Self {
99            ux: transform[0],
100            uy: transform[2],
101            vx: transform[1],
102            vy: transform[3],
103            tx: transform[4],
104            ty: transform[5],
105        }
106    }
107}
108
109#[derive(Debug, Eq, PartialEq)]
110pub enum GeomPresTransformError {
111    ExceededScalingFactor { x: bool, y: bool },
112}
113
114impl fmt::Display for GeomPresTransformError {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match self {
117            GeomPresTransformError::ExceededScalingFactor { x: true, y: false } => {
118                write!(f, "exceeded scaling factor on the X axis (-1.0 to 1.0)")
119            }
120            GeomPresTransformError::ExceededScalingFactor { x: false, y: true } => {
121                write!(f, "exceeded scaling factor on the Y axis (-1.0 to 1.0)")
122            }
123            GeomPresTransformError::ExceededScalingFactor { x: true, y: true } => {
124                write!(f, "exceeded scaling factor on both axis (-1.0 to 1.0)")
125            }
126            _ => panic!("cannot display invalid GeomPresTransformError"),
127        }
128    }
129}
130
131impl Error for GeomPresTransformError {}
132
133#[derive(Default, Clone, Copy, Debug, Eq, PartialEq)]
134pub struct GeomPresTransform(pub(crate) AffineTransform);
135
136impl GeomPresTransform {
137    /// ```text
138    /// [ x' ]   [ t.0 t.1 t.4 ] [ x ]
139    /// [ y' ] = [ t.2 t.3 t.5 ] [ y ]
140    /// [ 1  ]   [   0   0   1 ] [ 1 ]
141    /// ```
142    #[inline]
143    pub fn new(mut transform: [f32; 9]) -> Option<Self> {
144        (transform[6].abs() <= f32::EPSILON && transform[7].abs() <= f32::EPSILON)
145            .then(|| {
146                if (transform[8] - 1.0).abs() > f32::EPSILON {
147                    let recip = transform[8].recip();
148                    for val in &mut transform[..6] {
149                        *val *= recip;
150                    }
151                }
152
153                Self::try_from(AffineTransform {
154                    ux: transform[0],
155                    vx: transform[1],
156                    uy: transform[3],
157                    vy: transform[4],
158                    tx: transform[2],
159                    ty: transform[5],
160                })
161                .ok()
162            })
163            .flatten()
164    }
165
166    #[inline]
167    pub fn is_identity(&self) -> bool {
168        self.0.is_identity()
169    }
170
171    pub(crate) fn transform(&self, point: Point) -> Point {
172        self.0.transform(point)
173    }
174
175    #[inline]
176    pub fn to_array(&self) -> [f32; 6] {
177        [
178            self.0.ux, self.0.vx, self.0.uy, self.0.vy, self.0.tx, self.0.ty,
179        ]
180    }
181}
182
183impl TryFrom<[f32; 6]> for GeomPresTransform {
184    type Error = GeomPresTransformError;
185    fn try_from(transform: [f32; 6]) -> Result<Self, Self::Error> {
186        GeomPresTransform::try_from(AffineTransform::from(transform))
187    }
188}
189
190impl TryFrom<AffineTransform> for GeomPresTransform {
191    type Error = GeomPresTransformError;
192
193    fn try_from(t: AffineTransform) -> Result<Self, Self::Error> {
194        let scales_up_x = t.ux * t.ux + t.uy * t.uy > MAX_SCALING_FACTOR_X;
195        let scales_up_y = t.vx * t.vx + t.vy * t.vy > MAX_SCALING_FACTOR_Y;
196
197        (!scales_up_x && !scales_up_y).then_some(Self(t)).ok_or(
198            GeomPresTransformError::ExceededScalingFactor {
199                x: scales_up_x,
200                y: scales_up_y,
201            },
202        )
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn default_identity() {
212        let transform = GeomPresTransform::default();
213
214        assert_eq!(
215            transform.transform(Point::new(2.0, 3.0)),
216            Point::new(2.0, 3.0)
217        );
218    }
219
220    #[test]
221    fn as_slice() {
222        let slice = [0.1, 0.5, 0.4, 0.3, 0.7, 0.9];
223
224        assert_eq!(
225            slice,
226            GeomPresTransform::try_from(slice).unwrap().to_array()
227        );
228    }
229
230    #[test]
231    fn scale_translate() {
232        let transform = GeomPresTransform::try_from([0.1, 0.5, 0.4, 0.3, 0.5, 0.6]).unwrap();
233
234        assert_eq!(
235            transform.transform(Point::new(2.0, 3.0)),
236            Point::new(2.2, 2.3)
237        );
238    }
239
240    #[test]
241    fn wrong_scaling_factor() {
242        let transform = [
243            0.1,
244            MAX_SCALING_FACTOR_Y.sqrt(),
245            MAX_SCALING_FACTOR_X.sqrt(),
246            0.1,
247            0.5,
248            0.0,
249        ];
250
251        assert_eq!(
252            GeomPresTransform::try_from(transform),
253            Err(GeomPresTransformError::ExceededScalingFactor { x: true, y: true })
254        );
255    }
256
257    #[test]
258    fn wrong_scaling_factor_x() {
259        let transform = [0.1, 0.0, MAX_SCALING_FACTOR_X.sqrt(), 0.0, 0.5, 0.0];
260
261        assert_eq!(
262            GeomPresTransform::try_from(transform),
263            Err(GeomPresTransformError::ExceededScalingFactor { x: true, y: false })
264        );
265    }
266
267    #[test]
268    fn wrong_scaling_factor_y() {
269        let transform = [0.0, MAX_SCALING_FACTOR_Y.sqrt(), 0.0, 0.1, 0.5, 0.0];
270
271        assert_eq!(
272            GeomPresTransform::try_from(transform),
273            Err(GeomPresTransformError::ExceededScalingFactor { x: false, y: true })
274        );
275    }
276
277    #[test]
278    fn correct_scaling_factor() {
279        let transform = [1.0, MAX_SCALING_FACTOR_Y.sqrt(), 0.0, 0.0, 0.5, 0.0];
280
281        assert_eq!(
282            transform,
283            GeomPresTransform::try_from(transform).unwrap().to_array()
284        );
285    }
286}