From cd26e0fc2a8c9a9f7edbeba72b3b0008f76e4927 Mon Sep 17 00:00:00 2001 From: X-Ryl669 Date: Fri, 3 Dec 2021 09:28:27 +0100 Subject: [PATCH] feat(rlottie): add animation control options (#2857) * Add support for ARGB32 to RGBA5658 format conversion required by LVGL * Add animation control for rlottie animation * Add support for composable play control and event sending upon reaching end of animation. * Add protection against multiple event posting upon animation ending * Actually pause the timer is the animation is paused. Resume on playing again. * Improve documentation for the additional features * Stop the timer if not looping and we've reached the end of the animation in the provided direction * Fix various defects and improve documentation * Add support for swapped RGB565 format too * Let pause actually pause without messing the current frame index in the animation. * Set invalid destination frame upon construction so it's possible to pause from frame 0. * Set impossible destination frame index So it's updated on first pause --- docs/libs/rlottie.md | 24 ++++++ src/extra/libs/rlottie/lv_rlottie.c | 117 ++++++++++++++++++++++++++-- src/extra/libs/rlottie/lv_rlottie.h | 13 ++++ 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/docs/libs/rlottie.md b/docs/libs/rlottie.md index a8e493834..b7c183492 100644 --- a/docs/libs/rlottie.md +++ b/docs/libs/rlottie.md @@ -71,6 +71,30 @@ 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: +```c +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. + ## Example ```eval_rst diff --git a/src/extra/libs/rlottie/lv_rlottie.c b/src/extra/libs/rlottie/lv_rlottie.c index dce856d34..35a5a3389 100644 --- a/src/extra/libs/rlottie/lv_rlottie.c +++ b/src/extra/libs/rlottie/lv_rlottie.c @@ -6,7 +6,6 @@ /********************* * INCLUDES *********************/ - #include "lv_rlottie.h" #if LV_USE_RLOTTIE @@ -14,10 +13,12 @@ * DEFINES *********************/ #define MY_CLASS &lv_rlottie_class +#define LV_ARGB32 32 /********************** * TYPEDEFS **********************/ +#define LV_ARGB32 32 /********************** * STATIC PROTOTYPES @@ -80,6 +81,23 @@ lv_obj_t * lv_rlottie_create_from_raw(lv_obj_t * parent, lv_coord_t width, lv_co return obj; } +void lv_rlottie_set_play_mode(lv_obj_t * obj, const lv_rlottie_ctrl_t ctrl) +{ + lv_rlottie_t * rlottie = (lv_rlottie_t *) obj; + rlottie->play_ctrl = ctrl; + + if(rlottie->task && (rlottie->dest_frame != rlottie->current_frame || + (rlottie->play_ctrl & LV_RLOTTIE_CTRL_PAUSE) == LV_RLOTTIE_CTRL_PLAY)) { + lv_timer_resume(rlottie->task); + } +} + +void lv_rlottie_set_current_frame(lv_obj_t * obj, const size_t goto_frame) +{ + lv_rlottie_t * rlottie = (lv_rlottie_t *) obj; + rlottie->current_frame = goto_frame < rlottie->total_frames ? goto_frame : rlottie->total_frames - 1; +} + /********************** * STATIC FUNCTIONS **********************/ @@ -104,9 +122,9 @@ static void lv_rlottie_constructor(const lv_obj_class_t * class_p, lv_obj_t * ob rlottie->framerate = (size_t)lottie_animation_get_framerate(rlottie->animation); rlottie->current_frame = 0; - rlottie->scanline_width = create_width * LV_COLOR_DEPTH / 8; + rlottie->scanline_width = create_width * LV_ARGB32 / 8; - size_t allocaled_buf_size = (create_width * create_height * LV_COLOR_DEPTH / 8); + size_t allocaled_buf_size = (create_width * create_height * LV_ARGB32 / 8); rlottie->allocated_buf = lv_mem_alloc(allocaled_buf_size); if(rlottie->allocated_buf != NULL) { rlottie->allocated_buffer_size = allocaled_buf_size; @@ -122,6 +140,9 @@ static void lv_rlottie_constructor(const lv_obj_class_t * class_p, lv_obj_t * ob lv_img_set_src(obj, &rlottie->imgdsc); + rlottie->play_ctrl = LV_RLOTTIE_CTRL_FORWARD | LV_RLOTTIE_CTRL_PLAY | LV_RLOTTIE_CTRL_LOOP; + rlottie->dest_frame = rlottie->total_frames; /* invalid destination frame so it's possible to pause on frame 0 */ + rlottie->task = lv_timer_create(next_frame_task_cb, 1000 / rlottie->framerate, obj); lv_obj_update_layout(obj); @@ -145,6 +166,8 @@ static void lv_rlottie_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj if(rlottie->task) { lv_timer_del(rlottie->task); rlottie->task = NULL; + rlottie->play_ctrl = LV_RLOTTIE_CTRL_FORWARD; + rlottie->dest_frame = 0; } if(rlottie->allocated_buf) { @@ -155,14 +178,89 @@ static void lv_rlottie_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj } +#if LV_COLOR_DEPTH == 16 +static void convert_to_rgba5658(uint32_t * pix, const size_t width, const size_t height) +{ + /* rlottie draws in ARGB32 format, but LVGL only deal with RGB565 format with (optional 8 bit alpha channel) + so convert in place here the received buffer to LVGL format. */ + uint8_t * dest = (uint8_t *)pix; + uint32_t * src = pix; + for(size_t y = 0; y < height; y++) { + /* Convert a 4 bytes per pixel in format ARGB to R5G6B5A8 format + naive way: + r = ((c & 0xFF0000) >> 19) + g = ((c & 0xFF00) >> 10) + b = ((c & 0xFF) >> 3) + rgb565 = (r << 11) | (g << 5) | b + a = c >> 24; + That's 3 mask, 6 bitshift and 2 or operations + + A bit better: + r = ((c & 0xF80000) >> 8) + g = ((c & 0xFC00) >> 5) + b = ((c & 0xFF) >> 3) + rgb565 = r | g | b + a = c >> 24; + That's 3 mask, 3 bitshifts and 2 or operations */ + for(size_t x = 0; x < width; x++) { + uint32_t in = src[x]; +#if LV_COLOR_16_SWAP == 0 + uint16_t r = (uint16_t)(((in & 0xF80000) >> 8) | ((in & 0xFC00) >> 5) | ((in & 0xFF) >> 3)); +#else + /* We want: rrrr rrrr GGGg gggg bbbb bbbb => gggb bbbb rrrr rGGG */ + uint16_t r = (uint16_t)(((c & 0xF80000) >> 16) | ((c & 0xFC00) >> 13) | ((c & 0x1C00) << 3) | ((c & 0xF8) << 5)); +#endif + + lv_memcpy(dest, &r, sizeof(r)); + dest[sizeof(r)] = (uint8_t)(in >> 24); + dest += LV_IMG_PX_SIZE_ALPHA_BYTE; + } + src += width; + } +} +#endif + static void next_frame_task_cb(lv_timer_t * t) { lv_obj_t * obj = t->user_data; lv_rlottie_t * rlottie = (lv_rlottie_t *) obj; - if(rlottie->current_frame == rlottie->total_frames) - rlottie->current_frame = 0; - else - ++rlottie->current_frame; + + if((rlottie->play_ctrl & LV_RLOTTIE_CTRL_PAUSE) == LV_RLOTTIE_CTRL_PAUSE) { + if(rlottie->current_frame == rlottie->dest_frame) { + /* Pause the timer too when it has run once to avoid CPU consumption */ + lv_timer_pause(t); + return; + } + rlottie->dest_frame = rlottie->current_frame; + } + else { + if((rlottie->play_ctrl & LV_RLOTTIE_CTRL_BACKWARD) == LV_RLOTTIE_CTRL_BACKWARD) { + if(rlottie->current_frame > 0) + --rlottie->current_frame; + else { /* Looping ? */ + if((rlottie->play_ctrl & LV_RLOTTIE_CTRL_LOOP) == LV_RLOTTIE_CTRL_LOOP) + rlottie->current_frame = rlottie->total_frames - 1; + else { + lv_event_send(obj, LV_EVENT_READY, NULL); + lv_timer_pause(t); + return; + } + } + } + else { + if(rlottie->current_frame < rlottie->total_frames) + ++rlottie->current_frame; + else { /* Looping ? */ + if((rlottie->play_ctrl & LV_RLOTTIE_CTRL_LOOP) == LV_RLOTTIE_CTRL_LOOP) + rlottie->current_frame = 0; + else { + lv_event_send(obj, LV_EVENT_READY, NULL); + lv_timer_pause(t); + return; + } + } + } + } lottie_animation_render( rlottie->animation, @@ -173,8 +271,11 @@ static void next_frame_task_cb(lv_timer_t * t) rlottie->scanline_width ); - lv_obj_invalidate(obj); +#if LV_COLOR_DEPTH == 16 + convert_to_rgba5658(rlottie->allocated_buf, rlottie->imgdsc.header.w, rlottie->imgdsc.header.h); +#endif + lv_obj_invalidate(obj); } #endif /*LV_USE_RLOTTIE*/ diff --git a/src/extra/libs/rlottie/lv_rlottie.h b/src/extra/libs/rlottie/lv_rlottie.h index 7730f2fb9..c53a7b891 100644 --- a/src/extra/libs/rlottie/lv_rlottie.h +++ b/src/extra/libs/rlottie/lv_rlottie.h @@ -25,6 +25,14 @@ extern "C" { /********************** * TYPEDEFS **********************/ +typedef enum { + LV_RLOTTIE_CTRL_FORWARD = 0, + LV_RLOTTIE_CTRL_BACKWARD = 1, + LV_RLOTTIE_CTRL_PAUSE = 2, + LV_RLOTTIE_CTRL_PLAY = 0, /* Yes, play = 0 is the default mode */ + LV_RLOTTIE_CTRL_LOOP = 8, +} lv_rlottie_ctrl_t; + typedef struct { lv_img_t img_ext; Lottie_Animation * animation; @@ -36,6 +44,8 @@ typedef struct { uint32_t * allocated_buf; size_t allocated_buffer_size; size_t scanline_width; + lv_rlottie_ctrl_t play_ctrl; + size_t dest_frame; } lv_rlottie_t; extern const lv_obj_class_t lv_rlottie_class; @@ -49,6 +59,9 @@ lv_obj_t * lv_rlottie_create_from_file(lv_obj_t * parent, lv_coord_t width, lv_c 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); + /********************** * MACROS **********************/