Lottie player

Allows to use Lottie animations in LVGL. Taken from this base repository

LVGL provides the interface to Samsung/rlottie library's C API. That is the actual Lottie player is not part of LVGL, it needs to be built separately.

Build Rlottie

To build Samsung's Rlottie C++14-compatible compiler and optionally CMake 3.14 or higher is required.

To build on desktop you can follow the instructions from Rlottie's README. In the most basic case it looks like this:

mkdir rlottie_workdir
cd rlottie_workdir
git clone https://github.com/Samsung/rlottie.git
mkdir build
cd build
cmake ../rlottie
make -j
sudo make install

And finally add the -lrlottie flag to your linker.

On embedded systems you need to take care of integrating Rlottie to the given build system.

ESP-IDF example at bottom

Usage

You can use animation from files or raw data (text). In either case first you need to enable LV_USE_RLOTTIE in lv_conf.h.

The width and height of the object be set in the create function and the animation will be scaled accordingly.

Use Rlottie from file

To create a Lottie animation from file use:

  lv_obj_t * lottie = lv_rlottie_create_from_file(parent, width, height, "path/to/lottie.json");

Note that, Rlottie uses the standard STDIO C file API, so you can use the path "normally" and no LVGL specific driver letter is required.

Use Rlottie from raw string data

lv_example_rlottie_approve.c contains an example animation in raw format. Instead storing the JSON string a hex array is stored for the following reasons:

  • avoid escaping " in the JSON file

  • some compilers don't support very long strings

lvgl/scripts/filetohex.py can be used to convert a Lottie file a hex array. E.g.:

./filetohex.py path/to/lottie.json > out.txt

To create an animation from raw data:

extern const uint8_t lottie_data[];
lv_obj_t* lottie = lv_rlottie_create_from_raw(parent, width, height, (const char *)lottie_data);

Getting animations

Lottie is standard and popular format so you can find many animation files on the web. For example: https://lottiefiles.com/

You can also create your own animations with Adobe After Effects or similar software.

Controlling animations

LVGL provides two functions to control the animation mode: lv_rlottie_set_play_mode and lv_rlottie_set_current_frame. You'll combine your intentions when calling the first method, like in these examples:

lv_obj_t * lottie = lv_rlottie_create_from_file(scr, 128, 128, "test.json");
lv_obj_center(lottie);
// Pause to a specific frame
lv_rlottie_set_current_frame(lottie, 50);
lv_rlottie_set_play_mode(lottie, LV_RLOTTIE_CTRL_PAUSE); // The specified frame will be displayed and then the animation will pause

// Play backward and loop
lv_rlottie_set_play_mode(lottie, LV_RLOTTIE_CTRL_PLAY | LV_RLOTTIE_CTRL_BACKWARD | LV_RLOTTIE_CTRL_LOOP);

// Play forward once (no looping)
lv_rlottie_set_play_mode(lottie, LV_RLOTTIE_CTRL_PLAY | LV_RLOTTIE_CTRL_FORWARD);

The default animation mode is play forward with loop.

If you don't enable looping, a LV_EVENT_READY is sent when the animation can not make more progress without looping.

To get the number of frames in an animation or the current frame index, you can cast the lv_obj_t instance to a lv_rlottie_t instance and inspect the current_frame and total_frames members.

ESP-IDF Example

Background

Rlottie can be expensive to render on embedded hardware. Lottie animations tend to use a large amount of CPU time and can use large portions of RAM. This will vary from lottie to lottie but in general for best performance:

  • Limit total # of frames in the animation

  • Where possible, try to avoid bezier type animations

  • Limit animation render size

If your ESP32 chip does not have SPIRAM you will face severe limitations in render size.

To give a better idea on this, lets assume you want to render a 240x320 lottie animation.

In order to pass initialization of the lv_rlottie_t object, you need 240x320x32/8 (307k) available memory. The latest ESP32-S3 has 256kb RAM available for this (before freeRtos and any other initialization starts taking chunks out). So while you can probably start to render a 50x50 animation without SPIRAM, PSRAM is highly recommended.

Additionally, while you might be able to pass initialization of the lv_rlottie_t object, as rlottie renders frame to frame, this consumes additional memory. A 30 frame animation that plays over 1 second probably has minimal issues, but a 300 frame animation playing over 10 seconds could very easily crash due to lack of memory as rlottie renders, depending on the complexity of the animation.

Rlottie will not compile for the IDF using the -02 compiler option at this time.

For stability in lottie animations, I found that they run best in the IDF when enabling LV_MEM_CUSTOM (using stdlib.h)

For all its faults, when running right-sized animations, they provide a wonderful utility to LVGL on embedded LCDs and can look really good when done properly.

When picking/designing a lottie animation consider the following limitations:

  • Build the lottie animation to be sized for the intended size - it can scale/resize, but performance will be best when the base lottie size is as intended

  • Limit total number of frames, the longer the lottie animation is, the more memory it will consume for rendering (rlottie consumes IRAM for rendering)

  • Build the lottie animation for the intended frame rate - default lottie is 60fps, embedded LCDs likely wont go above 30fps

IDF Setup

Where the LVGL simulator uses the installed rlottie lib, the IDF works best when using rlottie as a submodule under the components directory.

cd 'your/project/directory'
git add submodule 
git add submodule https://github.com/Samsung/rlottie.git ./components/rlottie/rlottie
git submodule update --init --recursive

Now, Rlottie is available as a component in the IDF, but it requires some additional changes and a CMakeLists file to tell the IDF how to compile.

Rlottie patch file

Rlottie relies on a dynamic linking for an image loader lib. This needs to be disabled as the IDF doesn't play nice with dynamic linking.

A patch file is available in lvgl uner: /env_support/esp/rlottie/0001-changes-to-compile-with-esp-idf.patch

