1 | // [Blend2D] |
2 | // 2D Vector Graphics Powered by a JIT Compiler. |
3 | // |
4 | // [License] |
5 | // Zlib - See LICENSE.md file in the package. |
6 | |
7 | #include "./blapi-build_p.h" |
8 | #include "./blmath_p.h" |
9 | #include "./blmatrix_p.h" |
10 | #include "./blruntime_p.h" |
11 | #include "./blsimd_p.h" |
12 | |
13 | // ============================================================================ |
14 | // [BLMatrix2D - Global Variables] |
15 | // ============================================================================ |
16 | |
17 | const BLMatrix2D blMatrix2DIdentity { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 }; |
18 | |
19 | BLMapPointDArrayFunc blMatrix2DMapPointDArrayFuncs[BL_MATRIX2D_TYPE_COUNT]; |
20 | |
21 | // ============================================================================ |
22 | // [BLMatrix2D - Reset] |
23 | // ============================================================================ |
24 | |
25 | BLResult blMatrix2DSetIdentity(BLMatrix2D* self) noexcept { |
26 | self->reset(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); |
27 | return BL_SUCCESS; |
28 | } |
29 | |
30 | BLResult blMatrix2DSetTranslation(BLMatrix2D* self, double x, double y) noexcept { |
31 | self->reset(1.0, 0.0, 0.0, 1.0, x, y); |
32 | return BL_SUCCESS; |
33 | } |
34 | |
35 | BLResult blMatrix2DSetScaling(BLMatrix2D* self, double x, double y) noexcept { |
36 | self->reset(x, 0.0, 0.0, y, 0.0, 0.0); |
37 | return BL_SUCCESS; |
38 | } |
39 | |
40 | BLResult blMatrix2DSetSkewing(BLMatrix2D* self, double x, double y) noexcept { |
41 | double xTan = blTan(x); |
42 | double yTan = blTan(y); |
43 | |
44 | self->reset(1.0, yTan, xTan, 1.0, 0.0, 0.0); |
45 | return BL_SUCCESS; |
46 | } |
47 | |
48 | BLResult blMatrix2DSetRotation(BLMatrix2D* self, double angle, double x, double y) noexcept { |
49 | double as = blSin(angle); |
50 | double ac = blCos(angle); |
51 | |
52 | self->reset(ac, as, -as, ac, x, y); |
53 | return BL_SUCCESS; |
54 | } |
55 | |
56 | // ============================================================================ |
57 | // [BLMatrix2D - Ops] |
58 | // ============================================================================ |
59 | |
60 | BLResult blMatrix2DApplyOp(BLMatrix2D* self, uint32_t opType, const void* opData) noexcept { |
61 | BLMatrix2D* a = self; |
62 | const double* data = static_cast<const double*>(opData); |
63 | |
64 | switch (opType) { |
65 | // |1 0| |
66 | // A' = |0 1| |
67 | // |0 0| |
68 | case BL_MATRIX2D_OP_RESET: |
69 | a->reset(); |
70 | return BL_SUCCESS; |
71 | |
72 | // |
73 | // A' = B |
74 | // |
75 | case BL_MATRIX2D_OP_ASSIGN: |
76 | a->reset(*static_cast<const BLMatrix2D*>(opData)); |
77 | return BL_SUCCESS; |
78 | |
79 | // [1 0] |
80 | // A' = [0 1] * A |
81 | // [X Y] |
82 | case BL_MATRIX2D_OP_TRANSLATE: { |
83 | double x = data[0]; |
84 | double y = data[1]; |
85 | |
86 | a->m20 += x * a->m00 + y * a->m10; |
87 | a->m21 += x * a->m01 + y * a->m11; |
88 | |
89 | return BL_SUCCESS; |
90 | } |
91 | |
92 | // [X 0] |
93 | // A' = [0 Y] * A |
94 | // [0 0] |
95 | case BL_MATRIX2D_OP_SCALE: { |
96 | double x = data[0]; |
97 | double y = data[1]; |
98 | |
99 | a->m00 *= x; |
100 | a->m01 *= x; |
101 | a->m10 *= y; |
102 | a->m11 *= y; |
103 | |
104 | return BL_SUCCESS; |
105 | } |
106 | |
107 | // [ 1 tan(y)] |
108 | // A' = [tan(x) 1 ] * A |
109 | // [ 0 0 ] |
110 | case BL_MATRIX2D_OP_SKEW: { |
111 | double x = data[0]; |
112 | double y = data[1]; |
113 | double xTan = blTan(x); |
114 | double yTan = blTan(y); |
115 | |
116 | double t00 = yTan * a->m10; |
117 | double t01 = yTan * a->m11; |
118 | |
119 | a->m10 += xTan * a->m00; |
120 | a->m11 += xTan * a->m01; |
121 | |
122 | a->m00 += t00; |
123 | a->m01 += t01; |
124 | |
125 | return BL_SUCCESS; |
126 | } |
127 | |
128 | // Tx and Ty are zero unless rotating about a point: |
129 | // |
130 | // Tx = Px - cos(a) * Px + sin(a) * Py |
131 | // Ty = Py - sin(a) * Px - cos(a) * Py |
132 | // |
133 | // [ cos(a) sin(a)] |
134 | // A' = [-sin(a) cos(a)] * A |
135 | // [ Tx Ty ] |
136 | case BL_MATRIX2D_OP_ROTATE: |
137 | case BL_MATRIX2D_OP_ROTATE_PT: { |
138 | double angle = data[0]; |
139 | double as = blSin(angle); |
140 | double ac = blCos(angle); |
141 | |
142 | double t00 = as * a->m10 + ac * a->m00; |
143 | double t01 = as * a->m11 + ac * a->m01; |
144 | double t10 = ac * a->m10 - as * a->m00; |
145 | double t11 = ac * a->m11 - as * a->m01; |
146 | |
147 | if (opType == BL_MATRIX2D_OP_ROTATE_PT) { |
148 | double px = data[1]; |
149 | double py = data[2]; |
150 | |
151 | double tx = px - ac * px + as * py; |
152 | double ty = py - as * px - ac * py; |
153 | |
154 | double t20 = tx * a->m00 + ty * a->m10 + a->m20; |
155 | double t21 = tx * a->m01 + ty * a->m11 + a->m21; |
156 | |
157 | a->m20 = t20; |
158 | a->m21 = t21; |
159 | } |
160 | |
161 | a->m00 = t00; |
162 | a->m01 = t01; |
163 | |
164 | a->m10 = t10; |
165 | a->m11 = t11; |
166 | |
167 | return BL_SUCCESS; |
168 | } |
169 | |
170 | // A' = B * A |
171 | case BL_MATRIX2D_OP_TRANSFORM: { |
172 | const BLMatrix2D* b = static_cast<const BLMatrix2D*>(opData); |
173 | |
174 | a->reset(b->m00 * a->m00 + b->m01 * a->m10, |
175 | b->m00 * a->m01 + b->m01 * a->m11, |
176 | b->m10 * a->m00 + b->m11 * a->m10, |
177 | b->m10 * a->m01 + b->m11 * a->m11, |
178 | b->m20 * a->m00 + b->m21 * a->m10 + a->m20, |
179 | b->m20 * a->m01 + b->m21 * a->m11 + a->m21); |
180 | |
181 | return BL_SUCCESS; |
182 | } |
183 | |
184 | // [1 0] |
185 | // A' = A * [0 1] |
186 | // [X Y] |
187 | case BL_MATRIX2D_OP_POST_TRANSLATE: { |
188 | double x = data[0]; |
189 | double y = data[1]; |
190 | |
191 | a->m20 += x; |
192 | a->m21 += y; |
193 | |
194 | return BL_SUCCESS; |
195 | } |
196 | |
197 | // [X 0] |
198 | // A' = A * [0 Y] |
199 | // [0 0] |
200 | case BL_MATRIX2D_OP_POST_SCALE: { |
201 | double x = data[0]; |
202 | double y = data[1]; |
203 | |
204 | a->m00 *= x; |
205 | a->m01 *= y; |
206 | a->m10 *= x; |
207 | a->m11 *= y; |
208 | a->m20 *= x; |
209 | a->m21 *= y; |
210 | |
211 | return BL_SUCCESS; |
212 | } |
213 | |
214 | // [ 1 tan(y)] |
215 | // A' = A * [tan(x) 1 ] |
216 | // [ 0 0 ] |
217 | case BL_MATRIX2D_OP_POST_SKEW:{ |
218 | double x = data[0]; |
219 | double y = data[1]; |
220 | double xTan = blTan(x); |
221 | double yTan = blTan(y); |
222 | |
223 | double t00 = a->m01 * xTan; |
224 | double t10 = a->m11 * xTan; |
225 | double t20 = a->m21 * xTan; |
226 | |
227 | a->m01 += a->m00 * yTan; |
228 | a->m11 += a->m10 * yTan; |
229 | a->m21 += a->m20 * yTan; |
230 | |
231 | a->m00 += t00; |
232 | a->m10 += t10; |
233 | a->m20 += t20; |
234 | |
235 | return BL_SUCCESS; |
236 | } |
237 | |
238 | // [ cos(a) sin(a)] |
239 | // A' = A * [-sin(a) cos(a)] |
240 | // [ x' y' ] |
241 | case BL_MATRIX2D_OP_POST_ROTATE: |
242 | case BL_MATRIX2D_OP_POST_ROTATE_PT: { |
243 | double angle = data[0]; |
244 | double as = blSin(angle); |
245 | double ac = blCos(angle); |
246 | |
247 | double t00 = a->m00 * ac - a->m01 * as; |
248 | double t01 = a->m00 * as + a->m01 * ac; |
249 | double t10 = a->m10 * ac - a->m11 * as; |
250 | double t11 = a->m10 * as + a->m11 * ac; |
251 | double t20 = a->m20 * ac - a->m21 * as; |
252 | double t21 = a->m20 * as + a->m21 * ac; |
253 | |
254 | a->reset(t00, t01, t10, t11, t20, t21); |
255 | if (opType != BL_MATRIX2D_OP_POST_ROTATE_PT) |
256 | return BL_SUCCESS; |
257 | |
258 | double px = data[1]; |
259 | double py = data[2]; |
260 | |
261 | a->m20 = t20 + px - ac * px + as * py; |
262 | a->m21 = t21 + py - as * px - ac * py; |
263 | |
264 | return BL_SUCCESS; |
265 | } |
266 | |
267 | // A' = A * B |
268 | case BL_MATRIX2D_OP_POST_TRANSFORM: { |
269 | const BLMatrix2D* b = static_cast<const BLMatrix2D*>(opData); |
270 | |
271 | a->reset(a->m00 * b->m00 + a->m01 * b->m10, |
272 | a->m00 * b->m01 + a->m01 * b->m11, |
273 | a->m10 * b->m00 + a->m11 * b->m10, |
274 | a->m10 * b->m01 + a->m11 * b->m11, |
275 | a->m20 * b->m00 + a->m21 * b->m10 + b->m20, |
276 | a->m20 * b->m01 + a->m21 * b->m11 + b->m21); |
277 | |
278 | return BL_SUCCESS; |
279 | } |
280 | |
281 | default: |
282 | return blTraceError(BL_ERROR_INVALID_VALUE); |
283 | } |
284 | } |
285 | |
286 | BLResult blMatrix2DInvert(BLMatrix2D* dst, const BLMatrix2D* src) noexcept { |
287 | double d = src->m00 * src->m11 - src->m01 * src->m10; |
288 | |
289 | if (d == 0.0) |
290 | return blTraceError(BL_ERROR_INVALID_VALUE); |
291 | |
292 | double t00 = src->m11; |
293 | double t01 = -src->m01; |
294 | double t10 = -src->m10; |
295 | double t11 = src->m00; |
296 | |
297 | t00 /= d; |
298 | t01 /= d; |
299 | t10 /= d; |
300 | t11 /= d; |
301 | |
302 | double t20 = -(src->m20 * t00 + src->m21 * t10); |
303 | double t21 = -(src->m20 * t01 + src->m21 * t11); |
304 | |
305 | dst->reset(t00, t01, t10, t11, t20, t21); |
306 | return BL_SUCCESS; |
307 | } |
308 | |
309 | // ============================================================================ |
310 | // [BLMatrix2D - Type] |
311 | // ============================================================================ |
312 | |
313 | uint32_t blMatrix2DGetType(const BLMatrix2D* self) noexcept { |
314 | double m00 = self->m00; |
315 | double m01 = self->m01; |
316 | double m10 = self->m10; |
317 | double m11 = self->m11; |
318 | double m20 = self->m20; |
319 | double m21 = self->m21; |
320 | |
321 | const uint32_t kBit00 = 1u << 3; |
322 | const uint32_t kBit01 = 1u << 2; |
323 | const uint32_t kBit10 = 1u << 1; |
324 | const uint32_t kBit11 = 1u << 0; |
325 | |
326 | #if defined(BL_TARGET_OPT_SSE2) |
327 | |
328 | // NOTE: Ideally this should be somewhere else, but for simplicity and easier |
329 | // displatch it was placed here. We do this as C++ compilers still cannot figure |
330 | // out how to do this and this is a much better solution compared to scalar |
331 | // versions compilers produce. |
332 | using namespace SIMD; |
333 | uint32_t valueMsk = uint32_t(_mm_movemask_pd(vcmpnepd(vsetd128(m00, m01), vzerod128())) << 2) | |
334 | uint32_t(_mm_movemask_pd(vcmpnepd(vsetd128(m10, m11), vzerod128())) << 0) ; |
335 | #else |
336 | uint32_t valueMsk = (uint32_t(m00 != 0.0) << 3) | (uint32_t(m01 != 0.0) << 2) | |
337 | (uint32_t(m10 != 0.0) << 1) | (uint32_t(m11 != 0.0) << 0) ; |
338 | #endif |
339 | |
340 | // Bit-table that contains ones for `valueMsk` combinations that are considered valid. |
341 | uint32_t validTab = (0u << (0 | 0 | 0 | 0 )) | // [m00==0 m01==0 m10==0 m11==0] |
342 | (0u << (0 | 0 | 0 | kBit11)) | // [m00==0 m01==0 m10==0 m11!=0] |
343 | (0u << (0 | 0 | kBit10 | 0 )) | // [m00==0 m01==0 m10!=0 m11==0] |
344 | (1u << (0 | 0 | kBit10 | kBit11)) | // [m00==0 m01==0 m10!=0 m11!=0] |
345 | (0u << (0 | kBit01 | 0 | 0 )) | // [m00==0 m01!=0 m10==0 m11==0] |
346 | (0u << (0 | kBit01 | 0 | kBit11)) | // [m00==0 m01!=0 m10==0 m11!=0] |
347 | (1u << (0 | kBit01 | kBit10 | 0 )) | // [m00==0 m01!=0 m10!=0 m11==0] [SWAP] |
348 | (1u << (0 | kBit01 | kBit10 | kBit11)) | // [m00==0 m01!=0 m10!=0 m11!=0] |
349 | (0u << (kBit00 | 0 | 0 | 0 )) | // [m00!=0 m01==0 m10==0 m11==0] |
350 | (1u << (kBit00 | 0 | 0 | kBit11)) | // [m00!=0 m01==0 m10==0 m11!=0] [SCALE] |
351 | (0u << (kBit00 | 0 | kBit10 | 0 )) | // [m00!=0 m01==0 m10!=0 m11==0] |
352 | (1u << (kBit00 | 0 | kBit10 | kBit11)) | // [m00!=0 m01==0 m10!=0 m11!=0] [AFFINE] |
353 | (1u << (kBit00 | kBit01 | 0 | 0 )) | // [m00!=0 m01!=0 m10==0 m11==0] |
354 | (1u << (kBit00 | kBit01 | 0 | kBit11)) | // [m00!=0 m01!=0 m10==0 m11!=0] [AFFINE] |
355 | (1u << (kBit00 | kBit01 | kBit10 | 0 )) | // [m00!=0 m01!=0 m10!=0 m11==0] [AFFINE] |
356 | (1u << (kBit00 | kBit01 | kBit10 | kBit11)) ; // [m00!=0 m01!=0 m10!=0 m11!=0] [AFFINE] |
357 | |
358 | double d = m00 * m11 - m01 * m10; |
359 | if (!((1u << valueMsk) & validTab) || !blIsFinite(d) || !blIsFinite(m20) || !blIsFinite(m21)) |
360 | return BL_MATRIX2D_TYPE_INVALID; |
361 | |
362 | // Matrix is not swap/affine if: |
363 | // [. 0] |
364 | // [0 .] |
365 | // [. .] |
366 | if (valueMsk != (kBit00 | kBit11)) |
367 | return (valueMsk == (kBit01 | kBit10)) |
368 | ? BL_MATRIX2D_TYPE_SWAP |
369 | : BL_MATRIX2D_TYPE_AFFINE; |
370 | |
371 | // Matrix is not scaling if: |
372 | // [1 .] |
373 | // [. 1] |
374 | // [. .] |
375 | if (!((m00 == 1.0) & (m11 == 1.0))) |
376 | return BL_MATRIX2D_TYPE_SCALE; |
377 | |
378 | // Matrix is not translation if: |
379 | // [. .] |
380 | // [. .] |
381 | // [0 0] |
382 | if (!((m20 == 0.0) & (m21 == 0.0))) |
383 | return BL_MATRIX2D_TYPE_TRANSLATE; |
384 | |
385 | return BL_MATRIX2D_TYPE_IDENTITY; |
386 | } |
387 | |
388 | // ============================================================================ |
389 | // [BLMatrix2D - Map] |
390 | // ============================================================================ |
391 | |
392 | BLResult blMatrix2DMapPointDArray(const BLMatrix2D* self, BLPoint* dst, const BLPoint* src, size_t count) noexcept { |
393 | uint32_t matrixType = BL_MATRIX2D_TYPE_AFFINE; |
394 | |
395 | if (count >= BL_MATRIX_TYPE_MINIMUM_SIZE) |
396 | matrixType = self->type(); |
397 | |
398 | return blMatrix2DMapPointDArrayFuncs[matrixType](self, dst, src, count); |
399 | } |
400 | |
401 | // ============================================================================ |
402 | // [BLMatrix2D - MapPointDArray] |
403 | // ============================================================================ |
404 | |
405 | BL_DIAGNOSTIC_PUSH(BL_DIAGNOSTIC_NO_UNUSED_FUNCTIONS) |
406 | |
407 | static BLResult BL_CDECL blMatrix2DMapPointDArrayIdentity(const BLMatrix2D* self, BLPoint* dst, const BLPoint* src, size_t size) noexcept { |
408 | BL_UNUSED(self); |
409 | if (dst == src) |
410 | return BL_SUCCESS; |
411 | |
412 | for (size_t i = 0; i < size; i++) |
413 | dst[i] = src[i]; |
414 | |
415 | return BL_SUCCESS; |
416 | } |
417 | |
418 | static BLResult BL_CDECL blMatrix2DMapPointDArrayTranslate(const BLMatrix2D* self, BLPoint* dst, const BLPoint* src, size_t size) noexcept { |
419 | double m20 = self->m20; |
420 | double m21 = self->m21; |
421 | |
422 | for (size_t i = 0; i < size; i++) |
423 | dst[i].reset(src[i].x + m20, |
424 | src[i].y + m21); |
425 | |
426 | return BL_SUCCESS; |
427 | } |
428 | |
429 | static BLResult BL_CDECL blMatrix2DMapPointDArrayScale(const BLMatrix2D* self, BLPoint* dst, const BLPoint* src, size_t size) noexcept { |
430 | double m00 = self->m00; |
431 | double m11 = self->m11; |
432 | double m20 = self->m20; |
433 | double m21 = self->m21; |
434 | |
435 | for (size_t i = 0; i < size; i++) |
436 | dst[i].reset(src[i].x * m00 + m20, |
437 | src[i].y * m11 + m21); |
438 | |
439 | return BL_SUCCESS; |
440 | } |
441 | |
442 | static BLResult BL_CDECL blMatrix2DMapPointDArraySwap(const BLMatrix2D* self, BLPoint* dst, const BLPoint* src, size_t size) noexcept { |
443 | double m10 = self->m10; |
444 | double m01 = self->m01; |
445 | double m20 = self->m20; |
446 | double m21 = self->m21; |
447 | |
448 | for (size_t i = 0; i < size; i++) |
449 | dst[i].reset(src[i].y * m10 + m20, |
450 | src[i].x * m01 + m21); |
451 | |
452 | return BL_SUCCESS; |
453 | } |
454 | |
455 | static BLResult BL_CDECL blMatrix2DMapPointDArrayAffine(const BLMatrix2D* self, BLPoint* dst, const BLPoint* src, size_t size) noexcept { |
456 | double m00 = self->m00; |
457 | double m01 = self->m01; |
458 | double m10 = self->m10; |
459 | double m11 = self->m11; |
460 | double m20 = self->m20; |
461 | double m21 = self->m21; |
462 | |
463 | for (size_t i = 0; i < size; i++) |
464 | dst[i].reset(src[i].x * m00 + src[i].y * m10 + m20, |
465 | src[i].x * m01 + src[i].y * m11 + m21); |
466 | |
467 | return BL_SUCCESS; |
468 | } |
469 | |
470 | BL_DIAGNOSTIC_POP |
471 | |
472 | // ============================================================================ |
473 | // [BLMatrix2D - Runtime Init] |
474 | // ============================================================================ |
475 | |
476 | #ifdef BL_BUILD_OPT_SSE2 |
477 | BL_HIDDEN void blMatrix2DRtInit_SSE2(BLRuntimeContext* rt) noexcept; |
478 | #endif |
479 | |
480 | #ifdef BL_BUILD_OPT_AVX |
481 | BL_HIDDEN void blMatrix2DRtInit_AVX(BLRuntimeContext* rt) noexcept; |
482 | #endif |
483 | |
484 | void blMatrix2DRtInit(BLRuntimeContext* rt) noexcept { |
485 | #if !defined(BL_TARGET_OPT_SSE2) |
486 | BL_UNUSED(rt); |
487 | BLMapPointDArrayFunc* funcs = blMatrix2DMapPointDArrayFuncs; |
488 | |
489 | blAssignFunc(&funcs[BL_MATRIX2D_TYPE_IDENTITY ], blMatrix2DMapPointDArrayIdentity); |
490 | blAssignFunc(&funcs[BL_MATRIX2D_TYPE_TRANSLATE], blMatrix2DMapPointDArrayTranslate); |
491 | blAssignFunc(&funcs[BL_MATRIX2D_TYPE_SCALE ], blMatrix2DMapPointDArrayScale); |
492 | blAssignFunc(&funcs[BL_MATRIX2D_TYPE_SWAP ], blMatrix2DMapPointDArraySwap); |
493 | blAssignFunc(&funcs[BL_MATRIX2D_TYPE_AFFINE ], blMatrix2DMapPointDArrayAffine); |
494 | blAssignFunc(&funcs[BL_MATRIX2D_TYPE_INVALID ], blMatrix2DMapPointDArrayAffine); |
495 | #endif |
496 | |
497 | #ifdef BL_BUILD_OPT_SSE2 |
498 | if (blRuntimeHasSSE2(rt)) blMatrix2DRtInit_SSE2(rt); |
499 | #endif |
500 | |
501 | #ifdef BL_BUILD_OPT_AVX |
502 | if (blRuntimeHasAVX(rt)) blMatrix2DRtInit_AVX(rt); |
503 | #endif |
504 | } |
505 | |
506 | // ============================================================================ |
507 | // [BLMatrix2D - Unit Tests] |
508 | // ============================================================================ |
509 | |
510 | #ifdef BL_TEST |
511 | UNIT(blend2d_matrix) { |
512 | INFO("Testing matrix types" ); |
513 | { |
514 | BLMatrix2D m; |
515 | |
516 | m = BLMatrix2D::makeIdentity(); |
517 | EXPECT(m.type() == BL_MATRIX2D_TYPE_IDENTITY); |
518 | |
519 | m = BLMatrix2D::makeTranslation(1.0, 2.0); |
520 | EXPECT(m.type() == BL_MATRIX2D_TYPE_TRANSLATE); |
521 | |
522 | m = BLMatrix2D::makeScaling(2.0, 2.0); |
523 | EXPECT(m.type() == BL_MATRIX2D_TYPE_SCALE); |
524 | |
525 | m.m10 = 3.0; |
526 | EXPECT(m.type() == BL_MATRIX2D_TYPE_AFFINE); |
527 | |
528 | m.reset(0.0, 1.0, 1.0, 0.0, 0.0, 0.0); |
529 | EXPECT(m.type() == BL_MATRIX2D_TYPE_SWAP); |
530 | |
531 | m.reset(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); |
532 | EXPECT(m.type() == BL_MATRIX2D_TYPE_INVALID); |
533 | } |
534 | |
535 | INFO("Testing whether special-case transformations match matrix multiplication" ); |
536 | { |
537 | enum BL_TEST_MATRIX : uint32_t { |
538 | BL_TEST_MATRIX_IDENTITY, |
539 | BL_TEST_MATRIX_TRANSLATE, |
540 | BL_TEST_MATRIX_SCALE, |
541 | BL_TEST_MATRIX_SKEW, |
542 | BL_TEST_MATRIX_ROTATE, |
543 | BL_TEST_MATRIX_COUNT |
544 | }; |
545 | |
546 | static const BLPoint ptOffset(128.0, 64.0); |
547 | static const BLPoint ptScale(1.5, 2.0); |
548 | static const BLPoint ptSkew(1.5, 2.0); |
549 | static const double angle = 0.9; |
550 | |
551 | auto testMatrixName = [](uint32_t type) noexcept -> const char* { |
552 | switch (type) { |
553 | case BL_TEST_MATRIX_IDENTITY : return "Identity" ; |
554 | case BL_TEST_MATRIX_TRANSLATE: return "Translate" ; |
555 | case BL_TEST_MATRIX_SCALE : return "Scale" ; |
556 | case BL_TEST_MATRIX_SKEW : return "Skew" ; |
557 | case BL_TEST_MATRIX_ROTATE : return "Rotate" ; |
558 | default: return "Unknown" ; |
559 | } |
560 | }; |
561 | |
562 | auto createTestMatrix = [](uint32_t type) noexcept -> BLMatrix2D { |
563 | switch (type) { |
564 | case BL_TEST_MATRIX_TRANSLATE: return BLMatrix2D::makeTranslation(ptOffset); |
565 | case BL_TEST_MATRIX_SCALE : return BLMatrix2D::makeScaling(ptScale); |
566 | case BL_TEST_MATRIX_SKEW : return BLMatrix2D::makeSkewing(ptSkew); |
567 | case BL_TEST_MATRIX_ROTATE : return BLMatrix2D::makeRotation(angle); |
568 | |
569 | default: |
570 | return BLMatrix2D::makeIdentity(); |
571 | } |
572 | }; |
573 | |
574 | auto compare = [](const BLMatrix2D& a, const BLMatrix2D& b) noexcept -> bool { |
575 | double diff = blMax(blAbs(a.m00 - b.m00), |
576 | blAbs(a.m01 - b.m01), |
577 | blAbs(a.m10 - b.m10), |
578 | blAbs(a.m11 - b.m11), |
579 | blAbs(a.m20 - b.m20), |
580 | blAbs(a.m21 - b.m21)); |
581 | // If Blend2D is compiled with FMA enabled there could be a difference |
582 | // greater than our blEpsilon<double>, so use a more relaxed value here. |
583 | return diff < 1e-8; |
584 | }; |
585 | |
586 | BLMatrix2D m, n; |
587 | BLMatrix2D a = BLMatrix2D::makeIdentity(); |
588 | BLMatrix2D b; |
589 | |
590 | for (uint32_t aType = 0; aType < BL_TEST_MATRIX_COUNT; aType++) { |
591 | for (uint32_t bType = 0; bType < BL_TEST_MATRIX_COUNT; bType++) { |
592 | a = createTestMatrix(aType); |
593 | b = createTestMatrix(bType); |
594 | |
595 | m = a; |
596 | n = a; |
597 | |
598 | for (uint32_t post = 0; post < 2; post++) { |
599 | if (!post) |
600 | m.transform(b); |
601 | else |
602 | m.postTransform(b); |
603 | |
604 | switch (bType) { |
605 | case BL_TEST_MATRIX_IDENTITY: |
606 | break; |
607 | |
608 | case BL_TEST_MATRIX_TRANSLATE: |
609 | if (!post) |
610 | n.translate(ptOffset); |
611 | else |
612 | n.postTranslate(ptOffset); |
613 | break; |
614 | |
615 | case BL_TEST_MATRIX_SCALE: |
616 | if (!post) |
617 | n.scale(ptScale); |
618 | else |
619 | n.postScale(ptScale); |
620 | break; |
621 | |
622 | case BL_TEST_MATRIX_SKEW: |
623 | if (!post) |
624 | n.skew(ptSkew); |
625 | else |
626 | n.postSkew(ptSkew); |
627 | break; |
628 | |
629 | case BL_TEST_MATRIX_ROTATE: |
630 | if (!post) |
631 | n.rotate(angle); |
632 | else |
633 | n.postRotate(angle); |
634 | break; |
635 | } |
636 | |
637 | if (!compare(m, n)) { |
638 | INFO("Matrices don't match [%s x %s]\n" , testMatrixName(aType), testMatrixName(bType)); |
639 | INFO(" [% 3.14f | % 3.14f] [% 3.14f | % 3.14f]\n" , a.m00, a.m01, b.m00, b.m01); |
640 | INFO(" A [% 3.14f | % 3.14f] B [% 3.14f | % 3.14f]\n" , a.m10, a.m11, b.m10, b.m11); |
641 | INFO(" [% 3.14f | % 3.14f] [% 3.14f | % 3.14f]\n" , a.m20, a.m21, b.m20, b.m21); |
642 | INFO("\n" ); |
643 | INFO("Operation: %s\n" , post ? "M = A * B" : "M = B * A" ); |
644 | INFO(" [% 3.14f | % 3.14f] [% 3.14f | % 3.14f]\n" , m.m00, m.m01, n.m00, n.m01); |
645 | INFO(" M [% 3.14f | % 3.14f] != N [% 3.14f | % 3.14f]\n" , m.m10, m.m11, n.m10, n.m11); |
646 | INFO(" [% 3.14f | % 3.14f] [% 3.14f | % 3.14f]\n" , m.m20, m.m21, n.m20, n.m21); |
647 | EXPECT(false); |
648 | } |
649 | } |
650 | } |
651 | } |
652 | } |
653 | } |
654 | #endif |
655 | |