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(quality: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(data: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}
845