use oxideav_vp8::{decode_vp8, DecodeError, Vp8DecodedFrame};
pub fn decode_lossy_rgba(bitstream: &[u8]) -> Result<(u32, u32, Vec<u8>), DecodeError> {
let frame = decode_vp8(bitstream)?;
let (w, h) = (frame.width, frame.height);
let rgba = yuv420_to_rgba(&frame);
Ok((w, h, rgba))
}
fn yuv420_to_rgba(frame: &Vp8DecodedFrame) -> Vec<u8> {
let w = frame.width as usize;
let h = frame.height as usize;
let uv_w = w.div_ceil(2);
let mut rgba = Vec::with_capacity(w * h * 4);
for y in 0..h {
let y_row = y * w;
let uv_row = (y / 2) * uv_w;
for x in 0..w {
let yv = frame.y[y_row + x];
let cu = frame.u[uv_row + (x / 2)];
let cv = frame.v[uv_row + (x / 2)];
let [r, g, b] = ycbcr_to_rgb(yv, cu, cv);
rgba.push(r);
rgba.push(g);
rgba.push(b);
rgba.push(0xff);
}
}
rgba
}
fn ycbcr_to_rgb(y: u8, cb: u8, cv: u8) -> [u8; 3] {
const HALF: i32 = 1 << 15;
let yi = y as i32;
let d = cb as i32 - 128; let e = cv as i32 - 128;
let r = ((yi << 16) + 91_881 * e + HALF) >> 16;
let g = ((yi << 16) - 22_554 * d - 46_802 * e + HALF) >> 16;
let b = ((yi << 16) + 116_130 * d + HALF) >> 16;
[clamp_u8(r), clamp_u8(g), clamp_u8(b)]
}
#[inline]
fn clamp_u8(v: i32) -> u8 {
v.clamp(0, 255) as u8
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ycbcr_neutral_chroma_is_grey() {
for y in [0u8, 1, 64, 127, 128, 200, 255] {
assert_eq!(ycbcr_to_rgb(y, 128, 128), [y, y, y]);
}
}
#[test]
fn ycbcr_pure_primaries_round_to_expected() {
let red = ycbcr_to_rgb(128, 128, 255);
assert_eq!(red[0], 255); assert!(red[1] < 60, "green low for max-Cr, got {}", red[1]);
assert_eq!(red[2], 128);
let blue = ycbcr_to_rgb(128, 255, 128);
assert_eq!(blue[2], 255); assert_eq!(blue[0], 128); assert!(blue[1] < 100, "green low for max-Cb, got {}", blue[1]);
}
#[test]
fn ycbcr_clamps_out_of_range() {
assert_eq!(ycbcr_to_rgb(255, 128, 255)[0], 255);
assert_eq!(ycbcr_to_rgb(0, 128, 0)[0], 0);
}
#[test]
fn yuv420_to_rgba_produces_flat_buffer_with_opaque_alpha() {
let frame = Vp8DecodedFrame {
width: 2,
height: 2,
y: vec![10, 20, 30, 40],
u: vec![128],
v: vec![128],
};
let rgba = yuv420_to_rgba(&frame);
assert_eq!(rgba.len(), 2 * 2 * 4);
assert_eq!(&rgba[0..4], &[10, 10, 10, 0xff]);
assert_eq!(&rgba[4..8], &[20, 20, 20, 0xff]);
assert_eq!(&rgba[8..12], &[30, 30, 30, 0xff]);
assert_eq!(&rgba[12..16], &[40, 40, 40, 0xff]);
}
#[test]
fn yuv420_to_rgba_handles_odd_dimensions() {
let frame = Vp8DecodedFrame {
width: 3,
height: 1,
y: vec![100, 110, 120],
u: vec![128, 128],
v: vec![128, 128],
};
let rgba = yuv420_to_rgba(&frame);
assert_eq!(rgba.len(), 3 * 4);
assert_eq!(&rgba[0..4], &[100, 100, 100, 0xff]);
assert_eq!(&rgba[4..8], &[110, 110, 110, 0xff]);
assert_eq!(&rgba[8..12], &[120, 120, 120, 0xff]);
}
}