mirror of
https://github.com/lvgl/lvgl.git
synced 2025-01-28 07:03:00 +08:00
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
This commit is contained in:
parent
91fc579ee7
commit
cd26e0fc2a
@ -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
|
||||
|
||||
|
@ -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*/
|
||||
|
@ -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
|
||||
**********************/
|
||||
|
Loading…
x
Reference in New Issue
Block a user