Reduce alloc in filtering/unfiltering by reusing buffers (#191)

This commit is contained in:
Kamal Ahmad 2019-09-04 20:09:54 +05:00 committed by Josh Holmer
parent 239ca81db7
commit 68b763bcdd
2 changed files with 60 additions and 51 deletions

View file

@ -1,12 +1,12 @@
pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec<u8> { pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) {
let mut filtered = Vec::with_capacity(data.len()); buf.reserve(data.len());
match filter { match filter {
0 => { 0 => {
filtered.extend_from_slice(data); buf.extend_from_slice(data);
} }
1 => { 1 => {
filtered.extend_from_slice(&data[0..bpp]); buf.extend_from_slice(&data[0..bpp]);
filtered.extend( buf.extend(
data.iter() data.iter()
.skip(bpp) .skip(bpp)
.zip(data.iter()) .zip(data.iter())
@ -15,9 +15,9 @@ pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec
} }
2 => { 2 => {
if last_line.is_empty() { if last_line.is_empty() {
filtered.extend_from_slice(data); buf.extend_from_slice(data);
} else { } else {
filtered.extend( buf.extend(
data.iter() data.iter()
.zip(last_line.iter()) .zip(last_line.iter())
.map(|(cur, last)| cur.wrapping_sub(*last)), .map(|(cur, last)| cur.wrapping_sub(*last)),
@ -27,12 +27,12 @@ pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec
3 => { 3 => {
for (i, byte) in data.iter().enumerate() { for (i, byte) in data.iter().enumerate() {
if last_line.is_empty() { if last_line.is_empty() {
filtered.push(match i.checked_sub(bpp) { buf.push(match i.checked_sub(bpp) {
Some(x) => byte.wrapping_sub(data[x] >> 1), Some(x) => byte.wrapping_sub(data[x] >> 1),
None => *byte, None => *byte,
}); });
} else { } else {
filtered.push(match i.checked_sub(bpp) { buf.push(match i.checked_sub(bpp) {
Some(x) => byte.wrapping_sub( Some(x) => byte.wrapping_sub(
((u16::from(data[x]) + u16::from(last_line[i])) >> 1) as u8, ((u16::from(data[x]) + u16::from(last_line[i])) >> 1) as u8,
), ),
@ -44,12 +44,12 @@ pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec
4 => { 4 => {
for (i, byte) in data.iter().enumerate() { for (i, byte) in data.iter().enumerate() {
if last_line.is_empty() { if last_line.is_empty() {
filtered.push(match i.checked_sub(bpp) { buf.push(match i.checked_sub(bpp) {
Some(x) => byte.wrapping_sub(data[x]), Some(x) => byte.wrapping_sub(data[x]),
None => *byte, None => *byte,
}); });
} else { } else {
filtered.push(match i.checked_sub(bpp) { buf.push(match i.checked_sub(bpp) {
Some(x) => { Some(x) => {
byte.wrapping_sub(paeth_predictor(data[x], last_line[i], last_line[x])) byte.wrapping_sub(paeth_predictor(data[x], last_line[i], last_line[x]))
} }
@ -60,33 +60,33 @@ pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec
} }
_ => unreachable!(), _ => unreachable!(),
} }
filtered
} }
pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec<u8> { pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) {
let mut unfiltered = Vec::with_capacity(data.len()); assert_eq!(buf.len(), 0);
buf.reserve(data.len());
match filter { match filter {
0 => { 0 => {
unfiltered.extend_from_slice(data); buf.extend_from_slice(data);
} }
1 => { 1 => {
for (i, byte) in data.iter().enumerate() { for (i, byte) in data.iter().enumerate() {
match i.checked_sub(bpp) { match i.checked_sub(bpp) {
Some(x) => { Some(x) => {
let b = unfiltered[x]; let b = buf[x];
unfiltered.push(byte.wrapping_add(b)); buf.push(byte.wrapping_add(b));
} }
None => { None => {
unfiltered.push(*byte); buf.push(*byte);
} }
}; };
} }
} }
2 => { 2 => {
if last_line.is_empty() { if last_line.is_empty() {
unfiltered.extend_from_slice(data); buf.extend_from_slice(data);
} else { } else {
unfiltered.extend( buf.extend(
data.iter() data.iter()
.zip(last_line.iter()) .zip(last_line.iter())
.map(|(cur, last)| cur.wrapping_add(*last)), .map(|(cur, last)| cur.wrapping_add(*last)),
@ -98,23 +98,23 @@ pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> V
if last_line.is_empty() { if last_line.is_empty() {
match i.checked_sub(bpp) { match i.checked_sub(bpp) {
Some(x) => { Some(x) => {
let b = unfiltered[x]; let b = buf[x];
unfiltered.push(byte.wrapping_add(b >> 1)); buf.push(byte.wrapping_add(b >> 1));
} }
None => { None => {
unfiltered.push(*byte); buf.push(*byte);
} }
}; };
} else { } else {
match i.checked_sub(bpp) { match i.checked_sub(bpp) {
Some(x) => { Some(x) => {
let b = unfiltered[x]; let b = buf[x];
unfiltered.push(byte.wrapping_add( buf.push(byte.wrapping_add(
((u16::from(b) + u16::from(last_line[i])) >> 1) as u8, ((u16::from(b) + u16::from(last_line[i])) >> 1) as u8,
)); ));
} }
None => { None => {
unfiltered.push(byte.wrapping_add(last_line[i] >> 1)); buf.push(byte.wrapping_add(last_line[i] >> 1));
} }
}; };
}; };
@ -125,25 +125,25 @@ pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> V
if last_line.is_empty() { if last_line.is_empty() {
match i.checked_sub(bpp) { match i.checked_sub(bpp) {
Some(x) => { Some(x) => {
let b = unfiltered[x]; let b = buf[x];
unfiltered.push(byte.wrapping_add(b)); buf.push(byte.wrapping_add(b));
} }
None => { None => {
unfiltered.push(*byte); buf.push(*byte);
} }
}; };
} else { } else {
match i.checked_sub(bpp) { match i.checked_sub(bpp) {
Some(x) => { Some(x) => {
let b = unfiltered[x]; let b = buf[x];
unfiltered.push(byte.wrapping_add(paeth_predictor( buf.push(byte.wrapping_add(paeth_predictor(
b, b,
last_line[i], last_line[i],
last_line[x], last_line[x],
))); )));
} }
None => { None => {
unfiltered.push(byte.wrapping_add(last_line[i])); buf.push(byte.wrapping_add(last_line[i]));
} }
}; };
}; };
@ -151,7 +151,6 @@ pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> V
} }
_ => unreachable!(), _ => unreachable!(),
} }
unfiltered
} }
fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 { fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 {

View file

@ -282,17 +282,19 @@ impl PngImage {
let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize; let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) 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;
let mut unfiltered_buf = Vec::new();
for line in self.scan_lines() { for line in self.scan_lines() {
if let Some(pass) = line.pass { if let Some(pass) = line.pass {
if pass != last_pass { if pass != last_pass {
last_line = Vec::new(); last_line.clear();
last_pass = pass; last_pass = pass;
} }
} }
let unfiltered_line = unfilter_line(line.filter, bpp, &line.data, &last_line); unfilter_line(line.filter, bpp, &line.data, &last_line, &mut unfiltered_buf);
unfiltered.push(0); unfiltered.push(0);
unfiltered.extend_from_slice(&unfiltered_line); unfiltered.extend_from_slice(&unfiltered_buf);
last_line = unfiltered_line; std::mem::swap(&mut last_line, &mut unfiltered_buf);
unfiltered_buf.clear();
} }
unfiltered unfiltered
} }
@ -309,7 +311,9 @@ impl PngImage {
let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize; let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
let mut last_line: &[u8] = &[]; let mut last_line: &[u8] = &[];
let mut last_pass: Option<u8> = None; let mut last_pass: Option<u8> = None;
let mut f_buf = Vec::new();
for line in self.scan_lines() { for line in self.scan_lines() {
f_buf.clear();
match filter { match filter {
0 | 1 | 2 | 3 | 4 => { 0 | 1 | 2 | 3 | 4 => {
let filter = if last_pass == line.pass || filter <= 1 { let filter = if last_pass == line.pass || filter <= 1 {
@ -318,28 +322,34 @@ impl PngImage {
0 0
}; };
filtered.push(filter); filtered.push(filter);
filtered.extend_from_slice(&filter_line(filter, bpp, &line.data, last_line)); filter_line(filter, bpp, &line.data, last_line, &mut f_buf);
filtered.extend_from_slice(&f_buf);
} }
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: Vec<(u8, Vec<u8>)> = Vec::with_capacity(5); let mut best_filter = 0;
let mut best_line = Vec::new();
let mut best_size = std::u64::MAX;
// 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.push((filter, filter_line(filter, bpp, &line.data, last_line))); filter_line(filter, bpp, &line.data, last_line, &mut f_buf);
let size = f_buf.iter().fold(0u64, |acc, &x| {
let signed = x as i8;
acc + i16::from(signed).abs() as u64
});
if size < best_size {
best_size = size;
best_filter = filter;
std::mem::swap(&mut best_line, &mut f_buf);
}
f_buf.clear() //discard buffer, and start again
} }
let (best_filter, best_line) = trials filtered.push(best_filter);
.iter() filtered.extend_from_slice(&best_line);
.min_by_key(|(_, line)| {
line.iter().fold(0u64, |acc, &x| {
let signed = x as i8;
acc + i16::from(signed).abs() as u64
})
})
.unwrap();
filtered.push(*best_filter);
filtered.extend_from_slice(best_line);
} }
_ => unreachable!(), _ => unreachable!(),
} }