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 "tvgMath.h"
27 #include "tvgPaint.h"
28 #include "tvgFill.h"
29 #include "tvgTaskScheduler.h"
30 #include "tvgLottieModel.h"
31 
32 
33 /************************************************************************/
34 /* Internal Class Implementation                                        */
35 /************************************************************************/
36 
37 
38 
39 /************************************************************************/
40 /* External Class Implementation                                        */
41 /************************************************************************/
42 
reset()43 void LottieSlot::reset()
44 {
45     if (!overridden) return;
46 
47     for (auto pair = pairs.begin(); pair < pairs.end(); ++pair) {
48         switch (type) {
49             case LottieProperty::Type::ColorStop: {
50                 static_cast<LottieGradient*>(pair->obj)->colorStops.release();
51                 static_cast<LottieGradient*>(pair->obj)->colorStops = *static_cast<LottieColorStop*>(pair->prop);
52                 static_cast<LottieColorStop*>(pair->prop)->frames = nullptr;
53                 break;
54             }
55             case LottieProperty::Type::Color: {
56                 static_cast<LottieSolid*>(pair->obj)->color.release();
57                 static_cast<LottieSolid*>(pair->obj)->color = *static_cast<LottieColor*>(pair->prop);
58                 static_cast<LottieColor*>(pair->prop)->frames = nullptr;
59                 break;
60             }
61             case LottieProperty::Type::TextDoc: {
62                 static_cast<LottieText*>(pair->obj)->doc.release();
63                 static_cast<LottieText*>(pair->obj)->doc = *static_cast<LottieTextDoc*>(pair->prop);
64                 static_cast<LottieTextDoc*>(pair->prop)->frames = nullptr;
65                 break;
66             }
67             default: break;
68         }
69         delete(pair->prop);
70         pair->prop = nullptr;
71     }
72     overridden = false;
73 }
74 
75 
assign(LottieObject * target)76 void LottieSlot::assign(LottieObject* target)
77 {
78     //apply slot object to all targets
79     for (auto pair = pairs.begin(); pair < pairs.end(); ++pair) {
80         //backup the original properties before overwriting
81         switch (type) {
82             case LottieProperty::Type::ColorStop: {
83                 if (!overridden) {
84                     pair->prop = new LottieColorStop;
85                     *static_cast<LottieColorStop*>(pair->prop) = static_cast<LottieGradient*>(pair->obj)->colorStops;
86                 }
87 
88                 pair->obj->override(&static_cast<LottieGradient*>(target)->colorStops);
89                 break;
90             }
91             case LottieProperty::Type::Color: {
92                 if (!overridden) {
93                     pair->prop = new LottieColor;
94                     *static_cast<LottieColor*>(pair->prop) = static_cast<LottieSolid*>(pair->obj)->color;
95                 }
96 
97                 pair->obj->override(&static_cast<LottieSolid*>(target)->color);
98                 break;
99             }
100             case LottieProperty::Type::TextDoc: {
101                 if (!overridden) {
102                     pair->prop = new LottieTextDoc;
103                     *static_cast<LottieTextDoc*>(pair->prop) = static_cast<LottieText*>(pair->obj)->doc;
104                 }
105 
106                 pair->obj->override(&static_cast<LottieText*>(target)->doc);
107                 break;
108             }
109             default: break;
110         }
111     }
112     overridden = true;
113 }
114 
115 
range(float frameNo,float totalLen,float & start,float & end)116 void LottieTextRange::range(float frameNo, float totalLen, float& start, float& end)
117 {
118     auto divisor = (rangeUnit == Unit::Percent) ? (100.0f / totalLen) : 1.0f;
119     auto offset = this->offset(frameNo) / divisor;
120     start = nearbyintf(this->start(frameNo) / divisor) + offset;
121     end = nearbyintf(this->end(frameNo) / divisor) + offset;
122 
123     if (start > end) std::swap(start, end);
124 
125     if (random == 0) return;
126 
127     auto range = end - start;
128     auto len = (rangeUnit == Unit::Percent) ? 100.0f : totalLen;
129     start = static_cast<float>(random % int(len - range));
130     end = start + range;
131 }
132 
133 
~LottieImage()134 LottieImage::~LottieImage()
135 {
136     free(b64Data);
137     free(mimeType);
138 }
139 
140 
prepare()141 void LottieImage::prepare()
142 {
143     LottieObject::type = LottieObject::Image;
144 
145     auto picture = Picture::gen().release();
146 
147     //force to load a picture on the same thread
148     TaskScheduler::async(false);
149 
150     if (size > 0) picture->load((const char*)b64Data, size, mimeType, false);
151     else picture->load(path);
152 
153     TaskScheduler::async(true);
154 
155     picture->size(width, height);
156     PP(picture)->ref();
157 
158     pooler.push(picture);
159 }
160 
161 
segment(float frameNo,float & start,float & end,LottieExpressions * exps)162 void LottieTrimpath::segment(float frameNo, float& start, float& end, LottieExpressions* exps)
163 {
164     start = this->start(frameNo, exps) * 0.01f;
165     tvg::clamp(start, 0.0f, 1.0f);
166     end = this->end(frameNo, exps) * 0.01f;
167     tvg::clamp(end, 0.0f, 1.0f);
168 
169     auto o = fmodf(this->offset(frameNo, exps), 360.0f) / 360.0f;  //0 ~ 1
170 
171     auto diff = fabs(start - end);
172     if (tvg::zero(diff)) {
173         start = 0.0f;
174         end = 0.0f;
175         return;
176     }
177     if (tvg::equal(diff, 1.0f) || tvg::equal(diff, 2.0f)) {
178         start = 0.0f;
179         end = 1.0f;
180         return;
181     }
182 
183     if (start > end) std::swap(start, end);
184     start += o;
185     end += o;
186 }
187 
188 
populate(ColorStop & color,size_t count)189 uint32_t LottieGradient::populate(ColorStop& color, size_t count)
190 {
191     if (!color.input) return 0;
192 
193     uint32_t alphaCnt = (color.input->count - (count * 4)) / 2;
194     Array<Fill::ColorStop> output(count + alphaCnt);
195     uint32_t cidx = 0;               //color count
196     uint32_t clast = count * 4;
197     if (clast > color.input->count) clast = color.input->count;
198     uint32_t aidx = clast;           //alpha count
199     Fill::ColorStop cs;
200 
201     //merge color stops.
202     for (uint32_t i = 0; i < color.input->count; ++i) {
203         if (cidx == clast || aidx == color.input->count) break;
204         if ((*color.input)[cidx] == (*color.input)[aidx]) {
205             cs.offset = (*color.input)[cidx];
206             cs.r = (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f);
207             cs.g = (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f);
208             cs.b = (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f);
209             cs.a = (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f);
210             cidx += 4;
211             aidx += 2;
212         } else if ((*color.input)[cidx] < (*color.input)[aidx]) {
213             cs.offset = (*color.input)[cidx];
214             cs.r = (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f);
215             cs.g = (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f);
216             cs.b = (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f);
217             //generate alpha value
218             if (output.count > 0) {
219                 auto p = ((*color.input)[cidx] - output.last().offset) / ((*color.input)[aidx] - output.last().offset);
220                 cs.a = lerp<uint8_t>(output.last().a, (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f), p);
221             } else cs.a = (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f);
222             cidx += 4;
223         } else {
224             cs.offset = (*color.input)[aidx];
225             cs.a = (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f);
226             //generate color value
227             if (output.count > 0) {
228                 auto p = ((*color.input)[aidx] - output.last().offset) / ((*color.input)[cidx] - output.last().offset);
229                 cs.r = lerp<uint8_t>(output.last().r, (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f), p);
230                 cs.g = lerp<uint8_t>(output.last().g, (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f), p);
231                 cs.b = lerp<uint8_t>(output.last().b, (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f), p);
232             } else {
233                 cs.r = (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f);
234                 cs.g = (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f);
235                 cs.b = (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f);
236             }
237             aidx += 2;
238         }
239         output.push(cs);
240     }
241 
242     //color remains
243     while (cidx + 3 < clast) {
244         cs.offset = (*color.input)[cidx];
245         cs.r = (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f);
246         cs.g = (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f);
247         cs.b = (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f);
248         cs.a = (output.count > 0) ? output.last().a : 255;
249         output.push(cs);
250         cidx += 4;
251     }
252 
253     //alpha remains
254     while (aidx < color.input->count) {
255         cs.offset = (*color.input)[aidx];
256         cs.a = (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f);
257         if (output.count > 0) {
258             cs.r = output.last().r;
259             cs.g = output.last().g;
260             cs.b = output.last().b;
261         } else cs.r = cs.g = cs.b = 255;
262         output.push(cs);
263         aidx += 2;
264     }
265 
266     color.data = output.data;
267     output.data = nullptr;
268 
269     color.input->reset();
270     delete(color.input);
271 
272     return output.count;
273 }
274 
275 
fill(float frameNo,LottieExpressions * exps)276 Fill* LottieGradient::fill(float frameNo, LottieExpressions* exps)
277 {
278     auto opacity = this->opacity(frameNo);
279     if (opacity == 0) return nullptr;
280 
281     Fill* fill = nullptr;
282     auto s = start(frameNo, exps);
283     auto e = end(frameNo, exps);
284 
285     //Linear Graident
286     if (id == 1) {
287         fill = LinearGradient::gen().release();
288         static_cast<LinearGradient*>(fill)->linear(s.x, s.y, e.x, e.y);
289     }
290     //Radial Gradient
291     if (id == 2) {
292         fill = RadialGradient::gen().release();
293 
294         auto w = fabsf(e.x - s.x);
295         auto h = fabsf(e.y - s.y);
296         auto r = (w > h) ? (w + 0.375f * h) : (h + 0.375f * w);
297         auto progress = this->height(frameNo, exps) * 0.01f;
298 
299         if (tvg::zero(progress)) {
300             P(static_cast<RadialGradient*>(fill))->radial(s.x, s.y, r, s.x, s.y, 0.0f);
301         } else {
302             if (tvg::equal(progress, 1.0f)) progress = 0.99f;
303             auto startAngle = rad2deg(tvg::atan2(e.y - s.y, e.x - s.x));
304             auto angle = deg2rad((startAngle + this->angle(frameNo, exps)));
305             auto fx = s.x + cos(angle) * progress * r;
306             auto fy = s.y + sin(angle) * progress * r;
307             // Lottie doesn't have any focal radius concept
308             P(static_cast<RadialGradient*>(fill))->radial(s.x, s.y, r, fx, fy, 0.0f);
309         }
310     }
311 
312     if (!fill) return nullptr;
313 
314     colorStops(frameNo, fill, exps);
315 
316     //multiply the current opacity with the fill
317     if (opacity < 255) {
318         const Fill::ColorStop* colorStops;
319         auto cnt = fill->colorStops(&colorStops);
320         for (uint32_t i = 0; i < cnt; ++i) {
321             const_cast<Fill::ColorStop*>(&colorStops[i])->a = MULTIPLY(colorStops[i].a, opacity);
322         }
323     }
324 
325     return fill;
326 }
327 
328 
LottieGroup()329 LottieGroup::LottieGroup()
330 {
331     reqFragment = false;
332     buildDone = false;
333     trimpath = false;
334     visible = false;
335     allowMerge = true;
336 }
337 
338 
prepare(LottieObject::Type type)339 void LottieGroup::prepare(LottieObject::Type type)
340 {
341     LottieObject::type = type;
342 
343     if (children.count == 0) return;
344 
345     size_t strokeCnt = 0;
346     size_t fillCnt = 0;
347 
348     for (auto c = children.end() - 1; c >= children.begin(); --c) {
349         auto child = static_cast<LottieObject*>(*c);
350 
351         if (child->type == LottieObject::Type::Trimpath) trimpath = true;
352 
353         /* Figure out if this group is a simple path drawing.
354            In that case, the rendering context can be sharable with the parent's. */
355         if (allowMerge && (child->type == LottieObject::Group || !child->mergeable())) allowMerge = false;
356 
357         //Figure out this group has visible contents
358         switch (child->type) {
359             case LottieObject::Group: {
360                 visible |= static_cast<LottieGroup*>(child)->visible;
361                 break;
362             }
363             case LottieObject::Rect:
364             case LottieObject::Ellipse:
365             case LottieObject::Path:
366             case LottieObject::Polystar:
367             case LottieObject::Image:
368             case LottieObject::Text: {
369                 visible = true;
370                 break;
371             }
372             default: break;
373         }
374 
375         if (reqFragment) continue;
376 
377         /* Figure out if the rendering context should be fragmented.
378            Multiple stroking or grouping with a stroking would occur this.
379            This fragment resolves the overlapped stroke outlines. */
380         if (child->type == LottieObject::Group && !child->mergeable()) {
381             if (strokeCnt > 0 || fillCnt > 0) reqFragment = true;
382         } else if (child->type == LottieObject::SolidStroke || child->type == LottieObject::GradientStroke) {
383             if (strokeCnt > 0) reqFragment = true;
384             else ++strokeCnt;
385         } else if (child->type == LottieObject::SolidFill || child->type == LottieObject::GradientFill) {
386             if (fillCnt > 0) reqFragment = true;
387             else ++fillCnt;
388         }
389     }
390 
391     //Reverse the drawing order if this group has a trimpath.
392     if (!trimpath) return;
393 
394     for (uint32_t i = 0; i < children.count - 1; ) {
395         auto child2 = children[i + 1];
396         if (!child2->mergeable() || child2->type == LottieObject::Transform) {
397             i += 2;
398             continue;
399         }
400         auto child = children[i];
401         if (!child->mergeable() || child->type == LottieObject::Transform) {
402             i++;
403             continue;
404         }
405         children[i] = child2;
406         children[i + 1] = child;
407         i++;
408     }
409 }
410 
411 
~LottieLayer()412 LottieLayer::~LottieLayer()
413 {
414     //No need to free assets children because the Composition owns them.
415     if (rid) children.clear();
416 
417     for (auto m = masks.begin(); m < masks.end(); ++m) {
418         delete(*m);
419     }
420 
421     for (auto e = effects.begin(); e < effects.end(); ++e) {
422         delete(*e);
423     }
424 
425     delete(transform);
426     free(name);
427 }
428 
429 
prepare(RGB24 * color)430 void LottieLayer::prepare(RGB24* color)
431 {
432     /* if layer is hidden, only useful data is its transform matrix.
433        so force it to be a Null Layer and release all resource. */
434     if (hidden) {
435         type = LottieLayer::Null;
436         for (auto p = children.begin(); p < children.end(); ++p) delete(*p);
437         children.reset();
438         return;
439     }
440 
441     //prepare the viewport clipper
442     if (type == LottieLayer::Precomp) {
443         auto clipper = Shape::gen().release();
444         clipper->appendRect(0.0f, 0.0f, w, h);
445         PP(clipper)->ref();
446         statical.pooler.push(clipper);
447     //prepare solid fill in advance if it is a layer type.
448     } else if (color && type == LottieLayer::Solid) {
449         auto solidFill = Shape::gen().release();
450         solidFill->appendRect(0, 0, static_cast<float>(w), static_cast<float>(h));
451         solidFill->fill(color->rgb[0], color->rgb[1], color->rgb[2]);
452         PP(solidFill)->ref();
453         statical.pooler.push(solidFill);
454     }
455 
456     LottieGroup::prepare(LottieObject::Layer);
457 }
458 
459 
remap(LottieComposition * comp,float frameNo,LottieExpressions * exp)460 float LottieLayer::remap(LottieComposition* comp, float frameNo, LottieExpressions* exp)
461 {
462     if (timeRemap.frames || timeRemap.value) {
463         frameNo = comp->frameAtTime(timeRemap(frameNo, exp));
464     } else {
465         frameNo -= startFrame;
466     }
467     return (frameNo / timeStretch);
468 }
469 
470 
~LottieComposition()471 LottieComposition::~LottieComposition()
472 {
473     if (!initiated && root) delete(root->scene);
474 
475     delete(root);
476     free(version);
477     free(name);
478 
479     //delete interpolators
480     for (auto i = interpolators.begin(); i < interpolators.end(); ++i) {
481         free((*i)->key);
482         free(*i);
483     }
484 
485     //delete assets
486     for (auto a = assets.begin(); a < assets.end(); ++a) {
487         delete(*a);
488     }
489 
490     //delete fonts
491     for (auto f = fonts.begin(); f < fonts.end(); ++f) {
492         delete(*f);
493     }
494 
495     //delete slots
496     for (auto s = slots.begin(); s < slots.end(); ++s) {
497         delete(*s);
498     }
499 
500     for (auto m = markers.begin(); m < markers.end(); ++m) {
501         delete(*m);
502     }
503 }
504 
505 #endif /* LV_USE_THORVG_INTERNAL */
506 
507