Tweaks and Microoptimizations (#146)

* Fixed alpha benchmark

* Dedupe function

* Use integer math when rounding

* Fewer temporaries when slicing palette

* Filtering microoptimizations
This commit is contained in:
Kornel 2018-11-20 13:39:46 +00:00 committed by Josh Holmer
parent d82406a959
commit 588d5bd52b
4 changed files with 39 additions and 58 deletions

View file

@ -261,8 +261,7 @@ fn reductions_alpha_black(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_alpha_channel(AlphaOptim::Black);
png.reduced_alpha_channel(AlphaOptim::Black)
});
}
@ -272,8 +271,7 @@ fn reductions_alpha_white(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_alpha_channel(AlphaOptim::White);
png.reduced_alpha_channel(AlphaOptim::White)
});
}
@ -283,8 +281,7 @@ fn reductions_alpha_left(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_alpha_channel(AlphaOptim::Left);
png.reduced_alpha_channel(AlphaOptim::Left)
});
}
@ -294,8 +291,7 @@ fn reductions_alpha_right(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_alpha_channel(AlphaOptim::Right);
png.reduced_alpha_channel(AlphaOptim::Right)
});
}
@ -305,8 +301,7 @@ fn reductions_alpha_up(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_alpha_channel(AlphaOptim::Up);
png.reduced_alpha_channel(AlphaOptim::Up)
});
}
@ -316,7 +311,6 @@ fn reductions_alpha_down(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
let mut safe_png = png.clone();
safe_png.reduce_alpha_channel(AlphaOptim::Down);
png.reduced_alpha_channel(AlphaOptim::Down)
});
}

View file

