DitherInk

How e-paper frame buffers are laid out

Last updated 2026-10-28

When an image shows up shifted, mirrored, inverted or in the wrong colors, the pixels are almost always right but packed differently from what the driver expects. This guide explains the layouts DitherInk writes and the options that change them.

Rows, bytes and padding

Every format stores pixels row by row, top to bottom, left to right. Each row starts on a new byte. If the width is not a multiple of the pixels that fit in a byte, the end of the row is padded.

For example, a 250-pixel-wide black and white panel stores 8 pixels per byte. 250 ÷ 8 = 31.25, so each row takes 32 bytes and the last 6 bits are padding. A buffer for 250×122 is therefore 32 × 122 = 3,904 bytes, not 250 × 122 ÷ 8 = 3,812.5. If your driver’s buffer size does not match, check the row padding first.

1 bit per pixel (black and white)

Eight pixels per byte. With MSB first (the default), the highest bit is the leftmost pixel:

pixels:  W B W B W B W B
bits:    1 0 1 0 1 0 1 0   = 0xAA
  • 1 = white, 0 = black is the most common convention. If your image appears as a negative, tick Invert bits.
  • Some drivers read the lowest bit first. Choose LSB first and the same pixels become 0x55.

Two planes (3-color panels)

Black/white/red and black/white/yellow panels usually take two separate 1-bit buffers of the same size:

  1. Black and white plane: 1 where the pixel is not black.
  2. Color plane: 1 where the pixel is red (or yellow).

The .bin file contains the black and white plane followed by the color plane. In a .h file you can keep them as one array or tick Split planes to get name_black[] and name_color[]. If your driver treats 0 as “color”, untick Color plane: 1 means color. If it expects the color plane first, change Plane order.

2 bits per pixel (4-gray and 4-color)

Four pixels per byte, leftmost pixel in the top two bits. Each value is a code from 0 to 3:

codes:   0  1  2  3
bits:    00 01 10 11   = 0x1B

4 bits per pixel (6-color and 7-color)

Two pixels per byte. The high nibble is the left pixel. Each nibble is a color code:

codes:   0 1  2 3  5 6
bytes:   0x01 0x23 0x56

Color codes are not universal. For 6-color panels DitherInk writes black=0, white=1, yellow=2, red=3, blue=5, green=6; for 7-color panels black=0, white=1, green=2, blue=3, red=4, yellow=5, orange=6. If colors come out swapped, compare these with your driver’s color table and edit Color codes under Advanced output options. Your changes are remembered for next time.

Rotation

The panel has a fixed native layout, for example 800×480 for a 7.5” panel. When you choose Portrait, you edit a 480×800 image and DitherInk rotates the buffer 90° counterclockwise so it matches the native layout. If the image ends up upside down on your frame, set Buffer rotation to 270°. Rotation values are counterclockwise.

If you override the rotation and the resulting buffer no longer matches the native resolution, DitherInk still creates the file but shows a warning.

Column-major scanning

A few controllers fill their RAM column by column instead of row by row. Column-major scan writes the buffer that way. It only affects .bin and .h files; PNG and BMP files always contain the picture as you see it.

PNG and BMP files

Indexed PNG and BMP files store the color code as each pixel value, with the palette set to the pure colors. That makes them easy to read in Python: the pixel value is exactly what the panel expects.

from PIL import Image
codes = Image.open("my_image.png").load()
print(codes[0, 0])  # color code of the top-left pixel

Open the converter →

Ad