1use crate::decoder::lossless::decode_lossless_webp_to_rgba;
8use crate::decoder::lossy::{decode_lossy_vp8_frame_to_rgba, decode_lossy_webp_to_rgba};
9use crate::decoder::{
10 get_features, parse_animation_webp, DecodedImage, DecoderError, ParsedAnimationFrame,
11 WebpFormat,
12};
13use bin_rs::io::read_u32_le;
14use bin_rs::reader::BinaryReader;
15use std::collections::HashMap;
16
17type Error = Box<dyn std::error::Error>;
18
19pub type Metadata = HashMap<String, DataMap>;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum DataMap {
25 UInt(u64),
26 UIntAllay(Vec<u64>),
27 Raw(Vec<u8>),
28 Ascii(String),
29 None,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct RGBA {
35 pub red: u8,
36 pub green: u8,
37 pub blue: u8,
38 pub alpha: u8,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum ResponseCommand {
44 Abort,
45 Continue,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub struct CallbackResponse {
51 pub response: ResponseCommand,
52}
53
54impl CallbackResponse {
55 pub fn abort() -> Self {
56 Self {
57 response: ResponseCommand::Abort,
58 }
59 }
60
61 pub fn cont() -> Self {
62 Self {
63 response: ResponseCommand::Continue,
64 }
65 }
66}
67
68pub trait DrawCallback: Sync + Send {
70 fn init(
71 &mut self,
72 width: usize,
73 height: usize,
74 option: Option<InitOptions>,
75 ) -> Result<Option<CallbackResponse>, Error>;
76 fn draw(
77 &mut self,
78 start_x: usize,
79 start_y: usize,
80 width: usize,
81 height: usize,
82 data: &[u8],
83 option: Option<DrawOptions>,
84 ) -> Result<Option<CallbackResponse>, Error>;
85 fn terminate(
86 &mut self,
87 term: Option<TerminateOptions>,
88 ) -> Result<Option<CallbackResponse>, Error>;
89 fn next(&mut self, next: Option<NextOptions>) -> Result<Option<CallbackResponse>, Error>;
90 fn verbose(
91 &mut self,
92 verbose: &str,
93 option: Option<VerboseOptions>,
94 ) -> Result<Option<CallbackResponse>, Error>;
95 fn set_metadata(
96 &mut self,
97 key: &str,
98 value: DataMap,
99 ) -> Result<Option<CallbackResponse>, Error>;
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct InitOptions {
105 pub loop_count: u32,
106 pub background: Option<RGBA>,
107 pub animation: bool,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
112pub struct DrawOptions {}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
116pub struct TerminateOptions {}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
120pub struct VerboseOptions {}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum NextOption {
125 Continue,
126 Next,
127 Dispose,
128 ClearAbort,
129 Terminate,
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum NextDispose {
135 None,
136 Override,
137 Background,
138 Previous,
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143pub enum NextBlend {
144 Source,
145 Override,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct ImageRect {
151 pub start_x: i32,
152 pub start_y: i32,
153 pub width: usize,
154 pub height: usize,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
159pub struct NextOptions {
160 pub flag: NextOption,
161 pub await_time: u64,
162 pub image_rect: Option<ImageRect>,
163 pub dispose_option: Option<NextDispose>,
164 pub blend: Option<NextBlend>,
165}
166
167pub struct DecodeOptions<'a> {
169 pub debug_flag: usize,
170 pub drawer: &'a mut dyn DrawCallback,
171 pub options: Option<Metadata>,
172}
173
174impl<'a> DecodeOptions<'a> {
175 pub fn new(drawer: &'a mut dyn DrawCallback) -> Self {
176 Self {
177 debug_flag: 0,
178 drawer,
179 options: None,
180 }
181 }
182}
183
184fn argb_to_rgba(argb: u32) -> RGBA {
185 RGBA {
186 red: ((argb >> 16) & 0xff) as u8,
187 green: ((argb >> 8) & 0xff) as u8,
188 blue: (argb & 0xff) as u8,
189 alpha: (argb >> 24) as u8,
190 }
191}
192
193fn map_error(error: DecoderError) -> Error {
194 Box::new(error)
195}
196
197fn read_container<B: BinaryReader>(reader: &mut B) -> Result<Vec<u8>, Error> {
198 let header = reader.read_bytes_no_move(12)?;
199 if header.len() < 12 || &header[0..4] != b"RIFF" || &header[8..12] != b"WEBP" {
200 return Err(Box::new(std::io::Error::new(
201 std::io::ErrorKind::InvalidData,
202 "not a WebP RIFF container",
203 )));
204 }
205
206 let riff_size = read_u32_le(&header, 4) as usize;
207 let total_size = riff_size + 8;
208 if total_size < 12 {
209 return Err(Box::new(std::io::Error::new(
210 std::io::ErrorKind::InvalidData,
211 "invalid WebP container length",
212 )));
213 }
214
215 Ok(reader.read_bytes_as_vec(total_size)?)
216}
217
218fn next_options(frame: &ParsedAnimationFrame<'_>) -> NextOptions {
219 NextOptions {
220 flag: NextOption::Continue,
221 await_time: frame.duration as u64,
222 image_rect: Some(ImageRect {
223 start_x: frame.x_offset as i32,
224 start_y: frame.y_offset as i32,
225 width: frame.width,
226 height: frame.height,
227 }),
228 dispose_option: Some(if frame.dispose_to_background {
229 NextDispose::Background
230 } else {
231 NextDispose::None
232 }),
233 blend: Some(if frame.blend {
234 NextBlend::Source
235 } else {
236 NextBlend::Override
237 }),
238 }
239}
240
241fn decode_frame_rgba(frame: &ParsedAnimationFrame<'_>) -> Result<DecodedImage, DecoderError> {
242 let image = match &frame.image_chunk.fourcc {
243 b"VP8L" => {
244 if frame.alpha_chunk.is_some() {
245 return Err(DecoderError::Bitstream(
246 "VP8L animation frame must not carry ALPH chunk",
247 ));
248 }
249 crate::decoder::decode_lossless_vp8l_to_rgba(frame.image_data)?
250 }
251 b"VP8 " => decode_lossy_vp8_frame_to_rgba(frame.image_data, frame.alpha_data)?,
252 _ => return Err(DecoderError::Bitstream("unsupported animation frame chunk")),
253 };
254
255 if image.width != frame.width || image.height != frame.height {
256 return Err(DecoderError::Bitstream(
257 "animation frame dimensions do not match bitstream",
258 ));
259 }
260 Ok(image)
261}
262
263fn read_le32(bytes: &[u8]) -> usize {
264 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize
265}
266
267type ScannedChunk<'a> = ([u8; 4], &'a [u8]);
268
269fn scan_chunks(data: &[u8]) -> Result<Vec<ScannedChunk<'_>>, DecoderError> {
270 if data.len() < 12 {
271 return Err(DecoderError::NotEnoughData("RIFF header"));
272 }
273 if &data[0..4] != b"RIFF" || &data[8..12] != b"WEBP" {
274 return Err(DecoderError::Bitstream("wrong RIFF WEBP signature"));
275 }
276
277 let riff_size = read_le32(&data[4..8]);
278 let limit = riff_size + 8;
279 if limit > data.len() {
280 return Err(DecoderError::NotEnoughData("truncated RIFF payload"));
281 }
282
283 let mut offset = 12;
284 let mut chunks = Vec::new();
285 while offset + 8 <= limit {
286 let size = read_le32(&data[offset + 4..offset + 8]);
287 let padded_size = size + (size & 1);
288 let chunk_end = offset + 8 + padded_size;
289 if chunk_end > limit {
290 return Err(DecoderError::NotEnoughData("chunk payload"));
291 }
292
293 let fourcc: [u8; 4] = data[offset..offset + 4]
294 .try_into()
295 .expect("valid fourcc slice");
296 let payload = &data[offset + 8..offset + 8 + size];
297 chunks.push((fourcc, payload));
298 offset = chunk_end;
299 }
300
301 Ok(chunks)
302}
303
304fn webp_codec_name(format: WebpFormat, animated: bool) -> &'static str {
305 if animated {
306 "Animated"
307 } else {
308 match format {
309 WebpFormat::Lossy => "Lossy",
310 WebpFormat::Lossless => "Lossless",
311 WebpFormat::Undefined => "Undefined",
312 }
313 }
314}
315
316fn make_metadata(data: &[u8]) -> Result<Metadata, DecoderError> {
317 let features = get_features(data)?;
318 let chunks = scan_chunks(data)?;
319 let mut map = HashMap::new();
320
321 map.insert("Format".to_string(), DataMap::Ascii("WEBP".to_string()));
322 map.insert("width".to_string(), DataMap::UInt(features.width as u64));
323 map.insert("height".to_string(), DataMap::UInt(features.height as u64));
324 map.insert(
325 "WebP codec".to_string(),
326 DataMap::Ascii(webp_codec_name(features.format, features.has_animation).to_string()),
327 );
328 map.insert(
329 "WebP has alpha".to_string(),
330 DataMap::Ascii(features.has_alpha.to_string()),
331 );
332 map.insert(
333 "WebP animated".to_string(),
334 DataMap::Ascii(features.has_animation.to_string()),
335 );
336
337 if let Some(vp8x) = features.vp8x {
338 map.insert(
339 "canvas width".to_string(),
340 DataMap::UInt(vp8x.canvas_width as u64),
341 );
342 map.insert(
343 "canvas height".to_string(),
344 DataMap::UInt(vp8x.canvas_height as u64),
345 );
346 }
347
348 if features.has_animation {
349 let parsed = parse_animation_webp(data)?;
350 map.insert(
351 "Animation frames".to_string(),
352 DataMap::UInt(parsed.frames.len() as u64),
353 );
354 map.insert(
355 "Animation loop count".to_string(),
356 DataMap::UInt(parsed.animation.loop_count as u64),
357 );
358 map.insert(
359 "Animation background color".to_string(),
360 DataMap::UInt(parsed.animation.background_color as u64),
361 );
362 map.insert(
363 "Animation frame durations".to_string(),
364 DataMap::UIntAllay(
365 parsed
366 .frames
367 .iter()
368 .map(|frame| frame.duration as u64)
369 .collect(),
370 ),
371 );
372 }
373
374 for (fourcc, payload) in chunks {
375 match &fourcc {
376 b"ICCP" => {
377 map.insert("ICC Profile".to_string(), DataMap::Raw(payload.to_vec()));
378 }
379 b"EXIF" => {
380 map.insert("EXIF Raw".to_string(), DataMap::Raw(payload.to_vec()));
381 }
382 b"XMP " => match String::from_utf8(payload.to_vec()) {
383 Ok(xmp) => {
384 map.insert("XMP".to_string(), DataMap::Ascii(xmp));
385 }
386 Err(_) => {
387 map.insert("XMP Raw".to_string(), DataMap::Raw(payload.to_vec()));
388 }
389 },
390 _ => {}
391 }
392 }
393
394 Ok(map)
395}
396
397pub fn decode<B: BinaryReader>(
400 reader: &mut B,
401 option: &mut DecodeOptions<'_>,
402) -> Result<(), Error> {
403 let data = read_container(reader)?;
404 let metadata = make_metadata(&data).map_err(map_error)?;
405 let features = get_features(&data).map_err(map_error)?;
406
407 if features.has_animation {
408 let parsed = parse_animation_webp(&data).map_err(map_error)?;
409 let init = InitOptions {
410 loop_count: parsed.animation.loop_count as u32,
411 background: Some(argb_to_rgba(parsed.animation.background_color)),
412 animation: true,
413 };
414 option
415 .drawer
416 .init(parsed.features.width, parsed.features.height, Some(init))?;
417
418 let mut allow_multi_image = false;
419 for (index, frame) in parsed.frames.iter().enumerate() {
420 let decoded = decode_frame_rgba(frame).map_err(map_error)?;
421 if index == 0 {
422 option.drawer.draw(
423 frame.x_offset,
424 frame.y_offset,
425 frame.width,
426 frame.height,
427 &decoded.rgba,
428 None,
429 )?;
430
431 let result = option.drawer.next(Some(next_options(frame)))?;
432 if let Some(response) = result {
433 if response.response == ResponseCommand::Continue {
434 allow_multi_image = true;
435 option
436 .drawer
437 .draw(0, 0, frame.width, frame.height, &decoded.rgba, None)?;
438 }
439 }
440 continue;
441 }
442
443 if !allow_multi_image {
444 continue;
445 }
446
447 let result = option.drawer.next(Some(next_options(frame)))?;
448 if let Some(response) = result {
449 if response.response == ResponseCommand::Abort {
450 break;
451 }
452 }
453
454 option
455 .drawer
456 .draw(0, 0, frame.width, frame.height, &decoded.rgba, None)?;
457 }
458 } else {
459 let init = InitOptions {
460 loop_count: 0,
461 background: None,
462 animation: false,
463 };
464 option
465 .drawer
466 .init(features.width, features.height, Some(init))?;
467
468 let decoded = match features.format {
469 WebpFormat::Lossy => decode_lossy_webp_to_rgba(&data).map_err(map_error)?,
470 WebpFormat::Lossless => decode_lossless_webp_to_rgba(&data).map_err(map_error)?,
471 WebpFormat::Undefined => {
472 return Err(Box::new(std::io::Error::new(
473 std::io::ErrorKind::Unsupported,
474 "unsupported WebP format",
475 )));
476 }
477 };
478
479 option
480 .drawer
481 .draw(0, 0, decoded.width, decoded.height, &decoded.rgba, None)?;
482 }
483
484 for (key, value) in metadata {
485 option.drawer.set_metadata(&key, value)?;
486 }
487 option.drawer.terminate(None)?;
488
489 Ok(())
490}