1 | //! Encoding of WebP images. |
2 | use 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 |
7 | use std::io::{self, Write}; |
8 | use std::slice::ChunksExact; |
9 | |
10 | #[cfg (feature = "webp-encoder" )] |
11 | use libwebp::{Encoder, PixelLayout, WebPMemory}; |
12 | |
13 | use crate::error::{ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind}; |
14 | use crate::flat::SampleLayout; |
15 | use crate::{ColorType, ImageEncoder, ImageError, ImageFormat, ImageResult}; |
16 | |
17 | /// WebP Encoder. |
18 | pub 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)] |
29 | pub struct WebPQuality(Quality); |
30 | |
31 | #[derive (Debug, Copy, Clone)] |
32 | enum 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 | |
39 | impl 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 | |
62 | impl Default for WebPQuality { |
63 | fn default() -> Self { |
64 | #[allow (deprecated)] |
65 | Self::lossy(quality:WebPQuality::DEFAULT) |
66 | } |
67 | } |
68 | |
69 | impl<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 | |
729 | impl<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)] |
743 | mod 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" )] |
770 | mod 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 | |