rlottie is a platform independent standalone c++ library for rendering vector based animations and art in realtime

Related tags

Graphics rlottie
Overview

rlottie

Build Status Build status Gitter

rlottie is a platform independent standalone c++ library for rendering vector based animations and art in realtime.

Lottie loads and renders animations and vectors exported in the bodymovin JSON format. Bodymovin JSON can be created and exported from After Effects with bodymovin, Sketch with Lottie Sketch Export, and from Haiku.

For the first time, designers can create and ship beautiful animations without an engineer painstakingly recreating it by hand. Since the animation is backed by JSON they are extremely small in size but can be large in complexity!

Here are small samples of the power of Lottie.

Contents

Building Lottie

rlottie supports meson and cmake build system. rlottie is written in C++14. and has a public header dependency of C++11

Meson Build

install meson and ninja if not already installed.

Run meson to configure rlottie

meson build

Run ninja to build rlottie

ninja -C build

Cmake Build

Install cmake if not already installed

Create a build directory for out of source build

mkdir build

Run cmake command inside build directory to configure rlottie.

cd build
cmake ..

# install in a different path. eg ~/test/usr/lib
cmake -DCMAKE_INSTALL_PREFIX=~/test ..

# static build
cmake -DBUILD_SHARED_LIBS=OFF ..

Run make to build rlottie

make -j 2

To install rlottie library

make install

Test

Configure to build test

meson configure -Dtest=true

Build test suit

ninja

Run test suit

ninja test

Back to contents

Demo

If you want to see rlottie library in action without building it please visit rlottie online viewer

While building rlottie library it generates a simple lottie to GIF converter which can be used to convert lottie json file to GIF file.

Run Demo

lottie2gif [lottie file name]

Previewing Lottie JSON Files

Please visit rlottie online viewer

rlottie online viewer uses rlottie wasm library to render the resource locally in your browser. To test your JSON resource drag and drop it to the browser window.

Quick Start

Lottie loads and renders animations and vectors exported in the bodymovin JSON format. Bodymovin JSON can be created and exported from After Effects with bodymovin, Sketch with Lottie Sketch Export, and from Haiku.

You can quickly load a Lottie animation with:

auto animation = rlottie::Animation::loadFromFile("absolute_path/test.json");

You can load a lottie animation from raw data with:

auto animation = rlottie::Animation::loadFromData(std::string(rawData), std::string(cacheKey));

Properties like frameRate , totalFrame , duration can be queried with:

# get the frame rate of the resource.
double frameRate = animation->frameRate();

#get total frame that exists in the resource
size_t totalFrame = animation->totalFrame();

#get total animation duration in sec for the resource
double duration = animation->duration();

Render a particular frame in a surface buffer immediately with:

rlottie::Surface surface(buffer, width , height , stride);
animation->renderSync(frameNo, surface);

Render a particular frame in a surface buffer asyncronousely with:

rlottie::Surface surface(buffer, width , height , stride);
# give a render request
std::future
    handle = animation->
   render(frameNo, surface);
...
#when the render data is needed
rlottie::Surface surface = handle.get();
  

Back to contents

Dynamic Property

You can update properties dynamically at runtime. This can be used for a variety of purposes such as:

  • Theming (day and night or arbitrary themes).
  • Responding to events such as an error or a success.
  • Animating a single part of an animation in response to an event.
  • Responding to view sizes or other values not known at design time.

Understanding After Effects

To understand how to change animation properties in Lottie, you should first understand how animation properties are stored in Lottie. Animation properties are stored in a data tree that mimics the information hierarchy of After Effects. In After Effects a Composition is a collection of Layers that each have their own timelines. Layer objects have string names, and their contents can be an image, shape layers, fills, strokes, or just about anything that is drawable. Each object in After Effects has a name. Lottie can find these objects and properties by their name using a KeyPath.

Usage

To update a property at runtime, you need 3 things:

  1. KeyPath
  2. rLottie::Property
  3. setValue()

KeyPath

A KeyPath is used to target a specific content or a set of contents that will be updated. A KeyPath is specified by a list of strings that correspond to the hierarchy of After Effects contents in the original animation. KeyPaths can include the specific name of the contents or wildcards:

  • Wildcard *
    • Wildcards match any single content name in its position in the keypath.
  • Globstar **
    • Globstars match zero or more layers.

