diff --git a/CHANGELOG.md b/CHANGELOG.md index b4039abe..ea721276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Fix program version that is displayed when running `oxipng -V` - Ensure `--quiet` mode is actually quiet (@SethDusek [#20](https://github.com/shssoichiro/oxipng/pull/20)) - Imply `--quiet` when `--stdout` is enabled + - Use heuristics to determine best combination for `-o1` ([#21](https://github.com/shssoichiro/oxipng/issues/21)) **Version 0.1.1** - Fix `oxipng *` writing all input files to one output file ([#15](https://github.com/shssoichiro/oxipng/issues/15)) diff --git a/src/lib.rs b/src/lib.rs index 773048aa..46334abe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,11 +41,14 @@ pub struct Options { pub palette_reduction: bool, pub idat_recoding: bool, pub strip: bool, + pub use_heuristics: bool, } pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { // Decode PNG from file - if opts.verbosity.is_some() { println!("Processing: {}", filepath.to_str().unwrap()) }; + if opts.verbosity.is_some() { + println!("Processing: {}", filepath.to_str().unwrap()) + }; let in_file = Path::new(filepath); let mut png = match png::PngData::new(&in_file) { Ok(x) => x, @@ -73,6 +76,31 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { println!(" File size = {} bytes", file_original_size); } + let mut filter = opts.filter.clone(); + let compression = opts.compression.clone(); + let memory = opts.memory.clone(); + let mut strategies = opts.strategies.clone(); + + if opts.use_heuristics { + // Heuristically determine which set of options to use + if png.ihdr_data.bit_depth.as_u8() >= 8 && + png.ihdr_data.color_type != png::ColorType::Indexed { + if filter.is_empty() { + filter.insert(5); + } + if strategies.is_empty() { + strategies.insert(1); + } + } else { + if filter.is_empty() { + filter.insert(0); + } + if strategies.is_empty() { + strategies.insert(0); + } + } + } + let mut something_changed = false; if opts.bit_depth_reduction { @@ -118,16 +146,17 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { if opts.idat_recoding || something_changed { // Go through selected permutations and determine the best let mut best: Option<(u8, u8, u8, u8, Vec)> = None; - let combinations = opts.filter.len() * opts.compression.len() * opts.memory.len() * - opts.strategies.len(); + let combinations = filter.len() * compression.len() * memory.len() * strategies.len(); let mut results = Vec::with_capacity(combinations); - if opts.verbosity.is_some() { println!("Trying: {} combinations", combinations) }; + if opts.verbosity.is_some() { + println!("Trying: {} combinations", combinations) + }; crossbeam::scope(|scope| { - for f in &opts.filter { + for f in &filter { let filtered = png.filter_image(*f); - for zc in &opts.compression { - for zm in &opts.memory { - for zs in &opts.strategies { + for zc in &compression { + for zm in &memory { + for zs in &strategies { let moved_filtered = filtered.clone(); results.push(scope.spawn(move || { let new_idat = match deflate::deflate::deflate(&moved_filtered, @@ -173,13 +202,13 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { if let Some(better) = best { png.idat_data = better.4.clone(); if opts.verbosity.is_some() { - println!("Found better combination:"); - println!(" zc = {} zm = {} zs = {} f = {} {} bytes", - better.1, - better.2, - better.3, - better.0, - png.idat_data.len()); + println!("Found better combination:"); + println!(" zc = {} zm = {} zs = {} f = {} {} bytes", + better.1, + better.2, + better.3, + better.0, + png.idat_data.len()); } } } @@ -240,25 +269,25 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { if opts.verbosity.is_some() { if idat_original_size >= png.idat_data.len() { println!(" IDAT size = {} bytes ({} bytes decrease)", - png.idat_data.len(), - idat_original_size - png.idat_data.len()); + png.idat_data.len(), + idat_original_size - png.idat_data.len()); } else { println!(" IDAT size = {} bytes ({} bytes increase)", - png.idat_data.len(), - png.idat_data.len() - idat_original_size); + png.idat_data.len(), + png.idat_data.len() - idat_original_size); } if file_original_size >= output_data.len() { println!(" file size = {} bytes ({} bytes = {:.2}% decrease)", - output_data.len(), - file_original_size - output_data.len(), - (file_original_size - output_data.len()) as f64 / file_original_size as f64 * - 100f64); + output_data.len(), + file_original_size - output_data.len(), + (file_original_size - output_data.len()) as f64 / file_original_size as f64 * + 100f64); } else { println!(" file size = {} bytes ({} bytes = {:.2}% increase)", - output_data.len(), - output_data.len() - file_original_size, - (output_data.len() - file_original_size) as f64 / file_original_size as f64 * - 100f64); + output_data.len(), + output_data.len() - file_original_size, + (output_data.len() - file_original_size) as f64 / file_original_size as f64 * + 100f64); } } Ok(()) diff --git a/src/main.rs b/src/main.rs index 989473f0..59f1093f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,6 +46,7 @@ fn main() { palette_reduction: true, idat_recoding: true, strip: false, + use_heuristics: false, }; let matches = App::new("oxipng") @@ -202,7 +203,7 @@ fn main() { .long("strip")) .after_help("Optimization levels: -o 0 => --zc 3 --nz (0 or 1 trials) - -o 1 => --zc 9 (1 trial) + -o 1 => --zc 9 (1 trial, determined heuristically) -o 2 => --zc 9 --zs 0-3 --f 0,5 (8 trials) -o 3 => --zc 9 --zm 8-9 --zs 0-3 --f 0,5 (16 trials) -o 4 => --zc 9 --zm 8-9 --zs 0-3 --f 0-5 (48 trials) @@ -268,12 +269,11 @@ fn parse_opts_into_struct(matches: &ArgMatches, opts: &mut oxipng::Options) -> R opts.compression = compression; } Some("1") => { - let mut filter = HashSet::new(); - filter.insert(0); + let filter = HashSet::new(); opts.filter = filter; - let mut strategies = HashSet::new(); - strategies.insert(0); + let strategies = HashSet::new(); opts.strategies = strategies; + opts.use_heuristics = true; } // 2 is the default Some("3") => { diff --git a/src/png.rs b/src/png.rs index a3f73b74..91d5b052 100644 --- a/src/png.rs +++ b/src/png.rs @@ -68,7 +68,7 @@ impl fmt::Display for BitDepth { } impl BitDepth { - fn as_u8(&self) -> u8 { + pub fn as_u8(&self) -> u8 { match *self { BitDepth::One => 1, BitDepth::Two => 2, @@ -77,7 +77,7 @@ impl BitDepth { BitDepth::Sixteen => 16, } } - fn from_u8(depth: u8) -> BitDepth { + pub fn from_u8(depth: u8) -> BitDepth { match depth { 1 => BitDepth::One, 2 => BitDepth::Two, diff --git a/tests/filters.rs b/tests/filters.rs index d10d23ed..600aa6b3 100644 --- a/tests/filters.rs +++ b/tests/filters.rs @@ -45,6 +45,7 @@ fn get_opts(input: &Path) -> oxipng::Options { palette_reduction: true, idat_recoding: true, strip: false, + use_heuristics: false, } } diff --git a/tests/flags.rs b/tests/flags.rs index c69913ed..f1381b7d 100644 --- a/tests/flags.rs +++ b/tests/flags.rs @@ -45,6 +45,7 @@ fn get_opts(input: &Path) -> oxipng::Options { palette_reduction: true, idat_recoding: true, strip: false, + use_heuristics: false, } } diff --git a/tests/reduction.rs b/tests/reduction.rs index 3662302e..854eb662 100644 --- a/tests/reduction.rs +++ b/tests/reduction.rs @@ -45,6 +45,7 @@ fn get_opts(input: &Path) -> oxipng::Options { palette_reduction: true, idat_recoding: true, strip: false, + use_heuristics: false, } }