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 "tvgPaint.h"
25
26/************************************************************************/
27/* Internal Class Implementation */
28/************************************************************************/
29
30
31static bool _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform, RenderTransform* rTransform, RenderRegion& viewport)
32{
33 /* Access Shape class by Paint is bad... but it's ok still it's an internal usage. */
34 auto shape = static_cast<Shape*>(cmpTarget);
35
36 //Rectangle Candidates?
37 const Point* pts;
38 if (shape->pathCoords(&pts) != 4) return false;
39
40 if (rTransform) rTransform->update();
41
42 //No rotation and no skewing
43 if (pTransform && (!mathRightAngle(&pTransform->m) || mathSkewed(&pTransform->m))) return false;
44 if (rTransform && (!mathRightAngle(&rTransform->m) || mathSkewed(&rTransform->m))) return false;
45
46 //Perpendicular Rectangle?
47 auto pt1 = pts + 0;
48 auto pt2 = pts + 1;
49 auto pt3 = pts + 2;
50 auto pt4 = pts + 3;
51
52 if ((mathEqual(pt1->x, pt2->x) && mathEqual(pt2->y, pt3->y) && mathEqual(pt3->x, pt4->x) && mathEqual(pt1->y, pt4->y)) ||
53 (mathEqual(pt2->x, pt3->x) && mathEqual(pt1->y, pt2->y) && mathEqual(pt1->x, pt4->x) && mathEqual(pt3->y, pt4->y))) {
54
55 auto v1 = *pt1;
56 auto v2 = *pt3;
57
58 if (rTransform) {
59 mathMultiply(&v1, &rTransform->m);
60 mathMultiply(&v2, &rTransform->m);
61 }
62
63 if (pTransform) {
64 mathMultiply(&v1, &pTransform->m);
65 mathMultiply(&v2, &pTransform->m);
66 }
67
68 //sorting
69 if (v1.x > v2.x) {
70 auto tmp = v2.x;
71 v2.x = v1.x;
72 v1.x = tmp;
73 }
74
75 if (v1.y > v2.y) {
76 auto tmp = v2.y;
77 v2.y = v1.y;
78 v1.y = tmp;
79 }
80
81 viewport.x = static_cast<int32_t>(v1.x);
82 viewport.y = static_cast<int32_t>(v1.y);
83 viewport.w = static_cast<int32_t>(ceil(v2.x - viewport.x));
84 viewport.h = static_cast<int32_t>(ceil(v2.y - viewport.y));
85
86 if (viewport.w < 0) viewport.w = 0;
87 if (viewport.h < 0) viewport.h = 0;
88
89 return true;
90 }
91
92 return false;
93}
94
95
96Paint* Paint::Impl::duplicate()
97{
98 auto ret = smethod->duplicate();
99
100 //duplicate Transform
101 if (rTransform) {
102 ret->pImpl->rTransform = new RenderTransform();
103 *ret->pImpl->rTransform = *rTransform;
104 ret->pImpl->renderFlag |= RenderUpdateFlag::Transform;
105 }
106
107 ret->pImpl->opacity = opacity;
108
109 if (compData) ret->pImpl->composite(ret, compData->target->duplicate(), compData->method);
110
111 return ret;
112}
113
114
115bool Paint::Impl::rotate(float degree)
116{
117 if (rTransform) {
118 if (mathEqual(degree, rTransform->degree)) return true;
119 } else {
120 if (mathZero(degree)) return true;
121 rTransform = new RenderTransform();
122 }
123 rTransform->degree = degree;
124 if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform;
125
126 return true;
127}
128
129
130bool Paint::Impl::scale(float factor)
131{
132 if (rTransform) {
133 if (mathEqual(factor, rTransform->scale)) return true;
134 } else {
135 if (mathZero(factor)) return true;
136 rTransform = new RenderTransform();
137 }
138 rTransform->scale = factor;
139 if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform;
140
141 return true;
142}
143
144
145bool Paint::Impl::translate(float x, float y)
146{
147 if (rTransform) {
148 if (mathEqual(x, rTransform->x) && mathEqual(y, rTransform->y)) return true;
149 } else {
150 if (mathZero(x) && mathZero(y)) return true;
151 rTransform = new RenderTransform();
152 }
153 rTransform->x = x;
154 rTransform->y = y;
155 if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform;
156
157 return true;
158}
159
160
161bool Paint::Impl::render(RenderMethod& renderer)
162{
163 Compositor* cmp = nullptr;
164
165 /* Note: only ClipPath is processed in update() step.
166 Create a composition image. */
167 if (compData && compData->method != CompositeMethod::ClipPath && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) {
168 auto region = smethod->bounds(renderer);
169 if (MASK_OPERATION(compData->method)) region.add(compData->target->pImpl->smethod->bounds(renderer));
170 if (region.w == 0 || region.h == 0) return true;
171 cmp = renderer.target(region, COMPOSITE_TO_COLORSPACE(renderer, compData->method));
172 if (renderer.beginComposite(cmp, CompositeMethod::None, 255)) {
173 compData->target->pImpl->render(renderer);
174 }
175 }
176
177 if (cmp) renderer.beginComposite(cmp, compData->method, compData->target->pImpl->opacity);
178
179 renderer.blend(blendMethod);
180 auto ret = smethod->render(renderer);
181
182 if (cmp) renderer.endComposite(cmp);
183
184 return ret;
185}
186
187
188RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
189{
190 if (renderFlag & RenderUpdateFlag::Transform) {
191 if (!rTransform) return nullptr;
192 if (!rTransform->update()) {
193 delete(rTransform);
194 rTransform = nullptr;
195 }
196 }
197
198 /* 1. Composition Pre Processing */
199 RenderData trd = nullptr; //composite target render data
200 RenderRegion viewport;
201 bool compFastTrack = false;
202 bool childClipper = false;
203
204 if (compData) {
205 auto target = compData->target;
206 auto method = compData->method;
207 target->pImpl->ctxFlag &= ~ContextFlag::FastTrack; //reset
208
209 /* If transform has no rotation factors && ClipPath / AlphaMasking is a simple rectangle,
210 we can avoid regular ClipPath / AlphaMasking sequence but use viewport for performance */
211 auto tryFastTrack = false;
212 if (target->identifier() == TVG_CLASS_ID_SHAPE) {
213 if (method == CompositeMethod::ClipPath) tryFastTrack = true;
214 //OPTIMIZE HERE: Actually, this condition AlphaMask is useless. We can skip it?
215 else if (method == CompositeMethod::AlphaMask) {
216 auto shape = static_cast<Shape*>(target);
217 uint8_t a;
218 shape->fillColor(nullptr, nullptr, nullptr, &a);
219 if (a == 255 && shape->opacity() == 255 && !shape->fill()) tryFastTrack = true;
220 //OPTIMIZE HERE: Actually, this condition InvAlphaMask is useless. We can skip it?
221 } else if (method == CompositeMethod::InvAlphaMask) {
222 auto shape = static_cast<Shape*>(target);
223 uint8_t a;
224 shape->fillColor(nullptr, nullptr, nullptr, &a);
225 if ((a == 0 || shape->opacity() == 0) && !shape->fill()) tryFastTrack = true;
226 }
227 if (tryFastTrack) {
228 RenderRegion viewport2;
229 if ((compFastTrack = _compFastTrack(target, pTransform, target->pImpl->rTransform, viewport2))) {
230 viewport = renderer.viewport();
231 viewport2.intersect(viewport);
232 renderer.viewport(viewport2);
233 target->pImpl->ctxFlag |= ContextFlag::FastTrack;
234 }
235 }
236 }
237 if (!compFastTrack) {
238 childClipper = compData->method == CompositeMethod::ClipPath ? true : false;
239 trd = target->pImpl->update(renderer, pTransform, clips, 255, pFlag, childClipper);
240 if (childClipper) clips.push(trd);
241 }
242 }
243
244 /* 2. Main Update */
245 RenderData rd = nullptr;
246 auto newFlag = static_cast<RenderUpdateFlag>(pFlag | renderFlag);
247 renderFlag = RenderUpdateFlag::None;
248 opacity = MULTIPLY(opacity, this->opacity);
249
250 if (rTransform && pTransform) {
251 RenderTransform outTransform(pTransform, rTransform);
252 rd = smethod->update(renderer, &outTransform, clips, opacity, newFlag, clipper);
253 } else {
254 auto outTransform = pTransform ? pTransform : rTransform;
255 rd = smethod->update(renderer, outTransform, clips, opacity, newFlag, clipper);
256 }
257
258 /* 3. Composition Post Processing */
259 if (compFastTrack) renderer.viewport(viewport);
260 else if (childClipper) clips.pop();
261
262 return rd;
263}
264
265
266bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed)
267{
268 Matrix* m = nullptr;
269
270 //Case: No transformed, quick return!
271 if (!transformed || !(m = this->transform())) return smethod->bounds(x, y, w, h);
272
273 //Case: Transformed
274 auto tx = 0.0f;
275 auto ty = 0.0f;
276 auto tw = 0.0f;
277 auto th = 0.0f;
278
279 auto ret = smethod->bounds(&tx, &ty, &tw, &th);
280
281 //Get vertices
282 Point pt[4] = {{tx, ty}, {tx + tw, ty}, {tx + tw, ty + th}, {tx, ty + th}};
283
284 //New bounding box
285 auto x1 = FLT_MAX;
286 auto y1 = FLT_MAX;
287 auto x2 = -FLT_MAX;
288 auto y2 = -FLT_MAX;
289
290 //Compute the AABB after transformation
291 for (int i = 0; i < 4; i++) {
292 mathMultiply(&pt[i], m);
293
294 if (pt[i].x < x1) x1 = pt[i].x;
295 if (pt[i].x > x2) x2 = pt[i].x;
296 if (pt[i].y < y1) y1 = pt[i].y;
297 if (pt[i].y > y2) y2 = pt[i].y;
298 }
299
300 if (x) *x = x1;
301 if (y) *y = y1;
302 if (w) *w = x2 - x1;
303 if (h) *h = y2 - y1;
304
305 return ret;
306}
307
308
309/************************************************************************/
310/* External Class Implementation */
311/************************************************************************/
312
313Paint :: Paint() : pImpl(new Impl())
314{
315}
316
317
318Paint :: ~Paint()
319{
320 delete(pImpl);
321}
322
323
324Result Paint::rotate(float degree) noexcept
325{
326 if (pImpl->rotate(degree)) return Result::Success;
327 return Result::FailedAllocation;
328}
329
330
331Result Paint::scale(float factor) noexcept
332{
333 if (pImpl->scale(factor)) return Result::Success;
334 return Result::FailedAllocation;
335}
336
337
338Result Paint::translate(float x, float y) noexcept
339{
340 if (pImpl->translate(x, y)) return Result::Success;
341 return Result::FailedAllocation;
342}
343
344
345Result Paint::transform(const Matrix& m) noexcept
346{
347 if (pImpl->transform(m)) return Result::Success;
348 return Result::FailedAllocation;
349}
350
351
352Matrix Paint::transform() noexcept
353{
354 auto pTransform = pImpl->transform();
355 if (pTransform) return *pTransform;
356 return {1, 0, 0, 0, 1, 0, 0, 0, 1};
357}
358
359
360TVG_DEPRECATED Result Paint::bounds(float* x, float* y, float* w, float* h) const noexcept
361{
362 return this->bounds(x, y, w, h, false);
363}
364
365
366Result Paint::bounds(float* x, float* y, float* w, float* h, bool transform) const noexcept
367{
368 if (pImpl->bounds(x, y, w, h, transform)) return Result::Success;
369 return Result::InsufficientCondition;
370}
371
372
373Paint* Paint::duplicate() const noexcept
374{
375 return pImpl->duplicate();
376}
377
378
379Result Paint::composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept
380{
381 auto p = target.release();
382 if (pImpl->composite(this, p, method)) return Result::Success;
383 delete(p);
384 return Result::InvalidArguments;
385}
386
387
388CompositeMethod Paint::composite(const Paint** target) const noexcept
389{
390 if (pImpl->compData) {
391 if (target) *target = pImpl->compData->target;
392 return pImpl->compData->method;
393 } else {
394 if (target) *target = nullptr;
395 return CompositeMethod::None;
396 }
397}
398
399
400Result Paint::opacity(uint8_t o) noexcept
401{
402 if (pImpl->opacity == o) return Result::Success;
403
404 pImpl->opacity = o;
405 pImpl->renderFlag |= RenderUpdateFlag::Color;
406
407 return Result::Success;
408}
409
410
411uint8_t Paint::opacity() const noexcept
412{
413 return pImpl->opacity;
414}
415
416
417uint32_t Paint::identifier() const noexcept
418{
419 return pImpl->id;
420}
421
422
423Result Paint::blend(BlendMethod method) const noexcept
424{
425 pImpl->blendMethod = method;
426
427 return Result::Success;
428}
429
430
431BlendMethod Paint::blend() const noexcept
432{
433 return pImpl->blendMethod;
434}
435