Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
affine_transform.hpp
Go to the documentation of this file.
1#pragma once
2
3// AffineTransform — 2D affine transform value type.
4//
5// Lives in `pulp::canvas` because it is the lowest layer that needs it:
6// `pulp::canvas::Path` transforms itself, and `core/view` links `core/canvas`
7// (not the other way round), so `pulp::view` re-exports this type rather than
8// declaring a second one. See the alias in core/view/include/pulp/view/geometry.hpp.
9//
10// Matrix layout matches the six floats every 2D graphics API on the planet
11// agrees on (and the existing `Canvas::AffineTransform2x3` snapshot type):
12//
13// [ a c e ] x' = a*x + c*y + e
14// [ b d f ] y' = b*x + d*y + f
15// [ 0 0 1 ]
16//
17// Identity is {1, 0, 0, 1, 0, 0}.
18
19#include <pulp/canvas/rectangle_list.hpp> // pulp::canvas::Rect2D
20
21#include <cmath>
22#include <optional>
23
24namespace pulp::canvas {
25
37struct Point2D {
38 float x = 0.0f;
39 float y = 0.0f;
40
41 constexpr Point2D operator+(const Point2D& p) const { return {x + p.x, y + p.y}; }
42 constexpr Point2D operator-(const Point2D& p) const { return {x - p.x, y - p.y}; }
43 constexpr Point2D operator*(float s) const { return {x * s, y * s}; }
44 constexpr bool operator==(const Point2D& p) const { return x == p.x && y == p.y; }
45 constexpr bool operator!=(const Point2D& p) const { return !(*this == p); }
46};
47
49public:
50 float a = 1.0f, b = 0.0f;
51 float c = 0.0f, d = 1.0f;
52 float e = 0.0f, f = 0.0f;
53
54 constexpr AffineTransform() = default;
55 constexpr AffineTransform(float a_, float b_, float c_,
56 float d_, float e_, float f_)
57 : a(a_), b(b_), c(c_), d(d_), e(e_), f(f_) {}
58
59 // ── Factories ────────────────────────────────────────────────────────
60 static constexpr AffineTransform identity() { return {}; }
61
62 static constexpr AffineTransform translation(float tx, float ty) {
63 return {1.0f, 0.0f, 0.0f, 1.0f, tx, ty};
64 }
65
66 static constexpr AffineTransform scaling(float sx, float sy) {
67 return {sx, 0.0f, 0.0f, sy, 0.0f, 0.0f};
68 }
69
71 static constexpr AffineTransform scaling(float sx, float sy,
72 float px, float py) {
73 return {sx, 0.0f, 0.0f, sy, px - px * sx, py - py * sy};
74 }
75
76 static AffineTransform rotation(float radians) {
77 const float s = std::sin(radians);
78 const float co = std::cos(radians);
79 return {co, s, -s, co, 0.0f, 0.0f};
80 }
81
83 static AffineTransform rotation(float radians, float px, float py) {
84 const float s = std::sin(radians);
85 const float co = std::cos(radians);
86 return {co, s, -s, co,
87 px - px * co + py * s,
88 py - px * s - py * co};
89 }
90
92 static constexpr AffineTransform shear(float shx, float shy) {
93 return {1.0f, shy, shx, 1.0f, 0.0f, 0.0f};
94 }
95
96 // ── Composition ──────────────────────────────────────────────────────
100 constexpr AffineTransform followed_by(const AffineTransform& next) const {
101 return {
102 next.a * a + next.c * b,
103 next.b * a + next.d * b,
104 next.a * c + next.c * d,
105 next.b * c + next.d * d,
106 next.a * e + next.c * f + next.e,
107 next.b * e + next.d * f + next.f,
108 };
109 }
110
113 constexpr AffineTransform operator*(const AffineTransform& rhs) const {
114 return rhs.followed_by(*this);
115 }
116
117 constexpr AffineTransform translated(float tx, float ty) const {
118 return followed_by(translation(tx, ty));
119 }
120 constexpr AffineTransform scaled(float sx, float sy) const {
121 return followed_by(scaling(sx, sy));
122 }
123 AffineTransform rotated(float radians) const {
124 return followed_by(rotation(radians));
125 }
126 AffineTransform rotated(float radians, float px, float py) const {
127 return followed_by(rotation(radians, px, py));
128 }
129 constexpr AffineTransform sheared(float shx, float shy) const {
130 return followed_by(shear(shx, shy));
131 }
132
133 // ── Inversion ────────────────────────────────────────────────────────
134 constexpr float determinant() const { return a * d - b * c; }
135
138 bool is_singular() const {
139 const float det = determinant();
140 return !std::isfinite(det) || std::abs(det) < 1e-12f;
141 }
142
146 std::optional<AffineTransform> inverted() const {
147 if (is_singular()) return std::nullopt;
148 const float det = determinant();
149 const float inv = 1.0f / det;
150 return AffineTransform{
151 d * inv,
152 -b * inv,
153 -c * inv,
154 a * inv,
155 (c * f - d * e) * inv,
156 (b * e - a * f) * inv,
157 };
158 }
159
160 // ── Application ──────────────────────────────────────────────────────
161 constexpr Point2D transform_point(Point2D p) const {
162 return {a * p.x + c * p.y + e, b * p.x + d * p.y + f};
163 }
164
165 constexpr void transform_point(float& x, float& y) const {
166 const float nx = a * x + c * y + e;
167 const float ny = b * x + d * y + f;
168 x = nx;
169 y = ny;
170 }
171
176 Rect2D transform_rect(const Rect2D& r) const {
177 const Point2D p0 = transform_point({r.x, r.y});
178 const Point2D p1 = transform_point({r.right(), r.y});
179 const Point2D p2 = transform_point({r.right(), r.bottom()});
180 const Point2D p3 = transform_point({r.x, r.bottom()});
181 const float minx = std::min(std::min(p0.x, p1.x), std::min(p2.x, p3.x));
182 const float maxx = std::max(std::max(p0.x, p1.x), std::max(p2.x, p3.x));
183 const float miny = std::min(std::min(p0.y, p1.y), std::min(p2.y, p3.y));
184 const float maxy = std::max(std::max(p0.y, p1.y), std::max(p2.y, p3.y));
185 return {minx, miny, maxx - minx, maxy - miny};
186 }
187
188 // ── Queries ──────────────────────────────────────────────────────────
189 constexpr bool is_identity() const {
190 return a == 1.0f && b == 0.0f && c == 0.0f &&
191 d == 1.0f && e == 0.0f && f == 0.0f;
192 }
193
194 constexpr bool operator==(const AffineTransform& o) const {
195 return a == o.a && b == o.b && c == o.c &&
196 d == o.d && e == o.e && f == o.f;
197 }
198 constexpr bool operator!=(const AffineTransform& o) const {
199 return !(*this == o);
200 }
201};
202
203} // namespace pulp::canvas
constexpr float determinant() const
Definition affine_transform.hpp:134
static constexpr AffineTransform scaling(float sx, float sy)
Definition affine_transform.hpp:66
constexpr AffineTransform translated(float tx, float ty) const
Definition affine_transform.hpp:117
static constexpr AffineTransform shear(float shx, float shy)
Shear. shx slants x by y; shy slants y by x.
Definition affine_transform.hpp:92
constexpr AffineTransform(float a_, float b_, float c_, float d_, float e_, float f_)
Definition affine_transform.hpp:55
AffineTransform rotated(float radians, float px, float py) const
Definition affine_transform.hpp:126
Rect2D transform_rect(const Rect2D &r) const
Axis-aligned bounding box of the four transformed corners.
Definition affine_transform.hpp:176
float e
Definition affine_transform.hpp:52
float a
Definition affine_transform.hpp:50
constexpr AffineTransform operator*(const AffineTransform &rhs) const
Standard matrix product: (m1 * m2) applies m2 first, then m1.
Definition affine_transform.hpp:113
AffineTransform rotated(float radians) const
Definition affine_transform.hpp:123
constexpr bool is_identity() const
Definition affine_transform.hpp:189
constexpr bool operator!=(const AffineTransform &o) const
Definition affine_transform.hpp:198
constexpr AffineTransform sheared(float shx, float shy) const
Definition affine_transform.hpp:129
std::optional< AffineTransform > inverted() const
The inverse, or nullopt when singular.
Definition affine_transform.hpp:146
static AffineTransform rotation(float radians)
Definition affine_transform.hpp:76
constexpr bool operator==(const AffineTransform &o) const
Definition affine_transform.hpp:194
static constexpr AffineTransform scaling(float sx, float sy, float px, float py)
Scale about the pivot (px, py) — the pivot is a fixed point.
Definition affine_transform.hpp:71
float d
Definition affine_transform.hpp:51
bool is_singular() const
True when the transform collapses area to zero and therefore cannot be inverted (e....
Definition affine_transform.hpp:138
float c
Definition affine_transform.hpp:51
constexpr AffineTransform scaled(float sx, float sy) const
Definition affine_transform.hpp:120
static constexpr AffineTransform identity()
Definition affine_transform.hpp:60
float f
Definition affine_transform.hpp:52
float b
Definition affine_transform.hpp:50
constexpr AffineTransform followed_by(const AffineTransform &next) const
Return the transform that applies *this FIRST, then next.
Definition affine_transform.hpp:100
constexpr Point2D transform_point(Point2D p) const
Definition affine_transform.hpp:161
static constexpr AffineTransform translation(float tx, float ty)
Definition affine_transform.hpp:62
static AffineTransform rotation(float radians, float px, float py)
Rotate about the pivot (px, py) — the pivot is a fixed point.
Definition affine_transform.hpp:83
constexpr void transform_point(float &x, float &y) const
Definition affine_transform.hpp:165
constexpr AffineTransform()=default
Definition affine_transform.hpp:24
A 2D point.
Definition affine_transform.hpp:37
float x
Definition affine_transform.hpp:38
constexpr bool operator!=(const Point2D &p) const
Definition affine_transform.hpp:45
constexpr Point2D operator+(const Point2D &p) const
Definition affine_transform.hpp:41
constexpr Point2D operator-(const Point2D &p) const
Definition affine_transform.hpp:42
float y
Definition affine_transform.hpp:39
constexpr Point2D operator*(float s) const
Definition affine_transform.hpp:43
constexpr bool operator==(const Point2D &p) const
Definition affine_transform.hpp:44
Axis-aligned rectangle in canvas coordinates.
Definition rectangle_list.hpp:28