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)
- [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))

View file

@ -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);
});
}

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
/// Default: `15`
pub window: u8,
/// Alpha filtering strategies to use
pub alphas: HashSet<colors::AlphaOptim>,
/// 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<Vec<u8>, PngError> {
type TrialWithData = (u8, u8, u8, u8, Vec<u8>);
type TrialWithData = (TrialOptions, Vec<u8>);
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<TrialOptions> = 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<u8>)> = Vec::with_capacity(filter.len());
filter
let filters: HashMap<u8, Vec<u8>> = filter
.par_iter()
.with_max_len(1)
.map(|f| (*f, png.filter_image(*f)))
.collect_into(&mut filters_tmp);
let filters: HashMap<u8, Vec<u8>> = 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,
}

View file

@ -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<Options, String> {
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;
}

View file

@ -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<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 {
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::<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;
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;
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,
);
}