1use otf_pixels_core::{
9 Animation, Codec, DecodeCapability, Decoder, Format, ImageDescriptor, Limits, Orientation,
10 PixelFormat, PixelsError, Result, Source,
11};
12
13const MAX_COMPRESSED: usize = 256 * 1024 * 1024;
19
20#[derive(Debug)]
22pub struct WebPDecoder {
23 descriptor: ImageDescriptor,
24 pixels: Vec<u8>,
26 row: u32,
28 orientation: Orientation,
30 icc: Option<Vec<u8>>,
32 animation: Option<Animation>,
34}
35
36impl WebPDecoder {
37 pub fn new<S: Source>(mut source: S, limits: Limits) -> Result<Self> {
50 let mut bytes = Vec::new();
51 let mut chunk = [0_u8; 64 * 1024];
52 loop {
53 if bytes.len() > MAX_COMPRESSED {
54 return Err(PixelsError::malformed(
55 "webp",
56 format!("stream exceeds {MAX_COMPRESSED} bytes"),
57 ));
58 }
59 match source.read(&mut chunk)? {
60 0 => break,
61 read => {
62 let Some(filled) = chunk.get(..read) else {
63 break;
64 };
65 bytes.extend_from_slice(filled);
66 }
67 }
68 }
69
70 let container = crate::riff::parse(&bytes)?;
71 let orientation = container
73 .exif
74 .and_then(Orientation::from_exif_block)
75 .unwrap_or_default();
76 let pixel = if container.has_alpha {
77 PixelFormat::Rgba8
78 } else {
79 PixelFormat::Rgb8
80 };
81 let descriptor =
83 ImageDescriptor::with_limits(container.width, container.height, pixel, &limits)?;
84 let frame = container.frame;
85 let rgba = decode_rgba(
86 container.bitstream,
87 frame.width as usize,
88 frame.height as usize,
89 )?;
90
91 let channels = pixel.channels();
95 let (canvas_width, frame_width) = (container.width as usize, frame.width as usize);
96 let mut pixels =
97 vec![0_u8; container.width as usize * container.height as usize * channels];
98 for (y, source) in rgba.chunks_exact(frame_width * 4).enumerate() {
99 let row = (frame.y as usize + y) * canvas_width + frame.x as usize;
100 let Some(target) = pixels.get_mut(row * channels..(row + frame_width) * channels)
101 else {
102 return Err(PixelsError::malformed(
103 "webp",
104 "a frame overruns its canvas",
105 ));
106 };
107 for (out, sample) in target
108 .chunks_exact_mut(channels)
109 .zip(source.chunks_exact(4))
110 {
111 out.copy_from_slice(sample.get(..channels).unwrap_or(&[]));
112 }
113 }
114 Ok(Self {
115 descriptor,
116 pixels,
117 row: 0,
118 orientation,
119 icc: container.icc.map(<[u8]>::to_vec),
120 animation: Animation::new(container.frame_durations_ms.clone(), container.loop_count),
121 })
122 }
123}
124
125fn decode_rgba(
127 bitstream: crate::riff::Bitstream<'_>,
128 width: usize,
129 height: usize,
130) -> Result<Vec<u8>> {
131 match bitstream {
132 crate::riff::Bitstream::Lossless(stream) => {
133 let argb = crate::vp8l::decode(stream, width, height)?;
134 Ok(argb
135 .into_iter()
136 .flat_map(|p| {
137 let [blue, green, red, alpha] = p.to_le_bytes();
138 [red, green, blue, alpha]
139 })
140 .collect())
141 }
142 crate::riff::Bitstream::Lossy { vp8, alpha } => {
143 let frame = crate::vp8::decode(vp8)?;
144 if (frame.width, frame.height) != (width, height) {
145 return Err(PixelsError::malformed(
146 "webp",
147 "the VP8 frame's size differs from the container's",
148 ));
149 }
150 let alpha = match alpha {
152 Some(chunk) => crate::alpha::decode(chunk, width, height)?,
153 None => vec![255; width * height],
154 };
155 Ok(crate::yuv::to_rgb(
156 &frame.y,
157 frame.y_stride,
158 &frame.u,
159 &frame.v,
160 frame.uv_stride,
161 width,
162 height,
163 Some(&alpha),
164 ))
165 }
166 }
167}
168
169impl Decoder for WebPDecoder {
170 fn descriptor(&self) -> ImageDescriptor {
171 self.descriptor
172 }
173
174 fn orientation(&self) -> Orientation {
175 self.orientation
176 }
177
178 fn icc_profile(&self) -> Option<&[u8]> {
179 self.icc.as_deref()
180 }
181
182 fn animation(&self) -> Option<Animation> {
183 self.animation.clone()
184 }
185
186 fn capability(&self) -> DecodeCapability {
187 DecodeCapability::Sequential
191 }
192
193 fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
194 if self.row >= self.descriptor.height {
195 return Err(PixelsError::invalid_argument(
196 "out",
197 format!("all {} rows have already been read", self.descriptor.height),
198 ));
199 }
200 let row_bytes = self.descriptor.row_bytes();
201 if out.len() != row_bytes {
202 return Err(PixelsError::invalid_argument(
203 "out",
204 format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
205 ));
206 }
207 let start = self.row as usize * row_bytes;
208 let row = self
209 .pixels
210 .get(start..)
211 .and_then(|rest| rest.get(..row_bytes))
212 .ok_or_else(|| PixelsError::malformed("webp", "decoded image is short"))?;
213 out.copy_from_slice(row);
214 self.row += 1;
215 Ok(())
216 }
217}
218
219#[must_use]
225pub fn probe(prefix: &[u8]) -> bool {
226 prefix.get(..4) == Some(&crate::SIGNATURE_RIFF[..])
227 && prefix.get(8..12) == Some(&crate::SIGNATURE_WEBP[..])
228}
229
230#[derive(Debug, Clone, Copy, Default)]
232pub struct WebPCodec;
233
234impl Codec for WebPCodec {
235 fn format(&self) -> Format {
236 Format::WebP
237 }
238
239 fn magic_len(&self) -> usize {
240 12
241 }
242
243 fn probe(&self, prefix: &[u8]) -> bool {
244 probe(prefix)
245 }
246}
247
248#[cfg(test)]
249#[allow(
250 clippy::unwrap_used,
251 clippy::indexing_slicing,
252 reason = "tests operate on known-good values and assert shapes directly"
253)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn probe_needs_the_form_type_not_just_riff() {
259 let mut header = Vec::from(*b"RIFF");
260 header.extend_from_slice(&[0, 0, 0, 0]);
261 header.extend_from_slice(b"WEBP");
262 assert!(probe(&header));
263
264 let mut wav = Vec::from(*b"RIFF");
266 wav.extend_from_slice(&[0, 0, 0, 0]);
267 wav.extend_from_slice(b"WAVE");
268 assert!(!probe(&wav));
269
270 assert!(!probe(b"RIFF"));
272 assert!(!probe(b""));
273 assert!(!probe(b"\x89PNG\r\n\x1a\n"));
274 }
275
276 #[test]
277 fn a_stream_that_is_not_a_webp_is_rejected() {
278 let error = WebPDecoder::new(&b"not a webp at all"[..], Limits::default()).unwrap_err();
279 assert_eq!(
280 error.code(),
281 otf_pixels_core::ErrorCode::Malformed,
282 "{error}"
283 );
284 }
285}