Properties

rLottie::Property is an enumeration of properties that can be set. They correspond to the animatable value in After Effects and the available properties are listed below.

enum class Property {
    FillColor,     /*!< Color property of Fill object , value type is rlottie::Color */
    FillOpacity,   /*!< Opacity property of Fill object , value type is float [ 0 .. 100] */
    StrokeColor,   /*!< Color property of Stroke object , value type is rlottie::Color */
    StrokeOpacity, /*!< Opacity property of Stroke object , value type is float [ 0 .. 100] */
    StrokeWidth,   /*!< stroke with property of Stroke object , value type is float */
    ...
};

setValue()

setValue() requires a keypath of string and value. The value can be Color, Size and Point structure or a function that returns them. Color, Size, and Point vary depending on the type of rLottie::Property. This value or function(callback) is called and applied to every frame. This value can be set differently for each frame by using the FrameInfo argument passed to the function.

Usage

animation->setValue
   (
   "**",rlottie::Color(
   0, 
   1, 
   0));
  
animation->setValue
   (
   "Layer1.Box 1.Fill1",
    [](
   const rlottie::FrameInfo& info) {
         
   if (info.
   curFrame() < 
   15 )
             
   return 
   rlottie::Color(
   0, 
   1, 
   0);
         
   else {
             
   return 
   rlottie::Color(
   1, 
   0, 
   0);
         }
     });
  

Back to contents

Supported After Effects Features

Shapes Supported
Shape πŸ‘
Ellipse πŸ‘
Rectangle πŸ‘
Rounded Rectangle πŸ‘
Polystar πŸ‘
Group πŸ‘
Trim Path (individually) πŸ‘
Trim Path (simultaneously) πŸ‘
Renderable Supported
Fill πŸ‘
Stroke πŸ‘
Radial Gradient πŸ‘
Linear Gradient πŸ‘
Gradient Stroke πŸ‘
Transforms Supported
Position πŸ‘
Position (separated X/Y) πŸ‘
Scale πŸ‘
Skew ⛔️
Rotation πŸ‘
Anchor Point πŸ‘
Opacity πŸ‘
Parenting πŸ‘
Auto Orient πŸ‘
Interpolation Supported
Linear Interpolation πŸ‘
Bezier Interpolation πŸ‘
Hold Interpolation πŸ‘
Spatial Bezier Interpolation πŸ‘
Rove Across Time πŸ‘
Masks Supported
Mask Path πŸ‘
Mask Opacity πŸ‘
Add πŸ‘
Subtract πŸ‘
Intersect πŸ‘
Lighten ⛔️
Darken ⛔️
Difference ⛔️
Expansion ⛔️
Feather ⛔️
Mattes Supported
Alpha Matte πŸ‘
Alpha Inverted Matte πŸ‘
Luma Matte πŸ‘
Luma Inverted Matte πŸ‘
Merge Paths Supported
Merge ⛔️
Add ⛔️
Subtract ⛔️
Intersect ⛔️
Exclude Intersection ⛔️
Layer Effects Supported
Fill ⛔️
Stroke ⛔️
Tint ⛔️
Tritone ⛔️
Levels Individual Controls ⛔️
Text Supported
Glyphs ⛔️
Fonts ⛔️
Transform ⛔️
Fill ⛔️
Stroke ⛔️
Tracking ⛔️
Anchor point grouping ⛔️
Text Path ⛔️
Per-character 3D ⛔️
Range selector (Units) ⛔️
Range selector (Based on) ⛔️
Range selector (Amount) ⛔️
Range selector (Shape) ⛔️
Range selector (Ease High) ⛔️
Range selector (Ease Low) ⛔️
Range selector (Randomize order) ⛔️
expression selector ⛔️
Other Supported
Expressions ⛔️
Images πŸ‘
Precomps πŸ‘
Time Stretch πŸ‘
Time remap πŸ‘
Markers πŸ‘

Back to contents

Issues or Feature Requests?

File github issues for anything that is broken. Be sure to check the list of supported features before submitting. If an animation is not working, please attach the After Effects file to your issue. Debugging without the original can be very difficult. For immidiate assistant or support please reach us in Gitter

