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 "tvgLottieLoader.h"
27 #include "tvgLottieModel.h"
28 #include "tvgLottieParser.h"
29 #include "tvgLottieBuilder.h"
30 #include "tvgStr.h"
31 
32 /************************************************************************/
33 /* Internal Class Implementation                                        */
34 /************************************************************************/
35 
run(unsigned tid)36 void LottieLoader::run(unsigned tid)
37 {
38     //update frame
39     if (comp) {
40         builder->update(comp, frameNo);
41     //initial loading
42     } else {
43         LottieParser parser(content, dirName);
44         if (!parser.parse()) return;
45         {
46             ScopedLock lock(key);
47             comp = parser.comp;
48         }
49         builder->build(comp);
50 
51         release();
52     }
53     rebuild = false;
54 }
55 
56 
release()57 void LottieLoader::release()
58 {
59     if (copy) {
60         free((char*)content);
61         content = nullptr;
62     }
63     free(dirName);
64     dirName = nullptr;
65 }
66 
67 
68 /************************************************************************/
69 /* External Class Implementation                                        */
70 /************************************************************************/
71 
LottieLoader()72 LottieLoader::LottieLoader() : FrameModule(FileType::Lottie), builder(new LottieBuilder)
73 {
74 
75 }
76 
77 
~LottieLoader()78 LottieLoader::~LottieLoader()
79 {
80     done();
81 
82     release();
83 
84     //TODO: correct position?
85     delete(comp);
86     delete(builder);
87 }
88 
89 
header()90 bool LottieLoader::header()
91 {
92     //A single thread doesn't need to perform intensive tasks.
93     if (TaskScheduler::threads() == 0) {
94         LoadModule::read();
95         run(0);
96         if (comp) {
97             w = static_cast<float>(comp->w);
98             h = static_cast<float>(comp->h);
99             frameDuration = comp->duration();
100             frameCnt = comp->frameCnt();
101             frameRate = comp->frameRate;
102             return true;
103         } else {
104             return false;
105         }
106     }
107 
108     //Quickly validate the given Lottie file without parsing in order to get the animation info.
109     auto startFrame = 0.0f;
110     auto endFrame = 0.0f;
111     uint32_t depth = 0;
112 
113     auto p = content;
114 
115     while (*p != '\0') {
116         if (*p == '{') {
117             ++depth;
118             ++p;
119             continue;
120         }
121         if (*p == '}') {
122             --depth;
123             ++p;
124             continue;
125         }
126         if (depth != 1) {
127             ++p;
128             continue;
129         }
130         //version.
131         if (!strncmp(p, "\"v\":", 4)) {
132             p += 4;
133             continue;
134         }
135 
136         //framerate
137         if (!strncmp(p, "\"fr\":", 5)) {
138             p += 5;
139             auto e = strstr(p, ",");
140             if (!e) e = strstr(p, "}");
141             frameRate = strToFloat(p, nullptr);
142             p = e;
143             continue;
144         }
145 
146         //start frame
147         if (!strncmp(p, "\"ip\":", 5)) {
148             p += 5;
149             auto e = strstr(p, ",");
150             if (!e) e = strstr(p, "}");
151             startFrame = strToFloat(p, nullptr);
152             p = e;
153             continue;
154         }
155 
156         //end frame
157         if (!strncmp(p, "\"op\":", 5)) {
158             p += 5;
159             auto e = strstr(p, ",");
160             if (!e) e = strstr(p, "}");
161             endFrame = strToFloat(p, nullptr);
162             p = e;
163             continue;
164         }
165 
166         //width
167         if (!strncmp(p, "\"w\":", 4)) {
168             p += 4;
169             auto e = strstr(p, ",");
170             if (!e) e = strstr(p, "}");
171             w = strToFloat(p, nullptr);
172             p = e;
173             continue;
174         }
175         //height
176         if (!strncmp(p, "\"h\":", 4)) {
177             p += 4;
178             auto e = strstr(p, ",");
179             if (!e) e = strstr(p, "}");
180             h = strToFloat(p, nullptr);
181             p = e;
182             continue;
183         }
184         ++p;
185     }
186 
187     if (frameRate < FLOAT_EPSILON) {
188         TVGLOG("LOTTIE", "Not a Lottie file? Frame rate is 0!");
189         return false;
190     }
191 
192     frameCnt = (endFrame - startFrame);
193     frameDuration = frameCnt / frameRate;
194 
195     TVGLOG("LOTTIE", "info: frame rate = %f, duration = %f size = %f x %f", frameRate, frameDuration, w, h);
196 
197     return true;
198 }
199 
200 
open(const char * data,uint32_t size,bool copy)201 bool LottieLoader::open(const char* data, uint32_t size, bool copy)
202 {
203     if (copy) {
204         content = (char*)malloc(size + 1);
205         if (!content) return false;
206         memcpy((char*)content, data, size);
207         const_cast<char*>(content)[size] = '\0';
208     } else content = data;
209 
210     this->dirName = strdup(".");
211 
212     this->size = size;
213     this->copy = copy;
214 
215     return header();
216 }
217 
218 
open(const string & path)219 bool LottieLoader::open(const string& path)
220 {
221     auto f = fopen(path.c_str(), "r");
222     if (!f) return false;
223 
224     fseek(f, 0, SEEK_END);
225 
226     size = ftell(f);
227     if (size == 0) {
228         fclose(f);
229         return false;
230     }
231 
232     auto content = (char*)(malloc(sizeof(char) * size + 1));
233     fseek(f, 0, SEEK_SET);
234     auto ret = fread(content, sizeof(char), size, f);
235     if (ret < size) {
236         fclose(f);
237         return false;
238     }
239     content[size] = '\0';
240 
241     fclose(f);
242 
243     this->dirName = strDirname(path.c_str());
244     this->content = content;
245     this->copy = true;
246 
247     return header();
248 }
249 
250 
resize(Paint * paint,float w,float h)251 bool LottieLoader::resize(Paint* paint, float w, float h)
252 {
253     if (!paint) return false;
254 
255     auto sx = w / this->w;
256     auto sy = h / this->h;
257     Matrix m = {sx, 0, 0, 0, sy, 0, 0, 0, 1};
258     paint->transform(m);
259 
260     //apply the scale to the base clipper
261     const Paint* clipper;
262     paint->composite(&clipper);
263     if (clipper) const_cast<Paint*>(clipper)->transform(m);
264 
265     return true;
266 }
267 
268 
read()269 bool LottieLoader::read()
270 {
271     //the loading has been already completed
272     if (!LoadModule::read()) return true;
273 
274     if (!content || size == 0) return false;
275 
276     TaskScheduler::request(this);
277 
278     return true;
279 }
280 
281 
paint()282 Paint* LottieLoader::paint()
283 {
284     done();
285 
286     if (!comp) return nullptr;
287     comp->initiated = true;
288     return comp->root->scene;
289 }
290 
291 
override(const char * slot)292 bool LottieLoader::override(const char* slot)
293 {
294     if (!ready() || comp->slots.count == 0) return false;
295 
296     auto success = true;
297 
298     //override slots
299     if (slot) {
300         //Copy the input data because the JSON parser will encode the data immediately.
301         auto temp = strdup(slot);
302 
303         //parsing slot json
304         LottieParser parser(temp, dirName);
305         parser.comp = comp;
306 
307         auto idx = 0;
308         while (auto sid = parser.sid(idx == 0)) {
309             for (auto s = comp->slots.begin(); s < comp->slots.end(); ++s) {
310                 if (strcmp((*s)->sid, sid)) continue;
311                 if (!parser.apply(*s)) success = false;
312                 break;
313             }
314             ++idx;
315         }
316 
317         if (idx < 1) success = false;
318         free(temp);
319         rebuild = overridden = success;
320     //reset slots
321     } else if (overridden) {
322         for (auto s = comp->slots.begin(); s < comp->slots.end(); ++s) {
323             (*s)->reset();
324         }
325         overridden = false;
326         rebuild = true;
327     }
328     return success;
329 }
330 
331 
frame(float no)332 bool LottieLoader::frame(float no)
333 {
334     auto frameNo = no + startFrame();
335 
336     //This ensures that the target frame number is reached.
337     frameNo *= 10000.0f;
338     frameNo = nearbyintf(frameNo);
339     frameNo *= 0.0001f;
340 
341     //Skip update if frame diff is too small.
342     if (fabsf(this->frameNo - frameNo) <= 0.0009f) return false;
343 
344     this->done();
345 
346     this->frameNo = frameNo;
347 
348     TaskScheduler::request(this);
349 
350     return true;
351 }
352 
353 
startFrame()354 float LottieLoader::startFrame()
355 {
356     return frameCnt * segmentBegin;
357 }
358 
359 
totalFrame()360 float LottieLoader::totalFrame()
361 {
362     return (segmentEnd - segmentBegin) * frameCnt;
363 }
364 
365 
curFrame()366 float LottieLoader::curFrame()
367 {
368     return frameNo - startFrame();
369 }
370 
371 
duration()372 float LottieLoader::duration()
373 {
374     if (segmentBegin == 0.0f && segmentEnd == 1.0f) return frameDuration;
375     return frameCnt * (segmentEnd - segmentBegin) / frameRate;
376 }
377 
378 
sync()379 void LottieLoader::sync()
380 {
381     done();
382 
383     if (rebuild) run(0);
384 }
385 
386 
markersCnt()387 uint32_t LottieLoader::markersCnt()
388 {
389     return ready() ? comp->markers.count : 0;
390 }
391 
392 
markers(uint32_t index)393 const char* LottieLoader::markers(uint32_t index)
394 {
395     if (!ready() || index >= comp->markers.count) return nullptr;
396     auto marker = comp->markers.begin() + index;
397     return (*marker)->name;
398 }
399 
400 
segment(const char * marker,float & begin,float & end)401 bool LottieLoader::segment(const char* marker, float& begin, float& end)
402 {
403     if (!ready() || comp->markers.count == 0) return false;
404 
405     for (auto m = comp->markers.begin(); m < comp->markers.end(); ++m) {
406         if (!strcmp(marker, (*m)->name)) {
407             begin = (*m)->time / frameCnt;
408             end = ((*m)->time + (*m)->duration) / frameCnt;
409             return true;
410         }
411     }
412     return false;
413 }
414 
415 
ready()416 bool LottieLoader::ready()
417 {
418     {
419         ScopedLock lock(key);
420         if (comp) return true;
421     }
422     done();
423     if (comp) return true;
424     return false;
425 }
426 
427 #endif /* LV_USE_THORVG_INTERNAL */
428 
429