1 /*
2  * Copyright (c) 2023 - 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 <cstring>
27 #include <algorithm>
28 
29 #include "tvgCommon.h"
30 #include "tvgMath.h"
31 #include "tvgLottieModel.h"
32 #include "tvgLottieBuilder.h"
33 #include "tvgLottieExpressions.h"
34 
35 
36 /************************************************************************/
37 /* Internal Class Implementation                                        */
38 /************************************************************************/
39 
40 static bool _buildComposition(LottieComposition* comp, LottieLayer* parent);
41 static bool _draw(LottieGroup* parent, LottieShape* shape, RenderContext* ctx);
42 
43 
_rotationXYZ(Matrix * m,float degreeX,float degreeY,float degreeZ)44 static void _rotationXYZ(Matrix* m, float degreeX, float degreeY, float degreeZ)
45 {
46     auto radianX = deg2rad(degreeX);
47     auto radianY = deg2rad(degreeY);
48     auto radianZ = deg2rad(degreeZ);
49 
50     auto cx = cosf(radianX), sx = sinf(radianX);
51     auto cy = cosf(radianY), sy = sinf(radianY);;
52     auto cz = cosf(radianZ), sz = sinf(radianZ);;
53     m->e11 = cy * cz;
54     m->e12 = -cy * sz;
55     m->e21 = sx * sy * cz + cx * sz;
56     m->e22 = -sx * sy * sz + cx * cz;
57 }
58 
59 
_rotationZ(Matrix * m,float degree)60 static void _rotationZ(Matrix* m, float degree)
61 {
62     if (degree == 0.0f) return;
63     auto radian = deg2rad(degree);
64     m->e11 = cosf(radian);
65     m->e12 = -sinf(radian);
66     m->e21 = sinf(radian);
67     m->e22 = cosf(radian);
68 }
69 
70 
_skew(Matrix * m,float angleDeg,float axisDeg)71 static void _skew(Matrix* m, float angleDeg, float axisDeg)
72 {
73     auto angle = -deg2rad(angleDeg);
74     float tanVal = tanf(angle);
75 
76     axisDeg = fmod(axisDeg, 180.0f);
77     if (fabsf(axisDeg) < 0.01f || fabsf(axisDeg - 180.0f) < 0.01f || fabsf(axisDeg + 180.0f) < 0.01f) {
78         float cosVal = cosf(deg2rad(axisDeg));
79         auto B = cosVal * cosVal * tanVal;
80         m->e12 += B * m->e11;
81         m->e22 += B * m->e21;
82         return;
83     } else if (fabsf(axisDeg - 90.0f) < 0.01f || fabsf(axisDeg + 90.0f) < 0.01f) {
84         float sinVal = -sinf(deg2rad(axisDeg));
85         auto C = sinVal * sinVal * tanVal;
86         m->e11 -= C * m->e12;
87         m->e21 -= C * m->e22;
88         return;
89     }
90 
91     auto axis = -deg2rad(axisDeg);
92     float cosVal = cosf(axis);
93     float sinVal = sinf(axis);
94     auto A = sinVal * cosVal * tanVal;
95     auto B = cosVal * cosVal * tanVal;
96     auto C = sinVal * sinVal * tanVal;
97 
98     auto e11 = m->e11;
99     auto e21 = m->e21;
100     m->e11 = (1.0f - A) * e11 - C * m->e12;
101     m->e12 = B * e11 + (1.0f + A) * m->e12;
102     m->e21 = (1.0f - A) * e21 - C * m->e22;
103     m->e22 = B * e21 + (1.0f + A) * m->e22;
104 }
105 
106 
_updateTransform(LottieTransform * transform,float frameNo,bool autoOrient,Matrix & matrix,uint8_t & opacity,LottieExpressions * exps)107 static bool _updateTransform(LottieTransform* transform, float frameNo, bool autoOrient, Matrix& matrix, uint8_t& opacity, LottieExpressions* exps)
108 {
109     identity(&matrix);
110 
111     if (!transform) {
112         opacity = 255;
113         return false;
114     }
115 
116     if (transform->coords) {
117         translate(&matrix, transform->coords->x(frameNo, exps), transform->coords->y(frameNo, exps));
118     } else {
119         auto position = transform->position(frameNo, exps);
120         translate(&matrix, position.x, position.y);
121     }
122 
123     auto angle = 0.0f;
124     if (autoOrient) angle = transform->position.angle(frameNo);
125     if (transform->rotationEx) _rotationXYZ(&matrix, transform->rotationEx->x(frameNo, exps), transform->rotationEx->y(frameNo, exps), transform->rotation(frameNo, exps) + angle);
126     else _rotationZ(&matrix, transform->rotation(frameNo, exps) + angle);
127 
128 
129     auto skewAngle = transform->skewAngle(frameNo, exps);
130     if (skewAngle != 0.0f) {
131         // For angles where tangent explodes, the shape degenerates into an infinitely thin line.
132         // This is handled by zeroing out the matrix due to finite numerical precision.
133         skewAngle = fmod(skewAngle, 180.0f);
134         if (fabsf(skewAngle - 90.0f) < 0.01f || fabsf(skewAngle + 90.0f) < 0.01f) return false;
135         _skew(&matrix, skewAngle, transform->skewAxis(frameNo, exps));
136     }
137 
138     auto scale = transform->scale(frameNo, exps);
139     scaleR(&matrix, scale.x * 0.01f, scale.y * 0.01f);
140 
141     //Lottie specific anchor transform.
142     auto anchor = transform->anchor(frameNo, exps);
143     translateR(&matrix, -anchor.x, -anchor.y);
144 
145     //invisible just in case.
146     if (scale.x == 0.0f || scale.y == 0.0f) opacity = 0;
147     else opacity = transform->opacity(frameNo, exps);
148 
149     return true;
150 }
151 
152 
updateTransform(LottieLayer * layer,float frameNo)153 void LottieBuilder::updateTransform(LottieLayer* layer, float frameNo)
154 {
155     if (!layer || tvg::equal(layer->cache.frameNo, frameNo)) return;
156 
157     auto transform = layer->transform;
158     auto parent = layer->parent;
159 
160     if (parent) updateTransform(parent, frameNo);
161 
162     auto& matrix = layer->cache.matrix;
163 
164     _updateTransform(transform, frameNo, layer->autoOrient, matrix, layer->cache.opacity, exps);
165 
166     if (parent) {
167         if (!identity((const Matrix*) &parent->cache.matrix)) {
168             if (identity((const Matrix*) &matrix)) layer->cache.matrix = parent->cache.matrix;
169             else layer->cache.matrix = parent->cache.matrix * matrix;
170         }
171     }
172     layer->cache.frameNo = frameNo;
173 }
174 
175 
updateTransform(LottieGroup * parent,LottieObject ** child,float frameNo,TVG_UNUSED Inlist<RenderContext> & contexts,RenderContext * ctx)176 void LottieBuilder::updateTransform(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
177 {
178     auto transform = static_cast<LottieTransform*>(*child);
179     if (!transform) return;
180 
181     uint8_t opacity;
182 
183     if (parent->mergeable()) {
184         if (!ctx->transform) ctx->transform = (Matrix*)malloc(sizeof(Matrix));
185         _updateTransform(transform, frameNo, false, *ctx->transform, opacity, exps);
186         return;
187     }
188 
189     ctx->merging = nullptr;
190 
191     Matrix matrix;
192     if (!_updateTransform(transform, frameNo, false, matrix, opacity, exps)) return;
193 
194     ctx->propagator->transform(PP(ctx->propagator)->transform() * matrix);
195     ctx->propagator->opacity(MULTIPLY(opacity, PP(ctx->propagator)->opacity));
196 
197     //FIXME: preserve the stroke width. too workaround, need a better design.
198     if (P(ctx->propagator)->rs.strokeWidth() > 0.0f) {
199         auto denominator = sqrtf(matrix.e11 * matrix.e11 + matrix.e12 * matrix.e12);
200         if (denominator > 1.0f) ctx->propagator->stroke(ctx->propagator->strokeWidth() / denominator);
201     }
202 }
203 
204 
updateGroup(LottieGroup * parent,LottieObject ** child,float frameNo,TVG_UNUSED Inlist<RenderContext> & pcontexts,RenderContext * ctx)205 void LottieBuilder::updateGroup(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& pcontexts, RenderContext* ctx)
206 {
207     auto group = static_cast<LottieGroup*>(*child);
208 
209     if (!group->visible) return;
210 
211     //Prepare render data
212     group->scene = parent->scene;
213     group->reqFragment |= ctx->reqFragment;
214 
215     //generate a merging shape to consolidate partial shapes into a single entity
216     if (group->mergeable()) _draw(parent, nullptr, ctx);
217 
218     Inlist<RenderContext> contexts;
219     auto propagator = group->mergeable() ? ctx->propagator : static_cast<Shape*>(PP(ctx->propagator)->duplicate(group->pooling()));
220     contexts.back(new RenderContext(*ctx, propagator, group->mergeable()));
221 
222     updateChildren(group, frameNo, contexts);
223 
224     contexts.free();
225 }
226 
227 
_updateStroke(LottieStroke * stroke,float frameNo,RenderContext * ctx,LottieExpressions * exps)228 static void _updateStroke(LottieStroke* stroke, float frameNo, RenderContext* ctx, LottieExpressions* exps)
229 {
230     ctx->propagator->stroke(stroke->width(frameNo, exps));
231     ctx->propagator->stroke(stroke->cap);
232     ctx->propagator->stroke(stroke->join);
233     ctx->propagator->strokeMiterlimit(stroke->miterLimit);
234 
235     if (stroke->dashattr) {
236         float dashes[2];
237         dashes[0] = stroke->dashSize(frameNo, exps);
238         dashes[1] = dashes[0] + stroke->dashGap(frameNo, exps);
239         P(ctx->propagator)->strokeDash(dashes, 2, stroke->dashOffset(frameNo, exps));
240     } else {
241         ctx->propagator->stroke(nullptr, 0);
242     }
243 }
244 
245 
_fragmented(LottieGroup * parent,LottieObject ** child,Inlist<RenderContext> & contexts,RenderContext * ctx)246 static bool _fragmented(LottieGroup* parent, LottieObject** child, Inlist<RenderContext>& contexts, RenderContext* ctx)
247 {
248     if (!ctx->reqFragment) return false;
249     if (ctx->fragmenting) return true;
250 
251     contexts.back(new RenderContext(*ctx, static_cast<Shape*>(PP(ctx->propagator)->duplicate(parent->pooling()))));
252     auto fragment = contexts.tail;
253     fragment->begin = child - 1;
254     ctx->fragmenting = true;
255 
256     return false;
257 }
258 
259 
updateSolidStroke(LottieGroup * parent,LottieObject ** child,float frameNo,Inlist<RenderContext> & contexts,RenderContext * ctx)260 void LottieBuilder::updateSolidStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
261 {
262     if (_fragmented(parent, child, contexts, ctx)) return;
263 
264     auto stroke = static_cast<LottieSolidStroke*>(*child);
265 
266     ctx->merging = nullptr;
267     auto color = stroke->color(frameNo, exps);
268     ctx->propagator->stroke(color.rgb[0], color.rgb[1], color.rgb[2], stroke->opacity(frameNo, exps));
269     _updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx, exps);
270 }
271 
272 
updateGradientStroke(LottieGroup * parent,LottieObject ** child,float frameNo,Inlist<RenderContext> & contexts,RenderContext * ctx)273 void LottieBuilder::updateGradientStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
274 {
275     if (_fragmented(parent, child, contexts, ctx)) return;
276 
277     auto stroke = static_cast<LottieGradientStroke*>(*child);
278 
279     ctx->merging = nullptr;
280     ctx->propagator->stroke(unique_ptr<Fill>(stroke->fill(frameNo, exps)));
281     _updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx, exps);
282 }
283 
284 
updateSolidFill(LottieGroup * parent,LottieObject ** child,float frameNo,Inlist<RenderContext> & contexts,RenderContext * ctx)285 void LottieBuilder::updateSolidFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
286 {
287     if (_fragmented(parent, child, contexts, ctx)) return;
288 
289     auto fill = static_cast<LottieSolidFill*>(*child);
290 
291     ctx->merging = nullptr;
292     auto color = fill->color(frameNo, exps);
293     ctx->propagator->fill(color.rgb[0], color.rgb[1], color.rgb[2], fill->opacity(frameNo, exps));
294     ctx->propagator->fill(fill->rule);
295 
296     if (ctx->propagator->strokeWidth() > 0) ctx->propagator->order(true);
297 }
298 
299 
updateGradientFill(LottieGroup * parent,LottieObject ** child,float frameNo,Inlist<RenderContext> & contexts,RenderContext * ctx)300 void LottieBuilder::updateGradientFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
301 {
302     if (_fragmented(parent, child, contexts, ctx)) return;
303 
304     auto fill = static_cast<LottieGradientFill*>(*child);
305 
306     ctx->merging = nullptr;
307     //TODO: reuse the fill instance?
308     ctx->propagator->fill(unique_ptr<Fill>(fill->fill(frameNo, exps)));
309     ctx->propagator->fill(fill->rule);
310 
311     if (ctx->propagator->strokeWidth() > 0) ctx->propagator->order(true);
312 }
313 
314 
_draw(LottieGroup * parent,LottieShape * shape,RenderContext * ctx)315 static bool _draw(LottieGroup* parent, LottieShape* shape, RenderContext* ctx)
316 {
317     if (ctx->merging) return false;
318 
319     if (shape) {
320         ctx->merging = shape->pooling();
321         PP(ctx->propagator)->duplicate(ctx->merging);
322     } else {
323         ctx->merging = static_cast<Shape*>(ctx->propagator->duplicate());
324     }
325 
326     parent->scene->push(cast(ctx->merging));
327 
328     return true;
329 }
330 
331 
_repeat(LottieGroup * parent,Shape * path,RenderContext * ctx)332 static void _repeat(LottieGroup* parent, Shape* path, RenderContext* ctx)
333 {
334     Array<Shape*> propagators;
335     propagators.push(ctx->propagator);
336     Array<Shape*> shapes;
337 
338     for (auto repeater = ctx->repeaters.end() - 1; repeater >= ctx->repeaters.begin(); --repeater) {
339         shapes.reserve(repeater->cnt);
340 
341         for (int i = 0; i < repeater->cnt; ++i) {
342             auto multiplier = repeater->offset + static_cast<float>(i);
343 
344             for (auto propagator = propagators.begin(); propagator < propagators.end(); ++propagator) {
345                 auto shape = static_cast<Shape*>((*propagator)->duplicate());
346                 P(shape)->rs.path = P(path)->rs.path;
347 
348                 auto opacity = repeater->interpOpacity ? lerp<uint8_t>(repeater->startOpacity, repeater->endOpacity, static_cast<float>(i + 1) / repeater->cnt) : repeater->startOpacity;
349                 shape->opacity(opacity);
350 
351                 Matrix m;
352                 identity(&m);
353                 translate(&m, repeater->position.x * multiplier + repeater->anchor.x, repeater->position.y * multiplier + repeater->anchor.y);
354                 scale(&m, powf(repeater->scale.x * 0.01f, multiplier), powf(repeater->scale.y * 0.01f, multiplier));
355                 rotate(&m, repeater->rotation * multiplier);
356                 translateR(&m, -repeater->anchor.x, -repeater->anchor.y);
357                 m = repeater->transform * m;
358 
359                 Matrix inv;
360                 inverse(&repeater->transform, &inv);
361                 shape->transform(m * (inv * PP(shape)->transform()));
362                 shapes.push(shape);
363             }
364         }
365 
366         propagators.clear();
367         propagators.reserve(shapes.count);
368 
369         //push repeat shapes in order.
370         if (repeater->inorder) {
371             for (auto shape = shapes.begin(); shape < shapes.end(); ++shape) {
372                 parent->scene->push(cast(*shape));
373                 propagators.push(*shape);
374             }
375         } else if (!shapes.empty()) {
376             for (auto shape = shapes.end() - 1; shape >= shapes.begin(); --shape) {
377                 parent->scene->push(cast(*shape));
378                 propagators.push(*shape);
379             }
380         }
381         shapes.clear();
382     }
383 }
384 
385 
_appendRect(Shape * shape,float x,float y,float w,float h,float r,const LottieOffsetModifier * offsetPath,Matrix * transform,bool clockwise)386 static void _appendRect(Shape* shape, float x, float y, float w, float h, float r, const LottieOffsetModifier* offsetPath, Matrix* transform, bool clockwise)
387 {
388     //sharp rect
389     if (tvg::zero(r)) {
390         PathCommand commands[] = {
391             PathCommand::MoveTo, PathCommand::LineTo, PathCommand::LineTo,
392             PathCommand::LineTo, PathCommand::Close
393         };
394 
395         Point points[4];
396         if (clockwise) {
397             points[0] = {x + w, y};
398             points[1] = {x + w, y + h};
399             points[2] = {x, y + h};
400             points[3] = {x, y};
401         } else {
402             points[0] = {x + w, y};
403             points[1] = {x, y};
404             points[2] = {x, y + h};
405             points[3] = {x + w, y + h};
406         }
407         if (transform) {
408             for (int i = 0; i < 4; i++) {
409                 points[i] *= *transform;
410             }
411         }
412 
413         if (offsetPath) offsetPath->modifyRect(commands, 5, points, 4, P(shape)->rs.path.cmds, P(shape)->rs.path.pts);
414         else shape->appendPath(commands, 5, points, 4);
415     //round rect
416     } else {
417         constexpr int cmdCnt = 10;
418         PathCommand commands[cmdCnt];
419 
420         auto halfW = w * 0.5f;
421         auto halfH = h * 0.5f;
422         auto rx = r > halfW ? halfW : r;
423         auto ry = r > halfH ? halfH : r;
424         auto hrx = rx * PATH_KAPPA;
425         auto hry = ry * PATH_KAPPA;
426 
427         constexpr int ptsCnt = 17;
428         Point points[ptsCnt];
429         if (clockwise) {
430             commands[0] = PathCommand::MoveTo; commands[1] = PathCommand::LineTo; commands[2] = PathCommand::CubicTo;
431             commands[3] = PathCommand::LineTo; commands[4] = PathCommand::CubicTo;commands[5] = PathCommand::LineTo;
432             commands[6] = PathCommand::CubicTo; commands[7] = PathCommand::LineTo; commands[8] = PathCommand::CubicTo;
433             commands[9] = PathCommand::Close;
434 
435             points[0] = {x + w, y + ry}; //moveTo
436             points[1] = {x + w, y + h - ry}; //lineTo
437             points[2] = {x + w, y + h - ry + hry}; points[3] = {x + w - rx + hrx, y + h}; points[4] = {x + w - rx, y + h}; //cubicTo
438             points[5] = {x + rx, y + h}, //lineTo
439             points[6] = {x + rx - hrx, y + h}; points[7] = {x, y + h - ry + hry}; points[8] = {x, y + h - ry}; //cubicTo
440             points[9] = {x, y + ry}, //lineTo
441             points[10] = {x, y + ry - hry}; points[11] = {x + rx - hrx, y}; points[12] = {x + rx, y}; //cubicTo
442             points[13] = {x + w - rx, y}; //lineTo
443             points[14] = {x + w - rx + hrx, y}; points[15] = {x + w, y + ry - hry}; points[16] = {x + w, y + ry}; //cubicTo
444         } else {
445             commands[0] = PathCommand::MoveTo; commands[1] = PathCommand::CubicTo; commands[2] = PathCommand::LineTo;
446             commands[3] = PathCommand::CubicTo; commands[4] = PathCommand::LineTo; commands[5] = PathCommand::CubicTo;
447             commands[6] = PathCommand::LineTo; commands[7] = PathCommand::CubicTo; commands[8] = PathCommand::LineTo;
448             commands[9] = PathCommand::Close;
449 
450             points[0] = {x + w, y + ry}; //moveTo
451             points[1] = {x + w, y + ry - hry}; points[2] = {x + w - rx + hrx, y}; points[3] = {x + w - rx, y}; //cubicTo
452             points[4] = {x + rx, y}, //lineTo
453             points[5] = {x + rx - hrx, y}; points[6] = {x, y + ry - hry}; points[7] = {x, y + ry}; //cubicTo
454             points[8] = {x, y + h - ry}; //lineTo
455             points[9] = {x, y + h - ry + hry}; points[10] = {x + rx - hrx, y + h}; points[11] = {x + rx, y + h}; //cubicTo
456             points[12] = {x + w - rx, y + h}; //lineTo
457             points[13] = {x + w - rx + hrx, y + h}; points[14] = {x + w, y + h - ry + hry}; points[15] = {x + w, y + h - ry}; //cubicTo
458             points[16] = {x + w, y + ry}; //lineTo
459         }
460         if (transform) {
461             for (int i = 0; i < ptsCnt; i++) {
462                 points[i] *= *transform;
463             }
464         }
465 
466         if (offsetPath) offsetPath->modifyRect(commands, cmdCnt, points, ptsCnt, P(shape)->rs.path.cmds, P(shape)->rs.path.pts);
467         else shape->appendPath(commands, cmdCnt, points, ptsCnt);
468     }
469 }
470 
471 
updateRect(LottieGroup * parent,LottieObject ** child,float frameNo,TVG_UNUSED Inlist<RenderContext> & contexts,RenderContext * ctx)472 void LottieBuilder::updateRect(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
473 {
474     auto rect = static_cast<LottieRect*>(*child);
475 
476     auto position = rect->position(frameNo, exps);
477     auto size = rect->size(frameNo, exps);
478     auto r = rect->radius(frameNo, exps);
479     if (r == 0.0f)  {
480         if (ctx->roundness) ctx->roundness->modifyRect(size, r);
481     } else {
482         r = std::min({r, size.x * 0.5f, size.y * 0.5f});
483     }
484 
485     if (!ctx->repeaters.empty()) {
486         auto shape = rect->pooling();
487         shape->reset();
488         _appendRect(shape, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, r, ctx->offsetPath, ctx->transform, rect->clockwise);
489         _repeat(parent, shape, ctx);
490     } else {
491         _draw(parent, rect, ctx);
492         _appendRect(ctx->merging, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, r, ctx->offsetPath, ctx->transform, rect->clockwise);
493     }
494 }
495 
496 
_appendCircle(Shape * shape,float cx,float cy,float rx,float ry,const LottieOffsetModifier * offsetPath,Matrix * transform,bool clockwise)497 static void _appendCircle(Shape* shape, float cx, float cy, float rx, float ry, const LottieOffsetModifier* offsetPath, Matrix* transform, bool clockwise)
498 {
499     if (offsetPath) offsetPath->modifyEllipse(rx, ry);
500 
501     if (rx == 0.0f || ry == 0.0f) return;
502 
503     auto rxKappa = rx * PATH_KAPPA;
504     auto ryKappa = ry * PATH_KAPPA;
505 
506     constexpr int cmdsCnt = 6;
507     PathCommand commands[cmdsCnt] = {
508         PathCommand::MoveTo, PathCommand::CubicTo, PathCommand::CubicTo,
509         PathCommand::CubicTo, PathCommand::CubicTo, PathCommand::Close
510     };
511 
512     constexpr int ptsCnt = 13;
513     Point points[ptsCnt];
514 
515     if (clockwise) {
516         points[0] = {cx, cy - ry}; //moveTo
517         points[1] = {cx + rxKappa, cy - ry}; points[2] = {cx + rx, cy - ryKappa}; points[3] = {cx + rx, cy}; //cubicTo
518         points[4] = {cx + rx, cy + ryKappa}; points[5] = {cx + rxKappa, cy + ry}; points[6] = {cx, cy + ry}; //cubicTo
519         points[7] = {cx - rxKappa, cy + ry}; points[8] = {cx - rx, cy + ryKappa}; points[9] = {cx - rx, cy}; //cubicTo
520         points[10] = {cx - rx, cy - ryKappa}; points[11] = {cx - rxKappa, cy - ry}; points[12] = {cx, cy - ry}; //cubicTo
521     } else {
522         points[0] = {cx, cy - ry}; //moveTo
523         points[1] = {cx - rxKappa, cy - ry}; points[2] = {cx - rx, cy - ryKappa}; points[3] = {cx - rx, cy}; //cubicTo
524         points[4] = {cx - rx, cy + ryKappa}; points[5] = {cx - rxKappa, cy + ry}; points[6] = {cx, cy + ry}; //cubicTo
525         points[7] = {cx + rxKappa, cy + ry}; points[8] = {cx + rx, cy + ryKappa}; points[9] = {cx + rx, cy}; //cubicTo
526         points[10] = {cx + rx, cy - ryKappa}; points[11] = {cx + rxKappa, cy - ry}; points[12] = {cx, cy - ry}; //cubicTo
527     }
528 
529     if (transform) {
530         for (int i = 0; i < ptsCnt; ++i) {
531             points[i] *= *transform;
532         }
533     }
534 
535     shape->appendPath(commands, cmdsCnt, points, ptsCnt);
536 }
537 
538 
updateEllipse(LottieGroup * parent,LottieObject ** child,float frameNo,TVG_UNUSED Inlist<RenderContext> & contexts,RenderContext * ctx)539 void LottieBuilder::updateEllipse(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
540 {
541     auto ellipse = static_cast<LottieEllipse*>(*child);
542 
543     auto position = ellipse->position(frameNo, exps);
544     auto size = ellipse->size(frameNo, exps);
545 
546     if (!ctx->repeaters.empty()) {
547         auto shape = ellipse->pooling();
548         shape->reset();
549         _appendCircle(shape, position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->offsetPath, ctx->transform, ellipse->clockwise);
550         _repeat(parent, shape, ctx);
551     } else {
552         _draw(parent, ellipse, ctx);
553         _appendCircle(ctx->merging, position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->offsetPath, ctx->transform, ellipse->clockwise);
554     }
555 }
556 
557 
updatePath(LottieGroup * parent,LottieObject ** child,float frameNo,TVG_UNUSED Inlist<RenderContext> & contexts,RenderContext * ctx)558 void LottieBuilder::updatePath(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
559 {
560     auto path = static_cast<LottiePath*>(*child);
561 
562     if (!ctx->repeaters.empty()) {
563         auto shape = path->pooling();
564         shape->reset();
565         path->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, ctx->transform, ctx->roundness, ctx->offsetPath, exps);
566         _repeat(parent, shape, ctx);
567     } else {
568         _draw(parent, path, ctx);
569         if (path->pathset(frameNo, P(ctx->merging)->rs.path.cmds, P(ctx->merging)->rs.path.pts, ctx->transform, ctx->roundness, ctx->offsetPath, exps)) {
570             P(ctx->merging)->update(RenderUpdateFlag::Path);
571         }
572     }
573 }
574 
575 
_updateStar(TVG_UNUSED LottieGroup * parent,LottiePolyStar * star,Matrix * transform,const LottieRoundnessModifier * roundness,const LottieOffsetModifier * offsetPath,float frameNo,Shape * merging,LottieExpressions * exps)576 static void _updateStar(TVG_UNUSED LottieGroup* parent, LottiePolyStar* star, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath, float frameNo, Shape* merging, LottieExpressions* exps)
577 {
578     static constexpr auto POLYSTAR_MAGIC_NUMBER = 0.47829f / 0.28f;
579 
580     auto ptsCnt = star->ptsCnt(frameNo, exps);
581     auto innerRadius = star->innerRadius(frameNo, exps);
582     auto outerRadius = star->outerRadius(frameNo, exps);
583     auto innerRoundness = star->innerRoundness(frameNo, exps) * 0.01f;
584     auto outerRoundness = star->outerRoundness(frameNo, exps) * 0.01f;
585 
586     auto angle = deg2rad(-90.0f);
587     auto partialPointRadius = 0.0f;
588     auto anglePerPoint = (2.0f * MATH_PI / ptsCnt);
589     auto halfAnglePerPoint = anglePerPoint * 0.5f;
590     auto partialPointAmount = ptsCnt - floorf(ptsCnt);
591     auto longSegment = false;
592     auto numPoints = size_t(ceilf(ptsCnt) * 2);
593     auto direction = star->clockwise ? 1.0f : -1.0f;
594     auto hasRoundness = false;
595     bool roundedCorner = roundness && (tvg::zero(innerRoundness) || tvg::zero(outerRoundness));
596 
597     Shape* shape;
598     if (roundedCorner || offsetPath) {
599         shape = star->pooling();
600         shape->reset();
601     } else {
602         shape = merging;
603     }
604 
605     float x, y;
606 
607     if (!tvg::zero(partialPointAmount)) {
608         angle += halfAnglePerPoint * (1.0f - partialPointAmount) * direction;
609     }
610 
611     if (!tvg::zero(partialPointAmount)) {
612         partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius);
613         x = partialPointRadius * cosf(angle);
614         y = partialPointRadius * sinf(angle);
615         angle += anglePerPoint * partialPointAmount * 0.5f * direction;
616     } else {
617         x = outerRadius * cosf(angle);
618         y = outerRadius * sinf(angle);
619         angle += halfAnglePerPoint * direction;
620     }
621 
622     if (tvg::zero(innerRoundness) && tvg::zero(outerRoundness)) {
623         P(shape)->rs.path.pts.reserve(numPoints + 2);
624         P(shape)->rs.path.cmds.reserve(numPoints + 3);
625     } else {
626         P(shape)->rs.path.pts.reserve(numPoints * 3 + 2);
627         P(shape)->rs.path.cmds.reserve(numPoints + 3);
628         hasRoundness = true;
629     }
630 
631     Point in = {x, y};
632     if (transform) in *= *transform;
633     shape->moveTo(in.x, in.y);
634 
635     for (size_t i = 0; i < numPoints; i++) {
636         auto radius = longSegment ? outerRadius : innerRadius;
637         auto dTheta = halfAnglePerPoint;
638         if (!tvg::zero(partialPointRadius) && i == numPoints - 2) {
639             dTheta = anglePerPoint * partialPointAmount * 0.5f;
640         }
641         if (!tvg::zero(partialPointRadius) && i == numPoints - 1) {
642             radius = partialPointRadius;
643         }
644         auto previousX = x;
645         auto previousY = y;
646         x = radius * cosf(angle);
647         y = radius * sinf(angle);
648 
649         if (hasRoundness) {
650             auto cp1Theta = (tvg::atan2(previousY, previousX) - MATH_PI2 * direction);
651             auto cp1Dx = cosf(cp1Theta);
652             auto cp1Dy = sinf(cp1Theta);
653             auto cp2Theta = (tvg::atan2(y, x) - MATH_PI2 * direction);
654             auto cp2Dx = cosf(cp2Theta);
655             auto cp2Dy = sinf(cp2Theta);
656 
657             auto cp1Roundness = longSegment ? innerRoundness : outerRoundness;
658             auto cp2Roundness = longSegment ? outerRoundness : innerRoundness;
659             auto cp1Radius = longSegment ? innerRadius : outerRadius;
660             auto cp2Radius = longSegment ? outerRadius : innerRadius;
661 
662             auto cp1x = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER * cp1Dx / ptsCnt;
663             auto cp1y = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER * cp1Dy / ptsCnt;
664             auto cp2x = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER * cp2Dx / ptsCnt;
665             auto cp2y = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER * cp2Dy / ptsCnt;
666 
667             if (!tvg::zero(partialPointAmount) && ((i == 0) || (i == numPoints - 1))) {
668                 cp1x *= partialPointAmount;
669                 cp1y *= partialPointAmount;
670                 cp2x *= partialPointAmount;
671                 cp2y *= partialPointAmount;
672             }
673             Point in2 = {previousX - cp1x, previousY - cp1y};
674             Point in3 = {x + cp2x, y + cp2y};
675             Point in4 = {x, y};
676             if (transform) {
677                 in2 *= *transform;
678                 in3 *= *transform;
679                 in4 *= *transform;
680             }
681             shape->cubicTo(in2.x, in2.y, in3.x, in3.y, in4.x, in4.y);
682         } else {
683             Point in = {x, y};
684             if (transform) in *= *transform;
685             shape->lineTo(in.x, in.y);
686         }
687         angle += dTheta * direction;
688         longSegment = !longSegment;
689     }
690     shape->close();
691 
692     if (roundedCorner) {
693         if (offsetPath) {
694             auto intermediate = Shape::gen();
695             roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(intermediate)->rs.path.cmds, P(intermediate)->rs.path.pts, outerRoundness, hasRoundness);
696             offsetPath->modifyPolystar(P(intermediate)->rs.path.cmds, P(intermediate)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts);
697         } else {
698             roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, outerRoundness, hasRoundness);
699         }
700     } else if (offsetPath) offsetPath->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts);
701 }
702 
703 
_updatePolygon(LottieGroup * parent,LottiePolyStar * star,Matrix * transform,const LottieRoundnessModifier * roundness,const LottieOffsetModifier * offsetPath,float frameNo,Shape * merging,LottieExpressions * exps)704 static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath, float frameNo, Shape* merging, LottieExpressions* exps)
705 {
706     static constexpr auto POLYGON_MAGIC_NUMBER = 0.25f;
707 
708     auto ptsCnt = size_t(floor(star->ptsCnt(frameNo, exps)));
709     auto radius = star->outerRadius(frameNo, exps);
710     auto outerRoundness = star->outerRoundness(frameNo, exps) * 0.01f;
711 
712     auto angle = deg2rad(-90.0f);
713     auto anglePerPoint = 2.0f * MATH_PI / float(ptsCnt);
714     auto direction = star->clockwise ? 1.0f : -1.0f;
715     auto hasRoundness = !tvg::zero(outerRoundness);
716     bool roundedCorner = roundness && !hasRoundness;
717     auto x = radius * cosf(angle);
718     auto y = radius * sinf(angle);
719 
720     angle += anglePerPoint * direction;
721 
722     Shape* shape;
723     if (roundedCorner || offsetPath) {
724         shape = star->pooling();
725         shape->reset();
726     } else {
727         shape = merging;
728         if (hasRoundness) {
729             P(shape)->rs.path.pts.reserve(ptsCnt * 3 + 2);
730             P(shape)->rs.path.cmds.reserve(ptsCnt + 3);
731         } else {
732             P(shape)->rs.path.pts.reserve(ptsCnt + 2);
733             P(shape)->rs.path.cmds.reserve(ptsCnt + 3);
734         }
735     }
736 
737     Point in = {x, y};
738     if (transform) in *= *transform;
739     shape->moveTo(in.x, in.y);
740 
741     for (size_t i = 0; i < ptsCnt; i++) {
742         auto previousX = x;
743         auto previousY = y;
744         x = (radius * cosf(angle));
745         y = (radius * sinf(angle));
746 
747         if (hasRoundness) {
748             auto cp1Theta = tvg::atan2(previousY, previousX) - MATH_PI2 * direction;
749             auto cp1Dx = cosf(cp1Theta);
750             auto cp1Dy = sinf(cp1Theta);
751             auto cp2Theta = tvg::atan2(y, x) - MATH_PI2 * direction;
752             auto cp2Dx = cosf(cp2Theta);
753             auto cp2Dy = sinf(cp2Theta);
754 
755             auto cp1x = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp1Dx;
756             auto cp1y = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp1Dy;
757             auto cp2x = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp2Dx;
758             auto cp2y = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp2Dy;
759 
760             Point in2 = {previousX - cp1x, previousY - cp1y};
761             Point in3 = {x + cp2x, y + cp2y};
762             Point in4 = {x, y};
763             if (transform) {
764                 in2 *= *transform;
765                 in3 *= *transform;
766                 in4 *= *transform;
767             }
768             shape->cubicTo(in2.x, in2.y, in3.x, in3.y, in4.x, in4.y);
769         } else {
770             Point in = {x, y};
771             if (transform) in *= *transform;
772             shape->lineTo(in.x, in.y);
773         }
774         angle += anglePerPoint * direction;
775     }
776     shape->close();
777 
778     if (roundedCorner) {
779         if (offsetPath) {
780             auto intermediate = Shape::gen();
781             roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(intermediate)->rs.path.cmds, P(intermediate)->rs.path.pts, 0.0f, false);
782             offsetPath->modifyPolystar(P(intermediate)->rs.path.cmds, P(intermediate)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts);
783         } else {
784             roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, 0.0f, false);
785         }
786     } else if (offsetPath) offsetPath->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts);
787 }
788 
789 
updatePolystar(LottieGroup * parent,LottieObject ** child,float frameNo,TVG_UNUSED Inlist<RenderContext> & contexts,RenderContext * ctx)790 void LottieBuilder::updatePolystar(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
791 {
792     auto star = static_cast<LottiePolyStar*>(*child);
793 
794     //Optimize: Can we skip the individual coords transform?
795     Matrix matrix;
796     identity(&matrix);
797     auto position = star->position(frameNo, exps);
798     translate(&matrix, position.x, position.y);
799     rotate(&matrix, star->rotation(frameNo, exps));
800 
801     if (ctx->transform) matrix = *ctx->transform * matrix;
802 
803     auto identity = tvg::identity((const Matrix*)&matrix);
804 
805     if (!ctx->repeaters.empty()) {
806         auto shape = star->pooling();
807         shape->reset();
808         if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, ctx->roundness, ctx->offsetPath, frameNo, shape, exps);
809         else _updatePolygon(parent, star, identity  ? nullptr : &matrix, ctx->roundness, ctx->offsetPath, frameNo, shape, exps);
810         _repeat(parent, shape, ctx);
811     } else {
812         _draw(parent, star, ctx);
813         if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, ctx->roundness, ctx->offsetPath, frameNo, ctx->merging, exps);
814         else _updatePolygon(parent, star, identity  ? nullptr : &matrix, ctx->roundness, ctx->offsetPath, frameNo, ctx->merging, exps);
815         P(ctx->merging)->update(RenderUpdateFlag::Path);
816     }
817 }
818 
819 
updateRoundedCorner(TVG_UNUSED LottieGroup * parent,LottieObject ** child,float frameNo,TVG_UNUSED Inlist<RenderContext> & contexts,RenderContext * ctx)820 void LottieBuilder::updateRoundedCorner(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
821 {
822     auto roundedCorner = static_cast<LottieRoundedCorner*>(*child);
823     auto r = roundedCorner->radius(frameNo, exps);
824     if (r < LottieRoundnessModifier::ROUNDNESS_EPSILON) return;
825 
826     if (!ctx->roundness) ctx->roundness = new LottieRoundnessModifier(r);
827     else if (ctx->roundness->r < r) ctx->roundness->r = r;
828 }
829 
830 
updateOffsetPath(TVG_UNUSED LottieGroup * parent,LottieObject ** child,float frameNo,TVG_UNUSED Inlist<RenderContext> & contexts,RenderContext * ctx)831 void LottieBuilder::updateOffsetPath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
832 {
833     auto offsetPath = static_cast<LottieOffsetPath*>(*child);
834     if (!ctx->offsetPath) ctx->offsetPath = new LottieOffsetModifier(offsetPath->offset(frameNo, exps), offsetPath->miterLimit(frameNo, exps), offsetPath->join);
835 }
836 
837 
updateRepeater(TVG_UNUSED LottieGroup * parent,LottieObject ** child,float frameNo,TVG_UNUSED Inlist<RenderContext> & contexts,RenderContext * ctx)838 void LottieBuilder::updateRepeater(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
839 {
840     auto repeater = static_cast<LottieRepeater*>(*child);
841 
842     RenderRepeater r;
843     r.cnt = static_cast<int>(repeater->copies(frameNo, exps));
844     r.transform = PP(ctx->propagator)->transform();
845     r.offset = repeater->offset(frameNo, exps);
846     r.position = repeater->position(frameNo, exps);
847     r.anchor = repeater->anchor(frameNo, exps);
848     r.scale = repeater->scale(frameNo, exps);
849     r.rotation = repeater->rotation(frameNo, exps);
850     r.startOpacity = repeater->startOpacity(frameNo, exps);
851     r.endOpacity = repeater->endOpacity(frameNo, exps);
852     r.inorder = repeater->inorder;
853     r.interpOpacity = (r.startOpacity == r.endOpacity) ? false : true;
854     ctx->repeaters.push(r);
855 
856     ctx->merging = nullptr;
857 }
858 
859 
updateTrimpath(TVG_UNUSED LottieGroup * parent,LottieObject ** child,float frameNo,TVG_UNUSED Inlist<RenderContext> & contexts,RenderContext * ctx)860 void LottieBuilder::updateTrimpath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
861 {
862     auto trimpath = static_cast<LottieTrimpath*>(*child);
863 
864     float begin, end;
865     trimpath->segment(frameNo, begin, end, exps);
866 
867     if (P(ctx->propagator)->rs.stroke) {
868         auto pbegin = P(ctx->propagator)->rs.stroke->trim.begin;
869         auto pend = P(ctx->propagator)->rs.stroke->trim.end;
870         auto length = fabsf(pend - pbegin);
871         begin = (length * begin) + pbegin;
872         end = (length * end) + pbegin;
873     }
874 
875     P(ctx->propagator)->strokeTrim(begin, end, trimpath->type == LottieTrimpath::Type::Simultaneous);
876     ctx->merging = nullptr;
877 }
878 
879 
updateChildren(LottieGroup * parent,float frameNo,Inlist<RenderContext> & contexts)880 void LottieBuilder::updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderContext>& contexts)
881 {
882     contexts.head->begin = parent->children.end() - 1;
883 
884     while (!contexts.empty()) {
885         auto ctx = contexts.front();
886         ctx->reqFragment = parent->reqFragment;
887         for (auto child = ctx->begin; child >= parent->children.data; --child) {
888             //Here switch-case statements are more performant than virtual methods.
889             switch ((*child)->type) {
890                 case LottieObject::Group: {
891                     updateGroup(parent, child, frameNo, contexts, ctx);
892                     break;
893                 }
894                 case LottieObject::Transform: {
895                     updateTransform(parent, child, frameNo, contexts, ctx);
896                     break;
897                 }
898                 case LottieObject::SolidFill: {
899                     updateSolidFill(parent, child, frameNo, contexts, ctx);
900                     break;
901                 }
902                 case LottieObject::SolidStroke: {
903                     updateSolidStroke(parent, child, frameNo, contexts, ctx);
904                     break;
905                 }
906                 case LottieObject::GradientFill: {
907                     updateGradientFill(parent, child, frameNo, contexts, ctx);
908                     break;
909                 }
910                 case LottieObject::GradientStroke: {
911                     updateGradientStroke(parent, child, frameNo, contexts, ctx);
912                     break;
913                 }
914                 case LottieObject::Rect: {
915                     updateRect(parent, child, frameNo, contexts, ctx);
916                     break;
917                 }
918                 case LottieObject::Ellipse: {
919                     updateEllipse(parent, child, frameNo, contexts, ctx);
920                     break;
921                 }
922                 case LottieObject::Path: {
923                     updatePath(parent, child, frameNo, contexts, ctx);
924                     break;
925                 }
926                 case LottieObject::Polystar: {
927                     updatePolystar(parent, child, frameNo, contexts, ctx);
928                     break;
929                 }
930                 case LottieObject::Trimpath: {
931                     updateTrimpath(parent, child, frameNo, contexts, ctx);
932                     break;
933                 }
934                 case LottieObject::Repeater: {
935                     updateRepeater(parent, child, frameNo, contexts, ctx);
936                     break;
937                 }
938                 case LottieObject::RoundedCorner: {
939                     updateRoundedCorner(parent, child, frameNo, contexts, ctx);
940                     break;
941                 }
942                 case LottieObject::OffsetPath: {
943                     updateOffsetPath(parent, child, frameNo, contexts, ctx);
944                     break;
945                 }
946                 default: break;
947             }
948             if (ctx->propagator->opacity() == 0) break;
949         }
950         delete(ctx);
951     }
952 }
953 
954 
updatePrecomp(LottieComposition * comp,LottieLayer * precomp,float frameNo)955 void LottieBuilder::updatePrecomp(LottieComposition* comp, LottieLayer* precomp, float frameNo)
956 {
957     if (precomp->children.empty()) return;
958 
959     frameNo = precomp->remap(comp, frameNo, exps);
960 
961     for (auto c = precomp->children.end() - 1; c >= precomp->children.begin(); --c) {
962         auto child = static_cast<LottieLayer*>(*c);
963         if (!child->matteSrc) updateLayer(comp, precomp->scene, child, frameNo);
964     }
965 
966     //clip the layer viewport
967     auto clipper = precomp->statical.pooling(true);
968     clipper->transform(precomp->cache.matrix);
969     precomp->scene->clip(cast(clipper));
970 }
971 
972 
updateSolid(LottieLayer * layer)973 void LottieBuilder::updateSolid(LottieLayer* layer)
974 {
975     auto solidFill = layer->statical.pooling(true);
976     solidFill->opacity(layer->cache.opacity);
977     layer->scene->push(cast(solidFill));
978 }
979 
980 
updateImage(LottieGroup * layer)981 void LottieBuilder::updateImage(LottieGroup* layer)
982 {
983     auto image = static_cast<LottieImage*>(layer->children.first());
984     layer->scene->push(tvg::cast(image->pooling(true)));
985 }
986 
987 
updateText(LottieLayer * layer,float frameNo)988 void LottieBuilder::updateText(LottieLayer* layer, float frameNo)
989 {
990     auto text = static_cast<LottieText*>(layer->children.first());
991     auto& doc = text->doc(frameNo);
992     auto p = doc.text;
993 
994     if (!p || !text->font) return;
995 
996     auto scale = doc.size;
997     Point cursor = {0.0f, 0.0f};
998     auto scene = Scene::gen();
999     int line = 0;
1000     int space = 0;
1001     auto lineSpacing = 0.0f;
1002     auto totalLineSpacing = 0.0f;
1003 
1004     //text string
1005     int idx = 0;
1006     auto totalChars = strlen(p);
1007     while (true) {
1008         //TODO: remove nested scenes.
1009         //end of text, new line of the cursor position
1010         if (*p == 13 || *p == 3 || *p == '\0') {
1011             //text layout position
1012             auto ascent = text->font->ascent * scale;
1013             if (ascent > doc.bbox.size.y) ascent = doc.bbox.size.y;
1014             Point layout = {doc.bbox.pos.x, doc.bbox.pos.y + ascent - doc.shift};
1015 
1016             //adjust the layout
1017             if (doc.justify == 1) layout.x += doc.bbox.size.x - (cursor.x * scale);  //right aligned
1018             else if (doc.justify == 2) layout.x += (doc.bbox.size.x * 0.5f) - (cursor.x * 0.5f * scale);  //center aligned
1019 
1020             scene->translate(layout.x, layout.y);
1021             scene->scale(scale);
1022 
1023             layer->scene->push(std::move(scene));
1024 
1025             if (*p == '\0') break;
1026             ++p;
1027 
1028             totalLineSpacing += lineSpacing;
1029             lineSpacing = 0.0f;
1030 
1031             //new text group, single scene for each line
1032             scene = Scene::gen();
1033             cursor.x = 0.0f;
1034             cursor.y = (++line * doc.height + totalLineSpacing) / scale;
1035             continue;
1036         }
1037 
1038         if (*p == ' ') ++space;
1039 
1040         //find the glyph
1041         bool found = false;
1042         for (auto g = text->font->chars.begin(); g < text->font->chars.end(); ++g) {
1043             auto glyph = *g;
1044             //draw matched glyphs
1045             if (!strncmp(glyph->code, p, glyph->len)) {
1046                 auto shape = text->pooling();
1047                 shape->reset();
1048                 for (auto g = glyph->children.begin(); g < glyph->children.end(); ++g) {
1049                     auto group = static_cast<LottieGroup*>(*g);
1050                     for (auto p = group->children.begin(); p < group->children.end(); ++p) {
1051                         if (static_cast<LottiePath*>(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, nullptr, nullptr)) {
1052                             P(shape)->update(RenderUpdateFlag::Path);
1053                         }
1054                     }
1055                 }
1056                 shape->fill(doc.color.rgb[0], doc.color.rgb[1], doc.color.rgb[2]);
1057                 shape->translate(cursor.x, cursor.y);
1058                 shape->opacity(255);
1059 
1060                 if (doc.stroke.render) {
1061                     shape->stroke(StrokeJoin::Round);
1062                     shape->stroke(doc.stroke.width / scale);
1063                     shape->stroke(doc.stroke.color.rgb[0], doc.stroke.color.rgb[1], doc.stroke.color.rgb[2]);
1064                 }
1065 
1066                 if (!text->ranges.empty()) {
1067                     Point scaling = {1.0f, 1.0f};
1068                     auto rotation = 0.0f;
1069                     Point translation = {0.0f, 0.0f};
1070 
1071                     //text range process
1072                     for (auto s = text->ranges.begin(); s < text->ranges.end(); ++s) {
1073                         float start, end;
1074                         (*s)->range(frameNo, float(totalChars), start, end);
1075 
1076                         auto basedIdx = idx;
1077                         if ((*s)->based == LottieTextRange::Based::CharsExcludingSpaces) basedIdx = idx - space;
1078                         else if ((*s)->based == LottieTextRange::Based::Words) basedIdx = line + space;
1079                         else if ((*s)->based == LottieTextRange::Based::Lines) basedIdx = line;
1080 
1081                         if (basedIdx < start || basedIdx >= end) continue;
1082 
1083                         translation = translation + (*s)->style.position(frameNo);
1084                         auto temp = (*s)->style.scale(frameNo);
1085                         scaling.x *= temp.x * 0.01f;
1086                         scaling.y *= temp.y * 0.01f;
1087                         rotation += (*s)->style.rotation(frameNo);
1088 
1089                         shape->opacity((*s)->style.opacity(frameNo));
1090 
1091                         auto color = (*s)->style.fillColor(frameNo);
1092                         shape->fill(color.rgb[0], color.rgb[1], color.rgb[2], (*s)->style.fillOpacity(frameNo));
1093 
1094                         if (doc.stroke.render) {
1095                             auto strokeColor = (*s)->style.strokeColor(frameNo);
1096                             shape->stroke((*s)->style.strokeWidth(frameNo) / scale);
1097                             shape->stroke(strokeColor.rgb[0], strokeColor.rgb[1], strokeColor.rgb[2], (*s)->style.strokeOpacity(frameNo));
1098                         }
1099                         cursor.x += (*s)->style.letterSpacing(frameNo);
1100 
1101                         auto spacing = (*s)->style.lineSpacing(frameNo);
1102                         if (spacing > lineSpacing) lineSpacing = spacing;
1103                     }
1104                     Matrix matrix;
1105                     identity(&matrix);
1106                     translate(&matrix, translation.x / scale + cursor.x, translation.y / scale + cursor.y);
1107                     tvg::scale(&matrix, scaling.x, scaling.y);
1108                     rotate(&matrix, rotation);
1109                     shape->transform(matrix);
1110                 }
1111 
1112                 scene->push(cast(shape));
1113 
1114                 p += glyph->len;
1115                 idx += glyph->len;
1116 
1117                 //advance the cursor position horizontally
1118                 cursor.x += glyph->width + doc.tracking;
1119 
1120                 found = true;
1121                 break;
1122             }
1123         }
1124 
1125         if (!found) {
1126             ++p;
1127             ++idx;
1128         }
1129     }
1130 }
1131 
1132 
updateMaskings(LottieLayer * layer,float frameNo)1133 void LottieBuilder::updateMaskings(LottieLayer* layer, float frameNo)
1134 {
1135     if (layer->masks.count == 0) return;
1136 
1137     //Apply the base mask
1138     auto pMask = static_cast<LottieMask*>(layer->masks[0]);
1139     auto pMethod = pMask->method;
1140     auto opacity = pMask->opacity(frameNo);
1141     auto expand = pMask->expand(frameNo);
1142 
1143     auto pShape = layer->pooling();
1144     pShape->reset();
1145     pShape->fill(255, 255, 255, opacity);
1146     pShape->transform(layer->cache.matrix);
1147 
1148     //Apply Masking Expansion (Offset)
1149     if (expand == 0.0f) {
1150         pMask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, nullptr, nullptr, exps);
1151     } else {
1152         //TODO: Once path direction support is implemented, ensure that the direction is ignored here
1153         auto offset = LottieOffsetModifier(pMask->expand(frameNo));
1154         pMask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, nullptr, &offset, exps);
1155     }
1156 
1157     auto compMethod = (pMethod == CompositeMethod::SubtractMask || pMethod == CompositeMethod::InvAlphaMask) ? CompositeMethod::InvAlphaMask : CompositeMethod::AlphaMask;
1158 
1159     //Cheaper. Replace the masking with a clipper
1160     if (layer->masks.count == 1 && compMethod == CompositeMethod::AlphaMask && opacity == 255) {
1161         layer->scene->clip(tvg::cast(pShape));
1162         return;
1163     }
1164 
1165     //Introduce an intermediate scene for embracing the matte + masking
1166     if (layer->matteTarget) {
1167         auto scene = Scene::gen().release();
1168         scene->push(cast(layer->scene));
1169         layer->scene = scene;
1170     }
1171 
1172     layer->scene->composite(tvg::cast(pShape), compMethod);
1173 
1174     //Apply the subsquent masks
1175     for (auto m = layer->masks.begin() + 1; m < layer->masks.end(); ++m) {
1176         auto mask = static_cast<LottieMask*>(*m);
1177         auto method = mask->method;
1178         if (method == CompositeMethod::None) continue;
1179 
1180         //Append the mask shape
1181         if (pMethod == method && (method == CompositeMethod::SubtractMask || method == CompositeMethod::DifferenceMask)) {
1182             mask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, nullptr, nullptr, exps);
1183         //Chain composition
1184         } else {
1185             auto shape = layer->pooling();
1186             shape->reset();
1187             shape->fill(255, 255, 255, mask->opacity(frameNo));
1188             shape->transform(layer->cache.matrix);
1189             mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, nullptr, nullptr, exps);
1190             pShape->composite(tvg::cast(shape), method);
1191             pShape = shape;
1192             pMethod = method;
1193         }
1194     }
1195 }
1196 
1197 
updateMatte(LottieComposition * comp,float frameNo,Scene * scene,LottieLayer * layer)1198 bool LottieBuilder::updateMatte(LottieComposition* comp, float frameNo, Scene* scene, LottieLayer* layer)
1199 {
1200     auto target = layer->matteTarget;
1201     if (!target) return true;
1202 
1203     updateLayer(comp, scene, target, frameNo);
1204 
1205     if (target->scene) {
1206         layer->scene->composite(cast(target->scene), layer->matteType);
1207     } else if (layer->matteType == CompositeMethod::AlphaMask || layer->matteType == CompositeMethod::LumaMask) {
1208         //matte target is not exist. alpha blending definitely bring an invisible result
1209         delete(layer->scene);
1210         layer->scene = nullptr;
1211         return false;
1212     }
1213     return true;
1214 }
1215 
1216 
updateEffect(LottieLayer * layer,float frameNo)1217 void LottieBuilder::updateEffect(LottieLayer* layer, float frameNo)
1218 {
1219     if (layer->effects.count == 0) return;
1220 
1221     for (auto ef = layer->effects.begin(); ef < layer->effects.end(); ++ef) {
1222         if (!(*ef)->enable) continue;
1223         switch ((*ef)->type) {
1224             case LottieEffect::GaussianBlur: {
1225                 auto effect = static_cast<LottieGaussianBlur*>(*ef);
1226                 layer->scene->push(SceneEffect::GaussianBlur, sqrt(effect->blurness(frameNo)), effect->direction(frameNo) - 1, effect->wrap(frameNo), 25);
1227                 break;
1228             }
1229             default: break;
1230         }
1231     }
1232 }
1233 
1234 
updateLayer(LottieComposition * comp,Scene * scene,LottieLayer * layer,float frameNo)1235 void LottieBuilder::updateLayer(LottieComposition* comp, Scene* scene, LottieLayer* layer, float frameNo)
1236 {
1237     layer->scene = nullptr;
1238 
1239     //visibility
1240     if (frameNo < layer->inFrame || frameNo >= layer->outFrame) return;
1241 
1242     updateTransform(layer, frameNo);
1243 
1244     //full transparent scene. no need to perform
1245     if (layer->type != LottieLayer::Null && layer->cache.opacity == 0) return;
1246 
1247     //Prepare render data
1248     layer->scene = Scene::gen().release();
1249     layer->scene->id = layer->id;
1250 
1251     //ignore opacity when Null layer?
1252     if (layer->type != LottieLayer::Null) layer->scene->opacity(layer->cache.opacity);
1253 
1254     layer->scene->transform(layer->cache.matrix);
1255 
1256     if (!updateMatte(comp, frameNo, scene, layer)) return;
1257 
1258     switch (layer->type) {
1259         case LottieLayer::Precomp: {
1260             updatePrecomp(comp, layer, frameNo);
1261             break;
1262         }
1263         case LottieLayer::Solid: {
1264             updateSolid(layer);
1265             break;
1266         }
1267         case LottieLayer::Image: {
1268             updateImage(layer);
1269             break;
1270         }
1271         case LottieLayer::Text: {
1272             updateText(layer, frameNo);
1273             break;
1274         }
1275         default: {
1276             if (!layer->children.empty()) {
1277                 Inlist<RenderContext> contexts;
1278                 contexts.back(new RenderContext(layer->pooling()));
1279                 updateChildren(layer, frameNo, contexts);
1280                 contexts.free();
1281             }
1282             break;
1283         }
1284     }
1285 
1286     updateMaskings(layer, frameNo);
1287 
1288     layer->scene->blend(layer->blendMethod);
1289 
1290     updateEffect(layer, frameNo);
1291 
1292     //the given matte source was composited by the target earlier.
1293     if (!layer->matteSrc) scene->push(cast(layer->scene));
1294 }
1295 
1296 
_buildReference(LottieComposition * comp,LottieLayer * layer)1297 static void _buildReference(LottieComposition* comp, LottieLayer* layer)
1298 {
1299     for (auto asset = comp->assets.begin(); asset < comp->assets.end(); ++asset) {
1300         if (layer->rid != (*asset)->id) continue;
1301         if (layer->type == LottieLayer::Precomp) {
1302             auto assetLayer = static_cast<LottieLayer*>(*asset);
1303             if (_buildComposition(comp, assetLayer)) {
1304                 layer->children = assetLayer->children;
1305                 layer->reqFragment = assetLayer->reqFragment;
1306             }
1307         } else if (layer->type == LottieLayer::Image) {
1308             layer->children.push(*asset);
1309         }
1310         break;
1311     }
1312 }
1313 
1314 
_buildHierarchy(LottieGroup * parent,LottieLayer * child)1315 static void _buildHierarchy(LottieGroup* parent, LottieLayer* child)
1316 {
1317     if (child->pidx == -1) return;
1318 
1319     if (child->matteTarget && child->pidx == child->matteTarget->idx) {
1320         child->parent = child->matteTarget;
1321         return;
1322     }
1323 
1324     for (auto p = parent->children.begin(); p < parent->children.end(); ++p) {
1325         auto parent = static_cast<LottieLayer*>(*p);
1326         if (child == parent) continue;
1327         if (child->pidx == parent->idx) {
1328             child->parent = parent;
1329             break;
1330         }
1331         if (parent->matteTarget && parent->matteTarget->idx == child->pidx) {
1332             child->parent = parent->matteTarget;
1333             break;
1334         }
1335     }
1336 }
1337 
1338 
_attachFont(LottieComposition * comp,LottieLayer * parent)1339 static void _attachFont(LottieComposition* comp, LottieLayer* parent)
1340 {
1341     //TODO: Consider to migrate this attachment to the frame update time.
1342     for (auto c = parent->children.begin(); c < parent->children.end(); ++c) {
1343         auto text = static_cast<LottieText*>(*c);
1344         auto& doc = text->doc(0);
1345         if (!doc.name) continue;
1346         auto len = strlen(doc.name);
1347         for (uint32_t i = 0; i < comp->fonts.count; ++i) {
1348             auto font = comp->fonts[i];
1349             auto len2 = strlen(font->name);
1350             if (len == len2 && !strcmp(font->name, doc.name)) {
1351                 text->font = font;
1352                 break;
1353             }
1354         }
1355     }
1356 }
1357 
1358 
_buildComposition(LottieComposition * comp,LottieLayer * parent)1359 static bool _buildComposition(LottieComposition* comp, LottieLayer* parent)
1360 {
1361     if (parent->children.count == 0) return false;
1362     if (parent->buildDone) return true;
1363     parent->buildDone = true;
1364 
1365     for (auto c = parent->children.begin(); c < parent->children.end(); ++c) {
1366         auto child = static_cast<LottieLayer*>(*c);
1367 
1368         //attach the precomp layer.
1369         if (child->rid) _buildReference(comp, child);
1370 
1371         if (child->matteType != CompositeMethod::None) {
1372             //no index of the matte layer is provided: the layer above is used as the matte source
1373             if (child->mid == -1) {
1374                 if (c > parent->children.begin()) {
1375                     child->matteTarget = static_cast<LottieLayer*>(*(c - 1));
1376                 }
1377             //matte layer is specified by an index.
1378             } else child->matteTarget = parent->layerByIdx(child->mid);
1379         }
1380 
1381         if (child->matteTarget) {
1382             //parenting
1383             _buildHierarchy(parent, child->matteTarget);
1384             //precomp referencing
1385             if (child->matteTarget->rid) _buildReference(comp, child->matteTarget);
1386         }
1387         _buildHierarchy(parent, child);
1388 
1389         //attach the necessary font data
1390         if (child->type == LottieLayer::Text) _attachFont(comp, child);
1391     }
1392     return true;
1393 }
1394 
1395 
1396 /************************************************************************/
1397 /* External Class Implementation                                        */
1398 /************************************************************************/
1399 
update(LottieComposition * comp,float frameNo)1400 bool LottieBuilder::update(LottieComposition* comp, float frameNo)
1401 {
1402     if (comp->root->children.empty()) return false;
1403 
1404     frameNo += comp->root->inFrame;
1405     if (frameNo <comp->root->inFrame) frameNo = comp->root->inFrame;
1406     if (frameNo >= comp->root->outFrame) frameNo = (comp->root->outFrame - 1);
1407 
1408     //update children layers
1409     auto root = comp->root;
1410     root->scene->clear();
1411 
1412     if (exps && comp->expressions) exps->update(comp->timeAtFrame(frameNo));
1413 
1414     for (auto child = root->children.end() - 1; child >= root->children.begin(); --child) {
1415         auto layer = static_cast<LottieLayer*>(*child);
1416         if (!layer->matteSrc) updateLayer(comp, root->scene, layer, frameNo);
1417     }
1418 
1419     return true;
1420 }
1421 
1422 
build(LottieComposition * comp)1423 void LottieBuilder::build(LottieComposition* comp)
1424 {
1425     if (!comp) return;
1426 
1427     comp->root->scene = Scene::gen().release();
1428 
1429     _buildComposition(comp, comp->root);
1430 
1431     if (!update(comp, 0)) return;
1432 
1433     //viewport clip
1434     auto clip = Shape::gen();
1435     clip->appendRect(0, 0, comp->w, comp->h);
1436     comp->root->scene->clip(std::move(clip));
1437 }
1438 
1439 #endif /* LV_USE_THORVG_INTERNAL */
1440 
1441