Image to RGB565 Converter
Turn an image into an RGB565 C array, preview it on the panel's real pixel grid and see the flash cost as you go. Presets for ILI9341, ST7789, ST7735, GC9A01 and ILI9488, ready-made TFT_eSPI and Adafruit_GFX sketches, and multi-frame output. Files never leave your browser.
Leaving swap off and calling setSwapBytes(true) in TFT_eSPI is the common setup. Whichever you choose, the generated sketch writes the matching line.
Load an image to see the generated array.Getting an image onto a TFT display
On a microcontroller you do not open an image file, you compile the image in as a C array. ILI9341, ST7789, ST7735, GC9A01 and ILI9488 all expect 16 bits per pixel in RGB565. This page reduces the image to that format, shows the result at the panel's real resolution, and tells you how many bytes the array costs.
How RGB565 is packed
The top 5 bits of red, the top 6 of green and the top 5 of blue go into a single 16-bit word:
uint16_t rgb565 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);Green gets the extra bit because the eye is most sensitive to it. What is left is 32, 64 and 32 steps per channel, so smooth 24-bit gradients snap to those steps and band. Turn on dithering to break the bands: Floyd-Steinberg pushes the rounding error into the neighbouring pixels.
Blitting the array
With TFT_eSPI it is a single call, once the byte order is settled:
#include <TFT_eSPI.h>
#include "tft_image.h"
TFT_eSPI tft = TFT_eSPI();
void setup() {
tft.init();
tft.fillScreen(TFT_BLACK);
tft.setSwapBytes(true); // array generated big-endian
tft.pushImage(0, 0, TFT_IMAGE_WIDTH, TFT_IMAGE_HEIGHT, tft_image);
}On Adafruit_GFX the equivalent is tft.drawRGBBitmap(x, y, array, width, height). That API never swaps bytes, so leave Swap bytes off when you are targeting it.
If the colours come out wrong
Red and blue trading places does not mean the image is broken, it means the two bytes reach the driver in the wrong order. Fix it in exactly one place: either generate the array pre-swapped or let the library swap, never both. When the same image looks right on one board and inverted on another, this line is usually the difference.
What it costs in flash
In RGB565 the size is simply width x height x 2. A few common panels:
- 128x128 = 32,768 bytes (32 KB)
- 160x128 = 40,960 bytes (40 KB)
- 240x240 = 115,200 bytes (112.5 KB)
- 320x240 = 153,600 bytes (150 KB)
- 480x320 = 307,200 bytes (300 KB)
So one full-screen image already overruns an Arduino Uno's entire 32 KB of flash, while it sits comfortably in the ESP32's 1.25 MB app partition. The budget bars on the page make that comparison live. If you are tight on space, in order: shrink the image, switch to RGB332 or 8-bit grayscale, or for a single-colour logo generate a 1-bit bitmap with LCD Assistant, which costs one bit per pixel.
Round panels and transparency
A round panel such as the GC9A01 still expects a rectangular buffer, it simply never lights the corners. The round mask fills everything outside the circle with the background colour and switches the preview to a round bezel, so overflowing corners do not fool you into thinking the image is off-centre. If you would rather the background not be drawn at all, tick transparent colour: the generated sketch passes that colour to pushImage and pixels matching it are left as they are on screen.
Multiple frames
Select several files and they all go into one header, followed by a _frames[] pointer table, and the generated TFT_eSPI sketch cycles through it in loop(). If your source is a GIF, look at the GIF to OLED animation tool instead of splitting frames by hand. For a single colour value the RGB565 colour converter is quicker, and if you are on LVGL the LVGL image converter emits the lv_image_dsc_t form.
Frequently Asked Questions
Red and blue are swapped on the display
Byte order. Add tft.setSwapBytes(true) in TFT_eSPI, or tick Swap bytes here. Do both and the swap is applied twice, which puts you back where you started.
The image is skewed or shifted on screen
Almost always the width in the array and the width passed to pushImage disagree. Use the _WIDTH and _HEIGHT defines from the header instead of typing numbers. The other common cause is rotation: setRotation has to match the aspect you converted for.
Gradients look banded
RGB565 only has 32/64/32 steps per channel, so smooth ramps snap to them. Turn on dithering. Leave it off for hard-edged logos, where it only adds noise along the edges.
The compiler says "will not fit in region"
The array does not fit in flash. Shrink the image, switch to RGB332 or 8-bit grayscale, or put the image on SPIFFS/LittleFS as a raw .bin and read it at runtime. The Download raw .bin button produces exactly that file.
Should I turn PROGMEM off?
Never on AVR: without it the array is copied into RAM and the board hangs at boot. On ESP32 and RP2040 PROGMEM is already a no-op, const data is read straight from flash, so leaving it ticked costs nothing either.
Are my images uploaded anywhere?
No. Files are read into browser memory, processed on a canvas, and no network request is made at any point. The page works offline once loaded.