/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.66k | pub fn new_lossless(w: W) -> Self { |
35 | 1.66k | Self { |
36 | 1.66k | inner: image_webp::WebPEncoder::new(w), |
37 | 1.66k | } |
38 | 1.66k | } Unexecuted instantiation: <image::codecs::webp::encoder::WebPEncoder<_>>::new_lossless <image::codecs::webp::encoder::WebPEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::new_lossless Line | Count | Source | 34 | 1.66k | pub fn new_lossless(w: W) -> Self { | 35 | 1.66k | Self { | 36 | 1.66k | inner: image_webp::WebPEncoder::new(w), | 37 | 1.66k | } | 38 | 1.66k | } |
|
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 the buffer does not hold exactly the number of bytes required for the given |
47 | | /// `width`, `height`, and color type, accounting for rows padded to whole bytes for |
48 | | /// sub-byte color types: `height * ((width * color_type.bits_per_pixel() as u32 + 7) / 8)`. |
49 | | #[track_caller] |
50 | 1.66k | pub fn encode( |
51 | 1.66k | self, |
52 | 1.66k | buf: &[u8], |
53 | 1.66k | width: u32, |
54 | 1.66k | height: u32, |
55 | 1.66k | color_type: ExtendedColorType, |
56 | 1.66k | ) -> ImageResult<()> { |
57 | 1.66k | let expected_buffer_len = color_type.buffer_size(width, height); |
58 | 1.66k | assert_eq!( |
59 | | expected_buffer_len, |
60 | 1.66k | buf.len() as u64, |
61 | 0 | "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image", |
62 | 0 | buf.len(), |
63 | | ); |
64 | | |
65 | 1.66k | let color_type = match color_type { |
66 | 0 | ExtendedColorType::L8 => image_webp::ColorType::L8, |
67 | 0 | ExtendedColorType::La8 => image_webp::ColorType::La8, |
68 | 0 | ExtendedColorType::Rgb8 => image_webp::ColorType::Rgb8, |
69 | 1.66k | ExtendedColorType::Rgba8 => image_webp::ColorType::Rgba8, |
70 | | _ => { |
71 | 0 | return Err(ImageError::Unsupported( |
72 | 0 | UnsupportedError::from_format_and_kind( |
73 | 0 | ImageFormat::WebP.into(), |
74 | 0 | UnsupportedErrorKind::Color(color_type), |
75 | 0 | ), |
76 | 0 | )) |
77 | | } |
78 | | }; |
79 | | |
80 | 1.66k | self.inner |
81 | 1.66k | .encode(buf, width, height, color_type) |
82 | 1.66k | .map_err(ImageError::from_webp_encode) |
83 | 1.66k | } Unexecuted instantiation: <image::codecs::webp::encoder::WebPEncoder<_>>::encode <image::codecs::webp::encoder::WebPEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode Line | Count | Source | 50 | 1.66k | pub fn encode( | 51 | 1.66k | self, | 52 | 1.66k | buf: &[u8], | 53 | 1.66k | width: u32, | 54 | 1.66k | height: u32, | 55 | 1.66k | color_type: ExtendedColorType, | 56 | 1.66k | ) -> ImageResult<()> { | 57 | 1.66k | let expected_buffer_len = color_type.buffer_size(width, height); | 58 | 1.66k | assert_eq!( | 59 | | expected_buffer_len, | 60 | 1.66k | buf.len() as u64, | 61 | 0 | "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image", | 62 | 0 | buf.len(), | 63 | | ); | 64 | | | 65 | 1.66k | let color_type = match color_type { | 66 | 0 | ExtendedColorType::L8 => image_webp::ColorType::L8, | 67 | 0 | ExtendedColorType::La8 => image_webp::ColorType::La8, | 68 | 0 | ExtendedColorType::Rgb8 => image_webp::ColorType::Rgb8, | 69 | 1.66k | ExtendedColorType::Rgba8 => image_webp::ColorType::Rgba8, | 70 | | _ => { | 71 | 0 | return Err(ImageError::Unsupported( | 72 | 0 | UnsupportedError::from_format_and_kind( | 73 | 0 | ImageFormat::WebP.into(), | 74 | 0 | UnsupportedErrorKind::Color(color_type), | 75 | 0 | ), | 76 | 0 | )) | 77 | | } | 78 | | }; | 79 | | | 80 | 1.66k | self.inner | 81 | 1.66k | .encode(buf, width, height, color_type) | 82 | 1.66k | .map_err(ImageError::from_webp_encode) | 83 | 1.66k | } |
|
84 | | } |
85 | | |
86 | | impl<W: Write> ImageEncoder for WebPEncoder<W> { |
87 | | #[track_caller] |
88 | 1.66k | fn write_image( |
89 | 1.66k | self, |
90 | 1.66k | buf: &[u8], |
91 | 1.66k | width: u32, |
92 | 1.66k | height: u32, |
93 | 1.66k | color_type: ExtendedColorType, |
94 | 1.66k | ) -> ImageResult<()> { |
95 | 1.66k | self.encode(buf, width, height, color_type) |
96 | 1.66k | } Unexecuted instantiation: <image::codecs::webp::encoder::WebPEncoder<_> as image::io::encoder::ImageEncoder>::write_image <image::codecs::webp::encoder::WebPEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::write_image Line | Count | Source | 88 | 1.66k | fn write_image( | 89 | 1.66k | self, | 90 | 1.66k | buf: &[u8], | 91 | 1.66k | width: u32, | 92 | 1.66k | height: u32, | 93 | 1.66k | color_type: ExtendedColorType, | 94 | 1.66k | ) -> ImageResult<()> { | 95 | 1.66k | self.encode(buf, width, height, color_type) | 96 | 1.66k | } |
|
97 | | |
98 | 0 | fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> { |
99 | 0 | self.inner.set_icc_profile(icc_profile); |
100 | 0 | Ok(()) |
101 | 0 | } Unexecuted instantiation: <image::codecs::webp::encoder::WebPEncoder<_> as image::io::encoder::ImageEncoder>::set_icc_profile Unexecuted instantiation: <image::codecs::webp::encoder::WebPEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::set_icc_profile |
102 | | |
103 | 0 | fn set_exif_metadata(&mut self, exif: Vec<u8>) -> Result<(), UnsupportedError> { |
104 | 0 | self.inner.set_exif_metadata(exif); |
105 | 0 | Ok(()) |
106 | 0 | } Unexecuted instantiation: <image::codecs::webp::encoder::WebPEncoder<_> as image::io::encoder::ImageEncoder>::set_exif_metadata Unexecuted instantiation: <image::codecs::webp::encoder::WebPEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::set_exif_metadata |
107 | | |
108 | 0 | fn set_xmp_metadata(&mut self, xmp: Vec<u8>) -> Result<(), UnsupportedError> { |
109 | 0 | self.inner.set_xmp_metadata(xmp); |
110 | 0 | Ok(()) |
111 | 0 | } Unexecuted instantiation: <image::codecs::webp::encoder::WebPEncoder<_> as image::io::encoder::ImageEncoder>::set_xmp_metadata Unexecuted instantiation: <image::codecs::webp::encoder::WebPEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::set_xmp_metadata |
112 | | |
113 | 0 | fn make_compatible_img( |
114 | 0 | &self, |
115 | 0 | _: crate::io::encoder::MethodSealedToImage, |
116 | 0 | img: &DynamicImage, |
117 | 0 | ) -> Option<DynamicImage> { |
118 | 0 | crate::io::encoder::dynimage_conversion_8bit(img) |
119 | 0 | } Unexecuted instantiation: <image::codecs::webp::encoder::WebPEncoder<_> as image::io::encoder::ImageEncoder>::make_compatible_img Unexecuted instantiation: <image::codecs::webp::encoder::WebPEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::make_compatible_img |
120 | | } |
121 | | |
122 | | impl ImageError { |
123 | 0 | fn from_webp_encode(e: image_webp::EncodingError) -> Self { |
124 | 0 | match e { |
125 | 0 | image_webp::EncodingError::IoError(e) => ImageError::IoError(e), |
126 | 0 | _ => ImageError::Encoding(EncodingError::new(ImageFormat::WebP.into(), e)), |
127 | | } |
128 | 0 | } |
129 | | } |
130 | | |
131 | | #[cfg(test)] |
132 | | mod tests { |
133 | | use crate::{ImageDecoder as _, ImageEncoder, RgbaImage}; |
134 | | |
135 | | #[test] |
136 | | fn write_webp() { |
137 | | let img = RgbaImage::from_raw(10, 6, (0..240).collect()).unwrap(); |
138 | | |
139 | | let mut output = Vec::new(); |
140 | | super::WebPEncoder::new_lossless(&mut output) |
141 | | .write_image( |
142 | | img.subpixels(), |
143 | | img.width(), |
144 | | img.height(), |
145 | | crate::ExtendedColorType::Rgba8, |
146 | | ) |
147 | | .unwrap(); |
148 | | |
149 | | let img2 = crate::load_from_memory_with_format(&output, crate::ImageFormat::WebP) |
150 | | .unwrap() |
151 | | .to_rgba8(); |
152 | | |
153 | | assert_eq!(img, img2); |
154 | | } |
155 | | |
156 | | #[test] |
157 | | fn roundtrip_xmp() { |
158 | | let img = RgbaImage::from_raw(10, 6, (0..240).collect()).unwrap(); |
159 | | let xmp = b"<x:xmpmeta xmlns:x=\"adobe:ns:meta/\"><rdf:RDF></rdf:RDF></x:xmpmeta>".to_vec(); |
160 | | |
161 | | let mut output = Vec::new(); |
162 | | { |
163 | | let mut encoder = super::WebPEncoder::new_lossless(&mut output); |
164 | | encoder.set_xmp_metadata(xmp.clone()).unwrap(); |
165 | | encoder |
166 | | .write_image( |
167 | | img.subpixels(), |
168 | | img.width(), |
169 | | img.height(), |
170 | | crate::ExtendedColorType::Rgba8, |
171 | | ) |
172 | | .unwrap(); |
173 | | } |
174 | | |
175 | | let mut decoder = crate::codecs::webp::WebPDecoder::new(std::io::Cursor::new(&output)) |
176 | | .expect("Could not decode image"); |
177 | | let decoded_xmp = decoder |
178 | | .xmp_metadata() |
179 | | .expect("Error decoding XMP") |
180 | | .expect("XMP is empty"); |
181 | | assert_eq!(xmp, decoded_xmp); |
182 | | } |
183 | | } |