1//! Encoding of WebP images.
2
3use std::io::Write;
4
5use crate::error::{EncodingError, UnsupportedError, UnsupportedErrorKind};
6use 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.
26pub struct WebPEncoder<W> {
27 inner: image_webp::WebPEncoder<W>,
28}
29
30impl<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
84impl<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
102impl 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)]
112mod 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