From 667267d9bba60036db965eb60ebbbc295ede8a5c Mon Sep 17 00:00:00 2001 From: Dorian Burton <120594826+dburton95@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:56:08 -0400 Subject: [PATCH] Fixes the greyscale cutoff for boulder.png A fixed cutoff (e.g. "<=200 is ink") only makes sense for sprites with a light background to split against; a mostly-opaque 16x16 icon has almost no pixel above that cutoff, so every such icon collapsed onto the same "all ink" hash and was flagged as a near-duplicate of anything else that also collapsed -- which was most of them, boulder.png included. Thresholding against the image's own mean keeps the split meaningful (and roughly balanced) no matter how light or dark the source is. --- tools/modkit.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tools/modkit.py b/tools/modkit.py index 64b212d5..99d98089 100644 --- a/tools/modkit.py +++ b/tools/modkit.py @@ -988,14 +988,25 @@ def cmd_add_release_workflow(args, repo): # ---------------------------------------------------------------- lint def ahash(image): - """Ink-mask hash over the 8x8 downscale: background (the lightest GB - shade) vs ink. Swapping the three ink shades -- the classic recolor -- - leaves the mask intact, which is exactly what MK302 wants to catch.""" + """Ink-mask hash over the 8x8 downscale: background vs ink, split at + THIS image's own average brightness rather than a fixed shade. Swapping + the three ink shades -- the classic recolor -- leaves the mask intact, + which is exactly what MK302 wants to catch. + + A fixed cutoff (e.g. "<=200 is ink") only makes sense for sprites with a + light background to split against; a mostly-opaque 16x16 icon has almost + no pixel above that cutoff, so every such icon collapsed onto the same + "all ink" hash and was flagged as a near-duplicate of anything else that + also collapsed -- which was most of them, boulder.png included. + Thresholding against the image's own mean keeps the split meaningful + (and roughly balanced) no matter how light or dark the source is.""" from PIL import Image small = image.convert("L").resize((8, 8), Image.LANCZOS) raw = (small.get_flattened_data() if hasattr(small, "get_flattened_data") else small.getdata()) - return sum((1 << i) for i, p in enumerate(raw) if p <= 200) + raw = list(raw) + average = sum(raw) / len(raw) + return sum((1 << i) for i, p in enumerate(raw) if p <= average) def hamming(a, b):