Image to Byte Array for Arduino OLED (image2cpp Alternative)
If you've ever tried to put a custom logo or image on an Arduino OLED display, you've probably run into this question: "How do I
turn my PNG file into something Arduino understands?" The answer is converting your image to a byte array—a long
list of numbers that represents your image pixel by pixel. I remember being totally baffled by this the first time. The code
examples showed these huge blocks of hex numbers like 0xFF, 0x81, 0x42... and I had no clue where they came from.
Here's the thing: your Arduino can't read PNG, JPG, or GIF files directly. It has no file system, no image decoder, nothing like that. Instead, images have to be converted into a byte array stored in PROGMEM (flash memory) that your code can reference. Each byte in that array controls 8 pixels on your display. This guide explains exactly how that conversion works, the popular image2cpp tool, and when you'll want an alternative that handles animations too.
By the end, you'll understand not just how to convert images, but why the process works the way it does. That understanding will save you when things go wrong—like when your image shows up upside down, inverted, or scrambled (all mistakes I've made and will help you avoid).
What exactly is an "image to byte array" conversion?
Let me explain this in the simplest way possible, because once it clicks, everything else makes sense.
Step 1: Your image becomes black and white
OLED displays like the SSD1306 are monochrome—each pixel is either ON (lit) or OFF (dark). There's no gray, no color. So the first thing that happens is your colorful image gets converted to pure black and white. A "threshold" value decides which pixels become white and which become black. Pixels brighter than the threshold turn on; darker ones turn off.
Step 2: Pixels get packed into bytes
Now here's the clever part. Instead of storing each pixel separately (wasteful), 8 pixels get packed into a single byte. A byte
is 8 bits, and each bit represents one pixel: 1 means "on," 0 means "off." So the byte 0b11110000 (or 0xF0
in hex) means "first four pixels on, last four pixels off."
For a 128×64 pixel display, you have 8,192 pixels total. Pack them 8 per byte, and you get exactly 1,024 bytes per image. That's why people say a full-screen 128×64 frame is "1KB." Now you know where that number comes from!
Step 3: The bytes become a C++ array
Finally, all those bytes get written out as a C++ array, like this:
const unsigned char myLogo[] PROGMEM = {
0xFF, 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42,
// ... hundreds more bytes ...
};
The PROGMEM keyword tells Arduino to store this array in flash memory (where there's lots of room) instead of RAM
(where there's barely any). Then in your code, you display it with one line:
display.drawBitmap(0, 0, myLogo, 128, 64, WHITE);
That's the whole magic. Image → black & white → packed bytes → C++ array → displayed with drawBitmap. Simple once you see it laid out.
image2cpp: the classic tool for single images
When it comes to converting one image to a byte array, image2cpp (created by javl, hugely popular on GitHub) is the tool everyone reaches for. And honestly, it's excellent at what it does. I've used it countless times for static logos and icons.
How to use image2cpp
- Upload your image: Drag a PNG, JPG, or BMP into the tool
- Set the canvas size: Match your display (usually 128×64)
- Adjust the threshold: Slide it until your image looks right in the preview—this controls which pixels are black vs white
- Pick the output format: Choose "Arduino code, single bitmap" for Adafruit GFX
- Copy the generated array: Paste it into your sketch and you're done
For a logo on your project's splash screen, or a single weather icon, image2cpp is perfect. Quick, free, no nonsense.
Common image2cpp gotchas (learn from my mistakes)
- Image appears inverted: Toggle the "Invert image colors" checkbox. Sometimes your background and foreground swap.
- Wrong orientation: Make sure the "Draw mode" matches how your library reads bitmaps. Horizontal vs vertical byte ordering matters.
- Blurry or muddy result: Your source image had too many midtones. Increase contrast in an image editor first, or enable dithering.
- Array too big: You probably didn't resize the image to your display dimensions before converting.
When you need more than image2cpp (the animation problem)
Here's where image2cpp hits its limit. It's built for single images. But what if you want an animation? Say you have a 15-frame loading spinner, or you want to convert an animated GIF. With image2cpp, you'd have to:
- Manually split your GIF into 15 separate PNG files
- Convert each one individually through image2cpp (15 times!)
- Copy-paste 15 arrays into your sketch
- Write the frame-cycling logic yourself
- Add timing code for the right FPS
- Debug it all when one array has the wrong size
I did this once for a 20-frame animation. It took me over an hour and I made two copy-paste errors that caused glitches. Never again. That experience is actually what led me to build a better tool.
What an animation-focused alternative gives you
- Multi-frame conversion – Import a whole GIF or PNG sequence at once, get all frames converted automatically
- Timeline with FPS control – Adjust playback speed visually and preview before exporting
- Multiple library targets – Export for Adafruit SSD1306, U8g2, or MicroPython framebuf
- Automatic SH1106 offset – No manual coordinate adjustments for SH1106 displays
- Complete project export – Get the full working sketch (arrays + playback loop), not just isolated arrays
- Built-in dithering – Floyd-Steinberg dithering for better-looking conversions of photos and gradients
Using the OLED Animation Maker as an image2cpp alternative
Here's how to convert images (single or animated) using the free OLED animation maker. The workflow handles both static images and full animations:
- Import tab: Drop your PNG, JPG, or GIF. Every frame automatically converts to byte arrays.
- Adjust threshold and dithering: Fine-tune how the image converts to black and white, with a live preview.
- Draw tab (optional): Use the built-in pixel editor if you want to design from scratch or touch up imported frames.
- Animate tab: Arrange frames on a timeline, set your FPS, and preview the motion.
- Get the Code: Export the complete sketch—byte arrays plus the playback loop—ready to upload.
You get the same core output as image2cpp (PROGMEM byte arrays), but with the animation workflow built in and your choice of library. For single images, it works just like image2cpp. For animations, it saves you that hour of tedious manual work.
Popular searches: image to byte array arduino · image2cpp alternative · png to oled arduino · oled bitmap generator · progmem bitmap · custom image oled arduino
Frequently asked questions
What is the difference between a byte array and a bitmap?
They're basically the same thing in this context. A "bitmap" is an image made of pixels, and a "byte array" is how that bitmap is stored in code—as a list of bytes where each bit represents one pixel. When people say "convert image to byte array," they mean turning a picture into this code-friendly format.
Why use PROGMEM for image arrays?
Arduino has very little RAM (only 2KB on Uno). A single 128×64 image needs 1KB—half your RAM! PROGMEM stores the array in flash memory instead (32KB on Uno), freeing up RAM for your actual program. Always use PROGMEM for image data.
My image shows up inverted (black and white swapped). How do I fix it?
This is super common. Either toggle the "invert" option in your conversion tool, or change the color parameter in drawBitmap from WHITE to BLACK (or vice versa). It's a one-click fix once you know where to look.
Can I convert a color image, or does it have to be black and white?
You can start with any color image. The conversion tool automatically converts it to black and white using a threshold (and optionally dithering for better results). Just keep in mind the final display is monochrome, so high-contrast images work best.
What's dithering and should I use it?
Dithering simulates shades of gray using patterns of black and white dots—like how newspapers print photos. Use it for photos and gradients where you want to preserve detail. Skip it for logos, text, and simple graphics where you want clean solid shapes.