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