use std::collections::VecDeque;
use oxideav_core::Decoder;
use oxideav_core::{
CodecId, CodecParameters, Error, Frame, Packet, Result, TimeBase, VideoFrame, VideoPlane,
};
use oxideav_vp8::decode_frame as decode_vp8_frame;
use crate::demux::{decode_frame_payload, DecodedAlph};
use crate::vp8l;
pub fn decode_webp(buf: &[u8]) -> Result<WebpImage> {
let cursor = std::io::Cursor::new(buf.to_vec());
let mut demuxer = crate::demux::open_boxed(Box::new(cursor))?;
let mut frames = Vec::new();
let streams = demuxer.streams().to_vec();
let params = &streams[0].params;
let w = params.width.unwrap_or(0);
let h = params.height.unwrap_or(0);
let mut dec = WebpDecoder::new(w, h);
loop {
match demuxer.next_packet() {
Ok(pkt) => {
let dur = pkt.duration.unwrap_or(0) as u32;
dec.send_packet(&pkt)?;
loop {
match dec.receive_frame() {
Ok(Frame::Video(vf)) => frames.push(WebpFrame {
width: w,
height: h,
duration_ms: dur,
rgba: vf.planes[0].data.clone(),
}),
Ok(_) => {}
Err(Error::NeedMore) => break,
Err(e) => return Err(e),
}
}
}
Err(Error::Eof) => break,
Err(e) => return Err(e),
}
}
Ok(WebpImage {
width: w,
height: h,
frames,
})
}
#[derive(Debug, Clone)]
pub struct WebpImage {
pub width: u32,
pub height: u32,
pub frames: Vec<WebpFrame>,
}
#[derive(Debug, Clone)]
pub struct WebpFrame {
pub width: u32,
pub height: u32,
pub duration_ms: u32,
pub rgba: Vec<u8>,
}
pub fn make_vp8l_decoder(params: &CodecParameters) -> Result<Box<dyn Decoder>> {
let w = params.width.unwrap_or(0);
let h = params.height.unwrap_or(0);
Ok(Box::new(Vp8lStandalone {
codec_id: params.codec_id.clone(),
width: w,
height: h,
queued: VecDeque::new(),
pending_pts: None,
pending_tb: TimeBase::new(1, 1000),
}))
}
struct Vp8lStandalone {
codec_id: CodecId,
width: u32,
height: u32,
queued: VecDeque<VideoFrame>,
pending_pts: Option<i64>,
pending_tb: TimeBase,
}
impl Decoder for Vp8lStandalone {
fn codec_id(&self) -> &CodecId {
&self.codec_id
}
fn send_packet(&mut self, packet: &Packet) -> Result<()> {
self.pending_pts = packet.pts;
self.pending_tb = packet.time_base;
let img = vp8l::decode(&packet.data)?;
if self.width == 0 {
self.width = img.width;
}
if self.height == 0 {
self.height = img.height;
}
let rgba = img.to_rgba();
let vf = VideoFrame {
pts: self.pending_pts,
planes: vec![VideoPlane {
stride: (img.width as usize) * 4,
data: rgba,
}],
};
self.queued.push_back(vf);
Ok(())
}
fn receive_frame(&mut self) -> Result<Frame> {
self.queued
.pop_front()
.map(Frame::Video)
.ok_or(Error::NeedMore)
}
fn flush(&mut self) -> Result<()> {
Ok(())
}
}
pub struct WebpDecoder {
codec_id: CodecId,
canvas_w: u32,
canvas_h: u32,
canvas: Vec<u8>, queued: VecDeque<VideoFrame>,
pending_pts: Option<i64>,
pending_tb: TimeBase,
first_frame: bool,
prefer_yuva420p: bool,
}
impl WebpDecoder {
pub fn new(w: u32, h: u32) -> Self {
Self {
codec_id: CodecId::new(crate::demux::WEBP_CODEC_ID),
canvas_w: w,
canvas_h: h,
canvas: vec![0; (w as usize) * (h as usize) * 4],
queued: VecDeque::new(),
pending_pts: None,
pending_tb: TimeBase::new(1, 1000),
first_frame: true,
prefer_yuva420p: false,
}
}
pub fn new_yuva420p(w: u32, h: u32) -> Self {
let mut dec = Self::new(w, h);
dec.prefer_yuva420p = true;
dec
}
pub fn set_prefer_yuva420p(&mut self, prefer: bool) {
self.prefer_yuva420p = prefer;
}
}
impl Decoder for WebpDecoder {
fn codec_id(&self) -> &CodecId {
&self.codec_id
}
fn send_packet(&mut self, packet: &Packet) -> Result<()> {
self.pending_pts = packet.pts;
self.pending_tb = packet.time_base;
let payload = decode_frame_payload(&packet.data)?;
if self.canvas_w == 0 || self.canvas_h == 0 {
self.canvas_w = payload.canvas.0;
self.canvas_h = payload.canvas.1;
self.canvas = vec![0; (self.canvas_w as usize) * (self.canvas_h as usize) * 4];
}
let _ = self.first_frame;
if self.prefer_yuva420p
&& !payload.is_vp8l
&& payload.x_offset == 0
&& payload.y_offset == 0
&& payload.width == self.canvas_w
&& payload.height == self.canvas_h
{
if let Some(alph) = payload.alph.as_ref() {
let yuva = decode_vp8_alph_to_yuva420p(
payload.image,
payload.width,
payload.height,
alph,
self.pending_pts,
)?;
self.queued.push_back(yuva);
self.first_frame = false;
return Ok(());
}
}
let tile_rgba = if payload.is_vp8l {
let img = vp8l::decode(payload.image)?;
img.to_rgba()
} else {
decode_vp8_to_rgba(payload.image, payload.width, payload.height)?
};
let tile_rgba = if let Some(alph) = &payload.alph {
overlay_alpha(tile_rgba, payload.width, payload.height, alph)?
} else if !payload.is_vp8l {
set_alpha_opaque(tile_rgba)
} else {
tile_rgba
};
composite(
&mut self.canvas,
self.canvas_w,
self.canvas_h,
&tile_rgba,
payload.x_offset,
payload.y_offset,
payload.width,
payload.height,
payload.blend_with_previous,
);
let vf = VideoFrame {
pts: self.pending_pts,
planes: vec![VideoPlane {
stride: (self.canvas_w as usize) * 4,
data: self.canvas.clone(),
}],
};
self.queued.push_back(vf);
if payload.dispose_to_background {
let x0 = payload.x_offset as usize;
let y0 = payload.y_offset as usize;
let x1 = (x0 + payload.width as usize).min(self.canvas_w as usize);
let y1 = (y0 + payload.height as usize).min(self.canvas_h as usize);
let w = self.canvas_w as usize;
for y in y0..y1 {
for x in x0..x1 {
let i = (y * w + x) * 4;
self.canvas[i] = 0;
self.canvas[i + 1] = 0;
self.canvas[i + 2] = 0;
self.canvas[i + 3] = 0;
}
}
}
self.first_frame = false;
Ok(())
}
fn receive_frame(&mut self) -> Result<Frame> {
self.queued
.pop_front()
.map(Frame::Video)
.ok_or(Error::NeedMore)
}
fn flush(&mut self) -> Result<()> {
Ok(())
}
fn reset(&mut self) -> Result<()> {
if self.canvas_w > 0 && self.canvas_h > 0 {
self.canvas = vec![0; (self.canvas_w as usize) * (self.canvas_h as usize) * 4];
} else {
self.canvas.clear();
}
self.queued.clear();
self.pending_pts = None;
self.first_frame = true;
Ok(())
}
}
const KY_SCALE: i32 = 19077;
const KV_TO_R: i32 = 26149;
const KU_TO_G: i32 = 6419;
const KV_TO_G: i32 = 13320;
const KU_TO_B: i32 = 33050;
const YUV_HALF2: i32 = 1 << 13;
const KR_CST: i32 = -KY_SCALE * 16 - KV_TO_R * 128 + YUV_HALF2;
const KG_CST: i32 = -KY_SCALE * 16 + KU_TO_G * 128 + KV_TO_G * 128 + YUV_HALF2;
const KB_CST: i32 = -KY_SCALE * 16 - KU_TO_B * 128 + YUV_HALF2;
#[inline]
fn yuv_to_r(y: i32, v: i32) -> u8 {
((KY_SCALE * y + KV_TO_R * v + KR_CST) >> 14).clamp(0, 255) as u8
}
#[inline]
fn yuv_to_g(y: i32, u: i32, v: i32) -> u8 {
((KY_SCALE * y - KU_TO_G * u - KV_TO_G * v + KG_CST) >> 14).clamp(0, 255) as u8
}
#[inline]
fn yuv_to_b(y: i32, u: i32) -> u8 {
((KY_SCALE * y + KU_TO_B * u + KB_CST) >> 14).clamp(0, 255) as u8
}
fn decode_vp8_to_rgba(bytes: &[u8], frame_w: u32, frame_h: u32) -> Result<Vec<u8>> {
let vf = decode_vp8_frame(bytes)?;
let w = frame_w as usize;
let h = frame_h as usize;
let y = &vf.planes[0];
let u = &vf.planes[1];
let v = &vf.planes[2];
let mut out = vec![0u8; w * h * 4];
for j in 0..h {
for i in 0..w {
let y_val = y.data[j * y.stride + i] as i32;
let u_val = u.data[(j / 2) * u.stride + (i / 2)] as i32;
let v_val = v.data[(j / 2) * v.stride + (i / 2)] as i32;
let idx = (j * w + i) * 4;
out[idx] = yuv_to_r(y_val, v_val);
out[idx + 1] = yuv_to_g(y_val, u_val, v_val);
out[idx + 2] = yuv_to_b(y_val, u_val);
out[idx + 3] = 0xff;
}
}
Ok(out)
}
fn decode_vp8_alph_to_yuva420p(
bytes: &[u8],
frame_w: u32,
frame_h: u32,
alph: &DecodedAlph<'_>,
pts: Option<i64>,
) -> Result<VideoFrame> {
let vf = decode_vp8_frame(bytes)?;
let w = frame_w as usize;
let h = frame_h as usize;
let cw = w / 2 + (w & 1);
let ch = h / 2 + (h & 1);
let y_in = &vf.planes[0];
let u_in = &vf.planes[1];
let v_in = &vf.planes[2];
let mut y_plane = Vec::with_capacity(w * h);
for j in 0..h {
let row_start = j * y_in.stride;
y_plane.extend_from_slice(&y_in.data[row_start..row_start + w]);
}
let mut u_plane = Vec::with_capacity(cw * ch);
for j in 0..ch {
let row_start = j * u_in.stride;
u_plane.extend_from_slice(&u_in.data[row_start..row_start + cw]);
}
let mut v_plane = Vec::with_capacity(cw * ch);
for j in 0..ch {
let row_start = j * v_in.stride;
v_plane.extend_from_slice(&v_in.data[row_start..row_start + cw]);
}
let alpha = decode_alpha_plane(frame_w, frame_h, alph)?;
if alpha.len() != w * h {
return Err(Error::invalid("WebP: alpha plane size mismatch (Yuva420P)"));
}
Ok(VideoFrame {
pts,
planes: vec![
VideoPlane {
stride: w,
data: y_plane,
},
VideoPlane {
stride: cw,
data: u_plane,
},
VideoPlane {
stride: cw,
data: v_plane,
},
VideoPlane {
stride: w,
data: alpha,
},
],
})
}
fn set_alpha_opaque(mut rgba: Vec<u8>) -> Vec<u8> {
for i in (3..rgba.len()).step_by(4) {
rgba[i] = 0xff;
}
rgba
}
fn overlay_alpha(
mut rgba: Vec<u8>,
width: u32,
height: u32,
alph: &DecodedAlph<'_>,
) -> Result<Vec<u8>> {
let alpha = decode_alpha_plane(width, height, alph)?;
if alpha.len() != (width as usize) * (height as usize) {
return Err(Error::invalid("WebP: alpha plane size mismatch"));
}
for (i, &a) in alpha.iter().enumerate() {
rgba[i * 4 + 3] = a;
}
Ok(rgba)
}
fn decode_alpha_plane(width: u32, height: u32, alph: &DecodedAlph<'_>) -> Result<Vec<u8>> {
let mut plane = match alph.compression {
0 => alph.data.to_vec(),
1 => {
let mut synth = Vec::with_capacity(alph.data.len() + 5);
synth.push(0x2f);
let w = width.saturating_sub(1) & 0x3fff;
let h = height.saturating_sub(1) & 0x3fff;
let packed = w | (h << 14);
synth.extend_from_slice(&packed.to_le_bytes());
synth.extend_from_slice(alph.data);
let img = vp8l::decode(&synth)?;
img.pixels.iter().map(|p| ((p >> 8) & 0xff) as u8).collect()
}
_ => return Err(Error::invalid("WebP: unknown ALPH compression")),
};
unfilter_alpha(&mut plane, width as usize, height as usize, alph.filtering);
Ok(plane)
}
fn unfilter_alpha(plane: &mut [u8], w: usize, h: usize, filt: u8) {
match filt {
0 => {}
1 => {
for y in 0..h {
for x in 1..w {
let i = y * w + x;
let left = plane[i - 1] as u16;
plane[i] = ((plane[i] as u16 + left) & 0xff) as u8;
}
}
}
2 => {
for y in 1..h {
for x in 0..w {
let i = y * w + x;
let top = plane[i - w] as u16;
plane[i] = ((plane[i] as u16 + top) & 0xff) as u8;
}
}
}
3 => {
for y in 0..h {
for x in 0..w {
let i = y * w + x;
let pred = if y == 0 && x == 0 {
0
} else if y == 0 {
plane[i - 1] as i32
} else if x == 0 {
plane[i - w] as i32
} else {
let l = plane[i - 1] as i32;
let t = plane[i - w] as i32;
let tl = plane[i - w - 1] as i32;
(l + t - tl).clamp(0, 255)
};
plane[i] = ((plane[i] as i32 + pred) & 0xff) as u8;
}
}
}
_ => {}
}
}
#[allow(clippy::too_many_arguments)]
fn composite(
canvas: &mut [u8],
canvas_w: u32,
canvas_h: u32,
tile: &[u8],
x: u32,
y: u32,
w: u32,
h: u32,
blend: bool,
) {
let cw = canvas_w as usize;
for j in 0..h as usize {
let cy = y as usize + j;
if cy >= canvas_h as usize {
break;
}
for i in 0..w as usize {
let cx = x as usize + i;
if cx >= canvas_w as usize {
break;
}
let src = &tile[(j * w as usize + i) * 4..(j * w as usize + i) * 4 + 4];
let dst_idx = (cy * cw + cx) * 4;
if blend && src[3] < 0xff {
let sa = src[3] as u32;
let ia = 255 - sa;
for c in 0..3 {
let s = src[c] as u32;
let d = canvas[dst_idx + c] as u32;
canvas[dst_idx + c] = ((s * sa + d * ia + 127) / 255) as u8;
}
let da = canvas[dst_idx + 3] as u32;
canvas[dst_idx + 3] = (sa + ((da * ia) + 127) / 255) as u8;
} else {
canvas[dst_idx..dst_idx + 4].copy_from_slice(src);
}
}
}
}