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(); let png = PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); png.reduced_alpha_channel(AlphaOptim::Black)
safe_png.reduce_alpha_channel(AlphaOptim::Black);
}); });
} }
@ -272,8 +271,7 @@ fn reductions_alpha_white(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); png.reduced_alpha_channel(AlphaOptim::White)
safe_png.reduce_alpha_channel(AlphaOptim::White);
}); });
} }
@ -283,8 +281,7 @@ fn reductions_alpha_left(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); png.reduced_alpha_channel(AlphaOptim::Left)
safe_png.reduce_alpha_channel(AlphaOptim::Left);
}); });
} }
@ -294,8 +291,7 @@ fn reductions_alpha_right(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); png.reduced_alpha_channel(AlphaOptim::Right)
safe_png.reduce_alpha_channel(AlphaOptim::Right);
}); });
} }
@ -305,8 +301,7 @@ fn reductions_alpha_up(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); png.reduced_alpha_channel(AlphaOptim::Up)
safe_png.reduce_alpha_channel(AlphaOptim::Up);
}); });
} }
@ -316,7 +311,6 @@ fn reductions_alpha_down(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap(); let png = PngData::new(&input, false).unwrap();
b.iter(|| { b.iter(|| {
let mut safe_png = png.clone(); png.reduced_alpha_channel(AlphaOptim::Down)
safe_png.reduce_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 /// 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>> { pub fn deflate(data: &[u8], zc: u8, zs: u8, zw: u8, max_size: &AtomicMin) -> PngResult<Vec<u8>> {
if is_cfzlib_supported() { #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
return cfzlib_deflate(data, zc, zs, zw, max_size); {
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) 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")] #[cfg(target_arch = "x86_64")]
fn is_cfzlib_supported() -> bool { fn is_cfzlib_supported() -> bool {
if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { 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; let mut current_y: usize = pass_constants.y_shift as usize;
for line in png.scan_lines() { for line in png.scan_lines() {
let bit_vec = BitVec::from_bytes(&line.data); let bit_vec = BitVec::from_bytes(&line.data);
let bits_in_line = ((png.ihdr_data.width - u32::from(pass_constants.x_shift)) as f32 let bits_in_line = ((png.ihdr_data.width - u32::from(pass_constants.x_shift) + u32::from(pass_constants.x_step) - 1)
/ f32::from(pass_constants.x_step)).ceil() as usize / u32::from(pass_constants.x_step)) as usize * bits_per_pixel as usize;
* bits_per_pixel as usize;
for (i, bit) in bit_vec.iter().enumerate() { for (i, bit) in bit_vec.iter().enumerate() {
// Avoid moving padded 0's into new image // Avoid moving padded 0's into new image
if i >= bits_in_line { if i >= bits_in_line {

View file

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