Comments
  • Integrating with Godot Engine / License

    Integrating with Godot Engine / License

    Describe the project you are working on:

    Multiplayer game.

    Describe the problem or limitation you are having in your project:

    Want to play animated vector graphics.

    Describe the feature / enhancement and how it helps to overcome the problem or limitation:

    Adding telegram / Lottie animations to Godot Engine.

    Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:

    Wanted to use rlottie, but LGPL is not compatible with MIT license. Godot Engine is a single executable so LGPL does not apply.

    Need to get rlottie to output into a format where there's individual shapes / meshes and then transforms are applied on the group.

    opened by fire 14
  • Color red becomes purple on android

    Color red becomes purple on android

    All rlottie frame's red color becomes purple. The issue happens only on newer versions of rlottie lib code. The older version on telegram works fine.

    image Become image

    opened by sonphan12 13
  • [rlottie] C API does not render pixel data in the surface buffer.

    [rlottie] C API does not render pixel data in the surface buffer.

    main.zip Attached the test source code.

    Expectation: After executing the code, I expect to see some pixel data in the surface buffer. Actual behaviour: After flushing the rendering data, the buffer content is still zero. See attached GDB picture.

    To build the source code: gcc -Lbuild/src/ -Wall -g -Og -o vtest main.c -lrlottie

    BufferContentAfterRendering

    opened by boothub 13
  • Expose Composition

    Expose Composition

    In addition to rendering to a surface, is it possible to expose Composition?

    The goal is to get the paths that are in the composition and layers and convert the model to a format a 2d vector player can handle.

    Not sure how to handle the conversion of the paths and layers in the animation (that can be disabled) and recreating an animation player. See https://docs.godotengine.org/en/stable/classes/class_animationplayer.html.

    Instead of:

    std::unique_ptr<rlottie::Animation> lottie =
    			rlottie::Animation::loadFromFile(path.utf8().ptr());
    // ...
    lottie->renderSync(frame_i, surface);
    

    We can do:

    std::unique_ptr<rlottie::Animation> lottie =
    			rlottie::Animation::loadFromFile(path.utf8().ptr());
    rlottie::Composition composition = lottie->renderModel()
    
    class Composition : public Object {
    public:
        Composition() : Object(Object::Type::Composition) {}
        std::vector<LayerInfo>     layerInfoList() const;
        const std::vector<Marker> &markers() const { return mMarkers; }
        double                     duration() const
        {
            return frameDuration() / frameRate();  // in second
        }
        size_t frameAtPos(double pos) const
        {
            if (pos < 0) pos = 0;
            if (pos > 1) pos = 1;
            return size_t(round(pos * frameDuration()));
        }
        long frameAtTime(double timeInSec) const
        {
            return long(frameAtPos(timeInSec / duration()));
        }
        size_t totalFrame() const { return mEndFrame - mStartFrame; }
        long   frameDuration() const { return mEndFrame - mStartFrame - 1; }
        float  frameRate() const { return mFrameRate; }
        size_t startFrame() const { return mStartFrame; }
        size_t endFrame() const { return mEndFrame; }
        VSize  size() const { return mSize; }
        void   processRepeaterObjects();
        void   updateStats();
    
    public:
        struct Stats {
            uint16_t precompLayerCount{0};
            uint16_t solidLayerCount{0};
            uint16_t shapeLayerCount{0};
            uint16_t imageLayerCount{0};
            uint16_t nullLayerCount{0};
        };
    
    public:
        std::string                              mVersion;
        VSize                                    mSize;
        long                                     mStartFrame{0};
        long                                     mEndFrame{0};
        float                                    mFrameRate{60};
        BlendMode                                mBlendMode{BlendMode::Normal};
        Layer *                                  mRootLayer{nullptr};
        std::unordered_map<std::string, Asset *> mAssets;
    
        std::vector<Marker> mMarkers;
        VArenaAlloc         mArenaAlloc{2048};
        Stats               mStats;
    };
    
    opened by fire 10
  • render problem

    render problem

    I have tested some simple compositions as followed. But some strange problems have appeared.

    1. when rendering shapes, it has black edges, however in AE file is not contained.
    2. it seems that the shapes with opacity is not support?
    3. when rendering images, especially the image moved or zoomed in/out, the texture is incoherently if you concat them to a video and has some color blocks.

    hope for your reply! thanks~

    shape.aep.zip image aep.zip ζˆͺ屏2020-03-26上午10 56 56

    opened by shudct 10
  • image's opacity and  Gaussian blur

    image's opacity and Gaussian blur

    Our test json has image layers with opacity(not 100) or Gaussian blur, I wonder can rlottie render these effect or not.

    Below is our json and images. img_0.png has 10 transparency. img_11.png is gaussian blurred. Thanks~ test.zip

    bug Question 
    opened by shudct 8
  • Allow passing a color replacement map.

    Allow passing a color replacement map.

    Hi. I have a need to be able to replace colors by a map (color -> color) in a loading animation.

    I understand that this may be just a not welcome feature in your library, but different Linux package maintainers are not happy that they can't use upstream rlottie instead of my fork, so I ask if this feature could make it here in any form.

    This is my sample straightforward implementation. The alternative was for me to decode the json, go through and replace the colors and encode back before passing it to rlottie - overhead is too big for me, bad idea.

    If you agree to add such feature but with different design approach please let me know, I'm ready to implement a different way of doing the same (color format, whatever), just without large runtime overhead.

    opened by john-preston 8
  • Windows: M_PI undefined

    Windows: M_PI undefined

    On Windows M_PI is defined only if

    #define _USE_MATH_DEFINES

    is called just before #include in src/lottie/lottiemodel.cpp, src/vector/vdasher.cpp, vbezier.cpp and vpath.cpp too. See

    https://docs.microsoft.com/fr-fr/cpp/c-runtime-library/math-constants?view=vs-2019

    you can, if you want, embed this macro in a #ifdef_WIN32

    opened by vtorri 8
  • Release build failing to parse, Debug - ok

    Release build failing to parse, Debug - ok

    Building rlottie static to Debug and Release on macOS x64 10.14.6

    If build in Debug (-DCMAKE_BUILD_TYPE=Debug) - file parses ok, image renders. If build in Release (Release, RelWithDebInfo or MinSizeRel) - parser fails. Failing method is void LottieParserImpl::parseComposition() - Debug and Release behave differentry in while (const char *key = NextObjectKey()) { ... } loop.

    In Debug it loops over all objects, in Release - it misses layers and some others, what leads if (comp->mVersion.empty() || !comp->mRootLayer) to fail.

    sample file: https://assets4.lottiefiles.com/packages/lf20_GsFUFN.json

    bug 
    opened by nokeya 7
  • bodymovin upgrade test

    bodymovin upgrade test

    Bodymovin 5.5.2 version is released, there is a report rlottie doens't work on it. Need to verify rlottie with any sample resource exported that version whether it's working or not

    opened by hermet 7
  • Optimize compatibility with windows

    Optimize compatibility with windows

    1.On windows, stbi__fopen opens a path containing non-English characters or symbols will fail due to encoding problems, so the filename parameter is assumed to be utf-8 and converted to utf-16, and then call _wfopen_s or _wfopen 2. In Windows 7 multithreading mode, unloading rlottie.dll will cause the process to be stuck and unable to exit. You need to actively stop RleTaskScheduler before main return

    opened by xspeed1989 6
  • How to get all keypaths?

    How to get all keypaths?

    You say that in order to change a property I need to have a keypath. How can I get all object's keypaths from a loaded lottie file?

    The idea is to have the user select an object (or all of them) to change properties.

    opened by WindowsNT 0
  • Suggest fuzzer for rlottie::Animation::loadFromFile()

    Suggest fuzzer for rlottie::Animation::loadFromFile()

    I suggest this fuzzer for continuous vulnerability checks.

    /*
     * This fuzzer is generated by UTopia with some manual modifications.
     * (UTopia Project: https://github.com/Samsung/UTopia)
     */
    
    #include "FuzzedDataProvider.h"
    #include "rlottie.h"
    #include <fstream>
    
    bool saveFile(std::string Path, std::string Content) {
      std::ofstream OFS(Path);
      if (!OFS.is_open())
        return false;
    
      OFS << Content;
      return true;
    }
    
    extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, uint32_t size) {
      FuzzedDataProvider provider(data, size);
      std::string InputPath = "input";
      auto input = provider.ConsumeRandomLengthString();
      saveFile(InputPath, input);
      auto out = rlottie::Animation::loadFromFile(InputPath.c_str());
      if (!out)
        return 0;
      out->totalFrame();
      size_t width, height;
      out->size(width, height);
    
      return 0;
    }
    
    opened by autofuzzoss 0
  • ClamAV reports viruses found from rlottie's source

    ClamAV reports viruses found from rlottie's source

    Hey,

    when doing a scan with latest clamav, https://www.clamav.net/ against rlotties sources, it detects "Keyloggers".

    ----------- SCAN SUMMARY ----------- Known viruses: 8639436 Engine version: 0.105.1 Scanned directories: 43 Scanned files: 341 Infected files: 2 Data scanned: 44.32 MB Data read: 23.65 MB (ratio 1.87:1) Time: 11.913 sec (0 m 11 s) Start Date: 2022:10:09 10:24:55 End Date: 2022:10:09 10:25:06

    Scanning /tmp/rlottie/example/win32Player/rlottie.dll /tmp/rlottie/example/win32Player/rlottie.dll: Win.Keylogger.Generic-9973679-0 FOUND

    Scanning /tmp/rlottie/example/win32Player/rlottiePlayer.exe /tmp/rlottie/example/win32Player/rlottiePlayer.exe: Win.Keylogger.Generic-9973679-0 FOUND

    As I believe it may be a false positive, you may want to report it to ClamAV via https://www.clamav.net/contact

    opened by juippis 1
  • totalFrame() is one frame less

    totalFrame() is one frame less

    totalFrame() returns one frame less than should. It returns the id of the last frame (equal to "op" Out Point), not the total amout of frames. Comparing to https://lottiefiles.com/tools/json-editor resources are always one frame shorter and different in progress (the same distance in pixels is one frame less, so there is always a small difference in the rendered view).

    opened by mmaciola 0
  • Use after free in surface cache code

    Use after free in surface cache code

    Found this via Valgrind:

       VBitmap make_surface(
            size_t width, size_t height,
            VBitmap::Format format = VBitmap::Format::ARGB32_Premultiplied)
        {
            if (mCache.empty()) return {width, height, format};
    
            auto surface = mCache.back();
            surface.reset(width, height, format);
    
            mCache.pop_back();
            return surface;
        }
    
        void release_surface(VBitmap &surface) { mCache.push_back(surface); }
    

    Here we have the sequence:

    1. VBitmap & surface = mCache.back() => we take a reference on the last element
    2. cache.pop_back(); => delete the last element in the vector
    3. return surface; => return a dangling reference on a deleted element

    Instead of auto surface here, it should be VBitmap surface since auto resolve to a reference (vector::back() returns a reference on its last element)

    opened by X-Ryl669 0
