Reduce allocations (#122)

* Iterate with fewer temp allocations

* Avoid allocation when not reducing

* Avoid slow modulo
This commit is contained in:
Kornel 2018-07-20 23:40:51 +01:00 committed by Josh Holmer
parent c530bba261
commit 76a2e51c15
4 changed files with 68 additions and 53 deletions

View file

@ -295,7 +295,7 @@ impl PngData {
} }
_ => unreachable!(), _ => unreachable!(),
} }
last_line = line.data; last_line = line.data.to_vec();
last_pass = line.pass; last_pass = line.pass;
} }
filtered filtered
@ -322,17 +322,17 @@ impl PngData {
for line in self.scan_lines() { for line in self.scan_lines() {
reduced.push(line.filter); reduced.push(line.filter);
for (i, byte) in line.data.iter().enumerate() { for (i, &byte) in line.data.iter().enumerate() {
if i % 2 == 0 { if i % 2 == 0 {
// High byte // High byte
high_byte = *byte; high_byte = byte;
} else { } else {
// Low byte // Low byte
if high_byte != *byte { if high_byte != byte {
// Can't reduce, exit early // Can't reduce, exit early
return false; return false;
} }
reduced.push(*byte); reduced.push(byte);
} }
} }
} }
@ -406,8 +406,8 @@ impl PngData {
let mut seen = HashSet::with_capacity(indexed_palette.len()); let mut seen = HashSet::with_capacity(indexed_palette.len());
for line in self.scan_lines() { for line in self.scan_lines() {
match self.ihdr_data.bit_depth { match self.ihdr_data.bit_depth {
BitDepth::Eight => for byte in &line.data { BitDepth::Eight => for &byte in line.data {
seen.insert(*byte); seen.insert(byte);
}, },
BitDepth::Four => { BitDepth::Four => {
let bitvec = BitVec::from_bytes(&line.data); let bitvec = BitVec::from_bytes(&line.data);
@ -489,52 +489,52 @@ impl PngData {
for line in self.scan_lines() { for line in self.scan_lines() {
new_data.push(line.filter); new_data.push(line.filter);
match self.ihdr_data.bit_depth { match self.ihdr_data.bit_depth {
BitDepth::Eight => for byte in &line.data { BitDepth::Eight => for &byte in line.data {
if let Some(new_idx) = index_map.get(byte) { if let Some(&new_idx) = index_map.get(&byte) {
new_data.push(*new_idx); new_data.push(new_idx);
} else { } else {
new_data.push(*byte); new_data.push(byte);
} }
}, },
BitDepth::Four => for byte in &line.data { BitDepth::Four => for &byte in line.data {
let upper = *byte & 0b1111_0000; let upper = byte & 0b1111_0000;
let lower = *byte & 0b0000_1111; let lower = byte & 0b0000_1111;
let mut new_byte = 0u8; let mut new_byte = 0u8;
new_byte |= if let Some(new_idx) = index_map.get(&(upper >> 4)) { new_byte |= if let Some(&new_idx) = index_map.get(&(upper >> 4)) {
*new_idx << 4 new_idx << 4
} else { } else {
upper upper
}; };
new_byte |= if let Some(new_idx) = index_map.get(&lower) { new_byte |= if let Some(&new_idx) = index_map.get(&lower) {
*new_idx new_idx
} else { } else {
lower lower
}; };
new_data.push(new_byte); new_data.push(new_byte);
}, },
BitDepth::Two => for byte in &line.data { BitDepth::Two => for &byte in line.data {
let one = *byte & 0b1100_0000; let one = byte & 0b1100_0000;
let two = *byte & 0b0011_0000; let two = byte & 0b0011_0000;
let three = *byte & 0b0000_1100; let three = byte & 0b0000_1100;
let four = *byte & 0b0000_0011; let four = byte & 0b0000_0011;
let mut new_byte = 0u8; let mut new_byte = 0u8;
new_byte |= if let Some(new_idx) = index_map.get(&(one >> 6)) { new_byte |= if let Some(&new_idx) = index_map.get(&(one >> 6)) {
*new_idx << 6 new_idx << 6
} else { } else {
one one
}; };
new_byte |= if let Some(new_idx) = index_map.get(&(two >> 4)) { new_byte |= if let Some(&new_idx) = index_map.get(&(two >> 4)) {
*new_idx << 4 new_idx << 4
} else { } else {
two two
}; };
new_byte |= if let Some(new_idx) = index_map.get(&(three >> 2)) { new_byte |= if let Some(&new_idx) = index_map.get(&(three >> 2)) {
*new_idx << 2 new_idx << 2
} else { } else {
three three
}; };
new_byte |= if let Some(new_idx) = index_map.get(&four) { new_byte |= if let Some(&new_idx) = index_map.get(&four) {
*new_idx new_idx
} else { } else {
four four
}; };

View file

@ -12,7 +12,7 @@ pub struct ScanLines<'a> {
} }
impl<'a> Iterator for ScanLines<'a> { impl<'a> Iterator for ScanLines<'a> {
type Item = ScanLine; type Item = ScanLine<'a>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.end == self.png.raw_data.len() { if self.end == self.png.raw_data.len() {
None None
@ -109,7 +109,7 @@ impl<'a> Iterator for ScanLines<'a> {
} }
Some(ScanLine { Some(ScanLine {
filter: self.png.raw_data[self.start], filter: self.png.raw_data[self.start],
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(), data: &self.png.raw_data[(self.start + 1)..self.end],
pass: current_pass, pass: current_pass,
}) })
} else { } else {
@ -122,7 +122,7 @@ impl<'a> Iterator for ScanLines<'a> {
self.end = self.start + bytes_per_line + 1; self.end = self.start + bytes_per_line + 1;
Some(ScanLine { Some(ScanLine {
filter: self.png.raw_data[self.start], filter: self.png.raw_data[self.start],
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(), data: &self.png.raw_data[(self.start + 1)..self.end],
pass: None, pass: None,
}) })
} }
@ -131,11 +131,11 @@ impl<'a> Iterator for ScanLines<'a> {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// A scan line in a PNG image /// A scan line in a PNG image
pub struct ScanLine { pub struct ScanLine<'a> {
/// The filter type used to encode the current scan line (0-4) /// The filter type used to encode the current scan line (0-4)
pub filter: u8, pub filter: u8,
/// The byte data for the current scan line, encoded with the filter specified in the `filter` field /// The byte data for the current scan line, encoded with the filter specified in the `filter` field
pub data: Vec<u8>, pub data: &'a[u8],
/// The current pass if the image is interlaced /// The current pass if the image is interlaced
pub pass: Option<u8>, pub pass: Option<u8>,
} }

View file

@ -1,24 +1,37 @@
use png::PngData; use png::PngData;
pub fn reduce_alpha_channel(png: &mut PngData, bpp_factor: usize) -> Option<Vec<u8>> { pub fn reduce_alpha_channel(png: &mut PngData, channels: u8) -> Option<Vec<u8>> {
let mut reduced = Vec::with_capacity(png.raw_data.len()); let byte_depth = png.ihdr_data.bit_depth.as_u8() >> 3;
let byte_depth: u8 = png.ihdr_data.bit_depth.as_u8() >> 3; let bpp = channels * byte_depth;
let bpp: usize = bpp_factor * byte_depth as usize; let bpp_mask = bpp - 1;
let colored_bytes = bpp - byte_depth as usize; assert_eq!(0, bpp & bpp_mask);
let colored_bytes = bpp - byte_depth;
for line in png.scan_lines() { for line in png.scan_lines() {
reduced.push(line.filter); for (i, &byte) in line.data.iter().enumerate() {
for (i, byte) in line.data.iter().enumerate() { if i as u8 & bpp_mask >= colored_bytes {
if i % bpp >= colored_bytes { if byte != 255 {
if *byte != 255 {
return None; return None;
} }
} else {
reduced.push(*byte);
} }
} }
} }
let mut reduced = Vec::with_capacity(png.raw_data.len());
for line in png.scan_lines() {
reduced.push(line.filter);
for (i, &byte) in line.data.iter().enumerate() {
if i as u8 & bpp_mask >= colored_bytes {
continue;
} else {
reduced.push(byte);
}
}
}
// sBIT contains information about alpha channel's original depth,
// and alpha has just been removed
if let Some(sbit_header) = png.aux_headers.get_mut(&"sBIT".to_string()) { if let Some(sbit_header) = png.aux_headers.get_mut(&"sBIT".to_string()) {
assert_eq!(sbit_header.len(), bpp_factor); assert_eq!(sbit_header.len(), channels as usize);
sbit_header.pop(); sbit_header.pop();
} }

View file

@ -16,16 +16,18 @@ pub fn reduce_rgba_to_rgb(png: &mut PngData) -> bool {
pub fn reduce_rgba_to_grayscale_alpha(png: &mut PngData) -> bool { pub fn reduce_rgba_to_grayscale_alpha(png: &mut PngData) -> bool {
let mut reduced = Vec::with_capacity(png.raw_data.len()); let mut reduced = Vec::with_capacity(png.raw_data.len());
let byte_depth: u8 = png.ihdr_data.bit_depth.as_u8() >> 3; let byte_depth = png.ihdr_data.bit_depth.as_u8() >> 3;
let bpp: usize = 4 * byte_depth as usize; let bpp = 4 * byte_depth;
let colored_bytes = bpp - byte_depth as usize; let bpp_mask = bpp - 1;
assert_eq!(0, bpp & bpp_mask);
let colored_bytes = bpp - byte_depth;
for line in png.scan_lines() { for line in png.scan_lines() {
reduced.push(line.filter); reduced.push(line.filter);
let mut low_bytes = Vec::with_capacity(4); let mut low_bytes = Vec::with_capacity(4);
let mut high_bytes = Vec::with_capacity(4); let mut high_bytes = Vec::with_capacity(4);
let mut trans_bytes = Vec::with_capacity(byte_depth as usize); let mut trans_bytes = Vec::with_capacity(byte_depth as usize);
for (i, byte) in line.data.iter().enumerate() { for (i, byte) in line.data.iter().enumerate() {
if i % bpp < colored_bytes { if i as u8 & bpp_mask < colored_bytes {
if byte_depth == 1 || i % 2 == 1 { if byte_depth == 1 || i % 2 == 1 {
low_bytes.push(*byte); low_bytes.push(*byte);
} else { } else {
@ -35,7 +37,7 @@ pub fn reduce_rgba_to_grayscale_alpha(png: &mut PngData) -> bool {
trans_bytes.push(*byte); trans_bytes.push(*byte);
} }
if i % bpp == bpp - 1 { if (i as u8 & bpp_mask) == bpp - 1 {
if low_bytes.iter().unique().count() > 1 { if low_bytes.iter().unique().count() > 1 {
return false; return false;
} }