use core::convert::TryInto;
use crate::{common::BytesPerPixel, Compression};
mod paeth;
#[cfg(feature = "unstable")]
mod simd;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Filter {
NoFilter,
Sub,
Up,
Avg,
Paeth,
Adaptive,
MinEntropy,
}
impl Default for Filter {
fn default() -> Self {
Filter::Adaptive
}
}
impl From<RowFilter> for Filter {
fn from(value: RowFilter) -> Self {
match value {
RowFilter::NoFilter => Filter::NoFilter,
RowFilter::Sub => Filter::Sub,
RowFilter::Up => Filter::Up,
RowFilter::Avg => Filter::Avg,
RowFilter::Paeth => Filter::Paeth,
}
}
}
impl Filter {
pub(crate) fn from_simple(compression: Compression) -> Self {
match compression {
Compression::NoCompression => Filter::NoFilter, Compression::Fastest => Filter::Up, Compression::Fast => Filter::Adaptive,
Compression::Balanced => Filter::Adaptive,
Compression::High => Filter::Adaptive,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum RowFilter {
NoFilter = 0,
Sub = 1,
Up = 2,
Avg = 3,
Paeth = 4,
}
impl Default for RowFilter {
fn default() -> Self {
RowFilter::Up
}
}
impl RowFilter {
pub fn from_u8(n: u8) -> Option<Self> {
match n {
0 => Some(Self::NoFilter),
1 => Some(Self::Sub),
2 => Some(Self::Up),
3 => Some(Self::Avg),
4 => Some(Self::Paeth),
_ => None,
}
}
pub fn from_method(strat: Filter) -> Option<Self> {
match strat {
Filter::NoFilter => Some(Self::NoFilter),
Filter::Sub => Some(Self::Sub),
Filter::Up => Some(Self::Up),
Filter::Avg => Some(Self::Avg),
Filter::Paeth => Some(Self::Paeth),
Filter::Adaptive | Filter::MinEntropy => None,
}
}
}
pub(crate) fn unfilter(
mut filter: RowFilter,
tbpp: BytesPerPixel,
previous: &[u8],
current: &mut [u8],
) {
use self::RowFilter::*;
if previous.is_empty() {
if filter == Paeth {
filter = Sub;
} else if filter == Up {
filter = NoFilter;
}
}
match filter {
NoFilter => {}
Sub => match tbpp {
BytesPerPixel::One => {
current.iter_mut().reduce(|&mut prev, curr| {
*curr = curr.wrapping_add(prev);
curr
});
}
BytesPerPixel::Two => {
let mut prev = [0; 2];
for chunk in current.chunks_exact_mut(2) {
let new_chunk = [
chunk[0].wrapping_add(prev[0]),
chunk[1].wrapping_add(prev[1]),
];
*TryInto::<&mut [u8; 2]>::try_into(chunk).unwrap() = new_chunk;
prev = new_chunk;
}
}
BytesPerPixel::Three => {
let mut prev = [0; 3];
for chunk in current.chunks_exact_mut(3) {
let new_chunk = [
chunk[0].wrapping_add(prev[0]),
chunk[1].wrapping_add(prev[1]),
chunk[2].wrapping_add(prev[2]),
];
*TryInto::<&mut [u8; 3]>::try_into(chunk).unwrap() = new_chunk;
prev = new_chunk;
}
}
BytesPerPixel::Four => {
let mut prev = [0; 4];
for chunk in current.chunks_exact_mut(4) {
let new_chunk = [
chunk[0].wrapping_add(prev[0]),
chunk[1].wrapping_add(prev[1]),
chunk[2].wrapping_add(prev[2]),
chunk[3].wrapping_add(prev[3]),
];
*TryInto::<&mut [u8; 4]>::try_into(chunk).unwrap() = new_chunk;
prev = new_chunk;
}
}
BytesPerPixel::Six => {
let mut prev = [0; 6];
for chunk in current.chunks_exact_mut(6) {
let new_chunk = [
chunk[0].wrapping_add(prev[0]),
chunk[1].wrapping_add(prev[1]),
chunk[2].wrapping_add(prev[2]),
chunk[3].wrapping_add(prev[3]),
chunk[4].wrapping_add(prev[4]),
chunk[5].wrapping_add(prev[5]),
];
*TryInto::<&mut [u8; 6]>::try_into(chunk).unwrap() = new_chunk;
prev = new_chunk;
}
}
BytesPerPixel::Eight => {
let mut prev = [0; 8];
for chunk in current.chunks_exact_mut(8) {
let new_chunk = [
chunk[0].wrapping_add(prev[0]),
chunk[1].wrapping_add(prev[1]),
chunk[2].wrapping_add(prev[2]),
chunk[3].wrapping_add(prev[3]),
chunk[4].wrapping_add(prev[4]),
chunk[5].wrapping_add(prev[5]),
chunk[6].wrapping_add(prev[6]),
chunk[7].wrapping_add(prev[7]),
];
*TryInto::<&mut [u8; 8]>::try_into(chunk).unwrap() = new_chunk;
prev = new_chunk;
}
}
},
Up => {
for (curr, &above) in current.iter_mut().zip(previous) {
*curr = curr.wrapping_add(above);
}
}
Avg if previous.is_empty() => match tbpp {
BytesPerPixel::One => {
current.iter_mut().reduce(|&mut prev, curr| {
*curr = curr.wrapping_add(prev / 2);
curr
});
}
BytesPerPixel::Two => {
let mut prev = [0; 2];
for chunk in current.chunks_exact_mut(2) {
let new_chunk = [
chunk[0].wrapping_add(prev[0] / 2),
chunk[1].wrapping_add(prev[1] / 2),
];
*TryInto::<&mut [u8; 2]>::try_into(chunk).unwrap() = new_chunk;
prev = new_chunk;
}
}
BytesPerPixel::Three => {
let mut prev = [0; 3];
for chunk in current.chunks_exact_mut(3) {
let new_chunk = [
chunk[0].wrapping_add(prev[0] / 2),
chunk[1].wrapping_add(prev[1] / 2),
chunk[2].wrapping_add(prev[2] / 2),
];
*TryInto::<&mut [u8; 3]>::try_into(chunk).unwrap() = new_chunk;
prev = new_chunk;
}
}
BytesPerPixel::Four => {
let mut prev = [0; 4];
for chunk in current.chunks_exact_mut(4) {
let new_chunk = [
chunk[0].wrapping_add(prev[0] / 2),
chunk[1].wrapping_add(prev[1] / 2),
chunk[2].wrapping_add(prev[2] / 2),
chunk[3].wrapping_add(prev[3] / 2),
];
*TryInto::<&mut [u8; 4]>::try_into(chunk).unwrap() = new_chunk;
prev = new_chunk;
}
}
BytesPerPixel::Six => {
let mut prev = [0; 6];
for chunk in current.chunks_exact_mut(6) {
let new_chunk = [
chunk[0].wrapping_add(prev[0] / 2),
chunk[1].wrapping_add(prev[1] / 2),
chunk[2].wrapping_add(prev[2] / 2),
chunk[3].wrapping_add(prev[3] / 2),
chunk[4].wrapping_add(prev[4] / 2),
chunk[5].wrapping_add(prev[5] / 2),
];
*TryInto::<&mut [u8; 6]>::try_into(chunk).unwrap() = new_chunk;
prev = new_chunk;
}
}
BytesPerPixel::Eight => {
let mut prev = [0; 8];
for chunk in current.chunks_exact_mut(8) {
let new_chunk = [
chunk[0].wrapping_add(prev[0] / 2),
chunk[1].wrapping_add(prev[1] / 2),
chunk[2].wrapping_add(prev[2] / 2),
chunk[3].wrapping_add(prev[3] / 2),
chunk[4].wrapping_add(prev[4] / 2),
chunk[5].wrapping_add(prev[5] / 2),
chunk[6].wrapping_add(prev[6] / 2),
chunk[7].wrapping_add(prev[7] / 2),
];
*TryInto::<&mut [u8; 8]>::try_into(chunk).unwrap() = new_chunk;
prev = new_chunk;
}
}
},
Avg => match tbpp {
BytesPerPixel::One => {
let mut lprev = [0; 1];
for (chunk, above) in current.chunks_exact_mut(1).zip(previous.chunks_exact(1)) {
let new_chunk =
[chunk[0].wrapping_add(((above[0] as u16 + lprev[0] as u16) / 2) as u8)];
*TryInto::<&mut [u8; 1]>::try_into(chunk).unwrap() = new_chunk;
lprev = new_chunk;
}
}
BytesPerPixel::Two => {
let mut lprev = [0; 2];
for (chunk, above) in current.chunks_exact_mut(2).zip(previous.chunks_exact(2)) {
let new_chunk = [
chunk[0].wrapping_add(((above[0] as u16 + lprev[0] as u16) / 2) as u8),
chunk[1].wrapping_add(((above[1] as u16 + lprev[1] as u16) / 2) as u8),
];
*TryInto::<&mut [u8; 2]>::try_into(chunk).unwrap() = new_chunk;
lprev = new_chunk;
}
}
BytesPerPixel::Three => {
let mut lprev = [0; 3];
for (chunk, above) in current.chunks_exact_mut(3).zip(previous.chunks_exact(3)) {
let new_chunk = [
chunk[0].wrapping_add(((above[0] as u16 + lprev[0] as u16) / 2) as u8),
chunk[1].wrapping_add(((above[1] as u16 + lprev[1] as u16) / 2) as u8),
chunk[2].wrapping_add(((above[2] as u16 + lprev[2] as u16) / 2) as u8),
];
*TryInto::<&mut [u8; 3]>::try_into(chunk).unwrap() = new_chunk;
lprev = new_chunk;
}
}
BytesPerPixel::Four => {
let mut lprev = [0; 4];
for (chunk, above) in current.chunks_exact_mut(4).zip(previous.chunks_exact(4)) {
let new_chunk = [
chunk[0].wrapping_add(((above[0] as u16 + lprev[0] as u16) / 2) as u8),
chunk[1].wrapping_add(((above[1] as u16 + lprev[1] as u16) / 2) as u8),
chunk[2].wrapping_add(((above[2] as u16 + lprev[2] as u16) / 2) as u8),
chunk[3].wrapping_add(((above[3] as u16 + lprev[3] as u16) / 2) as u8),
];
*TryInto::<&mut [u8; 4]>::try_into(chunk).unwrap() = new_chunk;
lprev = new_chunk;
}
}
BytesPerPixel::Six => {
let mut lprev = [0; 6];
for (chunk, above) in current.chunks_exact_mut(6).zip(previous.chunks_exact(6)) {
let new_chunk = [
chunk[0].wrapping_add(((above[0] as u16 + lprev[0] as u16) / 2) as u8),
chunk[1].wrapping_add(((above[1] as u16 + lprev[1] as u16) / 2) as u8),
chunk[2].wrapping_add(((above[2] as u16 + lprev[2] as u16) / 2) as u8),
chunk[3].wrapping_add(((above[3] as u16 + lprev[3] as u16) / 2) as u8),
chunk[4].wrapping_add(((above[4] as u16 + lprev[4] as u16) / 2) as u8),
chunk[5].wrapping_add(((above[5] as u16 + lprev[5] as u16) / 2) as u8),
];
*TryInto::<&mut [u8; 6]>::try_into(chunk).unwrap() = new_chunk;
lprev = new_chunk;
}
}
BytesPerPixel::Eight => {
let mut lprev = [0; 8];
for (chunk, above) in current.chunks_exact_mut(8).zip(previous.chunks_exact(8)) {
let new_chunk = [
chunk[0].wrapping_add(((above[0] as u16 + lprev[0] as u16) / 2) as u8),
chunk[1].wrapping_add(((above[1] as u16 + lprev[1] as u16) / 2) as u8),
chunk[2].wrapping_add(((above[2] as u16 + lprev[2] as u16) / 2) as u8),
chunk[3].wrapping_add(((above[3] as u16 + lprev[3] as u16) / 2) as u8),
chunk[4].wrapping_add(((above[4] as u16 + lprev[4] as u16) / 2) as u8),
chunk[5].wrapping_add(((above[5] as u16 + lprev[5] as u16) / 2) as u8),
chunk[6].wrapping_add(((above[6] as u16 + lprev[6] as u16) / 2) as u8),
chunk[7].wrapping_add(((above[7] as u16 + lprev[7] as u16) / 2) as u8),
];
*TryInto::<&mut [u8; 8]>::try_into(chunk).unwrap() = new_chunk;
lprev = new_chunk;
}
}
},
Paeth => paeth::unfilter(tbpp, previous, current),
}
}
fn filter_internal(
method: RowFilter,
bpp: usize,
len: usize,
previous: &[u8],
current: &[u8],
output: &mut [u8],
) -> RowFilter {
use self::RowFilter::*;
const CHUNK_SIZE: usize = 32;
match method {
NoFilter => {
output.copy_from_slice(current);
NoFilter
}
Sub => {
let mut out_chunks = output[bpp..].chunks_exact_mut(CHUNK_SIZE);
let mut cur_chunks = current[bpp..].chunks_exact(CHUNK_SIZE);
let mut prev_chunks = current[..len - bpp].chunks_exact(CHUNK_SIZE);
for ((out, cur), prev) in (&mut out_chunks).zip(&mut cur_chunks).zip(&mut prev_chunks) {
for i in 0..CHUNK_SIZE {
out[i] = cur[i].wrapping_sub(prev[i]);
}
}
for ((out, cur), &prev) in out_chunks
.into_remainder()
.iter_mut()
.zip(cur_chunks.remainder())
.zip(prev_chunks.remainder())
{
*out = cur.wrapping_sub(prev);
}
output[..bpp].copy_from_slice(¤t[..bpp]);
Sub
}
Up => {
let mut out_chunks = output.chunks_exact_mut(CHUNK_SIZE);
let mut cur_chunks = current.chunks_exact(CHUNK_SIZE);
let mut prev_chunks = previous.chunks_exact(CHUNK_SIZE);
for ((out, cur), prev) in (&mut out_chunks).zip(&mut cur_chunks).zip(&mut prev_chunks) {
for i in 0..CHUNK_SIZE {
out[i] = cur[i].wrapping_sub(prev[i]);
}
}
for ((out, cur), &prev) in out_chunks
.into_remainder()
.iter_mut()
.zip(cur_chunks.remainder())
.zip(prev_chunks.remainder())
{
*out = cur.wrapping_sub(prev);
}
Up
}
Avg => {
let mut out_chunks = output[bpp..].chunks_exact_mut(CHUNK_SIZE);
let mut cur_chunks = current[bpp..].chunks_exact(CHUNK_SIZE);
let mut cur_minus_bpp_chunks = current[..len - bpp].chunks_exact(CHUNK_SIZE);
let mut prev_chunks = previous[bpp..].chunks_exact(CHUNK_SIZE);
for (((out, cur), cur_minus_bpp), prev) in (&mut out_chunks)
.zip(&mut cur_chunks)
.zip(&mut cur_minus_bpp_chunks)
.zip(&mut prev_chunks)
{
for i in 0..CHUNK_SIZE {
out[i] = cur[i].wrapping_sub(
(cur_minus_bpp[i] & prev[i]) + ((cur_minus_bpp[i] ^ prev[i]) >> 1),
);
}
}
for (((out, cur), &cur_minus_bpp), &prev) in out_chunks
.into_remainder()
.iter_mut()
.zip(cur_chunks.remainder())
.zip(cur_minus_bpp_chunks.remainder())
.zip(prev_chunks.remainder())
{
*out = cur.wrapping_sub((cur_minus_bpp & prev) + ((cur_minus_bpp ^ prev) >> 1));
}
for i in 0..bpp {
output[i] = current[i].wrapping_sub(previous[i] / 2);
}
Avg
}
Paeth => {
let mut out_chunks = output[bpp..].chunks_exact_mut(CHUNK_SIZE);
let mut cur_chunks = current[bpp..].chunks_exact(CHUNK_SIZE);
let mut a_chunks = current[..len - bpp].chunks_exact(CHUNK_SIZE);
let mut b_chunks = previous[bpp..].chunks_exact(CHUNK_SIZE);
let mut c_chunks = previous[..len - bpp].chunks_exact(CHUNK_SIZE);
for ((((out, cur), a), b), c) in (&mut out_chunks)
.zip(&mut cur_chunks)
.zip(&mut a_chunks)
.zip(&mut b_chunks)
.zip(&mut c_chunks)
{
for i in 0..CHUNK_SIZE {
out[i] = cur[i].wrapping_sub(paeth::filter_paeth_fpnge(a[i], b[i], c[i]));
}
}
for ((((out, cur), &a), &b), &c) in out_chunks
.into_remainder()
.iter_mut()
.zip(cur_chunks.remainder())
.zip(a_chunks.remainder())
.zip(b_chunks.remainder())
.zip(c_chunks.remainder())
{
*out = cur.wrapping_sub(paeth::filter_paeth_fpnge(a, b, c));
}
for i in 0..bpp {
output[i] = current[i].wrapping_sub(paeth::filter_paeth_fpnge(0, previous[i], 0));
}
Paeth
}
}
}
fn adaptive_filter(
f: impl Fn(&[u8]) -> u64,
bpp: usize,
len: usize,
previous: &[u8],
current: &[u8],
output: &mut [u8],
) -> RowFilter {
use RowFilter::*;
let mut min_cost: u64 = u64::MAX;
let mut filter_choice = RowFilter::NoFilter;
for &filter in [Up, Sub, Avg, Paeth].iter() {
filter_internal(filter, bpp, len, previous, current, output);
let cost = f(output);
if cost <= min_cost {
min_cost = cost;
filter_choice = filter;
if cost == 0 {
return filter_choice;
}
}
}
if filter_choice != Paeth {
filter_internal(filter_choice, bpp, len, previous, current, output);
}
filter_choice
}
pub(crate) fn filter(
method: Filter,
bpp: BytesPerPixel,
previous: &[u8],
current: &[u8],
output: &mut [u8],
) -> RowFilter {
let bpp = bpp.into_usize();
let len = current.len();
match method {
Filter::Adaptive => adaptive_filter(sum_buffer, bpp, len, previous, current, output),
Filter::MinEntropy => adaptive_filter(entropy, bpp, len, previous, current, output),
_ => {
let filter = RowFilter::from_method(method).unwrap();
filter_internal(filter, bpp, len, previous, current, output)
}
}
}
fn ilog2i(i: u32) -> u32 {
let log = 32 - i.leading_zeros() - 1;
i * log + ((i - (1 << log)) << 1)
}
fn entropy(buf: &[u8]) -> u64 {
let mut counts = [[0_u32; 256]; 4];
let mut total = 0;
let mut chunks = buf.chunks_exact(8);
for chunk in &mut chunks {
if chunk == [0; 8] {
continue;
}
for j in 0..2 {
counts[0][chunk[j * 4] as usize] += 1;
counts[1][chunk[1 + j * 4] as usize] += 1;
counts[2][chunk[2 + j * 4] as usize] += 1;
counts[3][chunk[3 + j * 4] as usize] += 1;
}
total += 8;
}
for &lit in chunks.remainder() {
counts[0][lit as usize] += 1;
total += 1;
}
if counts[0][0] == total {
return 0;
}
#[allow(clippy::needless_range_loop)]
for i in 0..256 {
counts[0][i] += counts[1][i] + counts[2][i] + counts[3][i];
}
let mut entropy = ilog2i(total);
for &count in &counts[0] {
if count > 0 {
entropy = entropy.saturating_sub(ilog2i(count));
}
}
entropy as u64
}
fn sum_buffer(buf: &[u8]) -> u64 {
const CHUNK_SIZE: usize = 32;
let mut buf_chunks = buf.chunks_exact(CHUNK_SIZE);
let mut sum = 0_u64;
for chunk in &mut buf_chunks {
let mut acc = 0;
for &b in chunk {
acc += u64::from((b as i8).unsigned_abs());
}
sum = sum.saturating_add(acc);
}
let mut acc = 0;
for &b in buf_chunks.remainder() {
acc += u64::from((b as i8).unsigned_abs());
}
sum.saturating_add(acc)
}
#[cfg(test)]
mod test {
use super::*;
use core::iter;
#[test]
fn roundtrip() {
const LEN: u8 = 240;
let previous: Vec<_> = iter::repeat(1).take(LEN.into()).collect();
let current: Vec<_> = (0..LEN).collect();
let expected = current.clone();
let roundtrip = |kind: RowFilter, bpp: BytesPerPixel| {
let mut output = vec![0; LEN.into()];
filter(kind.into(), bpp, &previous, ¤t, &mut output);
unfilter(kind, bpp, &previous, &mut output);
assert_eq!(
output, expected,
"Filtering {:?} with {:?} does not roundtrip",
bpp, kind
);
};
let filters = [
RowFilter::NoFilter,
RowFilter::Sub,
RowFilter::Up,
RowFilter::Avg,
RowFilter::Paeth,
];
let bpps = [
BytesPerPixel::One,
BytesPerPixel::Two,
BytesPerPixel::Three,
BytesPerPixel::Four,
BytesPerPixel::Six,
BytesPerPixel::Eight,
];
for &filter in filters.iter() {
for &bpp in bpps.iter() {
roundtrip(filter, bpp);
}
}
}
#[test]
fn roundtrip_ascending_previous_line() {
const LEN: u8 = 240;
let previous: Vec<_> = (0..LEN).collect();
let current: Vec<_> = (0..LEN).collect();
let expected = current.clone();
let roundtrip = |kind: RowFilter, bpp: BytesPerPixel| {
let mut output = vec![0; LEN.into()];
filter(kind.into(), bpp, &previous, ¤t, &mut output);
unfilter(kind, bpp, &previous, &mut output);
assert_eq!(
output, expected,
"Filtering {:?} with {:?} does not roundtrip",
bpp, kind
);
};
let filters = [
RowFilter::NoFilter,
RowFilter::Sub,
RowFilter::Up,
RowFilter::Avg,
RowFilter::Paeth,
];
let bpps = [
BytesPerPixel::One,
BytesPerPixel::Two,
BytesPerPixel::Three,
BytesPerPixel::Four,
BytesPerPixel::Six,
BytesPerPixel::Eight,
];
for &filter in filters.iter() {
for &bpp in bpps.iter() {
roundtrip(filter, bpp);
}
}
}
#[test]
fn sum_buffer_test() {
let sum = (0..=128).sum::<u64>() + (1..=127).sum::<u64>();
let buf: Vec<u8> = (0_u8..=255).collect();
assert_eq!(sum, crate::filter::sum_buffer(&buf));
}
}