1 | //! Encoding of WebP images. |
2 | use std::collections::BinaryHeap; |
3 | use std::io::{self, Write}; |
4 | use std::slice::ChunksExact; |
5 | |
6 | use 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)] |
14 | pub 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 | |
25 | quick_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 | |
44 | struct BitWriter<W> { |
45 | writer: W, |
46 | buffer: u64, |
47 | nbits: u8, |
48 | } |
49 | |
50 | impl<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 | |
81 | fn write_single_entry_huffman_tree<W: Write>(w: &mut BitWriter<W>, symbol: u8) -> io::Result<()> { |
82 | w.write_bits(bits:1, nbits:2)?; |
83 | if symbol <= 1 { |
84 | w.write_bits(bits:0, nbits:1)?; |
85 | w.write_bits(bits:u64::from(symbol), nbits:1)?; |
86 | } else { |
87 | w.write_bits(bits:1, nbits:1)?; |
88 | w.write_bits(bits:u64::from(symbol), nbits:8)?; |
89 | } |
90 | Ok(()) |
91 | } |
92 | |
93 | fn 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 | |
219 | fn 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 | |
287 | const fn length_to_symbol(len: u16) -> (u16, u8) { |
288 | let len: u16 = len - 1; |
289 | let highest_bit: u16 = len.ilog2() as u16; |
290 | let second_highest_bit: u16 = (len >> (highest_bit - 1)) & 1; |
291 | let extra_bits: u16 = highest_bit - 1; |
292 | let symbol: u16 = 2 * highest_bit + second_highest_bit; |
293 | (symbol, extra_bits as u8) |
294 | } |
295 | |
296 | #[inline (always)] |
297 | fn count_run( |
298 | pixel: &[u8], |
299 | it: &mut std::iter::Peekable<ChunksExact<u8>>, |
300 | frequencies1: &mut [u32; 280], |
301 | ) { |
302 | let mut run_length: usize = 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: usize = 256 + run_length - 1; |
310 | frequencies1[symbol] += 1; |
311 | } else { |
312 | let (symbol: u16, _extra_bits: u8) = length_to_symbol(len:run_length as u16); |
313 | frequencies1[256 + symbol as usize] += 1; |
314 | } |
315 | } |
316 | } |
317 | |
318 | #[inline (always)] |
319 | fn 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: usize = 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: usize = 256 + run_length - 1; |
334 | w.write_bits(bits:u64::from(codes1[symbol]), nbits:lengths1[symbol])?; |
335 | } else { |
336 | let (symbol: u16, extra_bits: u8) = length_to_symbol(len:run_length as u16); |
337 | w.write_bits( |
338 | bits:u64::from(codes1[256 + symbol as usize]), |
339 | nbits:lengths1[256 + symbol as usize], |
340 | )?; |
341 | w.write_bits( |
342 | (run_length as u64 - 1) & ((1 << extra_bits) - 1), |
343 | nbits: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)] |
355 | pub struct EncoderParams { |
356 | /// Use a predictor transform. Enabled by default. |
357 | pub use_predictor_transform: bool, |
358 | } |
359 | |
360 | impl 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. |
373 | fn 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 | |
599 | const 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 | |
607 | fn write_chunk<W: Write>(mut w: W, name: &[u8], data: &[u8]) -> io::Result<()> { |
608 | debug_assert!(name.len() == 4); |
609 | |
610 | w.write_all(buf:name)?; |
611 | w.write_all(&(data.len() as u32).to_le_bytes())?; |
612 | w.write_all(buf:data)?; |
613 | if data.len() % 2 == 1 { |
614 | w.write_all(&[0])?; |
615 | } |
616 | Ok(()) |
617 | } |
618 | |
619 | /// WebP Encoder. |
620 | pub 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 | |
628 | impl<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)] |
744 | mod 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 | } |
856 | |