Move Zlib-specific options under Zlib compressor (#210)

Make it statically obvious that they're not compatible with other compressors.
This commit is contained in:
Ingvar Stepanyan 2020-04-17 15:05:13 +01:00 committed by GitHub
parent 380c21ea97
commit a497513d89
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 97 additions and 67 deletions

View file

@ -2,6 +2,7 @@ use crate::atomicmin::AtomicMin;
use crate::error::PngError; use crate::error::PngError;
use crate::Deadline; use crate::Deadline;
use crate::PngResult; use crate::PngResult;
use indexmap::IndexSet;
use miniz_oxide; use miniz_oxide;
use std::cmp::max; use std::cmp::max;
use zopfli; use zopfli;
@ -59,11 +60,27 @@ pub fn zopfli_deflate(data: &[u8]) -> PngResult<Vec<u8>> {
Ok(output) Ok(output)
} }
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
/// DEFLATE algorithms supported by oxipng /// DEFLATE algorithms supported by oxipng
pub enum Deflaters { pub enum Deflaters {
/// Use the Zlib/Miniz DEFLATE implementation /// Use the Zlib/Miniz DEFLATE implementation
Zlib, Zlib {
/// Which zlib compression levels to try on the file (1-9)
///
/// Default: `9`
compression: IndexSet<u8>,
/// Which zlib compression strategies to try on the file (0-3)
///
/// Default: `0-3`
strategies: IndexSet<u8>,
/// Window size to use when compressing the file, as `2^window` bytes.
///
/// Doesn't affect compression but may affect speed and memory usage.
/// 8-15 are valid values.
///
/// Default: `15`
window: u8,
},
/// Use the better but slower Zopfli implementation /// Use the better but slower Zopfli implementation
Zopfli, Zopfli,
/// Use libdeflater. /// Use libdeflater.

View file

@ -155,21 +155,6 @@ pub struct Options {
/// ///
/// Default: `None` /// Default: `None`
pub interlace: Option<u8>, pub interlace: Option<u8>,
/// Which zlib compression levels to try on the file (1-9)
///
/// Default: `9`
pub compression: IndexSet<u8>,
/// Which zlib compression strategies to try on the file (0-3)
///
/// Default: `0-3`
pub strategies: IndexSet<u8>,
/// Window size to use when compressing the file, as `2^window` bytes.
///
/// Doesn't affect compression but may affect speed and memory usage.
/// 8-15 are valid values.
///
/// Default: `15`
pub window: u8,
/// Alpha filtering strategies to use /// Alpha filtering strategies to use
pub alphas: IndexSet<colors::AlphaOptim>, pub alphas: IndexSet<colors::AlphaOptim>,
/// Whether to attempt bit depth reduction /// Whether to attempt bit depth reduction
@ -229,14 +214,18 @@ impl Options {
// on an `Options` struct generated by the `default` method. // on an `Options` struct generated by the `default` method.
fn apply_preset_0(mut self) -> Self { fn apply_preset_0(mut self) -> Self {
self.idat_recoding = false; self.idat_recoding = false;
self.compression.clear(); if let Deflaters::Zlib { compression, .. } = &mut self.deflate {
self.compression.insert(3); compression.clear();
compression.insert(3);
}
self self
} }
fn apply_preset_1(mut self) -> Self { fn apply_preset_1(mut self) -> Self {
self.filter.clear(); self.filter.clear();
self.strategies.clear(); if let Deflaters::Zlib { strategies, .. } = &mut self.deflate {
strategies.clear();
}
self.use_heuristics = true; self.use_heuristics = true;
self self
} }
@ -257,15 +246,21 @@ impl Options {
} }
fn apply_preset_5(mut self) -> Self { fn apply_preset_5(mut self) -> Self {
for i in 3..9 { if let Deflaters::Zlib { compression, .. } = &mut self.deflate {
self.compression.insert(i); compression.clear();
for i in 3..9 {
compression.insert(i);
}
} }
self.apply_preset_4() self.apply_preset_4()
} }
fn apply_preset_6(mut self) -> Self { fn apply_preset_6(mut self) -> Self {
for i in 1..3 { if let Deflaters::Zlib { compression, .. } = &mut self.deflate {
self.compression.insert(i); compression.clear();
for i in 1..3 {
compression.insert(i);
}
} }
self.apply_preset_5() self.apply_preset_5()
} }
@ -297,16 +292,17 @@ impl Default for Options {
verbosity: Some(0), verbosity: Some(0),
filter, filter,
interlace: None, interlace: None,
compression,
strategies,
window: 15,
alphas, alphas,
bit_depth_reduction: true, bit_depth_reduction: true,
color_type_reduction: true, color_type_reduction: true,
palette_reduction: true, palette_reduction: true,
idat_recoding: true, idat_recoding: true,
strip: Headers::None, strip: Headers::None,
deflate: Deflaters::Zlib, deflate: Deflaters::Zlib {
compression,
strategies,
window: 15,
},
use_heuristics: false, use_heuristics: false,
timeout: None, timeout: None,
} }
@ -476,26 +472,26 @@ fn optimize_png(
} }
let mut filter = opts.filter.clone(); let mut filter = opts.filter.clone();
let compression = &opts.compression; let mut strategies = match &opts.deflate {
let mut strategies = opts.strategies.clone(); Deflaters::Zlib { strategies, .. } => Some(strategies.clone()),
_ => None,
};
if opts.use_heuristics { if opts.use_heuristics {
// Heuristically determine which set of options to use // Heuristically determine which set of options to use
if png.raw.ihdr.bit_depth.as_u8() >= 8 let (use_filter, use_strategy) = if png.raw.ihdr.bit_depth.as_u8() >= 8
&& png.raw.ihdr.color_type != colors::ColorType::Indexed && png.raw.ihdr.color_type != colors::ColorType::Indexed
{ {
if filter.is_empty() { (5, 1)
filter.insert(5);
}
if strategies.is_empty() {
strategies.insert(1);
}
} else { } else {
if filter.is_empty() { (0, 0)
filter.insert(0); };
} if filter.is_empty() {
filter.insert(use_filter);
}
if let Some(strategies) = &mut strategies {
if strategies.is_empty() { if strategies.is_empty() {
strategies.insert(0); strategies.insert(use_strategy);
} }
} }
} }
@ -517,17 +513,17 @@ fn optimize_png(
if opts.idat_recoding || reduction_occurred { if opts.idat_recoding || reduction_occurred {
// Go through selected permutations and determine the best // Go through selected permutations and determine the best
let combinations = if opts.deflate == Deflaters::Zlib && !deadline.passed() { let combinations = if let Deflaters::Zlib { compression, .. } = &opts.deflate {
filter.len() * compression.len() * strategies.len() filter.len() * compression.len() * strategies.as_ref().unwrap().len()
} else { } else {
filter.len() filter.len()
}; };
let mut results: Vec<TrialOptions> = Vec::with_capacity(combinations); let mut results: Vec<TrialOptions> = Vec::with_capacity(combinations);
for f in &filter { for f in &filter {
if opts.deflate == Deflaters::Zlib { if let Deflaters::Zlib { compression, .. } = &opts.deflate {
for zc in compression { for zc in compression {
for zs in &strategies { for zs in strategies.as_ref().unwrap() {
results.push(TrialOptions { results.push(TrialOptions {
filter: *f, filter: *f,
compression: *zc, compression: *zc,
@ -576,11 +572,11 @@ fn optimize_png(
} }
let filtered = &filters[&trial.filter]; let filtered = &filters[&trial.filter];
let new_idat = match opts.deflate { let new_idat = match opts.deflate {
Deflaters::Zlib => deflate::deflate( Deflaters::Zlib { window, .. } => deflate::deflate(
filtered, filtered,
trial.compression, trial.compression,
trial.strategy, trial.strategy,
opts.window, window,
&best_size, &best_size,
&deadline, &deadline,
), ),

View file

@ -325,14 +325,6 @@ fn parse_opts_into_struct(
opts.filter = parse_numeric_range_opts(x, 0, 5).unwrap(); opts.filter = parse_numeric_range_opts(x, 0, 5).unwrap();
} }
if let Some(x) = matches.value_of("compression") {
opts.compression = parse_numeric_range_opts(x, 1, 9).unwrap();
}
if let Some(x) = matches.value_of("strategies") {
opts.strategies = parse_numeric_range_opts(x, 0, 3).unwrap();
}
if let Some(x) = matches.value_of("timeout") { if let Some(x) = matches.value_of("timeout") {
let num = x let num = x
.parse() .parse()
@ -340,18 +332,6 @@ fn parse_opts_into_struct(
opts.timeout = Some(Duration::from_secs(num)); opts.timeout = Some(Duration::from_secs(num));
} }
match matches.value_of("window") {
Some("256") => opts.window = 8,
Some("512") => opts.window = 9,
Some("1k") => opts.window = 10,
Some("2k") => opts.window = 11,
Some("4k") => opts.window = 12,
Some("8k") => opts.window = 13,
Some("16k") => opts.window = 14,
// 32k is default
_ => (),
}
let out_dir = if let Some(x) = matches.value_of("output_dir") { let out_dir = if let Some(x) = matches.value_of("output_dir") {
let path = PathBuf::from(x); let path = PathBuf::from(x);
if !path.exists() { if !path.exists() {
@ -482,9 +462,46 @@ fn parse_opts_into_struct(
} }
if matches.is_present("libdeflater") { if matches.is_present("libdeflater") {
if matches.is_present("zopfli") {
return Err("zopfli and libdeflater can't be used simultaneously".to_owned());
}
opts.deflate = Deflaters::Libdeflater; opts.deflate = Deflaters::Libdeflater;
} }
if let Deflaters::Zlib {
compression,
strategies,
window,
} = &mut opts.deflate
{
if let Some(x) = matches.value_of("compression") {
*compression = parse_numeric_range_opts(x, 1, 9).unwrap();
}
if let Some(x) = matches.value_of("strategies") {
*strategies = parse_numeric_range_opts(x, 0, 3).unwrap();
}
match matches.value_of("window") {
Some("256") => *window = 8,
Some("512") => *window = 9,
Some("1k") => *window = 10,
Some("2k") => *window = 11,
Some("4k") => *window = 12,
Some("8k") => *window = 13,
Some("16k") => *window = 14,
// 32k is default
_ => (),
}
} else if matches.is_present("compression")
|| matches.is_present("strategies")
|| matches.is_present("window")
{
return Err(
"compression, strategies and window options are compatible only with zlib".to_owned(),
);
}
if let Some(x) = matches.value_of("threads") { if let Some(x) = matches.value_of("threads") {
let threads = x.parse::<usize>().unwrap(); let threads = x.parse::<usize>().unwrap();