This u8g2 bitmap converter turns any image into a paste-ready XBM byte array for the u8g2 library — the de-facto standard graphics library for monochrome OLED and LCD displays on Arduino, ESP32, ESP8266 and RP2040. Drop in a PNG, JPG or BMP, match the canvas to your screen, and copy a static const unsigned char array you can render with u8g2.drawXBMP() in seconds. It is the missing companion to the classic image2cpp converter, tuned specifically for u8g2's XBM byte order.
What is u8g2 and why XBM byte order matters
u8g2 (by olikraus) is a full-featured monochrome graphics library that drives almost every 1-bit display a maker is likely to use: SSD1306 and SH1106 OLEDs (128×64 and 128×32), ST7565/ST7567 LCDs, the Nokia 5110 (PCD8544), and dozens more. Its drawXBMP() function expects bitmap data in the X BitMap (XBM) format, where the bits inside each byte are packed least-significant-bit first and every row is padded out to a whole number of bytes. This converter emits exactly that layout, so the image you see in the preview is the image u8g2 draws — no mirrored 8-pixel blocks, no off-by-one row shifts.
If you have ever pasted an array from a generic tool into u8g2 and watched it render as garbled vertical stripes, the cause is almost always a byte-order mismatch. Generic Adafruit-GFX-style arrays are MSB-first; u8g2's drawXBMP() is LSB-first. Toggle the Swap bits option here if your particular u8g2 build or a hand-edited array expects the opposite order.
Wiring an SSD1306 OLED for u8g2 (I2C)
The most common u8g2 target is a 0.96" SSD1306 OLED over I2C. Wire it to an Arduino Uno/Nano like this:
- VCC → 3.3V or 5V (most modules are 5V tolerant)
- GND → GND
- SDA → A4 (Uno/Nano) · GPIO21 (ESP32) · D2 (ESP8266)
- SCL → A5 (Uno/Nano) · GPIO22 (ESP32) · D1 (ESP8266)
Install U8g2 from the Arduino Library Manager (Sketch → Include Library → Manage Libraries → search "U8g2"). The library bundles every display constructor, so you only pick the one matching your panel.
Full Arduino sketch — display your bitmap with u8g2
Generate your array above, then drop it into this skeleton. Replace myBitmap with the variable name you chose:
#include <U8g2lib.h>
#include <Wire.h>
// Full-buffer constructor for a 128x64 SSD1306 on I2C
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
// <-- paste the generated bitmap array here -->
void setup() {
u8g2.begin();
}
void loop() {
u8g2.clearBuffer();
u8g2.drawXBMP(0, 0, myBitmap_width, myBitmap_height, myBitmap);
u8g2.sendBuffer();
delay(2000);
}
On a memory-constrained AVR board, the U8X8_PROGMEM qualifier (already added to the generated array) keeps the bitmap in flash so it does not eat your 2 KB of SRAM. On ESP32, ESP8266 and RP2040 the array is stored in flash automatically. If you have many large bitmaps and run low on flash, consider the page-buffer constructors (the 1 or 2 variants instead of F) to trade RAM for draw passes.
Choosing a dithering algorithm
Monochrome OLEDs can only show pixels fully on or off, so converting a photo or gradient requires dithering — scattering black and white dots to simulate grey. Floyd-Steinberg (the default) gives the most faithful result for photos. Atkinson produces a cleaner, higher-contrast look favoured for logos and UI icons. Bayer 4×4 gives a retro, regular cross-hatch. Binary threshold does no dithering at all — ideal for line art, fonts and one-colour logos. Adjust the threshold slider to push more pixels on or off.
Need the Adafruit GFX flavour instead, or color/RGB565 output for a TFT? Use the full image2cpp converter. Building an LVGL UI on an ESP32? Try the LVGL image converter. Driving a Waveshare e-ink panel? See the e-paper converter.