← All articles · April 18, 2026 · pixel.hjlabs.in · Powered by image2cpp

Adafruit GFX drawBitmap vs drawXBitmap: Which Should You Use?

Adafruit_GFX exposes two near-identical methods: drawBitmap() and drawXBitmap(). The signatures match. The names differ by one letter. And if you swap them, your image displays mirrored or scrambled. This post explains the actual difference (it's the bit order) and how to pick the right output mode in image2cpp.

The one-line difference

drawBitmap reads pixels MSB-first. drawXBitmap reads pixels LSB-first. That is the entire technical difference. Everything else — coordinates, colors, dimensions — is identical.

What MSB vs LSB means here

Each byte stores 8 horizontal pixels. The question is: which pixel goes into bit 7 (the most-significant bit) and which goes into bit 0 (the least-significant bit)?

Pixel 0 (leftmost)Pixel 7 (rightmost)
drawBitmap (MSB-first)bit 7 (0x80)bit 0 (0x01)
drawXBitmap (LSB-first, XBM)bit 0 (0x01)bit 7 (0x80)

So the same row "11110000" (left half white, right half black) is:

If you feed an MSB array to drawXBitmap (or vice-versa), every byte's bits are reversed and your image looks mirrored within each 8-pixel chunk — a distinctive "stripey" corruption that confused you the first time it happened.

Why two formats exist at all

The XBM format dates back to X11 in 1989. It's a plain C source file that any X-based Unix tool could include and compile directly. GIMP and ImageMagick can export XBM in one click. Adafruit added drawXBitmap so users could paste GIMP-exported XBM data without converting bit order.

MSB-first is the natural form when you think of a byte as a left-to-right binary number, which is why most homegrown converters (including the original javl/image2cpp default) emit MSB.

Picking the right output in image2cpp

In pixel.hjlabs.in/converter, the relevant dropdowns are:

Rule of thumb:

Code: side-by-side comparison

// MSB-first bitmap, drawBitmap
const unsigned char arrowMsb [] PROGMEM = {
  0x10, 0x38, 0x7C, 0xFE, 0x10, 0x10, 0x10, 0x10
};
display.drawBitmap(0, 0, arrowMsb, 8, 8, SSD1306_WHITE);

// Same arrow as XBM (LSB-first), drawXBitmap
static const unsigned char arrow_bits [] PROGMEM = {
  0x08, 0x1C, 0x3E, 0x7F, 0x08, 0x08, 0x08, 0x08
};
display.drawXBitmap(0, 0, arrow_bits, 8, 8, SSD1306_WHITE);

Both calls draw the same upward arrow at (0, 0). Note how every byte is bit-reversed between the two arrays.

Vertical vs Horizontal byte direction

This is a separate axis of confusion. drawBitmap and drawXBitmap in Adafruit_GFX both expect horizontal packing — bytes increment left-to-right within a row, then row-by-row. U8g2's drawBitmap() in page mode, however, expects vertical packing — each byte is 8 pixels tall, and bytes go left-to-right then page-by-page. The SSD1306 itself stores its frame buffer vertically, which is why U8g2 page-mode is so memory-efficient.

If your image looks like horizontal stripes shifted by 8 pixels, you have horizontal data being read as vertical (or vice versa).

Other GFX bitmap methods you should know

MethodBit orderColor depthUse for
drawBitmapMSB1-bitStandard mono bitmaps
drawXBitmapLSB (XBM)1-bitGIMP-exported XBM
drawGrayscaleBitmap1 byte/pixel8-bit grayscaleGrayscale displays
drawRGBBitmap2 bytes/pixelRGB565Color TFT displays
drawBitmap (mask version)MSB1-bit + maskSprite blitting with transparency

Performance: any difference?

Negligible. Both methods walk the array byte by byte and shift one bit at a time. The compiler generates near-identical assembly. If you're hammering the screen at 60+ FPS and need every cycle, you'd hand-roll a tile blitter anyway.

Picking by library

LibraryRecommended callimage2cpp output
Adafruit_SSD1306 / Adafruit_GFXdrawBitmapHorizontal, MSB
Adafruit_GFX (XBM)drawXBitmapXBM (LSB)
U8g2 (full buffer)drawBitmap or drawXBMMatch the call you use
U8g2 (page buffer)drawBitmap (vertical)Vertical, LSB
TFT_eSPIdrawBitmapHorizontal, MSB (mono) or RGB565

Quick debugging chart

Bottom line

Use drawBitmap with image2cpp's default Arduino output. Use drawXBitmap only when consuming XBM files from GIMP or older codebases. The "X" in drawXBitmap is for "X11", not "extended" or "extra". Test both modes side-by-side in image2cpp with the live preview — the visual feedback makes the difference instant.

More from hjLabs.in: og.hjlabs.in for project social previews, and enhance.hjlabs.in if your source PNG needs upscaling before conversion.

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 →

More from the blog