kurbo/
translate_scale.rs

1// Copyright 2019 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! A transformation that includes both scale and translation.
5
6use core::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
7
8use crate::{
9    Affine, Circle, CubicBez, Line, Point, QuadBez, Rect, RoundedRect, RoundedRectRadii, Vec2,
10};
11
12/// A transformation consisting of a uniform scaling followed by a translation.
13///
14/// If the translation is `(x, y)` and the scale is `s`, then this
15/// transformation represents this augmented matrix:
16///
17/// ```text
18/// | s 0 x |
19/// | 0 s y |
20/// | 0 0 1 |
21/// ```
22///
23/// See [`Affine`] for more details about the
24/// equivalence with augmented matrices.
25///
26/// Various multiplication ops are defined, and these are all defined
27/// to be consistent with matrix multiplication. Therefore,
28/// `TranslateScale * Point` is defined but not the other way around.
29///
30/// Also note that multiplication is not commutative. Thus,
31/// `TranslateScale::scale(2.0) * TranslateScale::translate(Vec2::new(1.0, 0.0))`
32/// has a translation of (2, 0), while
33/// `TranslateScale::translate(Vec2::new(1.0, 0.0)) * TranslateScale::scale(2.0)`
34/// has a translation of (1, 0). (Both have a scale of 2; also note that
35/// the first case can be written
36/// `2.0 * TranslateScale::translate(Vec2::new(1.0, 0.0))` as this case
37/// has an implicit conversion).
38///
39/// This transformation is less powerful than [`Affine`], but can be applied
40/// to more primitives, especially including [`Rect`].
41#[derive(Clone, Copy, Debug)]
42#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
43#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44pub struct TranslateScale {
45    /// The translation component of this transformation
46    pub translation: Vec2,
47    /// The scale component of this transformation
48    pub scale: f64,
49}
50
51impl TranslateScale {
52    /// Create a new transformation from translation and scale.
53    #[inline(always)]
54    pub const fn new(translation: Vec2, scale: f64) -> TranslateScale {
55        TranslateScale { translation, scale }
56    }
57
58    /// Create a new transformation with scale only.
59    #[inline(always)]
60    pub const fn scale(s: f64) -> TranslateScale {
61        TranslateScale::new(Vec2::ZERO, s)
62    }
63
64    /// Create a new transformation with translation only.
65    #[inline(always)]
66    pub fn translate(translation: impl Into<Vec2>) -> TranslateScale {
67        TranslateScale::new(translation.into(), 1.0)
68    }
69
70    /// Decompose transformation into translation and scale.
71    #[deprecated(note = "use the struct fields directly")]
72    #[inline(always)]
73    pub const fn as_tuple(self) -> (Vec2, f64) {
74        (self.translation, self.scale)
75    }
76
77    /// Create a transform that scales about a point other than the origin.
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// # use kurbo::{Point, TranslateScale};
83    /// # fn assert_near(p0: Point, p1: Point) {
84    /// #   assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
85    /// # }
86    /// let center = Point::new(1., 1.);
87    /// let ts = TranslateScale::from_scale_about(2., center);
88    /// // Should keep the point (1., 1.) stationary
89    /// assert_near(ts * center, center);
90    /// // (2., 2.) -> (3., 3.)
91    /// assert_near(ts * Point::new(2., 2.), Point::new(3., 3.));
92    /// ```
93    #[inline]
94    pub fn from_scale_about(scale: f64, focus: impl Into<Point>) -> Self {
95        // We need to create a transform that is equivalent to translating `focus`
96        // to the origin, followed by a normal scale, followed by reversing the translation.
97        // We need to find the (translation ∘ scale) that matches this.
98        let focus = focus.into().to_vec2();
99        let translation = focus - focus * scale;
100        Self::new(translation, scale)
101    }
102
103    /// Compute the inverse transform.
104    ///
105    /// Multiplying a transform with its inverse (either on the
106    /// left or right) results in the identity transform
107    /// (modulo floating point rounding errors).
108    ///
109    /// Produces NaN values when scale is zero.
110    #[inline]
111    pub fn inverse(self) -> TranslateScale {
112        let scale_recip = self.scale.recip();
113        TranslateScale {
114            translation: self.translation * -scale_recip,
115            scale: scale_recip,
116        }
117    }
118
119    /// Is this translate/scale [finite]?
120    ///
121    /// [finite]: f64::is_finite
122    #[inline]
123    pub fn is_finite(&self) -> bool {
124        self.translation.is_finite() && self.scale.is_finite()
125    }
126
127    /// Is this translate/scale [NaN]?
128    ///
129    /// [NaN]: f64::is_nan
130    #[inline]
131    pub fn is_nan(&self) -> bool {
132        self.translation.is_nan() || self.scale.is_nan()
133    }
134}
135
136impl Default for TranslateScale {
137    #[inline(always)]
138    fn default() -> TranslateScale {
139        TranslateScale::new(Vec2::ZERO, 1.0)
140    }
141}
142
143impl From<TranslateScale> for Affine {
144    #[inline(always)]
145    fn from(ts: TranslateScale) -> Affine {
146        let TranslateScale { translation, scale } = ts;
147        Affine::new([scale, 0.0, 0.0, scale, translation.x, translation.y])
148    }
149}
150
151impl Mul<Point> for TranslateScale {
152    type Output = Point;
153
154    #[inline]
155    fn mul(self, other: Point) -> Point {
156        (self.scale * other.to_vec2()).to_point() + self.translation
157    }
158}
159
160impl Mul for TranslateScale {
161    type Output = TranslateScale;
162
163    #[inline]
164    fn mul(self, other: TranslateScale) -> TranslateScale {
165        TranslateScale {
166            translation: self.translation + self.scale * other.translation,
167            scale: self.scale * other.scale,
168        }
169    }
170}
171
172impl MulAssign for TranslateScale {
173    #[inline]
174    fn mul_assign(&mut self, other: TranslateScale) {
175        *self = self.mul(other);
176    }
177}
178
179impl Mul<TranslateScale> for f64 {
180    type Output = TranslateScale;
181
182    #[inline]
183    fn mul(self, other: TranslateScale) -> TranslateScale {
184        TranslateScale {
185            translation: other.translation * self,
186            scale: other.scale * self,
187        }
188    }
189}
190
191impl Add<Vec2> for TranslateScale {
192    type Output = TranslateScale;
193
194    #[inline]
195    fn add(self, other: Vec2) -> TranslateScale {
196        TranslateScale {
197            translation: self.translation + other,
198            scale: self.scale,
199        }
200    }
201}
202
203impl Add<TranslateScale> for Vec2 {
204    type Output = TranslateScale;
205
206    #[inline]
207    fn add(self, other: TranslateScale) -> TranslateScale {
208        other + self
209    }
210}
211
212impl AddAssign<Vec2> for TranslateScale {
213    #[inline]
214    fn add_assign(&mut self, other: Vec2) {
215        *self = self.add(other);
216    }
217}
218
219impl Sub<Vec2> for TranslateScale {
220    type Output = TranslateScale;
221
222    #[inline]
223    fn sub(self, other: Vec2) -> TranslateScale {
224        TranslateScale {
225            translation: self.translation - other,
226            scale: self.scale,
227        }
228    }
229}
230
231impl SubAssign<Vec2> for TranslateScale {
232    #[inline]
233    fn sub_assign(&mut self, other: Vec2) {
234        *self = self.sub(other);
235    }
236}
237
238impl Mul<Circle> for TranslateScale {
239    type Output = Circle;
240
241    #[inline]
242    fn mul(self, other: Circle) -> Circle {
243        Circle::new(self * other.center, self.scale * other.radius)
244    }
245}
246
247impl Mul<Line> for TranslateScale {
248    type Output = Line;
249
250    #[inline]
251    fn mul(self, other: Line) -> Line {
252        Line::new(self * other.p0, self * other.p1)
253    }
254}
255
256impl Mul<Rect> for TranslateScale {
257    type Output = Rect;
258
259    #[inline]
260    fn mul(self, other: Rect) -> Rect {
261        let pt0 = self * Point::new(other.x0, other.y0);
262        let pt1 = self * Point::new(other.x1, other.y1);
263        (pt0, pt1).into()
264    }
265}
266
267impl Mul<RoundedRect> for TranslateScale {
268    type Output = RoundedRect;
269
270    #[inline]
271    fn mul(self, other: RoundedRect) -> RoundedRect {
272        RoundedRect::from_rect(self * other.rect(), self * other.radii())
273    }
274}
275
276impl Mul<RoundedRectRadii> for TranslateScale {
277    type Output = RoundedRectRadii;
278
279    #[inline]
280    fn mul(self, other: RoundedRectRadii) -> RoundedRectRadii {
281        RoundedRectRadii::new(
282            self.scale * other.top_left,
283            self.scale * other.top_right,
284            self.scale * other.bottom_right,
285            self.scale * other.bottom_left,
286        )
287    }
288}
289
290impl Mul<QuadBez> for TranslateScale {
291    type Output = QuadBez;
292
293    #[inline]
294    fn mul(self, other: QuadBez) -> QuadBez {
295        QuadBez::new(self * other.p0, self * other.p1, self * other.p2)
296    }
297}
298
299impl Mul<CubicBez> for TranslateScale {
300    type Output = CubicBez;
301
302    #[inline]
303    fn mul(self, other: CubicBez) -> CubicBez {
304        CubicBez::new(
305            self * other.p0,
306            self * other.p1,
307            self * other.p2,
308            self * other.p3,
309        )
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use crate::{Affine, Point, TranslateScale, Vec2};
316
317    fn assert_near(p0: Point, p1: Point) {
318        assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
319    }
320
321    #[test]
322    fn translate_scale() {
323        let p = Point::new(3.0, 4.0);
324        let ts = TranslateScale::new(Vec2::new(5.0, 6.0), 2.0);
325
326        assert_near(ts * p, Point::new(11.0, 14.0));
327    }
328
329    #[test]
330    fn conversions() {
331        let p = Point::new(3.0, 4.0);
332        let s = 2.0;
333        let t = Vec2::new(5.0, 6.0);
334        let ts = TranslateScale::new(t, s);
335
336        // Test that conversion to affine is consistent.
337        let a: Affine = ts.into();
338        assert_near(ts * p, a * p);
339
340        assert_near((s * p.to_vec2()).to_point(), TranslateScale::scale(s) * p);
341        assert_near(p + t, TranslateScale::translate(t) * p);
342    }
343
344    #[test]
345    fn inverse() {
346        let p = Point::new(3.0, 4.0);
347        let ts = TranslateScale::new(Vec2::new(5.0, 6.0), 2.0);
348
349        assert_near(p, (ts * ts.inverse()) * p);
350        assert_near(p, (ts.inverse() * ts) * p);
351    }
352}