diff --git a/CHANGELOG.md b/CHANGELOG.md index b7771c10..0c8e73cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ ### Version 0.17.0 (unreleased) - [SEMVER_MAJOR] Bump minimum required rustc version to 1.19.0 - - Change all transparent pixels to `rgba(0, 0, 0, 0)` to improve compression + - [SEMVER_MINOR] Oxipng will now, by default, attempt to change all transparent pixels to `rgba(0, 0, 0, 0)` to improve compression. + It does fast trials with filters 0 and 5 to see if this is an improvement over + the existing alpha channel. + - [SEMVER_MINOR] Add a `-a` option to the command line (`alphas` in the struct) which enables 6 different + trials for optimizing the alpha channel, using the previously mentioned fast heuristic. + This option will make optimization of images with transparency somewhat slower, + but may improve compression. ### Version 0.16.3 - Fix command-line help text ([#70](https://github.com/shssoichiro/oxipng/issues/70)) diff --git a/benches/reductions.rs b/benches/reductions.rs index 75cba1cc..602b963d 100644 --- a/benches/reductions.rs +++ b/benches/reductions.rs @@ -4,6 +4,7 @@ extern crate test; extern crate oxipng; use oxipng::png; +use oxipng::colors::AlphaOptim; use test::Bencher; use std::path::PathBuf; @@ -254,3 +255,81 @@ fn reductions_palette_full_reduction(b: &mut Bencher) { safe_png.reduce_palette(); }); } + +#[bench] +fn reductions_alpha_black(b: &mut Bencher) { + let input = test::black_box(PathBuf::from( + "tests/files/rgba_8_reduce_alpha_black.png", + )); + let png = png::PngData::new(&input, false).unwrap(); + + b.iter(|| { + let mut safe_png = png.clone(); + safe_png.reduce_alpha_channel(AlphaOptim::Black); + }); +} + +#[bench] +fn reductions_alpha_white(b: &mut Bencher) { + let input = test::black_box(PathBuf::from( + "tests/files/rgba_8_reduce_alpha_white.png", + )); + let png = png::PngData::new(&input, false).unwrap(); + + b.iter(|| { + let mut safe_png = png.clone(); + safe_png.reduce_alpha_channel(AlphaOptim::White); + }); +} + +#[bench] +fn reductions_alpha_left(b: &mut Bencher) { + let input = test::black_box(PathBuf::from( + "tests/files/rgba_8_reduce_alpha_left.png", + )); + let png = png::PngData::new(&input, false).unwrap(); + + b.iter(|| { + let mut safe_png = png.clone(); + safe_png.reduce_alpha_channel(AlphaOptim::Left); + }); +} + +#[bench] +fn reductions_alpha_right(b: &mut Bencher) { + let input = test::black_box(PathBuf::from( + "tests/files/rgba_8_reduce_alpha_right.png", + )); + let png = png::PngData::new(&input, false).unwrap(); + + b.iter(|| { + let mut safe_png = png.clone(); + safe_png.reduce_alpha_channel(AlphaOptim::Right); + }); +} + +#[bench] +fn reductions_alpha_up(b: &mut Bencher) { + let input = test::black_box(PathBuf::from( + "tests/files/rgba_8_reduce_alpha_up.png", + )); + let png = png::PngData::new(&input, false).unwrap(); + + b.iter(|| { + let mut safe_png = png.clone(); + safe_png.reduce_alpha_channel(AlphaOptim::Up); + }); +} + +#[bench] +fn reductions_alpha_down(b: &mut Bencher) { + let input = test::black_box(PathBuf::from( + "tests/files/rgba_8_reduce_alpha_down.png", + )); + let png = png::PngData::new(&input, false).unwrap(); + + b.iter(|| { + let mut safe_png = png.clone(); + safe_png.reduce_alpha_channel(AlphaOptim::Down); + }); +} diff --git a/src/colors.rs b/src/colors.rs index 3e9aaca4..dd6e7866 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -103,3 +103,33 @@ impl BitDepth { } } } + +#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)] +/// Potential optimization methods for alpha channel +pub enum AlphaOptim { + NoOp, + Black, + White, + Up, + Right, + Down, + Left, +} + +impl fmt::Display for AlphaOptim { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + match *self { + AlphaOptim::NoOp => "_", + AlphaOptim::Black => "B", + AlphaOptim::White => "W", + AlphaOptim::Up => "U", + AlphaOptim::Right => "R", + AlphaOptim::Down => "D", + AlphaOptim::Left => "L", + } + ) + } +} diff --git a/src/lib.rs b/src/lib.rs index b37a22b5..8d58a3ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,6 +90,8 @@ pub struct Options { /// 8-15 are valid values /// Default: `15` pub window: u8, + /// Alpha filtering strategies to use + pub alphas: HashSet, /// Whether to attempt bit depth reduction /// Default: `true` pub bit_depth_reduction: bool, @@ -241,6 +243,9 @@ impl Default for Options { for i in 0..4 { strategies.insert(i); } + let mut alphas = HashSet::new(); + alphas.insert(colors::AlphaOptim::NoOp); + alphas.insert(colors::AlphaOptim::Black); // Default to 1 thread on single-core, otherwise use threads = 1.5x CPU cores let num_cpus = num_cpus::get(); @@ -265,6 +270,7 @@ impl Default for Options { memory: memory, strategies: strategies, window: 15, + alphas, bit_depth_reduction: true, color_type_reduction: true, palette_reduction: true, @@ -429,7 +435,7 @@ fn optimize_png( original_data: &[u8], opts: &Options, ) -> Result, PngError> { - type TrialWithData = (u8, u8, u8, u8, Vec); + type TrialWithData = (TrialOptions, Vec); let original_png = png.clone(); @@ -495,7 +501,7 @@ fn optimize_png( } else { filter.len() }; - let mut results: Vec<(u8, u8, u8, u8)> = Vec::with_capacity(combinations); + let mut results: Vec = Vec::with_capacity(combinations); if opts.verbosity.is_some() { eprintln!("Trying: {} combinations", combinations); } @@ -505,24 +511,34 @@ fn optimize_png( for zc in compression { for zm in memory { for zs in &strategies { - results.push((*f, *zc, *zm, *zs)); + results.push(TrialOptions { + filter: *f, + compression: *zc, + memory: *zm, + strategy: *zs, + }); } } } } else { // Zopfli compression has no additional options - results.push((*f, 0, 0, 0)); + results.push(TrialOptions { + filter: *f, + compression: 0, + memory: 0, + strategy: 0, + }); } } - let mut filters_tmp: Vec<(u8, Vec)> = Vec::with_capacity(filter.len()); - filter + let filters: HashMap> = filter .par_iter() .with_max_len(1) - .map(|f| (*f, png.filter_image(*f))) - .collect_into(&mut filters_tmp); - - let filters: HashMap> = filters_tmp.into_iter().collect(); + .map(|f| { + let png = png.clone(); + (*f, png.filter_image(*f)) + }) + .collect(); let original_len = original_png.idat_data.len(); let added_interlacing = opts.interlace == Some(1) && original_png.ihdr_data.interlaced == 0; @@ -532,9 +548,15 @@ fn optimize_png( .into_par_iter() .with_max_len(1) .filter_map(|trial| { - let filtered = &filters[&trial.0]; + let filtered = &filters[&trial.filter]; let new_idat = if opts.deflate == Deflaters::Zlib { - deflate::deflate(filtered, trial.1, trial.2, trial.3, opts.window) + deflate::deflate( + filtered, + trial.compression, + trial.memory, + trial.strategy, + opts.window, + ) } else { deflate::zopfli_deflate(filtered) }.unwrap(); @@ -542,32 +564,33 @@ fn optimize_png( if opts.verbosity == Some(1) { eprintln!( " zc = {} zm = {} zs = {} f = {} {} bytes", - trial.1, - trial.2, - trial.3, - trial.0, + trial.compression, + trial.memory, + trial.strategy, + trial.filter, new_idat.len() ); } if new_idat.len() < original_len || added_interlacing || opts.force { - Some((trial.0, trial.1, trial.2, trial.3, new_idat)) + Some((trial, new_idat)) } else { None } }) - .reduce_with(|i, j| if i.4.len() <= j.4.len() { i } else { j }); + .reduce_with(|i, j| if i.1.len() <= j.1.len() { i } else { j }); if let Some(better) = best { - png.idat_data = better.4; + png.idat_data = better.1; if opts.verbosity.is_some() { + let opts = better.0; eprintln!("Found better combination:"); eprintln!( " zc = {} zm = {} zs = {} f = {} {} bytes", - better.1, - better.2, - better.3, - better.0, + opts.compression, + opts.memory, + opts.strategy, + opts.filter, png.idat_data.len() ); } @@ -673,8 +696,6 @@ fn perform_reductions(png: &mut png::PngData, opts: &Options) -> bool { report_reduction(png); } - png.reduce_alpha_channel(); - if let Some(interlacing) = opts.interlace { if png.change_interlacing(interlacing) { png.ihdr_data.interlaced = interlacing; @@ -682,6 +703,8 @@ fn perform_reductions(png: &mut png::PngData, opts: &Options) -> bool { } } + png.try_alpha_reduction(&opts.alphas); + reduction_occurred } @@ -744,3 +767,12 @@ fn perform_strip(png: &mut png::PngData, opts: &Options) { fn is_fully_optimized(original_size: usize, optimized_size: usize, opts: &Options) -> bool { original_size <= optimized_size && !opts.force && opts.interlace.is_none() } + +#[derive(Debug, PartialEq, Clone, Copy)] +/// Defines options to be used for a single compression trial +struct TrialOptions { + pub filter: u8, + pub compression: u8, + pub memory: u8, + pub strategy: u8, +} diff --git a/src/main.rs b/src/main.rs index f14557fc..ba19ac96 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ extern crate clap; extern crate regex; use clap::{App, Arg, ArgMatches}; +use oxipng::colors::AlphaOptim; use oxipng::deflate::Deflaters; use oxipng::headers::Headers; use oxipng::Options; @@ -156,6 +157,10 @@ fn main() { .possible_value("8k") .possible_value("16k") .possible_value("32k")) + .arg(Arg::with_name("alpha") + .help("Perform additional alpha optimizations") + .short("a") + .long("alpha")) .arg(Arg::with_name("no-bit-reduction") .help("No bit depth reduction") .long("nb")) @@ -336,6 +341,14 @@ fn parse_opts_into_struct(matches: &ArgMatches) -> Result { opts.stdout = true; } + if matches.is_present("alpha") { + opts.alphas.insert(AlphaOptim::White); + opts.alphas.insert(AlphaOptim::Up); + opts.alphas.insert(AlphaOptim::Down); + opts.alphas.insert(AlphaOptim::Left); + opts.alphas.insert(AlphaOptim::Right); + } + if matches.is_present("backup") { opts.backup = true; } diff --git a/src/png.rs b/src/png.rs index 9c625035..884dc4a3 100644 --- a/src/png.rs +++ b/src/png.rs @@ -1,6 +1,6 @@ use bit_vec::BitVec; use byteorder::{BigEndian, WriteBytesExt}; -use colors::{BitDepth, ColorType}; +use colors::{BitDepth, ColorType, AlphaOptim}; use crc::crc32; use deflate; use error::PngError; @@ -15,6 +15,12 @@ use std::io::Read; use std::iter::Iterator; use std::path::Path; +const STD_COMPRESSION: u8 = 8; +const STD_MEMORY: u8 = 9; +const STD_STRATEGY: u8 = 2; // Huffman only +const STD_WINDOW: u8 = 15; +const STD_FILTERS: [u8; 2] = [0, 5]; + #[derive(Debug, Clone)] /// An iterator over the scan lines of a PNG image pub struct ScanLines<'a> { @@ -737,7 +743,36 @@ impl PngData { changed } - pub fn reduce_alpha_channel(&mut self) -> bool { + pub fn try_alpha_reduction(&mut self, alphas: &HashSet) { + assert!(!alphas.is_empty()); + let best = alphas + .iter() + .map(|alpha| { + let mut image = self.clone(); + image.reduce_alpha_channel(*alpha); + let size = STD_FILTERS + .iter() + .map(|f| { + deflate::deflate( + &image.filter_image(*f), + STD_COMPRESSION, + STD_MEMORY, + STD_STRATEGY, + STD_WINDOW, + ).unwrap() + .len() + }) + .min() + .unwrap(); + (size, image) + }) + .min_by_key(|&(size, _)| size) + .unwrap(); + + self.raw_data = best.1.raw_data; + } + + pub fn reduce_alpha_channel(&mut self, optim: AlphaOptim) -> bool { let (bpc, bpp) = match self.ihdr_data.color_type { ColorType::RGBA => { match self.ihdr_data.bit_depth { @@ -757,20 +792,121 @@ impl PngData { return false; } }; - let mut reduced = Vec::with_capacity(self.raw_data.len()); - for line in self.scan_lines() { - reduced.push(line.filter); - for pixel in line.data.chunks(bpp) { - if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 { - for _ in 0..bpp { - reduced.push(0); + + match optim { + AlphaOptim::NoOp => { + return false; + } + AlphaOptim::Black => { + for line in self.scan_lines() { + reduced.push(line.filter); + for pixel in line.data.chunks(bpp) { + if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 { + for _ in 0..bpp { + reduced.push(0); + } + } else { + reduced.extend_from_slice(pixel); + } + } + } + } + AlphaOptim::White => { + for line in self.scan_lines() { + reduced.push(line.filter); + for pixel in line.data.chunks(bpp) { + if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 { + for _ in 0..(bpp - bpc) { + reduced.push(255); + } + for _ in 0..bpc { + reduced.push(0); + } + } else { + reduced.extend_from_slice(pixel); + } + } + } + } + AlphaOptim::Up => { + let mut lines = Vec::new(); + let scan_lines = self.scan_lines().collect::>(); + let mut last_line = vec![0; scan_lines[0].data.len()]; + let mut current_line = Vec::with_capacity(last_line.len()); + for line in scan_lines.into_iter().rev() { + current_line.push(line.filter); + for (pixel, last_pixel) in line.data.chunks(bpp).zip(last_line.chunks(bpp)) { + if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 { + current_line.extend_from_slice(&last_pixel[0..(bpp - bpc)]); + for _ in 0..bpc { + current_line.push(0); + } + } else { + current_line.extend_from_slice(pixel); + } + } + last_line = current_line.clone(); + lines.push(current_line.clone()); + current_line.clear(); + } + reduced.extend(lines.into_iter().rev().flatten()); + } + AlphaOptim::Down => { + let mut last_line = vec![0; self.scan_lines().next().unwrap().data.len()]; + for line in self.scan_lines() { + reduced.push(line.filter); + for (pixel, last_pixel) in line.data.chunks(bpp).zip(last_line.chunks(bpp)) { + if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 { + reduced.extend_from_slice(&last_pixel[0..(bpp - bpc)]); + for _ in 0..bpc { + reduced.push(0); + } + } else { + reduced.extend_from_slice(pixel); + } + } + last_line = reduced.clone(); + } + } + AlphaOptim::Left => { + for line in self.scan_lines() { + let mut line_bytes = Vec::with_capacity(line.data.len()); + let mut last_pixel = vec![0; bpp]; + for pixel in line.data.chunks(bpp).rev() { + if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 { + line_bytes.extend_from_slice(&last_pixel[0..(bpp - bpc)]); + for _ in 0..bpc { + line_bytes.push(0); + } + } else { + line_bytes.extend_from_slice(pixel); + } + last_pixel = pixel.to_owned(); + } + reduced.push(line.filter); + reduced.extend(line_bytes.chunks(bpp).rev().flatten()); + } + } + AlphaOptim::Right => { + for line in self.scan_lines() { + reduced.push(line.filter); + let mut last_pixel = vec![0; bpp]; + for pixel in line.data.chunks(bpp) { + if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 { + reduced.extend_from_slice(&last_pixel[0..(bpp - bpc)]); + for _ in 0..bpc { + reduced.push(0); + } + } else { + reduced.extend_from_slice(pixel); + } + last_pixel = pixel.to_owned(); } - } else { - reduced.extend_from_slice(pixel); } } } + self.raw_data = reduced; true } diff --git a/tests/files/grayscale_alpha_16_reduce_alpha_down.png b/tests/files/grayscale_alpha_16_reduce_alpha_down.png new file mode 100644 index 00000000..78b72778 Binary files /dev/null and b/tests/files/grayscale_alpha_16_reduce_alpha_down.png differ diff --git a/tests/files/grayscale_alpha_16_reduce_alpha_left.png b/tests/files/grayscale_alpha_16_reduce_alpha_left.png new file mode 100644 index 00000000..78b72778 Binary files /dev/null and b/tests/files/grayscale_alpha_16_reduce_alpha_left.png differ diff --git a/tests/files/grayscale_alpha_16_reduce_alpha_right.png b/tests/files/grayscale_alpha_16_reduce_alpha_right.png new file mode 100644 index 00000000..78b72778 Binary files /dev/null and b/tests/files/grayscale_alpha_16_reduce_alpha_right.png differ diff --git a/tests/files/grayscale_alpha_16_reduce_alpha_up.png b/tests/files/grayscale_alpha_16_reduce_alpha_up.png new file mode 100644 index 00000000..78b72778 Binary files /dev/null and b/tests/files/grayscale_alpha_16_reduce_alpha_up.png differ diff --git a/tests/files/grayscale_alpha_16_reduce_alpha_white.png b/tests/files/grayscale_alpha_16_reduce_alpha_white.png new file mode 100644 index 00000000..78b72778 Binary files /dev/null and b/tests/files/grayscale_alpha_16_reduce_alpha_white.png differ diff --git a/tests/files/grayscale_alpha_8_reduce_alpha_down.png b/tests/files/grayscale_alpha_8_reduce_alpha_down.png new file mode 100644 index 00000000..c2e1d1ce Binary files /dev/null and b/tests/files/grayscale_alpha_8_reduce_alpha_down.png differ diff --git a/tests/files/grayscale_alpha_8_reduce_alpha_left.png b/tests/files/grayscale_alpha_8_reduce_alpha_left.png new file mode 100644 index 00000000..c2e1d1ce Binary files /dev/null and b/tests/files/grayscale_alpha_8_reduce_alpha_left.png differ diff --git a/tests/files/grayscale_alpha_8_reduce_alpha_right.png b/tests/files/grayscale_alpha_8_reduce_alpha_right.png new file mode 100644 index 00000000..c2e1d1ce Binary files /dev/null and b/tests/files/grayscale_alpha_8_reduce_alpha_right.png differ diff --git a/tests/files/grayscale_alpha_8_reduce_alpha_up.png b/tests/files/grayscale_alpha_8_reduce_alpha_up.png new file mode 100644 index 00000000..c2e1d1ce Binary files /dev/null and b/tests/files/grayscale_alpha_8_reduce_alpha_up.png differ diff --git a/tests/files/grayscale_alpha_8_reduce_alpha_white.png b/tests/files/grayscale_alpha_8_reduce_alpha_white.png new file mode 100644 index 00000000..c2e1d1ce Binary files /dev/null and b/tests/files/grayscale_alpha_8_reduce_alpha_white.png differ diff --git a/tests/files/rgba_16_reduce_alpha_down.png b/tests/files/rgba_16_reduce_alpha_down.png new file mode 100644 index 00000000..4792bf00 Binary files /dev/null and b/tests/files/rgba_16_reduce_alpha_down.png differ diff --git a/tests/files/rgba_16_reduce_alpha_left.png b/tests/files/rgba_16_reduce_alpha_left.png new file mode 100644 index 00000000..4792bf00 Binary files /dev/null and b/tests/files/rgba_16_reduce_alpha_left.png differ diff --git a/tests/files/rgba_16_reduce_alpha_right.png b/tests/files/rgba_16_reduce_alpha_right.png new file mode 100644 index 00000000..4792bf00 Binary files /dev/null and b/tests/files/rgba_16_reduce_alpha_right.png differ diff --git a/tests/files/rgba_16_reduce_alpha_up.png b/tests/files/rgba_16_reduce_alpha_up.png new file mode 100644 index 00000000..4792bf00 Binary files /dev/null and b/tests/files/rgba_16_reduce_alpha_up.png differ diff --git a/tests/files/rgba_16_reduce_alpha_white.png b/tests/files/rgba_16_reduce_alpha_white.png new file mode 100644 index 00000000..4792bf00 Binary files /dev/null and b/tests/files/rgba_16_reduce_alpha_white.png differ diff --git a/tests/files/rgba_8_reduce_alpha_down.png b/tests/files/rgba_8_reduce_alpha_down.png new file mode 100644 index 00000000..22a5a059 Binary files /dev/null and b/tests/files/rgba_8_reduce_alpha_down.png differ diff --git a/tests/files/rgba_8_reduce_alpha_left.png b/tests/files/rgba_8_reduce_alpha_left.png new file mode 100644 index 00000000..22a5a059 Binary files /dev/null and b/tests/files/rgba_8_reduce_alpha_left.png differ diff --git a/tests/files/rgba_8_reduce_alpha_right.png b/tests/files/rgba_8_reduce_alpha_right.png new file mode 100644 index 00000000..22a5a059 Binary files /dev/null and b/tests/files/rgba_8_reduce_alpha_right.png differ diff --git a/tests/files/rgba_8_reduce_alpha_up.png b/tests/files/rgba_8_reduce_alpha_up.png new file mode 100644 index 00000000..22a5a059 Binary files /dev/null and b/tests/files/rgba_8_reduce_alpha_up.png differ diff --git a/tests/files/rgba_8_reduce_alpha_white.png b/tests/files/rgba_8_reduce_alpha_white.png new file mode 100644 index 00000000..22a5a059 Binary files /dev/null and b/tests/files/rgba_8_reduce_alpha_white.png differ diff --git a/tests/reduction.rs b/tests/reduction.rs index 33778f35..1cab344b 100644 --- a/tests/reduction.rs +++ b/tests/reduction.rs @@ -1,6 +1,6 @@ extern crate oxipng; -use oxipng::colors::{BitDepth, ColorType}; +use oxipng::colors::{BitDepth, ColorType, AlphaOptim}; use oxipng::png; use std::collections::HashSet; use std::error::Error; @@ -1096,7 +1096,6 @@ fn rgba_16_reduce_alpha_black() { ); } - #[test] fn rgba_8_reduce_alpha_black() { let input = PathBuf::from("tests/files/rgba_8_reduce_alpha_black.png"); @@ -1131,7 +1130,6 @@ fn grayscale_alpha_16_reduce_alpha_black() { ); } - #[test] fn grayscale_alpha_8_reduce_alpha_black() { let input = PathBuf::from("tests/files/grayscale_alpha_8_reduce_alpha_black.png"); @@ -1148,3 +1146,383 @@ fn grayscale_alpha_8_reduce_alpha_black() { BitDepth::Eight, ); } + +#[test] +fn rgba_16_reduce_alpha_white() { + let input = PathBuf::from("tests/files/rgba_16_reduce_alpha_white.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::White); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::RGBA, + BitDepth::Sixteen, + ColorType::RGBA, + BitDepth::Eight, + ); +} + +#[test] +fn rgba_8_reduce_alpha_white() { + let input = PathBuf::from("tests/files/rgba_8_reduce_alpha_white.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::White); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::RGBA, + BitDepth::Eight, + ColorType::RGBA, + BitDepth::Eight, + ); +} + +#[test] +fn grayscale_alpha_16_reduce_alpha_white() { + let input = PathBuf::from("tests/files/grayscale_alpha_16_reduce_alpha_white.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::White); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::GrayscaleAlpha, + BitDepth::Sixteen, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ); +} + +#[test] +fn grayscale_alpha_8_reduce_alpha_white() { + let input = PathBuf::from("tests/files/grayscale_alpha_8_reduce_alpha_white.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::White); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ); +} + +#[test] +fn rgba_16_reduce_alpha_down() { + let input = PathBuf::from("tests/files/rgba_16_reduce_alpha_down.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Down); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::RGBA, + BitDepth::Sixteen, + ColorType::RGBA, + BitDepth::Eight, + ); +} + +#[test] +fn rgba_8_reduce_alpha_down() { + let input = PathBuf::from("tests/files/rgba_8_reduce_alpha_down.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Down); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::RGBA, + BitDepth::Eight, + ColorType::RGBA, + BitDepth::Eight, + ); +} + +#[test] +fn grayscale_alpha_16_reduce_alpha_down() { + let input = PathBuf::from("tests/files/grayscale_alpha_16_reduce_alpha_down.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Down); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::GrayscaleAlpha, + BitDepth::Sixteen, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ); +} + +#[test] +fn grayscale_alpha_8_reduce_alpha_down() { + let input = PathBuf::from("tests/files/grayscale_alpha_8_reduce_alpha_down.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Down); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ); +} + +#[test] +fn rgba_16_reduce_alpha_up() { + let input = PathBuf::from("tests/files/rgba_16_reduce_alpha_up.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Up); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::RGBA, + BitDepth::Sixteen, + ColorType::RGBA, + BitDepth::Eight, + ); +} + +#[test] +fn rgba_8_reduce_alpha_up() { + let input = PathBuf::from("tests/files/rgba_8_reduce_alpha_up.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Up); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::RGBA, + BitDepth::Eight, + ColorType::RGBA, + BitDepth::Eight, + ); +} + +#[test] +fn grayscale_alpha_16_reduce_alpha_up() { + let input = PathBuf::from("tests/files/grayscale_alpha_16_reduce_alpha_up.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Up); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::GrayscaleAlpha, + BitDepth::Sixteen, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ); +} + +#[test] +fn grayscale_alpha_8_reduce_alpha_up() { + let input = PathBuf::from("tests/files/grayscale_alpha_8_reduce_alpha_up.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Up); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ); +} + +#[test] +fn rgba_16_reduce_alpha_left() { + let input = PathBuf::from("tests/files/rgba_16_reduce_alpha_left.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Left); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::RGBA, + BitDepth::Sixteen, + ColorType::RGBA, + BitDepth::Eight, + ); +} + +#[test] +fn rgba_8_reduce_alpha_left() { + let input = PathBuf::from("tests/files/rgba_8_reduce_alpha_left.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Left); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::RGBA, + BitDepth::Eight, + ColorType::RGBA, + BitDepth::Eight, + ); +} + +#[test] +fn grayscale_alpha_16_reduce_alpha_left() { + let input = PathBuf::from("tests/files/grayscale_alpha_16_reduce_alpha_left.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Left); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::GrayscaleAlpha, + BitDepth::Sixteen, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ); +} + +#[test] +fn grayscale_alpha_8_reduce_alpha_left() { + let input = PathBuf::from("tests/files/grayscale_alpha_8_reduce_alpha_left.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Left); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ); +} + +#[test] +fn rgba_16_reduce_alpha_right() { + let input = PathBuf::from("tests/files/rgba_16_reduce_alpha_right.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Right); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::RGBA, + BitDepth::Sixteen, + ColorType::RGBA, + BitDepth::Eight, + ); +} + +#[test] +fn rgba_8_reduce_alpha_right() { + let input = PathBuf::from("tests/files/rgba_8_reduce_alpha_right.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Right); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::RGBA, + BitDepth::Eight, + ColorType::RGBA, + BitDepth::Eight, + ); +} + +#[test] +fn grayscale_alpha_16_reduce_alpha_right() { + let input = PathBuf::from("tests/files/grayscale_alpha_16_reduce_alpha_right.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Right); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::GrayscaleAlpha, + BitDepth::Sixteen, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ); +} + +#[test] +fn grayscale_alpha_8_reduce_alpha_right() { + let input = PathBuf::from("tests/files/grayscale_alpha_8_reduce_alpha_right.png"); + let mut opts = get_opts(&input); + opts.alphas = HashSet::with_capacity(1); + opts.alphas.insert(AlphaOptim::Right); + let output = opts.out_file.clone(); + + test_it_converts( + &input, + &output, + &opts, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + ); +}