Releases(v0.2)
Owner
Samsung
Samsung Electronics Co.,Ltd.
Samsung
ORE (OpenGL Rendering Engine) is a rendering engine developed for my college minor project assessment.

ORE (OPENGL RENDERING ENGINE) What is ORE? ORE(OpenGL Rendering Engine) is a rendering engine with great and easy to use UI that allows the user to lo

HARSHIT BARGUJAR 3 Sep 23, 2022
A realtime Vulkan voxel path tracer.

brickmap-vulkan A realtime Vulkan voxel path tracer. This is a work in progress! This system is a Vulkan/SPIRV implementation of the BrickMap by stijn

brandon reinhart 0 Nov 3, 2022
A modern cross-platform low-level graphics library and rendering framework

Diligent Engine A Modern Cross-Platform Low-Level 3D Graphics Library Diligent Engine is a lightweight cross-platform graphics API abstraction library

Diligent Graphics 2.6k Nov 28, 2022
Cross-platform, graphics API agnostic, "Bring Your Own Engine/Framework" style rendering library.

bgfx - Cross-platform rendering library GitHub Discussions Discord Chat What is it? Cross-platform, graphics API agnostic, "Bring Your Own Engine/Fram

Π‘Ρ€Π°Π½ΠΈΠΌΠΈΡ€ ΠšΠ°Ρ€Π°ΡŸΠΈΡ› 12.4k Nov 27, 2022
Antialiased 2D vector drawing library on top of OpenGL for UI and visualizations.

