Convert PNG to C Array for Arduino: Free image2cpp 2026 Guide
If you have an Arduino, ESP32, or RP2040 wired to an OLED, TFT or e-paper display, you cannot just fopen("logo.png"). You need the image baked into your firmware as a C++ byte array. This guide walks the entire workflow in 2026 — from a PNG on your desktop to pixels on your panel — using image2cpp, the free in-browser converter.
Why a C array (and not just an SD card load)?
Three reasons makers and embedded engineers ship images as const arrays in flash:
- No filesystem. An ATmega328 (Arduino Uno) has no SD card by default. The flash itself is the storage.
- Fast boot. A splash screen drawn from PROGMEM appears in <50ms. Loading from SPIFFS or SD takes 200-1000ms.
- Predictable memory. The compiler tells you at link time if the array fits. No surprise out-of-memory at runtime.
The format you need
The exact byte layout depends on your display:
| Display | Format | Bits / pixel |
|---|---|---|
| SSD1306, SH1106 OLED (mono) | 1-bit horizontal MSB | 1 |
| U8g2 (XBM) | 1-bit horizontal LSB (XBM) | 1 |
| ILI9341, ST7735 TFT | RGB565 big-endian | 16 |
| Waveshare e-paper (B/W) | 1-bit horizontal MSB | 1 |
| Waveshare e-paper (3-color) | two 1-bit planes (B + R) | 2 effective |
| LVGL true color | RGB565 / RGB888 / ARGB8888 | 16-32 |
Picking the wrong format produces garbled output — the most common newbie bug. image2cpp exposes every option with a live preview so you can verify before pasting into your sketch.
Step 1 — Prepare your PNG
Resize the source PNG to your display's native resolution before conversion. A 4K logo squished to 128x64 looks worse than one you manually downsampled in GIMP or Photoshop with a sharpen pass.
For monochrome displays, also crank contrast and consider Floyd–Steinberg dithering. The tool can do this for you, but a good source image always wins.
Step 2 — Convert with image2cpp
Open pixel.hjlabs.in/converter. The flow:
- Drag the PNG into the upload zone
- Set canvas size (e.g. 128x64)
- Pick threshold or dithering mode
- Choose output format (matches your display from the table above)
- Copy the generated array
Step 3 — Drop into your Arduino sketch
Here is a complete working sketch for a 128x64 SSD1306 with a custom logo:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// 'logo', 128x64px — output of image2cpp
const unsigned char PROGMEM logo [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// ... 1024 bytes total for 128x64 mono ...
};
void setup() {
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
for(;;); // halt if init fails
}
display.clearDisplay();
display.drawBitmap(0, 0, logo, 128, 64, SSD1306_WHITE);
display.display();
}
void loop() {}
Step 4 — PROGMEM-safe array declaration
Always declare the array as const unsigned char PROGMEM on AVR boards (Uno, Nano, Mega). Without PROGMEM the compiler stuffs the array into SRAM and your sketch crashes the moment SRAM fills up — which is fast, since an Uno only has 2KB.
On ESP32, ESP8266 and ARM boards (RP2040, STM32), PROGMEM is an alias that has no effect — flash is read directly. But keeping it in the source preserves portability across platforms.
Common errors and fixes
- Image looks shifted / striped. Wrong byte direction (vertical vs horizontal). In image2cpp, toggle "Vertical — 1 bit per pixel" vs "Horizontal — 1 bit per pixel".
- Image is mirrored. XBM (LSB-first) vs MSB-first. Switch the output mode.
- Colors are inverted on TFT. Endianness. Set
tft.setSwapBytes(true)on TFT_eSPI, or invert in the converter. - Compile error: section .progmem.data full. The image exceeds available flash. Reduce resolution, or move to a board with more flash.
- Random pixels. You declared array length wrong. The byte count is
ceil(width / 8) * heightfor mono,width * height * 2for RGB565.
Sizing the image to fit your flash
Quick math:
- Mono 128x64 = 1024 bytes
- Mono 128x128 = 2048 bytes
- RGB565 128x160 (ST7735) = 40,960 bytes — too big for a Uno (32KB flash), fine for ESP32 (4MB).
- RGB565 320x240 (ILI9341) = 153,600 bytes — needs ESP32, RP2040 or larger MCU.
Beyond static images: animations
For a sprite or animation, generate one C array per frame, store all of them in PROGMEM, then iterate with millis()-based timing. We covered this in detail in our Arduino OLED tutorial and a dedicated post on frame-by-frame OLED animation.
Why image2cpp over alternatives?
The original javl/image2cpp on GitHub is great but ad-supported and unmaintained. pixel.hjlabs.in is a fork with a cleaner UI, batch upload, more output formats (LVGL, Waveshare 3-color e-paper), and zero ads. Everything still runs in the browser — your image never uploads to a server.
Bottom line
The 2026 workflow: prepare a PNG at native resolution, convert with image2cpp, paste into a const PROGMEM array, and call drawBitmap or pushImage. Open the converter now and ship pixels in five minutes.
Related from hjLabs.in: generate matching favicons and OG images for your project's GitHub README; if your firmware emits JSON over serial, the JSON formatter at fmt.hjlabs.in is handy for debugging.
Try image2cpp now — free, in browser
Drop in a PNG, JPG or BMP. Get a paste-ready C++ array for Arduino, ESP32 or RP2040. 100% client-side. Learn more about image2cpp or jump straight to the tool.
Open the converter →