@ -15,21 +15,17 @@ pub fn inflate(data: &[u8]) -> PngResult<Vec<u8>> {
}
/// Compress a data stream using the DEFLATE algorithm
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub fn deflate(data: &[u8], zc: u8, zs: u8, zw: u8, max_size: &AtomicMin) -> PngResult<Vec<u8>> {
if is_cfzlib_supported() {
return cfzlib_deflate(data, zc, zs, zw, max_size);
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
{
if is_cfzlib_supported() {
return cfzlib_deflate(data, zc, zs, zw, max_size);
}
}
miniz_stream::compress_to_vec_oxipng(data, zc, zw.into(), zs.into(), max_size)
}
/// Compress a data stream using the DEFLATE algorithm
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
pub fn deflate(data: &[u8], zc: u8, zs: u8, zw: u8, max_size: &AtomicMin) -> PngResult<Vec<u8>> {
miniz_stream::compress_to_vec_oxipng(data, zc, zw.into(), zs.into(), max_size)
}
#[cfg(target_arch = "x86_64")]
fn is_cfzlib_supported() -> bool {
if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") {

View file

@ -93,9 +93,8 @@ pub fn deinterlace_image(png: &mut PngData) {
let mut current_y: usize = pass_constants.y_shift as usize;
for line in png.scan_lines() {
let bit_vec = BitVec::from_bytes(&line.data);
let bits_in_line = ((png.ihdr_data.width - u32::from(pass_constants.x_shift)) as f32
/ f32::from(pass_constants.x_step)).ceil() as usize
* bits_per_pixel as usize;
let bits_in_line = ((png.ihdr_data.width - u32::from(pass_constants.x_shift) + u32::from(pass_constants.x_step) - 1)
/ u32::from(pass_constants.x_step)) as usize * bits_per_pixel as usize;
for (i, bit) in bit_vec.iter().enumerate() {
// Avoid moving padded 0's into new image
if i >= bits_in_line {

View file

@ -16,7 +16,7 @@ use reduction::color::*;
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::iter::Iterator;
use std::iter::{Iterator, repeat};
use std::path::Path;
const STD_COMPRESSION: u8 = 8;
@ -226,8 +226,7 @@ impl PngData {
/// Reverse all filters applied on the image, returning an unfiltered IDAT bytestream
pub fn unfilter_image(&self) -> Vec<u8> {
let mut unfiltered = Vec::with_capacity(self.raw_data.len());
let bpp = ((f32::from(self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel())) / 8f32)
.ceil() as usize;
let bpp = ((self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
let mut last_line: Vec<u8> = Vec::new();
let mut last_pass = 1;
for line in self.scan_lines() {
@ -254,9 +253,8 @@ impl PngData {
/// 5: All (heuristically pick the best filter for each line)
pub fn filter_image(&self, filter: u8) -> Vec<u8> {
let mut filtered = Vec::with_capacity(self.raw_data.len());
let bpp = ((f32::from(self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel())) / 8f32)
.ceil() as usize;
let mut last_line: Vec<u8> = Vec::new();
let bpp = ((self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
let mut last_line: &[u8] = &[];
let mut last_pass: Option<u8> = None;
for line in self.scan_lines() {
match filter {
@ -267,21 +265,21 @@ impl PngData {
0
};
filtered.push(filter);
filtered.extend_from_slice(&filter_line(filter, bpp, &line.data, &last_line));
filtered.extend_from_slice(&filter_line(filter, bpp, &line.data, last_line));
}
5 => {
// Heuristically guess best filter per line
// Uses MSAD algorithm mentioned in libpng reference docs
// http://www.libpng.org/pub/png/book/chapter09.html
let mut trials: HashMap<u8, Vec<u8>> = HashMap::with_capacity(5);
let mut trials: Vec<(u8, Vec<u8>)> = Vec::with_capacity(5);
// Avoid vertical filtering on first line of each interlacing pass
for filter in if last_pass == line.pass { 0..5 } else { 0..2 } {
trials.insert(filter, filter_line(filter, bpp, &line.data, &last_line));
trials.push((filter, filter_line(filter, bpp, &line.data, last_line)));
}
let (best_filter, best_line) = trials
.iter()
.min_by_key(|x| {
x.1.iter().fold(0u64, |acc, &x| {
.min_by_key(|(_, line)| {
line.iter().fold(0u64, |acc, &x| {
let signed = x as i8;
acc + i16::from(signed).abs() as u64
})
@ -291,7 +289,7 @@ impl PngData {
}
_ => unreachable!(),
}
last_line = line.data.to_vec();
last_line = line.data;
last_pass = line.pass;
}
filtered
@ -352,26 +350,20 @@ impl PngData {
}
// A palette with RGB or RGBA slices
let palette = if let Some(ref trns) = self.transparency_palette {
self.palette
.clone()
.unwrap()
.chunks(3)
.zip(trns.iter().chain([255].iter().cycle()))
.flat_map(|(pixel, trns)| {
let mut pixel = pixel.to_owned();
pixel.push(*trns);
pixel
}).collect()
let mut palette_tmp;
let mut indexed_palette: Vec<_> = if let Some(ref trns) = self.transparency_palette {
palette_tmp = Vec::with_capacity(1024);
for (pixel, trns) in self.palette.as_ref().unwrap().chunks(3)
.zip(trns.iter().cloned().chain(repeat(255))) {
palette_tmp.extend_from_slice(pixel);
palette_tmp.push(trns);
}
palette_tmp.chunks(4).collect()
} else {
self.palette.clone().unwrap()
palette_tmp = self.palette.clone().unwrap();
palette_tmp.chunks(3).collect()
};
let mut indexed_palette: Vec<&[u8]> = palette
.chunks(if self.transparency_palette.is_some() {
4
} else {
3
}).collect();
// A map of old indexes to new ones, for any moved
let mut index_map: HashMap<u8, u8> = HashMap::new();
@ -380,13 +372,13 @@ impl PngData {
{
// Find duplicate entries in the palette
let mut seen: HashMap<&[u8], u8> = HashMap::with_capacity(indexed_palette.len());
for (i, color) in indexed_palette.iter().enumerate() {
for (i, color) in indexed_palette.iter().cloned().enumerate() {
if seen.contains_key(color) {
let index = &seen[color];
let index = seen[color];
duplicates.push(i as u8);
index_map.insert(i as u8, *index);
index_map.insert(i as u8, index);
} else {
seen.insert(*color, i as u8);
seen.insert(color, i as u8);
}
}
}