Implement heuristic filter mode and optimize modes 1-4

This commit is contained in:
Joshua Holmer 2016-01-11 01:01:53 -05:00
parent 4765c2133a
commit 11e475ba15

View file

@ -280,13 +280,13 @@ impl PngData {
// This avoids casting to and from floats, which is expensive // This avoids casting to and from floats, which is expensive
let tmp = self.ihdr_data.bit_depth.as_u8() * self.bits_per_pixel_raw(); let tmp = self.ihdr_data.bit_depth.as_u8() * self.bits_per_pixel_raw();
let bpp = (tmp + tmp % 8) >> 3; let bpp = (tmp + tmp % 8) >> 3;
let mut last_line: Option<Vec<u8>> = None; let mut last_line: Vec<u8> = vec![];
for line in self.scan_lines() { for line in self.scan_lines() {
unfiltered.push(line.filter); unfiltered.push(line.filter);
match line.filter { match line.filter {
0 => { 0 => {
let mut data = line.data.clone(); let mut data = line.data.clone();
last_line = Some(data.clone()); last_line = data.clone();
unfiltered.append(&mut data); unfiltered.append(&mut data);
} }
1 => { 1 => {
@ -297,28 +297,28 @@ impl PngData {
None => data.push(*byte), None => data.push(*byte),
} }
} }
last_line = Some(data.clone()); last_line = data.clone();
unfiltered.append(&mut data); unfiltered.append(&mut data);
} }
2 => { 2 => {
let mut data = Vec::with_capacity(line.data.len()); let mut data = Vec::with_capacity(line.data.len());
for (i, byte) in line.data.iter().enumerate() { for (i, byte) in line.data.iter().enumerate() {
if let Some(last) = last_line.clone() { if last_line.len() > 0 {
data.push(byte.wrapping_add(last[i])); data.push(byte.wrapping_add(last_line[i]));
} else { } else {
data.push(*byte); data.push(*byte);
}; };
} }
last_line = Some(data.clone()); last_line = data.clone();
unfiltered.append(&mut data); unfiltered.append(&mut data);
} }
3 => { 3 => {
let mut data = Vec::with_capacity(line.data.len()); let mut data = Vec::with_capacity(line.data.len());
for (i, byte) in line.data.iter().enumerate() { for (i, byte) in line.data.iter().enumerate() {
if let Some(last) = last_line.clone() { if last_line.len() > 0 {
data.push(match i.checked_sub(bpp as usize) { data.push(match i.checked_sub(bpp as usize) {
Some(x) => byte.wrapping_add((line.data[x] + last[i]) >> 1), Some(x) => byte.wrapping_add(((line.data[x] as u16 + last_line[i] as u16) >> 1) as u8),
None => byte.wrapping_add(last[i] >> 1), None => byte.wrapping_add(last_line[i] >> 1),
}); });
} else { } else {
data.push(match i.checked_sub(bpp as usize) { data.push(match i.checked_sub(bpp as usize) {
@ -327,20 +327,20 @@ impl PngData {
}); });
}; };
} }
last_line = Some(data.clone()); last_line = data.clone();
unfiltered.append(&mut data); unfiltered.append(&mut data);
} }
4 => { 4 => {
let mut data = Vec::with_capacity(line.data.len()); let mut data = Vec::with_capacity(line.data.len());
for (i, byte) in line.data.iter().enumerate() { for (i, byte) in line.data.iter().enumerate() {
if let Some(last) = last_line.clone() { if last_line.len() > 0 {
data.push(match i.checked_sub(bpp as usize) { data.push(match i.checked_sub(bpp as usize) {
Some(x) => { Some(x) => {
byte.wrapping_add(paeth_predictor(line.data[x], byte.wrapping_add(paeth_predictor(line.data[x],
last[i], last_line[i],
last[x])) last_line[x]))
} }
None => byte.wrapping_add(last[i]), None => byte.wrapping_add(last_line[i]),
}); });
} else { } else {
data.push(match i.checked_sub(bpp as usize) { data.push(match i.checked_sub(bpp as usize) {
@ -349,7 +349,7 @@ impl PngData {
}); });
}; };
} }
last_line = Some(data.clone()); last_line = data.clone();
unfiltered.append(&mut data); unfiltered.append(&mut data);
} }
_ => panic!("Unreachable"), _ => panic!("Unreachable"),
@ -362,20 +362,15 @@ impl PngData {
// This avoids casting to and from floats, which is expensive // This avoids casting to and from floats, which is expensive
let tmp = self.ihdr_data.bit_depth.as_u8() * self.bits_per_pixel_raw(); let tmp = self.ihdr_data.bit_depth.as_u8() * self.bits_per_pixel_raw();
let bpp = (tmp + tmp % 8) >> 3; let bpp = (tmp + tmp % 8) >> 3;
let mut last_line: Option<Vec<u8>> = None; let mut last_line: Vec<u8> = vec![];
// We could try a different filter method for each line // We could try a different filter method for each line
// But that would be prohibitively slow and probably not provide much benefit // But that would be prohibitively slow and probably not provide much benefit
// So we just use one filter method for the whole image // So we just use one filter method for the whole image
for line in self.scan_lines() { for line in self.scan_lines() {
let internal_filter = match filter { if filter != 5 {
5 => { filtered.push(filter);
// TODO: Heuristically guess best filter per line }
0 match filter {
}
x => x,
};
filtered.push(internal_filter);
match internal_filter {
0 => { 0 => {
let mut data = line.data.clone(); let mut data = line.data.clone();
filtered.append(&mut data); filtered.append(&mut data);
@ -390,8 +385,8 @@ impl PngData {
} }
2 => { 2 => {
for (i, byte) in line.data.iter().enumerate() { for (i, byte) in line.data.iter().enumerate() {
if let Some(last) = last_line.clone() { if last_line.len() > 0 {
filtered.push(byte.wrapping_sub(last[i])); filtered.push(byte.wrapping_sub(last_line[i]));
} else { } else {
filtered.push(*byte); filtered.push(*byte);
}; };
@ -399,10 +394,10 @@ impl PngData {
} }
3 => { 3 => {
for (i, byte) in line.data.iter().enumerate() { for (i, byte) in line.data.iter().enumerate() {
if let Some(last) = last_line.clone() { if last_line.len() > 0 {
filtered.push(match i.checked_sub(bpp as usize) { filtered.push(match i.checked_sub(bpp as usize) {
Some(x) => byte.wrapping_sub((line.data[x] + last[i]) >> 1), Some(x) => byte.wrapping_sub(((line.data[x] as u16 + last_line[i] as u16) >> 1) as u8),
None => byte.wrapping_sub(last[i] >> 1), None => byte.wrapping_sub(last_line[i] >> 1),
}); });
} else { } else {
filtered.push(match i.checked_sub(bpp as usize) { filtered.push(match i.checked_sub(bpp as usize) {
@ -414,14 +409,14 @@ impl PngData {
} }
4 => { 4 => {
for (i, byte) in line.data.iter().enumerate() { for (i, byte) in line.data.iter().enumerate() {
if let Some(last) = last_line.clone() { if last_line.len() > 0 {
filtered.push(match i.checked_sub(bpp as usize) { filtered.push(match i.checked_sub(bpp as usize) {
Some(x) => { Some(x) => {
byte.wrapping_sub(paeth_predictor(line.data[x], byte.wrapping_sub(paeth_predictor(line.data[x],
last[i], last_line[i],
last[x])) last_line[x]))
} }
None => byte.wrapping_sub(last[i]), None => byte.wrapping_sub(last_line[i]),
}); });
} else { } else {
filtered.push(match i.checked_sub(bpp as usize) { filtered.push(match i.checked_sub(bpp as usize) {
@ -431,9 +426,92 @@ impl PngData {
}; };
} }
} }
5 => {
// Heuristically guess best filter per line
// Really simple algorithm, maybe we could replace this with something better
// libpng's heuristic no longer exists so I can't reference it
// Yes I know this code is ugly, but I didn't want to mess with mutable
// references from a HashMap that return options
// FIXME: Regardless of that, this is not very memory efficient
// Someone who's better at Rust can clean this up if they want
let line_0 = line.data.clone();
let mut line_1 = Vec::with_capacity(line.data.len());
let mut line_2 = Vec::with_capacity(line.data.len());
let mut line_3 = Vec::with_capacity(line.data.len());
let mut line_4 = Vec::with_capacity(line.data.len());
for (i, byte) in line.data.iter().enumerate() {
if last_line.len() > 0 {
match i.checked_sub(bpp as usize) {
Some(x) => {
line_1.push(byte.wrapping_sub(line.data[x]));
line_2.push(byte.wrapping_sub(last_line[i]));
line_3.push(byte.wrapping_sub(((line.data[x] as u16 + last_line[i] as u16) >> 1) as u8));
line_4.push(byte.wrapping_sub(paeth_predictor(line.data[x],
last_line[i],
last_line[x])));
},
None => {
line_1.push(*byte);
line_2.push(byte.wrapping_sub(last_line[i]));
line_3.push(byte.wrapping_sub(last_line[i] >> 1));
line_4.push(byte.wrapping_sub(last_line[i]));
},
}
} else {
match i.checked_sub(bpp as usize) {
Some(x) => {
line_1.push(byte.wrapping_sub(line.data[x]));
line_2.push(*byte);
line_3.push(byte.wrapping_sub(line.data[x] >> 1));
line_4.push(byte.wrapping_sub(line.data[x]));
},
None => {
line_1.push(*byte);
line_2.push(*byte);
line_3.push(*byte);
line_4.push(*byte);
},
}
};
}
// Count the number of unique bytes and take the lowest
let mut uniq_0 = line_0.clone();
uniq_0.sort();
uniq_0.dedup();
let mut uniq_1 = line_1.clone();
uniq_1.sort();
uniq_1.dedup();
let mut uniq_2 = line_2.clone();
uniq_2.sort();
uniq_2.dedup();
let mut uniq_3 = line_3.clone();
uniq_3.sort();
uniq_3.dedup();
let mut uniq_4 = line_4.clone();
uniq_4.sort();
uniq_4.dedup();
let mut best: (u8, &[u8], usize) = (0, &line_0, uniq_0.len());
if uniq_1.len() < best.2 {
best = (1, &line_1, uniq_1.len());
}
if uniq_2.len() < best.2 {
best = (2, &line_2, uniq_2.len());
}
if uniq_3.len() < best.2 {
best = (3, &line_3, uniq_3.len());
}
if uniq_4.len() < best.2 {
best = (4, &line_4, uniq_4.len());
}
filtered.push(best.0);
let mut data = best.1.to_owned();
filtered.append(&mut data);
}
_ => panic!("Unreachable"), _ => panic!("Unreachable"),
} }
last_line = Some(line.data.clone()); last_line = line.data.clone();
} }
filtered filtered
} }