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    /// Create a transform that scales about a point other than the origin.
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// # use kurbo::{Point, TranslateScale};
76    /// # fn assert_near(p0: Point, p1: Point) {
77    /// #   assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
78    /// # }
79    /// let center = Point::new(1., 1.);
80    /// let ts = TranslateScale::from_scale_about(2., center);
81    /// // Should keep the point (1., 1.) stationary
82    /// assert_near(ts * center, center);
83    /// // (2., 2.) -> (3., 3.)
84    /// assert_near(ts * Point::new(2., 2.), Point::new(3., 3.));
85    /// ```
86    #[inline]
87    pub fn from_scale_about(scale: f64, focus: impl Into<Point>) -> Self {
88        // We need to create a transform that is equivalent to translating `focus`
89        // to the origin, followed by a normal scale, followed by reversing the translation.
90        // We need to find the (translation ∘ scale) that matches this.
91        let focus = focus.into().to_vec2();
92        let translation = focus - focus * scale;
93        Self::new(translation, scale)
94    }
95
96    /// Compute the inverse transform.
97    ///
98    /// Multiplying a transform with its inverse (either on the
99    /// left or right) results in the identity transform
100    /// (modulo floating point rounding errors).
101    ///
102    /// Produces NaN values when scale is zero.
103    #[inline]
104    pub fn inverse(self) -> TranslateScale {
105        let scale_recip = self.scale.recip();
106        TranslateScale {
107            translation: self.translation * -scale_recip,
108            scale: scale_recip,
109        }
110    }
111
112    /// Is this translate/scale [finite]?
113    ///
114    /// [finite]: f64::is_finite
115    #[inline]
116    pub fn is_finite(&self) -> bool {
117        self.translation.is_finite() && self.scale.is_finite()
118    }
119
120    /// Is this translate/scale [NaN]?
121    ///
122    /// [NaN]: f64::is_nan
123    #[inline]
124    pub fn is_nan(&self) -> bool {
125        self.translation.is_nan() || self.scale.is_nan()
126    }
127}
128
129impl Default for TranslateScale {
130    #[inline(always)]
131    fn default() -> TranslateScale {
132        TranslateScale::new(Vec2::ZERO, 1.0)
133    }
134}
135
136impl From<TranslateScale> for Affine {
137    #[inline(always)]
138    fn from(ts: TranslateScale) -> Affine {
139        let TranslateScale { translation, scale } = ts;
140        Affine::new([scale, 0.0, 0.0, scale, translation.x, translation.y])
141    }
142}
143
144impl Mul<Point> for TranslateScale {
145    type Output = Point;
146
147    #[inline]
148    fn mul(self, other: Point) -> Point {
149        (self.scale * other.to_vec2()).to_point() + self.translation
150    }
151}
152
153impl Mul for TranslateScale {
154    type Output = TranslateScale;
155
156    #[inline]
157    fn mul(self, other: TranslateScale) -> TranslateScale {
158        TranslateScale {
159            translation: self.translation + self.scale * other.translation,
160            scale: self.scale * other.scale,
161        }
162    }
163}
164
165impl MulAssign for TranslateScale {
166    #[inline]
167    fn mul_assign(&mut self, other: TranslateScale) {
168        *self = self.mul(other);
169    }
170}
171
172impl Mul<TranslateScale> for f64 {
173    type Output = TranslateScale;
174
175    #[inline]
176    fn mul(self, other: TranslateScale) -> TranslateScale {
177        TranslateScale {
178            translation: other.translation * self,
179            scale: other.scale * self,
180        }
181    }
182}
183
184impl Add<Vec2> for TranslateScale {
185    type Output = TranslateScale;
186
187    #[inline]
188    fn add(self, other: Vec2) -> TranslateScale {
189        TranslateScale {
190            translation: self.translation + other,
191            scale: self.scale,
192        }
193    }
194}
195
196impl Add<TranslateScale> for Vec2 {
197    type Output = TranslateScale;
198
199    #[inline]
200    fn add(self, other: TranslateScale) -> TranslateScale {
201        other + self
202    }
203}
204
205impl AddAssign<Vec2> for TranslateScale {
206    #[inline]
207    fn add_assign(&mut self, other: Vec2) {
208        *self = self.add(other);
209    }
210}
211
212impl Sub<Vec2> for TranslateScale {
213    type Output = TranslateScale;
214
215    #[inline]
216    fn sub(self, other: Vec2) -> TranslateScale {
217        TranslateScale {
218            translation: self.translation - other,
219            scale: self.scale,
220        }
221    }
222}
223
224impl SubAssign<Vec2> for TranslateScale {
225    #[inline]
226    fn sub_assign(&mut self, other: Vec2) {
227        *self = self.sub(other);
228    }
229}
230
231impl Mul<Circle> for TranslateScale {
232    type Output = Circle;
233
234    #[inline]
235    fn mul(self, other: Circle) -> Circle {
236        Circle::new(self * other.center, self.scale * other.radius)
237    }
238}
239
240impl Mul<Line> for TranslateScale {
241    type Output = Line;
242
243    #[inline]
244    fn mul(self, other: Line) -> Line {
245        Line::new(self * other.p0, self * other.p1)
246    }
247}
248
249impl Mul<Rect> for TranslateScale {
250    type Output = Rect;
251
252    #[inline]
253    fn mul(self, other: Rect) -> Rect {
254        let pt0 = self * Point::new(other.x0, other.y0);
255        let pt1 = self * Point::new(other.x1, other.y1);
256        (pt0, pt1).into()
257    }
258}
259
260impl Mul<RoundedRect> for TranslateScale {
261    type Output = RoundedRect;
262
263    #[inline]
264    fn mul(self, other: RoundedRect) -> RoundedRect {
265        RoundedRect::from_rect(self * other.rect(), self * other.radii())
266    }
267}
268
269impl Mul<RoundedRectRadii> for TranslateScale {
270    type Output = RoundedRectRadii;
271
272    #[inline]
273    fn mul(self, other: RoundedRectRadii) -> RoundedRectRadii {
274        RoundedRectRadii::new(
275            self.scale * other.top_left,
276            self.scale * other.top_right,
277            self.scale * other.bottom_right,
278            self.scale * other.bottom_left,
279        )
280    }
281}
282
283impl Mul<QuadBez> for TranslateScale {
284    type Output = QuadBez;
285
286    #[inline]
287    fn mul(self, other: QuadBez) -> QuadBez {
288        QuadBez::new(self * other.p0, self * other.p1, self * other.p2)
289    }
290}
291
292impl Mul<CubicBez> for TranslateScale {
293    type Output = CubicBez;
294
295    #[inline]
296    fn mul(self, other: CubicBez) -> CubicBez {
297        CubicBez::new(
298            self * other.p0,
299            self * other.p1,
300            self * other.p2,
301            self * other.p3,
302        )
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use crate::{Affine, Point, TranslateScale, Vec2};
309
310    fn assert_near(p0: Point, p1: Point) {
311        assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
312    }
313
314    #[test]
315    fn translate_scale() {
316        let p = Point::new(3.0, 4.0);
317        let ts = TranslateScale::new(Vec2::new(5.0, 6.0), 2.0);
318
319        assert_near(ts * p, Point::new(11.0, 14.0));
320    }
321
322    #[test]
323    fn conversions() {
324        let p = Point::new(3.0, 4.0);
325        let s = 2.0;
326        let t = Vec2::new(5.0, 6.0);
327        let ts = TranslateScale::new(t, s);
328
329        // Test that conversion to affine is consistent.
330        let a: Affine = ts.into();
331        assert_near(ts * p, a * p);
332
333        assert_near((s * p.to_vec2()).to_point(), TranslateScale::scale(s) * p);
334        assert_near(p + t, TranslateScale::translate(t) * p);
335    }
336
337    #[test]
338    fn inverse() {
339        let p = Point::new(3.0, 4.0);
340        let ts = TranslateScale::new(Vec2::new(5.0, 6.0), 2.0);
341
342        assert_near(p, (ts * ts.inverse()) * p);
343        assert_near(p, (ts.inverse() * ts) * p);
344    }
345}