Select compression and filter modes heuristically for -o1

Closes #21
This commit is contained in:
Joshua Holmer 2016-03-04 15:24:58 -05:00
parent 849d36ec6c
commit c6452beb12
7 changed files with 67 additions and 34 deletions

View file

@ -2,6 +2,7 @@
- Fix program version that is displayed when running `oxipng -V` - 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)) - Ensure `--quiet` mode is actually quiet (@SethDusek [#20](https://github.com/shssoichiro/oxipng/pull/20))
- Imply `--quiet` when `--stdout` is enabled - 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** **Version 0.1.1**
- Fix `oxipng *` writing all input files to one output file ([#15](https://github.com/shssoichiro/oxipng/issues/15)) - Fix `oxipng *` writing all input files to one output file ([#15](https://github.com/shssoichiro/oxipng/issues/15))

View file

@ -41,11 +41,14 @@ pub struct Options {
pub palette_reduction: bool, pub palette_reduction: bool,
pub idat_recoding: bool, pub idat_recoding: bool,
pub strip: bool, pub strip: bool,
pub use_heuristics: bool,
} }
pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
// Decode PNG from file // 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 in_file = Path::new(filepath);
let mut png = match png::PngData::new(&in_file) { let mut png = match png::PngData::new(&in_file) {
Ok(x) => x, Ok(x) => x,
@ -73,6 +76,31 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
println!(" File size = {} bytes", file_original_size); 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; let mut something_changed = false;
if opts.bit_depth_reduction { if opts.bit_depth_reduction {
@ -118,16 +146,17 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
if opts.idat_recoding || something_changed { if opts.idat_recoding || something_changed {
// Go through selected permutations and determine the best // Go through selected permutations and determine the best
let mut best: Option<(u8, u8, u8, u8, Vec<u8>)> = None; let mut best: Option<(u8, u8, u8, u8, Vec<u8>)> = None;
let combinations = opts.filter.len() * opts.compression.len() * opts.memory.len() * let combinations = filter.len() * compression.len() * memory.len() * strategies.len();
opts.strategies.len();
let mut results = Vec::with_capacity(combinations); 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| { crossbeam::scope(|scope| {
for f in &opts.filter { for f in &filter {
let filtered = png.filter_image(*f); let filtered = png.filter_image(*f);
for zc in &opts.compression { for zc in &compression {
for zm in &opts.memory { for zm in &memory {
for zs in &opts.strategies { for zs in &strategies {
let moved_filtered = filtered.clone(); let moved_filtered = filtered.clone();
results.push(scope.spawn(move || { results.push(scope.spawn(move || {
let new_idat = match deflate::deflate::deflate(&moved_filtered, 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 { if let Some(better) = best {
png.idat_data = better.4.clone(); png.idat_data = better.4.clone();
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
println!("Found better combination:"); println!("Found better combination:");
println!(" zc = {} zm = {} zs = {} f = {} {} bytes", println!(" zc = {} zm = {} zs = {} f = {} {} bytes",
better.1, better.1,
better.2, better.2,
better.3, better.3,
better.0, better.0,
png.idat_data.len()); png.idat_data.len());
} }
} }
} }
@ -240,25 +269,25 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
if idat_original_size >= png.idat_data.len() { if idat_original_size >= png.idat_data.len() {
println!(" IDAT size = {} bytes ({} bytes decrease)", println!(" IDAT size = {} bytes ({} bytes decrease)",
png.idat_data.len(), png.idat_data.len(),
idat_original_size - png.idat_data.len()); idat_original_size - png.idat_data.len());
} else { } else {
println!(" IDAT size = {} bytes ({} bytes increase)", println!(" IDAT size = {} bytes ({} bytes increase)",
png.idat_data.len(), png.idat_data.len(),
png.idat_data.len() - idat_original_size); png.idat_data.len() - idat_original_size);
} }
if file_original_size >= output_data.len() { if file_original_size >= output_data.len() {
println!(" file size = {} bytes ({} bytes = {:.2}% decrease)", println!(" file size = {} bytes ({} bytes = {:.2}% decrease)",
output_data.len(), output_data.len(),
file_original_size - output_data.len(), file_original_size - output_data.len(),
(file_original_size - output_data.len()) as f64 / file_original_size as f64 * (file_original_size - output_data.len()) as f64 / file_original_size as f64 *
100f64); 100f64);
} else { } else {
println!(" file size = {} bytes ({} bytes = {:.2}% increase)", println!(" file size = {} bytes ({} bytes = {:.2}% increase)",
output_data.len(), output_data.len(),
output_data.len() - file_original_size, output_data.len() - file_original_size,
(output_data.len() - file_original_size) as f64 / file_original_size as f64 * (output_data.len() - file_original_size) as f64 / file_original_size as f64 *
100f64); 100f64);
} }
} }
Ok(()) Ok(())

View file

@ -46,6 +46,7 @@ fn main() {
palette_reduction: true, palette_reduction: true,
idat_recoding: true, idat_recoding: true,
strip: false, strip: false,
use_heuristics: false,
}; };
let matches = App::new("oxipng") let matches = App::new("oxipng")
@ -202,7 +203,7 @@ fn main() {
.long("strip")) .long("strip"))
.after_help("Optimization levels: .after_help("Optimization levels:
-o 0 => --zc 3 --nz (0 or 1 trials) -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 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 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) -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; opts.compression = compression;
} }
Some("1") => { Some("1") => {
let mut filter = HashSet::new(); let filter = HashSet::new();
filter.insert(0);
opts.filter = filter; opts.filter = filter;
let mut strategies = HashSet::new(); let strategies = HashSet::new();
strategies.insert(0);
opts.strategies = strategies; opts.strategies = strategies;
opts.use_heuristics = true;
} }
// 2 is the default // 2 is the default
Some("3") => { Some("3") => {

View file

@ -68,7 +68,7 @@ impl fmt::Display for BitDepth {
} }
impl BitDepth { impl BitDepth {
fn as_u8(&self) -> u8 { pub fn as_u8(&self) -> u8 {
match *self { match *self {
BitDepth::One => 1, BitDepth::One => 1,
BitDepth::Two => 2, BitDepth::Two => 2,
@ -77,7 +77,7 @@ impl BitDepth {
BitDepth::Sixteen => 16, BitDepth::Sixteen => 16,
} }
} }
fn from_u8(depth: u8) -> BitDepth { pub fn from_u8(depth: u8) -> BitDepth {
match depth { match depth {
1 => BitDepth::One, 1 => BitDepth::One,
2 => BitDepth::Two, 2 => BitDepth::Two,

View file

@ -45,6 +45,7 @@ fn get_opts(input: &Path) -> oxipng::Options {
palette_reduction: true, palette_reduction: true,
idat_recoding: true, idat_recoding: true,
strip: false, strip: false,
use_heuristics: false,
} }
} }

View file

@ -45,6 +45,7 @@ fn get_opts(input: &Path) -> oxipng::Options {
palette_reduction: true, palette_reduction: true,
idat_recoding: true, idat_recoding: true,
strip: false, strip: false,
use_heuristics: false,
} }
} }

View file

@ -45,6 +45,7 @@ fn get_opts(input: &Path) -> oxipng::Options {
palette_reduction: true, palette_reduction: true,
idat_recoding: true, idat_recoding: true,
strip: false, strip: false,
use_heuristics: false,
} }
} }