/* * Copyright (c) 2025 the ThorVG project. All rights reserved. * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include "Example.h" /************************************************************************/ /* ThorVG Drawing Contents */ /************************************************************************/ struct UserExample : tvgexam::Example { tvg::LottieAnimation* lottie; //designed the states: [angry, sad, mourn, wink, laughing] struct AnimState { std::string name; //state name float begin; //state begin frame number }; std::vector states; //states list int stateIdx = 0; //current state index struct { float from; //tweening from frame number float to; //tweening to frame number float beginTime; //tweening begin time bool active = false; //whether on-tweening or not } tween; ~UserExample() { delete(lottie); } void init() { //get the AnimState info (state name and its begin frame number) for (uint32_t i = 0; i < lottie->markersCnt(); ++i) { //specify the current segment to retrieve the segment's starting frame. float begin; auto name = lottie->marker(i); lottie->segment(name); static_cast(lottie)->segment(&begin); //save the current AnimState to the state list states.push_back({string(name), begin}); } //set the default state (Angry) lottie->segment(states[stateIdx].name.c_str()); } //stateIdx is the next desired state void tweening(int stateIdx) { //don't allow the overlapped tweening if (tween.active || stateIdx == this->stateIdx) return; //reset the current state lottie->segment(nullptr); //tweening trigger time tween.beginTime = timestamp(); //the current animation frame as the tweening "from" frame tween.from = lottie->curFrame(); //the next state begin frame as the tweening "to" frame tween.to = states[stateIdx].begin; tween.active = true; this->stateIdx = stateIdx; cout << "tween to: " << states[stateIdx].name << endl; } bool clicked(tvg::Canvas* canvas, int32_t x, int32_t y) override { int i = 0; for (auto& state : states) { if (auto paint = lottie->picture()->paint(tvg::Accessor::id(state.name.c_str()))) { float px, py, pw, ph; paint->bounds(&px, &py, &pw, &ph, true); //hit a emoji layer! if (x >= px && x <= px + pw && y >= py && y <= py + ph) { tweening(i); return true; } } ++i; } return false; } bool content(tvg::Canvas* canvas, uint32_t w, uint32_t h) override { //Animation Controller lottie = tvg::LottieAnimation::gen(); auto picture = lottie->picture(); //Background auto shape = tvg::Shape::gen(); shape->appendRect(0, 0, w, h); shape->fill(50, 50, 50); canvas->push(shape); if (!tvgexam::verify(picture->load(EXAMPLE_DIR"/lottie/emoji.json"))) return false; //image scaling preserving its aspect ratio float scale; float shiftX = 0.0f, shiftY = 0.0f; float w2, h2; picture->size(&w2, &h2); if (w2 > h2) { scale = w / w2; shiftY = (h - h2 * scale) * 0.5f; } else { scale = h / h2; shiftX = (w - w2 * scale) * 0.5f; } picture->scale(scale); picture->translate(shiftX, shiftY); canvas->push(picture); init(); return true; } bool tweening(tvg::Canvas* canvas) { //perform tweening for 0.25 seconds. //in this sample, we use linear interpolation. You can vary the progress //with a specific interpolation style (e.g., sine, cosine, or spring curves). auto progress = (timestamp() - tween.beginTime) / 0.25f; //perform the tweening effect if (progress < 1.0f) { if (lottie->tween(tween.from, tween.to, progress) == tvg::Result(0)) { canvas->update(); return true; } //tweening is over, set to the desired state } else { lottie->segment(states[stateIdx].name.c_str()); tween.active = false; elapsed = 0; //tweening is over, start to the desired state play if (lottie->frame(0) == tvg::Result(0)) { canvas->update(); return true; } } return false; } bool update(tvg::Canvas* canvas, uint32_t elapsed) override { if (!canvas) return false; //on state tweening if (tween.active) return tweening(canvas); //play the current state auto progress = tvgexam::progress(elapsed, lottie->duration()); //Update animation frame only when it's changed if (lottie->frame(lottie->totalFrame() * progress) == tvg::Result(0)) { canvas->update(); return true; } return false; } }; /************************************************************************/ /* Entry Point */ /************************************************************************/ int main(int argc, char **argv) { return tvgexam::main(new UserExample, argc, argv, false, 1024, 1024); }