Skip to main content
Nastrotek
NotesEmbedded UI

Displaying Expressive GIFs on ESP32 with LVGL

A practical guide to animated GIF expressions in ESP32 firmware: convert assets, enable LV_USE_GIF, store files, and render with lv_gif.

Share

LinkedInFacebookX
Real ESP32 display prototype running an LVGL interface during development

In this guide, I will walk you from preparing a GIF to checking memory use and playing the animation reliably on an ESP32.

GIFs are a quick way to give embedded interfaces a little personality: a happy face, blinking eyes, a thinking state, or a small response when a robot receives a command. With ESP32 and LVGL, the main challenge is not creating the widget. It is preparing the asset carefully and keeping memory use under control.

This note walks through a compact workflow: prepare the GIF, enable the decoder, choose where the asset lives, and display it with lv_gif.

The Basic Flow

For an expression GIF, there are usually two practical paths:

  • Convert the GIF into a C array and compile it into the firmware.
  • Keep the .gif file in a filesystem such as SPIFFS, LittleFS, or an SD card, then pass the file path to LVGL.

The first path is simple when you only have a few animations and want everything inside the application binary. The second works better when you have many expressions, want to update assets without rebuilding all firmware, or want to organize GIFs as downloadable packs.

Enable GIF Support in LVGL

In lv_conf.h, enable the GIF widget:

#define LV_USE_GIF 1

According to the LVGL documentation, lv_gif uses a GIF decoder to display animated GIFs. Once the feature is enabled, you can create a widget with lv_gif_create() and set its source with lv_gif_set_src().

Option 1: Embed the GIF as a C Array

If the GIF should ship with the firmware, convert the .gif file into a C asset. A common LVGL workflow is to use the LVGL converter script and keep the image data in raw form:

python ./scripts/LVGLImage.py --cf RAW --ofmt C -o . --name mochi_smile mochi-smile.gif

Then declare and display it:

LV_IMAGE_DECLARE(mochi_smile);

void show_mochi_smile(void)
{
    lv_obj_t * gif = lv_gif_create(lv_screen_active());
    lv_gif_set_color_format(gif, LV_COLOR_FORMAT_RGB565);
    lv_gif_set_src(gif, &mochi_smile);
    lv_obj_center(gif);
}

If the GIF needs transparency, LV_COLOR_FORMAT_ARGB8888 preserves alpha better, but it costs more RAM. On many RGB565 TFT displays, using LV_COLOR_FORMAT_RGB565 can reduce conversion work and is friendlier to ESP32 memory. Call lv_gif_set_color_format() before lv_gif_set_src() so LVGL does not allocate the default framebuffer first and then replace it.

Option 2: Load the GIF from a Filesystem

When the GIF lives in SPIFFS, LittleFS, or an SD card, pass the file path to lv_gif_set_src():

void show_mochi_eyes(void)
{
    lv_obj_t * gif = lv_gif_create(lv_screen_active());
    lv_gif_set_color_format(gif, LV_COLOR_FORMAT_RGB565);
    lv_gif_set_src(gif, "S:/emoji/mochi-eyes.gif");
    lv_obj_center(gif);
}

The important detail is that LVGL must have a matching filesystem driver. Without that driver, the widget cannot open the file even if the path looks correct. In ESP-IDF projects, this usually means mounting the partition and bridging it through lv_fs_drv.

Plan RAM Before Adding More Animations

An animated GIF is not just a small file in flash. LVGL needs RAM for the decoder and the framebuffer for the current frame. The LVGL docs give a useful estimate: roughly 25 kB plus (pixel_size + 1) x width x height bytes.

For a 128x128 GIF:

  • RGB565 is roughly 25 kB + 3 x 128 x 128, close to 74 kB.
  • ARGB8888 is roughly 25 kB + 5 x 128 x 128, above 106 kB.

The exact number depends on your LVGL configuration, heap state, display driver, and other firmware tasks. But the lesson is clear enough: on smaller ESP32 builds, do not start with oversized GIFs.

Preparing Expression GIFs

A simple pipeline for a robot face UI:

  • Design or choose GIFs at the actual display area size, such as 128x128 or 240x240.
  • Reduce the frame count when the animation does not need to be very smooth.
  • Reduce the palette if the visual style allows it.
  • Test one GIF at a time before loading a whole expression set.
  • Name files by state, such as idle.gif, happy.gif, thinking.gif, and sleepy.gif.

If you are prototyping a Mochi-style face or a small robot UI, you can start with the emoji GIF sets shared on Nastrotek, such as the Smile 3D Emoji GIF Pack, Eyes Emoji GIF Pack, Pepe Emoji GIF Pack, or Shiba GIF Pack.

A Small Expression-Switching Example

Instead of creating several GIF widgets on top of each other, keep one widget and switch its source based on state:

static lv_obj_t * face_gif;

void face_ui_create(void)
{
    face_gif = lv_gif_create(lv_screen_active());
    lv_gif_set_color_format(face_gif, LV_COLOR_FORMAT_RGB565);
    lv_obj_center(face_gif);
}

void face_ui_set_idle(void)
{
    lv_gif_set_src(face_gif, "S:/emoji/idle.gif");
}

void face_ui_set_happy(void)
{
    lv_gif_set_src(face_gif, "S:/emoji/happy.gif");
}

With C array assets, the idea is the same. The source is an image descriptor pointer instead of a file path.

Common Mistakes

  • Forgetting to enable LV_USE_GIF.
  • Using a file path before registering an LVGL filesystem driver.
  • Choosing a GIF that is too large for the available RAM.
  • Using ARGB8888 everywhere even when the display does not need alpha.
  • Switching sources from multiple tasks without coordinating with the LVGL task.
  • Using GIF dimensions above LV_GIF_MAX_WIDTH or LV_GIF_MAX_HEIGHT in lv_conf.h.

Conclusion

To display expressive GIFs on ESP32 with LVGL, start small: enable LV_USE_GIF, test one 128x128 GIF, choose between C arrays and filesystem storage, then measure RAM before expanding into a full expression set. Once the pipeline is stable, the Emoji packs on Nastrotek are a fast way to prototype a robot face UI without drawing every animation from scratch.

References

Share

LinkedInFacebookX

Keep exploring

Read next

Related articles

View more in Notes

Nastrotek uses cookies for analytics and ad personalization to help us understand how the site is used. You can accept or decline non-essential cookies.