Alpha heuristic improvements (#144)
* Compress alpha with strategy sensitive to repetitions * Update reductions flag when alpha changed * Avoid needlessly cloning out-of-date idat_data and temp raw_data
This commit is contained in:
parent
96eda85806
commit
4765eaa7f3
2 changed files with 40 additions and 37 deletions
|
|
@ -768,7 +768,9 @@ fn perform_reductions(png: &mut PngData, opts: &Options, deadline: &Deadline) ->
|
||||||
return reduction_occurred;
|
return reduction_occurred;
|
||||||
}
|
}
|
||||||
|
|
||||||
png.try_alpha_reduction(&opts.alphas);
|
if png.try_alpha_reduction(&opts.alphas) {
|
||||||
|
reduction_occurred = true;
|
||||||
|
}
|
||||||
|
|
||||||
reduction_occurred
|
reduction_occurred
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ use std::iter::Iterator;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
const STD_COMPRESSION: u8 = 8;
|
const STD_COMPRESSION: u8 = 8;
|
||||||
const STD_STRATEGY: u8 = 2; // Huffman only
|
/// Must use normal compression, as faster ones (Huffman/RLE-only) are not representative
|
||||||
|
const STD_STRATEGY: u8 = 0;
|
||||||
const STD_WINDOW: u8 = 15;
|
const STD_WINDOW: u8 = 15;
|
||||||
const STD_FILTERS: [u8; 2] = [0, 5];
|
const STD_FILTERS: [u8; 2] = [0, 5];
|
||||||
|
|
||||||
|
|
@ -586,7 +587,7 @@ impl PngData {
|
||||||
changed
|
changed
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_alpha_reduction(&mut self, alphas: &HashSet<AlphaOptim>) {
|
pub fn try_alpha_reduction(&mut self, alphas: &HashSet<AlphaOptim>) -> bool {
|
||||||
assert!(!alphas.is_empty());
|
assert!(!alphas.is_empty());
|
||||||
let alphas = alphas.iter().collect::<Vec<_>>();
|
let alphas = alphas.iter().collect::<Vec<_>>();
|
||||||
let best_size = AtomicMin::new(None);
|
let best_size = AtomicMin::new(None);
|
||||||
|
|
@ -596,8 +597,10 @@ impl PngData {
|
||||||
let alphas_iter = alphas.iter();
|
let alphas_iter = alphas.iter();
|
||||||
let best = alphas_iter
|
let best = alphas_iter
|
||||||
.filter_map(|&alpha| {
|
.filter_map(|&alpha| {
|
||||||
let mut image = self.clone();
|
let image = match self.reduced_alpha_channel(*alpha) {
|
||||||
image.reduce_alpha_channel(*alpha);
|
Some(image) => image,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
let filters_iter = STD_FILTERS.par_iter().with_max_len(1);
|
let filters_iter = STD_FILTERS.par_iter().with_max_len(1);
|
||||||
#[cfg(not(feature = "parallel"))]
|
#[cfg(not(feature = "parallel"))]
|
||||||
|
|
@ -622,10 +625,14 @@ impl PngData {
|
||||||
|
|
||||||
if let Some(best) = best {
|
if let Some(best) = best {
|
||||||
self.raw_data = best.1.raw_data;
|
self.raw_data = best.1.raw_data;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reduce_alpha_channel(&mut self, optim: AlphaOptim) -> bool {
|
/// It doesn't recompress `idat_data`, so this field is out of date
|
||||||
|
/// after the reduction.
|
||||||
|
pub fn reduced_alpha_channel(&self, optim: AlphaOptim) -> Option<Self> {
|
||||||
let (bpc, bpp) = match self.ihdr_data.color_type {
|
let (bpc, bpp) = match self.ihdr_data.color_type {
|
||||||
ColorType::RGBA | ColorType::GrayscaleAlpha => {
|
ColorType::RGBA | ColorType::GrayscaleAlpha => {
|
||||||
let cpp = self.channels_per_pixel();
|
let cpp = self.channels_per_pixel();
|
||||||
|
|
@ -633,38 +640,32 @@ impl PngData {
|
||||||
(bpc as usize, (bpc * cpp) as usize)
|
(bpc as usize, (bpc * cpp) as usize)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return false;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match optim {
|
let raw_data = match optim {
|
||||||
AlphaOptim::NoOp => {
|
AlphaOptim::NoOp => return None,
|
||||||
return false;
|
AlphaOptim::Black => self.reduced_alpha_to_black(bpc, bpp),
|
||||||
}
|
AlphaOptim::White => self.reduced_alpha_to_white(bpc, bpp),
|
||||||
AlphaOptim::Black => {
|
AlphaOptim::Up => self.reduced_alpha_to_up(bpc, bpp),
|
||||||
self.raw_data = self.reduce_alpha_to_black(bpc, bpp);
|
AlphaOptim::Down => self.reduced_alpha_to_down(bpc, bpp),
|
||||||
}
|
AlphaOptim::Left => self.reduced_alpha_to_left(bpc, bpp),
|
||||||
AlphaOptim::White => {
|
AlphaOptim::Right => self.reduced_alpha_to_right(bpc, bpp),
|
||||||
self.raw_data = self.reduce_alpha_to_white(bpc, bpp);
|
};
|
||||||
}
|
|
||||||
AlphaOptim::Up => {
|
|
||||||
self.raw_data = self.reduce_alpha_to_up(bpc, bpp);
|
|
||||||
}
|
|
||||||
AlphaOptim::Down => {
|
|
||||||
self.raw_data = self.reduce_alpha_to_down(bpc, bpp);
|
|
||||||
}
|
|
||||||
AlphaOptim::Left => {
|
|
||||||
self.raw_data = self.reduce_alpha_to_left(bpc, bpp);
|
|
||||||
}
|
|
||||||
AlphaOptim::Right => {
|
|
||||||
self.raw_data = self.reduce_alpha_to_right(bpc, bpp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
Some(Self {
|
||||||
|
raw_data,
|
||||||
|
idat_data: vec![],
|
||||||
|
ihdr_data: self.ihdr_data,
|
||||||
|
palette: self.palette.clone(),
|
||||||
|
transparency_pixel: self.transparency_pixel.clone(),
|
||||||
|
transparency_palette: self.transparency_palette.clone(),
|
||||||
|
aux_headers: self.aux_headers.clone(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reduce_alpha_to_black(&self, bpc: usize, bpp: usize) -> Vec<u8> {
|
fn reduced_alpha_to_black(&self, bpc: usize, bpp: usize) -> Vec<u8> {
|
||||||
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() {
|
for line in self.scan_lines() {
|
||||||
reduced.push(line.filter);
|
reduced.push(line.filter);
|
||||||
|
|
@ -681,7 +682,7 @@ impl PngData {
|
||||||
reduced
|
reduced
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reduce_alpha_to_white(&self, bpc: usize, bpp: usize) -> Vec<u8> {
|
fn reduced_alpha_to_white(&self, bpc: usize, bpp: usize) -> Vec<u8> {
|
||||||
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() {
|
for line in self.scan_lines() {
|
||||||
reduced.push(line.filter);
|
reduced.push(line.filter);
|
||||||
|
|
@ -701,7 +702,7 @@ impl PngData {
|
||||||
reduced
|
reduced
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reduce_alpha_to_up(&self, bpc: usize, bpp: usize) -> Vec<u8> {
|
fn reduced_alpha_to_up(&self, bpc: usize, bpp: usize) -> Vec<u8> {
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
let mut scan_lines = self.scan_lines().collect::<Vec<ScanLine>>();
|
let mut scan_lines = self.scan_lines().collect::<Vec<ScanLine>>();
|
||||||
scan_lines.reverse();
|
scan_lines.reverse();
|
||||||
|
|
@ -729,7 +730,7 @@ impl PngData {
|
||||||
flatten(lines.into_iter().rev()).collect()
|
flatten(lines.into_iter().rev()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reduce_alpha_to_down(&self, bpc: usize, bpp: usize) -> Vec<u8> {
|
fn reduced_alpha_to_down(&self, bpc: usize, bpp: usize) -> Vec<u8> {
|
||||||
let mut reduced = Vec::with_capacity(self.raw_data.len());
|
let mut reduced = Vec::with_capacity(self.raw_data.len());
|
||||||
let mut last_line = Vec::new();
|
let mut last_line = Vec::new();
|
||||||
for line in self.scan_lines() {
|
for line in self.scan_lines() {
|
||||||
|
|
@ -752,7 +753,7 @@ impl PngData {
|
||||||
reduced
|
reduced
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reduce_alpha_to_left(&self, bpc: usize, bpp: usize) -> Vec<u8> {
|
fn reduced_alpha_to_left(&self, bpc: usize, bpp: usize) -> Vec<u8> {
|
||||||
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() {
|
for line in self.scan_lines() {
|
||||||
let mut line_bytes = Vec::with_capacity(line.data.len());
|
let mut line_bytes = Vec::with_capacity(line.data.len());
|
||||||
|
|
@ -774,7 +775,7 @@ impl PngData {
|
||||||
reduced
|
reduced
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reduce_alpha_to_right(&self, bpc: usize, bpp: usize) -> Vec<u8> {
|
fn reduced_alpha_to_right(&self, bpc: usize, bpp: usize) -> Vec<u8> {
|
||||||
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() {
|
for line in self.scan_lines() {
|
||||||
reduced.push(line.filter);
|
reduced.push(line.filter);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue