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