Enable option for multiple alpha transformations

This commit is contained in:
Josh Holmer 2017-07-25 17:15:01 -04:00
parent 413f1aac7c
commit f41ef551d3
27 changed files with 714 additions and 40 deletions

View file

@ -1,6 +1,12 @@
### Version 0.17.0 (unreleased) ### Version 0.17.0 (unreleased)
- [SEMVER_MAJOR] Bump minimum required rustc version to 1.19.0 - [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 ### Version 0.16.3
- Fix command-line help text ([#70](https://github.com/shssoichiro/oxipng/issues/70)) - Fix command-line help text ([#70](https://github.com/shssoichiro/oxipng/issues/70))

View file

@ -4,6 +4,7 @@ extern crate test;
extern crate oxipng; extern crate oxipng;
use oxipng::png; use oxipng::png;
use oxipng::colors::AlphaOptim;
use test::Bencher; use test::Bencher;
use std::path::PathBuf; use std::path::PathBuf;
@ -254,3 +255,81 @@ fn reductions_palette_full_reduction(b: &mut Bencher) {
safe_png.reduce_palette(); 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);
});
}

View file

@ -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",
}
)
}
}

View file

@ -90,6 +90,8 @@ pub struct Options {
/// 8-15 are valid values /// 8-15 are valid values
/// Default: `15` /// Default: `15`
pub window: u8, pub window: u8,
/// Alpha filtering strategies to use
pub alphas: HashSet<colors::AlphaOptim>,
/// Whether to attempt bit depth reduction /// Whether to attempt bit depth reduction
/// Default: `true` /// Default: `true`
pub bit_depth_reduction: bool, pub bit_depth_reduction: bool,
@ -241,6 +243,9 @@ impl Default for Options {
for i in 0..4 { for i in 0..4 {
strategies.insert(i); 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 // Default to 1 thread on single-core, otherwise use threads = 1.5x CPU cores
let num_cpus = num_cpus::get(); let num_cpus = num_cpus::get();
@ -265,6 +270,7 @@ impl Default for Options {
memory: memory, memory: memory,
strategies: strategies, strategies: strategies,
window: 15, window: 15,
alphas,
bit_depth_reduction: true, bit_depth_reduction: true,
color_type_reduction: true, color_type_reduction: true,
palette_reduction: true, palette_reduction: true,
@ -429,7 +435,7 @@ fn optimize_png(
original_data: &[u8], original_data: &[u8],
opts: &Options, opts: &Options,
) -> Result<Vec<u8>, PngError> { ) -> Result<Vec<u8>, PngError> {
type TrialWithData = (u8, u8, u8, u8, Vec<u8>); type TrialWithData = (TrialOptions, Vec<u8>);
let original_png = png.clone(); let original_png = png.clone();
@ -495,7 +501,7 @@ fn optimize_png(
} else { } else {
filter.len() filter.len()
}; };
let mut results: Vec<(u8, u8, u8, u8)> = Vec::with_capacity(combinations); let mut results: Vec<TrialOptions> = Vec::with_capacity(combinations);
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
eprintln!("Trying: {} combinations", combinations); eprintln!("Trying: {} combinations", combinations);
} }
@ -505,24 +511,34 @@ fn optimize_png(
for zc in compression { for zc in compression {
for zm in memory { for zm in memory {
for zs in &strategies { for zs in &strategies {
results.push((*f, *zc, *zm, *zs)); results.push(TrialOptions {
filter: *f,
compression: *zc,
memory: *zm,
strategy: *zs,
});
} }
} }
} }
} else { } else {
// Zopfli compression has no additional options // 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<u8>)> = Vec::with_capacity(filter.len()); let filters: HashMap<u8, Vec<u8>> = filter
filter
.par_iter() .par_iter()
.with_max_len(1) .with_max_len(1)
.map(|f| (*f, png.filter_image(*f))) .map(|f| {
.collect_into(&mut filters_tmp); let png = png.clone();
(*f, png.filter_image(*f))
let filters: HashMap<u8, Vec<u8>> = filters_tmp.into_iter().collect(); })
.collect();
let original_len = original_png.idat_data.len(); let original_len = original_png.idat_data.len();
let added_interlacing = opts.interlace == Some(1) && original_png.ihdr_data.interlaced == 0; let added_interlacing = opts.interlace == Some(1) && original_png.ihdr_data.interlaced == 0;
@ -532,9 +548,15 @@ fn optimize_png(
.into_par_iter() .into_par_iter()
.with_max_len(1) .with_max_len(1)
.filter_map(|trial| { .filter_map(|trial| {
let filtered = &filters[&trial.0]; let filtered = &filters[&trial.filter];
let new_idat = if opts.deflate == Deflaters::Zlib { 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 { } else {
deflate::zopfli_deflate(filtered) deflate::zopfli_deflate(filtered)
}.unwrap(); }.unwrap();
@ -542,32 +564,33 @@ fn optimize_png(
if opts.verbosity == Some(1) { if opts.verbosity == Some(1) {
eprintln!( eprintln!(
" zc = {} zm = {} zs = {} f = {} {} bytes", " zc = {} zm = {} zs = {} f = {} {} bytes",
trial.1, trial.compression,
trial.2, trial.memory,
trial.3, trial.strategy,
trial.0, trial.filter,
new_idat.len() new_idat.len()
); );
} }
if new_idat.len() < original_len || added_interlacing || opts.force { 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 { } else {
None 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 { if let Some(better) = best {
png.idat_data = better.4; png.idat_data = better.1;
if opts.verbosity.is_some() { if opts.verbosity.is_some() {
let opts = better.0;
eprintln!("Found better combination:"); eprintln!("Found better combination:");
eprintln!( eprintln!(
" zc = {} zm = {} zs = {} f = {} {} bytes", " zc = {} zm = {} zs = {} f = {} {} bytes",
better.1, opts.compression,
better.2, opts.memory,
better.3, opts.strategy,
better.0, opts.filter,
png.idat_data.len() png.idat_data.len()
); );
} }
@ -673,8 +696,6 @@ fn perform_reductions(png: &mut png::PngData, opts: &Options) -> bool {
report_reduction(png); report_reduction(png);
} }
png.reduce_alpha_channel();
if let Some(interlacing) = opts.interlace { if let Some(interlacing) = opts.interlace {
if png.change_interlacing(interlacing) { if png.change_interlacing(interlacing) {
png.ihdr_data.interlaced = 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 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 { fn is_fully_optimized(original_size: usize, optimized_size: usize, opts: &Options) -> bool {
original_size <= optimized_size && !opts.force && opts.interlace.is_none() 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,
}

View file

@ -12,6 +12,7 @@ extern crate clap;
extern crate regex; extern crate regex;
use clap::{App, Arg, ArgMatches}; use clap::{App, Arg, ArgMatches};
use oxipng::colors::AlphaOptim;
use oxipng::deflate::Deflaters; use oxipng::deflate::Deflaters;
use oxipng::headers::Headers; use oxipng::headers::Headers;
use oxipng::Options; use oxipng::Options;
@ -156,6 +157,10 @@ fn main() {
.possible_value("8k") .possible_value("8k")
.possible_value("16k") .possible_value("16k")
.possible_value("32k")) .possible_value("32k"))
.arg(Arg::with_name("alpha")
.help("Perform additional alpha optimizations")
.short("a")
.long("alpha"))
.arg(Arg::with_name("no-bit-reduction") .arg(Arg::with_name("no-bit-reduction")
.help("No bit depth reduction") .help("No bit depth reduction")
.long("nb")) .long("nb"))
@ -336,6 +341,14 @@ fn parse_opts_into_struct(matches: &ArgMatches) -> Result<Options, String> {
opts.stdout = true; 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") { if matches.is_present("backup") {
opts.backup = true; opts.backup = true;
} }

View file

@ -1,6 +1,6 @@
use bit_vec::BitVec; use bit_vec::BitVec;
use byteorder::{BigEndian, WriteBytesExt}; use byteorder::{BigEndian, WriteBytesExt};
use colors::{BitDepth, ColorType}; use colors::{BitDepth, ColorType, AlphaOptim};
use crc::crc32; use crc::crc32;
use deflate; use deflate;
use error::PngError; use error::PngError;
@ -15,6 +15,12 @@ use std::io::Read;
use std::iter::Iterator; use std::iter::Iterator;
use std::path::Path; 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)] #[derive(Debug, Clone)]
/// An iterator over the scan lines of a PNG image /// An iterator over the scan lines of a PNG image
pub struct ScanLines<'a> { pub struct ScanLines<'a> {
@ -737,7 +743,36 @@ impl PngData {
changed changed
} }
pub fn reduce_alpha_channel(&mut self) -> bool { pub fn try_alpha_reduction(&mut self, alphas: &HashSet<AlphaOptim>) {
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 { let (bpc, bpp) = match self.ihdr_data.color_type {
ColorType::RGBA => { ColorType::RGBA => {
match self.ihdr_data.bit_depth { match self.ihdr_data.bit_depth {
@ -757,20 +792,121 @@ impl PngData {
return false; return false;
} }
}; };
let mut reduced = Vec::with_capacity(self.raw_data.len()); let mut reduced = Vec::with_capacity(self.raw_data.len());
for line in self.scan_lines() {
reduced.push(line.filter); match optim {
for pixel in line.data.chunks(bpp) { AlphaOptim::NoOp => {
if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 { return false;
for _ in 0..bpp { }
reduced.push(0); 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::<Vec<ScanLine>>();
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; self.raw_data = reduced;
true true
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View file

@ -1,6 +1,6 @@
extern crate oxipng; extern crate oxipng;
use oxipng::colors::{BitDepth, ColorType}; use oxipng::colors::{BitDepth, ColorType, AlphaOptim};
use oxipng::png; use oxipng::png;
use std::collections::HashSet; use std::collections::HashSet;
use std::error::Error; use std::error::Error;
@ -1096,7 +1096,6 @@ fn rgba_16_reduce_alpha_black() {
); );
} }
#[test] #[test]
fn rgba_8_reduce_alpha_black() { fn rgba_8_reduce_alpha_black() {
let input = PathBuf::from("tests/files/rgba_8_reduce_alpha_black.png"); let input = PathBuf::from("tests/files/rgba_8_reduce_alpha_black.png");
@ -1131,7 +1130,6 @@ fn grayscale_alpha_16_reduce_alpha_black() {
); );
} }
#[test] #[test]
fn grayscale_alpha_8_reduce_alpha_black() { fn grayscale_alpha_8_reduce_alpha_black() {
let input = PathBuf::from("tests/files/grayscale_alpha_8_reduce_alpha_black.png"); 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, 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,
);
}