image/codecs/webp/
encoder.rs

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) => ImageError::IoError(e),
106            _ => ImageError::Encoding(EncodingError::new(ImageFormat::WebP.into(), 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}