image/codecs/webp/
encoder.rs

1//! Encoding of WebP images.
2use std::collections::BinaryHeap;
3///
4/// Uses the simple encoding API from the [libwebp] library.
5///
6/// [libwebp]: https://developers.google.com/speed/webp/docs/api#simple_encoding_api
7use std::io::{self, Write};
8use std::slice::ChunksExact;
9
10#[cfg(feature = "webp-encoder")]
11use libwebp::{Encoder, PixelLayout, WebPMemory};
12
13use crate::error::{ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind};
14use crate::flat::SampleLayout;
15use crate::{ColorType, ImageEncoder, ImageError, ImageFormat, ImageResult};
16
17/// WebP Encoder.
18pub struct WebPEncoder<W> {
19    writer: W,
20    quality: WebPQuality,
21
22    chunk_buffer: Vec<u8>,
23    buffer: u64,
24    nbits: u8,
25}
26
27/// WebP encoder quality.
28#[derive(Debug, Copy, Clone)]
29pub struct WebPQuality(Quality);
30
31#[derive(Debug, Copy, Clone)]
32enum Quality {
33    Lossless,
34    #[allow(unused)]
35    #[deprecated = "Lossy encoding will be removed in a future version. See: https://github.com/image-rs/image/issues/1984"]
36    Lossy(u8),
37}
38
39impl WebPQuality {
40    /// Minimum lossy quality value (0).
41    pub const MIN: u8 = 0;
42    /// Maximum lossy quality value (100).
43    pub const MAX: u8 = 100;
44    /// Default lossy quality (80), providing a balance of quality and file size.
45    pub const DEFAULT: u8 = 80;
46
47    /// Lossless encoding.
48    pub fn lossless() -> Self {
49        Self(Quality::Lossless)
50    }
51
52    /// Lossy encoding. 0 = low quality, small size; 100 = high quality, large size.
53    ///
54    /// Values are clamped from 0 to 100.
55    #[deprecated = "Lossy encoding will be removed in a future version. See: https://github.com/image-rs/image/issues/1984"]
56    pub fn lossy(quality: u8) -> Self {
57        #[allow(deprecated)]
58        Self(Quality::Lossy(quality.clamp(Self::MIN, Self::MAX)))
59    }
60}
61
62impl Default for WebPQuality {
63    fn default() -> Self {
64        #[allow(deprecated)]
65        Self::lossy(WebPQuality::DEFAULT)
66    }
67}
68
69impl<W: Write> WebPEncoder<W> {
70    /// Create a new encoder that writes its output to `w`.
71    ///
72    /// Defaults to lossy encoding, see [`WebPQuality::DEFAULT`].
73    #[deprecated = "Use `new_lossless` instead. Lossy encoding will be removed in a future version. See: https://github.com/image-rs/image/issues/1984"]
74    #[cfg(feature = "webp-encoder")]
75    pub fn new(w: W) -> Self {
76        #[allow(deprecated)]
77        WebPEncoder::new_with_quality(w, WebPQuality::default())
78    }
79
80    /// Create a new encoder with the specified quality, that writes its output to `w`.
81    #[deprecated = "Use `new_lossless` instead. Lossy encoding will be removed in a future version. See: https://github.com/image-rs/image/issues/1984"]
82    #[cfg(feature = "webp-encoder")]
83    pub fn new_with_quality(w: W, quality: WebPQuality) -> Self {
84        Self {
85            writer: w,
86            quality,
87            chunk_buffer: Vec::new(),
88            buffer: 0,
89            nbits: 0,
90        }
91    }
92
93    /// Create a new encoder that writes its output to `w`.
94    ///
95    /// Uses "VP8L" lossless encoding.
96    pub fn new_lossless(w: W) -> Self {
97        Self {
98            writer: w,
99            quality: WebPQuality::lossless(),
100            chunk_buffer: Vec::new(),
101            buffer: 0,
102            nbits: 0,
103        }
104    }
105
106    fn write_bits(&mut self, bits: u64, nbits: u8) -> io::Result<()> {
107        debug_assert!(nbits <= 64);
108
109        self.buffer |= bits << self.nbits;
110        self.nbits += nbits;
111
112        if self.nbits >= 64 {
113            self.chunk_buffer.write_all(&self.buffer.to_le_bytes())?;
114            self.nbits -= 64;
115            self.buffer = bits.checked_shr((nbits - self.nbits) as u32).unwrap_or(0);
116        }
117        debug_assert!(self.nbits < 64);
118        Ok(())
119    }
120
121    fn flush(&mut self) -> io::Result<()> {
122        if self.nbits % 8 != 0 {
123            self.write_bits(0, 8 - self.nbits % 8)?;
124        }
125        if self.nbits > 0 {
126            self.chunk_buffer
127                .write_all(&self.buffer.to_le_bytes()[..self.nbits as usize / 8])
128                .unwrap();
129            self.buffer = 0;
130            self.nbits = 0;
131        }
132        Ok(())
133    }
134
135    fn write_single_entry_huffman_tree(&mut self, symbol: u8) -> io::Result<()> {
136        self.write_bits(1, 2)?;
137        if symbol <= 1 {
138            self.write_bits(0, 1)?;
139            self.write_bits(symbol as u64, 1)?;
140        } else {
141            self.write_bits(1, 1)?;
142            self.write_bits(symbol as u64, 8)?;
143        }
144        Ok(())
145    }
146
147    fn build_huffman_tree(
148        &mut self,
149        frequencies: &[u32],
150        lengths: &mut [u8],
151        codes: &mut [u16],
152        length_limit: u8,
153    ) -> bool {
154        assert_eq!(frequencies.len(), lengths.len());
155        assert_eq!(frequencies.len(), codes.len());
156
157        if frequencies.iter().filter(|&&f| f > 0).count() <= 1 {
158            lengths.fill(0);
159            codes.fill(0);
160            return false;
161        }
162
163        #[derive(Eq, PartialEq, Copy, Clone, Debug)]
164        struct Item(u32, u16);
165        impl Ord for Item {
166            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
167                other.0.cmp(&self.0)
168            }
169        }
170        impl PartialOrd for Item {
171            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
172                Some(self.cmp(other))
173            }
174        }
175
176        // Build a huffman tree
177        let mut internal_nodes = Vec::new();
178        let mut nodes = BinaryHeap::from_iter(
179            frequencies
180                .iter()
181                .enumerate()
182                .filter(|(_, &frequency)| frequency > 0)
183                .map(|(i, &frequency)| Item(frequency, i as u16)),
184        );
185        while nodes.len() > 1 {
186            let Item(frequency1, index1) = nodes.pop().unwrap();
187            let mut root = nodes.peek_mut().unwrap();
188            internal_nodes.push((index1, root.1));
189            *root = Item(
190                frequency1 + root.0,
191                internal_nodes.len() as u16 + frequencies.len() as u16 - 1,
192            );
193        }
194
195        // Walk the tree to assign code lengths
196        lengths.fill(0);
197        let mut stack = Vec::new();
198        stack.push((nodes.pop().unwrap().1, 0));
199        while let Some((node, depth)) = stack.pop() {
200            let node = node as usize;
201            if node < frequencies.len() {
202                lengths[node] = depth as u8;
203            } else {
204                let (left, right) = internal_nodes[node - frequencies.len()];
205                stack.push((left, depth + 1));
206                stack.push((right, depth + 1));
207            }
208        }
209
210        // Limit the codes to length length_limit
211        let mut max_length = 0;
212        for &length in lengths.iter() {
213            max_length = max_length.max(length);
214        }
215        if max_length > length_limit {
216            let mut counts = [0u32; 16];
217            for &length in lengths.iter() {
218                counts[length.min(length_limit) as usize] += 1;
219            }
220
221            let mut total = 0;
222            for (i, count) in counts
223                .iter()
224                .enumerate()
225                .skip(1)
226                .take(length_limit as usize)
227            {
228                total += count << (length_limit as usize - i);
229            }
230
231            while total > 1u32 << length_limit {
232                let mut i = length_limit as usize - 1;
233                while counts[i] == 0 {
234                    i -= 1;
235                }
236                counts[i] -= 1;
237                counts[length_limit as usize] -= 1;
238                counts[i + 1] += 2;
239                total -= 1;
240            }
241
242            // assign new lengths
243            let mut len = length_limit;
244            let mut indexes = frequencies.iter().copied().enumerate().collect::<Vec<_>>();
245            indexes.sort_unstable_by_key(|&(_, frequency)| frequency);
246            for &(i, frequency) in indexes.iter() {
247                if frequency > 0 {
248                    while counts[len as usize] == 0 {
249                        len -= 1;
250                    }
251                    lengths[i] = len;
252                    counts[len as usize] -= 1;
253                }
254            }
255        }
256
257        // Assign codes
258        codes.fill(0);
259        let mut code = 0u32;
260        for len in 1..=length_limit {
261            for (i, &length) in lengths.iter().enumerate() {
262                if length == len {
263                    codes[i] = (code as u16).reverse_bits() >> (16 - len);
264                    code += 1;
265                }
266            }
267            code <<= 1;
268        }
269        assert_eq!(code, 2 << length_limit);
270
271        true
272    }
273
274    fn write_huffman_tree(
275        &mut self,
276        frequencies: &[u32],
277        lengths: &mut [u8],
278        codes: &mut [u16],
279    ) -> io::Result<()> {
280        if !self.build_huffman_tree(frequencies, lengths, codes, 15) {
281            let symbol = frequencies
282                .iter()
283                .position(|&frequency| frequency > 0)
284                .unwrap_or(0);
285            return self.write_single_entry_huffman_tree(symbol as u8);
286        }
287
288        let mut code_length_lengths = [0u8; 16];
289        let mut code_length_codes = [0u16; 16];
290        let mut code_length_frequencies = [0u32; 16];
291        for &length in lengths.iter() {
292            code_length_frequencies[length as usize] += 1;
293        }
294        let single_code_length_length = !self.build_huffman_tree(
295            &code_length_frequencies,
296            &mut code_length_lengths,
297            &mut code_length_codes,
298            7,
299        );
300
301        const CODE_LENGTH_ORDER: [usize; 19] = [
302            17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
303        ];
304
305        // Write the huffman tree
306        self.write_bits(0, 1)?; // normal huffman tree
307        self.write_bits(19 - 4, 4)?; // num_code_lengths - 4
308
309        for &i in CODE_LENGTH_ORDER.iter() {
310            if i > 15 || code_length_frequencies[i] == 0 {
311                self.write_bits(0, 3)?;
312            } else if single_code_length_length {
313                self.write_bits(1, 3)?;
314            } else {
315                self.write_bits(code_length_lengths[i] as u64, 3)?;
316            }
317        }
318
319        match lengths.len() {
320            256 => {
321                self.write_bits(1, 1)?; // max_symbol is stored
322                self.write_bits(3, 3)?; // max_symbol_nbits / 2 - 2
323                self.write_bits(254, 8)?; // max_symbol - 2
324            }
325            280 => self.write_bits(0, 1)?,
326            _ => unreachable!(),
327        }
328
329        // Write the huffman codes
330        if !single_code_length_length {
331            for &len in lengths.iter() {
332                self.write_bits(
333                    code_length_codes[len as usize] as u64,
334                    code_length_lengths[len as usize],
335                )?;
336            }
337        }
338
339        Ok(())
340    }
341
342    fn length_to_symbol(len: u16) -> (u16, u8) {
343        let len = len - 1;
344        let highest_bit = 15 - len.leading_zeros() as u16; // TODO: use ilog2 once MSRV >= 1.67
345        let second_highest_bit = (len >> (highest_bit - 1)) & 1;
346        let extra_bits = highest_bit - 1;
347        let symbol = 2 * highest_bit + second_highest_bit;
348        (symbol, extra_bits as u8)
349    }
350
351    #[inline(always)]
352    fn count_run(
353        pixel: &[u8],
354        it: &mut std::iter::Peekable<ChunksExact<u8>>,
355        frequencies1: &mut [u32; 280],
356    ) {
357        let mut run_length = 0;
358        while run_length < 4096 && it.peek() == Some(&pixel) {
359            run_length += 1;
360            it.next();
361        }
362        if run_length > 0 {
363            if run_length <= 4 {
364                let symbol = 256 + run_length - 1;
365                frequencies1[symbol] += 1;
366            } else {
367                let (symbol, _extra_bits) = Self::length_to_symbol(run_length as u16);
368                frequencies1[256 + symbol as usize] += 1;
369            }
370        }
371    }
372
373    #[inline(always)]
374    fn write_run(
375        &mut self,
376        pixel: &[u8],
377        it: &mut std::iter::Peekable<ChunksExact<u8>>,
378        codes1: &[u16; 280],
379        lengths1: &[u8; 280],
380    ) -> io::Result<()> {
381        let mut run_length = 0;
382        while run_length < 4096 && it.peek() == Some(&pixel) {
383            run_length += 1;
384            it.next();
385        }
386        if run_length > 0 {
387            if run_length <= 4 {
388                let symbol = 256 + run_length - 1;
389                self.write_bits(codes1[symbol] as u64, lengths1[symbol])?;
390            } else {
391                let (symbol, extra_bits) = Self::length_to_symbol(run_length as u16);
392                self.write_bits(
393                    codes1[256 + symbol as usize] as u64,
394                    lengths1[256 + symbol as usize],
395                )?;
396                self.write_bits(
397                    (run_length as u64 - 1) & ((1 << extra_bits) - 1),
398                    extra_bits,
399                )?;
400            }
401        }
402        Ok(())
403    }
404
405    fn encode_lossless(
406        mut self,
407        data: &[u8],
408        width: u32,
409        height: u32,
410        color: ColorType,
411    ) -> ImageResult<()> {
412        if width == 0
413            || width > 16384
414            || height == 0
415            || height > 16384
416            || !SampleLayout::row_major_packed(color.channel_count(), width, height)
417                .fits(data.len())
418        {
419            return Err(ImageError::Parameter(ParameterError::from_kind(
420                ParameterErrorKind::DimensionMismatch,
421            )));
422        }
423
424        let (is_color, is_alpha) = match color {
425            ColorType::L8 => (false, false),
426            ColorType::La8 => (false, true),
427            ColorType::Rgb8 => (true, false),
428            ColorType::Rgba8 => (true, true),
429            _ => {
430                return Err(ImageError::Unsupported(
431                    UnsupportedError::from_format_and_kind(
432                        ImageFormat::WebP.into(),
433                        UnsupportedErrorKind::Color(color.into()),
434                    ),
435                ))
436            }
437        };
438
439        self.write_bits(0x2f, 8)?; // signature
440        self.write_bits(width as u64 - 1, 14)?;
441        self.write_bits(height as u64 - 1, 14)?;
442
443        self.write_bits(is_alpha as u64, 1)?; // alpha used
444        self.write_bits(0x0, 3)?; // version
445
446        // subtract green transform
447        self.write_bits(0b101, 3)?;
448
449        // predictor transform
450        self.write_bits(0b111001, 6)?;
451        self.write_bits(0x0, 1)?; // no color cache
452        self.write_single_entry_huffman_tree(2)?;
453        for _ in 0..4 {
454            self.write_single_entry_huffman_tree(0)?;
455        }
456
457        // transforms done
458        self.write_bits(0x0, 1)?;
459
460        // color cache
461        self.write_bits(0x0, 1)?;
462
463        // meta-huffman codes
464        self.write_bits(0x0, 1)?;
465
466        // expand to RGBA
467        let mut pixels = match color {
468            ColorType::L8 => data.iter().flat_map(|&p| [p, p, p, 255]).collect(),
469            ColorType::La8 => data
470                .chunks_exact(2)
471                .flat_map(|p| [p[0], p[0], p[0], p[1]])
472                .collect(),
473            ColorType::Rgb8 => data
474                .chunks_exact(3)
475                .flat_map(|p| [p[0], p[1], p[2], 255])
476                .collect(),
477            ColorType::Rgba8 => data.to_vec(),
478            _ => unreachable!(),
479        };
480
481        // compute subtract green transform
482        for pixel in pixels.chunks_exact_mut(4) {
483            pixel[0] = pixel[0].wrapping_sub(pixel[1]);
484            pixel[2] = pixel[2].wrapping_sub(pixel[1]);
485        }
486
487        // compute predictor transform
488        let row_bytes = width as usize * 4;
489        for y in (1..height as usize).rev() {
490            let (prev, current) =
491                pixels[(y - 1) * row_bytes..][..row_bytes * 2].split_at_mut(row_bytes);
492            for (c, p) in current.iter_mut().zip(prev) {
493                *c = c.wrapping_sub(*p);
494            }
495        }
496        for i in (4..row_bytes).rev() {
497            pixels[i] = pixels[i].wrapping_sub(pixels[i - 4]);
498        }
499        pixels[3] = pixels[3].wrapping_sub(255);
500
501        // compute frequencies
502        let mut frequencies0 = [0u32; 256];
503        let mut frequencies1 = [0u32; 280];
504        let mut frequencies2 = [0u32; 256];
505        let mut frequencies3 = [0u32; 256];
506        let mut it = pixels.chunks_exact(4).peekable();
507        match color {
508            ColorType::L8 => {
509                frequencies0[0] = 1;
510                frequencies2[0] = 1;
511                frequencies3[0] = 1;
512                while let Some(pixel) = it.next() {
513                    frequencies1[pixel[1] as usize] += 1;
514                    Self::count_run(pixel, &mut it, &mut frequencies1);
515                }
516            }
517            ColorType::La8 => {
518                frequencies0[0] = 1;
519                frequencies2[0] = 1;
520                while let Some(pixel) = it.next() {
521                    frequencies1[pixel[1] as usize] += 1;
522                    frequencies3[pixel[3] as usize] += 1;
523                    Self::count_run(pixel, &mut it, &mut frequencies1);
524                }
525            }
526            ColorType::Rgb8 => {
527                frequencies3[0] = 1;
528                while let Some(pixel) = it.next() {
529                    frequencies0[pixel[0] as usize] += 1;
530                    frequencies1[pixel[1] as usize] += 1;
531                    frequencies2[pixel[2] as usize] += 1;
532                    Self::count_run(pixel, &mut it, &mut frequencies1);
533                }
534            }
535            ColorType::Rgba8 => {
536                while let Some(pixel) = it.next() {
537                    frequencies0[pixel[0] as usize] += 1;
538                    frequencies1[pixel[1] as usize] += 1;
539                    frequencies2[pixel[2] as usize] += 1;
540                    frequencies3[pixel[3] as usize] += 1;
541                    Self::count_run(pixel, &mut it, &mut frequencies1);
542                }
543            }
544            _ => unreachable!(),
545        }
546
547        // compute and write huffman codes
548        let mut lengths0 = [0u8; 256];
549        let mut lengths1 = [0u8; 280];
550        let mut lengths2 = [0u8; 256];
551        let mut lengths3 = [0u8; 256];
552        let mut codes0 = [0u16; 256];
553        let mut codes1 = [0u16; 280];
554        let mut codes2 = [0u16; 256];
555        let mut codes3 = [0u16; 256];
556        self.write_huffman_tree(&frequencies1, &mut lengths1, &mut codes1)?;
557        if is_color {
558            self.write_huffman_tree(&frequencies0, &mut lengths0, &mut codes0)?;
559            self.write_huffman_tree(&frequencies2, &mut lengths2, &mut codes2)?;
560        } else {
561            self.write_single_entry_huffman_tree(0)?;
562            self.write_single_entry_huffman_tree(0)?;
563        }
564        if is_alpha {
565            self.write_huffman_tree(&frequencies3, &mut lengths3, &mut codes3)?;
566        } else {
567            self.write_single_entry_huffman_tree(0)?;
568        }
569        self.write_single_entry_huffman_tree(1)?;
570
571        // Write image data
572        let mut it = pixels.chunks_exact(4).peekable();
573        match color {
574            ColorType::L8 => {
575                while let Some(pixel) = it.next() {
576                    self.write_bits(
577                        codes1[pixel[1] as usize] as u64,
578                        lengths1[pixel[1] as usize],
579                    )?;
580                    self.write_run(pixel, &mut it, &codes1, &lengths1)?;
581                }
582            }
583            ColorType::La8 => {
584                while let Some(pixel) = it.next() {
585                    let len1 = lengths1[pixel[1] as usize];
586                    let len3 = lengths3[pixel[3] as usize];
587
588                    let code = codes1[pixel[1] as usize] as u64
589                        | (codes3[pixel[3] as usize] as u64) << len1;
590
591                    self.write_bits(code, len1 + len3)?;
592                    self.write_run(pixel, &mut it, &codes1, &lengths1)?;
593                }
594            }
595            ColorType::Rgb8 => {
596                while let Some(pixel) = it.next() {
597                    let len1 = lengths1[pixel[1] as usize];
598                    let len0 = lengths0[pixel[0] as usize];
599                    let len2 = lengths2[pixel[2] as usize];
600
601                    let code = codes1[pixel[1] as usize] as u64
602                        | (codes0[pixel[0] as usize] as u64) << len1
603                        | (codes2[pixel[2] as usize] as u64) << (len1 + len0);
604
605                    self.write_bits(code, len1 + len0 + len2)?;
606                    self.write_run(pixel, &mut it, &codes1, &lengths1)?;
607                }
608            }
609            ColorType::Rgba8 => {
610                while let Some(pixel) = it.next() {
611                    let len1 = lengths1[pixel[1] as usize];
612                    let len0 = lengths0[pixel[0] as usize];
613                    let len2 = lengths2[pixel[2] as usize];
614                    let len3 = lengths3[pixel[3] as usize];
615
616                    let code = codes1[pixel[1] as usize] as u64
617                        | (codes0[pixel[0] as usize] as u64) << len1
618                        | (codes2[pixel[2] as usize] as u64) << (len1 + len0)
619                        | (codes3[pixel[3] as usize] as u64) << (len1 + len0 + len2);
620
621                    self.write_bits(code, len1 + len0 + len2 + len3)?;
622                    self.write_run(pixel, &mut it, &codes1, &lengths1)?;
623                }
624            }
625            _ => unreachable!(),
626        }
627
628        // flush writer
629        self.flush()?;
630        if self.chunk_buffer.len() % 2 == 1 {
631            self.chunk_buffer.push(0);
632        }
633
634        // write container
635        self.writer.write_all(b"RIFF")?;
636        self.writer
637            .write_all(&(self.chunk_buffer.len() as u32 + 12).to_le_bytes())?;
638        self.writer.write_all(b"WEBP")?;
639        self.writer.write_all(b"VP8L")?;
640        self.writer
641            .write_all(&(self.chunk_buffer.len() as u32).to_le_bytes())?;
642        self.writer.write_all(&self.chunk_buffer)?;
643
644        Ok(())
645    }
646
647    #[cfg(feature = "webp-encoder")]
648    fn encode_lossy(
649        mut self,
650        data: &[u8],
651        width: u32,
652        height: u32,
653        color: ColorType,
654    ) -> ImageResult<()> {
655        // TODO: convert color types internally?
656        let layout = match color {
657            ColorType::Rgb8 => PixelLayout::Rgb,
658            ColorType::Rgba8 => PixelLayout::Rgba,
659            _ => {
660                return Err(ImageError::Unsupported(
661                    UnsupportedError::from_format_and_kind(
662                        ImageFormat::WebP.into(),
663                        UnsupportedErrorKind::Color(color.into()),
664                    ),
665                ))
666            }
667        };
668
669        // Validate dimensions upfront to avoid panics.
670        if width == 0
671            || height == 0
672            || !SampleLayout::row_major_packed(color.channel_count(), width, height)
673                .fits(data.len())
674        {
675            return Err(ImageError::Parameter(ParameterError::from_kind(
676                ParameterErrorKind::DimensionMismatch,
677            )));
678        }
679
680        // Call the native libwebp library to encode the image.
681        let encoder = Encoder::new(data, layout, width, height);
682        let encoded: WebPMemory = match self.quality.0 {
683            Quality::Lossless => encoder.encode_lossless(),
684            #[allow(deprecated)]
685            Quality::Lossy(quality) => encoder.encode(quality as f32),
686        };
687
688        // The simple encoding API in libwebp does not return errors.
689        if encoded.is_empty() {
690            return Err(ImageError::Encoding(crate::error::EncodingError::new(
691                ImageFormat::WebP.into(),
692                "encoding failed, output empty",
693            )));
694        }
695
696        self.writer.write_all(&encoded)?;
697        Ok(())
698    }
699
700    /// Encode image data with the indicated color type.
701    ///
702    /// The encoder requires image data be Rgb8 or Rgba8.
703    ///
704    /// # Panics
705    ///
706    /// Panics if `width * height * color.bytes_per_pixel() != data.len()`.
707    #[track_caller]
708    pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> {
709        let expected_buffer_len =
710            (width as u64 * height as u64).saturating_mul(color.bytes_per_pixel() as u64);
711        assert_eq!(
712            expected_buffer_len,
713            data.len() as u64,
714            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
715            data.len(),
716        );
717
718        if let WebPQuality(Quality::Lossless) = self.quality {
719            self.encode_lossless(data, width, height, color)
720        } else {
721            #[cfg(feature = "webp-encoder")]
722            return self.encode_lossy(data, width, height, color);
723            #[cfg(not(feature = "webp-encoder"))]
724            unreachable!()
725        }
726    }
727}
728
729impl<W: Write> ImageEncoder for WebPEncoder<W> {
730    #[track_caller]
731    fn write_image(
732        self,
733        buf: &[u8],
734        width: u32,
735        height: u32,
736        color_type: ColorType,
737    ) -> ImageResult<()> {
738        self.encode(buf, width, height, color_type)
739    }
740}
741
742#[cfg(test)]
743mod tests {
744    use crate::{ImageEncoder, RgbaImage};
745
746    #[test]
747    fn write_webp() {
748        let img = RgbaImage::from_raw(10, 6, (0..240).collect()).unwrap();
749
750        let mut output = Vec::new();
751        super::WebPEncoder::new_lossless(&mut output)
752            .write_image(
753                img.inner_pixels(),
754                img.width(),
755                img.height(),
756                crate::ColorType::Rgba8,
757            )
758            .unwrap();
759
760        let img2 = crate::load_from_memory_with_format(&output, crate::ImageFormat::WebP)
761            .unwrap()
762            .to_rgba8();
763
764        assert_eq!(img, img2);
765    }
766}
767
768#[cfg(test)]
769#[cfg(feature = "webp-encoder")]
770mod native_tests {
771    use crate::codecs::webp::{WebPEncoder, WebPQuality};
772    use crate::{ColorType, ImageEncoder};
773
774    #[derive(Debug, Clone)]
775    struct MockImage {
776        width: u32,
777        height: u32,
778        color: ColorType,
779        data: Vec<u8>,
780    }
781
782    impl quickcheck::Arbitrary for MockImage {
783        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
784            // Limit to small, non-empty images <= 512x512.
785            let width = u32::arbitrary(g) % 512 + 1;
786            let height = u32::arbitrary(g) % 512 + 1;
787            let (color, stride) = if bool::arbitrary(g) {
788                (ColorType::Rgb8, 3)
789            } else {
790                (ColorType::Rgba8, 4)
791            };
792            let size = width * height * stride;
793            let data: Vec<u8> = (0..size).map(|_| u8::arbitrary(g)).collect();
794            MockImage {
795                width,
796                height,
797                color,
798                data,
799            }
800        }
801    }
802
803    quickcheck! {
804        fn fuzz_webp_valid_image(image: MockImage, quality: u8) -> bool {
805            // Check valid images do not panic.
806            let mut buffer = Vec::<u8>::new();
807            for webp_quality in [WebPQuality::lossless(), #[allow(deprecated)] WebPQuality::lossy(quality)] {
808                buffer.clear();
809                #[allow(deprecated)]
810                let encoder = WebPEncoder::new_with_quality(&mut buffer, webp_quality);
811                if encoder
812                    .write_image(&image.data, image.width, image.height, image.color).is_err() {
813                    return false;
814                }
815            }
816            true
817        }
818
819        fn fuzz_webp_no_panic(data: Vec<u8>, width: u8, height: u8, quality: u8) -> bool {
820            // Check random (usually invalid) parameters do not panic.
821
822            if data.len() < width as usize * height as usize * 4 {
823                return true;
824            }
825
826            let mut buffer = Vec::<u8>::new();
827            for color in [ColorType::Rgb8, ColorType::Rgba8] {
828                for webp_quality in [WebPQuality::lossless(), #[allow(deprecated)] WebPQuality::lossy(quality)] {
829                    buffer.clear();
830                    #[allow(deprecated)]
831                    let encoder = WebPEncoder::new_with_quality(&mut buffer, webp_quality);
832                    // Ignore errors.
833                    let _ = encoder.write_image(
834                        &data[..width as usize * height as usize * color.bytes_per_pixel() as usize],
835                        width as u32,
836                        height as u32,
837                        color,
838                    );
839                }
840            }
841            true
842        }
843    }
844}