diff --git a/src/filters.rs b/src/filters.rs index 875c3420..54b9bb63 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -1,12 +1,12 @@ -pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec { - let mut filtered = Vec::with_capacity(data.len()); +pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec) { + buf.reserve(data.len()); match filter { 0 => { - filtered.extend_from_slice(data); + buf.extend_from_slice(data); } 1 => { - filtered.extend_from_slice(&data[0..bpp]); - filtered.extend( + buf.extend_from_slice(&data[0..bpp]); + buf.extend( data.iter() .skip(bpp) .zip(data.iter()) @@ -15,9 +15,9 @@ pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec } 2 => { if last_line.is_empty() { - filtered.extend_from_slice(data); + buf.extend_from_slice(data); } else { - filtered.extend( + buf.extend( data.iter() .zip(last_line.iter()) .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 => { for (i, byte) in data.iter().enumerate() { 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), None => *byte, }); } else { - filtered.push(match i.checked_sub(bpp) { + buf.push(match i.checked_sub(bpp) { Some(x) => byte.wrapping_sub( ((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 => { for (i, byte) in data.iter().enumerate() { 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]), None => *byte, }); } else { - filtered.push(match i.checked_sub(bpp) { + buf.push(match i.checked_sub(bpp) { Some(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!(), } - filtered } -pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec { - let mut unfiltered = Vec::with_capacity(data.len()); +pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec) { + assert_eq!(buf.len(), 0); + buf.reserve(data.len()); match filter { 0 => { - unfiltered.extend_from_slice(data); + buf.extend_from_slice(data); } 1 => { for (i, byte) in data.iter().enumerate() { match i.checked_sub(bpp) { Some(x) => { - let b = unfiltered[x]; - unfiltered.push(byte.wrapping_add(b)); + let b = buf[x]; + buf.push(byte.wrapping_add(b)); } None => { - unfiltered.push(*byte); + buf.push(*byte); } }; } } 2 => { if last_line.is_empty() { - unfiltered.extend_from_slice(data); + buf.extend_from_slice(data); } else { - unfiltered.extend( + buf.extend( data.iter() .zip(last_line.iter()) .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() { match i.checked_sub(bpp) { Some(x) => { - let b = unfiltered[x]; - unfiltered.push(byte.wrapping_add(b >> 1)); + let b = buf[x]; + buf.push(byte.wrapping_add(b >> 1)); } None => { - unfiltered.push(*byte); + buf.push(*byte); } }; } else { match i.checked_sub(bpp) { Some(x) => { - let b = unfiltered[x]; - unfiltered.push(byte.wrapping_add( + let b = buf[x]; + buf.push(byte.wrapping_add( ((u16::from(b) + u16::from(last_line[i])) >> 1) as u8, )); } 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() { match i.checked_sub(bpp) { Some(x) => { - let b = unfiltered[x]; - unfiltered.push(byte.wrapping_add(b)); + let b = buf[x]; + buf.push(byte.wrapping_add(b)); } None => { - unfiltered.push(*byte); + buf.push(*byte); } }; } else { match i.checked_sub(bpp) { Some(x) => { - let b = unfiltered[x]; - unfiltered.push(byte.wrapping_add(paeth_predictor( + let b = buf[x]; + buf.push(byte.wrapping_add(paeth_predictor( b, last_line[i], last_line[x], ))); } 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!(), } - unfiltered } fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 { diff --git a/src/png/mod.rs b/src/png/mod.rs index 2fd5184f..75760930 100644 --- a/src/png/mod.rs +++ b/src/png/mod.rs @@ -282,17 +282,19 @@ impl PngImage { let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize; let mut last_line: Vec = Vec::new(); let mut last_pass = 1; + let mut unfiltered_buf = Vec::new(); for line in self.scan_lines() { if let Some(pass) = line.pass { if pass != last_pass { - last_line = Vec::new(); + last_line.clear(); 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.extend_from_slice(&unfiltered_line); - last_line = unfiltered_line; + unfiltered.extend_from_slice(&unfiltered_buf); + std::mem::swap(&mut last_line, &mut unfiltered_buf); + unfiltered_buf.clear(); } unfiltered } @@ -309,7 +311,9 @@ impl PngImage { let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize; let mut last_line: &[u8] = &[]; let mut last_pass: Option = None; + let mut f_buf = Vec::new(); for line in self.scan_lines() { + f_buf.clear(); match filter { 0 | 1 | 2 | 3 | 4 => { let filter = if last_pass == line.pass || filter <= 1 { @@ -318,28 +322,34 @@ impl PngImage { 0 }; 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 => { // 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: Vec<(u8, Vec)> = 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 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 - .iter() - .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); + filtered.push(best_filter); + filtered.extend_from_slice(&best_line); } _ => unreachable!(), }