mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 02:56:17 -10:00
Compare PNG pixel formats in frame identity
Co-Authored-By: Codex GPT-6 <[email protected]>
This commit is contained in:
1 parent
62c9562dc1
commit
b33e54cb46
2 files changed
+19
-12
No files matched your search
@@ -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."""
|
||||
|
||||
@@ -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'))
|
||||
|
||||
Reference in new issue
Block a user