Skip to content

Commit aa5de9f

Browse files
authored
Add a Transform3DImageFilter to apply perspective projection effects. (#938)
1 parent 5f18d26 commit aa5de9f

18 files changed

+2027
-10
lines changed

include/tgfx/core/Image.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,5 +363,6 @@ class Image {
363363
friend class ScaledImage;
364364
friend class ImageShader;
365365
friend class Types;
366+
friend class Transform3DImageFilter;
366367
};
367368
} // namespace tgfx

include/tgfx/core/ImageFilter.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "tgfx/core/ColorFilter.h"
2222
#include "tgfx/core/Image.h"
2323
#include "tgfx/core/Matrix.h"
24+
#include "tgfx/core/Matrix3D.h"
2425
#include "tgfx/core/TileMode.h"
2526
#include "tgfx/gpu/Context.h"
2627
#include "tgfx/gpu/RuntimeEffect.h"
@@ -118,6 +119,14 @@ class ImageFilter {
118119
*/
119120
static std::shared_ptr<ImageFilter> Runtime(std::shared_ptr<RuntimeEffect> effect);
120121

122+
/**
123+
* Creates a filter that applies a perspective transformation to the input image.
124+
* @param matrix 3D transformation matrix used to 3D model coordinates to destination coordinates
125+
* for x and y before perspective division. The z value is mapped to the [-1, 1] range before
126+
* perspective division; content outside this z range will be clipped.
127+
*/
128+
static std::shared_ptr<ImageFilter> Transform3D(const Matrix3D& matrix);
129+
121130
virtual ~ImageFilter() = default;
122131

123132
/**
@@ -127,7 +136,7 @@ class ImageFilter {
127136
Rect filterBounds(const Rect& rect) const;
128137

129138
protected:
130-
enum class Type { Blur, DropShadow, InnerShadow, Color, Compose, Runtime };
139+
enum class Type { Blur, DropShadow, InnerShadow, Color, Compose, Runtime, Transform3D };
131140

132141
/**
133142
* Returns the type of this image filter.
@@ -174,4 +183,5 @@ class ImageFilter {
174183
friend class FilterImage;
175184
friend class Types;
176185
};
186+
177187
} // namespace tgfx

include/tgfx/core/Matrix3D.h

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
/////////////////////////////////////////////////////////////////////////////////////////////////
2+
//
3+
// Tencent is pleased to support the open source community by making tgfx available.
4+
//
5+
// Copyright (C) 2025 Tencent. All rights reserved.
6+
//
7+
// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
8+
// in compliance with the License. You may obtain a copy of the License at
9+
//
10+
// https://opensource.org/licenses/BSD-3-Clause
11+
//
12+
// unless required by applicable law or agreed to in writing, software distributed under the
13+
// license is distributed on an "as is" basis, without warranties or conditions of any kind,
14+
// either express or implied. see the license for the specific language governing permissions
15+
// and limitations under the license.
16+
//
17+
/////////////////////////////////////////////////////////////////////////////////////////////////
18+
19+
#pragma once
20+
21+
#include <cstring>
22+
#include "Vec.h"
23+
#include "tgfx/core/Rect.h"
24+
25+
namespace tgfx {
26+
27+
/**
28+
* Matrix3D holds a 4x4 matrix for transforming coordinates in 3D space. This allows mapping points
29+
* and vectors with translation, scaling, skewing, rotation, and perspective. These types of
30+
* transformations are collectively known as projective transformations. Projective transformations
31+
* preserve the straightness of lines but do not preserve parallelism, so parallel lines may not
32+
* remain parallel after transformation.
33+
* The elements of Matrix3D are in column-major order.
34+
* Matrix3D does not have a default constructor, so it must be explicitly initialized.
35+
*/
36+
class Matrix3D {
37+
public:
38+
/**
39+
* Creates a Matrix3D set to the identity matrix. The created Matrix3D is:
40+
*
41+
* | 1 0 0 0 |
42+
* | 0 1 0 0 |
43+
* | 0 0 1 0 |
44+
* | 0 0 0 1 |
45+
*/
46+
constexpr Matrix3D() : Matrix3D(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) {
47+
}
48+
49+
/**
50+
* Returns the matrix value at the given row and column.
51+
* @param r Row index, valid range 0..3.
52+
* @param c Column index, valid range 0..3.
53+
*/
54+
float getRowColumn(int r, int c) const {
55+
return values[c * 4 + r];
56+
}
57+
58+
/**
59+
* Sets the matrix value at the given row and column.
60+
* @param r Row index, valid range 0..3.
61+
* @param c Column index, valid range 0..3.
62+
*/
63+
void setRowColumn(int r, int c, float value) {
64+
values[c * 4 + r] = value;
65+
}
66+
67+
/**
68+
* Returns a reference to a constant identity Matrix3D. The returned Matrix3D is:
69+
*
70+
* | 1 0 0 0 |
71+
* | 0 1 0 0 |
72+
* | 0 0 1 0 |
73+
* | 0 0 0 1 |
74+
*/
75+
static const Matrix3D& I();
76+
77+
/**
78+
* Creates a Matrix3D that scales by (sx, sy, sz). The returned matrix is:
79+
*
80+
* | sx 0 0 0 |
81+
* | 0 sy 0 0 |
82+
* | 0 0 sz 0 |
83+
* | 0 0 0 1 |
84+
*/
85+
static Matrix3D MakeScale(float sx, float sy, float sz) {
86+
return {sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1};
87+
}
88+
89+
/**
90+
* Creates a Matrix3D that rotates by the given angle (in degrees) around the specified axis.
91+
* The returned matrix is:
92+
*
93+
* | t*x*x + c t*x*y + s*z t*x*z - s*y 0 |
94+
* | t*x*y - s*z t*y*y + c t*y*z + s*x 0 |
95+
* | t*x*z + s*y t*y*z - s*x t*z*z + c 0 |
96+
* | 0 0 0 1 |
97+
*
98+
* where:
99+
* x, y, z = normalized components of axis
100+
* c = cos(degrees)
101+
* s = sin(degrees)
102+
* t = 1 - c
103+
* @param axis The axis to rotate about.
104+
* @param degrees The angle of rotation in degrees.
105+
*/
106+
static Matrix3D MakeRotate(const Vec3& axis, float degrees) {
107+
Matrix3D m;
108+
m.setRotate(axis, degrees);
109+
return m;
110+
}
111+
112+
/**
113+
* Creates a Matrix3D that translates by (tx, ty, tz). The returned matrix is:
114+
*
115+
* | 1 0 0 tx |
116+
* | 0 1 0 ty |
117+
* | 0 0 1 tz |
118+
* | 0 0 0 1 |
119+
*/
120+
static Matrix3D MakeTranslate(float tx, float ty, float tz) {
121+
return {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1};
122+
}
123+
124+
/**
125+
* Creates a view matrix for a camera. This is commonly used to transform world coordinates to
126+
* camera (view) coordinates in 3D graphics.
127+
* @param eye The position of the camera.
128+
* @param center The point the camera is looking at.
129+
* @param up The up direction for the camera.
130+
*/
131+
static Matrix3D LookAt(const Vec3& eye, const Vec3& center, const Vec3& up);
132+
133+
/**
134+
* Creates a standard perspective projection matrix. This matrix maps 3D coordinates into
135+
* clip coordinates for perspective rendering.
136+
* The standard projection model is established by defining the camera position, orientation,
137+
* field of view, and near/far planes. Points inside the view frustum are projected onto the near
138+
* plane.
139+
* @param fovyDegrees Field of view angle in degrees (vertical).
140+
* @param aspect Aspect ratio (width / height).
141+
* @param nearZ Distance to the near clipping plane.
142+
* @param farZ Distance to the far clipping plane.
143+
*/
144+
static Matrix3D Perspective(float fovyDegrees, float aspect, float nearZ, float farZ);
145+
146+
/**
147+
* Maps a rectangle using this matrix.
148+
* If the matrix contains a perspective transformation, each corner of the rectangle is mapped as a
149+
* 4D point (x, y, 0, 1), and the resulting rectangle is computed from the projected points
150+
* (after perspective division).
151+
*/
152+
Rect mapRect(const Rect& src) const;
153+
154+
friend Matrix3D operator*(const Matrix3D& a, const Matrix3D& b) {
155+
Matrix3D result;
156+
result.setConcat(a, b);
157+
return result;
158+
}
159+
160+
private:
161+
constexpr Matrix3D(float m00, float m01, float m02, float m03, float m10, float m11, float m12,
162+
float m13, float m20, float m21, float m22, float m23, float m30, float m31,
163+
float m32, float m33)
164+
: values{m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33} {
165+
}
166+
167+
/**
168+
* Copies the matrix values into a 16-element array in column-major order.
169+
*/
170+
void getColMajor(float buffer[16]) const {
171+
memcpy(buffer, values, sizeof(values));
172+
}
173+
174+
/**
175+
* Copies the matrix values into a 16-element array in row-major order.
176+
*/
177+
void getRowMajor(float buffer[16]) const;
178+
179+
/**
180+
* Concatenates two matrices and stores the result in this matrix. M' = a * b.
181+
*/
182+
void setConcat(const Matrix3D& a, const Matrix3D& b);
183+
184+
/**
185+
* Concatenates the given matrix with this matrix, and stores the result in this matrix. M' = m * M.
186+
*/
187+
void preConcat(const Matrix3D& m);
188+
189+
/**
190+
* Concatenates this matrix with the given matrix, and stores the result in this matrix. M' = M * m.
191+
*/
192+
void postConcat(const Matrix3D& m);
193+
194+
/**
195+
* Pre-concatenates a scale to this matrix. M' = S * M.
196+
*/
197+
void preScale(float sx, float sy, float sz);
198+
199+
/**
200+
* Post-concatenates a scale to this matrix. M' = M * S.
201+
*/
202+
void postScale(float sx, float sy, float sz);
203+
204+
/**
205+
* Pre-concatenates a translation to this matrix. M' = T * M.
206+
*/
207+
void preTranslate(float tx, float ty, float tz);
208+
209+
/**
210+
* Post-concatenates a translation to this matrix. M' = M * T.
211+
*/
212+
void postTranslate(float tx, float ty, float tz);
213+
214+
/**
215+
* Pre-concatenates a rotation to this matrix. M' = R * M.
216+
*/
217+
void preRotate(const Vec3& axis, float degrees);
218+
219+
/**
220+
* Post-concatenates a rotation to this matrix. M' = M * R.
221+
*/
222+
void postRotate(const Vec3& axis, float degrees);
223+
224+
/**
225+
* Calculates the inverse of the current matrix and stores the result in the Matrix3D object
226+
* pointed to by inverse.
227+
* @param inverse Pointer to the Matrix3D object used to store the inverse matrix. Must not be
228+
* nullptr. If the current matrix is not invertible, inverse will not be modified.
229+
* @return Returns true if the inverse matrix exists; otherwise, returns false.
230+
*/
231+
bool invert(Matrix3D* inverse) const;
232+
233+
/**
234+
* Returns the transpose of the current matrix.
235+
*/
236+
Matrix3D transpose() const;
237+
238+
/**
239+
* Maps a 4D point (x, y, z, w) using this matrix.
240+
* If the current matrix contains a perspective transformation, the returned Vec4 is not
241+
* perspective-divided; i.e., the w component of the result may not be 1.
242+
*/
243+
Vec4 mapPoint(float x, float y, float z, float w) const;
244+
245+
Vec4 getCol(int i) const {
246+
Vec4 v;
247+
memcpy(&v, values + i * 4, sizeof(Vec4));
248+
return v;
249+
}
250+
251+
void setAll(float m00, float m01, float m02, float m03, float m10, float m11, float m12,
252+
float m13, float m20, float m21, float m22, float m23, float m30, float m31,
253+
float m32, float m33);
254+
255+
void setRow(int i, const Vec4& v) {
256+
values[i + 0] = v.x;
257+
values[i + 4] = v.y;
258+
values[i + 8] = v.z;
259+
values[i + 12] = v.w;
260+
}
261+
262+
void setColumn(int i, const Vec4& v) {
263+
memcpy(&values[i * 4], v.ptr(), sizeof(v));
264+
}
265+
266+
void setIdentity() {
267+
*this = Matrix3D();
268+
}
269+
270+
void setRotate(const Vec3& axis, float degrees);
271+
272+
void setRotateUnit(const Vec3& axis, float degrees);
273+
274+
void setRotateUnitSinCos(const Vec3& axis, float sinV, float cosV);
275+
276+
bool hasPerspective() const {
277+
return (values[3] != 0 || values[7] != 0 || values[11] != 0 || values[15] != 1);
278+
}
279+
280+
bool operator==(const Matrix3D& other) const;
281+
282+
bool operator!=(const Matrix3D& other) const {
283+
return !(other == *this);
284+
}
285+
286+
Vec4 operator*(const Vec4& v) const {
287+
return this->mapPoint(v.x, v.y, v.z, v.w);
288+
}
289+
290+
float values[16] = {.0f};
291+
};
292+
293+
} // namespace tgfx

0 commit comments

Comments
 (0)