1#![allow(dead_code)]
55
56use super::image_codec::RawDecodeResult;
57
58#[derive(Debug, thiserror::Error)]
62pub enum WebpError {
63 #[error("Invalid WebP: {0}")]
64 Invalid(String),
65 #[error("Unsupported WebP feature: {0}")]
66 Unsupported(String),
67 #[error("Truncated data")]
68 Truncated,
69}
70
71struct BitWriter {
74 data: Vec<u8>,
75 cur_byte: u32,
76 bit_count: u8, }
78
79impl BitWriter {
80 fn new() -> Self {
81 Self {
82 data: Vec::new(),
83 cur_byte: 0,
84 bit_count: 0,
85 }
86 }
87
88 fn write_bits(&mut self, value: u64, n: u8) {
90 debug_assert!(n <= 64);
91 let mut remaining = n;
92 let mut val = value;
93 while remaining > 0 {
94 let can_write = 8 - self.bit_count;
95 let to_write = remaining.min(can_write);
96 let mask = (1u64 << to_write) - 1;
97 let bits = (val & mask) as u32;
98 self.cur_byte |= bits << self.bit_count;
99 self.bit_count += to_write;
100 val >>= to_write;
101 remaining -= to_write;
102 if self.bit_count == 8 {
103 self.data.push(self.cur_byte as u8);
104 self.cur_byte = 0;
105 self.bit_count = 0;
106 }
107 }
108 }
109
110 fn flush(&mut self) {
112 if self.bit_count > 0 {
113 self.data.push(self.cur_byte as u8);
114 self.cur_byte = 0;
115 self.bit_count = 0;
116 }
117 }
118
119 fn into_bytes(mut self) -> Vec<u8> {
120 self.flush();
121 self.data
122 }
123}
124
125struct BitReader<'a> {
128 data: &'a [u8],
129 byte_pos: usize,
130 bit_pos: u8, }
132
133impl<'a> BitReader<'a> {
134 fn new(data: &'a [u8]) -> Self {
135 Self {
136 data,
137 byte_pos: 0,
138 bit_pos: 0,
139 }
140 }
141
142 fn read_bits(&mut self, n: u8) -> Result<u64, WebpError> {
144 debug_assert!(n <= 64);
145 let mut result: u64 = 0;
146 let mut filled = 0u8;
147 let mut remaining = n;
148 while remaining > 0 {
149 if self.byte_pos >= self.data.len() {
150 return Err(WebpError::Truncated);
151 }
152 let cur = self.data[self.byte_pos];
153 let available = 8 - self.bit_pos;
154 let to_read = remaining.min(available);
155 let mask = if to_read == 8 {
156 0xFFu8
157 } else {
158 (1u8 << to_read) - 1
159 };
160 let bits = ((cur >> self.bit_pos) & mask) as u64;
161 result |= bits << filled;
162 filled += to_read;
163 self.bit_pos += to_read;
164 remaining -= to_read;
165 if self.bit_pos == 8 {
166 self.byte_pos += 1;
167 self.bit_pos = 0;
168 }
169 }
170 Ok(result)
171 }
172}
173
174fn compute_code_lengths(freqs: &[u32], alphabet_size: usize, max_bits: u8) -> Vec<u8> {
185 use std::cmp::Reverse;
186 use std::collections::BinaryHeap;
187
188 let mut lengths = vec![0u8; alphabet_size];
189
190 let mut non_zero: Vec<usize> = (0..alphabet_size)
192 .filter(|&i| freqs.get(i).copied().unwrap_or(0) > 0)
193 .collect();
194
195 match non_zero.len() {
196 0 => return lengths,
197 1 => {
198 lengths[non_zero[0]] = 1;
199 return lengths;
200 }
201 _ => {}
202 }
203
204 non_zero.sort_unstable_by_key(|&i| (freqs[i], i));
205
206 let n = non_zero.len();
207
208 let mut node_freq = vec![0u64; 2 * n];
212 let mut depth = vec![0u8; 2 * n];
213
214 for (i, &sym) in non_zero.iter().enumerate() {
215 node_freq[i] = freqs[sym] as u64;
216 }
217
218 let mut heap: BinaryHeap<Reverse<(u64, usize, usize)>> =
220 (0..n).map(|i| Reverse((node_freq[i], i, i))).collect();
221
222 let mut next_node = n;
223 let mut counter = n;
224 let mut parent = vec![usize::MAX; 2 * n];
225
226 while heap.len() > 1 {
227 let Reverse((f1, _, id1)) = heap.pop().expect("heap non-empty");
228 let Reverse((f2, _, id2)) = heap.pop().expect("heap non-empty");
229
230 let new_id = next_node;
231 next_node += 1;
232 node_freq[new_id] = f1 + f2;
233 depth[new_id] = 0; parent[id1] = new_id;
235 parent[id2] = new_id;
236
237 heap.push(Reverse((node_freq[new_id], counter, new_id)));
238 counter += 1;
239 }
240
241 let root = if next_node > n { next_node - 1 } else { 0 };
243 parent[root] = root;
244
245 for i in 0..n {
246 let mut d = 0u8;
247 let mut cur = i;
248 while parent[cur] != cur {
249 d += 1;
250 cur = parent[cur];
251 if d > max_bits + 1 {
252 break; }
254 }
255 lengths[non_zero[i]] = d.min(max_bits);
256 }
257
258 let scale: i64 = 1i64 << max_bits;
272 loop {
273 let kraft: i64 = non_zero
274 .iter()
275 .map(|&sym| {
276 let l = lengths[sym] as u32;
277 if l > 0 {
278 scale >> l
279 } else {
280 0i64
281 }
282 })
283 .sum();
284
285 if kraft <= scale {
286 break;
287 }
288
289 let mut changed = false;
292 for &sym in non_zero.iter() {
293 if lengths[sym] < max_bits {
294 lengths[sym] += 1;
295 changed = true;
296 break;
297 }
298 }
299 if !changed {
300 break; }
302 }
303
304 lengths
305}
306
307fn canonical_codes_from_lengths(lengths: &[u8]) -> Vec<(u16, u8)> {
320 let alphabet_size = lengths.len();
321 let max_len = *lengths.iter().max().unwrap_or(&0) as usize;
322 let mut codes = vec![(0u16, 0u8); alphabet_size];
323
324 if max_len == 0 {
325 return codes;
326 }
327
328 let mut bl_count = vec![0u32; max_len + 1];
330 for &l in lengths {
331 if l > 0 {
332 bl_count[l as usize] += 1;
333 }
334 }
335
336 let mut next_code = vec![0u32; max_len + 2];
338 let mut code: u32 = 0;
339 bl_count[0] = 0;
340 for bits in 1..=max_len {
341 code = (code + bl_count[bits - 1]) << 1;
342 next_code[bits] = code;
343 }
344
345 for s in 0..alphabet_size {
347 let l = lengths[s] as usize;
348 if l > 0 {
349 let canon = next_code[l] as u16;
350 next_code[l] += 1;
351 let reversed = reverse_bits_u16(canon, l as u8);
353 codes[s] = (reversed, lengths[s]);
354 }
355 }
356
357 codes
358}
359
360#[inline]
362fn reverse_bits_u16(v: u16, n: u8) -> u16 {
363 debug_assert!(n <= 16);
364 let mut r = 0u16;
365 let mut x = v;
366 for _ in 0..n {
367 r = (r << 1) | (x & 1);
368 x >>= 1;
369 }
370 r
371}
372
373struct HuffTable {
382 table: Vec<(u16, u8)>,
386 max_bits: u8,
388 single_symbol: Option<u16>,
390}
391
392impl HuffTable {
393 fn build(lengths: &[u8]) -> Self {
395 let max_bits = lengths.iter().copied().max().unwrap_or(0);
396
397 let non_zero_syms: Vec<u16> = (0..lengths.len() as u16)
399 .filter(|&i| lengths[i as usize] > 0)
400 .collect();
401
402 if non_zero_syms.is_empty() {
403 return HuffTable {
404 table: vec![(0, 0); 1],
405 max_bits: 0,
406 single_symbol: None,
407 };
408 }
409
410 if non_zero_syms.len() == 1 {
412 return HuffTable {
413 table: vec![(non_zero_syms[0], 1); 2],
414 max_bits: 1,
415 single_symbol: Some(non_zero_syms[0]),
416 };
417 }
418
419 let codes = canonical_codes_from_lengths(lengths);
421
422 let table_size = 1usize << max_bits;
424 let mut table = vec![(0u16, 0u8); table_size];
425
426 for (sym, &(code, len)) in codes.iter().enumerate() {
427 if len == 0 {
428 continue;
429 }
430 let extra_bits = max_bits - len;
433 let num_fill = 1usize << extra_bits;
434 for fill in 0..num_fill {
435 let idx = (fill << len) | (code as usize);
437 table[idx] = (sym as u16, len);
438 }
439 }
440
441 HuffTable {
442 table,
443 max_bits,
444 single_symbol: None,
445 }
446 }
447
448 fn decode(&self, reader: &mut BitReader<'_>) -> Result<u16, WebpError> {
451 if self.max_bits == 0 {
452 return Err(WebpError::Invalid("Huffman table has max_bits=0".into()));
453 }
454
455 if let Some(sym) = self.single_symbol {
457 let _ = reader.read_bits(1)?; return Ok(sym);
459 }
460
461 let bits = reader.read_bits(self.max_bits)? as usize;
462 let (sym, code_len) = self.table[bits & (self.table.len() - 1)];
463
464 if code_len == 0 {
465 return Err(WebpError::Invalid(format!(
466 "invalid Huffman prefix bits={:b}",
467 bits
468 )));
469 }
470
471 let unused = self.max_bits - code_len;
473 if unused > 0 {
474 reader.unread_bits(unused);
475 }
476
477 Ok(sym)
478 }
479}
480
481type HuffTree = HuffTable;
483
484impl<'a> BitReader<'a> {
485 fn unread_bits(&mut self, n: u8) {
490 let mut remaining = n as u32;
491 while remaining > 0 {
492 if (self.bit_pos as u32) >= remaining {
493 self.bit_pos -= remaining as u8;
494 remaining = 0;
495 } else {
496 remaining -= self.bit_pos as u32;
497 if self.byte_pos > 0 {
498 self.byte_pos -= 1;
499 self.bit_pos = 8;
500 } else {
501 self.bit_pos = 0;
502 break;
503 }
504 }
505 }
506 }
507}
508
509#[derive(Debug)]
513enum HuffSpec {
514 Simple1 { symbol: u16 },
516 Simple2 { sym0: u16, sym1: u16 },
518 Standard { lengths: Vec<u8> },
520}
521
522fn analyse_freqs(freqs: &[u32], alphabet_size: usize) -> HuffSpec {
524 let mut distinct: Vec<u16> = (0..alphabet_size as u16)
525 .filter(|&i| freqs.get(i as usize).copied().unwrap_or(0) > 0)
526 .collect();
527 distinct.sort_unstable();
528
529 match distinct.len() {
530 0 => HuffSpec::Simple1 { symbol: 0 }, 1 => HuffSpec::Simple1 {
532 symbol: distinct[0],
533 },
534 2 => HuffSpec::Simple2 {
535 sym0: distinct[0],
536 sym1: distinct[1],
537 },
538 _ => {
539 let lengths = compute_code_lengths(freqs, alphabet_size, 15);
540 HuffSpec::Standard { lengths }
541 }
542 }
543}
544
545fn write_huff_tree(bw: &mut BitWriter, spec: &HuffSpec) {
547 match spec {
548 HuffSpec::Simple1 { symbol } => {
549 bw.write_bits(1, 1); bw.write_bits(0, 1); bw.write_bits(*symbol as u64, 8); }
553 HuffSpec::Simple2 { sym0, sym1 } => {
554 bw.write_bits(1, 1); bw.write_bits(1, 1); bw.write_bits(1, 1); bw.write_bits(*sym0 as u64, 8);
558 bw.write_bits(*sym1 as u64, 8);
559 }
560 HuffSpec::Standard { lengths } => {
561 write_standard_huff_tree(bw, lengths);
562 }
563 }
564}
565
566fn write_standard_huff_tree(bw: &mut BitWriter, lengths: &[u8]) {
568 bw.write_bits(0, 1); let alphabet_size = lengths.len();
571
572 bw.write_bits(0, 1);
574
575 const NUM_META: usize = 19;
579 let mut cl_freqs = [0u32; NUM_META];
580 for &l in lengths.iter().take(alphabet_size) {
581 cl_freqs[l as usize] += 1;
582 }
583
584 const CL_ORDER: [usize; 19] = [
586 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
587 ];
588
589 let cl_lengths = compute_code_lengths(&cl_freqs, NUM_META, 7);
590
591 let num_cl = {
593 let mut last = 4usize;
594 for i in 0..19 {
595 if cl_lengths[CL_ORDER[i]] > 0 {
596 last = i + 1;
597 }
598 }
599 last.max(4)
600 };
601
602 bw.write_bits((num_cl - 4) as u64, 4);
604
605 for i in 0..num_cl {
607 bw.write_bits(cl_lengths[CL_ORDER[i]] as u64, 3);
608 }
609
610 let cl_codes = canonical_codes_from_lengths(&cl_lengths);
612 for &l in &lengths[..alphabet_size] {
613 let (code, len) = cl_codes[l as usize];
614 if len == 0 {
615 bw.write_bits(0, 1);
616 } else {
617 bw.write_bits(code as u64, len);
618 }
619 }
620}
621
622fn read_huff_tree(br: &mut BitReader<'_>, alphabet_size: usize) -> Result<HuffTree, WebpError> {
624 let is_simple = br.read_bits(1)? != 0;
625 if is_simple {
626 let num_syms_minus1 = br.read_bits(1)?;
627 if num_syms_minus1 == 0 {
628 let sym = br.read_bits(8)? as usize;
630 let mut lengths = vec![0u8; alphabet_size];
631 if sym < alphabet_size {
632 lengths[sym] = 1;
633 }
634 return Ok(HuffTree::build(&lengths));
635 } else {
636 let _first_uses_8bits = br.read_bits(1)?; let sym0 = br.read_bits(8)? as usize;
639 let sym1 = br.read_bits(8)? as usize;
640 let mut lengths = vec![0u8; alphabet_size];
641 if sym0 < alphabet_size {
642 lengths[sym0] = 1;
643 }
644 if sym1 < alphabet_size && sym1 != sym0 {
645 lengths[sym1] = 1;
646 }
647 return Ok(HuffTree::build(&lengths));
648 }
649 }
650
651 let _use_length_limit = br.read_bits(1)?; const NUM_META: usize = 19;
655 const CL_ORDER: [usize; 19] = [
656 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
657 ];
658
659 let num_cl = (br.read_bits(4)? as usize) + 4;
660 let mut cl_lengths = [0u8; NUM_META];
661 for i in 0..num_cl {
662 cl_lengths[CL_ORDER[i]] = br.read_bits(3)? as u8;
663 }
664
665 let cl_tree = HuffTree::build(&cl_lengths);
666
667 let mut lengths = vec![0u8; alphabet_size];
669 for i in 0..alphabet_size {
670 let sym = cl_tree.decode(br)?;
671 match sym {
673 0..=15 => lengths[i] = sym as u8,
674 16 => {
675 let extra = br.read_bits(2)? as usize;
677 let prev = if i > 0 { lengths[i - 1] } else { 0 };
678 for j in 0..(3 + extra) {
679 if i + j < alphabet_size {
680 lengths[i + j] = prev;
681 }
682 }
683 }
684 17 => {
685 let extra = br.read_bits(3)? as usize;
687 for j in 0..(3 + extra) {
688 if i + j < alphabet_size {
689 lengths[i + j] = 0;
690 }
691 }
692 }
693 18 => {
694 let extra = br.read_bits(7)? as usize;
696 for j in 0..(11 + extra) {
697 if i + j < alphabet_size {
698 lengths[i + j] = 0;
699 }
700 }
701 }
702 _ => return Err(WebpError::Invalid(format!("unexpected CL code {}", sym))),
703 }
704 }
705
706 Ok(HuffTree::build(&lengths))
707}
708
709const ALPHA_G: usize = 256;
714const ALPHA_R: usize = 256;
715const ALPHA_B: usize = 256;
716const ALPHA_A: usize = 256;
717const ALPHA_D: usize = 40; pub fn webp_encode_rgb(width: u32, height: u32, pixels: &[u8]) -> Result<Vec<u8>, WebpError> {
725 let pixel_count = (width as usize) * (height as usize);
726 if pixels.len() != pixel_count * 3 {
727 return Err(WebpError::Invalid(format!(
728 "pixel buffer length {} != expected {}",
729 pixels.len(),
730 pixel_count * 3
731 )));
732 }
733 if width == 0 || height == 0 {
734 return Err(WebpError::Invalid("zero dimension".into()));
735 }
736 if width > 16384 || height > 16384 {
737 return Err(WebpError::Invalid(
738 "dimension exceeds VP8L max of 16384".into(),
739 ));
740 }
741
742 let mut argb: Vec<[u8; 4]> = Vec::with_capacity(pixel_count);
744 for px in pixels.chunks_exact(3) {
745 argb.push([255, px[0], px[1], px[2]]); }
747
748 let mut freq_g = [0u32; ALPHA_G];
750 let mut freq_r = [0u32; ALPHA_R];
751 let mut freq_b = [0u32; ALPHA_B];
752 let mut freq_a = [0u32; ALPHA_A];
753 let freq_d = [0u32; ALPHA_D]; for &[a, r, g, b] in &argb {
756 freq_g[g as usize] += 1;
757 freq_r[r as usize] += 1;
758 freq_b[b as usize] += 1;
759 freq_a[a as usize] += 1;
760 }
761
762 let spec_g = analyse_freqs(&freq_g, ALPHA_G);
764 let spec_r = analyse_freqs(&freq_r, ALPHA_R);
765 let spec_b = analyse_freqs(&freq_b, ALPHA_B);
766 let spec_a = analyse_freqs(&freq_a, ALPHA_A);
767 let spec_d = analyse_freqs(&freq_d, ALPHA_D);
768
769 let codes_g = build_encode_table(&spec_g, ALPHA_G);
771 let codes_r = build_encode_table(&spec_r, ALPHA_R);
772 let codes_b = build_encode_table(&spec_b, ALPHA_B);
773 let codes_a = build_encode_table(&spec_a, ALPHA_A);
774
775 let mut bw = BitWriter::new();
777
778 bw.write_bits((width - 1) as u64, 14);
780 bw.write_bits((height - 1) as u64, 14);
781
782 bw.write_bits(0, 1);
784
785 bw.write_bits(0, 3);
787
788 bw.write_bits(0, 1);
790
791 bw.write_bits(0, 1);
793
794 bw.write_bits(0, 1);
796
797 write_huff_tree(&mut bw, &spec_g);
799 write_huff_tree(&mut bw, &spec_r);
800 write_huff_tree(&mut bw, &spec_b);
801 write_huff_tree(&mut bw, &spec_a);
802 write_huff_tree(&mut bw, &spec_d);
803
804 for &[a, r, g, b] in &argb {
806 encode_symbol(&mut bw, &codes_g, g as usize);
807 encode_symbol(&mut bw, &codes_r, r as usize);
808 encode_symbol(&mut bw, &codes_b, b as usize);
809 encode_symbol(&mut bw, &codes_a, a as usize);
810 }
811
812 let vp8l_data = bw.into_bytes();
813
814 let chunk_payload_size = 1 + vp8l_data.len();
817 let chunk_size = chunk_payload_size as u32;
818
819 let padded_chunk = (chunk_payload_size + 1) & !1;
822 let riff_size = 4 + 4 + 4 + padded_chunk;
823
824 let mut out: Vec<u8> = Vec::with_capacity(12 + padded_chunk);
825 out.extend_from_slice(b"RIFF");
826 out.extend_from_slice(&(riff_size as u32).to_le_bytes());
827 out.extend_from_slice(b"WEBP");
828 out.extend_from_slice(b"VP8L");
829 out.extend_from_slice(&chunk_size.to_le_bytes());
830 out.push(0x2F); out.extend_from_slice(&vp8l_data);
832 if chunk_payload_size & 1 == 1 {
833 out.push(0x00); }
835
836 Ok(out)
837}
838
839fn build_encode_table(spec: &HuffSpec, alphabet_size: usize) -> Vec<(u16, u8)> {
841 match spec {
842 HuffSpec::Simple1 { symbol } => {
843 let mut table = vec![(0u16, 0u8); alphabet_size];
844 if (*symbol as usize) < alphabet_size {
845 table[*symbol as usize] = (0, 1);
846 }
847 table
848 }
849 HuffSpec::Simple2 { sym0, sym1 } => {
850 let mut table = vec![(0u16, 0u8); alphabet_size];
851 if (*sym0 as usize) < alphabet_size {
852 table[*sym0 as usize] = (0, 1); }
854 if (*sym1 as usize) < alphabet_size {
855 table[*sym1 as usize] = (1, 1); }
857 table
858 }
859 HuffSpec::Standard { lengths } => canonical_codes_from_lengths(lengths),
860 }
861}
862
863#[inline]
865fn encode_symbol(bw: &mut BitWriter, codes: &[(u16, u8)], sym: usize) {
866 if sym < codes.len() {
867 let (code, len) = codes[sym];
868 if len > 0 {
869 bw.write_bits(code as u64, len);
870 } else {
871 }
873 }
874}
875
876pub fn webp_decode(bytes: &[u8]) -> Result<RawDecodeResult, WebpError> {
880 if bytes.len() < 12 {
882 return Err(WebpError::Truncated);
883 }
884 if &bytes[0..4] != b"RIFF" {
885 return Err(WebpError::Invalid("missing RIFF header".into()));
886 }
887 if &bytes[8..12] != b"WEBP" {
888 return Err(WebpError::Invalid("missing WEBP fourcc".into()));
889 }
890
891 let mut pos = 12usize;
893 let file_end = bytes.len();
894
895 while pos + 8 <= file_end {
896 let fourcc = &bytes[pos..pos + 4];
897 let chunk_size = u32::from_le_bytes(
898 bytes[pos + 4..pos + 8]
899 .try_into()
900 .map_err(|_| WebpError::Truncated)?,
901 ) as usize;
902 let data_start = pos + 8;
903 let data_end = data_start + chunk_size;
904
905 if fourcc == b"VP8L" {
906 if bytes.len() < data_end {
907 return Err(WebpError::Truncated);
908 }
909 let chunk_data = &bytes[data_start..data_end];
910 return decode_vp8l(chunk_data);
911 } else if fourcc == b"VP8 " || fourcc == b"VP8X" {
912 return Err(WebpError::Unsupported(
913 "only VP8L (lossless) is supported".into(),
914 ));
915 }
916
917 let padded = (chunk_size + 1) & !1;
919 pos = data_start + padded;
920 }
921
922 Err(WebpError::Invalid("VP8L chunk not found".into()))
923}
924
925fn decode_vp8l(data: &[u8]) -> Result<RawDecodeResult, WebpError> {
927 if data.is_empty() {
928 return Err(WebpError::Truncated);
929 }
930 if data[0] != 0x2F {
931 return Err(WebpError::Invalid(format!(
932 "VP8L signature byte expected 0x2F, got 0x{:02X}",
933 data[0]
934 )));
935 }
936
937 let mut br = BitReader::new(&data[1..]);
938
939 let width = (br.read_bits(14)? as u32) + 1;
941 let height = (br.read_bits(14)? as u32) + 1;
942
943 let _alpha_used = br.read_bits(1)?;
944 let version = br.read_bits(3)?;
945 if version != 0 {
946 return Err(WebpError::Invalid(format!(
947 "VP8L version must be 0, got {}",
948 version
949 )));
950 }
951
952 let transform_present = br.read_bits(1)?;
954 if transform_present != 0 {
955 return Err(WebpError::Unsupported(
956 "VP8L transforms are not supported".into(),
957 ));
958 }
959
960 let color_cache_code_bits = br.read_bits(1)?;
962 if color_cache_code_bits != 0 {
963 return Err(WebpError::Unsupported(
964 "VP8L color cache not supported".into(),
965 ));
966 }
967
968 let huffman_meta = br.read_bits(1)?;
970 if huffman_meta != 0 {
971 return Err(WebpError::Unsupported(
972 "VP8L meta Huffman not supported".into(),
973 ));
974 }
975
976 let tree_g = read_huff_tree(&mut br, ALPHA_G)?;
978 let tree_r = read_huff_tree(&mut br, ALPHA_R)?;
979 let tree_b = read_huff_tree(&mut br, ALPHA_B)?;
980 let tree_a = read_huff_tree(&mut br, ALPHA_A)?;
981 let _tree_d = read_huff_tree(&mut br, ALPHA_D)?;
982
983 let pixel_count = (width as usize) * (height as usize);
985 let mut rgb_pixels: Vec<u8> = Vec::with_capacity(pixel_count * 3);
986
987 for _ in 0..pixel_count {
988 let g = tree_g.decode(&mut br)? as u8;
989 let r = tree_r.decode(&mut br)? as u8;
990 let b = tree_b.decode(&mut br)? as u8;
991 let _a = tree_a.decode(&mut br)? as u8;
992 rgb_pixels.push(r);
993 rgb_pixels.push(g);
994 rgb_pixels.push(b);
995 }
996
997 Ok(RawDecodeResult {
998 width: width as usize,
999 height: height as usize,
1000 pixels: rgb_pixels,
1001 })
1002}
1003
1004#[cfg(test)]
1007mod tests {
1008 use super::*;
1009
1010 fn make_solid_rgb(w: usize, h: usize, r: u8, g: u8, b: u8) -> Vec<u8> {
1011 let mut v = Vec::with_capacity(w * h * 3);
1012 for _ in 0..(w * h) {
1013 v.push(r);
1014 v.push(g);
1015 v.push(b);
1016 }
1017 v
1018 }
1019
1020 fn make_gradient(w: usize, h: usize) -> Vec<u8> {
1021 let mut v = Vec::with_capacity(w * h * 3);
1022 for y in 0..h {
1023 for x in 0..w {
1024 v.push(((x * 255) / w.max(1)) as u8);
1025 v.push(((y * 255) / h.max(1)) as u8);
1026 v.push(((x + y) % 256) as u8);
1027 }
1028 }
1029 v
1030 }
1031
1032 #[test]
1033 fn test_webp_header() {
1034 let pixels = make_solid_rgb(4, 4, 200, 100, 50);
1035 let encoded = webp_encode_rgb(4, 4, &pixels).expect("encode must succeed");
1036 assert!(encoded.starts_with(b"RIFF"), "must start with RIFF");
1037 assert_eq!(&encoded[8..12], b"WEBP", "must contain WEBP");
1038 assert!(
1039 encoded.windows(4).any(|w| w == b"VP8L"),
1040 "must contain VP8L chunk marker"
1041 );
1042 }
1043
1044 #[test]
1045 fn test_webp_roundtrip_solid_color() {
1046 let pixels = make_solid_rgb(4, 4, 127, 63, 200);
1047 let encoded = webp_encode_rgb(4, 4, &pixels).expect("encode");
1048 let decoded = webp_decode(&encoded).expect("decode");
1049 assert_eq!(decoded.width, 4);
1050 assert_eq!(decoded.height, 4);
1051 assert_eq!(
1052 decoded.pixels, pixels,
1053 "solid-color round-trip must be exact"
1054 );
1055 }
1056
1057 #[test]
1058 fn test_webp_roundtrip_gradient() {
1059 let pixels = make_gradient(8, 8);
1060 let encoded = webp_encode_rgb(8, 8, &pixels).expect("encode");
1061 let decoded = webp_decode(&encoded).expect("decode");
1062 assert_eq!(decoded.width, 8);
1063 assert_eq!(decoded.height, 8);
1064 assert_eq!(decoded.pixels, pixels, "gradient round-trip must be exact");
1065 }
1066
1067 #[test]
1068 fn test_webp_decode_invalid_returns_error() {
1069 let result = webp_decode(b"not a webp file at all 12345678");
1070 assert!(result.is_err(), "invalid input must return an error");
1071 }
1072
1073 #[test]
1074 fn test_webp_roundtrip_single_pixel() {
1075 let pixels = vec![42u8, 84u8, 168u8]; let encoded = webp_encode_rgb(1, 1, &pixels).expect("encode");
1077 let decoded = webp_decode(&encoded).expect("decode");
1078 assert_eq!(decoded.width, 1);
1079 assert_eq!(decoded.height, 1);
1080 assert_eq!(decoded.pixels, pixels);
1081 }
1082
1083 #[test]
1084 fn test_webp_roundtrip_checkerboard() {
1085 let mut pixels = Vec::with_capacity(4 * 4 * 3);
1087 for y in 0..4usize {
1088 for x in 0..4usize {
1089 if (x + y) % 2 == 0 {
1090 pixels.extend_from_slice(&[255, 0, 0]);
1091 } else {
1092 pixels.extend_from_slice(&[0, 0, 255]);
1093 }
1094 }
1095 }
1096 let encoded = webp_encode_rgb(4, 4, &pixels).expect("encode");
1097 let decoded = webp_decode(&encoded).expect("decode");
1098 assert_eq!(decoded.pixels, pixels);
1099 }
1100}