1 /*
2  * Copyright (c) 2020 - 2024 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 "../../lv_conf_internal.h"
24 #if LV_USE_THORVG_INTERNAL
25 
26 #include "tvgMath.h"
27 #include "tvgPaint.h"
28 #include "tvgShape.h"
29 #include "tvgPicture.h"
30 #include "tvgScene.h"
31 #include "tvgText.h"
32 
33 /************************************************************************/
34 /* Internal Class Implementation                                        */
35 /************************************************************************/
36 
37 #define PAINT_METHOD(ret, METHOD) \
38     switch (paint->type()) { \
39         case Type::Shape: ret = P((Shape*)paint)->METHOD; break; \
40         case Type::Scene: ret = P((Scene*)paint)->METHOD; break; \
41         case Type::Picture: ret = P((Picture*)paint)->METHOD; break; \
42         case Type::Text: ret = P((Text*)paint)->METHOD; break; \
43         default: ret = {}; \
44     }
45 
46 
_clipRect(RenderMethod * renderer,const Point * pts,const Matrix & pm,const Matrix & rm,RenderRegion & before)47 static Result _clipRect(RenderMethod* renderer, const Point* pts, const Matrix& pm, const Matrix& rm, RenderRegion& before)
48 {
49     //sorting
50     Point tmp[4];
51     Point min = {FLT_MAX, FLT_MAX};
52     Point max = {0.0f, 0.0f};
53 
54     for (int i = 0; i < 4; ++i) {
55         tmp[i] = pts[i];
56         tmp[i] *= rm;
57         tmp[i] *= pm;
58         if (tmp[i].x < min.x) min.x = tmp[i].x;
59         if (tmp[i].x > max.x) max.x = tmp[i].x;
60         if (tmp[i].y < min.y) min.y = tmp[i].y;
61         if (tmp[i].y > max.y) max.y = tmp[i].y;
62     }
63 
64     float region[4] = {float(before.x), float(before.x + before.w), float(before.y), float(before.y + before.h)};
65 
66     //figure out if the clipper is a superset of the current viewport(before) region
67     if (min.x <= region[0] && max.x >= region[1] && min.y <= region[2] && max.y >= region[3]) {
68         //viewport region is same, nothing to do.
69         return Result::Success;
70     //figure out if the clipper is totally outside of the viewport
71     } else if (max.x <= region[0] || min.x >= region[1] || max.y <= region[2] || min.y >= region[3]) {
72         renderer->viewport({0, 0, 0, 0});
73         return Result::Success;
74     }
75     return Result::InsufficientCondition;
76 }
77 
78 
_compFastTrack(RenderMethod * renderer,Paint * cmpTarget,const Matrix & pm,RenderRegion & before)79 static Result _compFastTrack(RenderMethod* renderer, Paint* cmpTarget, const Matrix& pm, RenderRegion& before)
80 {
81     /* Access Shape class by Paint is bad... but it's ok still it's an internal usage. */
82     auto shape = static_cast<Shape*>(cmpTarget);
83 
84     //Rectangle Candidates?
85     const Point* pts;
86     auto ptsCnt = shape->pathCoords(&pts);
87 
88     //nothing to clip
89     if (ptsCnt == 0) return Result::InvalidArguments;
90     if (ptsCnt != 4) return Result::InsufficientCondition;
91 
92     auto& rm = P(cmpTarget)->transform();
93 
94     //No rotation and no skewing, still can try out clipping the rect region.
95     auto tryClip = false;
96 
97     if ((!rightAngle(pm) || skewed(pm))) tryClip = true;
98     if ((!rightAngle(rm) || skewed(rm))) tryClip = true;
99 
100     if (tryClip) return _clipRect(renderer, pts, pm, rm, before);
101 
102     //Perpendicular Rectangle?
103     auto pt1 = pts + 0;
104     auto pt2 = pts + 1;
105     auto pt3 = pts + 2;
106     auto pt4 = pts + 3;
107 
108     if ((tvg::equal(pt1->x, pt2->x) && tvg::equal(pt2->y, pt3->y) && tvg::equal(pt3->x, pt4->x) && tvg::equal(pt1->y, pt4->y)) ||
109         (tvg::equal(pt2->x, pt3->x) && tvg::equal(pt1->y, pt2->y) && tvg::equal(pt1->x, pt4->x) && tvg::equal(pt3->y, pt4->y))) {
110 
111         RenderRegion after;
112 
113         auto v1 = *pt1;
114         auto v2 = *pt3;
115         v1 *= rm;
116         v2 *= rm;
117         v1 *= pm;
118         v2 *= pm;
119 
120         //sorting
121         if (v1.x > v2.x) std::swap(v1.x, v2.x);
122         if (v1.y > v2.y) std::swap(v1.y, v2.y);
123 
124         after.x = static_cast<int32_t>(v1.x);
125         after.y = static_cast<int32_t>(v1.y);
126         after.w = static_cast<int32_t>(ceil(v2.x - after.x));
127         after.h = static_cast<int32_t>(ceil(v2.y - after.y));
128 
129         if (after.w < 0) after.w = 0;
130         if (after.h < 0) after.h = 0;
131 
132         after.intersect(before);
133         renderer->viewport(after);
134 
135         return Result::Success;
136     }
137     return Result::InsufficientCondition;
138 }
139 
140 
bounds(RenderMethod * renderer) const141 RenderRegion Paint::Impl::bounds(RenderMethod* renderer) const
142 {
143     RenderRegion ret;
144     PAINT_METHOD(ret, bounds(renderer));
145     return ret;
146 }
147 
148 
iterator()149 Iterator* Paint::Impl::iterator()
150 {
151     Iterator* ret;
152     PAINT_METHOD(ret, iterator());
153     return ret;
154 }
155 
156 
duplicate(Paint * ret)157 Paint* Paint::Impl::duplicate(Paint* ret)
158 {
159     if (ret) ret->composite(nullptr, CompositeMethod::None);
160 
161     PAINT_METHOD(ret, duplicate(ret));
162 
163     //duplicate Transform
164     ret->pImpl->tr = tr;
165     ret->pImpl->renderFlag |= RenderUpdateFlag::Transform;
166 
167     ret->pImpl->opacity = opacity;
168 
169     if (compData) ret->pImpl->composite(ret, compData->target->duplicate(), compData->method);
170     if (clipper) ret->pImpl->clip(clipper->duplicate());
171 
172     return ret;
173 }
174 
175 
rotate(float degree)176 bool Paint::Impl::rotate(float degree)
177 {
178     if (tr.overriding) return false;
179     if (tvg::equal(degree, tr.degree)) return true;
180     tr.degree = degree;
181     renderFlag |= RenderUpdateFlag::Transform;
182 
183     return true;
184 }
185 
186 
scale(float factor)187 bool Paint::Impl::scale(float factor)
188 {
189     if (tr.overriding) return false;
190     if (tvg::equal(factor, tr.scale)) return true;
191     tr.scale = factor;
192     renderFlag |= RenderUpdateFlag::Transform;
193 
194     return true;
195 }
196 
197 
translate(float x,float y)198 bool Paint::Impl::translate(float x, float y)
199 {
200     if (tr.overriding) return false;
201     if (tvg::equal(x, tr.m.e13) && tvg::equal(y, tr.m.e23)) return true;
202     tr.m.e13 = x;
203     tr.m.e23 = y;
204     renderFlag |= RenderUpdateFlag::Transform;
205 
206     return true;
207 }
208 
209 
render(RenderMethod * renderer)210 bool Paint::Impl::render(RenderMethod* renderer)
211 {
212     if (opacity == 0) return true;
213 
214     RenderCompositor* cmp = nullptr;
215 
216     if (compData && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) {
217         RenderRegion region;
218         PAINT_METHOD(region, bounds(renderer));
219 
220         if (MASK_REGION_MERGING(compData->method)) region.add(P(compData->target)->bounds(renderer));
221         if (region.w == 0 || region.h == 0) return true;
222         cmp = renderer->target(region, COMPOSITE_TO_COLORSPACE(renderer, compData->method));
223         if (renderer->beginComposite(cmp, CompositeMethod::None, 255)) {
224             compData->target->pImpl->render(renderer);
225         }
226     }
227 
228     if (cmp) renderer->beginComposite(cmp, compData->method, compData->target->pImpl->opacity);
229 
230     bool ret;
231     PAINT_METHOD(ret, render(renderer));
232 
233     if (cmp) renderer->endComposite(cmp);
234 
235     return ret;
236 }
237 
238 
update(RenderMethod * renderer,const Matrix & pm,Array<RenderData> & clips,uint8_t opacity,RenderUpdateFlag pFlag,bool clipper)239 RenderData Paint::Impl::update(RenderMethod* renderer, const Matrix& pm, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
240 {
241     if (this->renderer != renderer) {
242         if (this->renderer) TVGERR("RENDERER", "paint's renderer has been changed!");
243         renderer->ref();
244         this->renderer = renderer;
245     }
246 
247     if (renderFlag & RenderUpdateFlag::Transform) tr.update();
248 
249     /* 1. Composition Pre Processing */
250     RenderData trd = nullptr;                 //composite target render data
251     RenderRegion viewport;
252     Result compFastTrack = Result::InsufficientCondition;
253 
254     if (compData) {
255         auto target = compData->target;
256         auto method = compData->method;
257         P(target)->ctxFlag &= ~ContextFlag::FastTrack;   //reset
258 
259         /* If the transformation has no rotational factors and the Alpha(InvAlpha)Masking involves a simple rectangle,
260            we can optimize by using the viewport instead of the regular AlphaMasking sequence for improved performance. */
261         if (target->type() == Type::Shape) {
262             auto shape = static_cast<Shape*>(target);
263             uint8_t a;
264             shape->fillColor(nullptr, nullptr, nullptr, &a);
265             //no gradient fill & no compositions of the composition target.
266             if (!shape->fill() && !(PP(shape)->compData)) {
267                 if ((method == CompositeMethod::AlphaMask && a == 255 && PP(shape)->opacity == 255) || (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0))) {
268                     viewport = renderer->viewport();
269                     if ((compFastTrack = _compFastTrack(renderer, target, pm, viewport)) == Result::Success) {
270                         P(target)->ctxFlag |= ContextFlag::FastTrack;
271                     }
272                 }
273             }
274         }
275         if (compFastTrack == Result::InsufficientCondition) {
276             trd = P(target)->update(renderer, pm, clips, 255, pFlag, false);
277         }
278     }
279 
280     /* 2. Clipping */
281     if (this->clipper) {
282         P(this->clipper)->ctxFlag &= ~ContextFlag::FastTrack;   //reset
283         viewport = renderer->viewport();
284         /* TODO: Intersect the clipper's clipper, if both are FastTrack.
285            Update the subsequent clipper first and check its ctxFlag. */
286         if (!P(this->clipper)->clipper && (compFastTrack = _compFastTrack(renderer, this->clipper, pm, viewport)) == Result::Success) {
287             P(this->clipper)->ctxFlag |= ContextFlag::FastTrack;
288         }
289         if (compFastTrack == Result::InsufficientCondition) {
290             trd = P(this->clipper)->update(renderer, pm, clips, 255, pFlag, true);
291             clips.push(trd);
292         }
293     }
294 
295     /* 3. Main Update */
296     auto newFlag = static_cast<RenderUpdateFlag>(pFlag | renderFlag);
297     renderFlag = RenderUpdateFlag::None;
298     opacity = MULTIPLY(opacity, this->opacity);
299 
300     RenderData rd = nullptr;
301 
302     tr.cm = pm * tr.m;
303     PAINT_METHOD(rd, update(renderer, tr.cm, clips, opacity, newFlag, clipper));
304 
305     /* 4. Composition Post Processing */
306     if (compFastTrack == Result::Success) renderer->viewport(viewport);
307     else if (this->clipper) clips.pop();
308 
309     return rd;
310 }
311 
312 
bounds(float * x,float * y,float * w,float * h,bool transformed,bool stroking,bool origin)313 bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking, bool origin)
314 {
315     bool ret;
316     const auto& m = this->transform(origin);
317 
318     //Case: No transformed, quick return!
319     if (!transformed || identity(&m)) {
320         PAINT_METHOD(ret, bounds(x, y, w, h, stroking));
321         return ret;
322     }
323 
324     //Case: Transformed
325     auto tx = 0.0f;
326     auto ty = 0.0f;
327     auto tw = 0.0f;
328     auto th = 0.0f;
329 
330     PAINT_METHOD(ret, bounds(&tx, &ty, &tw, &th, stroking));
331 
332     //Get vertices
333     Point pt[4] = {{tx, ty}, {tx + tw, ty}, {tx + tw, ty + th}, {tx, ty + th}};
334 
335     //New bounding box
336     auto x1 = FLT_MAX;
337     auto y1 = FLT_MAX;
338     auto x2 = -FLT_MAX;
339     auto y2 = -FLT_MAX;
340 
341     //Compute the AABB after transformation
342     for (int i = 0; i < 4; i++) {
343         pt[i] *= m;
344 
345         if (pt[i].x < x1) x1 = pt[i].x;
346         if (pt[i].x > x2) x2 = pt[i].x;
347         if (pt[i].y < y1) y1 = pt[i].y;
348         if (pt[i].y > y2) y2 = pt[i].y;
349     }
350 
351     if (x) *x = x1;
352     if (y) *y = y1;
353     if (w) *w = x2 - x1;
354     if (h) *h = y2 - y1;
355 
356     return ret;
357 }
358 
359 
reset()360 void Paint::Impl::reset()
361 {
362     if (clipper) {
363         delete(clipper);
364         clipper = nullptr;
365     }
366 
367     if (compData) {
368         if (P(compData->target)->unref() == 0) delete(compData->target);
369         free(compData);
370         compData = nullptr;
371     }
372 
373     tvg::identity(&tr.m);
374     tr.degree = 0.0f;
375     tr.scale = 1.0f;
376     tr.overriding = false;
377 
378     blendMethod = BlendMethod::Normal;
379     renderFlag = RenderUpdateFlag::None;
380     ctxFlag = ContextFlag::Invalid;
381     opacity = 255;
382     paint->id = 0;
383 }
384 
385 
386 /************************************************************************/
387 /* External Class Implementation                                        */
388 /************************************************************************/
389 
Paint()390 Paint :: Paint() : pImpl(new Impl(this))
391 {
392 }
393 
394 
~Paint()395 Paint :: ~Paint()
396 {
397     delete(pImpl);
398 }
399 
400 
rotate(float degree)401 Result Paint::rotate(float degree) noexcept
402 {
403     if (pImpl->rotate(degree)) return Result::Success;
404     return Result::InsufficientCondition;
405 }
406 
407 
scale(float factor)408 Result Paint::scale(float factor) noexcept
409 {
410     if (pImpl->scale(factor)) return Result::Success;
411     return Result::InsufficientCondition;
412 }
413 
414 
translate(float x,float y)415 Result Paint::translate(float x, float y) noexcept
416 {
417     if (pImpl->translate(x, y)) return Result::Success;
418     return Result::InsufficientCondition;
419 }
420 
421 
transform(const Matrix & m)422 Result Paint::transform(const Matrix& m) noexcept
423 {
424     if (pImpl->transform(m)) return Result::Success;
425     return Result::InsufficientCondition;
426 }
427 
428 
transform()429 Matrix Paint::transform() noexcept
430 {
431     return pImpl->transform();
432 }
433 
434 
bounds(float * x,float * y,float * w,float * h) const435 TVG_DEPRECATED Result Paint::bounds(float* x, float* y, float* w, float* h) const noexcept
436 {
437     return this->bounds(x, y, w, h, false);
438 }
439 
440 
bounds(float * x,float * y,float * w,float * h,bool transformed) const441 Result Paint::bounds(float* x, float* y, float* w, float* h, bool transformed) const noexcept
442 {
443     if (pImpl->bounds(x, y, w, h, transformed, true, transformed)) return Result::Success;
444     return Result::InsufficientCondition;
445 }
446 
447 
duplicate() const448 Paint* Paint::duplicate() const noexcept
449 {
450     return pImpl->duplicate();
451 }
452 
453 
clip(std::unique_ptr<Paint> clipper)454 Result Paint::clip(std::unique_ptr<Paint> clipper) noexcept
455 {
456     auto p = clipper.release();
457 
458     if (p && p->type() != Type::Shape) {
459         TVGERR("RENDERER", "Clipping only supports the Shape!");
460         return Result::NonSupport;
461     }
462     pImpl->clip(p);
463     return Result::Success;
464 }
465 
466 
composite(std::unique_ptr<Paint> target,CompositeMethod method)467 Result Paint::composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept
468 {
469     //TODO: remove. Keep this for the backward compatibility
470     if (target && method == CompositeMethod::ClipPath) return clip(std::move(target));
471 
472     auto p = target.release();
473     if (pImpl->composite(this, p, method)) return Result::Success;
474 
475     delete(p);
476     return Result::InvalidArguments;
477 }
478 
479 
composite(const Paint ** target) const480 CompositeMethod Paint::composite(const Paint** target) const noexcept
481 {
482     if (pImpl->compData) {
483         if (target) *target = pImpl->compData->target;
484         return pImpl->compData->method;
485     } else {
486         //TODO: remove. Keep this for the backward compatibility
487         if (pImpl->clipper) {
488             if (target) *target = pImpl->clipper;
489             return CompositeMethod::ClipPath;
490         }
491         if (target) *target = nullptr;
492         return CompositeMethod::None;
493     }
494 }
495 
496 
opacity(uint8_t o)497 Result Paint::opacity(uint8_t o) noexcept
498 {
499     if (pImpl->opacity == o) return Result::Success;
500 
501     pImpl->opacity = o;
502     pImpl->renderFlag |= RenderUpdateFlag::Color;
503 
504     return Result::Success;
505 }
506 
507 
opacity() const508 uint8_t Paint::opacity() const noexcept
509 {
510     return pImpl->opacity;
511 }
512 
513 
identifier() const514 TVG_DEPRECATED uint32_t Paint::identifier() const noexcept
515 {
516     return (uint32_t) type();
517 }
518 
519 
blend(BlendMethod method)520 Result Paint::blend(BlendMethod method) noexcept
521 {
522     //TODO: Remove later
523     if (method == BlendMethod::Hue || method == BlendMethod::Saturation || method == BlendMethod::Color || method == BlendMethod::Luminosity || method == BlendMethod::HardMix) return Result::NonSupport;
524 
525     if (pImpl->blendMethod != method) {
526         pImpl->blendMethod = method;
527         pImpl->renderFlag |= RenderUpdateFlag::Blend;
528     }
529 
530     return Result::Success;
531 }
532 
533 #endif /* LV_USE_THORVG_INTERNAL */
534 
535