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 | #ifndef _TVG_SHAPE_IMPL_H_ |
24 | #define _TVG_SHAPE_IMPL_H_ |
25 | |
26 | #include <memory.h> |
27 | #include "tvgMath.h" |
28 | #include "tvgPaint.h" |
29 | |
30 | /************************************************************************/ |
31 | /* Internal Class Implementation */ |
32 | /************************************************************************/ |
33 | |
34 | struct Shape::Impl |
35 | { |
36 | RenderShape rs; //shape data |
37 | RenderData rd = nullptr; //engine data |
38 | Shape* shape; |
39 | uint8_t flag = RenderUpdateFlag::None; |
40 | uint8_t opacity; //for composition |
41 | bool needComp; //composite or not |
42 | |
43 | Impl(Shape* s) : shape(s) |
44 | { |
45 | } |
46 | |
47 | bool dispose(RenderMethod& renderer) |
48 | { |
49 | auto ret = renderer.dispose(rd); |
50 | rd = nullptr; |
51 | return ret; |
52 | } |
53 | |
54 | bool render(RenderMethod& renderer) |
55 | { |
56 | Compositor* cmp = nullptr; |
57 | bool ret; |
58 | |
59 | if (needComp) { |
60 | cmp = renderer.target(bounds(renderer), renderer.colorSpace()); |
61 | renderer.beginComposite(cmp, CompositeMethod::None, opacity); |
62 | } |
63 | ret = renderer.renderShape(rd); |
64 | if (cmp) renderer.endComposite(cmp); |
65 | return ret; |
66 | } |
67 | |
68 | bool needComposition(uint8_t opacity) |
69 | { |
70 | if (opacity == 0) return false; |
71 | |
72 | //Shape composition is only necessary when stroking & fill are valid. |
73 | if (!rs.stroke || rs.stroke->width < FLT_EPSILON || rs.stroke->color[3] == 0) return false; |
74 | if (!rs.fill && rs.color[3] == 0) return false; |
75 | |
76 | //translucent fill & stroke |
77 | if (opacity < 255) return true; |
78 | |
79 | //Composition test |
80 | const Paint* target; |
81 | auto method = shape->composite(&target); |
82 | if (!target || method == tvg::CompositeMethod::ClipPath) return false; |
83 | if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) return false; |
84 | |
85 | return true; |
86 | } |
87 | |
88 | RenderData update(RenderMethod& renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) |
89 | { |
90 | if ((needComp = needComposition(opacity))) { |
91 | /* Overriding opacity value. If this scene is half-translucent, |
92 | It must do intermeidate composition with that opacity value. */ |
93 | this->opacity = opacity; |
94 | opacity = 255; |
95 | } |
96 | |
97 | rd = renderer.prepare(rs, rd, transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag), clipper); |
98 | flag = RenderUpdateFlag::None; |
99 | return rd; |
100 | } |
101 | |
102 | RenderRegion bounds(RenderMethod& renderer) |
103 | { |
104 | return renderer.region(rd); |
105 | } |
106 | |
107 | bool bounds(float* x, float* y, float* w, float* h) |
108 | { |
109 | //Path bounding size |
110 | if (rs.path.pts.count > 0 ) { |
111 | auto pts = rs.path.pts.data; |
112 | Point min = { pts->x, pts->y }; |
113 | Point max = { pts->x, pts->y }; |
114 | |
115 | for (auto pts2 = pts + 1; pts2 < rs.path.pts.end(); ++pts2) { |
116 | if (pts2->x < min.x) min.x = pts2->x; |
117 | if (pts2->y < min.y) min.y = pts2->y; |
118 | if (pts2->x > max.x) max.x = pts2->x; |
119 | if (pts2->y > max.y) max.y = pts2->y; |
120 | } |
121 | |
122 | if (x) *x = min.x; |
123 | if (y) *y = min.y; |
124 | if (w) *w = max.x - min.x; |
125 | if (h) *h = max.y - min.y; |
126 | } |
127 | |
128 | //Stroke feathering |
129 | if (rs.stroke) { |
130 | if (x) *x -= rs.stroke->width * 0.5f; |
131 | if (y) *y -= rs.stroke->width * 0.5f; |
132 | if (w) *w += rs.stroke->width; |
133 | if (h) *h += rs.stroke->width; |
134 | } |
135 | return rs.path.pts.count > 0 ? true : false; |
136 | } |
137 | |
138 | void reserveCmd(uint32_t cmdCnt) |
139 | { |
140 | rs.path.cmds.reserve(cmdCnt); |
141 | } |
142 | |
143 | void reservePts(uint32_t ptsCnt) |
144 | { |
145 | rs.path.pts.reserve(ptsCnt); |
146 | } |
147 | |
148 | void grow(uint32_t cmdCnt, uint32_t ptsCnt) |
149 | { |
150 | rs.path.cmds.grow(cmdCnt); |
151 | rs.path.pts.grow(ptsCnt); |
152 | } |
153 | |
154 | void append(const PathCommand* cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) |
155 | { |
156 | memcpy(rs.path.cmds.end(), cmds, sizeof(PathCommand) * cmdCnt); |
157 | memcpy(rs.path.pts.end(), pts, sizeof(Point) * ptsCnt); |
158 | rs.path.cmds.count += cmdCnt; |
159 | rs.path.pts.count += ptsCnt; |
160 | |
161 | flag |= RenderUpdateFlag::Path; |
162 | } |
163 | |
164 | void moveTo(float x, float y) |
165 | { |
166 | rs.path.cmds.push(PathCommand::MoveTo); |
167 | rs.path.pts.push({x, y}); |
168 | |
169 | flag |= RenderUpdateFlag::Path; |
170 | } |
171 | |
172 | void lineTo(float x, float y) |
173 | { |
174 | rs.path.cmds.push(PathCommand::LineTo); |
175 | rs.path.pts.push({x, y}); |
176 | |
177 | flag |= RenderUpdateFlag::Path; |
178 | } |
179 | |
180 | void cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) |
181 | { |
182 | rs.path.cmds.push(PathCommand::CubicTo); |
183 | rs.path.pts.push({cx1, cy1}); |
184 | rs.path.pts.push({cx2, cy2}); |
185 | rs.path.pts.push({x, y}); |
186 | |
187 | flag |= RenderUpdateFlag::Path; |
188 | } |
189 | |
190 | void close() |
191 | { |
192 | //Don't close multiple times. |
193 | if (rs.path.cmds.count > 0 && rs.path.cmds.last() == PathCommand::Close) return; |
194 | |
195 | rs.path.cmds.push(PathCommand::Close); |
196 | |
197 | flag |= RenderUpdateFlag::Path; |
198 | } |
199 | |
200 | bool strokeWidth(float width) |
201 | { |
202 | //TODO: Size Exception? |
203 | |
204 | if (!rs.stroke) rs.stroke = new RenderStroke(); |
205 | rs.stroke->width = width; |
206 | flag |= RenderUpdateFlag::Stroke; |
207 | |
208 | return true; |
209 | } |
210 | |
211 | bool strokeCap(StrokeCap cap) |
212 | { |
213 | if (!rs.stroke) rs.stroke = new RenderStroke(); |
214 | rs.stroke->cap = cap; |
215 | flag |= RenderUpdateFlag::Stroke; |
216 | |
217 | return true; |
218 | } |
219 | |
220 | bool strokeJoin(StrokeJoin join) |
221 | { |
222 | if (!rs.stroke) rs.stroke = new RenderStroke(); |
223 | rs.stroke->join = join; |
224 | flag |= RenderUpdateFlag::Stroke; |
225 | |
226 | return true; |
227 | } |
228 | |
229 | bool strokeMiterlimit(float miterlimit) |
230 | { |
231 | if (!rs.stroke) rs.stroke = new RenderStroke(); |
232 | rs.stroke->miterlimit = miterlimit; |
233 | flag |= RenderUpdateFlag::Stroke; |
234 | |
235 | return true; |
236 | } |
237 | |
238 | bool strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) |
239 | { |
240 | if (!rs.stroke) rs.stroke = new RenderStroke(); |
241 | if (rs.stroke->fill) { |
242 | delete(rs.stroke->fill); |
243 | rs.stroke->fill = nullptr; |
244 | flag |= RenderUpdateFlag::GradientStroke; |
245 | } |
246 | |
247 | rs.stroke->color[0] = r; |
248 | rs.stroke->color[1] = g; |
249 | rs.stroke->color[2] = b; |
250 | rs.stroke->color[3] = a; |
251 | |
252 | flag |= RenderUpdateFlag::Stroke; |
253 | |
254 | return true; |
255 | } |
256 | |
257 | Result strokeFill(unique_ptr<Fill> f) |
258 | { |
259 | auto p = f.release(); |
260 | if (!p) return Result::MemoryCorruption; |
261 | |
262 | if (!rs.stroke) rs.stroke = new RenderStroke(); |
263 | if (rs.stroke->fill && rs.stroke->fill != p) delete(rs.stroke->fill); |
264 | rs.stroke->fill = p; |
265 | |
266 | flag |= RenderUpdateFlag::Stroke; |
267 | flag |= RenderUpdateFlag::GradientStroke; |
268 | |
269 | return Result::Success; |
270 | } |
271 | |
272 | bool strokeDash(const float* pattern, uint32_t cnt) |
273 | { |
274 | //Reset dash |
275 | if (!pattern && cnt == 0) { |
276 | free(rs.stroke->dashPattern); |
277 | rs.stroke->dashPattern = nullptr; |
278 | } else { |
279 | if (!rs.stroke) rs.stroke = new RenderStroke(); |
280 | if (rs.stroke->dashCnt != cnt) { |
281 | free(rs.stroke->dashPattern); |
282 | rs.stroke->dashPattern = nullptr; |
283 | } |
284 | if (!rs.stroke->dashPattern) { |
285 | rs.stroke->dashPattern = static_cast<float*>(malloc(sizeof(float) * cnt)); |
286 | if (!rs.stroke->dashPattern) return false; |
287 | } |
288 | for (uint32_t i = 0; i < cnt; ++i) { |
289 | rs.stroke->dashPattern[i] = pattern[i]; |
290 | } |
291 | } |
292 | rs.stroke->dashCnt = cnt; |
293 | flag |= RenderUpdateFlag::Stroke; |
294 | |
295 | return true; |
296 | } |
297 | |
298 | bool strokeFirst() |
299 | { |
300 | if (!rs.stroke) return true; |
301 | return rs.stroke->strokeFirst; |
302 | } |
303 | |
304 | bool strokeFirst(bool strokeFirst) |
305 | { |
306 | if (!rs.stroke) rs.stroke = new RenderStroke(); |
307 | rs.stroke->strokeFirst = strokeFirst; |
308 | flag |= RenderUpdateFlag::Stroke; |
309 | |
310 | return true; |
311 | } |
312 | |
313 | void update(RenderUpdateFlag flag) |
314 | { |
315 | this->flag |= flag; |
316 | } |
317 | |
318 | Paint* duplicate() |
319 | { |
320 | auto ret = Shape::gen(); |
321 | |
322 | auto dup = ret.get()->pImpl; |
323 | dup->rs.rule = rs.rule; |
324 | |
325 | //Color |
326 | memcpy(dup->rs.color, rs.color, sizeof(rs.color)); |
327 | dup->flag = RenderUpdateFlag::Color; |
328 | |
329 | //Path |
330 | if (rs.path.cmds.count > 0 && rs.path.pts.count > 0) { |
331 | dup->rs.path.cmds = rs.path.cmds; |
332 | dup->rs.path.pts = rs.path.pts; |
333 | dup->flag |= RenderUpdateFlag::Path; |
334 | } |
335 | |
336 | //Stroke |
337 | if (rs.stroke) { |
338 | dup->rs.stroke = new RenderStroke(); |
339 | dup->rs.stroke->width = rs.stroke->width; |
340 | dup->rs.stroke->dashCnt = rs.stroke->dashCnt; |
341 | dup->rs.stroke->cap = rs.stroke->cap; |
342 | dup->rs.stroke->join = rs.stroke->join; |
343 | dup->rs.stroke->strokeFirst = rs.stroke->strokeFirst; |
344 | memcpy(dup->rs.stroke->color, rs.stroke->color, sizeof(rs.stroke->color)); |
345 | |
346 | if (rs.stroke->dashCnt > 0) { |
347 | dup->rs.stroke->dashPattern = static_cast<float*>(malloc(sizeof(float) * rs.stroke->dashCnt)); |
348 | memcpy(dup->rs.stroke->dashPattern, rs.stroke->dashPattern, sizeof(float) * rs.stroke->dashCnt); |
349 | } |
350 | |
351 | dup->flag |= RenderUpdateFlag::Stroke; |
352 | |
353 | if (rs.stroke->fill) { |
354 | dup->rs.stroke->fill = rs.stroke->fill->duplicate(); |
355 | dup->flag |= RenderUpdateFlag::GradientStroke; |
356 | } |
357 | } |
358 | |
359 | //Fill |
360 | if (rs.fill) { |
361 | dup->rs.fill = rs.fill->duplicate(); |
362 | dup->flag |= RenderUpdateFlag::Gradient; |
363 | } |
364 | |
365 | return ret.release(); |
366 | } |
367 | |
368 | Iterator* iterator() |
369 | { |
370 | return nullptr; |
371 | } |
372 | }; |
373 | |
374 | #endif //_TVG_SHAPE_IMPL_H_ |
375 | |