1 | /* |
2 | * Copyright (c) 2020 - 2023 the ThorVG project. All rights reserved. |
3 | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | * of this software and associated documentation files (the "Software"), to deal |
6 | * in the Software without restriction, including without limitation the rights |
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
8 | * copies of the Software, and to permit persons to whom the Software is |
9 | * furnished to do so, subject to the following conditions: |
10 | |
11 | * The above copyright notice and this permission notice shall be included in all |
12 | * copies or substantial portions of the Software. |
13 | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
20 | * SOFTWARE. |
21 | */ |
22 | |
23 | #include <math.h> |
24 | #include "tvgSwCommon.h" |
25 | |
26 | |
27 | /************************************************************************/ |
28 | /* Internal Class Implementation */ |
29 | /************************************************************************/ |
30 | |
31 | //clz: count leading zero’s |
32 | #if defined(_MSC_VER) && !defined(__clang__) |
33 | #include <intrin.h> |
34 | static uint32_t __inline _clz(uint32_t value) |
35 | { |
36 | unsigned long leadingZero = 0; |
37 | if (_BitScanReverse(&leadingZero, value)) return 31 - leadingZero; |
38 | else return 32; |
39 | } |
40 | #else |
41 | #define _clz(x) __builtin_clz((x)) |
42 | #endif |
43 | |
44 | |
45 | constexpr SwFixed CORDIC_FACTOR = 0xDBD95B16UL; //the Cordic shrink factor 0.858785336480436 * 2^32 |
46 | |
47 | //this table was generated for SW_FT_PI = 180L << 16, i.e. degrees |
48 | constexpr static auto ATAN_MAX = 23; |
49 | constexpr static SwFixed ATAN_TBL[] = { |
50 | 1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, |
51 | 14668L, 7334L, 3667L, 1833L, 917L, 458L, 229L, 115L, |
52 | 57L, 29L, 14L, 7L, 4L, 2L, 1L}; |
53 | |
54 | static inline SwCoord SATURATE(const SwCoord x) |
55 | { |
56 | return (x >> (sizeof(SwCoord) * 8 - 1)); |
57 | } |
58 | |
59 | |
60 | static inline SwFixed PAD_ROUND(const SwFixed x, int32_t n) |
61 | { |
62 | return (((x) + ((n)/2)) & ~((n)-1)); |
63 | } |
64 | |
65 | |
66 | static SwCoord _downscale(SwFixed x) |
67 | { |
68 | //multiply a give value by the CORDIC shrink factor |
69 | auto s = abs(x); |
70 | int64_t t = (s * static_cast<int64_t>(CORDIC_FACTOR)) + 0x100000000UL; |
71 | s = static_cast<SwFixed>(t >> 32); |
72 | if (x < 0) s = -s; |
73 | return s; |
74 | } |
75 | |
76 | |
77 | static int32_t _normalize(SwPoint& pt) |
78 | { |
79 | /* the highest bit in overflow-safe vector components |
80 | MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ |
81 | constexpr auto SAFE_MSB = 29; |
82 | |
83 | auto v = pt; |
84 | |
85 | //High order bit(MSB) |
86 | int32_t shift = 31 - _clz(abs(v.x) | abs(v.y)); |
87 | |
88 | if (shift <= SAFE_MSB) { |
89 | shift = SAFE_MSB - shift; |
90 | pt.x = static_cast<SwCoord>((unsigned long)v.x << shift); |
91 | pt.y = static_cast<SwCoord>((unsigned long)v.y << shift); |
92 | } else { |
93 | shift -= SAFE_MSB; |
94 | pt.x = v.x >> shift; |
95 | pt.y = v.y >> shift; |
96 | shift = -shift; |
97 | } |
98 | return shift; |
99 | } |
100 | |
101 | |
102 | static void _polarize(SwPoint& pt) |
103 | { |
104 | auto v = pt; |
105 | SwFixed theta; |
106 | |
107 | //Get the vector into [-PI/4, PI/4] sector |
108 | if (v.y > v.x) { |
109 | if (v.y > -v.x) { |
110 | auto tmp = v.y; |
111 | v.y = -v.x; |
112 | v.x = tmp; |
113 | theta = SW_ANGLE_PI2; |
114 | } else { |
115 | theta = v.y > 0 ? SW_ANGLE_PI : -SW_ANGLE_PI; |
116 | v.x = -v.x; |
117 | v.y = -v.y; |
118 | } |
119 | } else { |
120 | if (v.y < -v.x) { |
121 | theta = -SW_ANGLE_PI2; |
122 | auto tmp = -v.y; |
123 | v.y = v.x; |
124 | v.x = tmp; |
125 | } else { |
126 | theta = 0; |
127 | } |
128 | } |
129 | |
130 | auto atan = ATAN_TBL; |
131 | uint32_t i; |
132 | SwFixed j; |
133 | |
134 | //Pseudorotations. with right shifts |
135 | for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) { |
136 | if (v.y > 0) { |
137 | auto tmp = v.x + ((v.y + j) >> i); |
138 | v.y = v.y - ((v.x + j) >> i); |
139 | v.x = tmp; |
140 | theta += *atan++; |
141 | } else { |
142 | auto tmp = v.x - ((v.y + j) >> i); |
143 | v.y = v.y + ((v.x + j) >> i); |
144 | v.x = tmp; |
145 | theta -= *atan++; |
146 | } |
147 | } |
148 | |
149 | //round theta |
150 | if (theta >= 0) theta = PAD_ROUND(theta, 32); |
151 | else theta = -PAD_ROUND(-theta, 32); |
152 | |
153 | pt.x = v.x; |
154 | pt.y = theta; |
155 | } |
156 | |
157 | |
158 | static void _rotate(SwPoint& pt, SwFixed theta) |
159 | { |
160 | SwFixed x = pt.x; |
161 | SwFixed y = pt.y; |
162 | |
163 | //Rotate inside [-PI/4, PI/4] sector |
164 | while (theta < -SW_ANGLE_PI4) { |
165 | auto tmp = y; |
166 | y = -x; |
167 | x = tmp; |
168 | theta += SW_ANGLE_PI2; |
169 | } |
170 | |
171 | while (theta > SW_ANGLE_PI4) { |
172 | auto tmp = -y; |
173 | y = x; |
174 | x = tmp; |
175 | theta -= SW_ANGLE_PI2; |
176 | } |
177 | |
178 | auto atan = ATAN_TBL; |
179 | uint32_t i; |
180 | SwFixed j; |
181 | |
182 | for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) { |
183 | if (theta < 0) { |
184 | auto tmp = x + ((y + j) >> i); |
185 | y = y - ((x + j) >> i); |
186 | x = tmp; |
187 | theta += *atan++; |
188 | } else { |
189 | auto tmp = x - ((y + j) >> i); |
190 | y = y + ((x + j) >> i); |
191 | x = tmp; |
192 | theta -= *atan++; |
193 | } |
194 | } |
195 | |
196 | pt.x = static_cast<SwCoord>(x); |
197 | pt.y = static_cast<SwCoord>(y); |
198 | } |
199 | |
200 | |
201 | /************************************************************************/ |
202 | /* External Class Implementation */ |
203 | /************************************************************************/ |
204 | |
205 | SwFixed mathMean(SwFixed angle1, SwFixed angle2) |
206 | { |
207 | return angle1 + mathDiff(angle1, angle2) / 2; |
208 | } |
209 | |
210 | |
211 | bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut) |
212 | { |
213 | auto d1 = base[2] - base[3]; |
214 | auto d2 = base[1] - base[2]; |
215 | auto d3 = base[0] - base[1]; |
216 | |
217 | if (d1.small()) { |
218 | if (d2.small()) { |
219 | if (d3.small()) { |
220 | //basically a point. |
221 | //do nothing to retain original direction |
222 | } else { |
223 | angleIn = angleMid = angleOut = mathAtan(d3); |
224 | } |
225 | } else { |
226 | if (d3.small()) { |
227 | angleIn = angleMid = angleOut = mathAtan(d2); |
228 | } else { |
229 | angleIn = angleMid = mathAtan(d2); |
230 | angleOut = mathAtan(d3); |
231 | } |
232 | } |
233 | } else { |
234 | if (d2.small()) { |
235 | if (d3.small()) { |
236 | angleIn = angleMid = angleOut = mathAtan(d1); |
237 | } else { |
238 | angleIn = mathAtan(d1); |
239 | angleOut = mathAtan(d3); |
240 | angleMid = mathMean(angleIn, angleOut); |
241 | } |
242 | } else { |
243 | if (d3.small()) { |
244 | angleIn = mathAtan(d1); |
245 | angleMid = angleOut = mathAtan(d2); |
246 | } else { |
247 | angleIn = mathAtan(d1); |
248 | angleMid = mathAtan(d2); |
249 | angleOut = mathAtan(d3); |
250 | } |
251 | } |
252 | } |
253 | |
254 | auto theta1 = abs(mathDiff(angleIn, angleMid)); |
255 | auto theta2 = abs(mathDiff(angleMid, angleOut)); |
256 | |
257 | if ((theta1 < (SW_ANGLE_PI / 8)) && (theta2 < (SW_ANGLE_PI / 8))) return true; |
258 | return false; |
259 | } |
260 | |
261 | |
262 | int64_t mathMultiply(int64_t a, int64_t b) |
263 | { |
264 | int32_t s = 1; |
265 | |
266 | //move sign |
267 | if (a < 0) { |
268 | a = -a; |
269 | s = -s; |
270 | } |
271 | if (b < 0) { |
272 | b = -b; |
273 | s = -s; |
274 | } |
275 | int64_t c = (a * b + 0x8000L) >> 16; |
276 | return (s > 0) ? c : -c; |
277 | } |
278 | |
279 | |
280 | int64_t mathDivide(int64_t a, int64_t b) |
281 | { |
282 | int32_t s = 1; |
283 | |
284 | //move sign |
285 | if (a < 0) { |
286 | a = -a; |
287 | s = -s; |
288 | } |
289 | if (b < 0) { |
290 | b = -b; |
291 | s = -s; |
292 | } |
293 | int64_t q = b > 0 ? ((a << 16) + (b >> 1)) / b : 0x7FFFFFFFL; |
294 | return (s < 0 ? -q : q); |
295 | } |
296 | |
297 | |
298 | int64_t mathMulDiv(int64_t a, int64_t b, int64_t c) |
299 | { |
300 | int32_t s = 1; |
301 | |
302 | //move sign |
303 | if (a < 0) { |
304 | a = -a; |
305 | s = -s; |
306 | } |
307 | if (b < 0) { |
308 | b = -b; |
309 | s = -s; |
310 | } |
311 | if (c < 0) { |
312 | c = -c; |
313 | s = -s; |
314 | } |
315 | int64_t d = c > 0 ? (a * b + (c >> 1)) / c : 0x7FFFFFFFL; |
316 | |
317 | return (s > 0 ? d : -d); |
318 | } |
319 | |
320 | |
321 | void mathRotate(SwPoint& pt, SwFixed angle) |
322 | { |
323 | if (angle == 0 || (pt.x == 0 && pt.y == 0)) return; |
324 | |
325 | auto v = pt; |
326 | auto shift = _normalize(v); |
327 | |
328 | auto theta = angle; |
329 | _rotate(v, theta); |
330 | |
331 | v.x = _downscale(v.x); |
332 | v.y = _downscale(v.y); |
333 | |
334 | if (shift > 0) { |
335 | auto half = static_cast<int32_t>(1L << (shift - 1)); |
336 | pt.x = (v.x + half + SATURATE(v.x)) >> shift; |
337 | pt.y = (v.y + half + SATURATE(v.y)) >> shift; |
338 | } else { |
339 | shift = -shift; |
340 | pt.x = static_cast<SwCoord>((unsigned long)v.x << shift); |
341 | pt.y = static_cast<SwCoord>((unsigned long)v.y << shift); |
342 | } |
343 | } |
344 | |
345 | SwFixed mathTan(SwFixed angle) |
346 | { |
347 | SwPoint v = {CORDIC_FACTOR >> 8, 0}; |
348 | _rotate(v, angle); |
349 | return mathDivide(v.y, v.x); |
350 | } |
351 | |
352 | |
353 | SwFixed mathAtan(const SwPoint& pt) |
354 | { |
355 | if (pt.x == 0 && pt.y == 0) return 0; |
356 | |
357 | auto v = pt; |
358 | _normalize(v); |
359 | _polarize(v); |
360 | |
361 | return v.y; |
362 | } |
363 | |
364 | |
365 | SwFixed mathSin(SwFixed angle) |
366 | { |
367 | return mathCos(SW_ANGLE_PI2 - angle); |
368 | } |
369 | |
370 | |
371 | SwFixed mathCos(SwFixed angle) |
372 | { |
373 | SwPoint v = {CORDIC_FACTOR >> 8, 0}; |
374 | _rotate(v, angle); |
375 | return (v.x + 0x80L) >> 8; |
376 | } |
377 | |
378 | |
379 | SwFixed mathLength(const SwPoint& pt) |
380 | { |
381 | auto v = pt; |
382 | |
383 | //trivial case |
384 | if (v.x == 0) return abs(v.y); |
385 | if (v.y == 0) return abs(v.x); |
386 | |
387 | //general case |
388 | auto shift = _normalize(v); |
389 | _polarize(v); |
390 | v.x = _downscale(v.x); |
391 | |
392 | if (shift > 0) return (v.x + (static_cast<SwFixed>(1) << (shift -1))) >> shift; |
393 | return static_cast<SwFixed>((uint32_t)v.x << -shift); |
394 | } |
395 | |
396 | |
397 | void mathSplitCubic(SwPoint* base) |
398 | { |
399 | SwCoord a, b, c, d; |
400 | |
401 | base[6].x = base[3].x; |
402 | c = base[1].x; |
403 | d = base[2].x; |
404 | base[1].x = a = (base[0].x + c) / 2; |
405 | base[5].x = b = (base[3].x + d) / 2; |
406 | c = (c + d) / 2; |
407 | base[2].x = a = (a + c) / 2; |
408 | base[4].x = b = (b + c) / 2; |
409 | base[3].x = (a + b) / 2; |
410 | |
411 | base[6].y = base[3].y; |
412 | c = base[1].y; |
413 | d = base[2].y; |
414 | base[1].y = a = (base[0].y + c) / 2; |
415 | base[5].y = b = (base[3].y + d) / 2; |
416 | c = (c + d) / 2; |
417 | base[2].y = a = (a + c) / 2; |
418 | base[4].y = b = (b + c) / 2; |
419 | base[3].y = (a + b) / 2; |
420 | } |
421 | |
422 | |
423 | SwFixed mathDiff(SwFixed angle1, SwFixed angle2) |
424 | { |
425 | auto delta = angle2 - angle1; |
426 | |
427 | delta %= SW_ANGLE_2PI; |
428 | if (delta < 0) delta += SW_ANGLE_2PI; |
429 | if (delta > SW_ANGLE_PI) delta -= SW_ANGLE_2PI; |
430 | |
431 | return delta; |
432 | } |
433 | |
434 | |
435 | SwPoint mathTransform(const Point* to, const Matrix* transform) |
436 | { |
437 | if (!transform) return {TO_SWCOORD(to->x), TO_SWCOORD(to->y)}; |
438 | |
439 | auto tx = to->x * transform->e11 + to->y * transform->e12 + transform->e13; |
440 | auto ty = to->x * transform->e21 + to->y * transform->e22 + transform->e23; |
441 | |
442 | return {TO_SWCOORD(tx), TO_SWCOORD(ty)}; |
443 | } |
444 | |
445 | |
446 | bool mathClipBBox(const SwBBox& clipper, SwBBox& clipee) |
447 | { |
448 | clipee.max.x = (clipee.max.x < clipper.max.x) ? clipee.max.x : clipper.max.x; |
449 | clipee.max.y = (clipee.max.y < clipper.max.y) ? clipee.max.y : clipper.max.y; |
450 | clipee.min.x = (clipee.min.x > clipper.min.x) ? clipee.min.x : clipper.min.x; |
451 | clipee.min.y = (clipee.min.y > clipper.min.y) ? clipee.min.y : clipper.min.y; |
452 | |
453 | //Check valid region |
454 | if (clipee.max.x - clipee.min.x < 1 && clipee.max.y - clipee.min.y < 1) return false; |
455 | |
456 | //Check boundary |
457 | if (clipee.min.x >= clipper.max.x || clipee.min.y >= clipper.max.y || |
458 | clipee.max.x <= clipper.min.x || clipee.max.y <= clipper.min.y) return false; |
459 | |
460 | return true; |
461 | } |
462 | |
463 | |
464 | bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack) |
465 | { |
466 | if (!outline) return false; |
467 | |
468 | auto pt = outline->pts.data; |
469 | |
470 | if (outline->pts.empty() || outline->cntrs.empty()) { |
471 | renderRegion.reset(); |
472 | return false; |
473 | } |
474 | |
475 | auto xMin = pt->x; |
476 | auto xMax = pt->x; |
477 | auto yMin = pt->y; |
478 | auto yMax = pt->y; |
479 | |
480 | for (++pt; pt < outline->pts.end(); ++pt) { |
481 | if (xMin > pt->x) xMin = pt->x; |
482 | if (xMax < pt->x) xMax = pt->x; |
483 | if (yMin > pt->y) yMin = pt->y; |
484 | if (yMax < pt->y) yMax = pt->y; |
485 | } |
486 | //Since no antialiasing is applied in the Fast Track case, |
487 | //the rasterization region has to be rearranged. |
488 | //https://github.com/Samsung/thorvg/issues/916 |
489 | if (fastTrack) { |
490 | renderRegion.min.x = static_cast<SwCoord>(round(xMin / 64.0f)); |
491 | renderRegion.max.x = static_cast<SwCoord>(round(xMax / 64.0f)); |
492 | renderRegion.min.y = static_cast<SwCoord>(round(yMin / 64.0f)); |
493 | renderRegion.max.y = static_cast<SwCoord>(round(yMax / 64.0f)); |
494 | } else { |
495 | renderRegion.min.x = xMin >> 6; |
496 | renderRegion.max.x = (xMax + 63) >> 6; |
497 | renderRegion.min.y = yMin >> 6; |
498 | renderRegion.max.y = (yMax + 63) >> 6; |
499 | } |
500 | return mathClipBBox(clipRegion, renderRegion); |
501 | } |
502 | |