summaryrefslogtreecommitdiff
path: root/vendor/image/src/codecs/webp/encoder.rs
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
committerValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
commit1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch)
tree7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/image/src/codecs/webp/encoder.rs
parent5ecd8cf2cba827454317368b68571df0d13d7842 (diff)
downloadfparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz
fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/image/src/codecs/webp/encoder.rs')
-rw-r--r--vendor/image/src/codecs/webp/encoder.rs242
1 files changed, 242 insertions, 0 deletions
diff --git a/vendor/image/src/codecs/webp/encoder.rs b/vendor/image/src/codecs/webp/encoder.rs
new file mode 100644
index 0000000..0383046
--- /dev/null
+++ b/vendor/image/src/codecs/webp/encoder.rs
@@ -0,0 +1,242 @@
+//! Encoding of WebP images.
+///
+/// Uses the simple encoding API from the [libwebp] library.
+///
+/// [libwebp]: https://developers.google.com/speed/webp/docs/api#simple_encoding_api
+use std::io::Write;
+
+use libwebp::{Encoder, PixelLayout, WebPMemory};
+
+use crate::error::{
+ EncodingError, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
+};
+use crate::flat::SampleLayout;
+use crate::{ColorType, ImageEncoder, ImageError, ImageFormat, ImageResult};
+
+/// WebP Encoder.
+pub struct WebPEncoder<W> {
+ inner: W,
+ quality: WebPQuality,
+}
+
+/// WebP encoder quality.
+#[derive(Debug, Copy, Clone)]
+pub struct WebPQuality(Quality);
+
+#[derive(Debug, Copy, Clone)]
+enum Quality {
+ Lossless,
+ Lossy(u8),
+}
+
+impl WebPQuality {
+ /// Minimum lossy quality value (0).
+ pub const MIN: u8 = 0;
+ /// Maximum lossy quality value (100).
+ pub const MAX: u8 = 100;
+ /// Default lossy quality (80), providing a balance of quality and file size.
+ pub const DEFAULT: u8 = 80;
+
+ /// Lossless encoding.
+ pub fn lossless() -> Self {
+ Self(Quality::Lossless)
+ }
+
+ /// Lossy encoding. 0 = low quality, small size; 100 = high quality, large size.
+ ///
+ /// Values are clamped from 0 to 100.
+ pub fn lossy(quality: u8) -> Self {
+ Self(Quality::Lossy(quality.clamp(Self::MIN, Self::MAX)))
+ }
+}
+
+impl Default for WebPQuality {
+ fn default() -> Self {
+ Self::lossy(WebPQuality::DEFAULT)
+ }
+}
+
+impl<W: Write> WebPEncoder<W> {
+ /// Create a new encoder that writes its output to `w`.
+ ///
+ /// Defaults to lossy encoding, see [`WebPQuality::DEFAULT`].
+ pub fn new(w: W) -> Self {
+ WebPEncoder::new_with_quality(w, WebPQuality::default())
+ }
+
+ /// Create a new encoder with the specified quality, that writes its output to `w`.
+ pub fn new_with_quality(w: W, quality: WebPQuality) -> Self {
+ Self { inner: w, quality }
+ }
+
+ /// Encode image data with the indicated color type.
+ ///
+ /// The encoder requires image data be Rgb8 or Rgba8.
+ pub fn encode(
+ mut self,
+ data: &[u8],
+ width: u32,
+ height: u32,
+ color: ColorType,
+ ) -> ImageResult<()> {
+ // TODO: convert color types internally?
+ let layout = match color {
+ ColorType::Rgb8 => PixelLayout::Rgb,
+ ColorType::Rgba8 => PixelLayout::Rgba,
+ _ => {
+ return Err(ImageError::Unsupported(
+ UnsupportedError::from_format_and_kind(
+ ImageFormat::WebP.into(),
+ UnsupportedErrorKind::Color(color.into()),
+ ),
+ ))
+ }
+ };
+
+ // Validate dimensions upfront to avoid panics.
+ if width == 0
+ || height == 0
+ || !SampleLayout::row_major_packed(color.channel_count(), width, height)
+ .fits(data.len())
+ {
+ return Err(ImageError::Parameter(ParameterError::from_kind(
+ ParameterErrorKind::DimensionMismatch,
+ )));
+ }
+
+ // Call the native libwebp library to encode the image.
+ let encoder = Encoder::new(data, layout, width, height);
+ let encoded: WebPMemory = match self.quality.0 {
+ Quality::Lossless => encoder.encode_lossless(),
+ Quality::Lossy(quality) => encoder.encode(quality as f32),
+ };
+
+ // The simple encoding API in libwebp does not return errors.
+ if encoded.is_empty() {
+ return Err(ImageError::Encoding(EncodingError::new(
+ ImageFormat::WebP.into(),
+ "encoding failed, output empty",
+ )));
+ }
+
+ self.inner.write_all(&encoded)?;
+ Ok(())
+ }
+}
+
+impl<W: Write> ImageEncoder for WebPEncoder<W> {
+ fn write_image(
+ self,
+ buf: &[u8],
+ width: u32,
+ height: u32,
+ color_type: ColorType,
+ ) -> ImageResult<()> {
+ self.encode(buf, width, height, color_type)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::codecs::webp::{WebPEncoder, WebPQuality};
+ use crate::{ColorType, ImageEncoder};
+
+ #[test]
+ fn webp_lossless_deterministic() {
+ // 1x1 8-bit image buffer containing a single red pixel.
+ let rgb: &[u8] = &[255, 0, 0];
+ let rgba: &[u8] = &[255, 0, 0, 128];
+ for (color, img, expected) in [
+ (
+ ColorType::Rgb8,
+ rgb,
+ [
+ 82, 73, 70, 70, 28, 0, 0, 0, 87, 69, 66, 80, 86, 80, 56, 76, 15, 0, 0, 0, 47,
+ 0, 0, 0, 0, 7, 16, 253, 143, 254, 7, 34, 162, 255, 1, 0,
+ ],
+ ),
+ (
+ ColorType::Rgba8,
+ rgba,
+ [
+ 82, 73, 70, 70, 28, 0, 0, 0, 87, 69, 66, 80, 86, 80, 56, 76, 15, 0, 0, 0, 47,
+ 0, 0, 0, 16, 7, 16, 253, 143, 2, 6, 34, 162, 255, 1, 0,
+ ],
+ ),
+ ] {
+ // Encode it into a memory buffer.
+ let mut encoded_img = Vec::new();
+ {
+ let encoder =
+ WebPEncoder::new_with_quality(&mut encoded_img, WebPQuality::lossless());
+ encoder
+ .write_image(&img, 1, 1, color)
+ .expect("image encoding failed");
+ }
+
+ // WebP encoding should be deterministic.
+ assert_eq!(encoded_img, expected);
+ }
+ }
+
+ #[derive(Debug, Clone)]
+ struct MockImage {
+ width: u32,
+ height: u32,
+ color: ColorType,
+ data: Vec<u8>,
+ }
+
+ impl quickcheck::Arbitrary for MockImage {
+ fn arbitrary(g: &mut quickcheck::Gen) -> Self {
+ // Limit to small, non-empty images <= 512x512.
+ let width = u32::arbitrary(g) % 512 + 1;
+ let height = u32::arbitrary(g) % 512 + 1;
+ let (color, stride) = if bool::arbitrary(g) {
+ (ColorType::Rgb8, 3)
+ } else {
+ (ColorType::Rgba8, 4)
+ };
+ let size = width * height * stride;
+ let data: Vec<u8> = (0..size).map(|_| u8::arbitrary(g)).collect();
+ MockImage {
+ width,
+ height,
+ color,
+ data,
+ }
+ }
+ }
+
+ quickcheck! {
+ fn fuzz_webp_valid_image(image: MockImage, quality: u8) -> bool {
+ // Check valid images do not panic.
+ let mut buffer = Vec::<u8>::new();
+ for webp_quality in [WebPQuality::lossless(), WebPQuality::lossy(quality)] {
+ buffer.clear();
+ let encoder = WebPEncoder::new_with_quality(&mut buffer, webp_quality);
+ if !encoder
+ .write_image(&image.data, image.width, image.height, image.color)
+ .is_ok() {
+ return false;
+ }
+ }
+ true
+ }
+
+ fn fuzz_webp_no_panic(data: Vec<u8>, width: u8, height: u8, quality: u8) -> bool {
+ // Check random (usually invalid) parameters do not panic.
+ let mut buffer = Vec::<u8>::new();
+ for color in [ColorType::Rgb8, ColorType::Rgba8] {
+ for webp_quality in [WebPQuality::lossless(), WebPQuality::lossy(quality)] {
+ buffer.clear();
+ let encoder = WebPEncoder::new_with_quality(&mut buffer, webp_quality);
+ // Ignore errors.
+ let _ = encoder
+ .write_image(&data, width as u32, height as u32, color);
+ }
+ }
+ true
+ }
+ }
+}