1use crate::decoder::alpha::{parse_alpha_header, AlphaHeader};
4use crate::decoder::vp8::{get_info, get_lossless_info};
5use crate::decoder::vp8i::{
6 WebpFormat, ALPHA_FLAG, ANIMATION_FLAG, CHUNK_HEADER_SIZE, MAX_CHUNK_PAYLOAD, MAX_IMAGE_AREA,
7 RIFF_HEADER_SIZE, TAG_SIZE, VP8X_CHUNK_SIZE,
8};
9use crate::decoder::DecoderError;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct ChunkHeader {
14 pub fourcc: [u8; 4],
16 pub offset: usize,
18 pub size: usize,
20 pub padded_size: usize,
22 pub data_offset: usize,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct Vp8xHeader {
29 pub flags: u32,
31 pub canvas_width: usize,
33 pub canvas_height: usize,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub struct WebpFeatures {
40 pub width: usize,
42 pub height: usize,
44 pub has_alpha: bool,
46 pub has_animation: bool,
48 pub format: WebpFormat,
50 pub vp8x: Option<Vp8xHeader>,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct ParsedWebp<'a> {
57 pub features: WebpFeatures,
59 pub riff_size: Option<usize>,
61 pub image_chunk: ChunkHeader,
63 pub image_data: &'a [u8],
65 pub alpha_chunk: Option<ChunkHeader>,
67 pub alpha_data: Option<&'a [u8]>,
69 pub alpha_header: Option<AlphaHeader>,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub struct AnimationHeader {
76 pub background_color: u32,
78 pub loop_count: u16,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub struct ParsedAnimationFrame<'a> {
85 pub frame_chunk: ChunkHeader,
87 pub x_offset: usize,
89 pub y_offset: usize,
91 pub width: usize,
93 pub height: usize,
95 pub duration: usize,
97 pub blend: bool,
99 pub dispose_to_background: bool,
101 pub image_chunk: ChunkHeader,
103 pub image_data: &'a [u8],
105 pub alpha_chunk: Option<ChunkHeader>,
107 pub alpha_data: Option<&'a [u8]>,
109 pub alpha_header: Option<AlphaHeader>,
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
115pub struct ParsedAnimationWebp<'a> {
116 pub features: WebpFeatures,
118 pub riff_size: Option<usize>,
120 pub animation: AnimationHeader,
122 pub frames: Vec<ParsedAnimationFrame<'a>>,
124}
125
126fn read_le24(bytes: &[u8]) -> usize {
127 bytes[0] as usize | ((bytes[1] as usize) << 8) | ((bytes[2] as usize) << 16)
128}
129
130fn read_le32(bytes: &[u8]) -> usize {
131 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize
132}
133
134fn read_le16(bytes: &[u8]) -> u16 {
135 u16::from_le_bytes([bytes[0], bytes[1]])
136}
137
138fn padded_payload_size(size: usize) -> usize {
139 size + (size & 1)
140}
141
142fn parse_chunk(
143 data: &[u8],
144 offset: usize,
145 riff_limit: Option<usize>,
146) -> Result<ChunkHeader, DecoderError> {
147 if data.len() < offset + CHUNK_HEADER_SIZE {
148 return Err(DecoderError::NotEnoughData("chunk header"));
149 }
150 let size = read_le32(&data[offset + TAG_SIZE..offset + CHUNK_HEADER_SIZE]);
151 if size > MAX_CHUNK_PAYLOAD {
152 return Err(DecoderError::Bitstream("invalid chunk size"));
153 }
154
155 let padded_size = padded_payload_size(size);
156 let total_size = CHUNK_HEADER_SIZE + padded_size;
157 let end = offset + total_size;
158 if let Some(limit) = riff_limit {
159 if end > limit {
160 return Err(DecoderError::Bitstream("chunk exceeds RIFF payload"));
161 }
162 }
163 if data.len() < end {
164 return Err(DecoderError::NotEnoughData("chunk payload"));
165 }
166
167 Ok(ChunkHeader {
168 fourcc: data[offset..offset + TAG_SIZE].try_into().unwrap(),
169 offset,
170 size,
171 padded_size,
172 data_offset: offset + CHUNK_HEADER_SIZE,
173 })
174}
175
176fn parse_riff(data: &[u8]) -> Result<(Option<usize>, usize), DecoderError> {
177 if data.len() < RIFF_HEADER_SIZE {
178 return Err(DecoderError::NotEnoughData("RIFF header"));
179 }
180 if &data[..4] != b"RIFF" {
181 return Ok((None, 0));
182 }
183 if &data[8..12] != b"WEBP" {
184 return Err(DecoderError::Bitstream("wrong RIFF WEBP signature"));
185 }
186
187 let riff_size = read_le32(&data[4..8]);
188 if riff_size < TAG_SIZE + CHUNK_HEADER_SIZE {
189 return Err(DecoderError::Bitstream("RIFF payload is too small"));
190 }
191 if riff_size > MAX_CHUNK_PAYLOAD {
192 return Err(DecoderError::Bitstream("RIFF payload is too large"));
193 }
194 if riff_size > data.len() - CHUNK_HEADER_SIZE {
195 return Err(DecoderError::NotEnoughData("truncated RIFF payload"));
196 }
197
198 Ok((Some(riff_size), RIFF_HEADER_SIZE))
199}
200
201fn parse_vp8x(data: &[u8], offset: usize) -> Result<(Option<Vp8xHeader>, usize), DecoderError> {
202 if data.len() < offset + CHUNK_HEADER_SIZE {
203 return Ok((None, offset));
204 }
205 if &data[offset..offset + TAG_SIZE] != b"VP8X" {
206 return Ok((None, offset));
207 }
208
209 let chunk = parse_chunk(data, offset, None)?;
210 if chunk.size != VP8X_CHUNK_SIZE {
211 return Err(DecoderError::Bitstream("wrong VP8X chunk size"));
212 }
213
214 let flags = read_le32(&data[offset + 8..offset + 12]) as u32;
215 let canvas_width = read_le24(&data[offset + 12..offset + 15]) + 1;
216 let canvas_height = read_le24(&data[offset + 15..offset + 18]) + 1;
217 if (canvas_width as u64) * (canvas_height as u64) >= MAX_IMAGE_AREA {
218 return Err(DecoderError::Bitstream("canvas is too large"));
219 }
220
221 Ok((
222 Some(Vp8xHeader {
223 flags,
224 canvas_width,
225 canvas_height,
226 }),
227 offset + CHUNK_HEADER_SIZE + chunk.padded_size,
228 ))
229}
230
231pub fn get_features(data: &[u8]) -> Result<WebpFeatures, DecoderError> {
233 let (riff_size, mut offset) = parse_riff(data)?;
234 let riff_limit = riff_size.map(|size| size + CHUNK_HEADER_SIZE);
235
236 let (vp8x, next_offset) = parse_vp8x(data, offset)?;
237 offset = next_offset;
238 if riff_size.is_none() && vp8x.is_some() {
239 return Err(DecoderError::Bitstream("VP8X chunk requires RIFF"));
240 }
241
242 let mut has_alpha = vp8x
243 .map(|chunk| (chunk.flags & ALPHA_FLAG) != 0)
244 .unwrap_or(false);
245 let has_animation = vp8x
246 .map(|chunk| (chunk.flags & ANIMATION_FLAG) != 0)
247 .unwrap_or(false);
248
249 if let Some(vp8x) = vp8x {
250 if has_animation {
251 return Ok(WebpFeatures {
252 width: vp8x.canvas_width,
253 height: vp8x.canvas_height,
254 has_alpha,
255 has_animation,
256 format: WebpFormat::Undefined,
257 vp8x: Some(vp8x),
258 });
259 }
260 }
261
262 if data.len() < offset + TAG_SIZE {
263 return Err(DecoderError::NotEnoughData("chunk tag"));
264 }
265
266 if (riff_size.is_some() && vp8x.is_some())
267 || (riff_size.is_none() && vp8x.is_none() && &data[offset..offset + TAG_SIZE] == b"ALPH")
268 {
269 loop {
270 let chunk = parse_chunk(data, offset, riff_limit)?;
271 if &chunk.fourcc == b"VP8 " || &chunk.fourcc == b"VP8L" {
272 break;
273 }
274 if &chunk.fourcc == b"ALPH" {
275 has_alpha = true;
276 }
277 offset += CHUNK_HEADER_SIZE + chunk.padded_size;
278 }
279 }
280
281 let chunk = parse_chunk(data, offset, riff_limit)?;
282 let payload = &data[chunk.data_offset..chunk.data_offset + chunk.size];
283 let (format, width, height) = if &chunk.fourcc == b"VP8 " {
284 let (width, height) = get_info(payload, chunk.size)?;
285 (WebpFormat::Lossy, width, height)
286 } else if &chunk.fourcc == b"VP8L" {
287 let info = get_lossless_info(payload)?;
288 has_alpha |= info.has_alpha;
289 (WebpFormat::Lossless, info.width, info.height)
290 } else {
291 return Err(DecoderError::Bitstream("missing VP8/VP8L image chunk"));
292 };
293
294 if let Some(vp8x) = vp8x {
295 if vp8x.canvas_width != width || vp8x.canvas_height != height {
296 return Err(DecoderError::Bitstream(
297 "VP8X canvas does not match image size",
298 ));
299 }
300 }
301
302 Ok(WebpFeatures {
303 width,
304 height,
305 has_alpha,
306 has_animation,
307 format,
308 vp8x,
309 })
310}
311
312pub fn parse_still_webp(data: &[u8]) -> Result<ParsedWebp<'_>, DecoderError> {
314 let (riff_size, mut offset) = parse_riff(data)?;
315 let riff_limit = riff_size.map(|size| size + CHUNK_HEADER_SIZE);
316
317 let (vp8x, next_offset) = parse_vp8x(data, offset)?;
318 offset = next_offset;
319 if riff_size.is_none() && vp8x.is_some() {
320 return Err(DecoderError::Bitstream("VP8X chunk requires RIFF"));
321 }
322 if vp8x
323 .map(|chunk| (chunk.flags & ANIMATION_FLAG) != 0)
324 .unwrap_or(false)
325 {
326 return Err(DecoderError::Unsupported(
327 "animated WebP is not implemented",
328 ));
329 }
330
331 let mut alpha_chunk = None;
332 if data.len() < offset + TAG_SIZE {
333 return Err(DecoderError::NotEnoughData("chunk tag"));
334 }
335 if (riff_size.is_some() && vp8x.is_some())
336 || (riff_size.is_none() && vp8x.is_none() && &data[offset..offset + TAG_SIZE] == b"ALPH")
337 {
338 loop {
339 let chunk = parse_chunk(data, offset, riff_limit)?;
340 if &chunk.fourcc == b"VP8 " || &chunk.fourcc == b"VP8L" {
341 break;
342 }
343 if &chunk.fourcc == b"ALPH" {
344 alpha_chunk = Some(chunk);
345 }
346 offset += CHUNK_HEADER_SIZE + chunk.padded_size;
347 }
348 }
349
350 let image_chunk = parse_chunk(data, offset, riff_limit)?;
351 if &image_chunk.fourcc != b"VP8 " && &image_chunk.fourcc != b"VP8L" {
352 return Err(DecoderError::Bitstream("missing VP8/VP8L image chunk"));
353 }
354 let image_data = &data[image_chunk.data_offset..image_chunk.data_offset + image_chunk.size];
355 let mut features = get_features(data)?;
356 let alpha_data =
357 alpha_chunk.map(|chunk| &data[chunk.data_offset..chunk.data_offset + chunk.size]);
358 let alpha_header = alpha_data.map(parse_alpha_header).transpose()?;
359 if alpha_chunk.is_some() {
360 features.has_alpha = true;
361 }
362
363 Ok(ParsedWebp {
364 features,
365 riff_size,
366 image_chunk,
367 image_data,
368 alpha_chunk,
369 alpha_data,
370 alpha_header,
371 })
372}
373
374fn parse_animation_frame<'a>(
375 data: &'a [u8],
376 features: WebpFeatures,
377 chunk: ChunkHeader,
378 riff_limit: Option<usize>,
379) -> Result<ParsedAnimationFrame<'a>, DecoderError> {
380 if chunk.size < 16 {
381 return Err(DecoderError::Bitstream("ANMF chunk is too small"));
382 }
383
384 let header = &data[chunk.data_offset..chunk.data_offset + 16];
385 let x_offset = read_le24(&header[0..3]) * 2;
386 let y_offset = read_le24(&header[3..6]) * 2;
387 let width = read_le24(&header[6..9]) + 1;
388 let height = read_le24(&header[9..12]) + 1;
389 let duration = read_le24(&header[12..15]);
390 let flags = header[15];
391 if flags >> 2 != 0 {
392 return Err(DecoderError::Bitstream("ANMF reserved bits must be zero"));
393 }
394 if x_offset + width > features.width || y_offset + height > features.height {
395 return Err(DecoderError::Bitstream(
396 "ANMF frame exceeds animation canvas",
397 ));
398 }
399
400 let mut offset = chunk.data_offset + 16;
401 let frame_limit = Some(chunk.data_offset + chunk.size);
402 let mut alpha_chunk = None;
403 let image_chunk;
404 loop {
405 let subchunk = parse_chunk(data, offset, frame_limit)?;
406 if &subchunk.fourcc == b"VP8 " || &subchunk.fourcc == b"VP8L" {
407 image_chunk = subchunk;
408 break;
409 }
410 if &subchunk.fourcc == b"ALPH" {
411 alpha_chunk = Some(subchunk);
412 }
413 offset += CHUNK_HEADER_SIZE + subchunk.padded_size;
414 if let Some(limit) = riff_limit {
415 if offset > limit {
416 return Err(DecoderError::Bitstream(
417 "ANMF frame data exceeds RIFF payload",
418 ));
419 }
420 }
421 }
422
423 let image_data = &data[image_chunk.data_offset..image_chunk.data_offset + image_chunk.size];
424 let alpha_data = alpha_chunk
425 .map(|subchunk| &data[subchunk.data_offset..subchunk.data_offset + subchunk.size]);
426 let alpha_header = alpha_data.map(parse_alpha_header).transpose()?;
427
428 Ok(ParsedAnimationFrame {
429 frame_chunk: chunk,
430 x_offset,
431 y_offset,
432 width,
433 height,
434 duration,
435 blend: (flags & 0x02) == 0,
436 dispose_to_background: (flags & 0x01) != 0,
437 image_chunk,
438 image_data,
439 alpha_chunk,
440 alpha_data,
441 alpha_header,
442 })
443}
444
445pub fn parse_animation_webp(data: &[u8]) -> Result<ParsedAnimationWebp<'_>, DecoderError> {
447 let (riff_size, mut offset) = parse_riff(data)?;
448 let riff_limit = riff_size.map(|size| size + CHUNK_HEADER_SIZE);
449
450 let (vp8x, next_offset) = parse_vp8x(data, offset)?;
451 offset = next_offset;
452 let vp8x = vp8x.ok_or(DecoderError::Bitstream("animated WebP requires VP8X"))?;
453 if (vp8x.flags & ANIMATION_FLAG) == 0 {
454 return Err(DecoderError::Unsupported("animated WebP flag is not set"));
455 }
456
457 let anim_chunk = parse_chunk(data, offset, riff_limit)?;
458 if &anim_chunk.fourcc != b"ANIM" {
459 return Err(DecoderError::Bitstream("missing ANIM chunk"));
460 }
461 if anim_chunk.size != 6 {
462 return Err(DecoderError::Bitstream("wrong ANIM chunk size"));
463 }
464 let animation = AnimationHeader {
465 background_color: u32::from_le_bytes(
466 data[anim_chunk.data_offset..anim_chunk.data_offset + 4]
467 .try_into()
468 .unwrap(),
469 ),
470 loop_count: read_le16(&data[anim_chunk.data_offset + 4..anim_chunk.data_offset + 6]),
471 };
472 offset += CHUNK_HEADER_SIZE + anim_chunk.padded_size;
473
474 let features = WebpFeatures {
475 width: vp8x.canvas_width,
476 height: vp8x.canvas_height,
477 has_alpha: (vp8x.flags & ALPHA_FLAG) != 0,
478 has_animation: true,
479 format: WebpFormat::Undefined,
480 vp8x: Some(vp8x),
481 };
482
483 let limit = riff_limit.unwrap_or(data.len());
484 let mut frames = Vec::new();
485 while offset + CHUNK_HEADER_SIZE <= limit {
486 let chunk = parse_chunk(data, offset, riff_limit)?;
487 if &chunk.fourcc != b"ANMF" {
488 break;
489 }
490 let frame = parse_animation_frame(data, features, chunk, riff_limit)?;
491 frames.push(frame);
492 offset += CHUNK_HEADER_SIZE + chunk.padded_size;
493 }
494
495 if frames.is_empty() {
496 return Err(DecoderError::Bitstream("animated WebP has no ANMF frames"));
497 }
498
499 Ok(ParsedAnimationWebp {
500 features,
501 riff_size,
502 animation,
503 frames,
504 })
505}