This LVGL image converter turns any PNG, JPG or BMP into a ready-to-compile lv_img_dsc_t C array for LVGL — the Light and Versatile Graphics Library that powers most modern embedded GUIs on ESP32, STM32 and other microcontrollers. Pick RGB565 true color for a color TFT or 1-bit for a monochrome panel, set your size, and copy a descriptor you can drop straight into lv_img_set_src(). Conversion is 100% in-browser, so even confidential product UI art never leaves your machine.
What is LVGL and what is lv_img_dsc_t?
LVGL is an open-source graphics library for building rich, animated user interfaces on resource-constrained microcontrollers. It is the engine behind countless ESP32 smart-home panels, 3D-printer touchscreens, and STM32 instrument clusters. To show a static image, LVGL uses an lv_img_dsc_t — an image descriptor that bundles a pixel-data byte array with a small header describing the width, height and color format (the header.cf field). This converter builds the whole structure for you, including the correctly sized data_size field that trips up hand-written arrays.
RGB565 vs. 1-bit: which format to pick
For a color TFT — ILI9341, ST7789, ST7735 — choose True color RGB565. Each pixel becomes two bytes (5 bits red, 6 green, 5 blue) stored little-endian, matching LVGL's default LV_IMG_CF_TRUE_COLOR on a 16-bit display. For a monochrome OLED or an alpha mask, choose 1-bit, which emits LV_IMG_CF_ALPHA_1BIT. RGB565 is the right default for almost every touchscreen project; 1-bit keeps flash usage tiny when you only need a logo or icon outline.
Setting up LVGL on an ESP32
A typical ESP32 + LVGL stack uses the LovyanGFX or TFT_eSPI driver underneath LVGL. The essential steps:
- Add lvgl to your
platformio.inior Arduino libraries. - Copy
lv_conf_template.htolv_conf.hand setLV_COLOR_DEPTH 16. - If red and blue come out swapped, enable
LV_COLOR_16_SWAP 1— this is the single most common LVGL color bug. - Register your display flush callback with
lv_disp_drv_register().
Full example — show the image on screen
Generate your descriptor above (say you named it my_img), then:
#include "lvgl.h"
// <-- paste the generated lv_img_dsc_t (and its _map array) here -->
extern const lv_img_dsc_t my_img;
void show_logo() {
lv_obj_t * img = lv_img_create(lv_scr_act());
lv_img_set_src(img, &my_img);
lv_obj_center(img);
}
That is the whole flow: create an image object, point it at your descriptor, position it. Because the byte array is const, it lives in flash and costs no RAM beyond LVGL's draw buffer.
Reducing flash usage
An RGB565 image costs width × height × 2 bytes of flash — a full 320×240 ILI9341 background is about 150 KB, which can blow the partition budget on a bare ESP32. Three practical ways to cut that: scale the image down to the smallest size that still looks acceptable on the panel; convert decorative icons to 1-bit alpha instead of full color; or, for many images, switch to LVGL's run-time PNG/BMP decoder and store compressed files on SPIFFS/LittleFS rather than embedding raw arrays. For a handful of UI assets, though, the embedded lv_img_dsc_t this tool generates is the simplest, fastest-loading option — no filesystem, no decoder, just a pointer into flash.
LVGL v8 vs v9 note
The output here targets LVGL v8, whose lv_img_dsc_t and LV_IMG_CF_TRUE_COLOR enum are stable and widely used. On LVGL v9 the color-format enums were renamed (for example LV_COLOR_FORMAT_RGB565) and the descriptor is slightly leaner; the raw RGB565 pixel bytes are identical, so you only need to adjust the header.cf value. Keep the byte order little-endian and you are set.
Driving a plain SSD1306 instead of a color GUI? The u8g2 converter or the classic image2cpp tool are a better fit. For e-ink, see the e-paper converter.