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 "tvgMath.h"
24#include "tvgSwCommon.h"
25
26
27/************************************************************************/
28/* Internal Class Implementation */
29/************************************************************************/
30
31#define GRADIENT_STOP_SIZE 1024
32#define FIXPT_BITS 8
33#define FIXPT_SIZE (1<<FIXPT_BITS)
34
35
36static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint8_t opacity)
37{
38 if (!fill->ctable) {
39 fill->ctable = static_cast<uint32_t*>(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t)));
40 if (!fill->ctable) return false;
41 }
42
43 const Fill::ColorStop* colors;
44 auto cnt = fdata->colorStops(&colors);
45 if (cnt == 0 || !colors) return false;
46
47 auto pColors = colors;
48
49 auto a = MULTIPLY(pColors->a, opacity);
50 if (a < 255) fill->translucent = true;
51
52 auto r = pColors->r;
53 auto g = pColors->g;
54 auto b = pColors->b;
55 auto rgba = surface->join(r, g, b, a);
56
57 auto inc = 1.0f / static_cast<float>(GRADIENT_STOP_SIZE);
58 auto pos = 1.5f * inc;
59 uint32_t i = 0;
60
61 fill->ctable[i++] = ALPHA_BLEND(rgba | 0xff000000, a);
62
63 while (pos <= pColors->offset) {
64 fill->ctable[i] = fill->ctable[i - 1];
65 ++i;
66 pos += inc;
67 }
68
69 for (uint32_t j = 0; j < cnt - 1; ++j) {
70 auto curr = colors + j;
71 auto next = curr + 1;
72 auto delta = 1.0f / (next->offset - curr->offset);
73 auto a2 = MULTIPLY(next->a, opacity);
74 if (!fill->translucent && a2 < 255) fill->translucent = true;
75
76 auto rgba2 = surface->join(next->r, next->g, next->b, a2);
77
78 while (pos < next->offset && i < GRADIENT_STOP_SIZE) {
79 auto t = (pos - curr->offset) * delta;
80 auto dist = static_cast<int32_t>(255 * t);
81 auto dist2 = 255 - dist;
82
83 auto color = INTERPOLATE(rgba, rgba2, dist2);
84 fill->ctable[i] = ALPHA_BLEND((color | 0xff000000), (color >> 24));
85
86 ++i;
87 pos += inc;
88 }
89 rgba = rgba2;
90 a = a2;
91 }
92 rgba = ALPHA_BLEND((rgba | 0xff000000), a);
93
94 for (; i < GRADIENT_STOP_SIZE; ++i)
95 fill->ctable[i] = rgba;
96
97 //Make sure the last color stop is represented at the end of the table
98 fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba;
99
100 return true;
101}
102
103
104bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* transform)
105{
106 float x1, x2, y1, y2;
107 if (linear->linear(&x1, &y1, &x2, &y2) != Result::Success) return false;
108
109 fill->linear.dx = x2 - x1;
110 fill->linear.dy = y2 - y1;
111 fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy;
112
113 if (fill->linear.len < FLT_EPSILON) return true;
114
115 fill->linear.dx /= fill->linear.len;
116 fill->linear.dy /= fill->linear.len;
117 fill->linear.offset = -fill->linear.dx * x1 - fill->linear.dy * y1;
118
119 auto gradTransform = linear->transform();
120 bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform));
121
122 if (isTransformation) {
123 if (transform) gradTransform = mathMultiply(transform, &gradTransform);
124 } else if (transform) {
125 gradTransform = *transform;
126 isTransformation = true;
127 }
128
129 if (isTransformation) {
130 Matrix invTransform;
131 if (!mathInverse(&gradTransform, &invTransform)) return false;
132
133 fill->linear.offset += fill->linear.dx * invTransform.e13 + fill->linear.dy * invTransform.e23;
134
135 auto dx = fill->linear.dx;
136 fill->linear.dx = dx * invTransform.e11 + fill->linear.dy * invTransform.e21;
137 fill->linear.dy = dx * invTransform.e12 + fill->linear.dy * invTransform.e22;
138
139 fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy;
140 if (fill->linear.len < FLT_EPSILON) return true;
141 }
142
143 return true;
144}
145
146
147bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* transform)
148{
149 float radius, cx, cy;
150 if (radial->radial(&cx, &cy, &radius) != Result::Success) return false;
151 if (radius < FLT_EPSILON) return true;
152
153 float invR = 1.0f / radius;
154 fill->radial.shiftX = -cx;
155 fill->radial.shiftY = -cy;
156 fill->radial.a = radius;
157
158 auto gradTransform = radial->transform();
159 bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform));
160
161 if (isTransformation) {
162 if (transform) gradTransform = mathMultiply(transform, &gradTransform);
163 } else if (transform) {
164 gradTransform = *transform;
165 isTransformation = true;
166 }
167
168 if (isTransformation) {
169 Matrix invTransform;
170 if (!mathInverse(&gradTransform, &invTransform)) return false;
171
172 fill->radial.a11 = invTransform.e11 * invR;
173 fill->radial.a12 = invTransform.e12 * invR;
174 fill->radial.shiftX += invTransform.e13;
175 fill->radial.a21 = invTransform.e21 * invR;
176 fill->radial.a22 = invTransform.e22 * invR;
177 fill->radial.shiftY += invTransform.e23;
178 fill->radial.detSecDeriv = 2.0f * fill->radial.a11 * fill->radial.a11 + 2 * fill->radial.a21 * fill->radial.a21;
179
180 fill->radial.a *= sqrt(pow(invTransform.e11, 2) + pow(invTransform.e21, 2));
181 } else {
182 fill->radial.a11 = fill->radial.a22 = invR;
183 fill->radial.a12 = fill->radial.a21 = 0.0f;
184 fill->radial.detSecDeriv = 2.0f * invR * invR;
185 }
186 fill->radial.shiftX *= invR;
187 fill->radial.shiftY *= invR;
188
189 return true;
190}
191
192
193static inline uint32_t _clamp(const SwFill* fill, int32_t pos)
194{
195 switch (fill->spread) {
196 case FillSpread::Pad: {
197 if (pos >= GRADIENT_STOP_SIZE) pos = GRADIENT_STOP_SIZE - 1;
198 else if (pos < 0) pos = 0;
199 break;
200 }
201 case FillSpread::Repeat: {
202 pos = pos % GRADIENT_STOP_SIZE;
203 if (pos < 0) pos = GRADIENT_STOP_SIZE + pos;
204 break;
205 }
206 case FillSpread::Reflect: {
207 auto limit = GRADIENT_STOP_SIZE * 2;
208 pos = pos % limit;
209 if (pos < 0) pos = limit + pos;
210 if (pos >= GRADIENT_STOP_SIZE) pos = (limit - pos - 1);
211 break;
212 }
213 }
214 return pos;
215}
216
217
218static inline uint32_t _fixedPixel(const SwFill* fill, int32_t pos)
219{
220 int32_t i = (pos + (FIXPT_SIZE / 2)) >> FIXPT_BITS;
221 return fill->ctable[_clamp(fill, i)];
222}
223
224
225static inline uint32_t _pixel(const SwFill* fill, float pos)
226{
227 auto i = static_cast<int32_t>(pos * (GRADIENT_STOP_SIZE - 1) + 0.5f);
228 return fill->ctable[_clamp(fill, i)];
229}
230
231
232/************************************************************************/
233/* External Class Implementation */
234/************************************************************************/
235
236void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity)
237{
238 auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX;
239 auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY;
240
241 // detSecondDerivative = d(detFirstDerivative)/dx = d( d(det)/dx )/dx
242 auto detSecondDerivative = fill->radial.detSecDeriv;
243 // detFirstDerivative = d(det)/dx
244 auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative;
245 auto det = rx * rx + ry * ry;
246
247 if (opacity == 255) {
248 for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) {
249 *dst = opBlendNormal(_pixel(fill, sqrtf(det)), *dst, alpha(cmp));
250 det += detFirstDerivative;
251 detFirstDerivative += detSecondDerivative;
252 }
253 } else {
254 for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) {
255 *dst = opBlendNormal(_pixel(fill, sqrtf(det)), *dst, MULTIPLY(opacity, alpha(cmp)));
256 det += detFirstDerivative;
257 detFirstDerivative += detSecondDerivative;
258 }
259 }
260}
261
262
263void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a)
264{
265 auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX;
266 auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY;
267
268 // detSecondDerivative = d(detFirstDerivative)/dx = d( d(det)/dx )/dx
269 auto detSecondDerivative = fill->radial.detSecDeriv;
270 // detFirstDerivative = d(det)/dx
271 auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative;
272 auto det = rx * rx + ry * ry;
273
274 for (uint32_t i = 0 ; i < len ; ++i, ++dst) {
275 *dst = op(_pixel(fill, sqrtf(det)), *dst, a);
276 det += detFirstDerivative;
277 detFirstDerivative += detSecondDerivative;
278 }
279}
280
281
282void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a)
283{
284 auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX;
285 auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY;
286
287 // detSecondDerivative = d(detFirstDerivative)/dx = d( d(det)/dx )/dx
288 auto detSecondDerivative = fill->radial.detSecDeriv;
289 // detFirstDerivative = d(det)/dx
290 auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative;
291 auto det = rx * rx + ry * ry;
292
293 if (a == 255) {
294 for (uint32_t i = 0 ; i < len ; ++i, ++dst) {
295 auto tmp = op(_pixel(fill, sqrtf(det)), *dst, 255);
296 *dst = op2(tmp, *dst, 255);
297 det += detFirstDerivative;
298 detFirstDerivative += detSecondDerivative;
299 }
300 } else {
301 for (uint32_t i = 0 ; i < len ; ++i, ++dst) {
302 auto tmp = op(_pixel(fill, sqrtf(det)), *dst, 255);
303 auto tmp2 = op2(tmp, *dst, 255);
304 *dst = INTERPOLATE(tmp2, *dst, a);
305 det += detFirstDerivative;
306 detFirstDerivative += detSecondDerivative;
307 }
308 }
309}
310
311
312void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity)
313{
314 //Rotation
315 float rx = x + 0.5f;
316 float ry = y + 0.5f;
317 float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1);
318 float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1);
319
320 if (opacity == 255) {
321 if (mathZero(inc)) {
322 auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
323 for (uint32_t i = 0; i < len; ++i, ++dst, cmp += csize) {
324 *dst = opBlendNormal(color, *dst, alpha(cmp));
325 }
326 return;
327 }
328
329 auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
330 auto vMin = -vMax;
331 auto v = t + (inc * len);
332
333 //we can use fixed point math
334 if (v < vMax && v > vMin) {
335 auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
336 auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
337 for (uint32_t j = 0; j < len; ++j, ++dst, cmp += csize) {
338 *dst = opBlendNormal(_fixedPixel(fill, t2), *dst, alpha(cmp));
339 t2 += inc2;
340 }
341 //we have to fallback to float math
342 } else {
343 uint32_t counter = 0;
344 while (counter++ < len) {
345 *dst = opBlendNormal(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, alpha(cmp));
346 ++dst;
347 t += inc;
348 cmp += csize;
349 }
350 }
351 } else {
352 if (mathZero(inc)) {
353 auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
354 for (uint32_t i = 0; i < len; ++i, ++dst, cmp += csize) {
355 *dst = opBlendNormal(color, *dst, MULTIPLY(alpha(cmp), opacity));
356 }
357 return;
358 }
359
360 auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
361 auto vMin = -vMax;
362 auto v = t + (inc * len);
363
364 //we can use fixed point math
365 if (v < vMax && v > vMin) {
366 auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
367 auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
368 for (uint32_t j = 0; j < len; ++j, ++dst, cmp += csize) {
369 *dst = opBlendNormal(_fixedPixel(fill, t2), *dst, MULTIPLY(alpha(cmp), opacity));
370 t2 += inc2;
371 }
372 //we have to fallback to float math
373 } else {
374 uint32_t counter = 0;
375 while (counter++ < len) {
376 *dst = opBlendNormal(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, MULTIPLY(opacity, alpha(cmp)));
377 ++dst;
378 t += inc;
379 cmp += csize;
380 }
381 }
382 }
383}
384
385
386void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a)
387{
388 //Rotation
389 float rx = x + 0.5f;
390 float ry = y + 0.5f;
391 float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1);
392 float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1);
393
394 if (mathZero(inc)) {
395 auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
396 for (uint32_t i = 0; i < len; ++i, ++dst) {
397 *dst = op(color, *dst, a);
398 }
399 return;
400 }
401
402 auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
403 auto vMin = -vMax;
404 auto v = t + (inc * len);
405
406 //we can use fixed point math
407 if (v < vMax && v > vMin) {
408 auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
409 auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
410 for (uint32_t j = 0; j < len; ++j, ++dst) {
411 *dst = op(_fixedPixel(fill, t2), *dst, a);
412 t2 += inc2;
413 }
414 //we have to fallback to float math
415 } else {
416 uint32_t counter = 0;
417 while (counter++ < len) {
418 *dst = op(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, a);
419 ++dst;
420 t += inc;
421 }
422 }
423}
424
425
426void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a)
427{
428 //Rotation
429 float rx = x + 0.5f;
430 float ry = y + 0.5f;
431 float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1);
432 float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1);
433
434 if (mathZero(inc)) {
435 auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
436 if (a == 255) {
437 for (uint32_t i = 0; i < len; ++i, ++dst) {
438 auto tmp = op(color, *dst, a);
439 *dst = op2(tmp, *dst, 255);
440 }
441 } else {
442 for (uint32_t i = 0; i < len; ++i, ++dst) {
443 auto tmp = op(color, *dst, a);
444 auto tmp2 = op2(tmp, *dst, 255);
445 *dst = INTERPOLATE(tmp2, *dst, a);
446 }
447 }
448 return;
449 }
450
451 auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
452 auto vMin = -vMax;
453 auto v = t + (inc * len);
454
455 if (a == 255) {
456 //we can use fixed point math
457 if (v < vMax && v > vMin) {
458 auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
459 auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
460 for (uint32_t j = 0; j < len; ++j, ++dst) {
461 auto tmp = op(_fixedPixel(fill, t2), *dst, 255);
462 *dst = op2(tmp, *dst, 255);
463 t2 += inc2;
464 }
465 //we have to fallback to float math
466 } else {
467 uint32_t counter = 0;
468 while (counter++ < len) {
469 auto tmp = op(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, 255);
470 *dst = op2(tmp, *dst, 255);
471 ++dst;
472 t += inc;
473 }
474 }
475 } else {
476 //we can use fixed point math
477 if (v < vMax && v > vMin) {
478 auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
479 auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
480 for (uint32_t j = 0; j < len; ++j, ++dst) {
481 auto tmp = op(_fixedPixel(fill, t2), *dst, 255);
482 auto tmp2 = op2(tmp, *dst, 255);
483 *dst = INTERPOLATE(tmp2, *dst, a);
484 t2 += inc2;
485 }
486 //we have to fallback to float math
487 } else {
488 uint32_t counter = 0;
489 while (counter++ < len) {
490 auto tmp = op(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, 255);
491 auto tmp2 = op2(tmp, *dst, 255);
492 *dst = INTERPOLATE(tmp2, *dst, a);
493 ++dst;
494 t += inc;
495 }
496 }
497 }
498}
499
500
501bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable)
502{
503 if (!fill) return false;
504
505 fill->spread = fdata->spread();
506
507 if (ctable) {
508 if (!_updateColorTable(fill, fdata, surface, opacity)) return false;
509 }
510
511 if (fdata->identifier() == TVG_CLASS_ID_LINEAR) {
512 return _prepareLinear(fill, static_cast<const LinearGradient*>(fdata), transform);
513 } else if (fdata->identifier() == TVG_CLASS_ID_RADIAL) {
514 return _prepareRadial(fill, static_cast<const RadialGradient*>(fdata), transform);
515 }
516
517 //LOG: What type of gradient?!
518
519 return false;
520}
521
522
523void fillReset(SwFill* fill)
524{
525 if (fill->ctable) {
526 free(fill->ctable);
527 fill->ctable = nullptr;
528 }
529 fill->translucent = false;
530}
531
532
533void fillFree(SwFill* fill)
534{
535 if (!fill) return;
536
537 if (fill->ctable) free(fill->ctable);
538
539 free(fill);
540}
541