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]
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]
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]
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]
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: 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.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 #[inline]
121 pub fn is_finite(&self) -> bool {
122 self.translation.is_finite() && self.scale.is_finite()
123 }
124
125 /// Is this translate/scale NaN?
126 #[inline]
127 pub fn is_nan(&self) -> bool {
128 self.translation.is_nan() || self.scale.is_nan()
129 }
130}
131
132impl Default for TranslateScale {
133 #[inline]
134 fn default() -> TranslateScale {
135 TranslateScale::new(translation:Vec2::ZERO, scale:1.0)
136 }
137}
138
139impl From<TranslateScale> for Affine {
140 fn from(ts: TranslateScale) -> Affine {
141 let TranslateScale { translation: Vec2, scale: f64 } = ts;
142 Affine::new([scale, 0.0, 0.0, scale, translation.x, translation.y])
143 }
144}
145
146impl Mul<Point> for TranslateScale {
147 type Output = Point;
148
149 #[inline]
150 fn mul(self, other: Point) -> Point {
151 (self.scale * other.to_vec2()).to_point() + self.translation
152 }
153}
154
155impl Mul for TranslateScale {
156 type Output = TranslateScale;
157
158 #[inline]
159 fn mul(self, other: TranslateScale) -> TranslateScale {
160 TranslateScale {
161 translation: self.translation + self.scale * other.translation,
162 scale: self.scale * other.scale,
163 }
164 }
165}
166
167impl MulAssign for TranslateScale {
168 #[inline]
169 fn mul_assign(&mut self, other: TranslateScale) {
170 *self = self.mul(other);
171 }
172}
173
174impl Mul<TranslateScale> for f64 {
175 type Output = TranslateScale;
176
177 #[inline]
178 fn mul(self, other: TranslateScale) -> TranslateScale {
179 TranslateScale {
180 translation: other.translation * self,
181 scale: other.scale * self,
182 }
183 }
184}
185
186impl Add<Vec2> for TranslateScale {
187 type Output = TranslateScale;
188
189 #[inline]
190 fn add(self, other: Vec2) -> TranslateScale {
191 TranslateScale {
192 translation: self.translation + other,
193 scale: self.scale,
194 }
195 }
196}
197
198impl Add<TranslateScale> for Vec2 {
199 type Output = TranslateScale;
200
201 #[inline]
202 fn add(self, other: TranslateScale) -> TranslateScale {
203 other + self
204 }
205}
206
207impl AddAssign<Vec2> for TranslateScale {
208 #[inline]
209 fn add_assign(&mut self, other: Vec2) {
210 *self = self.add(other);
211 }
212}
213
214impl Sub<Vec2> for TranslateScale {
215 type Output = TranslateScale;
216
217 #[inline]
218 fn sub(self, other: Vec2) -> TranslateScale {
219 TranslateScale {
220 translation: self.translation - other,
221 scale: self.scale,
222 }
223 }
224}
225
226impl SubAssign<Vec2> for TranslateScale {
227 #[inline]
228 fn sub_assign(&mut self, other: Vec2) {
229 *self = self.sub(other);
230 }
231}
232
233impl Mul<Circle> for TranslateScale {
234 type Output = Circle;
235
236 #[inline]
237 fn mul(self, other: Circle) -> Circle {
238 Circle::new(self * other.center, self.scale * other.radius)
239 }
240}
241
242impl Mul<Line> for TranslateScale {
243 type Output = Line;
244
245 #[inline]
246 fn mul(self, other: Line) -> Line {
247 Line::new(self * other.p0, self * other.p1)
248 }
249}
250
251impl Mul<Rect> for TranslateScale {
252 type Output = Rect;
253
254 #[inline]
255 fn mul(self, other: Rect) -> Rect {
256 let pt0: Point = self * Point::new(x:other.x0, y:other.y0);
257 let pt1: Point = self * Point::new(x:other.x1, y:other.y1);
258 (pt0, pt1).into()
259 }
260}
261
262impl Mul<RoundedRect> for TranslateScale {
263 type Output = RoundedRect;
264
265 #[inline]
266 fn mul(self, other: RoundedRect) -> RoundedRect {
267 RoundedRect::from_rect(self * other.rect(), self * other.radii())
268 }
269}
270
271impl Mul<RoundedRectRadii> for TranslateScale {
272 type Output = RoundedRectRadii;
273
274 #[inline]
275 fn mul(self, other: RoundedRectRadii) -> RoundedRectRadii {
276 RoundedRectRadii::new(
277 self.scale * other.top_left,
278 self.scale * other.top_right,
279 self.scale * other.bottom_right,
280 self.scale * other.bottom_left,
281 )
282 }
283}
284
285impl Mul<QuadBez> for TranslateScale {
286 type Output = QuadBez;
287
288 #[inline]
289 fn mul(self, other: QuadBez) -> QuadBez {
290 QuadBez::new(self * other.p0, self * other.p1, self * other.p2)
291 }
292}
293
294impl Mul<CubicBez> for TranslateScale {
295 type Output = CubicBez;
296
297 #[inline]
298 fn mul(self, other: CubicBez) -> CubicBez {
299 CubicBez::new(
300 self * other.p0,
301 self * other.p1,
302 self * other.p2,
303 self * other.p3,
304 )
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use crate::{Affine, Point, TranslateScale, Vec2};
311
312 fn assert_near(p0: Point, p1: Point) {
313 assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
314 }
315
316 #[test]
317 fn translate_scale() {
318 let p = Point::new(3.0, 4.0);
319 let ts = TranslateScale::new(Vec2::new(5.0, 6.0), 2.0);
320
321 assert_near(ts * p, Point::new(11.0, 14.0));
322 }
323
324 #[test]
325 fn conversions() {
326 let p = Point::new(3.0, 4.0);
327 let s = 2.0;
328 let t = Vec2::new(5.0, 6.0);
329 let ts = TranslateScale::new(t, s);
330
331 // Test that conversion to affine is consistent.
332 let a: Affine = ts.into();
333 assert_near(ts * p, a * p);
334
335 assert_near((s * p.to_vec2()).to_point(), TranslateScale::scale(s) * p);
336 assert_near(p + t, TranslateScale::translate(t) * p);
337 }
338
339 #[test]
340 fn inverse() {
341 let p = Point::new(3.0, 4.0);
342 let ts = TranslateScale::new(Vec2::new(5.0, 6.0), 2.0);
343
344 assert_near(p, (ts * ts.inverse()) * p);
345 assert_near(p, (ts.inverse() * ts) * p);
346 }
347}
348