DitherInk

4-level Grayscale e-paper dithering

Four-level grayscale panels add dark gray and light gray between black and white. The extra levels make dithering patterns much finer, so photos look smoother than on a pure black and white panel.

Palette and color codes

The code is the value DitherInk writes for each pixel in .bin, .h, .png and .bmp files. The preview color is the brightened color used for matching and on-screen previews. The index color is the pure color stored in the PNG and BMP palette.

ColorCodePreview colorIndex color (PNG/BMP)
Black0#19191e#000000
Dark gray1#555555#555555
Light gray2#aaaaaa#aaaaaa
White3#ffffff#ffffff

Frame buffer format

2 bits per pixel (gray_2bpp). 4 pixels per byte, the top two bits are the leftmost pixel. The value is the gray level code. Rows are stored top to bottom and each row is padded to a whole byte.

Supported sizes

SizeResolution.bin size
2.7"264×17611,616 bytes (11.3 KB)
3.7"480×28033,600 bytes (32.8 KB)
3.7"416×24024,960 bytes (24.4 KB)
4.2"400×30030,000 bytes (29.3 KB)
7.5"800×48096,000 bytes (93.8 KB)

Convert an image to 4-level grayscale →

Ad

Tips

Use grayscale mode only if your driver supports the 4-gray waveform. Many panels can show 4 grays but default to black and white updates.

Example code (7.5" 800×480)

Arduino / ESP-IDF with the .h file

#include "my_image.h"  // downloaded from DitherInk

// MY_IMAGE_WIDTH x MY_IMAGE_HEIGHT = 800 x 480, gray_2bpp
// Color codes: black=0, dark_gray=1, light_gray=2, white=3
// The buffer is already in the panel's native orientation.

void showImage() {
  epd_write_frame(my_image, MY_IMAGE_SIZE);  // your driver's full-frame write
  epd_refresh();
}

Replace epd_write_frame and epd_refresh with the functions your display driver provides. Most drivers have a function that takes a full frame buffer.

ESP32 loading the .bin file from flash

#include <LittleFS.h>

constexpr size_t FRAME_BYTES = 96000;
static uint8_t frame[FRAME_BYTES];

bool loadFrame(const char* path) {
  File f = LittleFS.open(path, "r");
  if (!f || f.size() != FRAME_BYTES) return false;  // wrong size = wrong panel settings
  f.read(frame, FRAME_BYTES);
  f.close();
  return true;
}

Python (Raspberry Pi) reading the .bin file

import numpy as np

data = np.fromfile("my_image.bin", dtype=np.uint8)
assert data.size == 96000
codes = np.stack([(data >> s) & 0b11 for s in (6, 4, 2, 0)], axis=-1).reshape(480, -1)[:, :800]