From 8ca8094d8dc423508c02afb39963b0908f2f9f4e Mon Sep 17 00:00:00 2001 From: Harrison Burt <57491488+ChillFish8@users.noreply.github.com> Date: Mon, 28 Mar 2022 19:24:53 +0100 Subject: [PATCH] Fix more encoding bugs --- Cargo.lock | 91 ++++----------------------------------- Cargo.toml | 2 +- src/pipelines/jit.rs | 16 ++++++- src/pipelines/realtime.rs | 16 ++++++- src/processor/encoder.rs | 23 ++++++++-- webp/Cargo.toml | 4 +- webp/src/lib.rs | 76 +++++++++++--------------------- 7 files changed, 83 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e521eea..9363dae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -417,16 +417,6 @@ dependencies = [ "syn", ] -[[package]] -name = "deflate" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" -dependencies = [ - "adler32", - "byteorder", -] - [[package]] name = "deflate" version = "1.0.0" @@ -494,7 +484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4badb9489a465cb2c555af1f00f0bfd8cecd6fc12ac11da9d5b40c5dd5f0200" dependencies = [ "bit_field", - "deflate 1.0.0", + "deflate", "flume", "half", "inflate", @@ -870,25 +860,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "image" -version = "0.23.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "gif", - "jpeg-decoder 0.1.22", - "num-iter", - "num-rational 0.3.2", - "num-traits", - "png 0.16.8", - "scoped_threadpool", - "tiff 0.6.1", -] - [[package]] name = "image" version = "0.24.1" @@ -902,11 +873,11 @@ dependencies = [ "gif", "jpeg-decoder 0.2.2", "num-iter", - "num-rational 0.4.0", + "num-rational", "num-traits", - "png 0.17.5", + "png", "scoped_threadpool", - "tiff 0.7.1", + "tiff", ] [[package]] @@ -957,9 +928,6 @@ name = "jpeg-decoder" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" -dependencies = [ - "rayon", -] [[package]] name = "jpeg-decoder" @@ -1053,7 +1021,7 @@ dependencies = [ "enum_dispatch", "futures", "hashbrown 0.12.0", - "image 0.24.1", + "image", "mimalloc", "mime", "once_cell", @@ -1108,15 +1076,6 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" -[[package]] -name = "miniz_oxide" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" -dependencies = [ - "adler32", -] - [[package]] name = "miniz_oxide" version = "0.4.4" @@ -1217,17 +1176,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.0" @@ -1369,18 +1317,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "png" -version = "0.16.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" -dependencies = [ - "bitflags", - "crc32fast", - "deflate 0.8.6", - "miniz_oxide 0.3.7", -] - [[package]] name = "png" version = "0.17.5" @@ -1389,7 +1325,7 @@ checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" dependencies = [ "bitflags", "crc32fast", - "deflate 1.0.0", + "deflate", "miniz_oxide 0.5.1", ] @@ -1932,17 +1868,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "tiff" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" -dependencies = [ - "jpeg-decoder 0.1.22", - "miniz_oxide 0.4.4", - "weezl", -] - [[package]] name = "tiff" version = "0.7.1" @@ -2308,9 +2233,9 @@ checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" name = "webp" version = "0.2.0" dependencies = [ - "image 0.23.14", + "anyhow", + "image", "libwebp-sys", - "once_cell", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index db7c400..29cfd38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ crossbeam = "0.8.1" tracing = "0.1.30" tracing-futures = "0.2.5" tracing-subscriber = "0.3.8" -image = "0.24.1" +image = "0.24" base64 = "0.13.0" bytes = "1" anyhow = "1" diff --git a/src/pipelines/jit.rs b/src/pipelines/jit.rs index 61d145b..d396db0 100644 --- a/src/pipelines/jit.rs +++ b/src/pipelines/jit.rs @@ -23,7 +23,13 @@ impl JustInTimePipeline { impl Pipeline for JustInTimePipeline { fn on_upload(&self, kind: ImageKind, data: Vec) -> anyhow::Result { - let img = processor::encoder::encode_once(self.formats.original_image_store_format, kind, data.into())?; + let webp_config = webp::config( + self.formats.webp_config.quality.is_none(), + self.formats.webp_config.quality.unwrap_or(50f32), + self.formats.webp_config.method.unwrap_or(4) as i32, + self.formats.webp_config.threading, + ); + let img = processor::encoder::encode_once(webp_config, self.formats.original_image_store_format, kind, data.into())?; Ok(PipelineResult { response: None, @@ -39,7 +45,13 @@ impl Pipeline for JustInTimePipeline { sizing_id: u32, _custom_size: Option<(u32, u32)>, ) -> anyhow::Result { - let img = processor::encoder::encode_once(desired_kind, data_kind, data)?; + let webp_config = webp::config( + self.formats.webp_config.quality.is_none(), + self.formats.webp_config.quality.unwrap_or(50f32), + self.formats.webp_config.method.unwrap_or(4) as i32, + self.formats.webp_config.threading, + ); + let img = processor::encoder::encode_once(webp_config, desired_kind, data_kind, data)?; let (buff, sizing_id) = if sizing_id != 0 { if let Some(cfg) = self.presets.get(&sizing_id) { diff --git a/src/pipelines/realtime.rs b/src/pipelines/realtime.rs index 84a3bfa..762fc71 100644 --- a/src/pipelines/realtime.rs +++ b/src/pipelines/realtime.rs @@ -23,7 +23,13 @@ impl RealtimePipeline { impl Pipeline for RealtimePipeline { fn on_upload(&self, kind: ImageKind, data: Vec) -> anyhow::Result { - let img = processor::encoder::encode_once(self.formats.original_image_store_format, kind, data.into())?; + let webp_config = webp::config( + self.formats.webp_config.quality.is_none(), + self.formats.webp_config.quality.unwrap_or(50f32), + self.formats.webp_config.method.unwrap_or(4) as i32, + self.formats.webp_config.threading, + ); + let img = processor::encoder::encode_once(webp_config, self.formats.original_image_store_format, kind, data.into())?; Ok(PipelineResult { response: None, @@ -39,7 +45,13 @@ impl Pipeline for RealtimePipeline { sizing_id: u32, custom_size: Option<(u32, u32)>, ) -> anyhow::Result { - let img = processor::encoder::encode_once(desired_kind, data_kind, data)?; + let webp_config = webp::config( + self.formats.webp_config.quality.is_none(), + self.formats.webp_config.quality.unwrap_or(50f32), + self.formats.webp_config.method.unwrap_or(4) as i32, + self.formats.webp_config.threading, + ); + let img = processor::encoder::encode_once(webp_config, desired_kind, data_kind, data)?; let (buff, sizing_id) = if sizing_id != 0 { let maybe_resize = match self.presets.get(&sizing_id) { diff --git a/src/processor/encoder.rs b/src/processor/encoder.rs index 81f73e2..d814aa1 100644 --- a/src/processor/encoder.rs +++ b/src/processor/encoder.rs @@ -16,6 +16,12 @@ pub fn encode_following_config( data: Bytes, ) -> anyhow::Result> { let original_image = Arc::new(load_from_memory_with_format(data.as_ref(), kind.into())?); + let webp_config = webp::config( + cfg.webp_config.quality.is_none(), + cfg.webp_config.quality.unwrap_or(50f32), + cfg.webp_config.method.unwrap_or(4) as i32, + cfg.webp_config.threading, + ); let (tx, rx) = crossbeam::channel::bounded(4); @@ -24,9 +30,9 @@ pub fn encode_following_config( let tx_local = tx.clone(); let local = original_image.clone(); rayon::spawn(move || { - let result = encode_to(&local, ImageFormat::Png); + let result = encode_to(webp_config, &local, (*variant).into()); tx_local - .send(result.map(|v| EncodedImage { kind: ImageKind::Png, buff: v })) + .send(result.map(|v| EncodedImage { kind: *variant, buff: v })) .expect("Failed to respond to encoding request. Sender already closed."); }); } @@ -54,6 +60,7 @@ pub fn encode_following_config( pub fn encode_once( + webp_cfg: webp::WebPConfig, to: ImageKind, from: ImageKind, data: Bytes, @@ -64,7 +71,7 @@ pub fn encode_once( let encoded = if from != to { rayon::spawn(move || { - let result = encode_to(&original_image, to.into()); + let result = encode_to(webp_cfg, &original_image, to.into()); tx.send(result.map(|v| EncodedImage { kind: to, buff: v })) .expect("Failed to respond to encoding request. Sender already closed."); }); @@ -82,7 +89,15 @@ pub fn encode_once( #[inline] -pub fn encode_to(img: &DynamicImage, format: ImageFormat) -> anyhow::Result { +pub fn encode_to(webp_cfg: webp::WebPConfig, img: &DynamicImage, format: ImageFormat) -> anyhow::Result { + if let ImageFormat::WebP = format { + let webp_image = webp::Encoder::from_image(webp_cfg, img); + let encoded = webp_image.encode(); + + return Ok(Bytes::from(encoded?.to_vec())) + } + + let mut buff = Cursor::new(Vec::new()); img.write_to(&mut buff, format)?; Ok(Bytes::from(buff.into_inner())) diff --git a/webp/Cargo.toml b/webp/Cargo.toml index f42249a..e778ba0 100644 --- a/webp/Cargo.toml +++ b/webp/Cargo.toml @@ -8,5 +8,5 @@ edition = "2018" [dependencies] libwebp-sys = "0.3.2" -image = "0.23" -once_cell = "1.8.0" \ No newline at end of file +image = "0.24" +anyhow = "1" \ No newline at end of file diff --git a/webp/src/lib.rs b/webp/src/lib.rs index 77bfe8b..5ffa2f6 100644 --- a/webp/src/lib.rs +++ b/webp/src/lib.rs @@ -1,13 +1,13 @@ use std::fmt::{Debug, Error, Formatter}; use std::ops::{Deref, DerefMut}; -use image::{DynamicImage, GenericImageView, RgbaImage}; +use anyhow::{Result, anyhow}; +use image::{DynamicImage, RgbaImage}; use libwebp_sys::WebPEncodingError::VP8_ENC_OK; use libwebp_sys::WebPPreset::WEBP_PRESET_DEFAULT; use libwebp_sys::*; -use once_cell::sync::OnceCell; +pub use libwebp_sys::WebPConfig; -static CONFIG: OnceCell = OnceCell::new(); /// Inits the global encoder config. /// @@ -21,8 +21,8 @@ static CONFIG: OnceCell = OnceCell::new(); /// /// - multi_threading: /// If the system should to attempt to use in multi-threaded encoding. -pub fn init_global(lossless: bool, quality: f32, method: i32, multi_threading: bool) { - let cfg = WebPConfig { +pub fn config(lossless: bool, quality: f32, method: i32, multi_threading: bool) -> WebPConfig { + WebPConfig { lossless: if lossless { 1 } else { 0 }, quality, method, @@ -51,9 +51,7 @@ pub fn init_global(lossless: bool, quality: f32, method: i32, multi_threading: b use_delta_palette: 0, use_sharp_yuv: 0, pad: [100, 100], - }; - - let _ = CONFIG.set(cfg); + } } /// Picture is uninitialized. @@ -118,6 +116,7 @@ pub enum PixelLayout { } pub struct Encoder<'a> { + cfg: WebPConfig, layout: PixelLayout, image: &'a [u8], width: u32, @@ -126,30 +125,25 @@ pub struct Encoder<'a> { impl<'a> Encoder<'a> { /// Creates a new encoder from the given image. - pub fn from_image(image: &'a DynamicImage) -> Self { + pub fn from_image(cfg: WebPConfig, image: &'a DynamicImage) -> Self { match image { DynamicImage::ImageRgb8(image) => { - Self::from_rgb(image.as_ref(), image.width(), image.height()) + Self::from_rgb(cfg, image.as_ref(), image.width(), image.height()) }, DynamicImage::ImageRgba8(image) => { - Self::from_rgba(image.as_ref(), image.width(), image.height()) - }, - DynamicImage::ImageBgr8(image) => { - Self::from_bgr(image.as_ref(), image.width(), image.height()) - }, - DynamicImage::ImageBgra8(image) => { - Self::from_bgra(image.as_ref(), image.width(), image.height()) + Self::from_rgba(cfg,image.as_ref(), image.width(), image.height()) }, other => { let image = other.to_rgba8(); - Self::from_other(other.as_bytes(), other.width(), other.height(), image) + Self::from_other(cfg,other.as_bytes(), other.width(), other.height(), image) }, } } /// Creates a new encoder from the given image data in the RGB pixel layout. - pub fn from_rgb(image: &'a [u8], width: u32, height: u32) -> Self { + pub fn from_rgb(cfg: WebPConfig, image: &'a [u8], width: u32, height: u32) -> Self { Self { + cfg, image, width, height, @@ -158,8 +152,9 @@ impl<'a> Encoder<'a> { } /// Creates a new encoder from the given image data in the RGBA pixel layout. - pub fn from_rgba(image: &'a [u8], width: u32, height: u32) -> Self { + pub fn from_rgba(cfg: WebPConfig, image: &'a [u8], width: u32, height: u32) -> Self { Self { + cfg, image, width, height, @@ -167,30 +162,11 @@ impl<'a> Encoder<'a> { } } - /// Creates a new encoder from the given image data in the BGR pixel layout. - pub fn from_bgr(image: &'a [u8], width: u32, height: u32) -> Self { - Self { - image, - width, - height, - layout: PixelLayout::BGR, - } - } - - /// Creates a new encoder from the given image data in the BGRA pixel layout. - pub fn from_bgra(image: &'a [u8], width: u32, height: u32) -> Self { - Self { - image, - width, - height, - layout: PixelLayout::BGRA, - } - } - /// Creates a new encoder from the given image data in the Other layout, /// this creates a copy of the data to convert it to RGBA. - pub fn from_other(image: &'a [u8], width: u32, height: u32, other: RgbaImage) -> Self { + pub fn from_other(cfg: WebPConfig, image: &'a [u8], width: u32, height: u32, other: RgbaImage) -> Self { Self { + cfg, image, width, height, @@ -199,28 +175,26 @@ impl<'a> Encoder<'a> { } /// Encode the image with the given global config. - pub fn encode(&self) -> WebPMemory { + pub fn encode(self) -> Result { let (img, layout) = if let PixelLayout::Other(img) = &self.layout { (img.as_ref(), &PixelLayout::RGBA) } else { (self.image.as_ref(), &self.layout) }; - unsafe { encode(img, layout, self.width, self.height) } + unsafe { encode(self.cfg, img, layout, self.width, self.height) } } } macro_rules! check_ok { ( $e:expr, $msg:expr ) => {{ if $e == 0 { - panic!("{}", $msg); + return Err(anyhow!("{}", $msg)); } }}; } -unsafe fn encode(image: &[u8], layout: &PixelLayout, width: u32, height: u32) -> WebPMemory { - let cfg = CONFIG.get().expect("config un-initialised.").clone(); - +unsafe fn encode(cfg: WebPConfig, image: &[u8], layout: &PixelLayout, width: u32, height: u32) -> Result { let picture = empty_webp_picture(); let writer = WebPMemoryWriter { mem: std::ptr::null_mut::(), @@ -283,13 +257,13 @@ unsafe fn encode(image: &[u8], layout: &PixelLayout, width: u32, height: u32) -> WebPPictureFree(picture_ptr); if ok == 0 { WebPMemoryWriterClear(writer_ptr); - panic!( + return Err(anyhow!( "memory error. libwebp error code: {:?}", (*picture_ptr).error_code - ) + )) } - WebPMemory((*writer_ptr).mem, (*writer_ptr).size) + Ok(WebPMemory((*writer_ptr).mem, (*writer_ptr).size)) } /// This struct represents a safe wrapper around memory owned by libwebp. @@ -329,7 +303,7 @@ mod tests { use super::*; fn ensure_global() { - init_global(true, 50.0, 6, true) + config(true, 50.0, 6, true) } #[test]