/src/image/src/codecs/webp/encoder.rs
Line | Count | Source (jump to first uncovered line) |
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.81k | pub fn new_lossless(w: W) -> Self { |
35 | 1.81k | Self { |
36 | 1.81k | inner: image_webp::WebPEncoder::new(w), |
37 | 1.81k | } |
38 | 1.81k | } 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.81k | pub fn new_lossless(w: W) -> Self { | 35 | 1.81k | Self { | 36 | 1.81k | inner: image_webp::WebPEncoder::new(w), | 37 | 1.81k | } | 38 | 1.81k | } |
|
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.81k | pub fn encode( |
49 | 1.81k | self, |
50 | 1.81k | buf: &[u8], |
51 | 1.81k | width: u32, |
52 | 1.81k | height: u32, |
53 | 1.81k | color_type: ExtendedColorType, |
54 | 1.81k | ) -> ImageResult<()> { |
55 | 1.81k | let expected_buffer_len = color_type.buffer_size(width, height); |
56 | 1.81k | assert_eq!( |
57 | 1.81k | expected_buffer_len, |
58 | 1.81k | 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.81k | 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.81k | 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.81k | self.inner |
79 | 1.81k | .encode(buf, width, height, color_type) |
80 | 1.81k | .map_err(ImageError::from_webp_encode) |
81 | 1.81k | } 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 | 48 | 1.81k | pub fn encode( | 49 | 1.81k | self, | 50 | 1.81k | buf: &[u8], | 51 | 1.81k | width: u32, | 52 | 1.81k | height: u32, | 53 | 1.81k | color_type: ExtendedColorType, | 54 | 1.81k | ) -> ImageResult<()> { | 55 | 1.81k | let expected_buffer_len = color_type.buffer_size(width, height); | 56 | 1.81k | assert_eq!( | 57 | 1.81k | expected_buffer_len, | 58 | 1.81k | 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.81k | 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.81k | 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.81k | self.inner | 79 | 1.81k | .encode(buf, width, height, color_type) | 80 | 1.81k | .map_err(ImageError::from_webp_encode) | 81 | 1.81k | } |
|
82 | | } |
83 | | |
84 | | impl<W: Write> ImageEncoder for WebPEncoder<W> { |
85 | | #[track_caller] |
86 | 1.81k | fn write_image( |
87 | 1.81k | self, |
88 | 1.81k | buf: &[u8], |
89 | 1.81k | width: u32, |
90 | 1.81k | height: u32, |
91 | 1.81k | color_type: ExtendedColorType, |
92 | 1.81k | ) -> ImageResult<()> { |
93 | 1.81k | self.encode(buf, width, height, color_type) |
94 | 1.81k | } 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 | 86 | 1.81k | fn write_image( | 87 | 1.81k | self, | 88 | 1.81k | buf: &[u8], | 89 | 1.81k | width: u32, | 90 | 1.81k | height: u32, | 91 | 1.81k | color_type: ExtendedColorType, | 92 | 1.81k | ) -> ImageResult<()> { | 93 | 1.81k | self.encode(buf, width, height, color_type) | 94 | 1.81k | } |
|
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 | } 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 |
100 | | |
101 | 0 | fn make_compatible_img( |
102 | 0 | &self, |
103 | 0 | _: crate::io::encoder::MethodSealedToImage, |
104 | 0 | img: &DynamicImage, |
105 | 0 | ) -> Option<DynamicImage> { |
106 | 0 | crate::io::encoder::dynimage_conversion_8bit(img) |
107 | 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 |
108 | | } |
109 | | |
110 | | impl ImageError { |
111 | 0 | fn from_webp_encode(e: image_webp::EncodingError) -> Self { |
112 | 0 | match e { |
113 | 0 | image_webp::EncodingError::IoError(e) => ImageError::IoError(e), |
114 | 0 | _ => ImageError::Encoding(EncodingError::new(ImageFormat::WebP.into(), e)), |
115 | | } |
116 | 0 | } |
117 | | } |
118 | | |
119 | | #[cfg(test)] |
120 | | mod tests { |
121 | | use crate::{ImageEncoder, RgbaImage}; |
122 | | |
123 | | #[test] |
124 | | fn write_webp() { |
125 | | let img = RgbaImage::from_raw(10, 6, (0..240).collect()).unwrap(); |
126 | | |
127 | | let mut output = Vec::new(); |
128 | | super::WebPEncoder::new_lossless(&mut output) |
129 | | .write_image( |
130 | | img.inner_pixels(), |
131 | | img.width(), |
132 | | img.height(), |
133 | | crate::ExtendedColorType::Rgba8, |
134 | | ) |
135 | | .unwrap(); |
136 | | |
137 | | let img2 = crate::load_from_memory_with_format(&output, crate::ImageFormat::WebP) |
138 | | .unwrap() |
139 | | .to_rgba8(); |
140 | | |
141 | | assert_eq!(img, img2); |
142 | | } |
143 | | } |