diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..828232f4 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +format_strings = false diff --git a/src/lib.rs b/src/lib.rs index 807e1473..ba21cbaf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ extern crate libz_sys; extern crate libc; use std::path::Path; +use std::path::PathBuf; use std::collections::HashSet; pub mod deflate { @@ -12,11 +13,13 @@ pub mod deflate { } pub mod png; -pub struct Options<'a> { +pub struct Options { pub backup: bool, - pub out_file: &'a Path, + pub out_file: PathBuf, + pub out_dir: Option, + pub stdout: bool, pub fix_errors: bool, - pub force: bool, + pub pretend: bool, pub clobber: bool, pub create: bool, pub preserve_attrs: bool, @@ -31,6 +34,7 @@ pub struct Options<'a> { pub color_type_reduction: bool, pub palette_reduction: bool, pub idat_recoding: bool, + pub strip: bool, } pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { @@ -65,7 +69,6 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { // TODO: Bit depth/palette reduction // // TODO: Apply interlacing changes - // TODO: Force reencoding if interlacing was changed // // Go through selected permutations and determine the best let mut best: Option<(u8, u8, u8, u8)> = None; @@ -73,6 +76,7 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { let combinations = opts.filter.len() * opts.compression.len() * opts.memory.len() * opts.strategies.len(); println!("Trying: {} combinations", combinations); + // TODO: Force reencoding if interlacing was changed // TODO: Multithreading for f in &opts.filter { for zc in &opts.compression { diff --git a/src/main.rs b/src/main.rs index 4f153f67..bf33f2eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,16 +2,13 @@ extern crate optipng; extern crate clap; extern crate regex; -use clap::{App, Arg}; +use clap::{App, Arg, ArgMatches}; use regex::Regex; use std::collections::HashSet; -use std::env; use std::path::Path; +use std::path::PathBuf; fn main() { - // TODO: Handle wildcards - let filename = env::args().skip(1).next().unwrap(); - let infile = Path::new(&filename); let mut filter = HashSet::new(); filter.insert(0); filter.insert(5); @@ -26,9 +23,11 @@ fn main() { let default_opts = optipng::Options { backup: false, - out_file: infile, + out_file: PathBuf::new(), + out_dir: None, + stdout: false, + pretend: false, fix_errors: false, - force: false, clobber: true, create: true, preserve_attrs: false, @@ -43,9 +42,9 @@ fn main() { color_type_reduction: true, palette_reduction: true, idat_recoding: true, + strip: false, }; - // TODO: Handle command line args let matches = App::new("optipng") .version("2.0.0-alpha.1") .author("Joshua Holmer ") @@ -53,6 +52,7 @@ fn main() { .arg(Arg::with_name("files") .help("File(s) to compress") .index(1) + .multiple(true) .required(true)) .arg(Arg::with_name("optimization") .help("Optimization level - Default: 2") @@ -72,11 +72,20 @@ fn main() { .arg(Arg::with_name("output_dir") .help("Write output file(s) to ") .long("dir") - .takes_value(true)) + .takes_value(true) + .conflicts_with("output_file") + .conflicts_with("stdout")) .arg(Arg::with_name("output_file") .help("Write output file to ") .long("out") - .takes_value(true)) + .takes_value(true) + .conflicts_with("output_dir") + .conflicts_with("stdout")) + .arg(Arg::with_name("stdout") + .help("Write output to stdout") + .long("stdout") + .conflicts_with("output_dir") + .conflicts_with("output_file")) .arg(Arg::with_name("fix") .help("Enable error recovery") .long("fix")) @@ -101,9 +110,6 @@ fn main() { .short("v") .long("verbose") .conflicts_with("quiet")) - .arg(Arg::with_name("version") - .help("Show copyright and version info") - .short("V")) .arg(Arg::with_name("filters") .help("PNG delta filters (0-5) - Default: 0,5") .short("f") @@ -170,14 +176,14 @@ fn main() { .arg(Arg::with_name("no-color-reduction") .help("No color type reduction") .long("nc")) - .arg(Arg::with_name("no-bit-depth") + .arg(Arg::with_name("no-palette-reduction") .help("No palette reduction") .long("np")) .arg(Arg::with_name("no-reductions") .help("No reductions") .long("nx")) .arg(Arg::with_name("no-recoding") - .help("No IDAT recoding") + .help("No IDAT recoding unless necessary") .long("nz")) .arg(Arg::with_name("strip") .help("Strip all metadata objects") @@ -201,13 +207,11 @@ fn main() { let mut opts = default_opts; - if !matches.is_present("files") { - return (); - } + parse_opts_into_struct(&matches, &mut opts); - // TODO: Handle optimization presets for input in matches.values_of("files").unwrap() { - opts.out_file = Path::new(input); + opts.out_file = PathBuf::from(input); + // TODO: Handle wildcards match optipng::optimize(Path::new(input), &opts) { Ok(_) => (), Err(x) => println!("{}", x), @@ -215,7 +219,212 @@ fn main() { } } -fn parse_numeric_range_opts(input: &str, min_value: u8, max_value: u8) -> Result, String> { +fn parse_opts_into_struct(matches: &ArgMatches, opts: &mut optipng::Options) { + match matches.value_of("optimization") { + Some("0") => { + opts.idat_recoding = false; + let mut compression = HashSet::new(); + compression.insert(3); + opts.compression = compression; + } + Some("1") => { + let mut filter = HashSet::new(); + filter.insert(0); + opts.filter = filter; + let mut strategies = HashSet::new(); + strategies.insert(0); + opts.strategies = strategies; + } + // 2 is the default + Some("3") => { + let mut filter = HashSet::new(); + filter.insert(0); + filter.insert(5); + opts.filter = filter; + let mut compression = HashSet::new(); + compression.insert(9); + opts.compression = compression; + let mut memory = HashSet::new(); + for i in 8..10 { + memory.insert(i); + } + opts.memory = memory; + let mut strategies = HashSet::new(); + for i in 0..4 { + strategies.insert(i); + } + opts.strategies = strategies; + } + Some("4") => { + let mut filter = HashSet::new(); + for i in 0..6 { + filter.insert(i); + } + opts.filter = filter; + let mut compression = HashSet::new(); + compression.insert(9); + opts.compression = compression; + let mut memory = HashSet::new(); + for i in 8..10 { + memory.insert(i); + } + opts.memory = memory; + let mut strategies = HashSet::new(); + for i in 0..4 { + strategies.insert(i); + } + opts.strategies = strategies; + } + Some("5") => { + let mut filter = HashSet::new(); + for i in 0..6 { + filter.insert(i); + } + opts.filter = filter; + let mut compression = HashSet::new(); + for i in 3..10 { + compression.insert(i); + } + opts.compression = compression; + let mut memory = HashSet::new(); + for i in 8..10 { + memory.insert(i); + } + opts.memory = memory; + let mut strategies = HashSet::new(); + for i in 0..4 { + strategies.insert(i); + } + opts.strategies = strategies; + } + Some("6") => { + let mut filter = HashSet::new(); + for i in 0..6 { + filter.insert(i); + } + opts.filter = filter; + let mut compression = HashSet::new(); + for i in 1..10 { + compression.insert(i); + } + opts.compression = compression; + let mut memory = HashSet::new(); + for i in 7..10 { + memory.insert(i); + } + opts.memory = memory; + let mut strategies = HashSet::new(); + for i in 0..4 { + strategies.insert(i); + } + opts.strategies = strategies; + } + _ => (), // Use default + } + + if let Some(x) = matches.value_of("interlace") { + opts.interlace = x.parse::().ok(); + } + + if let Some(x) = matches.value_of("filters") { + 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("memory") { + opts.memory = 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(); + } + + 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 + _ => (), + } + + if let Some(x) = matches.value_of("output_dir") { + opts.out_dir = Some(PathBuf::from(x)); + } + + if let Some(x) = matches.value_of("output_file") { + opts.out_file = PathBuf::from(x); + } + + if matches.is_present("stdout") { + opts.stdout = true; + } + + if matches.is_present("backup") { + opts.backup = true; + } + + if matches.is_present("fix") { + opts.fix_errors = true; + } + + if matches.is_present("clobber") { + opts.clobber = false; + } + + if matches.is_present("pretend") { + opts.pretend = true; + } + + if matches.is_present("preserve") { + opts.preserve_attrs = true; + } + + if matches.is_present("quiet") { + opts.verbosity = None; + } + + if matches.is_present("verbose") { + opts.verbosity = Some(1); + } + + if matches.is_present("no-bit-reduction") { + opts.bit_depth_reduction = false; + } + + if matches.is_present("no-color-reduction") { + opts.color_type_reduction = false; + } + + if matches.is_present("no-palette-reduction") { + opts.palette_reduction = false; + } + + if matches.is_present("no-reductions") { + opts.bit_depth_reduction = false; + opts.color_type_reduction = false; + opts.palette_reduction = false; + } + + if matches.is_present("no-recoding") { + opts.idat_recoding = false; + } + + if matches.is_present("strip") { + opts.strip = true; + } +} + +fn parse_numeric_range_opts(input: &str, + min_value: u8, + max_value: u8) + -> Result, String> { let one_item = Regex::new(format!("^[{}-{}]$", min_value, max_value).as_ref()).unwrap(); let multiple_items = Regex::new(format!("^([{}-{}])(,|-)([{}-{}])$", min_value, @@ -224,9 +433,11 @@ fn parse_numeric_range_opts(input: &str, min_value: u8, max_value: u8) -> Result max_value) .as_ref()) .unwrap(); + let mut items = HashSet::new(); if one_item.is_match(input) { - return Ok(vec![input.parse::().unwrap()]); + items.insert(input.parse::().unwrap()); + return Ok(items); } if let Some(captures) = multiple_items.captures(input) { @@ -237,8 +448,17 @@ fn parse_numeric_range_opts(input: &str, min_value: u8, max_value: u8) -> Result } return Ok(match &captures[2] { - "," => vec![first, second], - "-" => (first..second + 1).collect(), + "," => { + items.insert(first); + items.insert(second); + return Ok(items); + } + "-" => { + for i in first..second + 1 { + items.insert(i); + } + return Ok(items); + } _ => panic!("Unreachable"), }); }