This project is not actively maintained. NanoVG NanoVG is small antialiased vector graphics rendering library for OpenGL. It has lean API modeled afte

Mikko Mononen 4.5k Nov 22, 2022
LibGizmo is a small, standalone library that adds a 3D matrix (4x4 floats) manipulation control called 'Gizmo'

LibGizmo is a small, standalone library that adds a 3D matrix (4x4 floats) manipulation control called 'Gizmo'

Cedric Guillemet 127 Oct 17, 2022
SoL (for Speed of Light, or sun in Spanish) is a Physically-based rendering library written in modern C++

SoL (for Speed of Light, or sun in Spanish) is a small rendering library written in C++20. Its goal is to strike a good balance between performance and usability, and allow easy experimentation for rendering researchers.

Arsène Pérard-Gayot 10 May 19, 2022
A standalone Dear ImGui node graph implementation.

ImNodes A standalone Dear ImGui node graph implementation. Library provides core features needed to create a node graph, while leaving it to the user

Rokas Kupstys 546 Nov 22, 2022
2D Vector Graphics Engine Powered by a JIT Compiler

Blend2D 2D Vector Graphics Powered by a JIT Compiler. Official Home Page (blend2d.com) Official Repository (blend2d/blend2d) Public Chat Channel Zlib

Blend2D 1.1k Nov 23, 2022
Source code for pbrt, the renderer described in the third edition of "Physically Based Rendering: From Theory To Implementation", by Matt Pharr, Wenzel Jakob, and Greg Humphreys.

