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