BC breaks for v10 (#715)
This is a collection of all the BC breaks mentioned in #714, except for 11 which I'm not including for now. Fixes #658. Fixes #660. It might be best to review each commit individually, referencing the notes in #714 and #660 (I just didn't want to create a dozen separate PRs).
This commit is contained in:
parent
2a3aa83928
commit
14532e8bf5
27 changed files with 256 additions and 336 deletions
|
|
@ -76,7 +76,7 @@ All options are case-sensitive.
|
|||
\* Note that oxipng is not a brute-force optimizer. This means that while higher optimization levels
|
||||
are almost always better or equal to lower levels, this is not guaranteed and it is possible in
|
||||
rare circumstances that a lower level may give a marginally smaller output. Similarly, using Zopfli
|
||||
compression (`-Z`) is not guaranteed to always be better than without.
|
||||
compression (`-z`) is not guaranteed to always be better than without.
|
||||
|
||||
## Git integration via [pre-commit]
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ fn interlacing_16_bits(b: &mut Bencher) {
|
|||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||
b.iter(|| png.raw.change_interlacing(true));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
|
|
@ -21,7 +21,7 @@ fn interlacing_8_bits(b: &mut Bencher) {
|
|||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||
b.iter(|| png.raw.change_interlacing(true));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
|
|
@ -31,7 +31,7 @@ fn interlacing_4_bits(b: &mut Bencher) {
|
|||
));
|
||||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||
b.iter(|| png.raw.change_interlacing(true));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
|
|
@ -41,7 +41,7 @@ fn interlacing_2_bits(b: &mut Bencher) {
|
|||
));
|
||||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||
b.iter(|| png.raw.change_interlacing(true));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
|
|
@ -51,7 +51,7 @@ fn interlacing_1_bits(b: &mut Bencher) {
|
|||
));
|
||||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||
b.iter(|| png.raw.change_interlacing(true));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
|
|
@ -61,7 +61,7 @@ fn deinterlacing_16_bits(b: &mut Bencher) {
|
|||
));
|
||||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||
b.iter(|| png.raw.change_interlacing(false));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
|
|
@ -71,7 +71,7 @@ fn deinterlacing_8_bits(b: &mut Bencher) {
|
|||
));
|
||||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||
b.iter(|| png.raw.change_interlacing(false));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
|
|
@ -81,7 +81,7 @@ fn deinterlacing_4_bits(b: &mut Bencher) {
|
|||
));
|
||||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||
b.iter(|| png.raw.change_interlacing(false));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
|
|
@ -91,7 +91,7 @@ fn deinterlacing_2_bits(b: &mut Bencher) {
|
|||
));
|
||||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||
b.iter(|| png.raw.change_interlacing(false));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
|
|
@ -101,5 +101,5 @@ fn deinterlacing_1_bits(b: &mut Bencher) {
|
|||
));
|
||||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||
b.iter(|| png.raw.change_interlacing(false));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,20 +3,18 @@
|
|||
extern crate oxipng;
|
||||
extern crate test;
|
||||
|
||||
use std::{num::NonZeroU8, path::PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use oxipng::{internal_tests::*, *};
|
||||
use test::Bencher;
|
||||
|
||||
const DEFAULT_ZOPFLI_ITERATIONS: NonZeroU8 = NonZeroU8::new(15).unwrap();
|
||||
|
||||
#[bench]
|
||||
fn zopfli_16_bits_strategy_0(b: &mut Bencher) {
|
||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||
zopfli_deflate(png.raw.data.as_ref(), ZopfliOptions::default()).ok();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -26,7 +24,7 @@ fn zopfli_8_bits_strategy_0(b: &mut Bencher) {
|
|||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||
zopfli_deflate(png.raw.data.as_ref(), ZopfliOptions::default()).ok();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +36,7 @@ fn zopfli_4_bits_strategy_0(b: &mut Bencher) {
|
|||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||
zopfli_deflate(png.raw.data.as_ref(), ZopfliOptions::default()).ok();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +48,7 @@ fn zopfli_2_bits_strategy_0(b: &mut Bencher) {
|
|||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||
zopfli_deflate(png.raw.data.as_ref(), ZopfliOptions::default()).ok();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -62,6 +60,6 @@ fn zopfli_1_bits_strategy_0(b: &mut Bencher) {
|
|||
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||
zopfli_deflate(png.raw.data.as_ref(), ZopfliOptions::default()).ok();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
47
src/cli.rs
47
src/cli.rs
|
|
@ -1,6 +1,6 @@
|
|||
use std::path::PathBuf;
|
||||
use std::{num::NonZeroU64, path::PathBuf};
|
||||
|
||||
use clap::{Arg, ArgAction, Command, value_parser};
|
||||
use clap::{Arg, ArgAction, Command, builder::ArgPredicate, value_parser};
|
||||
|
||||
include!("display_chunks.rs");
|
||||
|
||||
|
|
@ -97,10 +97,10 @@ Note that this will not preserve the directory structure of the input files when
|
|||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pretend")
|
||||
Arg::new("dry-run")
|
||||
.help("Do not write any files, only show compression results")
|
||||
.short('P')
|
||||
.long("pretend")
|
||||
.short('d')
|
||||
.long("dry-run")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
|
|
@ -163,24 +163,23 @@ transformation and may be unsuitable for some applications.")
|
|||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
// Note: The default value is not explicitly set here, as it is dependant on the `--nx` flag.
|
||||
Arg::new("interlace")
|
||||
.help("Set PNG interlacing type (0, 1, keep) [default: 0]")
|
||||
.help("Set PNG interlacing (off, on, keep)")
|
||||
.long_help("\
|
||||
Set the PNG interlacing type, where <type> is one of:
|
||||
Set the PNG interlacing mode, where <mode> is one of:
|
||||
|
||||
0 => Remove interlacing from all images that are processed
|
||||
1 => Apply Adam7 interlacing on all images that are processed
|
||||
keep => Keep the existing interlacing type of each image
|
||||
off => Remove interlacing from all images that are processed
|
||||
on => Apply Adam7 interlacing on all images that are processed
|
||||
keep => Keep the existing interlacing mode of each image
|
||||
|
||||
Note that interlacing can add 25-50% to the size of an optimized image. Only use it if you \
|
||||
believe the benefits outweigh the costs for your use case.
|
||||
|
||||
[default: 0]")
|
||||
believe the benefits outweigh the costs for your use case.")
|
||||
.short('i')
|
||||
.long("interlace")
|
||||
.value_name("type")
|
||||
.value_parser(["0", "1", "keep"])
|
||||
.value_name("mode")
|
||||
.value_parser(["off", "on", "keep", "0", "1"])
|
||||
.default_value("off")
|
||||
.default_value_if("no-reductions", ArgPredicate::IsPresent, "keep")
|
||||
.hide_possible_values(true),
|
||||
)
|
||||
.arg(
|
||||
|
|
@ -325,7 +324,8 @@ be processed successfully.")
|
|||
.long_help("\
|
||||
Use the much slower but stronger Zopfli compressor for main compression trials. \
|
||||
Recommended use is with '-o max' and '--fast'.")
|
||||
.short('Z')
|
||||
.short('z')
|
||||
.short_alias('Z') // Kept for backwards compatibility
|
||||
.long("zopfli")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
|
|
@ -338,7 +338,18 @@ speed up compression for large files. This option requires '--zopfli' to be set.
|
|||
.long("zi")
|
||||
.value_name("iterations")
|
||||
.default_value("15")
|
||||
.value_parser(1..=255)
|
||||
.value_parser(value_parser!(NonZeroU64))
|
||||
.requires("zopfli"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("iterations-without-improvement")
|
||||
.hide_short_help(true)
|
||||
.long_help("\
|
||||
Stop Zopfli compression after this number of iterations without improvement. Use this in \
|
||||
conjunction with a high value for '--zi' to achieve better compression in reasonable time.")
|
||||
.long("ziwi")
|
||||
.value_name("iterations")
|
||||
.value_parser(value_parser!(NonZeroU64))
|
||||
.requires("zopfli"),
|
||||
)
|
||||
.arg(
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ impl TryFrom<u8> for BitDepth {
|
|||
4 => Ok(Self::Four),
|
||||
8 => Ok(Self::Eight),
|
||||
16 => Ok(Self::Sixteen),
|
||||
_ => Err(PngError::new("Unexpected bit depth")),
|
||||
_ => Err(PngError::InvalidData),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ pub fn inflate(data: &[u8], out_size: usize) -> PngResult<Vec<u8>> {
|
|||
.zlib_decompress(data, &mut dest)
|
||||
.map_err(|err| match err {
|
||||
DecompressionError::BadData => PngError::InvalidData,
|
||||
DecompressionError::InsufficientSpace => PngError::new("inflated data too long"),
|
||||
DecompressionError::InsufficientSpace => PngError::InflatedDataTooLong(out_size),
|
||||
})?;
|
||||
dest.truncate(len);
|
||||
Ok(dest)
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
mod deflater;
|
||||
#[cfg(feature = "zopfli")]
|
||||
use std::num::NonZeroU8;
|
||||
use std::{fmt, fmt::Display};
|
||||
|
||||
pub use deflater::{crc32, deflate, inflate};
|
||||
|
||||
use crate::{PngError, PngResult};
|
||||
use std::{fmt, fmt::Display};
|
||||
|
||||
#[cfg(feature = "zopfli")]
|
||||
mod zopfli_oxipng;
|
||||
#[cfg(feature = "zopfli")]
|
||||
pub use zopfli::Options as ZopfliOptions;
|
||||
#[cfg(feature = "zopfli")]
|
||||
pub use zopfli_oxipng::deflate as zopfli_deflate;
|
||||
|
||||
/// DEFLATE algorithms supported by oxipng (for use in [`Options`][crate::Options])
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Deflaters {
|
||||
pub enum Deflater {
|
||||
/// Use libdeflater.
|
||||
Libdeflater {
|
||||
/// Which compression level to use on the file (0-12)
|
||||
|
|
@ -21,20 +21,15 @@ pub enum Deflaters {
|
|||
},
|
||||
#[cfg(feature = "zopfli")]
|
||||
/// Use the better but slower Zopfli implementation
|
||||
Zopfli {
|
||||
/// The number of compression iterations to do. 15 iterations are fine
|
||||
/// for small files, but bigger files will need to be compressed with
|
||||
/// less iterations, or else they will be too slow.
|
||||
iterations: NonZeroU8,
|
||||
},
|
||||
Zopfli(ZopfliOptions),
|
||||
}
|
||||
|
||||
impl Deflaters {
|
||||
impl Deflater {
|
||||
pub(crate) fn deflate(self, data: &[u8], max_size: Option<usize>) -> PngResult<Vec<u8>> {
|
||||
let compressed = match self {
|
||||
Self::Libdeflater { compression } => deflate(data, compression, max_size)?,
|
||||
#[cfg(feature = "zopfli")]
|
||||
Self::Zopfli { iterations } => zopfli_deflate(data, iterations)?,
|
||||
Self::Zopfli(options) => zopfli_deflate(data, options)?,
|
||||
};
|
||||
if let Some(max) = max_size {
|
||||
if compressed.len() > max {
|
||||
|
|
@ -45,13 +40,13 @@ impl Deflaters {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for Deflaters {
|
||||
impl Display for Deflater {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Libdeflater { compression } => write!(f, "zc = {compression}"),
|
||||
#[cfg(feature = "zopfli")]
|
||||
Self::Zopfli { iterations } => write!(f, "zopfli, zi = {iterations}"),
|
||||
Self::Zopfli(options) => write!(f, "zopfli, zi = {}", options.iteration_count),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
use std::num::NonZeroU8;
|
||||
|
||||
use crate::{PngError, PngResult};
|
||||
|
||||
pub fn deflate(data: &[u8], iterations: NonZeroU8) -> PngResult<Vec<u8>> {
|
||||
pub fn deflate(data: &[u8], options: zopfli::Options) -> PngResult<Vec<u8>> {
|
||||
let mut output = Vec::with_capacity(data.len());
|
||||
let options = zopfli::Options {
|
||||
iteration_count: iterations.into(),
|
||||
..Default::default()
|
||||
};
|
||||
// Since Rust v1.74, passing &[u8] directly into zopfli causes a regression in compressed size
|
||||
// for some files. Wrapping the slice in another Read implementer such as Box fixes it for now.
|
||||
match zopfli::compress(options, zopfli::Format::Zlib, Box::new(data), &mut output) {
|
||||
|
|
|
|||
56
src/error.rs
56
src/error.rs
|
|
@ -2,20 +2,22 @@ use std::{error::Error, fmt};
|
|||
|
||||
use crate::colors::{BitDepth, ColorType};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum PngError {
|
||||
DeflatedDataTooLong(usize),
|
||||
TimedOut,
|
||||
NotPNG,
|
||||
APNGNotSupported,
|
||||
APNGOutOfOrder,
|
||||
InvalidData,
|
||||
TruncatedData,
|
||||
ChunkMissing(&'static str),
|
||||
InvalidDepthForType(BitDepth, ColorType),
|
||||
IncorrectDataLength(usize, usize),
|
||||
C2PAMetadataPreventsChanges,
|
||||
ChunkMissing(&'static str),
|
||||
CRCMismatch([u8; 4]),
|
||||
DeflatedDataTooLong(usize),
|
||||
IncorrectDataLength(usize, usize),
|
||||
InflatedDataTooLong(usize),
|
||||
InvalidData,
|
||||
InvalidDepthForType(BitDepth, ColorType),
|
||||
NotPNG,
|
||||
ReadFailed(String, std::io::Error),
|
||||
TruncatedData,
|
||||
WriteFailed(String, std::io::Error),
|
||||
Other(Box<str>),
|
||||
}
|
||||
|
||||
|
|
@ -26,26 +28,32 @@ impl fmt::Display for PngError {
|
|||
#[cold]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
PngError::DeflatedDataTooLong(_) => f.write_str("deflated data too long"),
|
||||
PngError::TimedOut => f.write_str("timed out"),
|
||||
PngError::NotPNG => f.write_str("Invalid header detected; Not a PNG file"),
|
||||
PngError::InvalidData => f.write_str("Invalid data found; unable to read PNG file"),
|
||||
PngError::TruncatedData => {
|
||||
f.write_str("Missing data in the file; the file is truncated")
|
||||
}
|
||||
PngError::APNGNotSupported => f.write_str("APNG files are not (yet) supported"),
|
||||
PngError::APNGOutOfOrder => f.write_str("APNG chunks are out of order"),
|
||||
PngError::C2PAMetadataPreventsChanges => f.write_str(
|
||||
"The image contains C2PA manifest that would be invalidated by any file changes",
|
||||
),
|
||||
PngError::ChunkMissing(s) => write!(f, "Chunk {s} missing or empty"),
|
||||
PngError::InvalidDepthForType(d, ref c) => {
|
||||
write!(f, "Invalid bit depth {d} for color type {c}")
|
||||
}
|
||||
PngError::CRCMismatch(ref c) => write!(
|
||||
f,
|
||||
"CRC mismatch in {} chunk; May be recoverable by using --fix",
|
||||
String::from_utf8_lossy(c)
|
||||
),
|
||||
PngError::DeflatedDataTooLong(_) => f.write_str("Deflated data too long"),
|
||||
PngError::IncorrectDataLength(l1, l2) => write!(
|
||||
f,
|
||||
"Data length {l1} does not match the expected length {l2}"
|
||||
),
|
||||
PngError::C2PAMetadataPreventsChanges => f.write_str(
|
||||
"The image contains C2PA manifest that would be invalidated by any file changes",
|
||||
),
|
||||
PngError::InflatedDataTooLong(_) => f.write_str("Inflated data too long"),
|
||||
PngError::InvalidData => f.write_str("Invalid data found; unable to read PNG file"),
|
||||
PngError::InvalidDepthForType(d, ref c) => {
|
||||
write!(f, "Invalid bit depth {d} for color type {c}")
|
||||
}
|
||||
PngError::NotPNG => f.write_str("Invalid header detected; Not a PNG file"),
|
||||
PngError::ReadFailed(ref s, ref e) => write!(f, "Failed to read from {s}: {e}"),
|
||||
PngError::TruncatedData => {
|
||||
f.write_str("Missing data in the file; the file is truncated")
|
||||
}
|
||||
PngError::WriteFailed(ref s, ref e) => write!(f, "Failed to write to {s}: {e}"),
|
||||
PngError::Other(ref s) => f.write_str(s),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use std::sync::{
|
|||
|
||||
#[cfg(feature = "parallel")]
|
||||
use crossbeam_channel::{Receiver, Sender, unbounded};
|
||||
use deflate::Deflaters;
|
||||
use deflate::Deflater;
|
||||
use indexmap::IndexSet;
|
||||
use log::trace;
|
||||
use rayon::prelude::*;
|
||||
|
|
@ -50,7 +50,7 @@ impl Candidate {
|
|||
pub(crate) struct Evaluator {
|
||||
deadline: Arc<Deadline>,
|
||||
filters: IndexSet<FilterStrategy>,
|
||||
deflater: Deflaters,
|
||||
deflater: Deflater,
|
||||
optimize_alpha: bool,
|
||||
final_round: bool,
|
||||
nth: AtomicUsize,
|
||||
|
|
@ -68,7 +68,7 @@ impl Evaluator {
|
|||
pub fn new(
|
||||
deadline: Arc<Deadline>,
|
||||
filters: IndexSet<FilterStrategy>,
|
||||
deflater: Deflaters,
|
||||
deflater: Deflater,
|
||||
optimize_alpha: bool,
|
||||
final_round: bool,
|
||||
) -> Self {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ use log::{debug, trace, warn};
|
|||
use rgb::{RGB16, RGBA8};
|
||||
|
||||
use crate::{
|
||||
Deflaters, Options, PngResult,
|
||||
Deflater, Options, PngResult,
|
||||
colors::{BitDepth, ColorType},
|
||||
deflate::{crc32, inflate},
|
||||
display_chunks::DISPLAY_CHUNKS,
|
||||
error::PngError,
|
||||
interlace::Interlacing,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -22,8 +21,8 @@ pub struct IhdrData {
|
|||
pub color_type: ColorType,
|
||||
/// The bit depth of the image
|
||||
pub bit_depth: BitDepth,
|
||||
/// The interlacing mode of the image
|
||||
pub interlaced: Interlacing,
|
||||
/// Whether the image is interlaced
|
||||
pub interlaced: bool,
|
||||
}
|
||||
|
||||
impl IhdrData {
|
||||
|
|
@ -45,7 +44,7 @@ impl IhdrData {
|
|||
(w * bpp).div_ceil(8) * h
|
||||
}
|
||||
|
||||
if self.interlaced == Interlacing::None {
|
||||
if !self.interlaced {
|
||||
bitmap_size(bpp, w, h) + h
|
||||
} else {
|
||||
let mut size = bitmap_size(bpp, (w + 7) >> 3, (h + 7) >> 3) + ((h + 7) >> 3);
|
||||
|
|
@ -173,10 +172,7 @@ pub fn parse_next_chunk<'a>(
|
|||
|
||||
let chunk_bytes = &byte_data[chunk_start..chunk_start + 4 + length as usize];
|
||||
if !fix_errors && crc32(chunk_bytes) != crc {
|
||||
return Err(PngError::new(&format!(
|
||||
"CRC Mismatch in {} chunk; May be recoverable by using --fix",
|
||||
String::from_utf8_lossy(chunk_name)
|
||||
)));
|
||||
return Err(PngError::CRCMismatch(chunk_name.try_into().unwrap()));
|
||||
}
|
||||
|
||||
let name: [u8; 4] = chunk_name.try_into().unwrap();
|
||||
|
|
@ -209,12 +205,16 @@ pub fn parse_ihdr_chunk(
|
|||
},
|
||||
4 => ColorType::GrayscaleAlpha,
|
||||
6 => ColorType::RGBA,
|
||||
_ => return Err(PngError::new("Unexpected color type in header")),
|
||||
_ => return Err(PngError::InvalidData),
|
||||
},
|
||||
bit_depth: byte_data[8].try_into()?,
|
||||
width: read_be_u32(&byte_data[0..4]),
|
||||
height: read_be_u32(&byte_data[4..8]),
|
||||
interlaced: interlaced.try_into()?,
|
||||
interlaced: match interlaced {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => return Err(PngError::InvalidData),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -223,7 +223,7 @@ fn palette_to_rgba(
|
|||
palette_data: Option<Vec<u8>>,
|
||||
trns_data: Option<Vec<u8>>,
|
||||
) -> Result<Vec<RGBA8>, PngError> {
|
||||
let palette_data = palette_data.ok_or_else(|| PngError::new("no palette in indexed image"))?;
|
||||
let palette_data = palette_data.ok_or(PngError::ChunkMissing("PLTE"))?;
|
||||
let mut palette: Vec<_> = palette_data
|
||||
.chunks_exact(3)
|
||||
.map(|color| RGBA8::new(color[0], color[1], color[2], 255))
|
||||
|
|
@ -276,7 +276,7 @@ pub fn extract_icc(iccp: &Chunk) -> Option<Vec<u8>> {
|
|||
}
|
||||
|
||||
/// Make an iCCP chunk by compressing the ICC profile
|
||||
pub fn make_iccp(icc: &[u8], deflater: Deflaters, max_size: Option<usize>) -> PngResult<Chunk> {
|
||||
pub fn make_iccp(icc: &[u8], deflater: Deflater, max_size: Option<usize>) -> PngResult<Chunk> {
|
||||
let mut compressed = deflater.deflate(icc, max_size)?;
|
||||
let mut data = Vec::with_capacity(compressed.len() + 5);
|
||||
data.extend(b"icc"); // Profile name - generally unused, can be anything
|
||||
|
|
@ -348,7 +348,7 @@ pub fn preprocess_chunks(aux_chunks: &mut Vec<Chunk>, opts: &mut Options) {
|
|||
} else if opts.idat_recoding {
|
||||
// Try recompressing the profile
|
||||
let cur_len = aux_chunks[iccp_idx].data.len();
|
||||
if let Ok(iccp) = make_iccp(&icc, opts.deflate, Some(cur_len - 1)) {
|
||||
if let Ok(iccp) = make_iccp(&icc, opts.deflater, Some(cur_len - 1)) {
|
||||
debug!(
|
||||
"Recompressed iCCP chunk: {} ({} bytes decrease)",
|
||||
iccp.data.len(),
|
||||
|
|
|
|||
|
|
@ -1,42 +1,6 @@
|
|||
use std::{fmt, fmt::Display};
|
||||
|
||||
use bitvec::prelude::*;
|
||||
|
||||
use crate::{PngError, headers::IhdrData, png::PngImage};
|
||||
|
||||
/// Whether to enable progressive rendering. See [`Options`][crate::Options])
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Interlacing {
|
||||
/// Makes images load top to bottom.
|
||||
None,
|
||||
/// Makes it possible to render partially-loaded images at lower resolution. Usually increases file sizes.
|
||||
Adam7,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Interlacing {
|
||||
type Error = PngError;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Self::None),
|
||||
1 => Ok(Self::Adam7),
|
||||
_ => Err(PngError::new("Unexpected interlacing in header")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Interlacing {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(
|
||||
match self {
|
||||
Self::None => "non-interlaced",
|
||||
Self::Adam7 => "interlaced",
|
||||
},
|
||||
f,
|
||||
)
|
||||
}
|
||||
}
|
||||
use crate::{headers::IhdrData, png::PngImage};
|
||||
|
||||
#[must_use]
|
||||
pub fn interlace_image(png: &PngImage) -> PngImage {
|
||||
|
|
@ -89,7 +53,7 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
|
|||
data: output,
|
||||
ihdr: IhdrData {
|
||||
color_type: png.ihdr.color_type.clone(),
|
||||
interlaced: Interlacing::Adam7,
|
||||
interlaced: true,
|
||||
..png.ihdr
|
||||
},
|
||||
}
|
||||
|
|
@ -103,7 +67,7 @@ pub fn deinterlace_image(png: &PngImage) -> PngImage {
|
|||
},
|
||||
ihdr: IhdrData {
|
||||
color_type: png.ihdr.color_type.clone(),
|
||||
interlaced: Interlacing::None,
|
||||
interlaced: false,
|
||||
..png.ihdr
|
||||
},
|
||||
}
|
||||
|
|
|
|||
90
src/lib.rs
90
src/lib.rs
|
|
@ -41,13 +41,14 @@ use log::{debug, info, trace, warn};
|
|||
use rayon::prelude::*;
|
||||
pub use rgb::{RGB16, RGBA8};
|
||||
|
||||
#[cfg(feature = "zopfli")]
|
||||
pub use crate::deflate::ZopfliOptions;
|
||||
pub use crate::{
|
||||
colors::{BitDepth, ColorType},
|
||||
deflate::Deflaters,
|
||||
deflate::Deflater,
|
||||
error::PngError,
|
||||
filters::{FilterStrategy, RowFilter},
|
||||
headers::StripChunks,
|
||||
interlace::Interlacing,
|
||||
options::{InFile, Options, OutFile},
|
||||
};
|
||||
use crate::{
|
||||
|
|
@ -130,7 +131,7 @@ impl RawImage {
|
|||
height,
|
||||
color_type,
|
||||
bit_depth,
|
||||
interlaced: Interlacing::None,
|
||||
interlaced: false,
|
||||
},
|
||||
data,
|
||||
}),
|
||||
|
|
@ -146,7 +147,7 @@ impl RawImage {
|
|||
/// Add an ICC profile for the image
|
||||
pub fn add_icc_profile(&mut self, data: &[u8]) {
|
||||
// Compress with fastest compression level - will be recompressed during optimization
|
||||
let deflater = Deflaters::Libdeflater { compression: 1 };
|
||||
let deflater = Deflater::Libdeflater { compression: 1 };
|
||||
if let Ok(iccp) = make_iccp(data, deflater, None) {
|
||||
self.aux_chunks.push(iccp);
|
||||
}
|
||||
|
|
@ -181,7 +182,9 @@ impl RawImage {
|
|||
}
|
||||
|
||||
/// Perform optimization on the input file using the options provided
|
||||
pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<()> {
|
||||
///
|
||||
/// Returns the original and optimized file sizes
|
||||
pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(usize, usize)> {
|
||||
// Read in the file and try to decode as PNG.
|
||||
info!("Processing: {input}");
|
||||
|
||||
|
|
@ -218,7 +221,7 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
|||
let mut data = Vec::new();
|
||||
stdin()
|
||||
.read_to_end(&mut data)
|
||||
.map_err(|e| PngError::new(&format!("Error reading stdin: {e}")))?;
|
||||
.map_err(|e| PngError::ReadFailed("stdin".into(), e))?;
|
||||
data
|
||||
}
|
||||
};
|
||||
|
|
@ -230,14 +233,14 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
|||
|
||||
let in_length = in_data.len();
|
||||
|
||||
if is_fully_optimized(in_data.len(), optimized_output.len(), opts) {
|
||||
if is_fully_optimized(in_length, optimized_output.len(), opts) {
|
||||
match (output, input) {
|
||||
// If output path is None, it also means same as the input path
|
||||
(OutFile::Path { path, .. }, InFile::Path(input_path))
|
||||
if path.as_ref().is_none_or(|p| p == input_path) =>
|
||||
{
|
||||
info!("{input}: Could not optimize further, no change written");
|
||||
return Ok(());
|
||||
return Ok((in_length, in_length));
|
||||
}
|
||||
_ => {
|
||||
optimized_output = in_data;
|
||||
|
|
@ -261,26 +264,21 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
|||
|
||||
match (output, input) {
|
||||
(OutFile::None, _) => {
|
||||
info!("{savings}: Running in pretend mode, no output");
|
||||
info!("{savings}: Dry run, no output");
|
||||
}
|
||||
(&OutFile::StdOut, _) | (&OutFile::Path { path: None, .. }, &InFile::StdIn) => {
|
||||
let mut buffer = BufWriter::new(stdout());
|
||||
buffer
|
||||
.write_all(&optimized_output)
|
||||
.map_err(|e| PngError::new(&format!("Unable to write to stdout: {e}")))?;
|
||||
.map_err(|e| PngError::WriteFailed("stdout".into(), e))?;
|
||||
}
|
||||
(OutFile::Path { path, .. }, _) => {
|
||||
let output_path = path
|
||||
.as_ref()
|
||||
.map(|p| p.as_path())
|
||||
.unwrap_or_else(|| input.path().unwrap());
|
||||
let out_file = File::create(output_path).map_err(|err| {
|
||||
PngError::new(&format!(
|
||||
"Unable to write to file {}: {}",
|
||||
output_path.display(),
|
||||
err
|
||||
))
|
||||
})?;
|
||||
let out_file = File::create(output_path)
|
||||
.map_err(|err| PngError::WriteFailed(output_path.display().to_string(), err))?;
|
||||
if let Some(metadata_input) = &opt_metadata_preserved {
|
||||
copy_permissions(metadata_input, &out_file)?;
|
||||
}
|
||||
|
|
@ -290,13 +288,7 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
|||
.write_all(&optimized_output)
|
||||
// flush BufWriter so IO errors don't get swallowed silently on close() by drop!
|
||||
.and_then(|()| buffer.flush())
|
||||
.map_err(|e| {
|
||||
PngError::new(&format!(
|
||||
"Unable to write to {}: {}",
|
||||
output_path.display(),
|
||||
e
|
||||
))
|
||||
})?;
|
||||
.map_err(|e| PngError::WriteFailed(output_path.display().to_string(), e))?;
|
||||
// force drop and thereby closing of file handle before modifying any timestamp
|
||||
std::mem::drop(buffer);
|
||||
if let Some(metadata_input) = &opt_metadata_preserved {
|
||||
|
|
@ -305,7 +297,7 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
|||
info!("{}: {}", savings, output_path.display());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
Ok((in_length, optimized_output.len()))
|
||||
}
|
||||
|
||||
/// Perform optimization on the input file using the options provided, where the file is already
|
||||
|
|
@ -395,7 +387,7 @@ fn optimize_png(
|
|||
);
|
||||
}
|
||||
|
||||
if opts.interlace == Some(Interlacing::Adam7) && png.raw.ihdr.interlaced != Interlacing::Adam7 {
|
||||
if opts.interlace == Some(true) && !png.raw.ihdr.interlaced {
|
||||
warn!(
|
||||
"Interlacing was not enabled as it would result in a larger file. To override this, use `--force`."
|
||||
);
|
||||
|
|
@ -420,16 +412,16 @@ fn optimize_raw(
|
|||
// 8 is a little slower but not noticeably when used only for reductions (o3 and higher)
|
||||
// 9 is not appreciably better than 8
|
||||
// 10 and higher are quite slow - good for filters but only good for reductions if matching the main zc level
|
||||
let compression = match opts.deflate {
|
||||
Deflaters::Libdeflater { compression } => {
|
||||
let compression = match opts.deflater {
|
||||
Deflater::Libdeflater { compression } => {
|
||||
if opts.fast_evaluation { 7 } else { 8 }.min(compression)
|
||||
}
|
||||
_ => 8,
|
||||
};
|
||||
let eval_deflater = Deflaters::Libdeflater { compression };
|
||||
let eval_deflater = Deflater::Libdeflater { compression };
|
||||
// If only one filter is selected, use this for evaluations
|
||||
let eval_filters = if opts.filter.len() == 1 {
|
||||
opts.filter.clone()
|
||||
let eval_filters = if opts.filters.len() == 1 {
|
||||
opts.filters.clone()
|
||||
} else {
|
||||
// None and Bigrams work well together, especially for alpha reductions
|
||||
indexset! {FilterStrategy::NONE, FilterStrategy::Bigrams}
|
||||
|
|
@ -440,7 +432,7 @@ fn optimize_raw(
|
|||
eval_filters.clone(),
|
||||
eval_deflater,
|
||||
false,
|
||||
opts.deflate == eval_deflater,
|
||||
opts.deflater == eval_deflater,
|
||||
);
|
||||
let mut new_image = perform_reductions(image.clone(), opts, &deadline, &eval);
|
||||
let eval_result = eval.get_best_candidate();
|
||||
|
|
@ -465,7 +457,7 @@ fn optimize_raw(
|
|||
eval_filters,
|
||||
eval_deflater,
|
||||
);
|
||||
(result?, opts.deflate)
|
||||
(result?, opts.deflater)
|
||||
} else {
|
||||
// If idat_recoding is off and reductions were attempted but ended up choosing the baseline,
|
||||
// we should still check if the evaluator compressed the baseline smaller than the original.
|
||||
|
|
@ -490,9 +482,9 @@ fn perform_trials(
|
|||
max_size: Option<usize>,
|
||||
mut eval_result: Option<Candidate>,
|
||||
eval_filters: IndexSet<FilterStrategy>,
|
||||
eval_deflater: Deflaters,
|
||||
eval_deflater: Deflater,
|
||||
) -> Option<Candidate> {
|
||||
let mut filters = opts.filter.clone();
|
||||
let mut filters = opts.filters.clone();
|
||||
let fast_eval = opts.fast_evaluation && (filters.len() > 1 || eval_result.is_some());
|
||||
if fast_eval {
|
||||
// Perform a fast evaluation of selected filters followed by a single main compression trial
|
||||
|
|
@ -509,7 +501,7 @@ fn perform_trials(
|
|||
filters,
|
||||
eval_deflater,
|
||||
opts.optimize_alpha,
|
||||
opts.deflate == eval_deflater,
|
||||
opts.deflater == eval_deflater,
|
||||
);
|
||||
if let Some(result) = &eval_result {
|
||||
eval.set_best_size(result.estimated_output_size);
|
||||
|
|
@ -525,9 +517,9 @@ fn perform_trials(
|
|||
|
||||
if result.idat_data.is_none() {
|
||||
// Compress with the main deflater
|
||||
debug!("Trying filter {} with {}", result.filter, opts.deflate);
|
||||
debug!("Trying filter {} with {}", result.filter, opts.deflater);
|
||||
let (data, _) = image.filter_image(result.filter_used.clone(), opts.optimize_alpha);
|
||||
match opts.deflate.deflate(&data, max_size) {
|
||||
match opts.deflater.deflate(&data, max_size) {
|
||||
Ok(idat_data) => {
|
||||
result.estimated_output_size = result.image.estimated_output_size(&idat_data);
|
||||
result.idat_data = Some(idat_data);
|
||||
|
|
@ -555,8 +547,8 @@ fn perform_trials(
|
|||
}
|
||||
}
|
||||
|
||||
debug!("Trying {} filters with {}", filters.len(), opts.deflate);
|
||||
let eval = Evaluator::new(deadline, filters, opts.deflate, opts.optimize_alpha, true);
|
||||
debug!("Trying {} filters with {}", filters.len(), opts.deflater);
|
||||
let eval = Evaluator::new(deadline, filters, opts.deflater, opts.optimize_alpha, true);
|
||||
if let Some(max_size) = max_size {
|
||||
eval.set_best_size(max_size);
|
||||
}
|
||||
|
|
@ -616,9 +608,14 @@ impl Deadline {
|
|||
|
||||
/// Display the format of the image data
|
||||
fn report_format(prefix: &str, png: &PngImage) {
|
||||
let interlaced = if png.ihdr.interlaced {
|
||||
"interlaced"
|
||||
} else {
|
||||
"non-interlaced"
|
||||
};
|
||||
debug!(
|
||||
"{}{}-bit {}, {}",
|
||||
prefix, png.ihdr.bit_depth, png.ihdr.color_type, png.ihdr.interlaced
|
||||
prefix, png.ihdr.bit_depth, png.ihdr.color_type, interlaced
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -648,7 +645,7 @@ fn recompress_frames(
|
|||
let image = PngImage::new(ihdr, &frame.data)?;
|
||||
let (filtered, _) = image.filter_image(filter.clone(), opts.optimize_alpha);
|
||||
let max_size = Some(frame.data.len() - 1);
|
||||
if let Ok(data) = opts.deflate.deflate(&filtered, max_size) {
|
||||
if let Ok(data) = opts.deflater.deflate(&filtered, max_size) {
|
||||
debug!(
|
||||
"Recompressed fdAT #{:<2}: {} ({} bytes decrease)",
|
||||
i,
|
||||
|
|
@ -671,7 +668,7 @@ fn copy_permissions(metadata_input: &Metadata, out_file: &File) -> PngResult<()>
|
|||
.set_permissions(metadata_input.permissions())
|
||||
.map_err(|err_io| {
|
||||
PngError::new(&format!(
|
||||
"unable to set permissions for output file: {err_io}"
|
||||
"Unable to set permissions for output file: {err_io}"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
|
@ -683,12 +680,11 @@ fn copy_times(_: &Metadata, _: &Path) -> PngResult<()> {
|
|||
|
||||
#[cfg(feature = "filetime")]
|
||||
fn copy_times(input_path_meta: &Metadata, out_path: &Path) -> PngResult<()> {
|
||||
let atime = filetime::FileTime::from_last_access_time(input_path_meta);
|
||||
let mtime = filetime::FileTime::from_last_modification_time(input_path_meta);
|
||||
trace!("attempting to set file times: atime: {atime:?}, mtime: {mtime:?}");
|
||||
filetime::set_file_times(out_path, atime, mtime).map_err(|err_io| {
|
||||
trace!("attempting to set file modification time: {mtime:?}");
|
||||
filetime::set_file_mtime(out_path, mtime).map_err(|err_io| {
|
||||
PngError::new(&format!(
|
||||
"unable to set file times on {out_path:?}: {err_io}"
|
||||
"Unable to set file times on {out_path:?}: {err_io}"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
35
src/main.rs
35
src/main.rs
|
|
@ -17,7 +17,7 @@
|
|||
mod rayon;
|
||||
|
||||
#[cfg(feature = "zopfli")]
|
||||
use std::num::NonZeroU8;
|
||||
use std::num::NonZeroU64;
|
||||
use std::{
|
||||
ffi::OsString, fs::DirBuilder, io::Write, path::PathBuf, process::ExitCode, time::Duration,
|
||||
};
|
||||
|
|
@ -26,7 +26,9 @@ use clap::ArgMatches;
|
|||
mod cli;
|
||||
use indexmap::IndexSet;
|
||||
use log::{Level, LevelFilter, error, warn};
|
||||
use oxipng::{Deflaters, FilterStrategy, InFile, Options, OutFile, PngError, StripChunks};
|
||||
#[cfg(feature = "zopfli")]
|
||||
use oxipng::ZopfliOptions;
|
||||
use oxipng::{Deflater, FilterStrategy, InFile, Options, OutFile, PngError, StripChunks};
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::cli::DISPLAY_CHUNKS;
|
||||
|
|
@ -198,7 +200,7 @@ fn parse_opts_into_struct(
|
|||
};
|
||||
|
||||
if let Some(x) = matches.get_one::<IndexSet<u8>>("filters") {
|
||||
opts.filter = x
|
||||
opts.filters = x
|
||||
.iter()
|
||||
.map(|&f| match f {
|
||||
0..=4 => FilterStrategy::Basic(f.try_into().unwrap()),
|
||||
|
|
@ -233,7 +235,7 @@ fn parse_opts_into_struct(
|
|||
None
|
||||
};
|
||||
|
||||
let out_file = if matches.get_flag("pretend") {
|
||||
let out_file = if matches.get_flag("dry-run") {
|
||||
OutFile::None
|
||||
} else if matches.get_flag("stdout") {
|
||||
OutFile::StdOut
|
||||
|
|
@ -276,10 +278,10 @@ fn parse_opts_into_struct(
|
|||
opts.idat_recoding = !matches.get_flag("no-recoding");
|
||||
|
||||
if let Some(x) = matches.get_one::<String>("interlace") {
|
||||
opts.interlace = if x == "keep" {
|
||||
None
|
||||
} else {
|
||||
x.parse::<u8>().unwrap().try_into().ok()
|
||||
opts.interlace = match x.as_str() {
|
||||
"off" | "0" => Some(false),
|
||||
"on" | "1" => Some(true),
|
||||
_ => None, // keep
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -335,13 +337,18 @@ fn parse_opts_into_struct(
|
|||
|
||||
#[cfg(feature = "zopfli")]
|
||||
if matches.get_flag("zopfli") {
|
||||
let iterations = *matches.get_one::<i64>("iterations").unwrap();
|
||||
opts.deflate = Deflaters::Zopfli {
|
||||
iterations: NonZeroU8::new(iterations as u8).unwrap(),
|
||||
};
|
||||
let iteration_count = *matches.get_one::<NonZeroU64>("iterations").unwrap();
|
||||
let iterations_without_improvement = *matches
|
||||
.get_one::<NonZeroU64>("iterations-without-improvement")
|
||||
.unwrap_or(&NonZeroU64::MAX);
|
||||
opts.deflater = Deflater::Zopfli(ZopfliOptions {
|
||||
iteration_count,
|
||||
iterations_without_improvement,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
if let (Deflaters::Libdeflater { compression }, Some(x)) =
|
||||
(&mut opts.deflate, matches.get_one::<i64>("compression"))
|
||||
if let (Deflater::Libdeflater { compression }, Some(x)) =
|
||||
(&mut opts.deflater, matches.get_one::<i64>("compression"))
|
||||
{
|
||||
*compression = *x as u8;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,7 @@ use std::{
|
|||
use indexmap::{IndexSet, indexset};
|
||||
use log::warn;
|
||||
|
||||
use crate::{
|
||||
deflate::Deflaters, filters::FilterStrategy, headers::StripChunks, interlace::Interlacing,
|
||||
};
|
||||
use crate::{deflate::Deflater, filters::FilterStrategy, headers::StripChunks};
|
||||
|
||||
/// Write destination for [`optimize`][crate::optimize].
|
||||
/// You can use [`optimize_from_memory`](crate::optimize_from_memory) to avoid external I/O.
|
||||
|
|
@ -99,16 +97,14 @@ pub struct Options {
|
|||
/// Which `FilterStrategy` to try on the file
|
||||
///
|
||||
/// Default: `None,Sub,Entropy,Bigrams`
|
||||
pub filter: IndexSet<FilterStrategy>,
|
||||
/// Whether to change the interlacing type of the file.
|
||||
pub filters: IndexSet<FilterStrategy>,
|
||||
/// Whether to change the interlacing of the file.
|
||||
///
|
||||
/// These are the interlacing types avaliable:
|
||||
/// - `None` will not change the current interlacing type.
|
||||
/// - `Some(x)` will change the file to interlacing mode `x`.
|
||||
/// See [`Interlacing`] for the possible interlacing types.
|
||||
/// - `None` will not change the current interlacing.
|
||||
/// - `Some(x)` will turn interlacing on or off.
|
||||
///
|
||||
/// Default: `Some(Interlacing::None)`
|
||||
pub interlace: Option<Interlacing>,
|
||||
/// Default: `Some(false)`
|
||||
pub interlace: Option<bool>,
|
||||
/// Whether to allow transparent pixels to be altered to improve compression.
|
||||
///
|
||||
/// Default: `false`
|
||||
|
|
@ -148,7 +144,7 @@ pub struct Options {
|
|||
#[cfg_attr(feature = "zopfli", doc = "(e.g. Zopfli)")]
|
||||
///
|
||||
/// Default: `Libdeflater`
|
||||
pub deflate: Deflaters,
|
||||
pub deflater: Deflater,
|
||||
/// Whether to use fast evaluation to pick the best filter
|
||||
///
|
||||
/// Default: `true`
|
||||
|
|
@ -187,16 +183,16 @@ impl Options {
|
|||
// The following methods make assumptions that they are operating
|
||||
// on an `Options` struct generated by the `default` method.
|
||||
fn apply_preset_0(mut self) -> Self {
|
||||
self.filter.clear();
|
||||
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
|
||||
self.filters.clear();
|
||||
if let Deflater::Libdeflater { compression } = &mut self.deflater {
|
||||
*compression = 5;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn apply_preset_1(mut self) -> Self {
|
||||
self.filter.clear();
|
||||
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
|
||||
self.filters.clear();
|
||||
if let Deflater::Libdeflater { compression } = &mut self.deflater {
|
||||
*compression = 10;
|
||||
}
|
||||
self
|
||||
|
|
@ -208,7 +204,7 @@ impl Options {
|
|||
|
||||
fn apply_preset_3(mut self) -> Self {
|
||||
self.fast_evaluation = false;
|
||||
self.filter = indexset! {
|
||||
self.filters = indexset! {
|
||||
FilterStrategy::NONE,
|
||||
FilterStrategy::Bigrams,
|
||||
FilterStrategy::BigEnt,
|
||||
|
|
@ -218,7 +214,7 @@ impl Options {
|
|||
}
|
||||
|
||||
fn apply_preset_4(mut self) -> Self {
|
||||
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
|
||||
if let Deflater::Libdeflater { compression } = &mut self.deflater {
|
||||
*compression = 12;
|
||||
}
|
||||
self.apply_preset_3()
|
||||
|
|
@ -226,19 +222,19 @@ impl Options {
|
|||
|
||||
fn apply_preset_5(mut self) -> Self {
|
||||
self.fast_evaluation = false;
|
||||
self.filter.insert(FilterStrategy::UP);
|
||||
self.filter.insert(FilterStrategy::MinSum);
|
||||
self.filter.insert(FilterStrategy::BigEnt);
|
||||
self.filter.insert(FilterStrategy::Brute);
|
||||
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
|
||||
self.filters.insert(FilterStrategy::UP);
|
||||
self.filters.insert(FilterStrategy::MinSum);
|
||||
self.filters.insert(FilterStrategy::BigEnt);
|
||||
self.filters.insert(FilterStrategy::Brute);
|
||||
if let Deflater::Libdeflater { compression } = &mut self.deflater {
|
||||
*compression = 12;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn apply_preset_6(mut self) -> Self {
|
||||
self.filter.insert(FilterStrategy::AVERAGE);
|
||||
self.filter.insert(FilterStrategy::PAETH);
|
||||
self.filters.insert(FilterStrategy::AVERAGE);
|
||||
self.filters.insert(FilterStrategy::PAETH);
|
||||
self.apply_preset_5()
|
||||
}
|
||||
}
|
||||
|
|
@ -249,13 +245,13 @@ impl Default for Options {
|
|||
Self {
|
||||
fix_errors: false,
|
||||
force: false,
|
||||
filter: indexset! {
|
||||
filters: indexset! {
|
||||
FilterStrategy::NONE,
|
||||
FilterStrategy::SUB,
|
||||
FilterStrategy::Entropy,
|
||||
FilterStrategy::Bigrams
|
||||
},
|
||||
interlace: Some(Interlacing::None),
|
||||
interlace: Some(false),
|
||||
optimize_alpha: false,
|
||||
bit_depth_reduction: true,
|
||||
color_type_reduction: true,
|
||||
|
|
@ -264,7 +260,7 @@ impl Default for Options {
|
|||
idat_recoding: true,
|
||||
scale_16: false,
|
||||
strip: StripChunks::None,
|
||||
deflate: Deflaters::Libdeflater { compression: 11 },
|
||||
deflater: Deflater::Libdeflater { compression: 11 },
|
||||
fast_evaluation: true,
|
||||
timeout: None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::{BufReader, Read, Write},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{fs, io::Write, path::Path, sync::Arc};
|
||||
|
||||
use bitvec::bitarr;
|
||||
use libdeflater::{CompressionLvl, Compressor};
|
||||
|
|
@ -19,7 +14,7 @@ use crate::{
|
|||
error::PngError,
|
||||
filters::*,
|
||||
headers::*,
|
||||
interlace::{Interlacing, deinterlace_image, interlace_image},
|
||||
interlace::{deinterlace_image, interlace_image},
|
||||
};
|
||||
|
||||
pub(crate) mod scan_lines;
|
||||
|
|
@ -62,28 +57,7 @@ impl PngData {
|
|||
}
|
||||
|
||||
pub fn read_file(filepath: &Path) -> Result<Vec<u8>, PngError> {
|
||||
let file = match File::open(filepath) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return Err(PngError::new("Failed to open file for reading")),
|
||||
};
|
||||
let file_len = file.metadata().map(|m| m.len() as usize).unwrap_or(0);
|
||||
let mut reader = BufReader::new(file);
|
||||
// Check file for PNG header
|
||||
let mut header = [0; 8];
|
||||
if reader.read_exact(&mut header).is_err() {
|
||||
return Err(PngError::new("Not a PNG file: too small"));
|
||||
}
|
||||
if !file_header_is_valid(&header) {
|
||||
return Err(PngError::new("Invalid PNG header detected"));
|
||||
}
|
||||
// Read raw png data into memory
|
||||
let mut byte_data: Vec<u8> = Vec::with_capacity(file_len);
|
||||
byte_data.extend_from_slice(&header);
|
||||
match reader.read_to_end(&mut byte_data) {
|
||||
Ok(_) => (),
|
||||
Err(_) => return Err(PngError::new("Failed to read from file")),
|
||||
}
|
||||
Ok(byte_data)
|
||||
fs::read(filepath).map_err(|e| PngError::ReadFailed(filepath.display().to_string(), e))
|
||||
}
|
||||
|
||||
/// Create a new `PngData` struct by reading a slice
|
||||
|
|
@ -293,18 +267,17 @@ impl PngImage {
|
|||
Ok(image)
|
||||
}
|
||||
|
||||
/// Convert the image to the specified interlacing type
|
||||
/// Returns true if the interlacing was changed, false otherwise
|
||||
/// The `interlace` parameter specifies the *new* interlacing mode
|
||||
/// Enable or disable interlacing
|
||||
/// Returns the new image if the interlacing was changed, None otherwise
|
||||
/// Assumes that the data has already been de-filtered
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn change_interlacing(&self, interlace: Interlacing) -> Option<Self> {
|
||||
pub fn change_interlacing(&self, interlace: bool) -> Option<Self> {
|
||||
if interlace == self.ihdr.interlaced {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(if interlace == Interlacing::Adam7 {
|
||||
Some(if interlace {
|
||||
// Convert progressive to interlaced data
|
||||
interlace_image(self)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{interlace::Interlacing, png::PngImage};
|
||||
use crate::png::PngImage;
|
||||
|
||||
/// An iterator over the scan lines of a PNG image
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -69,7 +69,7 @@ impl ScanLineRanges {
|
|||
width: png.ihdr.width,
|
||||
height: png.ihdr.height,
|
||||
left: png.data.len(),
|
||||
pass: if png.ihdr.interlaced == Interlacing::Adam7 {
|
||||
pass: if png.ihdr.interlaced {
|
||||
Some((1, 0))
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{ColorType, Deadline, Deflaters, Options, evaluate::Evaluator, png::PngImage};
|
||||
use crate::{ColorType, Deadline, Deflater, Options, evaluate::Evaluator, png::PngImage};
|
||||
|
||||
pub mod alpha;
|
||||
use crate::alpha::*;
|
||||
|
|
@ -21,8 +21,8 @@ pub(crate) fn perform_reductions(
|
|||
|
||||
// At low compression levels, skip some transformations which are less likely to be effective
|
||||
// This currently affects optimization presets 0-2
|
||||
let cheap = match opts.deflate {
|
||||
Deflaters::Libdeflater { compression } => compression < 12 && opts.fast_evaluation,
|
||||
let cheap = match opts.deflater {
|
||||
Deflater::Libdeflater { compression } => compression < 12 && opts.fast_evaluation,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ use indexmap::IndexSet;
|
|||
use rgb::RGBA8;
|
||||
|
||||
use crate::{
|
||||
Interlacing,
|
||||
colors::{BitDepth, ColorType},
|
||||
headers::IhdrData,
|
||||
png::{PngImage, scan_lines::ScanLine},
|
||||
|
|
@ -134,7 +133,7 @@ pub fn sorted_palette(png: &PngImage) -> Option<PngImage> {
|
|||
#[must_use]
|
||||
pub fn sorted_palette_mzeng(png: &PngImage) -> Option<PngImage> {
|
||||
// Interlacing not currently supported
|
||||
if png.ihdr.bit_depth != BitDepth::Eight || png.ihdr.interlaced != Interlacing::None {
|
||||
if png.ihdr.bit_depth != BitDepth::Eight || png.ihdr.interlaced {
|
||||
return None;
|
||||
}
|
||||
let palette = match &png.ihdr.color_type {
|
||||
|
|
@ -156,7 +155,7 @@ pub fn sorted_palette_mzeng(png: &PngImage) -> Option<PngImage> {
|
|||
#[must_use]
|
||||
pub fn sorted_palette_battiato(png: &PngImage) -> Option<PngImage> {
|
||||
// Interlacing not currently supported
|
||||
if png.ihdr.bit_depth != BitDepth::Eight || png.ihdr.interlaced != Interlacing::None {
|
||||
if png.ihdr.bit_depth != BitDepth::Eight || png.ihdr.interlaced {
|
||||
return None;
|
||||
}
|
||||
let palette = match &png.ihdr.color_type {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ const RGBA: u8 = 6;
|
|||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||
let options = oxipng::Options {
|
||||
force: true,
|
||||
filter: indexset! {FilterStrategy::NONE},
|
||||
..Default::default()
|
||||
};
|
||||
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||
|
|
@ -32,8 +31,7 @@ fn test_it_converts(
|
|||
|
||||
let (output, mut opts) = get_opts(&input);
|
||||
let png = PngData::new(&input, &opts).unwrap();
|
||||
opts.filter = IndexSet::new();
|
||||
opts.filter.insert(filter);
|
||||
opts.filters = indexset! {filter};
|
||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
#[cfg(feature = "zopfli")]
|
||||
use std::num::NonZeroU8;
|
||||
use std::{
|
||||
fs::remove_file,
|
||||
path::{Path, PathBuf},
|
||||
|
|
@ -16,7 +14,7 @@ fn get_opts(input: &Path) -> (OutFile, Options) {
|
|||
let options = Options {
|
||||
force: true,
|
||||
fast_evaluation: false,
|
||||
filter: indexset! {FilterStrategy::NONE},
|
||||
filters: indexset! {FilterStrategy::NONE},
|
||||
..Default::default()
|
||||
};
|
||||
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||
|
|
@ -316,7 +314,7 @@ fn strip_chunks_none() {
|
|||
fn interlacing_0_to_1() {
|
||||
let input = PathBuf::from("tests/files/interlacing_0_to_1.png");
|
||||
let (output, mut opts) = get_opts(&input);
|
||||
opts.interlace = Some(Interlacing::Adam7);
|
||||
opts.interlace = Some(true);
|
||||
|
||||
test_it_converts_callbacks(
|
||||
input,
|
||||
|
|
@ -327,10 +325,10 @@ fn interlacing_0_to_1() {
|
|||
RGB,
|
||||
BitDepth::Eight,
|
||||
|png| {
|
||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||
assert!(!png.raw.ihdr.interlaced);
|
||||
},
|
||||
|png| {
|
||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
||||
assert!(png.raw.ihdr.interlaced);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -339,7 +337,7 @@ fn interlacing_0_to_1() {
|
|||
fn interlacing_1_to_0() {
|
||||
let input = PathBuf::from("tests/files/interlacing_1_to_0.png");
|
||||
let (output, mut opts) = get_opts(&input);
|
||||
opts.interlace = Some(Interlacing::None);
|
||||
opts.interlace = Some(false);
|
||||
|
||||
test_it_converts_callbacks(
|
||||
input,
|
||||
|
|
@ -350,10 +348,10 @@ fn interlacing_1_to_0() {
|
|||
RGB,
|
||||
BitDepth::Eight,
|
||||
|png| {
|
||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
||||
assert!(png.raw.ihdr.interlaced);
|
||||
},
|
||||
|png| {
|
||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||
assert!(!png.raw.ihdr.interlaced);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -362,7 +360,7 @@ fn interlacing_1_to_0() {
|
|||
fn interlacing_0_to_1_small_files() {
|
||||
let input = PathBuf::from("tests/files/interlacing_0_to_1_small_files.png");
|
||||
let (output, mut opts) = get_opts(&input);
|
||||
opts.interlace = Some(Interlacing::Adam7);
|
||||
opts.interlace = Some(true);
|
||||
|
||||
test_it_converts_callbacks(
|
||||
input,
|
||||
|
|
@ -373,10 +371,10 @@ fn interlacing_0_to_1_small_files() {
|
|||
RGB,
|
||||
BitDepth::Eight,
|
||||
|png| {
|
||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||
assert!(!png.raw.ihdr.interlaced);
|
||||
},
|
||||
|png| {
|
||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
||||
assert!(png.raw.ihdr.interlaced);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -385,7 +383,7 @@ fn interlacing_0_to_1_small_files() {
|
|||
fn interlacing_1_to_0_small_files() {
|
||||
let input = PathBuf::from("tests/files/interlacing_1_to_0_small_files.png");
|
||||
let (output, mut opts) = get_opts(&input);
|
||||
opts.interlace = Some(Interlacing::None);
|
||||
opts.interlace = Some(false);
|
||||
|
||||
test_it_converts_callbacks(
|
||||
input,
|
||||
|
|
@ -396,10 +394,10 @@ fn interlacing_1_to_0_small_files() {
|
|||
RGB,
|
||||
BitDepth::Eight,
|
||||
|png| {
|
||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
||||
assert!(png.raw.ihdr.interlaced);
|
||||
},
|
||||
|png| {
|
||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||
assert!(!png.raw.ihdr.interlaced);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -408,8 +406,8 @@ fn interlacing_1_to_0_small_files() {
|
|||
fn interlaced_0_to_1_other_filter_mode() {
|
||||
let input = PathBuf::from("tests/files/interlaced_0_to_1_other_filter_mode.png");
|
||||
let (output, mut opts) = get_opts(&input);
|
||||
opts.interlace = Some(Interlacing::Adam7);
|
||||
opts.filter = indexset! {FilterStrategy::PAETH};
|
||||
opts.interlace = Some(true);
|
||||
opts.filters = indexset! {FilterStrategy::PAETH};
|
||||
|
||||
test_it_converts_callbacks(
|
||||
input,
|
||||
|
|
@ -420,10 +418,10 @@ fn interlaced_0_to_1_other_filter_mode() {
|
|||
GRAY,
|
||||
BitDepth::Sixteen,
|
||||
|png| {
|
||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||
assert!(!png.raw.ihdr.interlaced);
|
||||
},
|
||||
|png| {
|
||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
||||
assert!(png.raw.ihdr.interlaced);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -437,8 +435,6 @@ fn preserve_attrs() {
|
|||
.metadata()
|
||||
.expect("unable to get file metadata for output file");
|
||||
#[cfg(feature = "filetime")]
|
||||
let atime_canon = filetime::FileTime::from_last_access_time(&meta_input);
|
||||
#[cfg(feature = "filetime")]
|
||||
let mtime_canon = filetime::FileTime::from_last_modification_time(&meta_input);
|
||||
|
||||
let (mut output, opts) = get_opts(&input);
|
||||
|
|
@ -458,12 +454,6 @@ fn preserve_attrs() {
|
|||
.metadata()
|
||||
.expect("unable to get file metadata for output file");
|
||||
#[cfg(feature = "filetime")]
|
||||
assert_eq!(
|
||||
&atime_canon,
|
||||
&filetime::FileTime::from_last_access_time(&meta_output),
|
||||
"expected access time to be identical to that of input",
|
||||
);
|
||||
#[cfg(feature = "filetime")]
|
||||
assert_eq!(
|
||||
&mtime_canon,
|
||||
&filetime::FileTime::from_last_modification_time(&meta_output),
|
||||
|
|
@ -649,9 +639,7 @@ fn scale_16() {
|
|||
fn zopfli_mode() {
|
||||
let input = PathBuf::from("tests/files/zopfli_mode.png");
|
||||
let (output, mut opts) = get_opts(&input);
|
||||
opts.deflate = Deflaters::Zopfli {
|
||||
iterations: NonZeroU8::new(15).unwrap(),
|
||||
};
|
||||
opts.deflater = Deflater::Zopfli(ZopfliOptions::default());
|
||||
|
||||
test_it_converts(
|
||||
input,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
let options = oxipng::Options {
|
||||
force: true,
|
||||
fast_evaluation: false,
|
||||
filter: indexset! {FilterStrategy::NONE},
|
||||
filters: indexset! {FilterStrategy::NONE},
|
||||
interlace: None,
|
||||
..Default::default()
|
||||
};
|
||||
|
|
@ -35,7 +35,7 @@ fn test_it_converts(
|
|||
|
||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
||||
assert!(png.raw.ihdr.interlaced);
|
||||
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const INDEXED: u8 = 3;
|
|||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||
let options = oxipng::Options {
|
||||
force: true,
|
||||
filter: indexset! {FilterStrategy::NONE},
|
||||
filters: indexset! {FilterStrategy::NONE},
|
||||
..Default::default()
|
||||
};
|
||||
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||
|
|
@ -19,7 +19,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
|
||||
fn test_it_converts(
|
||||
input: &str,
|
||||
interlace: Interlacing,
|
||||
interlace: bool,
|
||||
color_type_in: u8,
|
||||
bit_depth_in: BitDepth,
|
||||
color_type_out: u8,
|
||||
|
|
@ -31,14 +31,7 @@ fn test_it_converts(
|
|||
opts.interlace = Some(interlace);
|
||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||
assert_eq!(
|
||||
png.raw.ihdr.interlaced,
|
||||
if interlace == Interlacing::Adam7 {
|
||||
Interlacing::None
|
||||
} else {
|
||||
Interlacing::Adam7
|
||||
}
|
||||
);
|
||||
assert_eq!(png.raw.ihdr.interlaced, !interlace);
|
||||
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
|
|
@ -65,7 +58,7 @@ fn test_it_converts(
|
|||
fn deinterlace_rgb_16() {
|
||||
test_it_converts(
|
||||
"tests/files/interlaced_rgb_16_should_be_rgb_16.png",
|
||||
Interlacing::None,
|
||||
false,
|
||||
RGB,
|
||||
BitDepth::Sixteen,
|
||||
RGB,
|
||||
|
|
@ -77,7 +70,7 @@ fn deinterlace_rgb_16() {
|
|||
fn deinterlace_rgb_8() {
|
||||
test_it_converts(
|
||||
"tests/files/interlaced_rgb_8_should_be_rgb_8.png",
|
||||
Interlacing::None,
|
||||
false,
|
||||
RGB,
|
||||
BitDepth::Eight,
|
||||
RGB,
|
||||
|
|
@ -89,7 +82,7 @@ fn deinterlace_rgb_8() {
|
|||
fn deinterlace_palette_8() {
|
||||
test_it_converts(
|
||||
"tests/files/interlaced_palette_8_should_be_palette_8.png",
|
||||
Interlacing::None,
|
||||
false,
|
||||
INDEXED,
|
||||
BitDepth::Eight,
|
||||
INDEXED,
|
||||
|
|
@ -101,7 +94,7 @@ fn deinterlace_palette_8() {
|
|||
fn deinterlace_palette_4() {
|
||||
test_it_converts(
|
||||
"tests/files/interlaced_palette_4_should_be_palette_4.png",
|
||||
Interlacing::None,
|
||||
false,
|
||||
INDEXED,
|
||||
BitDepth::Four,
|
||||
INDEXED,
|
||||
|
|
@ -113,7 +106,7 @@ fn deinterlace_palette_4() {
|
|||
fn deinterlace_palette_2() {
|
||||
test_it_converts(
|
||||
"tests/files/interlaced_palette_2_should_be_palette_2.png",
|
||||
Interlacing::None,
|
||||
false,
|
||||
INDEXED,
|
||||
BitDepth::Two,
|
||||
INDEXED,
|
||||
|
|
@ -125,7 +118,7 @@ fn deinterlace_palette_2() {
|
|||
fn deinterlace_palette_1() {
|
||||
test_it_converts(
|
||||
"tests/files/interlaced_palette_1_should_be_palette_1.png",
|
||||
Interlacing::None,
|
||||
false,
|
||||
INDEXED,
|
||||
BitDepth::One,
|
||||
INDEXED,
|
||||
|
|
@ -137,7 +130,7 @@ fn deinterlace_palette_1() {
|
|||
fn interlace_rgb_16() {
|
||||
test_it_converts(
|
||||
"tests/files/rgb_16_should_be_rgb_16.png",
|
||||
Interlacing::Adam7,
|
||||
true,
|
||||
RGB,
|
||||
BitDepth::Sixteen,
|
||||
RGB,
|
||||
|
|
@ -149,7 +142,7 @@ fn interlace_rgb_16() {
|
|||
fn interlace_rgb_8() {
|
||||
test_it_converts(
|
||||
"tests/files/rgb_8_should_be_rgb_8.png",
|
||||
Interlacing::Adam7,
|
||||
true,
|
||||
RGB,
|
||||
BitDepth::Eight,
|
||||
RGB,
|
||||
|
|
@ -161,7 +154,7 @@ fn interlace_rgb_8() {
|
|||
fn interlace_palette_8() {
|
||||
test_it_converts(
|
||||
"tests/files/palette_8_should_be_palette_8.png",
|
||||
Interlacing::Adam7,
|
||||
true,
|
||||
INDEXED,
|
||||
BitDepth::Eight,
|
||||
INDEXED,
|
||||
|
|
@ -173,7 +166,7 @@ fn interlace_palette_8() {
|
|||
fn interlace_palette_4() {
|
||||
test_it_converts(
|
||||
"tests/files/palette_4_should_be_palette_4.png",
|
||||
Interlacing::Adam7,
|
||||
true,
|
||||
INDEXED,
|
||||
BitDepth::Four,
|
||||
INDEXED,
|
||||
|
|
@ -185,7 +178,7 @@ fn interlace_palette_4() {
|
|||
fn interlace_palette_2() {
|
||||
test_it_converts(
|
||||
"tests/files/palette_2_should_be_palette_2.png",
|
||||
Interlacing::Adam7,
|
||||
true,
|
||||
INDEXED,
|
||||
BitDepth::Two,
|
||||
INDEXED,
|
||||
|
|
@ -197,7 +190,7 @@ fn interlace_palette_2() {
|
|||
fn interlace_palette_1() {
|
||||
test_it_converts(
|
||||
"tests/files/palette_1_should_be_palette_1.png",
|
||||
Interlacing::Adam7,
|
||||
true,
|
||||
INDEXED,
|
||||
BitDepth::One,
|
||||
INDEXED,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use oxipng::{internal_tests::*, *};
|
|||
fn get_opts() -> Options {
|
||||
Options {
|
||||
force: true,
|
||||
filter: indexset! {FilterStrategy::NONE},
|
||||
filters: indexset! {FilterStrategy::NONE},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
|||
let options = oxipng::Options {
|
||||
force: true,
|
||||
fast_evaluation: false,
|
||||
filter: indexset! {FilterStrategy::NONE},
|
||||
filters: indexset! {FilterStrategy::NONE},
|
||||
..Default::default()
|
||||
};
|
||||
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||
|
|
@ -36,7 +36,7 @@ fn test_it_converts(
|
|||
|
||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in, "test file is broken");
|
||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||
assert!(!png.raw.ihdr.interlaced);
|
||||
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const RGBA: u8 = 6;
|
|||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||
let options = oxipng::Options {
|
||||
force: true,
|
||||
filter: indexset! {FilterStrategy::NONE},
|
||||
filters: indexset! {FilterStrategy::NONE},
|
||||
..Default::default()
|
||||
};
|
||||
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||
|
|
@ -75,7 +75,7 @@ fn test_it_converts(
|
|||
fn issue_42() {
|
||||
let input = "tests/files/issue-42.png";
|
||||
let (output, mut opts) = get_opts(Path::new(input));
|
||||
opts.interlace = Some(Interlacing::Adam7);
|
||||
opts.interlace = Some(true);
|
||||
test_it_converts(
|
||||
input,
|
||||
Some((output, opts)),
|
||||
|
|
@ -186,7 +186,7 @@ fn issue_175() {
|
|||
fn issue_182() {
|
||||
let input = "tests/files/issue-182.png";
|
||||
let (output, mut opts) = get_opts(Path::new(input));
|
||||
opts.interlace = Some(Interlacing::Adam7);
|
||||
opts.interlace = Some(true);
|
||||
|
||||
test_it_converts(
|
||||
input,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ fn test_it_converts(
|
|||
|
||||
let (output, mut opts) = get_opts(&input);
|
||||
let png = PngData::new(&input, &opts).unwrap();
|
||||
opts.filter = indexset! {filter};
|
||||
opts.filters = indexset! {filter};
|
||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue