Compare PNG pixel formats in frame identity

Co-Authored-By: Codex GPT-6 <[email protected]>
This commit is contained in:
levzzzandCodex GPT-6 committed 2026-11-05 18:41:36 +05:00
1 parent 62c9562dc1
commit b33e54cb46
2 files changed
+19 -12

No files matched your search

+11 -4
View File
@@ -14,9 +14,9 @@ def paeth(a, b, c):
return a if pa <= pb and pa <= pc else b if pb <= pc else c
def encode(path, width, height, depth, rows, kind):
def encode(path, width, height, depth, rows, kind, colour=2):
"""An RGB PNG of `rows` (bytes), every row filtered with PNG filter `kind` (0 None, 1 Sub, 2 Up, 3 Average, 4 Paeth)."""
bpp = 3 * depth // 8
bpp = {0: 1, 2: 3, 4: 2, 6: 4}[colour] * depth // 8
stream, previous = b'', bytes(width * bpp)
for row in rows:
out = bytearray()
@@ -31,7 +31,7 @@ def encode(path, width, height, depth, rows, kind):
def chunk(name, body):
return struct.pack('>I', len(body)) + name + body + struct.pack('>I', zlib.crc32(name + body))
header = struct.pack('>IIBBBBB', width, height, depth, 2, 0, 0, 0)
header = struct.pack('>IIBBBBB', width, height, depth, colour, 0, 0, 0)
Path(path).write_bytes(b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', header) + chunk(b'IDAT', zlib.compress(stream))
+ chunk(b'IEND', b''))
@@ -64,7 +64,7 @@ class CompareTests(unittest.TestCase):
encode(self.dir / 'b.png', 6, 4, 8, [bytes(r) for r in changed], 4)
problem = identity.compare(self.dir / 'a.png', self.dir / 'b.png', self.dir / 'd.png')
self.assertEqual(problem, '1 of 24 pixels (4.167%), largest step 4')
width, height, bpp, raw = identity.read_png(self.dir / 'd.png')
width, height, bpp, raw, _ = identity.read_png(self.dir / 'd.png')
diff = identity.unfilter(width, height, bpp, raw)
reds = [(x, y) for y, row in enumerate(diff) for x in range(width)
if row[3 * x] == 255 and row[3 * x + 1] < 255]
@@ -87,6 +87,13 @@ class CompareTests(unittest.TestCase):
self.assertIn('size or format differs',
identity.compare(self.dir / 'a.png', self.dir / 'b.png', self.dir / 'd.png'))
def test_same_bytes_with_different_pixel_formats_are_not_identical(self):
rows = [bytes((1, 2, 3, 4))]
encode(self.dir / 'a.png', 1, 1, 8, rows, 0, colour=6)
encode(self.dir / 'b.png', 1, 1, 16, rows, 0, colour=4)
self.assertIn('size or format differs',
identity.compare(self.dir / 'a.png', self.dir / 'b.png', self.dir / 'd.png'))
class SessionCheckTests(unittest.TestCase):
"""A session that didn't do its job would pass as identical in both builds: the harness says so instead."""
+8 -8
View File
@@ -112,7 +112,7 @@ def lavapipe():
# 🤖 Agent: PNG, with the standard library only.
def read_png(path):
"""(width, height, bytes per pixel, the IDAT stream decompressed): pixel rows still filtered."""
"""Width, height, bytes per pixel, filtered stream, and (depth, colour type)."""
data = Path(path).read_bytes()
if data[:8] != b'\x89PNG\r\n\x1a\n':
raise ValueError(f'{path}: not a PNG')
@@ -129,7 +129,7 @@ def read_png(path):
channels = {0: 1, 2: 3, 4: 2, 6: 4}[colour]
if interlace or depth not in (8, 16):
raise ValueError(f'{path}: {depth}-bit, interlace {interlace} is not what the game writes')
return width, height, channels * depth // 8, zlib.decompress(b''.join(idat))
return width, height, channels * depth // 8, zlib.decompress(b''.join(idat)), (depth, colour)
def unfilter(width, height, bpp, raw):
@@ -174,23 +174,23 @@ def write_png(path, width, height, rows):
def compare(base_path, head_path, diff_path):
"""None when the two PNGs hold the same pixels, else what differs (writing the diff image, unless None)."""
a, b = read_png(base_path), read_png(head_path)
if a[:3] != b[:3]:
return f'size or format differs: {a[:3]} against {b[:3]}'
if a[:3] != b[:3] or a[4] != b[4]:
return f'size or format differs: {a[:3] + (a[4],)} against {b[:3] + (b[4],)}'
if a[3] == b[3]:
return None
width, height, bpp, _ = a
rows_a, rows_b = unfilter(*a), unfilter(*b)
width, height, bpp, _, (depth, colour) = a
rows_a, rows_b = unfilter(*a[:4]), unfilter(*b[:4])
if rows_a == rows_b:
return None
if diff_path is None:
return 'the pixels differ'
channel = 2 if bpp in (6, 8) else 1
channel = depth // 8
differing, largest, picture = 0, 0, []
for ra, rb in zip(rows_a, rows_b):
out = bytearray()
for x in range(width):
pa, pb = ra[x * bpp:(x + 1) * bpp], rb[x * bpp:(x + 1) * bpp]
grey = pa[0] // 3 if bpp < 3 else (pa[0] + pa[channel] + pa[2 * channel]) // 9
grey = pa[0] // 3 if colour in (0, 4) else (pa[0] + pa[channel] + pa[2 * channel]) // 9
if pa != pb:
differing += 1
step = max(abs(int.from_bytes(pa[i:i + channel], 'big') - int.from_bytes(pb[i:i + channel], 'big'))