extern crate byteorder; extern crate crc; extern crate libc; extern crate libz_sys; use std::collections::HashSet; use std::fs; use std::fs::File; use std::io; use std::io::BufWriter; use std::io::Write; use std::path::Path; use std::path::PathBuf; pub mod deflate { pub mod deflate; pub mod stream; } pub mod png; pub struct Options { pub backup: bool, pub out_file: PathBuf, pub out_dir: Option, pub stdout: bool, pub fix_errors: bool, pub pretend: bool, pub recursive: bool, pub clobber: bool, pub create: bool, pub preserve_attrs: bool, pub verbosity: Option, pub filter: HashSet, pub interlace: Option, pub compression: HashSet, pub memory: HashSet, pub strategies: HashSet, pub window: u8, pub bit_depth_reduction: bool, 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> { // Decode PNG from file println!("Processing: {}", filepath.to_str().unwrap()); let in_file = Path::new(filepath); let mut png = match png::PngData::new(&in_file) { Ok(x) => x, Err(x) => return Err(x), }; // Print png info let idat_original_size = png.idat_data.len(); let file_original_size = filepath.metadata().unwrap().len() as usize; if opts.verbosity.is_some() { println!(" {}x{} pixels, PNG format", png.ihdr_data.width, png.ihdr_data.height); if let Some(palette) = png.palette.clone() { println!(" {} bits/pixel, {} colors in palette", png.ihdr_data.bit_depth, palette.len() / 3); } else { println!(" {}x{} bits/pixel, {:?}", png.bits_per_pixel_raw(), png.ihdr_data.bit_depth, png.ihdr_data.color_type); } println!(" IDAT size = {} bytes", idat_original_size); println!(" File size = {} bytes", file_original_size); } // TODO: Bit depth/palette reduction // // TODO: Apply interlacing changes // // TODO: Force reencoding if interlacing was changed if opts.idat_recoding { // Go through selected permutations and determine the best let mut best: Option<(u8, u8, u8, u8)> = None; 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 { let filtered = png.filter_image(*f); for zc in &opts.compression { for zm in &opts.memory { for zs in &opts.strategies { let new_idat = match deflate::deflate::deflate(&filtered, *zc, *zm, *zs, opts.window) { Ok(x) => x, Err(x) => return Err(x), }; if new_idat.len() < png.idat_data.len() { best = Some((*f, *zc, *zm, *zs)); png.idat_data = new_idat.clone(); } if opts.verbosity == Some(1) { println!(" zc = {} zm = {} zs = {} f = {} {} bytes", *zc, *zm, *zs, *f, new_idat.len()); } } } } } if let Some(better) = best { println!("Found better combination:"); println!(" zc = {} zm = {} zs = {} f = {} {} bytes", better.1, better.2, better.3, better.0, png.idat_data.len()); } } // TODO: Perform stripping let output_data = png.output(); if file_original_size <= output_data.len() { println!("File already optimized"); return Ok(()); } if opts.pretend { println!("Running in pretend mode, no output"); } else { if opts.backup { match fs::copy(in_file, in_file.with_extension(format!("bak.{}", in_file.extension() .unwrap() .to_str() .unwrap()))) { Ok(x) => x, Err(_) => { return Err(format!("Unable to write to backup file at {}", opts.out_file.display())) } }; } if opts.stdout { let mut buffer = BufWriter::new(io::stdout()); match buffer.write_all(&output_data) { Ok(_) => (), Err(_) => return Err(format!("Unable to write to stdout")), } } else { let out_file = match File::create(&opts.out_file) { Ok(x) => x, Err(_) => { return Err(format!("Unable to write to file {}", opts.out_file.display())) } }; let mut buffer = BufWriter::new(out_file); match buffer.write_all(&output_data) { Ok(_) => println!("Output: {}", opts.out_file.display()), Err(_) => { return Err(format!("Unable to write to file {}", opts.out_file.display())) } } } } 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); Ok(()) }