/src/image/src/codecs/webp/encoder.rs
Line | Count | Source |
1 | | //! Encoding of WebP images. |
2 | | |
3 | | use std::io::Write; |
4 | | |
5 | | use crate::error::{EncodingError, UnsupportedError, UnsupportedErrorKind}; |
6 | | use crate::{DynamicImage, 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 | 1.87k | pub fn new_lossless(w: W) -> Self { |
35 | 1.87k | Self { |
36 | 1.87k | inner: image_webp::WebPEncoder::new(w), |
37 | 1.87k | } |
38 | 1.87k | } |
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 | 1.87k | pub fn encode( |
49 | 1.87k | self, |
50 | 1.87k | buf: &[u8], |
51 | 1.87k | width: u32, |
52 | 1.87k | height: u32, |
53 | 1.87k | color_type: ExtendedColorType, |
54 | 1.87k | ) -> ImageResult<()> { |
55 | 1.87k | let expected_buffer_len = color_type.buffer_size(width, height); |
56 | 1.87k | assert_eq!( |
57 | | expected_buffer_len, |
58 | 1.87k | buf.len() as u64, |
59 | 0 | "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image", |
60 | 0 | buf.len(), |
61 | | ); |
62 | | |
63 | 1.87k | let color_type = match color_type { |
64 | 0 | ExtendedColorType::L8 => image_webp::ColorType::L8, |
65 | 0 | ExtendedColorType::La8 => image_webp::ColorType::La8, |
66 | 0 | ExtendedColorType::Rgb8 => image_webp::ColorType::Rgb8, |
67 | 1.87k | ExtendedColorType::Rgba8 => image_webp::ColorType::Rgba8, |
68 | | _ => { |
69 | 0 | return Err(ImageError::Unsupported( |
70 | 0 | UnsupportedError::from_format_and_kind( |
71 | 0 | ImageFormat::WebP.into(), |
72 | 0 | UnsupportedErrorKind::Color(color_type), |
73 | 0 | ), |
74 | 0 | )) |
75 | | } |
76 | | }; |
77 | | |
78 | 1.87k | self.inner |
79 | 1.87k | .encode(buf, width, height, color_type) |
80 | 1.87k | .map_err(ImageError::from_webp_encode) |
81 | 1.87k | } |
82 | | } |
83 | | |
84 | | impl<W: Write> ImageEncoder for WebPEncoder<W> { |
85 | | #[track_caller] |
86 | 1.87k | fn write_image( |
87 | 1.87k | self, |
88 | 1.87k | buf: &[u8], |
89 | 1.87k | width: u32, |
90 | 1.87k | height: u32, |
91 | 1.87k | color_type: ExtendedColorType, |
92 | 1.87k | ) -> ImageResult<()> { |
93 | 1.87k | self.encode(buf, width, height, color_type) |
94 | 1.87k | } |
95 | | |
96 | 0 | fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> { |
97 | 0 | self.inner.set_icc_profile(icc_profile); |
98 | 0 | Ok(()) |
99 | 0 | } |
100 | | |
101 | 0 | fn set_exif_metadata(&mut self, exif: Vec<u8>) -> Result<(), UnsupportedError> { |
102 | 0 | self.inner.set_exif_metadata(exif); |
103 | 0 | Ok(()) |
104 | 0 | } |
105 | | |
106 | 0 | fn make_compatible_img( |
107 | 0 | &self, |
108 | 0 | _: crate::io::encoder::MethodSealedToImage, |
109 | 0 | img: &DynamicImage, |
110 | 0 | ) -> Option<DynamicImage> { |
111 | 0 | crate::io::encoder::dynimage_conversion_8bit(img) |
112 | 0 | } |
113 | | } |
114 | | |
115 | | impl ImageError { |
116 | 0 | fn from_webp_encode(e: image_webp::EncodingError) -> Self { |
117 | 0 | match e { |
118 | 0 | image_webp::EncodingError::IoError(e) => ImageError::IoError(e), |
119 | 0 | _ => ImageError::Encoding(EncodingError::new(ImageFormat::WebP.into(), e)), |
120 | | } |
121 | 0 | } |
122 | | } |
123 | | |
124 | | #[cfg(test)] |
125 | | mod tests { |
126 | | use crate::{ImageEncoder, RgbaImage}; |
127 | | |
128 | | #[test] |
129 | | fn write_webp() { |
130 | | let img = RgbaImage::from_raw(10, 6, (0..240).collect()).unwrap(); |
131 | | |
132 | | let mut output = Vec::new(); |
133 | | super::WebPEncoder::new_lossless(&mut output) |
134 | | .write_image( |
135 | | img.inner_pixels(), |
136 | | img.width(), |
137 | | img.height(), |
138 | | crate::ExtendedColorType::Rgba8, |
139 | | ) |
140 | | .unwrap(); |
141 | | |
142 | | let img2 = crate::load_from_memory_with_format(&output, crate::ImageFormat::WebP) |
143 | | .unwrap() |
144 | | .to_rgba8(); |
145 | | |
146 | | assert_eq!(img, img2); |
147 | | } |
148 | | } |