| 1 | //! Encoding of WebP images. |
| 2 | |
| 3 | use std::io::Write; |
| 4 | |
| 5 | use crate::error::{EncodingError, UnsupportedError, UnsupportedErrorKind}; |
| 6 | use 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. |
| 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 | 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 | |
| 84 | impl<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 | |
| 102 | impl 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)] |
| 112 | mod 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 | |