Make dependency on image optional

After PR https://github.com/shssoichiro/oxipng/pull/481 was merged, the
`image` dependency became unused when building with debug assertions
disabled, as it is only used to implement output sanity checks when such
assertions are enabled.

The `image` crate transitively pulls a significant amount of
dependencies, so it's useful for OxiPNG users to get rid of them when
not needed.

[Cargo does not allow specifying dependencies that are only pulled when
debug assertions are
enabled](https://github.com/rust-lang/cargo/issues/7634), so the next
best way to give users some flexibility is to gate those debug
assertions behind a feature flag.

These changes add a `sanity-checks` feature flag that controls whether
the `image` crate and the related sanity checks are compiled in. This
feature is enabled by default to keep debug builds useful to catch
problems during development.
This commit is contained in:
Alejandro González 2023-04-15 15:44:41 +02:00
parent 110eae7c19
commit 9358f1b55f
No known key found for this signature in database
2 changed files with 49 additions and 41 deletions

View file

@ -53,6 +53,7 @@ optional = true
version = "2.1.0" version = "2.1.0"
[dependencies.image] [dependencies.image]
optional = true
default-features = false default-features = false
features = ["png"] features = ["png"]
version = "0.24.3" version = "0.24.3"
@ -65,6 +66,7 @@ binary = ["clap", "wild", "stderrlog"]
default = ["binary", "filetime", "parallel", "zopfli"] default = ["binary", "filetime", "parallel", "zopfli"]
parallel = ["rayon", "indexmap/rayon", "crossbeam-channel"] parallel = ["rayon", "indexmap/rayon", "crossbeam-channel"]
freestanding = ["libdeflater/freestanding"] freestanding = ["libdeflater/freestanding"]
sanity-checks = ["image"]
[lib] [lib]
name = "oxipng" name = "oxipng"

View file

@ -31,7 +31,6 @@ use crate::evaluate::Evaluator;
use crate::png::PngData; use crate::png::PngData;
use crate::png::PngImage; use crate::png::PngImage;
use crate::reduction::*; use crate::reduction::*;
use image::{DynamicImage, GenericImageView, ImageFormat, Pixel};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use rayon::prelude::*; use rayon::prelude::*;
use std::fmt; use std::fmt;
@ -659,7 +658,8 @@ fn optimize_png(
); );
} }
debug_assert!(validate_output(&output, original_data)); #[cfg(feature = "sanity-checks")]
debug_assert!(sanity_checks::validate_output(&output, original_data));
Ok(output) Ok(output)
} }
@ -1044,8 +1044,13 @@ fn copy_times(input_path_meta: &Metadata, out_path: &Path) -> PngResult<()> {
}) })
} }
/// Validate that the output png data still matches the original image #[cfg(feature = "sanity-checks")]
fn validate_output(output: &[u8], original_data: &[u8]) -> bool { mod sanity_checks {
use super::*;
use image::{DynamicImage, GenericImageView, ImageFormat, Pixel};
/// Validate that the output png data still matches the original image
pub(super) fn validate_output(output: &[u8], original_data: &[u8]) -> bool {
let (old_png, new_png) = rayon::join( let (old_png, new_png) = 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),
@ -1065,18 +1070,18 @@ fn validate_output(output: &[u8], original_data: &[u8]) -> bool {
} }
(Ok(new_png), Ok(old_png)) => images_equal(&old_png, &new_png), (Ok(new_png), Ok(old_png)) => images_equal(&old_png, &new_png),
} }
} }
/// Loads a PNG image from memory to a [DynamicImage] /// Loads a PNG image from memory to a [DynamicImage]
fn load_png_image_from_memory(png_data: &[u8]) -> Result<DynamicImage, image::ImageError> { fn load_png_image_from_memory(png_data: &[u8]) -> Result<DynamicImage, image::ImageError> {
let mut reader = image::io::Reader::new(Cursor::new(png_data)); let mut reader = image::io::Reader::new(Cursor::new(png_data));
reader.set_format(ImageFormat::Png); reader.set_format(ImageFormat::Png);
reader.no_limits(); reader.no_limits();
reader.decode() reader.decode()
} }
/// 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: &DynamicImage, new_png: &DynamicImage) -> bool {
let a = old_png.pixels().filter(|x| { let a = old_png.pixels().filter(|x| {
let p = x.2.channels(); let p = x.2.channels();
!(p.len() == 4 && p[3] == 0) !(p.len() == 4 && p[3] == 0)
@ -1086,4 +1091,5 @@ fn images_equal(old_png: &DynamicImage, new_png: &DynamicImage) -> bool {
!(p.len() == 4 && p[3] == 0) !(p.len() == 4 && p[3] == 0)
}); });
a.eq(b) a.eq(b)
}
} }