Skip to content

Instantly share code, notes, and snippets.

@kdungs
Created December 28, 2023 21:55
Show Gist options
  • Save kdungs/8f04beb784f758f8f74cf81fd905a0f1 to your computer and use it in GitHub Desktop.
Save kdungs/8f04beb784f758f8f74cf81fd905a0f1 to your computer and use it in GitHub Desktop.
Bayer dithering in Python. Useful for Playdate development...
#!/usr/bin/env python3
import argparse
import pathlib
import numpy as np
from PIL import Image
bayer2x2 = 1/4 * np.array([
[0, 2],
[3, 1],
])
bayer4x4 = 1/16 * np.array([
[ 0, 8, 2, 10],
[12, 4, 14, 6],
[ 3, 11, 1, 9],
[15, 7, 13, 5],
])
bayer8x8 = 1/64 * np.array([
[ 0, 32, 8, 40, 2, 34, 10, 42],
[48, 16, 56, 24, 50, 18, 58, 26],
[12, 44, 4, 36, 14, 46, 6, 38],
[60, 28, 52, 20, 62, 30, 54, 22],
[ 3, 35, 11, 43, 1, 33, 9, 41],
[51, 19, 59, 27, 49, 17, 57, 25],
[15, 47, 7, 39, 13, 45, 5, 37],
[63, 31, 55, 23, 61, 29, 53, 21],
])
MATS = {
"2x2": bayer2x2,
"4x4": bayer4x4,
"8x8": bayer8x8,
}
def dither(fname: pathlib.Path, oname: pathlib.Path, mat: np.ndarray):
img = Image.open(fname).convert("L")
pix = np.array(img) / 255
h, w = pix.shape
mh, mw = mat.shape
tmat = np.resize(
np.tile(mat, (int(np.ceil(h / mh)), int(np.ceil(w / mw)))),
pix.shape,
)
dithered = (pix > tmat).astype(np.uint8) * 255
Image.fromarray(dithered).save(oname)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
prog="dither",
description="apply ordered (Bayer) dithering to an image",
)
parser.add_argument(
"filename",
type=pathlib.Path,
)
parser.add_argument(
"-o", "--out",
help="if not specified, resulting image will have _dithered suffix",
type=pathlib.Path,
)
parser.add_argument(
"-m", "--matrix",
choices=["2x2", "4x4", "8x8"],
default="4x4",
)
args = parser.parse_args()
fname = args.filename
oname = args.out
if oname is None:
oname = fname.parent / f"{fname.stem}_dithered{fname.suffix}"
dither(fname, oname, MATS[args.matrix])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment