Enable option for multiple alpha transformations
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
82
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<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,
|
||||
}
|
||||
|
|
|
|||
13
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<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;
|
||||
}
|
||||
|
|
|
|||
158
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<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
|
||||
}
|
||||
|
|
|
|||
BIN
tests/files/grayscale_alpha_16_reduce_alpha_down.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
tests/files/grayscale_alpha_16_reduce_alpha_left.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
tests/files/grayscale_alpha_16_reduce_alpha_right.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
tests/files/grayscale_alpha_16_reduce_alpha_up.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
tests/files/grayscale_alpha_16_reduce_alpha_white.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
tests/files/grayscale_alpha_8_reduce_alpha_down.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
tests/files/grayscale_alpha_8_reduce_alpha_left.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
tests/files/grayscale_alpha_8_reduce_alpha_right.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
tests/files/grayscale_alpha_8_reduce_alpha_up.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
tests/files/grayscale_alpha_8_reduce_alpha_white.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
tests/files/rgba_16_reduce_alpha_down.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
tests/files/rgba_16_reduce_alpha_left.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
tests/files/rgba_16_reduce_alpha_right.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
tests/files/rgba_16_reduce_alpha_up.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
tests/files/rgba_16_reduce_alpha_white.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
tests/files/rgba_8_reduce_alpha_down.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
tests/files/rgba_8_reduce_alpha_left.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
tests/files/rgba_8_reduce_alpha_right.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
tests/files/rgba_8_reduce_alpha_up.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
tests/files/rgba_8_reduce_alpha_white.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||