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 | |
| 21 | use oxedyne_fe2o3_core::prelude::*; |
| 22 | use oxedyne_fe2o3_graphics::png; |
| 23 | |
| 24 | use std::{ |
| 25 | fs, |
| 26 | path::PathBuf, |
| 27 | }; |
| 28 | |
| 29 | /// Fixture name, dimensions, and the RGBA pixels Pillow reads from it, in row-major order. |
| 30 | type Case = (&'static str, usize, usize, &'static [(u8, u8, u8, u8)]); |
| 31 | |
| 32 | const 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] |
| 66 | fn 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 | } |