pub const VP8L_SIGNATURE: u8 = 0x2F;
pub fn decode(buf: &[u8]) -> Result<Vp8lImage, crate::WebpError> {
if buf.is_empty() || buf[0] != VP8L_SIGNATURE {
return Err(crate::WebpError::InvalidData);
}
let chunk = crate::vp8l_chunk::WebpLosslessChunk::from_payload(buf)
.map_err(|_| crate::WebpError::InvalidData)?;
let width = chunk.width();
let height = chunk.height();
let has_alpha = chunk.alpha_is_used();
let image = crate::vp8l_transform::decode_lossless(chunk.bitstream(), width, height)
.map_err(|_| crate::WebpError::InvalidData)?;
Ok(Vp8lImage {
width,
height,
pixels: image.pixels().to_vec(),
has_alpha,
})
}
pub use crate::{encode_vp8l_argb, encode_vp8l_argb_with, encode_vp8l_argb_with_metadata};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Vp8lImage {
pub width: u32,
pub height: u32,
pub pixels: Vec<u32>,
pub has_alpha: bool,
}
impl Vp8lImage {
pub fn to_rgba(&self) -> Vec<u8> {
#[cfg(feature = "simd")]
{
self.to_rgba_simd()
}
#[cfg(not(feature = "simd"))]
{
self.to_rgba_scalar()
}
}
pub fn to_rgba_scalar(&self) -> Vec<u8> {
let n = self.pixels.len();
let mut out = vec![0u8; n * 4];
for (chunk, &argb) in out.chunks_exact_mut(4).zip(self.pixels.iter()) {
chunk[0] = (argb >> 16) as u8;
chunk[1] = (argb >> 8) as u8;
chunk[2] = argb as u8;
chunk[3] = (argb >> 24) as u8;
}
out
}
#[cfg(feature = "simd")]
pub fn to_rgba_simd(&self) -> Vec<u8> {
use core::simd::{simd_swizzle, u8x16};
let n = self.pixels.len();
let mut out = vec![0u8; n * 4];
let pix_bytes: &[u8] =
unsafe { core::slice::from_raw_parts(self.pixels.as_ptr() as *const u8, n * 4) };
let main_len = (n / 4) * 16;
let mut src_iter = pix_bytes[..main_len].chunks_exact(16);
let mut dst_iter = out[..main_len].chunks_exact_mut(16);
for (src, dst) in (&mut src_iter).zip(&mut dst_iter) {
let v = u8x16::from_slice(src);
let shuffled: u8x16 =
simd_swizzle!(v, [2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15]);
dst.copy_from_slice(shuffled.as_array());
}
let tail_pixels = &self.pixels[(n / 4) * 4..];
for (chunk, &argb) in out[main_len..].chunks_exact_mut(4).zip(tail_pixels.iter()) {
chunk[0] = (argb >> 16) as u8;
chunk[1] = (argb >> 8) as u8;
chunk[2] = argb as u8;
chunk[3] = (argb >> 24) as u8;
}
out
}
}
#[derive(Debug, Default)]
pub struct HuffmanGroup {
_private: (),
}
impl HuffmanGroup {
pub fn new() -> Self {
Self { _private: () }
}
}
pub mod bit_reader {
pub use crate::vp8l_stream::{BitReader, BitReaderEof};
}
pub mod huffman {
pub use super::HuffmanGroup;
pub use crate::vp8l_prefix::{PrefixCode, PrefixError};
}
pub mod transform {
pub use crate::vp8l_transform::{
decode_lossless, decode_lossless_headerless, inverse_color, inverse_color_indexing,
inverse_color_table, inverse_predictor, inverse_subtract_green,
};
}
pub mod encoder {
pub use crate::vp8l_encode::EncodeError;
pub use crate::{encode_vp8l_argb, encode_vp8l_argb_with};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vp8l_encode::encode_vp8l_argb_with;
#[test]
fn signature_constant_matches_rfc_9649_3_4() {
assert_eq!(VP8L_SIGNATURE, 0x2F);
}
#[test]
fn vp8l_image_to_rgba_round_trip_shape() {
let img = Vp8lImage {
width: 2,
height: 1,
pixels: vec![0xff_aa_bb_cc, 0xff_11_22_33],
has_alpha: false,
};
let rgba = img.to_rgba();
assert_eq!(rgba.len(), 2 * 4);
assert_eq!(&rgba[0..4], &[0xaa, 0xbb, 0xcc, 0xff]);
assert_eq!(&rgba[4..8], &[0x11, 0x22, 0x33, 0xff]);
}
#[test]
fn bare_decode_round_trips_through_published_path() {
let (w, h) = (2u32, 2u32);
let argb = vec![
0xff_00_00_00u32,
0xff_ff_00_00,
0xff_00_ff_00,
0xff_00_00_ff,
];
let bare = encode_vp8l_argb_with(&argb, w, h, false).expect("encode");
let img = decode(&bare).expect("decode");
assert_eq!(img.width, w);
assert_eq!(img.height, h);
assert_eq!(img.pixels, argb);
}
#[test]
fn bare_decode_rejects_bad_signature() {
let err = decode(&[0x00, 0x00, 0x00, 0x00, 0x00]).expect_err("bad sig");
assert_eq!(err, crate::WebpError::InvalidData);
}
#[test]
fn to_rgba_scalar_matches_published_layout() {
let pixels: Vec<u32> = (0..32u32).map(|i| 0xff00_0000 | (i * 0x010101)).collect();
let img = Vp8lImage {
width: 32,
height: 1,
pixels,
has_alpha: false,
};
let bytes = img.to_rgba_scalar();
assert_eq!(bytes.len(), 32 * 4);
assert_eq!(&bytes[0..4], &[0x00, 0x00, 0x00, 0xff]);
assert_eq!(&bytes[31 * 4..32 * 4], &[0x1f, 0x1f, 0x1f, 0xff]);
}
#[cfg(feature = "simd")]
#[test]
fn to_rgba_simd_matches_scalar_byte_for_byte() {
let pixels: Vec<u32> = (0..67u32)
.map(|i| (i.wrapping_mul(0x6789_abcd)) | 0xff00_0000)
.collect();
let img = Vp8lImage {
width: 67,
height: 1,
pixels,
has_alpha: false,
};
let scalar = img.to_rgba_scalar();
let simd = img.to_rgba_simd();
assert_eq!(scalar, simd, "SIMD path must be byte-identical to scalar");
assert_eq!(img.to_rgba(), scalar);
}
#[test]
fn average2_swar_matches_per_channel_truncating_divide_reference() {
let argb = vec![0xff_aa_bb_ccu32, 0xff_55_44_33];
let bare = crate::vp8l_encode::encode_vp8l_argb_with(&argb, 2, 1, false).expect("encode");
let img = decode(&bare).expect("decode");
assert_eq!(img.pixels, argb);
}
}