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 "tvgShapeImpl.h" |
25 | |
26 | /************************************************************************/ |
27 | /* Internal Class Implementation */ |
28 | /************************************************************************/ |
29 | constexpr auto PATH_KAPPA = 0.552284f; |
30 | |
31 | /************************************************************************/ |
32 | /* External Class Implementation */ |
33 | /************************************************************************/ |
34 | |
35 | Shape :: Shape() : pImpl(new Impl(this)) |
36 | { |
37 | Paint::pImpl->id = TVG_CLASS_ID_SHAPE; |
38 | Paint::pImpl->method(new PaintMethod<Shape::Impl>(pImpl)); |
39 | } |
40 | |
41 | |
42 | Shape :: ~Shape() |
43 | { |
44 | delete(pImpl); |
45 | } |
46 | |
47 | |
48 | unique_ptr<Shape> Shape::gen() noexcept |
49 | { |
50 | return unique_ptr<Shape>(new Shape); |
51 | } |
52 | |
53 | |
54 | uint32_t Shape::identifier() noexcept |
55 | { |
56 | return TVG_CLASS_ID_SHAPE; |
57 | } |
58 | |
59 | |
60 | Result Shape::reset() noexcept |
61 | { |
62 | pImpl->rs.path.cmds.clear(); |
63 | pImpl->rs.path.pts.clear(); |
64 | |
65 | pImpl->flag = RenderUpdateFlag::Path; |
66 | |
67 | return Result::Success; |
68 | } |
69 | |
70 | |
71 | uint32_t Shape::pathCommands(const PathCommand** cmds) const noexcept |
72 | { |
73 | if (!cmds) return 0; |
74 | |
75 | *cmds = pImpl->rs.path.cmds.data; |
76 | return pImpl->rs.path.cmds.count; |
77 | } |
78 | |
79 | |
80 | uint32_t Shape::pathCoords(const Point** pts) const noexcept |
81 | { |
82 | if (!pts) return 0; |
83 | |
84 | *pts = pImpl->rs.path.pts.data; |
85 | return pImpl->rs.path.pts.count; |
86 | } |
87 | |
88 | |
89 | Result Shape::appendPath(const PathCommand *cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) noexcept |
90 | { |
91 | if (cmdCnt == 0 || ptsCnt == 0 || !cmds || !pts) return Result::InvalidArguments; |
92 | |
93 | pImpl->grow(cmdCnt, ptsCnt); |
94 | pImpl->append(cmds, cmdCnt, pts, ptsCnt); |
95 | |
96 | return Result::Success; |
97 | } |
98 | |
99 | |
100 | Result Shape::moveTo(float x, float y) noexcept |
101 | { |
102 | pImpl->moveTo(x, y); |
103 | |
104 | return Result::Success; |
105 | } |
106 | |
107 | |
108 | Result Shape::lineTo(float x, float y) noexcept |
109 | { |
110 | pImpl->lineTo(x, y); |
111 | |
112 | return Result::Success; |
113 | } |
114 | |
115 | |
116 | Result Shape::cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) noexcept |
117 | { |
118 | pImpl->cubicTo(cx1, cy1, cx2, cy2, x, y); |
119 | |
120 | return Result::Success; |
121 | } |
122 | |
123 | |
124 | Result Shape::close() noexcept |
125 | { |
126 | pImpl->close(); |
127 | |
128 | return Result::Success; |
129 | } |
130 | |
131 | |
132 | Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept |
133 | { |
134 | auto rxKappa = rx * PATH_KAPPA; |
135 | auto ryKappa = ry * PATH_KAPPA; |
136 | |
137 | pImpl->grow(6, 13); |
138 | pImpl->moveTo(cx, cy - ry); |
139 | pImpl->cubicTo(cx + rxKappa, cy - ry, cx + rx, cy - ryKappa, cx + rx, cy); |
140 | pImpl->cubicTo(cx + rx, cy + ryKappa, cx + rxKappa, cy + ry, cx, cy + ry); |
141 | pImpl->cubicTo(cx - rxKappa, cy + ry, cx - rx, cy + ryKappa, cx - rx, cy); |
142 | pImpl->cubicTo(cx - rx, cy - ryKappa, cx - rxKappa, cy - ry, cx, cy - ry); |
143 | pImpl->close(); |
144 | |
145 | return Result::Success; |
146 | } |
147 | |
148 | Result Shape::appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept |
149 | { |
150 | //just circle |
151 | if (sweep >= 360.0f || sweep <= -360.0f) return appendCircle(cx, cy, radius, radius); |
152 | |
153 | startAngle = (startAngle * M_PI) / 180.0f; |
154 | sweep = sweep * M_PI / 180.0f; |
155 | |
156 | auto nCurves = ceil(fabsf(sweep / float(M_PI_2))); |
157 | auto sweepSign = (sweep < 0 ? -1 : 1); |
158 | auto fract = fmodf(sweep, float(M_PI_2)); |
159 | fract = (mathZero(fract)) ? float(M_PI_2) * sweepSign : fract; |
160 | |
161 | //Start from here |
162 | Point start = {radius * cosf(startAngle), radius * sinf(startAngle)}; |
163 | |
164 | if (pie) { |
165 | pImpl->moveTo(cx, cy); |
166 | pImpl->lineTo(start.x + cx, start.y + cy); |
167 | } else { |
168 | pImpl->moveTo(start.x + cx, start.y + cy); |
169 | } |
170 | |
171 | for (int i = 0; i < nCurves; ++i) { |
172 | auto endAngle = startAngle + ((i != nCurves - 1) ? float(M_PI_2) * sweepSign : fract); |
173 | Point end = {radius * cosf(endAngle), radius * sinf(endAngle)}; |
174 | |
175 | //variables needed to calculate bezier control points |
176 | |
177 | //get bezier control points using article: |
178 | //(http://itc.ktu.lt/index.php/ITC/article/view/11812/6479) |
179 | auto ax = start.x; |
180 | auto ay = start.y; |
181 | auto bx = end.x; |
182 | auto by = end.y; |
183 | auto q1 = ax * ax + ay * ay; |
184 | auto q2 = ax * bx + ay * by + q1; |
185 | auto k2 = (4.0f/3.0f) * ((sqrtf(2 * q1 * q2) - q2) / (ax * by - ay * bx)); |
186 | |
187 | start = end; //Next start point is the current end point |
188 | |
189 | end.x += cx; |
190 | end.y += cy; |
191 | |
192 | Point ctrl1 = {ax - k2 * ay + cx, ay + k2 * ax + cy}; |
193 | Point ctrl2 = {bx + k2 * by + cx, by - k2 * bx + cy}; |
194 | |
195 | pImpl->cubicTo(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, end.x, end.y); |
196 | |
197 | startAngle = endAngle; |
198 | } |
199 | |
200 | if (pie) pImpl->close(); |
201 | |
202 | return Result::Success; |
203 | } |
204 | |
205 | |
206 | Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry) noexcept |
207 | { |
208 | auto halfW = w * 0.5f; |
209 | auto halfH = h * 0.5f; |
210 | |
211 | //clamping cornerRadius by minimum size |
212 | if (rx > halfW) rx = halfW; |
213 | if (ry > halfH) ry = halfH; |
214 | |
215 | //rectangle |
216 | if (rx == 0 && ry == 0) { |
217 | pImpl->grow(5, 4); |
218 | pImpl->moveTo(x, y); |
219 | pImpl->lineTo(x + w, y); |
220 | pImpl->lineTo(x + w, y + h); |
221 | pImpl->lineTo(x, y + h); |
222 | pImpl->close(); |
223 | //circle |
224 | } else if (mathEqual(rx, halfW) && mathEqual(ry, halfH)) { |
225 | return appendCircle(x + (w * 0.5f), y + (h * 0.5f), rx, ry); |
226 | } else { |
227 | auto hrx = rx * 0.5f; |
228 | auto hry = ry * 0.5f; |
229 | pImpl->grow(10, 17); |
230 | pImpl->moveTo(x + rx, y); |
231 | pImpl->lineTo(x + w - rx, y); |
232 | pImpl->cubicTo(x + w - rx + hrx, y, x + w, y + ry - hry, x + w, y + ry); |
233 | pImpl->lineTo(x + w, y + h - ry); |
234 | pImpl->cubicTo(x + w, y + h - ry + hry, x + w - rx + hrx, y + h, x + w - rx, y + h); |
235 | pImpl->lineTo(x + rx, y + h); |
236 | pImpl->cubicTo(x + rx - hrx, y + h, x, y + h - ry + hry, x, y + h - ry); |
237 | pImpl->lineTo(x, y + ry); |
238 | pImpl->cubicTo(x, y + ry - hry, x + rx - hrx, y, x + rx, y); |
239 | pImpl->close(); |
240 | } |
241 | |
242 | return Result::Success; |
243 | } |
244 | |
245 | |
246 | Result Shape::fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept |
247 | { |
248 | if (pImpl->rs.fill) { |
249 | delete(pImpl->rs.fill); |
250 | pImpl->rs.fill = nullptr; |
251 | pImpl->flag |= RenderUpdateFlag::Gradient; |
252 | } |
253 | |
254 | if (r == pImpl->rs.color[0] && g == pImpl->rs.color[1] && b == pImpl->rs.color[2] && a == pImpl->rs.color[3]) return Result::Success; |
255 | |
256 | pImpl->rs.color[0] = r; |
257 | pImpl->rs.color[1] = g; |
258 | pImpl->rs.color[2] = b; |
259 | pImpl->rs.color[3] = a; |
260 | pImpl->flag |= RenderUpdateFlag::Color; |
261 | |
262 | return Result::Success; |
263 | } |
264 | |
265 | |
266 | Result Shape::fill(unique_ptr<Fill> f) noexcept |
267 | { |
268 | auto p = f.release(); |
269 | if (!p) return Result::MemoryCorruption; |
270 | |
271 | if (pImpl->rs.fill && pImpl->rs.fill != p) delete(pImpl->rs.fill); |
272 | pImpl->rs.fill = p; |
273 | pImpl->flag |= RenderUpdateFlag::Gradient; |
274 | |
275 | return Result::Success; |
276 | } |
277 | |
278 | |
279 | Result Shape::fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept |
280 | { |
281 | pImpl->rs.fillColor(r, g, b, a); |
282 | |
283 | return Result::Success; |
284 | } |
285 | |
286 | |
287 | const Fill* Shape::fill() const noexcept |
288 | { |
289 | return pImpl->rs.fill; |
290 | } |
291 | |
292 | |
293 | Result Shape::order(bool strokeFirst) noexcept |
294 | { |
295 | if (!pImpl->strokeFirst(strokeFirst)) return Result::FailedAllocation; |
296 | |
297 | return Result::Success; |
298 | } |
299 | |
300 | |
301 | Result Shape::stroke(float width) noexcept |
302 | { |
303 | if (!pImpl->strokeWidth(width)) return Result::FailedAllocation; |
304 | |
305 | return Result::Success; |
306 | } |
307 | |
308 | |
309 | float Shape::strokeWidth() const noexcept |
310 | { |
311 | return pImpl->rs.strokeWidth(); |
312 | } |
313 | |
314 | |
315 | Result Shape::stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept |
316 | { |
317 | if (!pImpl->strokeColor(r, g, b, a)) return Result::FailedAllocation; |
318 | |
319 | return Result::Success; |
320 | } |
321 | |
322 | |
323 | Result Shape::strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept |
324 | { |
325 | if (!pImpl->rs.strokeColor(r, g, b, a)) return Result::InsufficientCondition; |
326 | |
327 | return Result::Success; |
328 | } |
329 | |
330 | |
331 | Result Shape::stroke(unique_ptr<Fill> f) noexcept |
332 | { |
333 | return pImpl->strokeFill(std::move(f)); |
334 | } |
335 | |
336 | |
337 | const Fill* Shape::strokeFill() const noexcept |
338 | { |
339 | return pImpl->rs.strokeFill(); |
340 | } |
341 | |
342 | |
343 | Result Shape::stroke(const float* dashPattern, uint32_t cnt) noexcept |
344 | { |
345 | if ((cnt == 1) || (!dashPattern && cnt > 0) || (dashPattern && cnt == 0)) { |
346 | return Result::InvalidArguments; |
347 | } |
348 | |
349 | for (uint32_t i = 0; i < cnt; i++) |
350 | if (dashPattern[i] < FLT_EPSILON) return Result::InvalidArguments; |
351 | |
352 | if (!pImpl->strokeDash(dashPattern, cnt)) return Result::FailedAllocation; |
353 | |
354 | return Result::Success; |
355 | } |
356 | |
357 | |
358 | uint32_t Shape::strokeDash(const float** dashPattern) const noexcept |
359 | { |
360 | return pImpl->rs.strokeDash(dashPattern); |
361 | } |
362 | |
363 | |
364 | Result Shape::stroke(StrokeCap cap) noexcept |
365 | { |
366 | if (!pImpl->strokeCap(cap)) return Result::FailedAllocation; |
367 | |
368 | return Result::Success; |
369 | } |
370 | |
371 | |
372 | Result Shape::stroke(StrokeJoin join) noexcept |
373 | { |
374 | if (!pImpl->strokeJoin(join)) return Result::FailedAllocation; |
375 | |
376 | return Result::Success; |
377 | } |
378 | |
379 | Result Shape::strokeMiterlimit(float miterlimit) noexcept |
380 | { |
381 | // https://www.w3.org/TR/SVG2/painting.html#LineJoin |
382 | // - A negative value for stroke-miterlimit must be treated as an illegal value. |
383 | if (miterlimit < 0.0f) return Result::NonSupport; |
384 | // TODO Find out a reasonable max value. |
385 | if (!pImpl->strokeMiterlimit(miterlimit)) return Result::FailedAllocation; |
386 | |
387 | return Result::Success; |
388 | } |
389 | |
390 | |
391 | StrokeCap Shape::strokeCap() const noexcept |
392 | { |
393 | return pImpl->rs.strokeCap(); |
394 | } |
395 | |
396 | |
397 | StrokeJoin Shape::strokeJoin() const noexcept |
398 | { |
399 | return pImpl->rs.strokeJoin(); |
400 | } |
401 | |
402 | float Shape::strokeMiterlimit() const noexcept |
403 | { |
404 | return pImpl->rs.strokeMiterlimit(); |
405 | } |
406 | |
407 | |
408 | Result Shape::fill(FillRule r) noexcept |
409 | { |
410 | pImpl->rs.rule = r; |
411 | |
412 | return Result::Success; |
413 | } |
414 | |
415 | |
416 | FillRule Shape::fillRule() const noexcept |
417 | { |
418 | return pImpl->rs.rule; |
419 | } |
420 | |