#!/usr/bin/env python3 """Preview the exact Ch438 source-space 3x3 binomial scanout.""" import math import sys from PIL import Image def binom3(a, b, c): return tuple((x + 2 * y + z + 2) // 4 for x, y, z in zip(a, b, c)) def score(a, b): ae = 0 se = 0 n = a.width * a.height * 3 for pa, pb in zip(a.getdata(), b.getdata()): for ca, cb in zip(pa, pb): d = int(ca) - int(cb) ae += abs(d) se += d * d return ae / n, math.sqrt(se / n) def main(argv): if len(argv) != 4: raise SystemExit( "usage: preview_scanout_ch438.py " ) src = Image.open(argv[1]).convert("RGB") if src.size != (640, 480): raise SystemExit(f"expected 640x480 framebuffer, got {src.size}") ref = Image.open(argv[2]).convert("RGB").resize( (640, 480), Image.Resampling.BILINEAR ) # RTL rounds after each separable pass, so mirror that order exactly. horizontal = Image.new("RGB", src.size) hp = horizontal.load() for y in range(480): for x in range(512): xm1 = max(0, x - 1) xp1 = min(511, x + 1) hp[x, y] = binom3(src.getpixel((xm1, y)), src.getpixel((x, y)), src.getpixel((xp1, y))) filtered = Image.new("RGB", src.size) fp = filtered.load() for y in range(32, 480): ym1 = max(32, y - 1) yp1 = min(479, y + 1) for x in range(512): fp[x, y] = binom3(horizontal.getpixel((x, ym1)), horizontal.getpixel((x, y)), horizontal.getpixel((x, yp1))) out = Image.new("RGB", (640, 480)) px = out.load() for y in range(480): sy = 32 + (y * 14) // 15 for x in range(640): sx = (x * 4) // 5 px[x, y] = filtered.getpixel((sx, sy)) out.save(argv[3]) mae, rmse = score(out, ref) print(f"binomial3x3 MAE={mae:.4f} RMSE={rmse:.4f}") print(f"wrote {argv[3]}") if __name__ == "__main__": main(sys.argv)