Add entropy filter

This commit is contained in:
Andrew 2022-11-05 14:07:54 +13:00
parent dfb6eb7c46
commit 5c25de48f1
2 changed files with 68 additions and 31 deletions

View file

@ -13,6 +13,7 @@ pub enum RowFilter {
Paeth, Paeth,
// Heuristic strategies // Heuristic strategies
MinSum, MinSum,
Entropy,
} }
impl TryFrom<u8> for RowFilter { impl TryFrom<u8> for RowFilter {
@ -38,14 +39,16 @@ impl Display for RowFilter {
Self::Average => "Average", Self::Average => "Average",
Self::Paeth => "Paeth", Self::Paeth => "Paeth",
Self::MinSum => "MinSum", Self::MinSum => "MinSum",
Self::Entropy => "Entropy",
} }
) )
} }
} }
impl RowFilter { impl RowFilter {
pub const LAST: u8 = Self::MinSum as u8; pub const LAST: u8 = Self::Entropy as u8;
pub const STANDARD: [Self; 5] = [Self::None, Self::Sub, Self::Up, Self::Average, Self::Paeth]; pub const STANDARD: [Self; 5] = [Self::None, Self::Sub, Self::Up, Self::Average, Self::Paeth];
pub const SINGLE_LINE: [Self; 2] = [Self::None, Self::Sub];
pub fn filter_line(self, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) { pub fn filter_line(self, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) {
assert!(data.len() >= bpp); assert!(data.len() >= bpp);

View file

@ -309,41 +309,69 @@ impl PngImage {
if last_pass != line.pass { if last_pass != line.pass {
last_line = &[]; last_line = &[];
} }
match filter {
RowFilter::MinSum => {
// 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 best_line = Vec::new();
let mut best_size = u64::MAX;
for try_filter in RowFilter::STANDARD { if filter <= RowFilter::Paeth {
// Avoid vertical filtering on first line of each interlacing pass // Standard filters
if last_pass != line.pass && try_filter > RowFilter::Sub { let filter = if last_pass == line.pass || filter <= RowFilter::Sub {
continue; filter
} } else {
try_filter.filter_line(bpp, line.data, last_line, &mut f_buf); RowFilter::None
let size = f_buf.iter().fold(0_u64, |acc, &x| { };
let signed = x as i8; filter.filter_line(bpp, line.data, last_line, &mut f_buf);
acc + i16::from(signed).unsigned_abs() as u64 filtered.extend_from_slice(&f_buf);
}); } else {
if size < best_size { // Heuristic filter selection strategies
best_size = size; let mut best_line = Vec::new();
std::mem::swap(&mut best_line, &mut f_buf); // Avoid vertical filtering on first line of each interlacing pass
let try_filters = if last_pass == line.pass {
RowFilter::STANDARD.iter()
} else {
RowFilter::SINGLE_LINE.iter()
};
match filter {
RowFilter::MinSum => {
// MSAD algorithm mentioned in libpng reference docs
// http://www.libpng.org/pub/png/book/chapter09.html
let mut best_size = usize::MAX;
for try_filter in try_filters {
try_filter.filter_line(bpp, line.data, last_line, &mut f_buf);
let size = f_buf.iter().fold(0, |acc, &x| {
let signed = x as i8;
acc + signed.unsigned_abs() as usize
});
if size < best_size {
best_size = size;
std::mem::swap(&mut best_line, &mut f_buf);
}
} }
} }
filtered.extend_from_slice(&best_line); RowFilter::Entropy => {
} // Shannon entropy algorithm, from LodePNG
_ => { // https://github.com/lvandeve/lodepng
let filter = if last_pass == line.pass || filter <= RowFilter::Sub { let mut best_size = i32::MIN;
filter for try_filter in try_filters {
} else { try_filter.filter_line(bpp, line.data, last_line, &mut f_buf);
RowFilter::None let mut counts = vec![0; 0x100];
}; for &i in f_buf.iter() {
filter.filter_line(bpp, line.data, last_line, &mut f_buf); counts[i as usize] += 1;
filtered.extend_from_slice(&f_buf); }
let size = counts.into_iter().fold(0, |acc, x| {
if x == 0 {
return acc;
}
acc + ilog2i(x)
}) as i32;
if size > best_size {
best_size = size;
std::mem::swap(&mut best_line, &mut f_buf);
}
}
}
_ => unreachable!(),
} }
filtered.extend_from_slice(&best_line);
} }
last_line = line.data; last_line = line.data;
last_pass = line.pass; last_pass = line.pass;
} }
@ -360,3 +388,9 @@ fn write_png_block(key: &[u8], header: &[u8], output: &mut Vec<u8>) {
output.append(&mut header_data); output.append(&mut header_data);
output.extend_from_slice(&crc.to_be_bytes()); output.extend_from_slice(&crc.to_be_bytes());
} }
// Integer approximation for i * log2(i) - much faster than float calculations
fn ilog2i(i: u32) -> u32 {
let log = 32 - i.leading_zeros() - 1;
i * log + ((i - (1 << log)) << 1)
}