# Image to C Array

> Turn a PNG, JPG or SVG into a C array for an OLED, TFT or e-paper panel — LVGL, Adafruit GFX, u8g2, TFT_eSPI — and read a framebuffer dump back into a picture.

- URL: https://bytescope.dev/en/image-to-c-array/
- Locale: en · Also: [ja](https://bytescope.dev/ja/image-to-c-array.md) · [zh-Hant](https://bytescope.dev/zh-Hant/image-to-c-array.md)
- Runs: Your images never leave your browser — every pixel is converted locally.
- Requires: Any modern browser (Chrome, Edge, Firefox, Safari), desktop and mobile.
- Updated: 2026-09-08

## What it does

### The array your display actually wants

A display driver does not take a PNG. It takes a run of bytes laid out exactly the way its controller reads them, and every part of that layout is a decision somebody has to make: one bit per pixel or sixteen, the leftmost pixel at bit 7 or at bit 0, rows one after another or eight-pixel-tall pages, red in the top five bits or the bottom five. Drop an image here, pick the format, and the bytes are packed to match — `mono`, `gray2`, `gray4`, `gray8`, the `alpha1` to `alpha8` masks LVGL wants, `rgb565`, `rgb888` and `argb8888`. The preview beside the controls is not the source image scaled down; it is the packed array read back out again, so what you are looking at is what the panel will show.

### One bit per pixel, and how to spend it

A 128x64 OLED gives you 8,192 pixels and one bit each, so the whole picture comes down to where the cut between black and white falls. For line art and icons a plain threshold is the right answer, and the slider moves it. A photograph needs the error spread around instead: Floyd–Steinberg diffuses it into the neighbouring pixels and gives the grainy newsprint look, while an ordered Bayer 4x4 matrix gives a regular cross-hatch that often reads more cleanly on a small panel. Brightness and contrast are applied before the cut, `invert` after it, and each of them redraws the preview as you drag — because the only way to know whether an icon survives one bit is to look at it at the size it will be shown.

### Reading a framebuffer back into a picture

The other direction is the one nobody builds, and it answers the question you actually have: is this array right? Paste a C array — `0x` bytes, plain decimal, `\x` escapes, a bare hex blob, with or without braces, comments and `#define` lines — or drop a raw `.bin` you pulled out of flash, and it is drawn as an image. Then drag the width slider. A framebuffer read at the wrong width shears into diagonal stripes, and the moment the width is right the picture snaps into place; that is the standard way to identify an unknown dump. It works just as well as a check on your own output: convert an icon, paste the array back, and if what comes out looks like your icon then the bit order and the row padding are right. A token that cannot be read is reported with its position rather than swallowed, so one typo in a pasted array costs one value and never the rest of the file.

### Templates, byte order, and the one that bites

The same bytes are wrapped differently depending on what is going to compile them: a plain `static const uint8_t` array, an Adafruit GFX `PROGMEM` array with `_WIDTH` and `_HEIGHT` macros, a u8g2 XBM with `#define` dimensions, an `lv_img_dsc_t` for LVGL v8 or an `lv_image_dsc_t` for v9, a `uint16_t` array for TFT_eSPI's `pushImage()`, or a MicroPython `bytes` literal with the `framebuf` constructor written out beside it. TFT_eSPI is the one that catches people: it reads each 16-bit pixel high byte first, so RGB565 has to be packed big-endian for that template and little-endian for every other one. Pick the template and the byte order follows it, because an array that compiles cleanly and displays in the wrong colours is a bug nobody enjoys finding. All of it runs in a Web Worker in this tab: no upload, no account, and the artwork for the product you have not announced yet stays on your machine.

## Limits

Does not emit LVGL's packed binary .bin image header (it writes the C struct literal a project compiles), does not generate bitmap fonts, does not decode animated GIF past the first frame, and does not dither RGB565, RGB888 or ARGB8888 per channel.

## FAQ

### What settings does an SSD1306 OLED need?

Format `mono`, pixel order set to pages, and LSB first. An SSD1306 or SH1106 stores its memory as eight-pixel-tall vertical pages, one byte per column, with the topmost pixel of the page in bit 0 — that is exactly what page order plus LSB first produces, and it is why a straightforward row-by-row array comes out looking like eight interleaved slices of your image. If you are feeding u8g2's XBM helpers rather than writing to the controller directly, use rows and MSB first instead.

### Why does my TFT_eSPI image come out the wrong colour?

Byte order, almost always. `pushImage()` reads a 16-bit buffer with the high byte first, so RGB565 packed low-byte-first turns pure red `0xf800` into `0x00f8`, a dark blue-grey — the array compiles, the size is right, and only the colours are wrong. Selecting the TFT_eSPI template here packs big-endian for you and says so. If you are pasting an array from somewhere else and the colours look scrambled, load it into the reverse viewer and switch the byte order there; the picture will tell you immediately which one it was written with.

### Can it produce LVGL's binary image file?

No, and it will not pretend to. What it emits is C source: the pixel array plus an `lv_img_dsc_t` (v8) or `lv_image_dsc_t` (v9) struct literal with named fields, which is what you add to a project and compile. The packed `.bin` form with its own header is a different artefact and is not implemented here rather than being guessed at. The colour-format field is also left to you — pass the token your build expects and it is written through verbatim; leave it empty and you get an `UNKNOWN` token with a `TODO` beside it, never an invented constant.

### I have a flash dump and no idea how wide the image is.

Drop the `.bin` into the reverse viewer, guess the pixel format, and drag the width scan from 1 upwards. Any width but the right one shifts each row by a constant amount, which turns straight edges into diagonals, so the display shears — until the width matches the real stride and everything lines up at once. Then adjust the offset to skip whatever header sits in front of the pixels. Short data is never an error here: pixels with no bytes behind them are drawn transparent and counted, so you can scan a partial dump without the tool refusing to show it.

### Is my image uploaded anywhere?

No. The file is decoded by your own browser, the pixels are quantized and packed in a Web Worker inside this tab, and everything is dropped when you close it. There is no upload step, no account and no server side to this page. That matters more here than on most tools: the icon set for an unreleased product is exactly the kind of file people are told not to paste into an online converter.

## Related tools

- [MicroPython Web IDE](https://bytescope.dev/en/micropython.md) — Paste the framebuf bytes straight into a board running MicroPython.
- [ESP Firmware Flasher](https://bytescope.dev/en/esp-flasher.md) — Write the firmware that carries the array to an ESP32 or ESP8266 over USB.
- [Hex Viewer / Editor](https://bytescope.dev/en/hex-editor.md) — Open the raw bytes, find the field you need, and edit it.
- [Image Format Converter](https://bytescope.dev/en/image-convert.md) — Resize or re-encode the artwork before it becomes an array.