Apply the patch file to your rlottie submodule.

CMakeLists for IDF

An example CMakeLists file has been provided at /env_support/esp/rlottie/CMakeLists.txt

Copy this CMakeLists file to 'your-project-directory'/components/rlottie/

In addition to the component CMakeLists file, you'll also need to tell your project level CMakeLists in your IDF project to require rlottie:

REQUIRES "lvgl" "rlottie"

From here, you should be able to use lv_rlottie objects in your ESP-IDF project as any other widget in LVGL ESP examples. Please remember that these animations can be highly resource constrained and this does not guarantee that every animation will work.

Additional Rlottie considerations in ESP-IDF

While unecessary, removing the rlottie/rlottie/example folder can remove many un-needed files for this embedded LVGL application

From here, you can use the relevant LVGL lv_rlottie functions to create lottie animations in LVGL on embedded hardware!

Please note, that while lottie animations are capable of running on many ESP chips, below is recommended for best performance.

  • ESP32-S3-WROOM-1-N16R8

    • 16mb quad spi flash

    • 8mb octal spi PSRAM

  • IDF4.4 or higher

The Esp-box devkit meets this spec and https://github.com/espressif/esp-box is a great starting point to adding lottie animations.

you'll need to enable LV_USE_RLOTTIE through idf.py menuconfig under LVGL component settings.

Additional changes to make use of SPIRAM

lv_alloc/realloc do not make use of SPIRAM. Given the high memory usage of lottie animations, it is recommended to shift as much out of internal DRAM into SPIRAM as possible. In order to do so, SPIRAM will need to be enabled in the menuconfig options for your given espressif chip.

There may be a better solution for this, but for the moment the recommendation is to make local modifications to the lvgl component in your espressif project. This is as simple as swapping lv_alloc/lv_realloc calls in lv_rlottie.c with heap_caps_malloc (for IDF) with the appropriate MALLOC_CAP call - for SPIRAM usage this is MALLOC_CAP_SPIRAM.

rlottie->allocated_buf = heap_caps_malloc(allocaled_buf_size+1, MALLOC_CAP_SPIRAM);

Example

Load a Lottie animation from raw data

C code  

 GitHub
#include "../../lv_examples.h"
#if LV_BUILD_EXAMPLES
#if LV_USE_RLOTTIE

/**
 * Load an lottie animation from flash
 */
void lv_example_rlottie_1(void)
{
    extern const uint8_t lv_example_rlottie_approve[];
    lv_obj_t * lottie = lv_rlottie_create_from_raw(lv_scr_act(), 100, 100, (const void *)lv_example_rlottie_approve);
    lv_obj_center(lottie);
}

#else
void lv_example_rlottie_1(void)
{
    /*TODO
     *fallback for online examples*/

    lv_obj_t * label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "Rlottie is not installed");
    lv_obj_center(label);
}

#endif
#endif

MicroPython code  

 GitHub Simulator
#!/opt/bin/lv_micropython -i
import lvgl as lv
import display_driver
#
# Load a lottie animation from flash
#
from lv_example_rlottie_approve import lv_example_rlottie_approve

lottie = lv.rlottie_create_from_raw(lv.scr_act(), 100, 100, lv_example_rlottie_approve)
lottie.center()


Load a Lottie animation from a file

C code  

 GitHub
#include "../../lv_examples.h"
#if LV_BUILD_EXAMPLES
#if LV_USE_RLOTTIE

/**
 * Load an lottie animation from file
 */
void lv_example_rlottie_2(void)
{
    /*The rlottie library uses STDIO file API, so there is no driver letter for LVGL*/
    lv_obj_t * lottie = lv_rlottie_create_from_file(lv_scr_act(), 100, 100,
                                                    "lvgl/examples/libs/rlottie/lv_example_rlottie_approve.json");
    lv_obj_center(lottie);
}

#else
void lv_example_rlottie_2(void)
{
    /*TODO
     *fallback for online examples*/

    lv_obj_t * label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "Rlottie is not installed");
    lv_obj_center(label);
}

#endif
#endif

MicroPython code  

 GitHub Simulator
#!/opt/bin/lv_micropython -i
import lvgl as lv
import display_driver

lottie = lv.rlottie_create_from_file(lv.scr_act(), 100, 100,"lv_example_rlottie_approve.json")
lottie.center()

API

Enums

enum lv_rlottie_ctrl_t

Values:

enumerator LV_RLOTTIE_CTRL_FORWARD
enumerator LV_RLOTTIE_CTRL_BACKWARD
enumerator LV_RLOTTIE_CTRL_PAUSE
enumerator LV_RLOTTIE_CTRL_PLAY
enumerator LV_RLOTTIE_CTRL_LOOP

Functions

lv_obj_t *lv_rlottie_create_from_file(lv_obj_t *parent, lv_coord_t width, lv_coord_t height, const char *path)
lv_obj_t *lv_rlottie_create_from_raw(lv_obj_t *parent, lv_coord_t width, lv_coord_t height, const char *rlottie_desc)
void lv_rlottie_set_play_mode(lv_obj_t *rlottie, const lv_rlottie_ctrl_t ctrl)
void lv_rlottie_set_current_frame(lv_obj_t *rlottie, const size_t goto_frame)

Variables

const lv_obj_class_t lv_rlottie_class
struct lv_rlottie_t

Public Members

lv_img_t img_ext
struct Lottie_Animation_S *animation
lv_timer_t *task
lv_img_dsc_t imgdsc
size_t total_frames
size_t current_frame
size_t framerate
uint32_t *allocated_buf
size_t allocated_buffer_size
size_t scanline_width
lv_rlottie_ctrl_t play_ctrl
size_t dest_frame