Skip to content

Commit

Permalink
Validate all frames in APNGs
Browse files Browse the repository at this point in the history
  • Loading branch information
andrews05 committed May 30, 2023
1 parent d03930f commit e1e7a17
Showing 1 changed file with 27 additions and 14 deletions.
41 changes: 27 additions & 14 deletions src/sanity_checks.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use image::{DynamicImage, GenericImageView, ImageFormat, Pixel};
use image::{codecs::png::PngDecoder, *};
use log::{error, warn};
use std::io::Cursor;

/// Validate that the output png data still matches the original image
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(output),
);

match (new_png, old_png) {
match (new_frames, old_frames) {
(Err(new_err), _) => {
error!("Failed to read output image for validation: {}", new_err);
false
Expand All @@ -21,26 +20,40 @@ pub fn validate_output(output: &[u8], original_data: &[u8]) -> bool {
warn!("Failed to read input image for validation: {}", old_err);
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]
fn load_png_image_from_memory(png_data: &[u8]) -> Result<DynamicImage, image::ImageError> {
let mut reader = image::io::Reader::new(Cursor::new(png_data));
reader.set_format(ImageFormat::Png);
reader.no_limits();
reader.decode()
/// Loads a PNG image from memory to frames of [RgbaImage]
fn load_png_image_from_memory(png_data: &[u8]) -> Result<Vec<RgbaImage>, image::ImageError> {
let decoder = PngDecoder::new(png_data)?;
if decoder.is_apng() {
decoder
.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
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 p = x.2.channels();
let p = x.channels();
!(p.len() == 4 && p[3] == 0)
});
let b = new_png.pixels().filter(|x| {
let p = x.2.channels();
let p = x.channels();
!(p.len() == 4 && p[3] == 0)
});
a.eq(b)
Expand Down

0 comments on commit e1e7a17

Please sign in to comment.