Validate all frames in APNGs

This commit is contained in:
Andrew 2023-05-30 18:34:28 +12:00 committed by Josh Holmer
parent 4ab941e305
commit 041e5f084b

View file

@ -1,15 +1,14 @@
use image::{DynamicImage, GenericImageView, ImageFormat, Pixel}; use image::{codecs::png::PngDecoder, *};
use log::{error, warn}; use log::{error, warn};
use std::io::Cursor;
/// Validate that the output png data still matches the original image /// Validate that the output png data still matches the original image
pub fn validate_output(output: &[u8], original_data: &[u8]) -> bool { pub fn validate_output(output: &[u8], original_data: &[u8]) -> bool {
let (old_png, new_png) = rayon::join( let (old_frames, new_frames) = rayon::join(
|| load_png_image_from_memory(original_data), || load_png_image_from_memory(original_data),
|| load_png_image_from_memory(output), || load_png_image_from_memory(output),
); );
match (new_png, old_png) { match (new_frames, old_frames) {
(Err(new_err), _) => { (Err(new_err), _) => {
error!("Failed to read output image for validation: {}", new_err); error!("Failed to read output image for validation: {}", new_err);
false false
@ -21,26 +20,40 @@ pub fn validate_output(output: &[u8], original_data: &[u8]) -> bool {
warn!("Failed to read input image for validation: {}", old_err); warn!("Failed to read input image for validation: {}", old_err);
true true
} }
(Ok(new_png), Ok(old_png)) => images_equal(&old_png, &new_png), (Ok(new_frames), Ok(old_frames)) if new_frames.len() != old_frames.len() => false,
(Ok(new_frames), Ok(old_frames)) => {
for (a, b) in old_frames.iter().zip(new_frames) {
if !images_equal(&a, &b) {
return false;
}
}
true
}
} }
} }
/// Loads a PNG image from memory to a [DynamicImage] /// Loads a PNG image from memory to frames of [RgbaImage]
fn load_png_image_from_memory(png_data: &[u8]) -> Result<DynamicImage, image::ImageError> { fn load_png_image_from_memory(png_data: &[u8]) -> Result<Vec<RgbaImage>, image::ImageError> {
let mut reader = image::io::Reader::new(Cursor::new(png_data)); let decoder = PngDecoder::new(png_data)?;
reader.set_format(ImageFormat::Png); if decoder.is_apng() {
reader.no_limits(); decoder
reader.decode() .apng()
.into_frames()
.map(|f| f.map(|f| f.into_buffer()))
.collect()
} else {
DynamicImage::from_decoder(decoder).map(|i| vec![i.into_rgba8()])
}
} }
/// Compares images pixel by pixel for equivalent content /// Compares images pixel by pixel for equivalent content
fn images_equal(old_png: &DynamicImage, new_png: &DynamicImage) -> bool { fn images_equal(old_png: &RgbaImage, new_png: &RgbaImage) -> bool {
let a = old_png.pixels().filter(|x| { let a = old_png.pixels().filter(|x| {
let p = x.2.channels(); let p = x.channels();
!(p.len() == 4 && p[3] == 0) !(p.len() == 4 && p[3] == 0)
}); });
let b = new_png.pixels().filter(|x| { let b = new_png.pixels().filter(|x| {
let p = x.2.channels(); let p = x.channels();
!(p.len() == 4 && p[3] == 0) !(p.len() == 4 && p[3] == 0)
}); });
a.eq(b) a.eq(b)