Fix more encoding bugs

This commit is contained in:
Harrison Burt 2022-03-28 19:24:53 +01:00
parent 465aeb0265
commit 8ca8094d8d
7 changed files with 83 additions and 145 deletions

91
Cargo.lock generated
View File

@ -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]]

View File

@ -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"

View File

@ -23,7 +23,13 @@ impl JustInTimePipeline {
impl Pipeline for JustInTimePipeline {
fn on_upload(&self, kind: ImageKind, data: Vec<u8>) -> anyhow::Result<PipelineResult> {
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<PipelineResult> {
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) {

View File

@ -23,7 +23,13 @@ impl RealtimePipeline {
impl Pipeline for RealtimePipeline {
fn on_upload(&self, kind: ImageKind, data: Vec<u8>) -> anyhow::Result<PipelineResult> {
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<PipelineResult> {
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) {

View File

@ -16,6 +16,12 @@ pub fn encode_following_config(
data: Bytes,
) -> anyhow::Result<Vec<EncodedImage>> {
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<Bytes> {
pub fn encode_to(webp_cfg: webp::WebPConfig, img: &DynamicImage, format: ImageFormat) -> anyhow::Result<Bytes> {
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()))

View File

@ -8,5 +8,5 @@ edition = "2018"
[dependencies]
libwebp-sys = "0.3.2"
image = "0.23"
once_cell = "1.8.0"
image = "0.24"
anyhow = "1"

View File

@ -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<WebPConfig> = OnceCell::new();
/// Inits the global encoder config.
///
@ -21,8 +21,8 @@ static CONFIG: OnceCell<WebPConfig> = 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<WebPMemory> {
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<WebPMemory> {
let picture = empty_webp_picture();
let writer = WebPMemoryWriter {
mem: std::ptr::null_mut::<u8>(),
@ -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]