pub const PREDICTOR_GROUP_SIZE: usize = 3;
#[inline]
fn trunc_div2(v: i32) -> i32 {
v / 2
}
#[inline]
fn wrap_i16(v: i32) -> i16 {
v as i16
}
pub fn apply_predictor(predictor_raw: u8, row: &[i16], prev_row: Option<&[i16]>) -> Vec<i16> {
apply_predictor_with_stride(predictor_raw, row, prev_row, PREDICTOR_GROUP_SIZE)
}
pub(crate) fn apply_predictor_with_stride(
predictor_raw: u8,
row: &[i16],
prev_row: Option<&[i16]>,
stride: usize,
) -> Vec<i16> {
let count = row.len();
match predictor_raw {
0 => unpredict_none(row),
1 => unpredict_paeth(row, prev_row, count, stride),
2 => unpredict_left(row, count, stride),
3 => unpredict_up(row, prev_row, count),
4 => unpredict_mean(row, prev_row, count, stride),
_ => row.to_vec(),
}
}
fn unpredict_none(data: &[i16]) -> Vec<i16> {
data.to_vec()
}
fn unpredict_left(data: &[i16], count: usize, stride: usize) -> Vec<i16> {
let mut output = vec![0i16; count];
let head = stride.min(count);
output[..head].copy_from_slice(&data[..head]);
for i in stride..count {
output[i] = wrap_i16(data[i] as i32 + output[i - stride] as i32);
}
output
}
fn unpredict_up(data: &[i16], prev_row: Option<&[i16]>, count: usize) -> Vec<i16> {
let mut output = vec![0i16; count];
for i in 0..count {
let up = prev_row.map_or(0, |p| p[i] as i32);
output[i] = wrap_i16(data[i] as i32 + up);
}
output
}
fn unpredict_mean(data: &[i16], prev_row: Option<&[i16]>, count: usize, stride: usize) -> Vec<i16> {
let mut output = vec![0i16; count];
for i in 0..stride.min(count) {
let up = prev_row.map_or(0, |p| p[i] as i32);
output[i] = wrap_i16(data[i] as i32 + up);
}
for i in stride..count {
let left = output[i - stride] as i32;
let up = prev_row.map_or(0, |p| p[i] as i32);
let predictor = trunc_div2(left + up + 1);
output[i] = wrap_i16(data[i] as i32 + predictor);
}
output
}
fn paeth_predictor(left: i32, up: i32, up_left: i32) -> i32 {
let dist_left = (up - up_left).abs();
let dist_up = (left - up_left).abs();
if dist_left <= dist_up { left } else { up }
}
fn unpredict_paeth(
data: &[i16],
prev_row: Option<&[i16]>,
count: usize,
stride: usize,
) -> Vec<i16> {
let mut output = vec![0i16; count];
for i in 0..stride.min(count) {
let up = prev_row.map_or(0, |p| p[i] as i32);
output[i] = wrap_i16(data[i] as i32 + up);
}
let mut i = stride;
while i < count {
let group_size = stride.min(count - i);
let left0 = output[i - stride] as i32;
let up0 = prev_row.map_or(0, |p| p[i] as i32);
let up_left0 = prev_row.map_or(0, |p| p[i - stride] as i32);
let predicted_first = paeth_predictor(left0, up0, up_left0);
let use_left = predicted_first == left0;
for offset in 0..group_size {
let left = output[i + offset - stride] as i32;
let up = prev_row.map_or(0, |p| p[i + offset] as i32);
let base = if use_left { left } else { up };
output[i + offset] = wrap_i16(data[i + offset] as i32 + base);
}
i += PREDICTOR_GROUP_SIZE;
}
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn none_predictor_is_identity() {
let row = vec![1i16, -2, 300, 0, 100];
let result = apply_predictor(0, &row, None);
assert_eq!(result, row);
}
#[test]
fn left_predictor_cumulative_sum() {
let row = vec![1i16, 2, 3, 4, 5, 6];
let result = apply_predictor(2, &row, None);
assert_eq!(result, vec![1, 2, 3, 5, 7, 9]);
}
#[test]
fn up_predictor_adds_prev_row() {
let row = vec![1i16, 2, 3];
let prev = vec![10i16, 20, 30];
let result = apply_predictor(3, &row, Some(&prev));
assert_eq!(result, vec![11, 22, 33]);
}
#[test]
fn up_predictor_no_prev_row_is_identity() {
let row = vec![5i16, 10, 15];
let result = apply_predictor(3, &row, None);
assert_eq!(result, row);
}
#[test]
fn mean_predictor_first_group_uses_up() {
let row = vec![0i16, 0, 0, 2, 4, 6];
let prev = vec![10i16, 20, 30, 40, 50, 60];
let result = apply_predictor(4, &row, Some(&prev));
assert_eq!(result, vec![10, 20, 30, 27, 39, 51]);
}
#[test]
fn paeth_predictor_no_prev_row() {
let row = vec![1i16, 2, 3, 4, 5, 6];
let result = apply_predictor(1, &row, None);
assert_eq!(result, vec![1, 2, 3, 5, 7, 9]);
}
#[test]
fn wrap_on_overflow() {
let row = vec![32767i16, 0, 0, 1, 0, 0];
let result = apply_predictor(2, &row, None);
assert_eq!(result[3], i16::MIN);
}
}