#!/usr/bin/env python3
"""
Deterministically recolor 'unifont-16.0.04-acj.png' by:
1. (Optional) median‐filter smoothing of the B/W mask
2. Counting black & white pixels
3. Generating all 24-bit RGB colors, sorting by luminance
4. Taking the darkest N and lightest M colors (no randomization)
5. Sorting each pool by Morton (Z-order) code to group similar colors
6. Rebuilding the image and saving as 'allrgb-plane0.png'
Ignores unknown CLI args (e.g. Jupyter’s -f).
"""
import argparse
import sys
from PIL import Image, ImageFilter
def luminance(color):
r, g, b = color
return 0.2126 * r + 0.7152 * g + 0.0722 * b
def morton_code(color):
"""Interleave the 8 bits of R, G, B into a 24-bit Morton (Z-order) code."""
r, g, b = color
code = 0
for bit in range(8):
# take bit (7-bit) down to 0
shift = 7 - bit
code <<= 3
code |= ((r >> shift) & 1) << 2 \
| ((g >> shift) & 1) << 1 \
| ((b >> shift) & 1)
return code
def parse_args():
p = argparse.ArgumentParser(
description="Deterministic recolor of B/W image with Morton-ordering",
add_help=False
)
p.add_argument(
"--median-filter-size", "-m",
type=int, default=1,
help="Size of median filter to smooth B/W mask (1 = no filter)"
)
p.add_argument(
"-h", "--help",
action="help",
help="Show this help message and exit"
)
# ignore unknown args
args, _ = p.parse_known_args()
return args
def main():
args = parse_args()
input_path = "unifont-16.0.04-acj.png"
output_fn = "allrgb-plane0.png"
# Load & B/W convert
try:
img = Image.open(input_path)
except IOError as e:
print(f"Error opening '{input_path}': {e}", file=sys.stderr)
sys.exit(1)
if img.mode != "L":
img = img.convert("L")
# Optional median‐filter
if args.median_filter_size > 1:
img = img.filter(ImageFilter.MedianFilter(size=args.median_filter_size))
w, h = img.size
pix_bw = img.load()
# Count black vs. white
black_count = white_count = 0
for y in range(h):
for x in range(w):
v = pix_bw[x, y]
if v == 0: black_count += 1
elif v == 255: white_count += 1
else:
raise ValueError(f"Non-B/W pixel {v} at {(x,y)}")
# Generate & sort all 24-bit colors by luminance
all_colors = [(r, g, b)
for r in range(256)
for g in range(256)
for b in range(256)]
all_colors.sort(key=luminance)
# Deterministic pools
darkest = all_colors[:black_count]
lightest = all_colors[-white_count:] if white_count else []
# Morton-order each pool to group similar colors
darkest.sort(key=morton_code)
lightest.sort(key=morton_code)
# Rebuild recolored RGB image
out = Image.new("RGB", (w, h))
di = iter(darkest)
li = iter(lightest)
for y in range(h):
for x in range(w):
if pix_bw[x, y] == 0:
out.putpixel((x, y), next(di))
else:
out.putpixel((x, y), next(li))
out.save(output_fn)
print(f"Recolored image saved to '{output_fn}'")
if __name__ == "__main__":
main()