⌁ Embedded & Hardware

Image to C Array Converter (OLED & TFT)

Convert PNG, JPG, BMP or GIF images into C byte arrays for SSD1306/SH1106 OLEDs and TFT displays — 1-bit, RGB565 and grayscale — with a preview rendered from the bytes.

SSD1306 & Adafruit GFX RGB565 for TFT_eSPI Dithering Preview from bytes

Loading Image to C Array…

What this page sends

  • Your input: The image is decoded and resampled with the Canvas API in this tab and not sent to a server.
  • Page load: Loading the page requests HTML, scripts and images from ByteKiln, fonts from Google Fonts, and sends Google Analytics page views, tool-usage events and catalog interactions (tool/category IDs, result status and whether a click followed search — never your input, output or search terms). Privacy & sharing

How the Image to C Array Converter (OLED & TFT) Works

Putting a logo or an icon on a small display means turning an image into exactly the bytes your graphics library expects — and every library expects something slightly different. Adafruit GFX's drawBitmap() reads 1-bit rows left to right with the most significant bit first, padded to a whole byte. U8g2's XBM functions read the same rows least significant bit first. The SSD1306 controller's own memory is organised in vertical 8-pixel pages. Colour TFTs want 16-bit RGB565, and some drivers want those two bytes swapped. Get any of it wrong and the display shows noise, a mirror image, or a negative. This converter resamples your image to the display size with the fit mode you choose, applies rotation, flipping and a background colour for transparent pixels, converts to 1-bit with a threshold or one of three dithering methods (or to colour or grayscale), and writes a C array with the right type, optional PROGMEM and size #defines. The preview is decoded back from those generated bytes, so what you see is what the display will draw.

Resampling

The image is decoded with createImageBitmap, which applies EXIF orientation, then drawn onto a canvas at the display size using contain, cover, stretch or the original size. Rotation and flipping are applied to the resampled pixels, and transparent areas are composited over the background colour you pick.

1-bit conversion

Pixels are converted to grayscale (Rec. 601 luma) and compared with the threshold. Floyd–Steinberg and Atkinson dithering diffuse the rounding error to neighbouring pixels, which keeps gradients and photos recognisable; ordered (Bayer 4×4) dithering produces a regular pattern that compresses and animates better.

Byte layouts

Horizontal: byte = 8 pixels of a row, bit 7 = leftmost. XBM: bit 0 = leftmost. Vertical pages: byte = 8 pixels of a column within an 8-row page, bit 0 = top. RGB565: 5 bits red, 6 green, 5 blue in a uint16_t, optionally byte-swapped. The encoders and decoders are round-trip tested on random bitmaps.

Code output

The variable name is turned into a valid C identifier, arrays are emitted with hex or binary literals, and the byte count is checked against AVR limits: more than 2 KB without PROGMEM exceeds an ATmega328P's RAM, and more than 32 KB exceeds its flash. An example sketch for Adafruit_SSD1306 or TFT_eSPI is included.

Working with the Image to C Array

Worked example: a 128×64 logo on an SSD1306

Pick the 128×64 preset, 1-bit output and Horizontal (Adafruit GFX) byte order. Each row of 128 pixels becomes 16 bytes, most significant bit on the left, so the whole screen is 16 × 64 = 1,024 bytes — small enough for an ATmega328P's flash with PROGMEM, and far too big for its 2 KB of RAM without it.

Adafruit_SSD1306 draws that array with drawBitmap(x, y, array, width, height, color). The tool generates the array and #defines; the sketch below is the matching example it offers. If the image comes out as vertical stripes, you picked Vertical pages (the SSD1306's native GDDRAM layout, for writing straight into the display buffer) — switch back to Horizontal.

Arduino sketch (Adafruit_SSD1306)
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// Paste the generated array here:
// #define LOGO_WIDTH  128
// #define LOGO_HEIGHT 64
// const uint8_t logo[] PROGMEM = { 0x00, 0x00, ... };  // 1,024 bytes

Adafruit_SSD1306 display(128, 64, &Wire, -1);

void setup() {
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {  // 0x3D on some 128x64 modules
    for (;;) {}
  }
  display.clearDisplay();
  display.drawBitmap(0, 0, logo, 128, 64, SSD1306_WHITE);
  display.display();
}

void loop() {}

Why does my bitmap draw garbled?

Almost always a byte-layout mismatch, not a bad image. Diagonal smearing means the width in drawBitmap() doesn't match the array (a 120-pixel-wide image still occupies 15 whole bytes per row — pass the real width, not a rounded one). Mirrored 8-pixel blocks mean the library expects least-significant-bit-first rows (XBM, used by U8g2's drawXBM). An inverted image means your display draws 1 as off: use the Invert option or draw with SSD1306_BLACK on a white background.

Limitations

  • One image at a time, up to 4,096 px per side before resizing. Animated GIFs use the first frame only.
  • Output is uncompressed: no RLE or LVGL image descriptors, and no palette-indexed formats beyond 1-bit, 4-bit and 8-bit grayscale, RGB332, RGB565 and RGB888.
  • The preview is rendered from the generated bytes, but it can't know your display's orientation, offset or color order; if the panel shows BGR or mirrored output, adjust rotation, flip or byte swap.
  • The PROGMEM warnings are for AVR (ATmega328P) limits; ESP32 and ARM boards keep const arrays in flash without PROGMEM.

FAQ

Short answers for the things developers usually ask before trusting a tool.

Is my image uploaded?

No. The image is decoded and resampled with your browser's Canvas API, and the bytes are generated in the page. The image is not sent to a server.

Which output format does my display need?

For Adafruit GFX drawBitmap() on an SSD1306 or SH1106, use 1-bit horizontal. For U8g2 drawXBMP() or Adafruit drawXBitmap(), use XBM. To copy straight into an SSD1306 frame buffer, use vertical pages. For colour TFTs driven by TFT_eSPI or Adafruit ST77xx, use RGB565.

Why is my image inverted or scrambled on the display?

Inverted: by default bright pixels are "on" (lit on an OLED); turn on Invert for dark artwork on a light background. Scrambled or striped: the byte layout doesn't match the draw function — horizontal vs vertical, or MSB vs LSB first (XBM). The preview is drawn from the generated bytes, so it shows the same mistake the display would.

Do I need PROGMEM?

On AVR boards like the Uno and Nano, yes — without it the array is copied into the 2 KB of RAM at startup. drawBitmap() expects PROGMEM data there. On ESP32, RP2040 and STM32, const arrays already stay in flash, so PROGMEM is harmless but unnecessary.

What happens with widths that aren't a multiple of 8?

In the horizontal and XBM formats each row is padded to a whole byte with zero bits, which is what drawBitmap() expects. In the vertical page format, a height that isn't a multiple of 8 pads the last page. The generated code includes a comment when padding is added.

Are phone photos rotated correctly?

Yes. The EXIF orientation that phone cameras write is applied when the image is decoded. Very large photos are first scaled down to 4096 pixels on the longest side, and animated GIFs use their first frame.

Related tools

Useful follow-ups when one conversion usually turns into three more.

Want the background and worked examples? There's a longer write-up.

Read the Image to C Array guide

Related guides