A command-line tool and Python library for converting PNG images into
LVGL binary (.bin) or C array (.c) image assets.
Supports LVGL v8 and LVGL v9 output via a single --lvgl-version flag.
- 🎨 Converts PNG to all major LVGL color formats (RGB565, ARGB8888, indexed, alpha-only, …)
- 📦 Optional compression: RLE or LZ4 (v9 only)
- 🔢 Stride alignment control (
--align) - 🔀 Pre-multiplied alpha output (
--premultiply) - 🌫️ Dithering for RGB565 gradients (
--rgb565dither) - 🗃️ Batch conversion of entire folders
- 🔄 Round-trip support:
.bin→.pngand.png→.bin/.c - 🏷️ LVGL v8 and v9 targets via
--lvgl-version
pip install pypng lz4💡 For indexed-color quantization, install pngquant:
sudo apt install pngquant # Debian / Ubuntu brew install pngquant # macOS
# LVGL v9 (default) — RGB565 binary
python3 LVGLImage.py --cf RGB565 --ofmt BIN image.png
# LVGL v9 — ARGB8888 C array
python3 LVGLImage.py --cf ARGB8888 --ofmt C image.png
# LVGL v8 — RGB565 C array
python3 LVGLImage.py --cf RGB565 --ofmt C --lvgl-version 8 image.png
# LVGL v8 — indexed I8 binary
python3 LVGLImage.py --cf I8 --ofmt BIN --lvgl-version 8 image.png
# Batch convert a folder (v9, LZ4 compressed)
python3 LVGLImage.py --cf RGB565 --ofmt BIN --compress LZ4 ./assets/| Option | Default | Description |
|---|---|---|
--cf |
I8 |
Color format (see list below) |
--ofmt |
BIN |
Output format: BIN, C, or PNG |
--lvgl-version |
9 |
Target LVGL version: 8 or 9 |
--compress |
NONE |
Compression: NONE, RLE, LZ4 (v9 only) |
--align |
1 |
Stride alignment in bytes |
--background |
0x000000 |
Background color for formats without alpha |
--premultiply |
off | Pre-multiply RGB by alpha |
--rgb565dither |
off | Dithering for RGB565 gradients |
--nemagfx |
off | NEMA-compatible I8 palette layout |
-o, --output |
./output |
Output folder |
--name |
— | Override output variable/file name (single file only) |
-v, --verbose |
off | Enable debug logging |
| Format | Description | v8 macro |
|---|---|---|
RGB565 |
16-bit RGB | LV_IMG_CF_TRUE_COLOR (depth=16) |
RGB888 |
24-bit RGB | LV_IMG_CF_TRUE_COLOR (depth=24) |
XRGB8888 |
32-bit RGB (no alpha) | LV_IMG_CF_TRUE_COLOR (depth=32) |
ARGB8888 |
32-bit RGBA | LV_IMG_CF_TRUE_COLOR_ALPHA (depth=32) |
RGB565A8 |
16-bit RGB + separate alpha map | LV_IMG_CF_TRUE_COLOR_ALPHA (depth=16) |
I1 / I2 / I4 / I8 |
Indexed (palette) | LV_IMG_CF_INDEXED_xBIT |
A1 / A2 / A4 / A8 |
Alpha-only | LV_IMG_CF_ALPHA_xBIT |
RAW / RAW_ALPHA |
Raw pass-through | LV_IMG_CF_RAW / LV_IMG_CF_RAW_ALPHA |
| Format | Description |
|---|---|
L8 |
8-bit grayscale |
AL88 |
8-bit grayscale + alpha |
ARGB8565 |
24-bit ARGB (565 color + 8-bit alpha) |
RGB565_SWAPPED |
Big-endian RGB565 |
ARGB8888_PREMULTIPLIED |
Pre-multiplied ARGB8888 |
🚫 Using a v9-only format with
--lvgl-version 8raises aParameterError.
| Aspect | LVGL v8 | LVGL v9 |
|---|---|---|
| Struct type | lv_img_dsc_t |
lv_image_dsc_t |
| CF macro prefix | LV_IMG_CF_* |
LV_COLOR_FORMAT_* |
| Header size | 4 bytes (bitfield) | 12 bytes |
stride field |
❌ implicit | ✅ explicit |
flags field |
❌ absent | ✅ present |
| Magic byte | ❌ none | 0x19 |
| Compression | ❌ not supported | ✅ RLE / LZ4 |
const lv_image_dsc_t my_image = {
.header = {
.magic = LV_IMAGE_HEADER_MAGIC,
.cf = LV_COLOR_FORMAT_RGB565,
.flags = 0,
.w = 100,
.h = 100,
.stride = 200,
.reserved_2 = 0,
},
.data_size = sizeof(my_image_map),
.data = my_image_map,
.reserved = NULL,
};/* Generated for LVGL v8 — lv_img_dsc_t */
const lv_img_dsc_t my_image = {
.header.cf = LV_IMG_CF_TRUE_COLOR,
.header.always_zero = 0,
.header.reserved = 0,
.header.w = 100,
.header.h = 100,
.data_size = sizeof(my_image_map),
.data = my_image_map,
};-
Indexed formats in v8 — The palette data layout differs between v8 (
ncolors × sizeof(lv_color_t)bytes, depth-dependent) and v9 (4 bytes BGRA per entry). Generated indexed images may need manual palette adjustment for v8. -
Compression + v8 — Raises
ParameterError. LVGL v8 uses a different compression mechanism incompatible with v9's RLE/LZ4 headers. -
Reading v8
.binfiles —TRUE_COLOR(cf=4) is decoded asRGB565andTRUE_COLOR_ALPHA(cf=5) asARGB8888by default (ambiguous without knowingLV_COLOR_DEPTHat build time).
from LVGLImage import LVGLImage, ColorFormat, CompressMethod
# Convert PNG → LVGL v9 binary
img = LVGLImage().from_png("icon.png", cf=ColorFormat.RGB565)
img.adjust_stride(align=4)
img.to_bin("icon.bin") # LVGL v9 (default)
img.to_bin("icon_v8.bin", lvgl_version=8) # LVGL v8
# Convert PNG → C array
img.to_c_array("icon.c") # LVGL v9
img.to_c_array("icon_v8.c", lvgl_version=8) # LVGL v8
# Pre-multiply alpha
img = LVGLImage().from_png("icon.png", cf=ColorFormat.ARGB8888)
img.premultiply()
img.to_bin("icon_pm.bin")
# Round-trip: bin → png
img = LVGLImage().from_bin("icon.bin")
img.to_png("icon_back.png")See the LVGL project for licensing information.