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