1 | //! Encoding of WebP images. |
2 | |
3 | use std::io::Write; |
4 | |
5 | use crate::error::{EncodingError, UnsupportedError, UnsupportedErrorKind}; |
6 | use crate::{ExtendedColorType, ImageEncoder, ImageError, ImageFormat, ImageResult}; |
7 | |
8 | /// WebP Encoder. |
9 | /// |
10 | /// ### Limitations |
11 | /// |
12 | /// Right now only **lossless** encoding is supported. |
13 | /// |
14 | /// If you need **lossy** encoding, you'll have to use `libwebp`. |
15 | /// Example code for encoding a [`DynamicImage`](crate::DynamicImage) with `libwebp` |
16 | /// via the [`webp`](https://docs.rs/webp/latest/webp/) crate can be found |
17 | /// [here](https://github.com/jaredforth/webp/blob/main/examples/convert.rs). |
18 | /// |
19 | /// ### Compression ratio |
20 | /// |
21 | /// This encoder reaches compression ratios higher than PNG at a fraction of the encoding time. |
22 | /// However, it does not reach the full potential of lossless WebP for reducing file size. |
23 | /// |
24 | /// If you need an even higher compression ratio at the cost of much slower encoding, |
25 | /// please encode the image with `libwebp` as outlined above. |
26 | pub struct WebPEncoder<W> { |
27 | inner: image_webp::WebPEncoder<W>, |
28 | } |
29 | |
30 | impl<W: Write> WebPEncoder<W> { |
31 | /// Create a new encoder that writes its output to `w`. |
32 | /// |
33 | /// Uses "VP8L" lossless encoding. |
34 | pub fn new_lossless(w: W) -> Self { |
35 | Self { |
36 | inner: image_webp::WebPEncoder::new(w), |
37 | } |
38 | } |
39 | |
40 | /// Encode image data with the indicated color type. |
41 | /// |
42 | /// The encoder requires image data be Rgb8 or Rgba8. |
43 | /// |
44 | /// # Panics |
45 | /// |
46 | /// Panics if `width * height * color.bytes_per_pixel() != data.len()`. |
47 | #[track_caller ] |
48 | pub fn encode( |
49 | self, |
50 | buf: &[u8], |
51 | width: u32, |
52 | height: u32, |
53 | color_type: ExtendedColorType, |
54 | ) -> ImageResult<()> { |
55 | let expected_buffer_len = color_type.buffer_size(width, height); |
56 | assert_eq!( |
57 | expected_buffer_len, |
58 | buf.len() as u64, |
59 | "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x {height} image" , |
60 | buf.len(), |
61 | ); |
62 | |
63 | let color_type = match color_type { |
64 | ExtendedColorType::L8 => image_webp::ColorType::L8, |
65 | ExtendedColorType::La8 => image_webp::ColorType::La8, |
66 | ExtendedColorType::Rgb8 => image_webp::ColorType::Rgb8, |
67 | ExtendedColorType::Rgba8 => image_webp::ColorType::Rgba8, |
68 | _ => { |
69 | return Err(ImageError::Unsupported( |
70 | UnsupportedError::from_format_and_kind( |
71 | ImageFormat::WebP.into(), |
72 | UnsupportedErrorKind::Color(color_type), |
73 | ), |
74 | )) |
75 | } |
76 | }; |
77 | |
78 | self.inner |
79 | .encode(buf, width, height, color_type) |
80 | .map_err(ImageError::from_webp_encode) |
81 | } |
82 | } |
83 | |
84 | impl<W: Write> ImageEncoder for WebPEncoder<W> { |
85 | #[track_caller ] |
86 | fn write_image( |
87 | self, |
88 | buf: &[u8], |
89 | width: u32, |
90 | height: u32, |
91 | color_type: ExtendedColorType, |
92 | ) -> ImageResult<()> { |
93 | self.encode(buf, width, height, color_type) |
94 | } |
95 | |
96 | fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> { |
97 | self.inner.set_icc_profile(icc_profile); |
98 | Ok(()) |
99 | } |
100 | } |
101 | |
102 | impl ImageError { |
103 | fn from_webp_encode(e: image_webp::EncodingError) -> Self { |
104 | match e { |
105 | image_webp::EncodingError::IoError(e: Error) => ImageError::IoError(e), |
106 | _ => ImageError::Encoding(EncodingError::new(format:ImageFormat::WebP.into(), err:e)), |
107 | } |
108 | } |
109 | } |
110 | |
111 | #[cfg (test)] |
112 | mod tests { |
113 | use crate::{ImageEncoder, RgbaImage}; |
114 | |
115 | #[test ] |
116 | fn write_webp() { |
117 | let img = RgbaImage::from_raw(10, 6, (0..240).collect()).unwrap(); |
118 | |
119 | let mut output = Vec::new(); |
120 | super::WebPEncoder::new_lossless(&mut output) |
121 | .write_image( |
122 | img.inner_pixels(), |
123 | img.width(), |
124 | img.height(), |
125 | crate::ExtendedColorType::Rgba8, |
126 | ) |
127 | .unwrap(); |
128 | |
129 | let img2 = crate::load_from_memory_with_format(&output, crate::ImageFormat::WebP) |
130 | .unwrap() |
131 | .to_rgba8(); |
132 | |
133 | assert_eq!(img, img2); |
134 | } |
135 | } |
136 | |