roo_display
API Documentation for roo_display
Loading...
Searching...
No Matches
smooth_transformation.h
Go to the documentation of this file.
1#pragma once
2
3#include <cmath>
4
9
10namespace roo_display {
11
12/// Transformation classes for smooth shapes.
13///
14/// Translation, scaling, and rotation are primitive transformations.
15/// `AffineTransformation` combines translation, scaling, and rotation. Use
16/// `then()` to compose transformations.
17///
18/// Common methods:
19/// - `apply(FpPoint)` transforms a point.
20/// - `then(T)` composes with another transformation.
21/// - `inversion()` returns the inverse transformation.
22/// - `transformExtents(Box)` returns a bounding box covering transformed
23/// pixels.
24///
25/// Prefer using the factory functions below and `auto`.
26
27/// Identity transformation.
28class IdentityTransformation;
29/// Translation transformation.
30class Translation;
31/// Scaling transformation.
32class Scaling;
33/// Rotation transformation.
34class Rotation;
35/// Affine transformation (composition of translation, scaling, rotation).
36class AffineTransformation;
37/// Projective transformation (homography in 2D).
38class ProjectiveTransformation;
39
40/// Return a translation by the specified vector.
41Translation Translate(float dx, float dy);
42
43/// Return a scaling about the origin.
44Scaling Scale(float sx, float sy);
45
46/// Return a scaling about a given center.
47AffineTransformation ScaleAbout(float sx, float sy, const FpPoint& center);
48
49/// Return a clockwise rotation about the origin.
50Rotation RotateRight(float angle);
51
52/// Return a 90-degree clockwise rotation about the origin.
53Rotation RotateRight();
54
55/// Return a clockwise rotation about a point.
56AffineTransformation RotateRightAbout(float angle, const FpPoint& center);
57
58/// Return a 90-degree clockwise rotation about a point.
59AffineTransformation RotateRightAbout(const FpPoint& center);
60
61/// Return a counter-clockwise rotation about the origin.
62Rotation RotateLeft(float angle);
63
64/// Return a 90-degree counter-clockwise rotation about the origin.
65Rotation RotateLeft();
66
67/// Return a 90-degree counter-clockwise rotation about a point.
68AffineTransformation RotateLeftAbout(float angle, const FpPoint& center);
69
70/// Return a shear rooted at the origin.
71AffineTransformation Shear(float sx, float sy);
72
73/// Return a shear rooted at the specified point.
74AffineTransformation Shear(float sx, float sy, const FpPoint& base);
75
76/// Return a horizontal shear rooted at the origin.
77AffineTransformation ShearHorizontally(float sx);
78
79/// Return a horizontal shear rooted at `base_y`.
80AffineTransformation ShearHorizontallyAbout(float sx, float base_y);
81
82/// Return a vertical shear rooted at the origin.
83AffineTransformation ShearVertically(float sy);
84
85/// Return a vertical shear rooted at `base_x`.
86AffineTransformation ShearVerticallyAbout(float sy, float base_x);
87
88/// Return a perspective transformation rooted at the origin.
89///
90/// Maps (x, y) to (x / d, y / d), where d = 1 + px * x + py * y.
91ProjectiveTransformation Perspective(float px, float py);
92
93/// Return a perspective transformation rooted at `base`.
94ProjectiveTransformation PerspectiveAbout(float px, float py,
95 const FpPoint& base);
96
97template <typename RasterType, typename TransformationType>
98class TransformedRaster;
99
100/// Return a rasterizable representation of a transformed raster.
101template <typename RasterType, typename TransformationType>
102TransformedRaster<RasterType, TransformationType> TransformRaster(
103 RasterType& original, TransformationType transformation);
104
105// Implementation details.
106
107/// Identity transformation.
109 public:
111 FpPoint apply(FpPoint p) const { return p; }
112
115 Scaling then(Scaling t) const;
116 Rotation then(Rotation t) const;
119
120 IdentityTransformation inversion() const { return *this; }
121
122 Box transformExtents(Box extents) const { return extents; }
123};
124
125/// Translation by (dx, dy).
127 public:
128 Translation(float dx, float dy) : dx_(dx), dy_(dy) {}
129
130 FpPoint apply(FpPoint p) const { return FpPoint{p.x + dx_, p.y + dy_}; }
131
132 float dx() const { return dx_; }
133 float dy() const { return dy_; }
134
135 Translation then(IdentityTransformation t) const { return *this; }
141
142 Translation inversion() const { return Translation(-dx_, -dy_); }
143
144 Box transformExtents(Box extents) const {
145 return Box((int16_t)floorf(extents.xMin() + dx_),
146 (int16_t)floorf(extents.yMin() + dy_),
147 (int16_t)ceilf(extents.xMax() + dx_),
148 (int16_t)ceilf(extents.yMax() + dy_));
149 }
150
151 private:
152 float dx_, dy_;
153};
154
155/// Scaling by (sx, sy).
156class Scaling {
157 public:
158 Scaling(float sx, float sy) : sx_(sx), sy_(sy) {}
159
160 FpPoint apply(FpPoint p) const { return FpPoint{p.x * sx_, p.y * sy_}; }
161
162 float sx() const { return sx_; }
163 float sy() const { return sy_; }
164
165 Scaling then(IdentityTransformation t) const { return *this; }
167 Scaling then(Scaling t) const;
171
172 Scaling inversion() const { return Scaling(1.0f / sx_, 1.0f / sy_); }
173
174 Box transformExtents(Box extents) const {
175 float xMin = sx_ * extents.xMin();
176 float yMin = sy_ * extents.yMin();
177 float xMax = sx_ * extents.xMax();
178 float yMax = sy_ * extents.yMax();
179 if (xMin > xMax) std::swap(xMin, xMax);
180 if (yMin > yMax) std::swap(yMin, yMax);
181 return Box((int16_t)floorf(xMin), (int16_t)floorf(yMin),
182 (int16_t)ceilf(xMax), (int16_t)ceilf(yMax));
183 }
184
185 private:
186 float sx_, sy_;
187};
188
189/// Rotation by angle in radians (clockwise).
190class Rotation {
191 public:
193 : theta_(theta), sin_theta_(sinf(theta)), cos_theta_(cosf(theta)) {}
194
196 return FpPoint{p.x * cos_theta_ - p.y * sin_theta_,
197 p.x * sin_theta_ + p.y * cos_theta_};
198 }
199
200 float theta() const { return theta_; }
201 float sin_theta() const { return sin_theta_; }
202 float cos_theta() const { return cos_theta_; }
203
204 Rotation then(IdentityTransformation t) const { return *this; }
207 Rotation then(Rotation t) const;
210
211 Rotation inversion() const { return Rotation(-theta_); }
212
213 Box transformExtents(Box extents) const;
214
215 private:
216 float theta_, sin_theta_, cos_theta_;
217};
218
219/// General affine transformation.
221 public:
222 AffineTransformation(float a11, float a12, float a21, float a22, float tx,
223 float ty)
224 : a11_(a11), a12_(a12), a21_(a21), a22_(a22), tx_(tx), ty_(ty) {}
225
227 : a11_(1.0), a12_(0), a21_(0), a22_(1.0), tx_(0), ty_(0) {}
228
230 : a11_(1.0), a12_(0), a21_(0), a22_(1.0), tx_(t.dx()), ty_(t.dy()) {}
231
233 : a11_(t.sx()), a12_(0), a21_(0), a22_(t.sy()), tx_(0), ty_(0) {}
234
236 : a11_(t.cos_theta()),
237 a12_(-t.sin_theta()),
238 a21_(t.sin_theta()),
239 a22_(t.cos_theta()),
240 tx_(0),
241 ty_(0) {}
242
244 return FpPoint{p.x * a11_ + p.y * a12_ + tx_,
245 p.x * a21_ + p.y * a22_ + ty_};
246 }
247
248 float a11() const { return a11_; }
249 float a12() const { return a12_; }
250 float a21() const { return a21_; }
251 float a22() const { return a22_; }
252 float tx() const { return tx_; }
253 float ty() const { return ty_; }
254
261
263 float inv_det = 1.0f / (a11_ * a22_ - a12_ * a21_);
264 float inv_a11 = a22_ * inv_det;
265 float inv_a12 = -a12_ * inv_det;
266 float inv_a21 = -a21_ * inv_det;
267 float inv_a22 = a11_ * inv_det;
268
270 -inv_a11 * tx_ - inv_a12 * ty_,
271 -inv_a21 * tx_ - inv_a22 * ty_);
272 }
273
274 Box transformExtents(Box extents) const;
275
276 private:
277 float a11_, a12_, a21_, a22_;
278 float tx_, ty_;
279};
280
281/// General projective transformation (2D homography).
283 public:
284 ProjectiveTransformation(float m11, float m12, float m13, float m21,
285 float m22, float m23, float m31, float m32,
286 float m33)
287 : m11_(m11),
288 m12_(m12),
289 m13_(m13),
290 m21_(m21),
291 m22_(m22),
292 m23_(m23),
293 m31_(m31),
294 m32_(m32),
295 m33_(m33) {}
296
298 : m11_(1.0f),
299 m12_(0.0f),
300 m13_(0.0f),
301 m21_(0.0f),
302 m22_(1.0f),
303 m23_(0.0f),
304 m31_(0.0f),
305 m32_(0.0f),
306 m33_(1.0f) {}
307
309 : m11_(1.0f),
310 m12_(0.0f),
311 m13_(t.dx()),
312 m21_(0.0f),
313 m22_(1.0f),
314 m23_(t.dy()),
315 m31_(0.0f),
316 m32_(0.0f),
317 m33_(1.0f) {}
318
320 : m11_(t.sx()),
321 m12_(0.0f),
322 m13_(0.0f),
323 m21_(0.0f),
324 m22_(t.sy()),
325 m23_(0.0f),
326 m31_(0.0f),
327 m32_(0.0f),
328 m33_(1.0f) {}
329
331 : m11_(t.cos_theta()),
332 m12_(-t.sin_theta()),
333 m13_(0.0f),
334 m21_(t.sin_theta()),
335 m22_(t.cos_theta()),
336 m23_(0.0f),
337 m31_(0.0f),
338 m32_(0.0f),
339 m33_(1.0f) {}
340
342 : m11_(t.a11()),
343 m12_(t.a12()),
344 m13_(t.tx()),
345 m21_(t.a21()),
346 m22_(t.a22()),
347 m23_(t.ty()),
348 m31_(0.0f),
349 m32_(0.0f),
350 m33_(1.0f) {}
351
353 float w = p.x * m31_ + p.y * m32_ + m33_;
354 return FpPoint{(p.x * m11_ + p.y * m12_ + m13_) / w,
355 (p.x * m21_ + p.y * m22_ + m23_) / w};
356 }
357
358 float m11() const { return m11_; }
359 float m12() const { return m12_; }
360 float m13() const { return m13_; }
361 float m21() const { return m21_; }
362 float m22() const { return m22_; }
363 float m23() const { return m23_; }
364 float m31() const { return m31_; }
365 float m32() const { return m32_; }
366 float m33() const { return m33_; }
367
369 return *this;
370 }
376
378
379 Box transformExtents(Box extents) const;
380
381 private:
382 float m11_, m12_, m13_;
383 float m21_, m22_, m23_;
384 float m31_, m32_, m33_;
385};
386
387// Uses bi-linear interpolation.
388template <typename RasterType, typename TransformationType>
390 public:
391 TransformedRaster(RasterType& original, TransformationType transformation)
392 : original_(original),
393 inverse_transformation_(transformation.inversion()),
394 extents_(transformation.transformExtents(original.extents())),
395 anchor_extents_(
396 transformation.transformExtents(original.anchorExtents())) {}
397
398 void readColors(const int16_t* x, const int16_t* y, uint32_t count,
399 Color* result) const override {
400 Box orig_extents = original_.extents();
401 if (orig_extents.empty()) {
402 for (uint32_t i = 0; i < count; ++i) {
403 result[i] = color::Transparent;
404 }
405 return;
406 }
407 typename RasterType::Reader reader;
408 const auto ptr = original_.buffer();
409 uint16_t w = orig_extents.width();
411 for (uint32_t i = 0; i < count; ++i) {
412 FpPoint orig =
413 inverse_transformation_.apply(FpPoint{(float)x[i], (float)y[i]});
414 int16_t xMin = (int16_t)floorf(orig.x);
415 int16_t yMin = (int16_t)floorf(orig.y);
416 int16_t xMax = (int16_t)ceilf(orig.x);
417 int16_t yMax = (int16_t)ceilf(orig.y);
418 if (xMax < orig_extents.xMin() || xMin > orig_extents.xMax() ||
419 yMax < orig_extents.yMin() || yMin > orig_extents.yMax()) {
420 result[i] = color::Transparent;
421 continue;
422 }
423 int32_t offset =
424 xMin - orig_extents.xMin() + (yMin - orig_extents.yMin()) * w;
425 if (xMin == xMax && yMin == yMax) {
426 result[i] = reader(ptr, offset, original_.color_mode());
427 continue;
428 }
429 if (xMin == xMax) {
430 int16_t interpolation_fraction = (int16_t)(256 * (orig.y - yMin));
431 if (yMin < orig_extents.yMin()) {
432 result[i] =
433 InterpolateColors(color::Transparent,
434 reader(ptr, offset + w, original_.color_mode()),
436 } else if (yMax > orig_extents.yMax()) {
437 result[i] =
438 InterpolateColors(original_.get(xMin, yMin), color::Transparent,
440 } else {
441 result[i] =
442 InterpolateColors(reader(ptr, offset, original_.color_mode()),
443 reader(ptr, offset + w, original_.color_mode()),
445 }
446 continue;
447 }
448 if (yMin == yMax) {
449 int16_t interpolation_fraction = (uint16_t)(256 * (orig.x - xMin));
450 if (xMin < orig_extents.xMin()) {
451 result[i] =
452 InterpolateColors(color::Transparent,
453 reader(ptr, offset + 1, original_.color_mode()),
455 } else if (xMax > orig_extents.xMax()) {
456 result[i] =
457 InterpolateColors(reader(ptr, offset, original_.color_mode()),
458 color::Transparent, interpolation_fraction);
459 } else {
460 result[i] =
461 InterpolateColors(reader(ptr, offset, original_.color_mode()),
462 reader(ptr, offset + 1, original_.color_mode()),
464 }
465 continue;
466 }
467 // General case.
468 int16_t interpolation_fraction_x = (int16_t)(256 * (orig.x - xMin));
469 int16_t interpolation_fraction_y = (int16_t)(256 * (orig.y - yMin));
470
471 // Color a = color::Transparent;
472 // Color b = color::Transparent;
473 // Color c = color::Transparent;
474 // Color d = color::Transparent;
475
476 // if (xMin >= orig_extents.xMin()) {
477 // if (yMin >= orig_extents.yMin()) {
478 // a = reader(ptr, offset, original_.color_mode());
479 // }
480 // if (yMax <= orig_extents.yMax()) {
481 // c = reader(ptr, offset + w, original_.color_mode());
482 // }
483 // }
484 // if (xMax <= orig_extents.xMax()) {
485 // if (yMin >= orig_extents.yMin()) {
486 // b = reader(ptr, offset + 1, original_.color_mode());
487 // }
488 // if (yMax <= orig_extents.yMax()) {
489 // d = reader(ptr, offset + w + 1, original_.color_mode());
490 // }
491 // }
492 // result[i] =
493 // InterpolateColors(InterpolateColors(a, b,
494 // interpolation_fraction_x),
495 // InterpolateColors(c, d,
496 // interpolation_fraction_x),
497 // interpolation_fraction_y);
498
499 Color ab_mix = color::Transparent;
500 if (yMin >= orig_extents.yMin()) {
501 if (xMin < orig_extents.xMin()) {
502 // ab_mix = color::Olive;
503 ab_mix =
504 InterpolateColors(color::Transparent,
505 reader(ptr, offset + 1, original_.color_mode()),
507 // ab_mix = InterpolateColorWithTransparency(
508 // reader(ptr, offset + 1, original_.color_mode())),
509 // interpolation_fraction_x);
510 } else if (xMax > orig_extents.xMax()) {
511 // ab_mix = color::BlueViolet;
512 ab_mix =
513 InterpolateColors(reader(ptr, offset, original_.color_mode()),
514 color::Transparent, interpolation_fraction_x);
515 // ab_mix = InterpolateColorWithTransparency(
516 // reader(ptr, offset,original_.color_mode())), 256 -
517 // interpolation_fraction_x);
518 } else {
519 // ab_mix =
520 // interpolator(reader(ptr, offset, original_.color_mode()),
521 // reader(ptr, offset + 1, original_.color_mode()),
522 // interpolation_fraction_x, original_.color_mode());
523 ab_mix =
524 InterpolateColors(reader(ptr, offset, original_.color_mode()),
525 reader(ptr, offset + 1, original_.color_mode()),
527 }
528 }
529 Color cd_mix = color::Transparent;
530 if (yMax <= orig_extents.yMax()) {
531 if (xMin < orig_extents.xMin()) {
532 // cd_mix = color::Red;
534 color::Transparent,
535 reader(ptr, offset + w + 1, original_.color_mode()),
537 // cd_mix = InterpolateColorWithTransparency(
538 // original_.color_mode().toArgbColor(reader(ptr, offset + w +
539 // 1, original_.color_mode())), interpolation_fraction_x);
540 } else if (xMax > orig_extents.xMax()) {
541 // cd_mix = color::Blue;
542 // cd_mix = InterpolateColorWithTransparency(
543 // original_.color_mode().toArgbColor(reader(ptr, offset + w,
544 // original_.color_mode())), 256 - interpolation_fraction_x);
545 cd_mix =
546 InterpolateColors(reader(ptr, offset + w, original_.color_mode()),
547 color::Transparent, interpolation_fraction_x);
548 } else {
549 // cd_mix =
550 // interpolator(reader(ptr, offset + w, original_.color_mode()),
551 // reader(ptr, offset + w + 1, original_.color_mode()),
552 // interpolation_fraction_x, original_.color_mode());
553 // interpolation_fraction_x, original_.color_mode());
555 reader(ptr, offset + w, original_.color_mode()),
556 reader(ptr, offset + w + 1, original_.color_mode()),
558 }
559 }
560
562 }
563 }
564
565 Box extents() const override { return extents_; }
566 Box anchorExtents() const override { return anchor_extents_; }
567
568 private:
569 RasterType& original_;
570 TransformationType inverse_transformation_;
571 Box extents_;
572 Box anchor_extents_;
573};
574
575template <typename RasterType, typename TransformationType>
581
582} // namespace roo_display
General affine transformation.
AffineTransformation then(IdentityTransformation t) const
AffineTransformation(float a11, float a12, float a21, float a22, float tx, float ty)
AffineTransformation(IdentityTransformation t)
AffineTransformation inversion() const
Axis-aligned integer rectangle.
Definition box.h:12
int16_t xMin() const
Minimum x (inclusive).
Definition box.h:65
int16_t xMax() const
Maximum x (inclusive).
Definition box.h:71
int16_t yMax() const
Maximum y (inclusive).
Definition box.h:74
int16_t yMin() const
Minimum y (inclusive).
Definition box.h:68
ARGB8888 color stored as a 32-bit unsigned integer.
Definition color.h:16
IdentityTransformation inversion() const
IdentityTransformation then(IdentityTransformation t) const
General projective transformation (2D homography).
ProjectiveTransformation(float m11, float m12, float m13, float m21, float m22, float m23, float m31, float m32, float m33)
ProjectiveTransformation inversion() const
ProjectiveTransformation then(IdentityTransformation t) const
ProjectiveTransformation(IdentityTransformation t)
Drawable that can provide a color for any point within its extents.
Rotation by angle in radians (clockwise).
FpPoint apply(FpPoint p) const
Rotation then(IdentityTransformation t) const
Box transformExtents(Box extents) const
Box transformExtents(Box extents) const
Scaling then(IdentityTransformation t) const
FpPoint apply(FpPoint p) const
Scaling(float sx, float sy)
Box extents() const override
Return the bounding box encompassing all pixels that need to be drawn.
Box anchorExtents() const override
Return the bounds used for alignment.
void readColors(const int16_t *x, const int16_t *y, uint32_t count, Color *result) const override
Read colors for the given points.
TransformedRaster(RasterType &original, TransformationType transformation)
Translation by (dx, dy).
FpPoint apply(FpPoint p) const
Translation then(IdentityTransformation t) const
Box transformExtents(Box extents) const
Defines 140 opaque HTML named colors.
AffineTransformation RotateRightAbout(float angle, const FpPoint &center)
Return a clockwise rotation about a point.
AffineTransformation RotateLeftAbout(float angle, const FpPoint &center)
Return a 90-degree counter-clockwise rotation about a point.
AffineTransformation ShearVertically(float sy)
Return a vertical shear rooted at the origin.
Scaling Scale(float sx, float sy)
Return a scaling about the origin.
AffineTransformation ShearHorizontallyAbout(float sx, float base_y)
Return a horizontal shear rooted at base_y.
TransformedRaster< RasterType, TransformationType > TransformRaster(RasterType &original, TransformationType transformation)
Return a rasterizable representation of a transformed raster.
Rotation RotateRight()
Return a 90-degree clockwise rotation about the origin.
AffineTransformation ShearVerticallyAbout(float sy, float base_x)
Return a vertical shear rooted at base_x.
ProjectiveTransformation PerspectiveAbout(float px, float py, const FpPoint &base)
Return a perspective transformation rooted at base.
AffineTransformation ShearHorizontally(float sx)
Return a horizontal shear rooted at the origin.
Translation Translate(float dx, float dy)
Return a translation by the specified vector.
Color InterpolateColors(Color c1, Color c2, int16_t fraction)
Interpolate between two colors with fraction in [0, 256].
AffineTransformation ScaleAbout(float sx, float sy, const FpPoint &center)
Return a scaling about a given center.
AffineTransformation Shear(float sx, float sy)
Return a shear rooted at the origin.
ProjectiveTransformation Perspective(float px, float py)
Return a perspective transformation rooted at the origin.
Rotation RotateLeft()
Return a 90-degree counter-clockwise rotation about the origin.
Floating-point point.
Definition point.h:12