pub(crate) mod alpha;
pub(crate) mod bool_dec;
pub(crate) mod bool_enc;
pub(crate) mod constants;
pub(crate) mod decode;
pub(crate) mod decode_incr;
pub(crate) mod decoder;
pub(crate) mod enc_header;
pub(crate) mod encoder;
pub(crate) mod fdct;
pub(crate) mod frame;
pub(crate) mod frame_header;
pub(crate) mod header;
pub(crate) mod idct;
pub(crate) mod loop_filter;
pub(crate) mod mb;
pub(crate) mod predict;
pub(crate) mod prelude;
pub(crate) mod prob_opt;
pub(crate) mod quant;
pub(crate) mod reconstruct;
pub(crate) mod rgb_to_yuv;
pub(crate) mod token;
pub(crate) mod tokens;
pub(crate) mod trellis;
pub(crate) mod work;
pub(crate) mod yuv;
#[cfg(feature = "bench")]
pub mod bench {
#[inline]
#[must_use]
pub fn sse_block(
src: &[u8],
src_off: usize,
src_stride: usize,
pred: &[u8],
pred_off: usize,
pred_stride: usize,
size: usize,
) -> i64 {
crate::lossy::frame::sse_block(src, src_off, src_stride, pred, pred_off, pred_stride, size)
}
#[inline]
#[must_use]
pub fn sse_block_reference(
src: &[u8],
src_off: usize,
src_stride: usize,
pred: &[u8],
pred_off: usize,
pred_stride: usize,
size: usize,
) -> i64 {
crate::lossy::frame::sse_block_reference(
src,
src_off,
src_stride,
pred,
pred_off,
pred_stride,
size,
)
}
#[inline]
#[must_use]
pub fn residual_block(
src: &[u8],
src_stride: usize,
src_x: usize,
src_y: usize,
pred: &[u8],
pred_off: usize,
pred_stride: usize,
) -> [i16; 16] {
crate::lossy::frame::residual_block(
src,
src_stride,
src_x,
src_y,
pred,
pred_off,
pred_stride,
)
}
#[inline]
#[must_use]
pub fn residual_block_reference(
src: &[u8],
src_stride: usize,
src_x: usize,
src_y: usize,
pred: &[u8],
pred_off: usize,
pred_stride: usize,
) -> [i16; 16] {
crate::lossy::frame::residual_block_reference(
src,
src_stride,
src_x,
src_y,
pred,
pred_off,
pred_stride,
)
}
#[inline]
pub fn true_motion(plane: &mut [u8], off: usize, stride: usize, size: usize) {
crate::lossy::predict::true_motion(plane, off, stride, size);
}
#[inline]
pub fn true_motion_reference(plane: &mut [u8], off: usize, stride: usize, size: usize) {
crate::lossy::predict::true_motion_reference(plane, off, stride, size);
}
}
pub use crate::stream::{DecodeOptions, ImageInfo, Progress, RowDrain};
pub use crate::{Codec, Dimensions, Effort, Error, Image, ImageRef, Metadata, PixelLayout, Result};
pub use decoder::IncrementalDecoder;
#[cfg(feature = "std")]
pub use encoder::encode_to;
pub use encoder::{LossyConfig, MetadataPolicy, Quality, encode, encode_image, encode_vp8};
pub use frame_header::FrameHeader;
use crate::lossy::prelude::*;
pub fn peek_dimensions(payload: &[u8]) -> Result<Dimensions> {
let header = FrameHeader::parse_key_frame(payload)?;
Dimensions::new(u32::from(header.width), u32::from(header.height)).map_err(|_| {
Error::InvalidBitstream {
codec: Codec::Lossy,
}
})
}
pub fn decode(payload: &[u8]) -> Result<Image> {
decode_with(payload, &DecodeOptions::default())
}
pub fn decode_with(payload: &[u8], options: &DecodeOptions) -> Result<Image> {
check_pixel_limit(payload, options)?;
let image = decode::decode_frame(payload)?;
Ok(repack(image, options.layout))
}
pub fn decode_argb(payload: &[u8]) -> Result<(Dimensions, Vec<u32>)> {
decode_argb_with(payload, &DecodeOptions::default())
}
pub fn decode_argb_with(payload: &[u8], options: &DecodeOptions) -> Result<(Dimensions, Vec<u32>)> {
check_pixel_limit(payload, options)?;
let image = decode::decode_frame(payload)?;
let argb = crate::image::unpack_pixels(PixelLayout::Rgba8, image.as_bytes());
Ok((image.dimensions(), argb))
}
fn check_pixel_limit(payload: &[u8], options: &DecodeOptions) -> Result<()> {
let pixels = peek_dimensions(payload)?.pixel_count();
if let Some(limit) = options.max_pixels.filter(|&limit| pixels > limit) {
return Err(Error::LimitExceeded { pixels, limit });
}
Ok(())
}
fn repack(image: Image, layout: PixelLayout) -> Image {
if layout == PixelLayout::Rgba8 {
return image;
}
let dims = image.dimensions();
let has_alpha = image.has_alpha();
let argb = crate::image::unpack_pixels(PixelLayout::Rgba8, image.as_bytes());
let bytes = crate::image::pack_pixels(layout, &argb);
Image::from_parts(dims, layout, bytes, has_alpha, Metadata::none())
}
#[must_use]
pub const fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
#[cfg(feature = "oracle")]
#[doc(hidden)]
pub type YuvPlanes = (u32, u32, Vec<u8>, Vec<u8>, Vec<u8>);
#[cfg(feature = "oracle")]
#[doc(hidden)]
#[must_use]
pub fn __reconstruct_yuv(payload: &[u8]) -> Option<YuvPlanes> {
let (planes, w, h) = decode::reconstruct_to_planes(payload).ok()?;
let (cw, ch) = (w.div_ceil(2), h.div_ceil(2));
Some((
u32::try_from(w).ok()?,
u32::try_from(h).ok()?,
planes.crop_y(w, h),
planes.crop_u(cw, ch),
planes.crop_v(cw, ch),
))
}
#[cfg(feature = "oracle")]
#[doc(hidden)]
#[must_use]
pub fn __frame_uses_skip(payload: &[u8]) -> Option<bool> {
decode::frame_uses_skip(payload)
}
#[cfg(feature = "oracle")]
#[doc(hidden)]
#[must_use]
pub fn __frame_filter_level(payload: &[u8]) -> Option<i32> {
decode::frame_filter_level(payload)
}
#[cfg(feature = "oracle")]
#[doc(hidden)]
#[must_use]
pub fn __frame_uses_i4x4(payload: &[u8]) -> Option<bool> {
decode::frame_uses_i4x4(payload)
}
#[cfg(feature = "oracle")]
#[doc(hidden)]
#[must_use]
pub fn __frame_segment_count(payload: &[u8]) -> Option<usize> {
decode::frame_segment_count(payload)
}
#[cfg(test)]
mod tests {
use super::{
Codec, DecodeOptions, Dimensions, Error, ImageRef, LossyConfig, PixelLayout, decode,
decode_argb, decode_argb_with, decode_with, encode_vp8, peek_dimensions, version,
};
fn key_frame_header(width: u16, height: u16) -> [u8; 10] {
let [wl, wh] = width.to_le_bytes();
let [hl, hh] = height.to_le_bytes();
[0x10, 0x00, 0x00, 0x9d, 0x01, 0x2a, wl, wh, hl, hh]
}
#[test]
fn peek_dimensions_reads_a_key_frame_size() {
let header = key_frame_header(320, 240);
assert_eq!(
peek_dimensions(&header).unwrap(),
Dimensions::new(320, 240).unwrap()
);
}
#[test]
fn decode_reconstructs_a_minimal_key_frame() {
let header = key_frame_header(16, 16);
let image = decode(&header).unwrap();
assert_eq!((image.width(), image.height()), (16, 16));
let px = image.as_bytes();
assert_eq!(px.len(), 16 * 16 * 4);
for (i, p) in px.chunks_exact(4).enumerate() {
assert_eq!(p[0], p[1], "pixel {i}: R != G");
assert_eq!(p[1], p[2], "pixel {i}: G != B");
assert_eq!(p[3], 0xff, "pixel {i}: alpha not opaque");
}
assert_eq!(&px[0..4], &[130, 130, 130, 255], "row 0 col 0");
let r3c15 = 3 * 16 * 4 + 15 * 4;
assert_eq!(&px[r3c15..r3c15 + 4], &[130, 130, 130, 255], "row 3 col 15");
let r4c0 = 4 * 16 * 4;
assert_eq!(&px[r4c0..r4c0 + 4], &[132, 132, 132, 255], "row 4 col 0");
let r15c15 = 15 * 16 * 4 + 15 * 4;
assert_eq!(
&px[r15c15..r15c15 + 4],
&[132, 132, 132, 255],
"row 15 col 15"
);
}
#[test]
fn decode_argb_matches_decode() {
let header = key_frame_header(16, 16);
let (dims, argb) = decode_argb(&header).unwrap();
assert_eq!(dims, Dimensions::new(16, 16).unwrap());
assert_eq!(argb.len(), 16 * 16);
let rgba = decode(&header).unwrap();
assert_eq!(
crate::image::unpack_pixels(PixelLayout::Rgba8, rgba.as_bytes()),
argb
);
}
#[test]
fn decode_rejects_a_truncated_header() {
assert_eq!(decode(&[0u8; 9]).unwrap_err(), Error::Truncated);
}
#[test]
fn decode_with_rejects_before_plane_alloc() {
let header = key_frame_header(16383, 16383);
let opts = DecodeOptions::default().max_pixels(1 << 20);
let expected = Error::LimitExceeded {
pixels: 16383 * 16383,
limit: 1 << 20,
};
assert_eq!(decode_with(&header, &opts).unwrap_err(), expected);
assert_eq!(decode_argb_with(&header, &opts).unwrap_err(), expected);
let small = key_frame_header(16, 16);
assert_eq!(
decode_with(&small, &DecodeOptions::default()).unwrap(),
decode(&small).unwrap()
);
}
#[test]
fn decode_with_honors_output_layout() {
let header = key_frame_header(16, 16);
let rgba = decode(&header).unwrap();
let bgra = decode_with(
&header,
&DecodeOptions::default().layout(PixelLayout::Bgra8),
)
.unwrap();
let r = rgba.as_bytes();
let b = bgra.as_bytes();
assert_eq!([r[0], r[1], r[2], r[3]], [b[2], b[1], b[0], b[3]]);
}
#[test]
fn decode_rejects_a_bad_start_code() {
let mut header = key_frame_header(16, 16);
header[3] = 0x00; assert_eq!(
decode(&header).unwrap_err(),
Error::InvalidBitstream {
codec: Codec::Lossy
}
);
}
#[test]
fn version_reports_the_cargo_package_version() {
assert_eq!(version(), env!("CARGO_PKG_VERSION"));
assert!(!version().is_empty(), "version must not be empty");
}
#[test]
fn decode_with_pixel_limit_is_inclusive_at_the_boundary() {
let header = key_frame_header(16, 16);
assert!(
decode_with(&header, &DecodeOptions::default().max_pixels(256)).is_ok(),
"256 pixels must pass a limit of exactly 256"
);
assert!(
decode_argb_with(&header, &DecodeOptions::default().max_pixels(256)).is_ok(),
"256 pixels must pass a limit of exactly 256 (argb)"
);
assert_eq!(
decode_with(&header, &DecodeOptions::default().max_pixels(255)).unwrap_err(),
Error::LimitExceeded {
pixels: 256,
limit: 255,
}
);
}
#[test]
fn repack_swaps_channels_on_a_colored_frame() {
let (w, h) = (16u32, 16u32);
let dims = Dimensions::new(w, h).unwrap();
let mut rgba = Vec::new();
for _ in 0..(w * h) {
rgba.extend_from_slice(&[220, 40, 30, 0xff]);
}
let img = ImageRef::new(dims, PixelLayout::Rgba8, &rgba).unwrap();
let (_dims, payload) = encode_vp8(img, &LossyConfig::new().with_quality(95)).unwrap();
let rgba_out = decode(&payload).unwrap();
let bgra_out = decode_with(
&payload,
&DecodeOptions::default().layout(PixelLayout::Bgra8),
)
.unwrap();
let r = rgba_out.as_bytes();
let b = bgra_out.as_bytes();
assert_ne!(
r[0], r[2],
"decoded frame must be colored for this test to bite"
);
for (rp, bp) in r.chunks_exact(4).zip(b.chunks_exact(4)) {
assert_eq!(
[bp[0], bp[1], bp[2], bp[3]],
[rp[2], rp[1], rp[0], rp[3]],
"Bgra8 output is not the channel-swap of Rgba8"
);
}
}
}