2. ThorVG
본격적으로 load api를 따라가면서 ThorVG가 어떻게 lottie를 파싱하는지 알아보자.
대략적인 흐름을 그려보자면 loader->parser->builder 순으로 파이프라인을 타고 있는데 parser는 rapidjson을 이용해 json을 parse하고 composite tree를 만든다. 이 composite tree를 가지고 builder에서 씬을 그린다.
이제 아래에서 코드를 자세히 뜯어보자.
1. load
// wasm/lottie-player/tvgWasmLottieAnimation
bool load(string data, string mimetype, uint32_t width, uint32_t height)
{
errorMsg = NoError;
if (!canvas) return false;
if (data.empty()) {
errorMsg = "Invalid data";
return false;
}
canvas->remove();
delete(animation);
animation = LottieAnimation::gen();
animation->picture()->origin(0.5f, 0.5f); //center-aligned
string filetype = mimetype;
if (filetype == "json") {
filetype = "lottie+json";
}
animation->picture()->resolver(resolver.func, &resolver.data);
if (animation->picture()->load(data.c_str(), data.size(), filetype.c_str()) != Result::Success) {
errorMsg = "load() fail";
return false;
}
animation->picture()->size(&psize[0], &psize[1]);
/* need to reset size to calculate scale in Picture.size internally before calling resize() */
this->width = 0;
this->height = 0;
resize(width, height);
if (canvas->add(animation->picture()) != Result::Success) {
errorMsg = "add() fail";
return false;
}
updated = true;
return true;
}
TvgLottieAnimation에서 노출하는 load의 구현체는 이렇게 되어 있다.
여기서 ThorVG의 코어에 진입하는 코드는 animation->picture()->load(data.c_str(), data.size(), filetype.c_str()) 이부분이다.
인자로 받은 data와 filetype을 load 함수에 넘겨주고 있다.
2. LoadMgr
picture->load는 tvgPicture에서 LoadMgr에 data,size, MIMEType을 넘겨준다.
// renderer/tvgLoaderMgr
static FileType _convert(const char* mimeType) {
if (!strcmp(mimeType, "svg") || !strcmp(mimeType, "svg+xml")) type = FileType::Svg;
else if (!strcmp(mimeType, "ttf") || !strcmp(mimeType, "otf")) type = FileType::Sfnt;
else if (!strcmp(mimeType, "lot") || !strcmp(mimeType, "lottie+json")) type = FileType::Lot;
else if (!strcmp(mimeType, "raw")) ...
else if (!strcmp(mimeType, "png")) ...
else if (!strcmp(mimeType, "jpg") || !strcmp(mimeType, "jpeg")) ...
else if (!strcmp(mimeType, "webp")) ...
}
LoadMgr은 넘겨받은 MIMEType을 기반으로 어떤 Loader를 사용할 지 결정한다. lottie-player에서는 lottie+json을 넘겨주고 있으니 LottieLoader를 사용한다.
3. LottieLoader
LottieLoader의 과정을 자세히 살펴보자 json 파싱은 LottieParser를 이용해 파싱하고 파싱이 끝나면 결과물을 comp에 저장한다. 이 comp를 builder에게 넘겨준다.
// loaders/lottie/tvgLottieLoader
bool LottieLoader::prepare() {
LottieParser parser(content, dirName, builder->expressions());
if (!parser.parse()) return false;
comp = parser.comp;
if (parser.slots) { ... apply(...); }
builder->build(comp); // 파싱 끝 → 빌더 호출
...
}
json을 파싱하는 코드는 아래와 같고 rapidjson을 파서로 사용한다. 결과물은 LottieCompositeTree이고 이는 lottie json을 오브젝트 그래프로 옮긴 것이다.
// loaders/lottie/tvgLottieParser
bool LottieParser::parse() {
if (!parseNext()) return false;
enterObject();
comp = new LottieComposition;
while (auto key = nextObjectKey()) {
if (KEY_AS("v")) comp->version = getStringCopy();
else if (KEY_AS("fr")) comp->frameRate = getFloat();
else if (KEY_AS("ip")) startFrame = getFloat(); // in-point
else if (KEY_AS("op")) endFrame = getFloat(); // out-point
else if (KEY_AS("w")) comp->w = getFloat();
else if (KEY_AS("h")) comp->h = getFloat();
else if (KEY_AS("assets")) parseAssets();
else if (KEY_AS("layers")) comp->root = parseLayers(comp->root);
else if (KEY_AS("fonts")) parseFonts();
else if (KEY_AS("chars")) parseChars(glyphs);
else if (KEY_AS("markers")) parseMarkers();
else if (KEY_AS("slots")) captureSlots(key);
else skip();
}
comp->root->inFrame = startFrame;
comp->root->outFrame = endFrame;
postProcess(glyphs);
return true;
}
4. LottieBuilder
빌더에서는 LottieCompositeTree에서 ThorVG 렌더러 오브젝트로 변환한다. root에 Scene을 넣어주고 ThorVG의 Shape, Text, Picture 등으로 씬을 그린다.
// loader/lottie/tvgLottieBuilder
void LottieBuilder::build(LottieComposition* comp) {
comp->root->scene = Scene::gen(); // thorvg Scene 생성
_buildComposition(comp, comp->root); // 재귀적 자식 레이어 → Scene/Shape/Text 매핑
auto clip = Shape::gen();
clip->appendRect(0, 0, comp->w, comp->h);
comp->root->scene->clip(clip); // viewport clip
to<SceneImpl>(comp->root->scene)->size({comp->w, comp->h});
}
3. update, render, draw
이렇게 만들어진 tvg 데이터들을 picture가 갖고 있고 load 작업이 끝나면 canvas->add(animation->picture())를 통해 캔버스에 추가한다.
그리고 프레임이 바뀌면 update, render(draw, sync) 작업을 순서대로 진행한다.
base-lottie-player에서 play를 호출하면 rAF를 이용해서 update에 새로운 프레임 number를 넣어준다. 그러면 canvas->update를 호출해서 씬을 갱신한다. 그 후 render를 호출하면 canvas->draw, canvas->sync를 차례대로 호출하여 씬을 그리고 canvas에 동기화 한다.
이러한 방식을 통해 lottie json을 화면에 재생하게 되는 것이다.
댓글
Discussion 원문