use oxideav_core::{Error, Result};
use super::bit_reader::BitReader;
use super::decode_image_stream;
#[derive(Debug)]
pub enum Transform {
Predictor {
tile_bits: u32,
sub_image: Vec<u32>,
sub_w: u32,
#[allow(dead_code)]
sub_h: u32,
xsize: u32,
},
Color {
tile_bits: u32,
sub_image: Vec<u32>,
sub_w: u32,
#[allow(dead_code)]
sub_h: u32,
xsize: u32,
},
SubtractGreen,
ColorIndex {
colors: Vec<u32>,
bits_per_pixel: u32,
orig_xsize: u32,
},
}
impl Transform {
pub fn read(br: &mut BitReader<'_>, xsize: u32, ysize: u32) -> Result<Self> {
let ty = br.read_bits(2)?;
match ty {
0 => {
let tile_bits = br.read_bits(3)? + 2;
let sub_w = subsampled_size(xsize, tile_bits);
let sub_h = subsampled_size(ysize, tile_bits);
let sub = decode_image_stream(br, sub_w, sub_h, false)?;
Ok(Transform::Predictor {
tile_bits,
sub_image: sub,
sub_w,
sub_h,
xsize,
})
}
1 => {
let tile_bits = br.read_bits(3)? + 2;
let sub_w = subsampled_size(xsize, tile_bits);
let sub_h = subsampled_size(ysize, tile_bits);
let sub = decode_image_stream(br, sub_w, sub_h, false)?;
Ok(Transform::Color {
tile_bits,
sub_image: sub,
sub_w,
sub_h,
xsize,
})
}
2 => Ok(Transform::SubtractGreen),
3 => {
let num_colors = br.read_bits(8)? + 1;
let mut colors_raw = decode_image_stream(br, num_colors, 1, false)?;
for i in 1..colors_raw.len() {
colors_raw[i] = add_argb(colors_raw[i], colors_raw[i - 1]);
}
let bits_per_pixel = if num_colors <= 2 {
1
} else if num_colors <= 4 {
2
} else if num_colors <= 16 {
4
} else {
8
};
Ok(Transform::ColorIndex {
colors: colors_raw,
bits_per_pixel,
orig_xsize: xsize,
})
}
_ => Err(Error::invalid("VP8L: invalid transform type")),
}
}
pub fn image_width_or_default(&self, default_w: u32) -> u32 {
match self {
Transform::ColorIndex {
bits_per_pixel,
orig_xsize,
..
} => {
let pack = 8 / *bits_per_pixel;
(orig_xsize + pack - 1) / pack
}
_ => default_w,
}
}
pub fn output_width(&self, input_w: u32) -> u32 {
match self {
Transform::ColorIndex { orig_xsize, .. } => *orig_xsize,
_ => input_w,
}
}
pub fn apply(&self, pixels: &[u32], width: u32, height: u32) -> Result<Vec<u32>> {
match self {
Transform::Predictor {
tile_bits,
sub_image,
sub_w,
..
} => Ok(apply_predictor(
pixels, width, height, *tile_bits, sub_image, *sub_w,
)),
Transform::Color {
tile_bits,
sub_image,
sub_w,
..
} => Ok(apply_color_transform(
pixels, width, height, *tile_bits, sub_image, *sub_w,
)),
Transform::SubtractGreen => Ok(apply_subtract_green(pixels)),
Transform::ColorIndex {
colors,
bits_per_pixel,
orig_xsize,
} => apply_color_index(pixels, width, height, colors, *bits_per_pixel, *orig_xsize),
}
}
}
fn subsampled_size(size: u32, bits: u32) -> u32 {
(size + (1 << bits) - 1) >> bits
}
#[inline]
fn add_argb(a: u32, b: u32) -> u32 {
let masked_sum = (a & 0x7f7f_7f7f).wrapping_add(b & 0x7f7f_7f7f);
masked_sum ^ ((a ^ b) & 0x8080_8080)
}
fn apply_predictor(
residual: &[u32],
width: u32,
height: u32,
tile_bits: u32,
sub_image: &[u32],
sub_w: u32,
) -> Vec<u32> {
let pixel_count = residual.len();
let mut out: Vec<u32> = Vec::with_capacity(pixel_count);
let w_usize = width as usize;
let sub_w_usize = sub_w as usize;
if height > 0 {
out.push(add_argb(residual[0], 0xff00_0000));
for x in 1..width as usize {
let pred = out[x - 1];
out.push(add_argb(residual[x], pred));
}
}
for y in 1..height {
let ty = (y >> tile_bits) as usize;
let row_base = (y * width) as usize;
let pred0 = out[row_base - w_usize];
out.push(add_argb(residual[row_base], pred0));
let mut x: u32 = 1;
while x < width {
let tx = (x >> tile_bits) as usize;
let mode = (sub_image[ty * sub_w_usize + tx] >> 8) & 0x0f;
let tile_end = (((x >> tile_bits) + 1) << tile_bits).min(width);
apply_predictor_tile_row(&mut out, residual, w_usize, y as usize, x, tile_end, mode);
x = tile_end;
}
}
out
}
#[inline]
fn apply_predictor_tile_row(
out: &mut Vec<u32>,
residual: &[u32],
w: usize,
y: usize,
x_start: u32,
x_end: u32,
mode: u32,
) {
let row_base = y * w;
match mode {
0 => {
for x in x_start..x_end {
let idx = row_base + x as usize;
out.push(add_argb(residual[idx], 0xff00_0000));
}
}
1 => {
for x in x_start..x_end {
let idx = row_base + x as usize;
let pred = out[idx - 1];
out.push(add_argb(residual[idx], pred));
}
}
2 => {
for x in x_start..x_end {
let idx = row_base + x as usize;
let pred = out[idx - w];
out.push(add_argb(residual[idx], pred));
}
}
4 => {
for x in x_start..x_end {
let idx = row_base + x as usize;
let pred = out[idx - w - 1];
out.push(add_argb(residual[idx], pred));
}
}
_ => {
for x in x_start..x_end {
let idx = row_base + x as usize;
let pred = predict_argb(out, w, x as usize, y, mode);
out.push(add_argb(residual[idx], pred));
}
}
}
}
fn predict_argb(out: &[u32], w: usize, x: usize, y: usize, mode: u32) -> u32 {
let l = out[y * w + x - 1];
let t = out[(y - 1) * w + x];
let tl = out[(y - 1) * w + x - 1];
let tr = if x + 1 < w {
out[(y - 1) * w + x + 1]
} else {
out[y * w]
};
match mode {
0 => 0xff00_0000, 1 => l,
2 => t,
3 => tr,
4 => tl,
5 => avg3(l, tr, t),
6 => avg2(l, tl),
7 => avg2(l, t),
8 => avg2(tl, t),
9 => avg2(t, tr),
10 => avg2(avg2(l, tl), avg2(t, tr)),
11 => select_argb(l, t, tl),
12 => clamp_add_sub_argb(l, t, tl),
13 => clamp_add_sub_half_argb(avg2(l, t), tl),
_ => 0xff00_0000,
}
}
#[inline]
fn avg2(a: u32, b: u32) -> u32 {
(a & b).wrapping_add(((a ^ b) & 0xfefe_fefe) >> 1)
}
fn avg3(a: u32, b: u32, c: u32) -> u32 {
avg2(a, avg2(b, c))
}
#[inline]
fn select_argb(l: u32, t: u32, tl: u32) -> u32 {
let lb = l.to_le_bytes();
let tb = t.to_le_bytes();
let tlb = tl.to_le_bytes();
let mut dl: u32 = 0;
let mut dt: u32 = 0;
for c in 0..4 {
dl += tb[c].abs_diff(tlb[c]) as u32;
dt += lb[c].abs_diff(tlb[c]) as u32;
}
if dl < dt {
l
} else {
t
}
}
#[inline]
fn clamp_add_sub_argb(l: u32, t: u32, tl: u32) -> u32 {
let lb = l.to_le_bytes();
let tb = t.to_le_bytes();
let tlb = tl.to_le_bytes();
let mut out = [0u8; 4];
for c in 0..4 {
let v = (lb[c] as i32) + (tb[c] as i32) - (tlb[c] as i32);
out[c] = v.clamp(0, 255) as u8;
}
u32::from_le_bytes(out)
}
#[inline]
fn clamp_add_sub_half_argb(a: u32, b: u32) -> u32 {
let ab = a.to_le_bytes();
let bb = b.to_le_bytes();
let mut out = [0u8; 4];
for c in 0..4 {
let av = ab[c] as i32;
let bv = bb[c] as i32;
let v = av + (av - bv) / 2;
out[c] = v.clamp(0, 255) as u8;
}
u32::from_le_bytes(out)
}
fn apply_color_transform(
pixels: &[u32],
width: u32,
height: u32,
tile_bits: u32,
sub_image: &[u32],
sub_w: u32,
) -> Vec<u32> {
let mut out = Vec::with_capacity(pixels.len());
let sub_w_usize = sub_w as usize;
for y in 0..height {
let ty = (y >> tile_bits) as usize;
let row_base = (y * width) as usize;
let mut x: u32 = 0;
while x < width {
let tx = (x >> tile_bits) as usize;
let coeffs = sub_image[ty * sub_w_usize + tx];
let r2b = ((coeffs >> 16) & 0xff) as i8 as i32;
let g2b = ((coeffs >> 8) & 0xff) as i8 as i32;
let g2r = (coeffs & 0xff) as i8 as i32;
let tile_end = (((x >> tile_bits) + 1) << tile_bits).min(width);
for xi in x..tile_end {
let p = pixels[row_base + xi as usize];
let a = (p >> 24) & 0xff;
let mut r = ((p >> 16) & 0xff) as i32;
let g = ((p >> 8) & 0xff) as i32;
let mut b = (p & 0xff) as i32;
r = (r + ((g2r * (g as i8 as i32)) >> 5)) & 0xff;
b = (b + ((g2b * (g as i8 as i32)) >> 5)) & 0xff;
b = (b + ((r2b * (r as i8 as i32)) >> 5)) & 0xff;
let argb = (a << 24)
| ((r as u32 & 0xff) << 16)
| ((g as u32 & 0xff) << 8)
| (b as u32 & 0xff);
out.push(argb);
}
x = tile_end;
}
}
out
}
fn apply_subtract_green(pixels: &[u32]) -> Vec<u32> {
pixels
.iter()
.map(|&p| {
let g = (p >> 8) & 0xff;
let g_rb = (g << 16) | g;
let masked_sum = (p & 0x7f7f_7f7f).wrapping_add(g_rb & 0x7f7f_7f7f);
masked_sum ^ ((p ^ g_rb) & 0x8080_8080)
})
.collect()
}
fn apply_color_index(
packed: &[u32],
width: u32,
_height: u32,
colors: &[u32],
bits_per_pixel: u32,
orig_xsize: u32,
) -> Result<Vec<u32>> {
let rows = packed.len() / width as usize;
let mut out = Vec::with_capacity((orig_xsize as usize) * rows.max(1));
let w = width as usize;
let ox_end = orig_xsize as usize;
match bits_per_pixel {
8 => apply_color_index_pack1(&mut out, packed, w, rows, colors, ox_end),
4 => apply_color_index_packed::<2, 4, 0x0f>(&mut out, packed, w, rows, colors, ox_end),
2 => apply_color_index_packed::<4, 2, 0x03>(&mut out, packed, w, rows, colors, ox_end),
1 => apply_color_index_packed::<8, 1, 0x01>(&mut out, packed, w, rows, colors, ox_end),
_ => return Err(Error::invalid("VP8L: invalid bits_per_pixel")),
}
Ok(out)
}
#[inline]
fn apply_color_index_pack1(
out: &mut Vec<u32>,
packed: &[u32],
width: usize,
rows: usize,
colors: &[u32],
_ox_end: usize,
) {
let num_colors = colors.len();
for y in 0..rows {
let row_base = y * width;
for xp in 0..width {
let p = packed[row_base + xp];
let idx = ((p >> 8) & 0xff) as usize;
let color = if idx < num_colors { colors[idx] } else { 0 };
out.push(color);
}
}
}
#[inline]
fn apply_color_index_packed<const PACK: usize, const BITS: u32, const MASK: u32>(
out: &mut Vec<u32>,
packed: &[u32],
width: usize,
rows: usize,
colors: &[u32],
ox_end: usize,
) {
let num_colors = colors.len();
let bulk_end = ox_end / PACK;
for y in 0..rows {
let row_base = y * width;
for xp in 0..bulk_end {
let g = (packed[row_base + xp] >> 8) & 0xff;
for sub in 0..PACK {
let idx = ((g >> (BITS * sub as u32)) & MASK) as usize;
let color = if idx < num_colors { colors[idx] } else { 0 };
out.push(color);
}
}
for xp in bulk_end..width {
let g = (packed[row_base + xp] >> 8) & 0xff;
for sub in 0..PACK {
let ox = xp * PACK + sub;
if ox >= ox_end {
break;
}
let idx = ((g >> (BITS * sub as u32)) & MASK) as usize;
let color = if idx < num_colors { colors[idx] } else { 0 };
out.push(color);
}
}
}
}