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 | |
31 | static 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 | |
96 | Paint* 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 | |
115 | bool 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 | |
130 | bool 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 | |
145 | bool 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 | |
161 | bool 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 | |
188 | RenderData 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 | |
266 | bool 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 | |
313 | Paint :: Paint() : pImpl(new Impl()) |
314 | { |
315 | } |
316 | |
317 | |
318 | Paint :: ~Paint() |
319 | { |
320 | delete(pImpl); |
321 | } |
322 | |
323 | |
324 | Result Paint::rotate(float degree) noexcept |
325 | { |
326 | if (pImpl->rotate(degree)) return Result::Success; |
327 | return Result::FailedAllocation; |
328 | } |
329 | |
330 | |
331 | Result Paint::scale(float factor) noexcept |
332 | { |
333 | if (pImpl->scale(factor)) return Result::Success; |
334 | return Result::FailedAllocation; |
335 | } |
336 | |
337 | |
338 | Result Paint::translate(float x, float y) noexcept |
339 | { |
340 | if (pImpl->translate(x, y)) return Result::Success; |
341 | return Result::FailedAllocation; |
342 | } |
343 | |
344 | |
345 | Result Paint::transform(const Matrix& m) noexcept |
346 | { |
347 | if (pImpl->transform(m)) return Result::Success; |
348 | return Result::FailedAllocation; |
349 | } |
350 | |
351 | |
352 | Matrix 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 | |
360 | TVG_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 | |
366 | Result 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 | |
373 | Paint* Paint::duplicate() const noexcept |
374 | { |
375 | return pImpl->duplicate(); |
376 | } |
377 | |
378 | |
379 | Result 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 | |
388 | CompositeMethod 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 | |
400 | Result 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 | |
411 | uint8_t Paint::opacity() const noexcept |
412 | { |
413 | return pImpl->opacity; |
414 | } |
415 | |
416 | |
417 | uint32_t Paint::identifier() const noexcept |
418 | { |
419 | return pImpl->id; |
420 | } |
421 | |
422 | |
423 | Result Paint::blend(BlendMethod method) const noexcept |
424 | { |
425 | pImpl->blendMethod = method; |
426 | |
427 | return Result::Success; |
428 | } |
429 | |
430 | |
431 | BlendMethod Paint::blend() const noexcept |
432 | { |
433 | return pImpl->blendMethod; |
434 | } |
435 | |