image_webp/
encoder.rs

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