Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_graphics/tests/png_oracle.rs

4.0 KiB, 3 runs

created by r1870400018:14169, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Decode PNGs that another implementation wrote, and check we read the pixels it says they hold.
2//!
3//! The unit tests in `png.rs` build their PNGs by hand from the specification, which is a stronger
4//! guard than a round trip but still one written by the same hand as the decoder: a
5//! misunderstanding of the format would be encoded into the fixture and the reader alike, and the
6//! test would pass. Our encoder only ever writes RGBA, so it emits no `tRNS` chunk at all, which
7//! is precisely why a round-trip suite could not construct an input that would fail -- and why the
8//! decoder ignored `tRNS` for as long as it did.
9//!
10//! The fixtures in `tests/png/` were therefore written by Pillow, and the expected pixels below are
11//! Pillow's own reading of them, taken from the files on disk. Nothing here originates with us, so
12//! agreement means agreement with an independent implementation. Against the decoder as it stood
13//! before `tRNS` was read, the three transparent fixtures each decode fully opaque, and this test
14//! fails.
15//!
16//! To regenerate: see `tests/png/gen.py`.
17//!
18//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
19//! Anthropic Claude
20
21use oxedyne_fe2o3_core::prelude::*;
22use oxedyne_fe2o3_graphics::png;
23
24use std::{
25 fs,
26 path::PathBuf,
27};
28
29/// Fixture name, dimensions, and the RGBA pixels Pillow reads from it, in row-major order.
30type Case = (&'static str, usize, usize, &'static [(u8, u8, u8, u8)]);
31
32const CASES: &[Case] = &[
33 // Greyscale, with a `tRNS` chunk naming one luminance as the transparent one.
34 (
35 "grey_trns",
36 4, 4,
37 &[(0,0,0,255), (64,64,64,255), (128,128,128,0), (255,255,255,255), (0,0,0,255), (64,64,64,255), (128,128,128,0), (255,255,255,255), (0,0,0,255), (64,64,64,255), (128,128,128,0), (255,255,255,255), (0,0,0,255), (64,64,64,255), (128,128,128,0), (255,255,255,255)],
38 ),
39 // Palette, with a `tRNS` chunk giving one alpha byte per palette entry: transparent, half, opaque.
40 (
41 "palette_trns",
42 4, 4,
43 &[(0,0,0,0), (7,13,29,128), (14,26,58,255), (21,39,87,255), (0,0,0,0), (7,13,29,128), (14,26,58,255), (21,39,87,255), (0,0,0,0), (7,13,29,128), (14,26,58,255), (21,39,87,255), (0,0,0,0), (7,13,29,128), (14,26,58,255), (21,39,87,255)],
44 ),
45 // Truecolour, with a `tRNS` chunk naming one RGB triple as the transparent one.
46 (
47 "rgb_trns",
48 4, 4,
49 &[(255,0,0,255), (0,255,0,0), (0,0,255,255), (10,20,30,255), (255,0,0,255), (0,255,0,0), (0,0,255,255), (10,20,30,255), (255,0,0,255), (0,255,0,0), (0,0,255,255), (10,20,30,255), (255,0,0,255), (0,255,0,0), (0,0,255,255), (10,20,30,255)],
50 ),
51 // A control: no `tRNS`, so every pixel is opaque, and reading `tRNS` must not change that.
52 (
53 "rgb_plain",
54 4, 4,
55 &[(1,2,3,255), (4,5,6,255), (7,8,9,255), (10,11,12,255), (1,2,3,255), (4,5,6,255), (7,8,9,255), (10,11,12,255), (1,2,3,255), (4,5,6,255), (7,8,9,255), (10,11,12,255), (1,2,3,255), (4,5,6,255), (7,8,9,255), (10,11,12,255)],
56 ),
57 // A control: alpha carried in the image data, where it always worked, and must still.
58 (
59 "rgba_plain",
60 4, 4,
61 &[(1,2,3,0), (4,5,6,85), (7,8,9,170), (10,11,12,255), (1,2,3,0), (4,5,6,85), (7,8,9,170), (10,11,12,255), (1,2,3,0), (4,5,6,85), (7,8,9,170), (10,11,12,255), (1,2,3,0), (4,5,6,85), (7,8,9,170), (10,11,12,255)],
62 ),
63];
64
65#[test]
66fn test_pillows_pngs_decode_to_the_pixels_pillow_reads_from_them() -> Outcome<()> {
67 let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests").join("png");
68 for (name, w, h, expected) in CASES {
69 let path = dir.join(fmt!("{}.png", name));
70 let buf = res!(fs::read(&path), IO, File);
71 let pm = res!(png::decode(&buf), Decode, Input);
72
73 req!(pm.width(), *w, "Width of {}.", name);
74 req!(pm.height(), *h, "Height of {}.", name);
75
76 for (i, exp) in expected.iter().enumerate() {
77 let (x, y) = (i % w, i / w);
78 let c = match pm.pixel(x, y) {
79 Some(c) => c,
80 None => return Err(err!(
81 "The fixture {} has no pixel at ({}, {}).", name, x, y;
82 Test, Missing)),
83 };
84 let got = (c.r, c.g, c.b, c.a);
85 req!(got, *exp,
86 "Pixel ({}, {}) of {}: an independent decoder reads {:?}, we read {:?}.",
87 x, y, name, exp, got);
88 }
89 }
90 Ok(())
91}