use crate::WebpError;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Vp8FreqDeltas {
pub y_dc_delta: i8,
pub y2_dc_delta: i8,
pub y2_ac_delta: i8,
pub uv_dc_delta: i8,
pub uv_ac_delta: i8,
}
pub fn quality_to_qindex(quality: f32) -> u8 {
if quality.is_nan() {
return 127;
}
let q = quality.clamp(0.0, 100.0);
let qi = ((100.0 - q) * 1.27).round();
qi.clamp(0.0, 127.0) as u8
}
#[cfg(feature = "registry")]
use oxideav_core::{
time::TimeBase, CodecId, CodecParameters, Encoder, Error as CoreError, Frame, MediaType,
Packet, Result as CoreResult,
};
#[cfg(feature = "registry")]
use std::collections::VecDeque;
#[cfg(feature = "registry")]
pub fn make_encoder(params: &CodecParameters) -> CoreResult<Box<dyn Encoder>> {
make_encoder_with_qindex(params, 32)
}
#[cfg(feature = "registry")]
pub fn make_encoder_with_quality(
params: &CodecParameters,
quality: f32,
) -> CoreResult<Box<dyn Encoder>> {
make_encoder_with_qindex(params, quality_to_qindex(quality))
}
#[cfg(feature = "registry")]
pub fn make_encoder_with_qindex(
params: &CodecParameters,
qindex: u8,
) -> CoreResult<Box<dyn Encoder>> {
let inner = oxideav_vp8::encoder::make_encoder_with_qindex(params, qindex)?;
let width = params
.width
.ok_or_else(|| CoreError::invalid("webp_vp8 encoder: missing width"))?;
let height = params
.height
.ok_or_else(|| CoreError::invalid("webp_vp8 encoder: missing height"))?;
if width == 0 || height == 0 {
return Err(CoreError::invalid(
"webp_vp8 encoder: width and height must be positive",
));
}
let mut output_params = params.clone();
output_params.media_type = MediaType::Video;
output_params.codec_id = CodecId::new(crate::CODEC_ID_VP8);
output_params.width = Some(width);
output_params.height = Some(height);
let time_base = params
.frame_rate
.map_or(TimeBase::new(1, 1_000), |r| TimeBase::new(r.den, r.num));
Ok(Box::new(WebpVp8LossyEncoder {
inner,
output_params,
time_base,
pending: VecDeque::new(),
flushed: false,
}))
}
#[cfg(feature = "registry")]
pub fn make_encoder_with_qindex_and_freq_deltas(
params: &CodecParameters,
qindex: u8,
_deltas: Vp8FreqDeltas,
) -> CoreResult<Box<dyn Encoder>> {
make_encoder_with_qindex(params, qindex)
}
#[cfg(feature = "registry")]
pub fn make_encoder_with_quality_and_freq_deltas(
params: &CodecParameters,
quality: f32,
_deltas: Vp8FreqDeltas,
) -> CoreResult<Box<dyn Encoder>> {
make_encoder_with_quality(params, quality)
}
#[cfg(feature = "registry")]
pub(crate) struct WebpVp8LossyEncoder {
inner: Box<dyn Encoder>,
output_params: CodecParameters,
time_base: TimeBase,
pending: VecDeque<Packet>,
flushed: bool,
}
#[cfg(feature = "registry")]
impl std::fmt::Debug for WebpVp8LossyEncoder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WebpVp8LossyEncoder")
.field("width", &self.output_params.width)
.field("height", &self.output_params.height)
.field("pending", &self.pending.len())
.field("flushed", &self.flushed)
.finish()
}
}
#[cfg(feature = "registry")]
impl Encoder for WebpVp8LossyEncoder {
fn codec_id(&self) -> &CodecId {
&self.output_params.codec_id
}
fn output_params(&self) -> &CodecParameters {
&self.output_params
}
fn send_frame(&mut self, frame: &Frame) -> CoreResult<()> {
self.inner.send_frame(frame)?;
let vp8_pkt = self.inner.receive_packet()?;
let width = self.output_params.width.unwrap_or(0);
let height = self.output_params.height.unwrap_or(0);
let webp_bytes = crate::build::build_webp_file(
&vp8_pkt.data,
crate::build::ImageKind::Lossy,
width,
height,
)
.map_err(|e| CoreError::invalid(format!("webp_vp8 encoder: container framing: {e}")))?;
let mut pkt = Packet::new(0, self.time_base, webp_bytes);
pkt.pts = vp8_pkt.pts;
pkt.dts = vp8_pkt.pts;
pkt.flags.keyframe = true;
self.pending.push_back(pkt);
Ok(())
}
fn receive_packet(&mut self) -> CoreResult<Packet> {
if let Some(p) = self.pending.pop_front() {
return Ok(p);
}
if self.flushed {
Err(CoreError::Eof)
} else {
Err(CoreError::NeedMore)
}
}
fn flush(&mut self) -> CoreResult<()> {
self.flushed = true;
let _ = self.inner.flush();
Ok(())
}
}
pub fn unsupported_for_standalone() -> WebpError {
WebpError::Unsupported
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn quality_to_qindex_endpoints() {
assert_eq!(quality_to_qindex(100.0), 0);
assert_eq!(quality_to_qindex(0.0), 127);
assert_eq!(quality_to_qindex(f32::NAN), 127);
assert_eq!(quality_to_qindex(-1.0), 127);
assert_eq!(quality_to_qindex(1000.0), 0);
}
#[test]
fn quality_to_qindex_midpoints() {
let mid = quality_to_qindex(50.0);
assert!((63..=64).contains(&mid), "midpoint qindex = {mid}");
assert_eq!(quality_to_qindex(75.0), 32);
}
#[test]
fn vp8_freq_deltas_default_is_zero() {
let d = Vp8FreqDeltas::default();
assert_eq!(d.y_dc_delta, 0);
assert_eq!(d.y2_dc_delta, 0);
assert_eq!(d.y2_ac_delta, 0);
assert_eq!(d.uv_dc_delta, 0);
assert_eq!(d.uv_ac_delta, 0);
}
}