Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ jobs:
sudo apt-get update -q -y
sudo apt install libsdl2-dev libjpeg-dev libpng-dev
sudo apt install libcairo2-dev
sudo apt install librlottie-dev libzip-dev
shell: bash
- name: default build
run: |
Expand Down
16 changes: 15 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,20 @@ ifeq ($(CONFIG_LOADER_TVG), y)
libtwin.a_files-y += src/image-tvg.c
endif

ifeq ($(CONFIG_LOADER_LOTTIE), y)
libtwin.a_files-y += src/image-lottie.c
ifneq ($(CC_IS_EMCC), 1)
libtwin.a_cflags-y += $(call dep,cflags,rlottie)
libtwin.a_cflags-y += $(call dep,cflags,libzip)
TARGET_LIBS += $(call dep,libs,rlottie)
TARGET_LIBS += $(call dep,libs,libzip)
else
# Emscripten rlottie port (includes zlib) - flags needed for both compile and link
libtwin.a_cflags-y += -sUSE_RLOTTIE=1 -sUSE_ZLIB=1
TARGET_LIBS += -sUSE_RLOTTIE=1 -sUSE_ZLIB=1
endif
endif

# Applications

libapps.a_files-y := apps/dummy.c
Expand All @@ -142,6 +156,7 @@ libapps.a_files-$(CONFIG_DEMO_CALCULATOR) += apps/calc.c
libapps.a_files-$(CONFIG_DEMO_SPLINE) += apps/spline.c
libapps.a_files-$(CONFIG_DEMO_ANIMATION) += apps/animation.c
libapps.a_files-$(CONFIG_DEMO_IMAGE) += apps/image.c
libapps.a_files-$(CONFIG_DEMO_LOTTIE) += apps/lottie.c

libapps.a_includes-y := include
# Emscripten size optimization
Expand Down Expand Up @@ -268,7 +283,6 @@ endif
# Build system integration

CFLAGS += -include config.h

