Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce alloc in filtering/unfiltering by reusing buffers #191

Merged
merged 2 commits into from
Sep 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 32 additions & 33 deletions src/filters.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec<u8> {
let mut filtered = Vec::with_capacity(data.len());
pub fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) {
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())
Expand All @@ -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)),
Expand All @@ -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,
),
Expand All @@ -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]))
}
Expand All @@ -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<u8> {
let mut unfiltered = Vec::with_capacity(data.len());
pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) {
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)),
Expand All @@ -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));
}
};
};
Expand All @@ -125,33 +125,32 @@ 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]));
}
};
};
}
}
_ => unreachable!(),
}
unfiltered
}

fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 {
Expand Down
46 changes: 28 additions & 18 deletions src/png/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> = 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
}
Expand All @@ -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<u8> = 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 {
Expand All @@ -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<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
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!(),
}
Expand Down