diff --git a/src/lib.rs b/src/lib.rs index 6f58d8cc..1f3346bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ pub struct Options { pub recursive: bool, pub clobber: bool, pub create: bool, + pub force: bool, pub preserve_attrs: bool, pub verbosity: Option, pub filter: HashSet, @@ -129,7 +130,7 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { // TODO: Perform stripping let output_data = png.output(); - if file_original_size <= output_data.len() { + if file_original_size <= output_data.len() && !opts.force { println!("File already optimized"); return Ok(()); } @@ -174,13 +175,26 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { } } } - println!(" IDAT size = {} bytes ({} bytes decrease)", - png.idat_data.len(), - idat_original_size - png.idat_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); + 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()); + } else { + println!(" IDAT size = {} bytes ({} bytes increate)", + 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); + } 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); + } Ok(()) } diff --git a/src/main.rs b/src/main.rs index cd867ffc..dbcfc4b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,7 @@ fn main() { fix_errors: false, clobber: true, create: true, + force: false, preserve_attrs: false, verbosity: Some(0), filter: filter, @@ -70,6 +71,10 @@ fn main() { .help("Back up modified files") .short("b") .long("backup")) + .arg(Arg::with_name("force") + .help("Write output even if larger than the original") + .short("F") + .long("force")) .arg(Arg::with_name("recursive") .help("Recurse into subdirectories") .short("r") @@ -409,6 +414,10 @@ fn parse_opts_into_struct(matches: &ArgMatches, opts: &mut oxipng::Options) -> R opts.backup = true; } + if matches.is_present("force") { + opts.force = true; + } + if matches.is_present("recursive") { opts.recursive = true; } diff --git a/src/png.rs b/src/png.rs index 4deb2b5d..6e258855 100644 --- a/src/png.rs +++ b/src/png.rs @@ -8,7 +8,7 @@ use std::io::prelude::*; use std::iter::Iterator; use std::path::Path; -#[derive(Debug)] +#[derive(Debug,PartialEq)] pub enum ColorType { Grayscale, RGB, @@ -43,7 +43,7 @@ impl ColorType { } } -#[derive(Debug)] +#[derive(Debug,PartialEq)] pub enum BitDepth { One, Two, diff --git a/tests/oxipng.rs b/tests/oxipng.rs new file mode 100644 index 00000000..3ccca87d --- /dev/null +++ b/tests/oxipng.rs @@ -0,0 +1,131 @@ +extern crate oxipng; + +use oxipng::png; +use std::collections::HashSet; +use std::fs::remove_file; +use std::path::Path; +use std::path::PathBuf; + +fn get_opts(input: &Path) -> oxipng::Options { + let mut filter = HashSet::new(); + filter.insert(0); + filter.insert(5); + let mut compression = HashSet::new(); + compression.insert(9); + let mut memory = HashSet::new(); + memory.insert(9); + let mut strategies = HashSet::new(); + for i in 0..4 { + strategies.insert(i); + } + + oxipng::Options { + backup: false, + out_file: input.with_extension("out.png").to_owned(), + out_dir: None, + stdout: false, + pretend: false, + recursive: false, + fix_errors: false, + force: true, + clobber: true, + create: true, + preserve_attrs: false, + verbosity: Some(0), + filter: filter, + interlace: None, + compression: compression, + memory: memory, + strategies: strategies, + window: 15, + bit_depth_reduction: true, + color_type_reduction: true, + palette_reduction: true, + idat_recoding: true, + strip: false, + } +} + +#[test] +fn reduce_rgba_png() { + let input = PathBuf::from("tests/files/test_rgba.png"); + let opts = get_opts(&input); + let output = opts.out_file.clone(); + + match oxipng::optimize(&input, &opts) { + Ok(_) => (), + Err(x) => panic!(x.to_owned()) + }; + assert!(output.exists()); + + let png = match png::PngData::new(&output) { + Ok(x) => { + remove_file(output).ok(); + x + }, + Err(x) => { + remove_file(output).ok(); + panic!(x.to_owned()) + }, + }; + + assert!(png.ihdr_data.color_type == png::ColorType::Indexed); + assert!(png.ihdr_data.bit_depth == png::BitDepth::Eight); + assert!(png.palette.unwrap().len() == 43 * 3); +} + +#[test] +fn reduce_rgb_png() { + let input = PathBuf::from("tests/files/test_rgb.png"); + let opts = get_opts(&input); + let output = opts.out_file.clone(); + + match oxipng::optimize(&input, &opts) { + Ok(_) => (), + Err(x) => panic!(x.to_owned()) + }; + assert!(output.exists()); + + let png = match png::PngData::new(&output) { + Ok(x) => { + remove_file(output).ok(); + x + }, + Err(x) => { + remove_file(output).ok(); + panic!(x.to_owned()) + }, + }; + + assert!(png.ihdr_data.color_type == png::ColorType::Indexed); + assert!(png.ihdr_data.bit_depth == png::BitDepth::Eight); + assert!(png.palette.unwrap().len() == 43 * 3); +} + +#[test] +fn reduce_palette_png() { + let input = PathBuf::from("tests/files/test_palette.png"); + let opts = get_opts(&input); + let output = opts.out_file.clone(); + + match oxipng::optimize(&input, &opts) { + Ok(_) => (), + Err(x) => panic!(x.to_owned()) + }; + assert!(output.exists()); + + let png = match png::PngData::new(&output) { + Ok(x) => { + remove_file(output).ok(); + x + }, + Err(x) => { + remove_file(output).ok(); + panic!(x.to_owned()) + }, + }; + + assert!(png.ihdr_data.color_type == png::ColorType::Indexed); + assert!(png.ihdr_data.bit_depth == png::BitDepth::Eight); + assert!(png.palette.unwrap().len() == 43 * 3); +}