Add entropy filter
This commit is contained in:
parent
dfb6eb7c46
commit
5c25de48f1
2 changed files with 68 additions and 31 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue