1use otf_pixels_core::{
9 Animation, Codec, DecodeCapability, Decoder, Format, ImageDescriptor, Limits, Orientation,
10 PixelFormat, PixelsError, Result, Source, SourceInfo,
11};
12
13const MAX_COMPRESSED: usize = 256 * 1024 * 1024;
19
20#[derive(Debug)]
22pub struct WebPDecoder {
23 descriptor: ImageDescriptor,
24 bytes: Vec<u8>,
26 pixels: Option<Vec<u8>>,
28 row: u32,
30 orientation: Orientation,
32 exif: Option<Vec<u8>>,
34 icc: Option<Vec<u8>>,
36 xmp: Option<Vec<u8>>,
38 info: SourceInfo,
40 animation: Option<Animation>,
42}
43
44impl WebPDecoder {
45 pub fn new<S: Source>(mut source: S, limits: Limits) -> Result<Self> {
64 let mut bytes = Vec::new();
65 let mut chunk = [0_u8; 64 * 1024];
66 loop {
67 if bytes.len() > MAX_COMPRESSED {
68 return Err(PixelsError::malformed(
69 "webp",
70 format!("stream exceeds {MAX_COMPRESSED} bytes"),
71 ));
72 }
73 match source.read(&mut chunk)? {
74 0 => break,
75 read => {
76 let Some(filled) = chunk.get(..read) else {
77 break;
78 };
79 bytes.extend_from_slice(filled);
80 }
81 }
82 }
83
84 let container = crate::riff::parse(&bytes)?;
85 let orientation = container
87 .exif
88 .and_then(Orientation::from_exif_block)
89 .unwrap_or_default();
90 let pixel = if container.has_alpha {
91 PixelFormat::Rgba8
92 } else {
93 PixelFormat::Rgb8
94 };
95 let descriptor =
97 ImageDescriptor::with_limits(container.width, container.height, pixel, &limits)?;
98 let icc = container.icc.map(<[u8]>::to_vec);
99 let xmp = container.xmp.map(<[u8]>::to_vec);
100 let mut info = SourceInfo::default();
101 info.density = container
102 .exif
103 .and_then(otf_pixels_core::Density::from_exif_block);
104 info.bits_per_sample = Some(8);
105 info.compression = Some(match container.bitstream {
106 crate::riff::Bitstream::Lossless(_) => "lossless",
107 crate::riff::Bitstream::Lossy { .. } => "lossy",
108 });
109 let exif = container
110 .exif
111 .map(|block| block.strip_prefix(b"Exif\0\0").unwrap_or(block).to_vec());
112 let animation = Animation::new(container.frame_durations_ms.clone(), container.loop_count);
113 Ok(Self {
114 descriptor,
115 bytes,
116 pixels: None,
117 row: 0,
118 orientation,
119 exif,
120 icc,
121 xmp,
122 info,
123 animation,
124 })
125 }
126
127 fn decode_canvas(&self) -> Result<Vec<u8>> {
129 let container = crate::riff::parse(&self.bytes)?;
130 let frame = container.frame;
131 let rgba = decode_rgba(
132 container.bitstream,
133 frame.width as usize,
134 frame.height as usize,
135 )?;
136
137 let channels = self.descriptor.pixel.channels();
141 let (canvas_width, frame_width) = (container.width as usize, frame.width as usize);
142 let mut pixels =
143 vec![0_u8; container.width as usize * container.height as usize * channels];
144 for (y, source) in rgba.chunks_exact(frame_width * 4).enumerate() {
145 let row = (frame.y as usize + y) * canvas_width + frame.x as usize;
146 let Some(target) = pixels.get_mut(row * channels..(row + frame_width) * channels)
147 else {
148 return Err(PixelsError::malformed(
149 "webp",
150 "a frame overruns its canvas",
151 ));
152 };
153 for (out, sample) in target
154 .chunks_exact_mut(channels)
155 .zip(source.chunks_exact(4))
156 {
157 out.copy_from_slice(sample.get(..channels).unwrap_or(&[]));
158 }
159 }
160 Ok(pixels)
161 }
162}
163
164fn decode_rgba(
166 bitstream: crate::riff::Bitstream<'_>,
167 width: usize,
168 height: usize,
169) -> Result<Vec<u8>> {
170 match bitstream {
171 crate::riff::Bitstream::Lossless(stream) => {
172 let argb = crate::vp8l::decode(stream, width, height)?;
173 Ok(argb
174 .into_iter()
175 .flat_map(|p| {
176 let [blue, green, red, alpha] = p.to_le_bytes();
177 [red, green, blue, alpha]
178 })
179 .collect())
180 }
181 crate::riff::Bitstream::Lossy { vp8, alpha } => {
182 let frame = crate::vp8::decode(vp8)?;
183 if (frame.width, frame.height) != (width, height) {
184 return Err(PixelsError::malformed(
185 "webp",
186 "the VP8 frame's size differs from the container's",
187 ));
188 }
189 let alpha = match alpha {
191 Some(chunk) => crate::alpha::decode(chunk, width, height)?,
192 None => vec![255; width * height],
193 };
194 Ok(crate::yuv::to_rgb(
195 &frame.y,
196 frame.y_stride,
197 &frame.u,
198 &frame.v,
199 frame.uv_stride,
200 width,
201 height,
202 Some(&alpha),
203 ))
204 }
205 }
206}
207
208impl Decoder for WebPDecoder {
209 fn descriptor(&self) -> ImageDescriptor {
210 self.descriptor
211 }
212
213 fn orientation(&self) -> Orientation {
214 self.orientation
215 }
216
217 fn icc_profile(&self) -> Option<&[u8]> {
218 self.icc.as_deref()
219 }
220
221 fn exif(&self) -> Option<&[u8]> {
223 self.exif.as_deref()
224 }
225
226 fn xmp(&self) -> Option<&[u8]> {
228 self.xmp.as_deref()
229 }
230
231 fn source_info(&self) -> SourceInfo {
233 self.info.clone()
234 }
235
236 fn animation(&self) -> Option<Animation> {
237 self.animation.clone()
238 }
239
240 fn capability(&self) -> DecodeCapability {
241 DecodeCapability::Sequential
245 }
246
247 fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
248 if self.row >= self.descriptor.height {
249 return Err(PixelsError::invalid_argument(
250 "out",
251 format!("all {} rows have already been read", self.descriptor.height),
252 ));
253 }
254 let row_bytes = self.descriptor.row_bytes();
255 if out.len() != row_bytes {
256 return Err(PixelsError::invalid_argument(
257 "out",
258 format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
259 ));
260 }
261 if self.pixels.is_none() {
262 self.pixels = Some(self.decode_canvas()?);
263 self.bytes = Vec::new();
265 }
266 let start = self.row as usize * row_bytes;
267 let row = self
268 .pixels
269 .as_deref()
270 .unwrap_or(&[])
271 .get(start..)
272 .and_then(|rest| rest.get(..row_bytes))
273 .ok_or_else(|| PixelsError::malformed("webp", "decoded image is short"))?;
274 out.copy_from_slice(row);
275 self.row += 1;
276 Ok(())
277 }
278}
279
280#[must_use]
286pub fn probe(prefix: &[u8]) -> bool {
287 prefix.get(..4) == Some(&crate::SIGNATURE_RIFF[..])
288 && prefix.get(8..12) == Some(&crate::SIGNATURE_WEBP[..])
289}
290
291#[derive(Debug, Clone, Copy, Default)]
293pub struct WebPCodec;
294
295impl Codec for WebPCodec {
296 fn format(&self) -> Format {
297 Format::WebP
298 }
299
300 fn magic_len(&self) -> usize {
301 12
302 }
303
304 fn probe(&self, prefix: &[u8]) -> bool {
305 probe(prefix)
306 }
307}
308
309#[cfg(test)]
310#[allow(
311 clippy::unwrap_used,
312 clippy::indexing_slicing,
313 reason = "tests operate on known-good values and assert shapes directly"
314)]
315mod tests {
316 use super::*;
317
318 #[test]
319 fn probe_needs_the_form_type_not_just_riff() {
320 let mut header = Vec::from(*b"RIFF");
321 header.extend_from_slice(&[0, 0, 0, 0]);
322 header.extend_from_slice(b"WEBP");
323 assert!(probe(&header));
324
325 let mut wav = Vec::from(*b"RIFF");
327 wav.extend_from_slice(&[0, 0, 0, 0]);
328 wav.extend_from_slice(b"WAVE");
329 assert!(!probe(&wav));
330
331 assert!(!probe(b"RIFF"));
333 assert!(!probe(b""));
334 assert!(!probe(b"\x89PNG\r\n\x1a\n"));
335 }
336
337 const LOSSY: &[u8] = include_bytes!("../tests/fixtures/lossy/alpha_blocks_q70.webp");
338
339 #[test]
340 fn opening_parses_the_header_and_leaves_the_pixels_for_the_first_row() {
341 let mut decoder = WebPDecoder::new(LOSSY, Limits::default()).unwrap();
342 assert_eq!(decoder.descriptor().pixel, PixelFormat::Rgba8);
344 assert!(decoder.pixels.is_none());
345
346 let mut row = vec![0_u8; decoder.descriptor().row_bytes()];
347 decoder.read_row(&mut row).unwrap();
348 assert!(decoder.pixels.is_some());
349 assert!(decoder.bytes.is_empty(), "the compressed file is released");
350 }
351
352 #[test]
353 fn a_bitstream_damaged_past_its_header_fails_on_the_first_row() {
354 let mut bytes = include_bytes!("../tests/fixtures/lossless/blocks_m6.webp").to_vec();
358 for byte in bytes.iter_mut().skip(25) {
359 *byte = 0xFF;
360 }
361 let mut decoder = WebPDecoder::new(&bytes[..], Limits::default()).unwrap();
362 let mut row = vec![0_u8; decoder.descriptor().row_bytes()];
363 let error = decoder.read_row(&mut row).unwrap_err();
364 assert_eq!(
365 error.code(),
366 otf_pixels_core::ErrorCode::Malformed,
367 "{error}"
368 );
369 }
370
371 #[test]
372 fn a_stream_that_is_not_a_webp_is_rejected() {
373 let error = WebPDecoder::new(&b"not a webp at all"[..], Limits::default()).unwrap_err();
374 assert_eq!(
375 error.code(),
376 otf_pixels_core::ErrorCode::Malformed,
377 "{error}"
378 );
379 }
380}