# Ensure composite-decls.h exists before including build rules
# (needed for dependency generation in mk/common.mk)
ifeq ($(filter config defconfig clean,$(MAKECMDGOALS)),)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ relies on certain third-party packages for full functionality and access to all
its features. We encourage the development environment to be installed with all optional
packages, including [libjpeg](https://www.ijg.org/) and [libpng](https://github.com/pnggroup/libpng).
* macOS: `brew install jpeg libpng`
* Ubuntu Linux / Debian: `sudo apt install libjpeg-dev libpng-dev`
* Ubuntu Linux / Debian: `sudo apt install libjpeg-dev libpng-dev librlottie-dev libzip-dev`
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The macOS installation instructions (line 71) should also be updated to include rlottie and libzip for consistency with the Ubuntu/Debian changes. Without these packages, macOS users won't have the dependencies for Lottie animation support.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 72:

<comment>The macOS installation instructions (line 71) should also be updated to include `rlottie` and `libzip` for consistency with the Ubuntu/Debian changes. Without these packages, macOS users won&#39;t have the dependencies for Lottie animation support.</comment>

<file context>
@@ -69,7 +69,7 @@ relies on certain third-party packages for full functionality and access to all
 packages, including [libjpeg](https://www.ijg.org/) and [libpng](https://github.com/pnggroup/libpng).
 * macOS: `brew install jpeg libpng`
-* Ubuntu Linux / Debian: `sudo apt install libjpeg-dev libpng-dev`
+* Ubuntu Linux / Debian: `sudo apt install libjpeg-dev libpng-dev librlottie-dev libzip-dev`
 
 The renderer implementation can either use the built-in pixel manipulation or be based on [Pixman](https://pixman.org/).
</file context>
Fix with Cubic


The renderer implementation can either use the built-in pixel manipulation or be based on [Pixman](https://pixman.org/).
The built-in renderer is simple and performs adequately on platforms without SIMD instructions,
Expand Down
27 changes: 27 additions & 0 deletions apps/apps_lottie.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Twin - A Tiny Window System
* Copyright (c) 2025 National Cheng Kung University, Taiwan
* All rights reserved.
*/

#ifndef _APPS_LOTTIE_H_
#define _APPS_LOTTIE_H_

#include <twin.h>

/**
* Start Lottie animation player
*
* @param screen Screen to create window on
* @param name Window title
* @param path Path to Lottie file (.json or .lottie)
* @param x Window X position
* @param y Window Y position
*/
void apps_lottie_start(twin_screen_t *screen,
const char *name,
const char *path,
int x,
int y);

#endif /* _APPS_LOTTIE_H_ */
208 changes: 208 additions & 0 deletions apps/lottie.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*
* Twin - A Tiny Window System
* Copyright (c) 2024 National Cheng Kung University, Taiwan
* All rights reserved.
*
* Lottie player with Play/Pause and Loop controls
*/

#include <stdlib.h>

#include <twin.h>

#include "apps_lottie.h"

#define BUTTON_SIZE twin_int_to_fixed(8)

typedef struct {
mado_lottie_image_t *lottie;
twin_pixmap_t *pix;
twin_timeout_t *timeout;
twin_label_t *status;
twin_custom_widget_t *display;
} lottie_app_t;

/*
* Animation display
*/

static void _lottie_paint(twin_custom_widget_t *widget)
{
lottie_app_t *app = (lottie_app_t *)twin_custom_widget_data(widget);
if (!app || !app->pix || !app->pix->animation)
return;

twin_pixmap_t *dst = twin_custom_widget_pixmap(widget);
if (!dst)
return;

twin_pixmap_t *frame = twin_animation_get_current_frame(app->pix->animation);
if (!frame)
return;

twin_fill(dst, 0xffe0e0e0, TWIN_SOURCE, 0, 0, dst->width, dst->height);

twin_coord_t ox = (dst->width - frame->width) / 2;
twin_coord_t oy = (dst->height - frame->height) / 2;
if (ox < 0) ox = 0;
if (oy < 0) oy = 0;

twin_operand_t srcop = {
.source_kind = TWIN_PIXMAP,
.u.pixmap = frame,
};
twin_composite(dst, ox, oy, &srcop, 0, 0, NULL, 0, 0,
TWIN_OVER, frame->width, frame->height);
}

static twin_dispatch_result_t _lottie_dispatch(twin_widget_t *widget,
twin_event_t *event,
void *closure)
{
(void)closure;
twin_custom_widget_t *cw = twin_widget_get_custom(widget);
if (cw && event->kind == TwinEventPaint)
_lottie_paint(cw);
return TwinDispatchContinue;
}

/*
* Timer
*/

static twin_time_t _lottie_timeout(twin_time_t now, void *closure)
{
(void)now;
lottie_app_t *app = closure;

if (!app || !app->lottie || !app->pix)
return 100;

if (mado_lottie_is_playing(app->lottie)) {
twin_animation_advance_frame(app->pix->animation);
if (app->display)
twin_custom_widget_queue_paint(app->display);
}

return mado_lottie_get_frame_delay(app->lottie);
}

/*
* Status update
*/

static void update_status(lottie_app_t *app)
{
if (!app->status || !app->lottie)
return;

bool playing = mado_lottie_is_playing(app->lottie);
bool loop = mado_lottie_is_looping(app->lottie);

const char *text;
if (playing)
text = loop ? "Playing | Loop" : "Playing";
else
text = loop ? "Paused | Loop" : "Paused";

twin_label_set(app->status, text, 0xff000000, BUTTON_SIZE, TwinStyleBold);
}

/*
* Button callbacks
*/

static twin_dispatch_result_t _play_cb(twin_widget_t *widget,
twin_event_t *event,
void *closure)
{
(void)widget;
if (event->kind != TwinEventButtonSignalUp)
return TwinDispatchContinue;

lottie_app_t *app = closure;
mado_lottie_set_playback(app->lottie, !mado_lottie_is_playing(app->lottie));
update_status(app);
return TwinDispatchDone;
}

static twin_dispatch_result_t _loop_cb(twin_widget_t *widget,
twin_event_t *event,
void *closure)
{
(void)widget;
if (event->kind != TwinEventButtonSignalUp)
return TwinDispatchContinue;

lottie_app_t *app = closure;
mado_lottie_set_loop(app->lottie, !mado_lottie_is_looping(app->lottie));
update_status(app);
return TwinDispatchDone;
}

/*
* Public API
*/

void apps_lottie_start(twin_screen_t *screen,
const char *name,
const char *path,
int x,
int y)
{
twin_pixmap_t *pix = twin_pixmap_from_file(path, TWIN_ARGB32);
if (!pix) {
return;
}

if (!pix->animation || !twin_animation_is_lottie(pix->animation)) {
twin_pixmap_destroy(pix);
return;
}

mado_lottie_image_t *lottie = twin_animation_get_lottie(pix->animation);
twin_animation_t *anim = pix->animation;

int win_w = anim->width + 10;
int win_h = anim->height + 30;

twin_toplevel_t *top = twin_toplevel_create(
screen, TWIN_ARGB32, TwinWindowApplication, x, y, win_w, win_h, name);
if (!top) {
twin_pixmap_destroy(pix);
return;
}

twin_custom_widget_t *display = twin_custom_widget_create(
&top->box, 0xffe0e0e0, anim->width, anim->height, 1, 10,
_lottie_dispatch, sizeof(lottie_app_t));

if (!display) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Resource leak: when display creation fails, the toplevel top is not destroyed. Consider also destroying the toplevel before returning.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/lottie.c, line 180:

<comment>Resource leak: when `display` creation fails, the toplevel `top` is not destroyed. Consider also destroying the toplevel before returning.</comment>

<file context>
@@ -0,0 +1,208 @@
+        &amp;top-&gt;box, 0xffe0e0e0, anim-&gt;width, anim-&gt;height, 1, 10,
+        _lottie_dispatch, sizeof(lottie_app_t));
+
+    if (!display) {
+        twin_pixmap_destroy(pix);
+        return;
</file context>
Fix with Cubic

twin_pixmap_destroy(pix);
return;
}

lottie_app_t *app = (lottie_app_t *)twin_custom_widget_data(display);
app->lottie = lottie;
app->pix = pix;
app->display = display;

/* Controls: Play | Loop | Status */
twin_box_t *bar = twin_box_create(&top->box, TwinBoxHorz);

twin_button_t *play = twin_button_create(bar, "Play", 0xff000000,
BUTTON_SIZE, TwinStyleBold);
twin_widget_set_callback(&play->label.widget, _play_cb, app);

twin_button_t *loop = twin_button_create(bar, "Loop", 0xff000000,
BUTTON_SIZE, TwinStyleBold);
twin_widget_set_callback(&loop->label.widget, _loop_cb, app);

app->status = twin_label_create(bar, "Playing | Loop", 0xff000000,
BUTTON_SIZE, TwinStyleBold);

app->timeout = twin_set_timeout(_lottie_timeout,
mado_lottie_get_frame_delay(lottie), app);

twin_toplevel_show(top);
}
6 changes: 6 additions & 0 deletions apps/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "apps_calc.h"
#include "apps_clock.h"
#include "apps_image.h"
#include "apps_lottie.h"
#include "apps_multi.h"
#include "apps_spline.h"

Expand Down Expand Up @@ -100,9 +101,14 @@ static void init_demo_apps(twin_context_t *ctx)
#endif
#if defined(CONFIG_DEMO_ANIMATION)
apps_animation_start(screen, "Viewer", ASSET_PATH "nyancat.gif", 20, 20);
apps_animation_start(screen, "Lottie Animation API", ASSET_PATH "nyancat.json", 20, 20);
#endif
#if defined(CONFIG_DEMO_IMAGE)
apps_image_start(screen, "Viewer", 20, 20);
#endif
#if defined(CONFIG_DEMO_LOTTIE)
apps_lottie_start(screen, "Lottie Viewer(JSON)", ASSET_PATH "nyancat.json", 20, 20);
apps_lottie_start(screen, "Lottie Viewer(dotLottie)", ASSET_PATH "nyancat.lottie", 20, 20);
#endif
twin_screen_set_active(screen, screen->top);
}
Expand Down
1 change: 1 addition & 0 deletions assets/nyancat.json

Large diffs are not rendered by default.

Binary file added assets/nyancat.lottie
Binary file not shown.
20 changes: 20 additions & 0 deletions configs/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ config HAVE_CAIRO
default n if CC_IS_EMCC
def_bool $(shell,pkg-config --exists cairo && echo y || echo n) if !CC_IS_EMCC

config HAVE_LIBRLOTTIE
bool
default y if CC_IS_EMCC
default $(shell,pkg-config --exists rlottie && echo y || echo n) if !CC_IS_EMCC

choice
prompt "Backend Selection"
default BACKEND_WASM if CC_IS_EMCC
Expand Down Expand Up @@ -251,6 +256,13 @@ config LOADER_TVG
Enable TinyVG vector graphics format support.
Compact binary format for scalable graphics.

config LOADER_LOTTIE
bool "Enable Lottie loader"
depends on HAVE_LIBRLOTTIE
default y
help
Enable Lottie graphics format support.

endmenu

menu "Demo Applications"
Expand Down Expand Up @@ -317,6 +329,14 @@ config DEMO_IMAGE
Automatically enables TVG loader.
Demonstrates resolution-independent graphics.

config DEMO_LOTTIE
bool "Build lottie demo"
select LOADER_LOTTIE
default y
depends on DEMO_APPLICATIONS
help
Shows lottie format rendering capabilities.

endmenu

menu "Tools"
Expand Down
Loading
Loading