#![warn(missing_debug_implementations)]
#![cfg_attr(feature = "simd", feature(portable_simd))]
pub mod alph;
pub mod anim;
pub mod anim_encode;
pub mod anmf;
pub mod build;
pub mod container;
pub mod decoder;
pub mod demux;
pub mod encoder;
pub mod encoder_anim;
pub mod encoder_vp8;
pub mod error;
pub mod meta_prefix;
#[cfg(feature = "registry")]
pub mod registry;
pub mod riff;
pub mod vp8_chunk;
pub mod vp8_decode;
pub mod vp8l;
pub mod vp8l_chunk;
pub mod vp8l_decode;
pub mod vp8l_encode;
pub mod vp8l_prefix;
pub mod vp8l_stream;
pub mod vp8l_transform;
pub mod vp8x;
#[cfg(feature = "registry")]
use oxideav_core::RuntimeContext;
#[cfg(feature = "registry")]
pub use registry::WebpDecoder;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
NotImplemented,
Unsupported(UnsupportedKind),
Container(container::ContainerError),
Vp8x(vp8x::Vp8xError),
Alph(alph::AlphError),
Anim(anim::AnimError),
Anmf(anmf::AnmfError),
Build(build::BuildError),
Lossy(vp8_chunk::WebpLossyError),
Vp8(oxideav_vp8::DecodeError),
Lossless(vp8l_chunk::WebpLosslessError),
Vp8lTransform(vp8l_stream::TransformListError),
Vp8lPrefix(vp8l_prefix::PrefixError),
Vp8lMetaPrefix(meta_prefix::MetaPrefixError),
Vp8lDecode(vp8l_decode::DecodeError),
Vp8lEncode(vp8l_encode::EncodeError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnsupportedKind {
LossyVp8,
NoImageData,
}
impl core::fmt::Display for UnsupportedKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::LossyVp8 => f.write_str("VP8 lossy bitstream (route to a VP8 decoder)"),
Self::NoImageData => {
f.write_str("no VP8L/VP8 image-data chunk (animation or header-only)")
}
}
}
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NotImplemented => f.write_str("oxideav-webp: pixel decode not implemented yet"),
Self::Unsupported(k) => write!(f, "oxideav-webp: unsupported image kind: {k}"),
Self::Container(e) => write!(f, "oxideav-webp container: {e}"),
Self::Vp8x(e) => write!(f, "oxideav-webp vp8x: {e}"),
Self::Alph(e) => write!(f, "oxideav-webp alph: {e}"),
Self::Anim(e) => write!(f, "oxideav-webp anim: {e}"),
Self::Anmf(e) => write!(f, "oxideav-webp anmf: {e}"),
Self::Build(e) => write!(f, "oxideav-webp build: {e}"),
Self::Lossy(e) => write!(f, "oxideav-webp lossy: {e}"),
Self::Vp8(e) => write!(f, "oxideav-webp vp8: {e}"),
Self::Lossless(e) => write!(f, "oxideav-webp lossless: {e}"),
Self::Vp8lTransform(e) => write!(f, "oxideav-webp vp8l-transform: {e}"),
Self::Vp8lPrefix(e) => write!(f, "oxideav-webp vp8l-prefix: {e}"),
Self::Vp8lMetaPrefix(e) => write!(f, "oxideav-webp vp8l-meta-prefix: {e}"),
Self::Vp8lDecode(e) => write!(f, "oxideav-webp vp8l-decode: {e}"),
Self::Vp8lEncode(e) => write!(f, "oxideav-webp vp8l-encode: {e}"),
}
}
}
impl std::error::Error for Error {}
impl From<container::ContainerError> for Error {
fn from(e: container::ContainerError) -> Self {
Self::Container(e)
}
}
impl From<vp8x::Vp8xError> for Error {
fn from(e: vp8x::Vp8xError) -> Self {
Self::Vp8x(e)
}
}
impl From<alph::AlphError> for Error {
fn from(e: alph::AlphError) -> Self {
Self::Alph(e)
}
}
impl From<anim::AnimError> for Error {
fn from(e: anim::AnimError) -> Self {
Self::Anim(e)
}
}
impl From<anmf::AnmfError> for Error {
fn from(e: anmf::AnmfError) -> Self {
Self::Anmf(e)
}
}
impl From<build::BuildError> for Error {
fn from(e: build::BuildError) -> Self {
Self::Build(e)
}
}
impl From<vp8_chunk::WebpLossyError> for Error {
fn from(e: vp8_chunk::WebpLossyError) -> Self {
Self::Lossy(e)
}
}
impl From<oxideav_vp8::DecodeError> for Error {
fn from(e: oxideav_vp8::DecodeError) -> Self {
Self::Vp8(e)
}
}
impl From<vp8l_chunk::WebpLosslessError> for Error {
fn from(e: vp8l_chunk::WebpLosslessError) -> Self {
Self::Lossless(e)
}
}
impl From<vp8l_stream::TransformListError> for Error {
fn from(e: vp8l_stream::TransformListError) -> Self {
Self::Vp8lTransform(e)
}
}
impl From<vp8l_prefix::PrefixError> for Error {
fn from(e: vp8l_prefix::PrefixError) -> Self {
Self::Vp8lPrefix(e)
}
}
impl From<meta_prefix::MetaPrefixError> for Error {
fn from(e: meta_prefix::MetaPrefixError) -> Self {
Self::Vp8lMetaPrefix(e)
}
}
impl From<vp8l_decode::DecodeError> for Error {
fn from(e: vp8l_decode::DecodeError) -> Self {
Self::Vp8lDecode(e)
}
}
impl From<vp8l_encode::EncodeError> for Error {
fn from(e: vp8l_encode::EncodeError) -> Self {
Self::Vp8lEncode(e)
}
}
pub fn parse_container(bytes: &[u8]) -> Result<container::WebpContainer, Error> {
container::parse(bytes).map_err(Into::into)
}
pub fn parse_vp8x_header(payload: &[u8]) -> Result<vp8x::Vp8xHeader, Error> {
vp8x::Vp8xHeader::parse(payload).map_err(Into::into)
}
pub fn parse_alph_header(payload: &[u8]) -> Result<alph::AlphHeader, Error> {
alph::AlphHeader::parse(payload).map_err(Into::into)
}
pub fn decode_alpha_plane(bytes: &[u8]) -> Result<Option<Vec<u8>>, Error> {
let c = container::parse(bytes)?;
let alph_chunk = match c.first_chunk_with_fourcc(container::fourcc::ALPH) {
Some(chunk) => chunk,
None => return Ok(None),
};
let (width, height) = if let Some(vp8x) = c.first_chunk_with_fourcc(container::fourcc::VP8X) {
let hdr = vp8x::Vp8xHeader::parse(vp8x.payload(bytes))?;
(hdr.canvas_width, hdr.canvas_height)
} else if let Some(vp8) = c.first_chunk_with_fourcc(container::fourcc::VP8) {
let lossy = vp8_chunk::WebpLossyChunk::from_chunk(bytes, vp8)?;
(u32::from(lossy.width()), u32::from(lossy.height()))
} else {
return Err(Error::Alph(alph::AlphError::EmptyPayload));
};
let plane = alph::decode_alpha(alph_chunk.payload(bytes), width, height)?;
Ok(Some(plane))
}
pub fn parse_anim_header(payload: &[u8]) -> Result<anim::AnimHeader, Error> {
anim::AnimHeader::parse(payload).map_err(Into::into)
}
pub fn parse_anmf_header(payload: &[u8]) -> Result<anmf::AnmfHeader, Error> {
anmf::AnmfHeader::parse(payload).map_err(Into::into)
}
pub fn build_webp_file(
payload: &[u8],
image_kind: build::ImageKind,
canvas_width: u32,
canvas_height: u32,
) -> Result<Vec<u8>, Error> {
build::build_webp_file(payload, image_kind, canvas_width, canvas_height).map_err(Into::into)
}
pub fn build_vp8x_chunk(
canvas_width: u32,
canvas_height: u32,
flags: build::Vp8xFlags,
) -> Result<Vec<u8>, Error> {
build::build_vp8x_chunk(canvas_width, canvas_height, flags).map_err(Into::into)
}
pub fn extract_lossy_chunk(bytes: &[u8]) -> Result<Option<vp8_chunk::WebpLossyChunk<'_>>, Error> {
let c = container::parse(bytes)?;
vp8_chunk::extract_lossy(bytes, &c).map_err(Into::into)
}
pub fn extract_lossless_chunk(
bytes: &[u8],
) -> Result<Option<vp8l_chunk::WebpLosslessChunk<'_>>, Error> {
let c = container::parse(bytes)?;
vp8l_chunk::extract_lossless(bytes, &c).map_err(Into::into)
}
pub fn read_vp8l_transform_list(bytes: &[u8]) -> Result<Option<vp8l_stream::TransformList>, Error> {
let c = container::parse(bytes)?;
let chunk = match vp8l_chunk::extract_lossless(bytes, &c)? {
Some(chunk) => chunk,
None => return Ok(None),
};
let mut reader = vp8l_stream::BitReader::new_after_image_header(chunk.bitstream());
let list = vp8l_stream::TransformList::read(&mut reader)?;
Ok(Some(list))
}
pub fn decode_lossless_image(bytes: &[u8]) -> Result<Option<vp8l_decode::DecodedImage>, Error> {
let c = container::parse(bytes)?;
let chunk = match vp8l_chunk::extract_lossless(bytes, &c)? {
Some(chunk) => chunk,
None => return Ok(None),
};
let width = chunk.width();
let height = chunk.height();
let image = vp8l_transform::decode_lossless(chunk.bitstream(), width, height)?;
Ok(Some(image))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodedWebp {
pub width: u32,
pub height: u32,
pub rgba: Vec<u8>,
}
pub fn decode_webp_image(bytes: &[u8]) -> Result<DecodedWebp, Error> {
let c = container::parse(bytes)?;
let vp8l = vp8l_chunk::extract_lossless(bytes, &c)?;
let Some(chunk) = vp8l else {
if let Some(vp8) = c.first_chunk_with_fourcc(container::fourcc::VP8) {
return decode_lossy_image(bytes, &c, vp8);
}
return Err(Error::Unsupported(UnsupportedKind::NoImageData));
};
let width = chunk.width();
let height = chunk.height();
let mut image = vp8l_transform::decode_lossless(chunk.bitstream(), width, height)?;
if let Some(alph) = c.first_chunk_with_fourcc(container::fourcc::ALPH) {
let plane = alph::decode_alpha(alph.payload(bytes), width, height)?;
let pixels = image.pixels_mut();
if plane.len() == pixels.len() {
for (px, &a) in pixels.iter_mut().zip(plane.iter()) {
*px = (*px & 0x00ff_ffff) | (u32::from(a) << 24);
}
}
}
Ok(DecodedWebp {
width,
height,
rgba: argb_to_rgba(image.pixels()),
})
}
fn decode_lossy_image(
bytes: &[u8],
c: &container::WebpContainer,
vp8: &container::WebpChunk,
) -> Result<DecodedWebp, Error> {
let (width, height, mut rgba) = vp8_decode::decode_lossy_rgba(vp8.payload(bytes))?;
if let Some(alph) = c.first_chunk_with_fourcc(container::fourcc::ALPH) {
let plane = alph::decode_alpha(alph.payload(bytes), width, height)?;
if plane.len() == (width as usize) * (height as usize) {
for (px, &a) in rgba.chunks_exact_mut(4).zip(plane.iter()) {
px[3] = a;
}
}
}
Ok(DecodedWebp {
width,
height,
rgba,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WebpImage {
pub width: u32,
pub height: u32,
pub frames: Vec<WebpFrame>,
pub metadata: WebpFileMetadata,
pub anim_background_rgba: Option<[u8; 4]>,
pub anim_loop_count: Option<u16>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WebpFrame {
pub rgba: Vec<u8>,
pub width: u32,
pub height: u32,
pub duration_ms: u32,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct WebpFileMetadata {
pub icc: Option<Vec<u8>>,
pub exif: Option<Vec<u8>>,
pub xmp: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct WebpMetadata<'a> {
pub icc: Option<&'a [u8]>,
pub exif: Option<&'a [u8]>,
pub xmp: Option<&'a [u8]>,
}
impl<'a> WebpMetadata<'a> {
pub fn is_empty(&self) -> bool {
self.icc.is_none() && self.exif.is_none() && self.xmp.is_none()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct WebpMetadataOwned {
pub icc: Option<Vec<u8>>,
pub exif: Option<Vec<u8>>,
pub xmp: Option<Vec<u8>>,
}
impl WebpMetadataOwned {
pub fn as_borrowed(&self) -> WebpMetadata<'_> {
WebpMetadata {
icc: self.icc.as_deref(),
exif: self.exif.as_deref(),
xmp: self.xmp.as_deref(),
}
}
pub fn is_empty(&self) -> bool {
self.icc.is_none() && self.exif.is_none() && self.xmp.is_none()
}
}
impl From<WebpMetadataOwned> for WebpFileMetadata {
fn from(m: WebpMetadataOwned) -> Self {
WebpFileMetadata {
icc: m.icc,
exif: m.exif,
xmp: m.xmp,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WebpError {
InvalidData,
Unsupported,
Eof,
NeedMore,
}
impl core::fmt::Display for WebpError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let s = match self {
Self::InvalidData => "oxideav-webp: invalid WebP data",
Self::Unsupported => "oxideav-webp: unsupported WebP feature",
Self::Eof => "oxideav-webp: unexpected end of input",
Self::NeedMore => "oxideav-webp: more input required",
};
f.write_str(s)
}
}
impl std::error::Error for WebpError {}
impl WebpError {
pub fn invalid<S: Into<String>>(_msg: S) -> Self {
Self::InvalidData
}
pub fn unsupported<S: Into<String>>(_msg: S) -> Self {
Self::Unsupported
}
}
impl From<Error> for WebpError {
fn from(e: Error) -> Self {
match e {
Error::Unsupported(_) | Error::NotImplemented => WebpError::Unsupported,
_ => WebpError::InvalidData,
}
}
}
impl From<oxideav_vp8::DecodeError> for WebpError {
fn from(e: oxideav_vp8::DecodeError) -> Self {
match e {
oxideav_vp8::DecodeError::Unsupported(_) => WebpError::Unsupported,
_ => WebpError::InvalidData,
}
}
}
impl From<oxideav_vp8::Vp8Error> for WebpError {
fn from(e: oxideav_vp8::Vp8Error) -> Self {
match e {
oxideav_vp8::Vp8Error::InvalidData(_) => WebpError::InvalidData,
oxideav_vp8::Vp8Error::Unsupported(_) => WebpError::Unsupported,
oxideav_vp8::Vp8Error::Eof => WebpError::Eof,
oxideav_vp8::Vp8Error::NeedMore => WebpError::NeedMore,
}
}
}
pub fn decode_webp(bytes: &[u8]) -> Result<WebpImage, WebpError> {
let c = container::parse(bytes).map_err(|_| WebpError::InvalidData)?;
if c.first_chunk_with_fourcc(container::fourcc::ANIM).is_some() {
return decode_animation(bytes, &c);
}
let decoded = decode_webp_image(bytes).map_err(WebpError::from)?;
let frame = WebpFrame {
rgba: decoded.rgba,
width: decoded.width,
height: decoded.height,
duration_ms: 0,
};
let metadata = metadata_from_container(bytes, &c);
Ok(WebpImage {
width: frame.width,
height: frame.height,
frames: vec![frame],
metadata,
anim_background_rgba: None,
anim_loop_count: None,
})
}
fn decode_animation(bytes: &[u8], c: &container::WebpContainer) -> Result<WebpImage, WebpError> {
let anim_chunk = c
.first_chunk_with_fourcc(container::fourcc::ANIM)
.ok_or(WebpError::InvalidData)?;
let anim =
anim::AnimHeader::parse(anim_chunk.payload(bytes)).map_err(|_| WebpError::InvalidData)?;
let bg = anim.background_color;
let vp8x_chunk = c
.first_chunk_with_fourcc(container::fourcc::VP8X)
.ok_or(WebpError::InvalidData)?;
let vp8x =
vp8x::Vp8xHeader::parse(vp8x_chunk.payload(bytes)).map_err(|_| WebpError::InvalidData)?;
let canvas_w = vp8x.canvas_width;
let canvas_h = vp8x.canvas_height;
let bg_rgba = [bg.red, bg.green, bg.blue, bg.alpha];
let canvas_bytes = canvas_w
.checked_mul(canvas_h)
.and_then(|n| n.checked_mul(4))
.ok_or(WebpError::InvalidData)? as usize;
let mut canvas: Vec<u8> = Vec::with_capacity(canvas_bytes);
for _ in 0..(canvas_bytes / 4) {
canvas.extend_from_slice(&bg_rgba);
}
let mut prev_rect: Option<(u32, u32, u32, u32, anmf::DisposalMethod)> = None;
let mut frames = Vec::new();
for anmf_chunk in c.chunks_with_fourcc(container::fourcc::ANMF) {
let payload = anmf_chunk.payload(bytes);
let header = anmf::AnmfHeader::parse(payload).map_err(|_| WebpError::InvalidData)?;
let frame_data = &payload[header.frame_data_offset()..];
let vp8l = find_subchunk(frame_data, container::fourcc::VP8L);
let Some(vp8l_payload) = vp8l else {
if find_subchunk(frame_data, container::fourcc::VP8).is_some() {
return Err(WebpError::Unsupported);
}
return Err(WebpError::InvalidData);
};
let chunk = vp8l_chunk::WebpLosslessChunk::from_payload(vp8l_payload)
.map_err(|e| WebpError::from(Error::from(e)))?;
let sub_w = chunk.width();
let sub_h = chunk.height();
let image = vp8l_transform::decode_lossless(chunk.bitstream(), sub_w, sub_h)
.map_err(|e| WebpError::from(Error::from(e)))?;
let mut pixels = image;
if let Some(alph_payload) = find_subchunk(frame_data, container::fourcc::ALPH) {
if let Ok(plane) = alph::decode_alpha(alph_payload, sub_w, sub_h) {
let px = pixels.pixels_mut();
if plane.len() == px.len() {
for (p, &a) in px.iter_mut().zip(plane.iter()) {
*p = (*p & 0x00ff_ffff) | (u32::from(a) << 24);
}
}
}
}
let sub_rgba = argb_to_rgba(pixels.pixels());
if let Some((px, py, pw, ph, anmf::DisposalMethod::Background)) = prev_rect {
fill_canvas_rect(&mut canvas, canvas_w, px, py, pw, ph, bg_rgba);
}
let right = header.x.checked_add(sub_w).ok_or(WebpError::InvalidData)?;
let bottom = header.y.checked_add(sub_h).ok_or(WebpError::InvalidData)?;
if right > canvas_w || bottom > canvas_h {
return Err(WebpError::InvalidData);
}
match header.blend {
anmf::BlendingMethod::Overwrite => {
blit_rect_overwrite(
&mut canvas,
canvas_w,
header.x,
header.y,
sub_w,
sub_h,
&sub_rgba,
);
}
anmf::BlendingMethod::AlphaBlend => {
blit_rect_alpha_blend(
&mut canvas,
canvas_w,
header.x,
header.y,
sub_w,
sub_h,
&sub_rgba,
);
}
}
frames.push(WebpFrame {
rgba: canvas.clone(),
width: canvas_w,
height: canvas_h,
duration_ms: header.duration_ms,
});
prev_rect = Some((header.x, header.y, sub_w, sub_h, header.dispose));
}
if frames.is_empty() {
return Err(WebpError::InvalidData);
}
Ok(WebpImage {
width: canvas_w,
height: canvas_h,
frames,
metadata: metadata_from_container(bytes, c),
anim_background_rgba: Some([bg.red, bg.green, bg.blue, bg.alpha]),
anim_loop_count: Some(anim.loop_count),
})
}
fn fill_canvas_rect(
canvas: &mut [u8],
canvas_w: u32,
x: u32,
y: u32,
w: u32,
h: u32,
rgba: [u8; 4],
) {
let canvas_w = canvas_w as usize;
let cw_bytes = canvas_w * 4;
let x = x as usize;
let y = y as usize;
let w = w as usize;
let h = h as usize;
for row in 0..h {
let off = (y + row) * cw_bytes + x * 4;
for col in 0..w {
canvas[off + col * 4] = rgba[0];
canvas[off + col * 4 + 1] = rgba[1];
canvas[off + col * 4 + 2] = rgba[2];
canvas[off + col * 4 + 3] = rgba[3];
}
}
}
fn blit_rect_overwrite(
canvas: &mut [u8],
canvas_w: u32,
x: u32,
y: u32,
w: u32,
h: u32,
src: &[u8],
) {
let canvas_w = canvas_w as usize;
let cw_bytes = canvas_w * 4;
let x = x as usize;
let y = y as usize;
let w = w as usize;
let h = h as usize;
let sw_bytes = w * 4;
for row in 0..h {
let src_off = row * sw_bytes;
let dst_off = (y + row) * cw_bytes + x * 4;
canvas[dst_off..dst_off + sw_bytes].copy_from_slice(&src[src_off..src_off + sw_bytes]);
}
}
fn blit_rect_alpha_blend(
canvas: &mut [u8],
canvas_w: u32,
x: u32,
y: u32,
w: u32,
h: u32,
src: &[u8],
) {
let canvas_w = canvas_w as usize;
let cw_bytes = canvas_w * 4;
let x = x as usize;
let y = y as usize;
let w = w as usize;
let h = h as usize;
for row in 0..h {
for col in 0..w {
let src_off = (row * w + col) * 4;
let dst_off = (y + row) * cw_bytes + (x + col) * 4;
let sr = src[src_off] as u32;
let sg = src[src_off + 1] as u32;
let sb = src[src_off + 2] as u32;
let sa = src[src_off + 3] as u32;
if sa == 255 {
canvas[dst_off] = sr as u8;
canvas[dst_off + 1] = sg as u8;
canvas[dst_off + 2] = sb as u8;
canvas[dst_off + 3] = 255;
continue;
}
if sa == 0 {
continue;
}
let dr = canvas[dst_off] as u32;
let dg = canvas[dst_off + 1] as u32;
let db = canvas[dst_off + 2] as u32;
let da = canvas[dst_off + 3] as u32;
let dst_factor = (da * (255 - sa) + 127) / 255;
let out_a = sa + dst_factor;
let out_r = (sr * sa + dr * dst_factor + out_a / 2)
.checked_div(out_a)
.unwrap_or(0);
let out_g = (sg * sa + dg * dst_factor + out_a / 2)
.checked_div(out_a)
.unwrap_or(0);
let out_b = (sb * sa + db * dst_factor + out_a / 2)
.checked_div(out_a)
.unwrap_or(0);
canvas[dst_off] = out_r.min(255) as u8;
canvas[dst_off + 1] = out_g.min(255) as u8;
canvas[dst_off + 2] = out_b.min(255) as u8;
canvas[dst_off + 3] = out_a.min(255) as u8;
}
}
}
fn find_subchunk(mut data: &[u8], target: container::FourCc) -> Option<&[u8]> {
while data.len() >= 8 {
let fourcc: container::FourCc = data[0..4].try_into().ok()?;
let size = u32::from_le_bytes(data[4..8].try_into().ok()?) as usize;
let payload_start = 8usize;
let payload_end = payload_start.checked_add(size)?;
if payload_end > data.len() {
return None;
}
if fourcc == target {
return Some(&data[payload_start..payload_end]);
}
let advance = payload_end + (size & 1);
if advance > data.len() {
return None;
}
data = &data[advance..];
}
None
}
pub fn extract_metadata(bytes: &[u8]) -> Result<WebpFileMetadata, WebpError> {
let c = container::parse(bytes).map_err(|_| WebpError::InvalidData)?;
Ok(metadata_from_container(bytes, &c))
}
fn metadata_from_container(bytes: &[u8], c: &container::WebpContainer) -> WebpFileMetadata {
let payload_of = |fourcc| {
c.first_chunk_with_fourcc(fourcc)
.map(|chunk| chunk.payload(bytes).to_vec())
};
WebpFileMetadata {
icc: payload_of(container::fourcc::ICCP),
exif: payload_of(container::fourcc::EXIF),
xmp: payload_of(container::fourcc::XMP),
}
}
pub fn encode_webp_lossless(rgba: &[u8], width: u32, height: u32) -> Result<Vec<u8>, Error> {
vp8l_encode::encode_webp_lossless(rgba, width, height).map_err(Into::into)
}
pub fn encode_vp8l_argb(argb: &[u32], width: u32, height: u32) -> Result<Vec<u8>, WebpError> {
vp8l_encode::encode_vp8l_argb(argb, width, height)
.map_err(Error::from)
.map_err(WebpError::from)
}
pub fn encode_vp8l_argb_with(
argb: &[u32],
width: u32,
height: u32,
has_alpha: bool,
) -> Result<Vec<u8>, WebpError> {
vp8l_encode::encode_vp8l_argb_with(argb, width, height, has_alpha)
.map_err(Error::from)
.map_err(WebpError::from)
}
pub fn encode_vp8l_argb_with_metadata(
width: u32,
height: u32,
argb: &[u32],
has_alpha: bool,
meta: &WebpMetadata<'_>,
) -> Result<Vec<u8>, WebpError> {
let payload = encode_vp8l_argb_with(argb, width, height, has_alpha)?;
if !has_alpha && meta.is_empty() {
return build::build_webp_file(&payload, build::ImageKind::Lossless, width, height)
.map_err(Error::from)
.map_err(WebpError::from);
}
let flags = build::Vp8xFlags {
has_iccp: meta.icc.is_some(),
has_alpha,
has_exif: meta.exif.is_some(),
has_xmp: meta.xmp.is_some(),
has_animation: false,
};
let vp8x_payload = build::build_vp8x_chunk(width, height, flags)
.map_err(Error::from)
.map_err(WebpError::from)?;
let mut body = Vec::new();
let mut push_chunk = |fourcc, payload: &[u8]| -> Result<(), WebpError> {
let chunk = build::build_chunk(fourcc, payload)
.map_err(Error::from)
.map_err(WebpError::from)?;
body.extend_from_slice(&chunk);
Ok(())
};
push_chunk(container::fourcc::VP8X, &vp8x_payload)?;
if let Some(icc) = meta.icc {
push_chunk(container::fourcc::ICCP, icc)?;
}
push_chunk(container::fourcc::VP8L, &payload)?;
if let Some(exif) = meta.exif {
push_chunk(container::fourcc::EXIF, exif)?;
}
if let Some(xmp) = meta.xmp {
push_chunk(container::fourcc::XMP, xmp)?;
}
let file_size = (body.len() as u64) + 4;
if file_size > u64::from(u32::MAX) {
return Err(WebpError::InvalidData);
}
let mut out = Vec::with_capacity(12 + body.len());
out.extend_from_slice(&container::fourcc::RIFF);
out.extend_from_slice(&(file_size as u32).to_le_bytes());
out.extend_from_slice(&container::fourcc::WEBP);
out.extend_from_slice(&body);
Ok(out)
}
#[doc(inline)]
pub use anim_encode::{
build_animated_webp, build_animated_webp_with_options, AnimEncoderOptions, AnimFrame,
AnimFrameMode, DeltaConfig, DownsampleKernel,
};
pub const CODEC_ID_VP8L: &str = "webp_vp8l";
pub const CODEC_ID_VP8: &str = "webp_vp8";
fn argb_to_rgba(pixels: &[u32]) -> Vec<u8> {
let mut out = Vec::with_capacity(pixels.len() * 4);
for &argb in pixels {
out.push((argb >> 16) as u8); out.push((argb >> 8) as u8); out.push(argb as u8); out.push((argb >> 24) as u8); }
out
}
#[cfg(feature = "registry")]
pub fn register(ctx: &mut RuntimeContext) {
registry::register(ctx);
}
#[cfg(feature = "registry")]
pub fn register_codecs(ctx: &mut RuntimeContext) {
registry::register_codecs(&mut ctx.codecs);
}
#[cfg(feature = "registry")]
pub fn register_containers(ctx: &mut RuntimeContext) {
registry::register_containers(&mut ctx.containers);
}
#[cfg(feature = "registry")]
oxideav_core::register!("webp", register);