pbrt, Version 3 This repository holds the source code to the version of pbrt that is described in the third edition of Physically Based Rendering: Fro

Matt Pharr 4.3k Nov 28, 2022
appleseed is an open source, physically-based global illumination rendering engine primarily designed for animation and visual effects.

appleseed is an open source, physically-based global illumination rendering engine primarily designed for animation and visual effects.

appleseedhq 2k Nov 23, 2022
Source Code for "Ray Tracing Gems: High-Quality and Real-Time Rendering with DXR and Other APIs" by Eric Haines and Tomas Akenine-MΓΆller

Apress Source Code This repository accompanies Ray Tracing Gems: High-Quality and Real-Time Rendering with DXR and Other APIs by Eric Haines and Tomas

Apress 851 Nov 21, 2022
Single header C library for rendering truetype text to the screen

kc_truetypeassembler.h Single header C library for assembling textured quads for text rendering using a graphics API. It generates a vertices and text

Kevin Chin 24 Nov 12, 2022
NVRHI (NVIDIA Rendering Hardware Interface) is a library that implements a common abstraction layer over multiple graphics APIs

NVRHI Introduction NVRHI (NVIDIA Rendering Hardware Interface) is a library that implements a common abstraction layer over multiple graphics APIs (GA

NVIDIA GameWorks 430 Nov 29, 2022
Im3d is a small, self-contained library for immediate mode rendering of basic primitives

Im3d is a small, self-contained library for immediate mode rendering of basic primitives (points, lines, triangles), plus an immediate mode UI which provides 3d manipulation 'gizmos' and other tools. It is platform and graphics API agnostic and designed to be compatible with VR.

John Chapman 825 Nov 24, 2022
Horde3D is a small 3D rendering and animation engine. It is written in an effort to create an engine being as lightweight and conceptually clean as possible.

Horde3D Horde3D is a 3D rendering engine written in C++ with an effort being as lightweight and conceptually clean as possible. Horde3D requires a ful

Volker Vogelhuber 1.3k Nov 28, 2022
A C++/DirectX 11 implementation of "A Scalable and Production Ready Sky and Atmosphere Rendering Technique"

Atmosphere Renderer A C++/DirectX 11 implementation of "A Scalable and Production Ready Sky and Atmosphere Rendering Technique" Features interactive e

Z Guan 37 Nov 20, 2022
This repository accompanies Ray Tracing Gems II: Next Generation Rendering with DXR, Vulkan, and OptiX

Apress Source Code This repository accompanies Ray Tracing Gems II: Next Generation Rendering with DXR, Vulkan, and OptiX by Adam Marrs, Peter Shirley

Apress 679 Nov 24, 2022
Vire is a C++ voxel rendering engine. It is written in C++14 and uses OpenGL for graphics.

Vire Vire is a C++ voxel rendering engine. It is written in C++14 and uses OpenGL for graphics. Downloads If you'd just like to just download and try,

null 35 Jul 27, 2022