From b70196aff99a5a253a778b6d165d7c25c20af33d Mon Sep 17 00:00:00 2001 From: Kozakura913 <98575220+kozakura913@users.noreply.github.com> Date: Thu, 13 Jul 2023 14:48:55 +0900 Subject: [PATCH 1/2] Added animation encoder error handling --- src/animation_encoder.rs | 205 +++++++++++++++++++++++++++++++++++++++ src/encoder.rs | 172 ++------------------------------ src/lib.rs | 4 + src/shared.rs | 25 ++++- 4 files changed, 240 insertions(+), 166 deletions(-) create mode 100644 src/animation_encoder.rs diff --git a/src/animation_encoder.rs b/src/animation_encoder.rs new file mode 100644 index 0000000..6547c8c --- /dev/null +++ b/src/animation_encoder.rs @@ -0,0 +1,205 @@ +use std::ffi::CString; + +#[cfg(feature = "img")] +use image::DynamicImage; +use libwebp_sys::*; + +#[cfg(feature = "img")] +use image::*; + +use crate::{shared::*, Encoder}; + +pub struct AnimFrame<'a> { + image: &'a [u8], + layout: PixelLayout, + width: u32, + height: u32, + timestamp: i32, + config: Option<&'a WebPConfig>, +} +impl<'a> AnimFrame<'a> { + pub fn new( + image: &'a [u8], + layout: PixelLayout, + width: u32, + height: u32, + timestamp: i32, + config: Option<&'a WebPConfig>, + ) -> Self { + Self { + image, + layout, + width, + height, + timestamp, + config, + } + } + #[cfg(feature = "img")] + pub fn from_image(image: &'a DynamicImage, timestamp: i32) -> Result { + match image { + DynamicImage::ImageLuma8(_) => Err("Unimplemented"), + DynamicImage::ImageLumaA8(_) => Err("Unimplemented"), + DynamicImage::ImageRgb8(image) => Ok(Self::from_rgb( + image.as_ref(), + image.width(), + image.height(), + timestamp, + )), + DynamicImage::ImageRgba8(image) => Ok(Self::from_rgba( + image.as_ref(), + image.width(), + image.height(), + timestamp, + )), + _ => Err("Unimplemented"), + } + } + /// 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, timestamp: i32) -> Self { + Self::new(image, PixelLayout::Rgb, width, height, timestamp, None) + } + /// 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, timestamp: i32) -> Self { + Self::new(image, PixelLayout::Rgba, width, height, timestamp, None) + } + pub fn get_image(&self) -> &[u8] { + &self.image + } + pub fn get_layout(&self) -> PixelLayout { + self.layout + } + pub fn get_time_ms(&self) -> i32 { + self.timestamp + } + pub fn width(&self) -> u32 { + self.width + } + pub fn height(&self) -> u32 { + self.height + } +} +impl<'a> From<&'a AnimFrame<'a>> for Encoder<'a> { + fn from(f: &'a AnimFrame) -> Self { + Encoder::new(f.get_image(), f.layout, f.width, f.height) + } +} +#[cfg(feature = "img")] +impl Into for &AnimFrame<'_> { + fn into(self) -> DynamicImage { + if self.layout.is_alpha() { + let image = + ImageBuffer::from_raw(self.width(), self.height(), self.get_image().to_owned()) + .expect("ImageBuffer couldn't be created"); + DynamicImage::ImageRgba8(image) + } else { + let image = + ImageBuffer::from_raw(self.width(), self.height(), self.get_image().to_owned()) + .expect("ImageBuffer couldn't be created"); + DynamicImage::ImageRgb8(image) + } + } +} +pub struct AnimEncoder<'a> { + frames: Vec>, + width: u32, + height: u32, + config: &'a WebPConfig, + muxparams: WebPMuxAnimParams, +} +impl<'a> AnimEncoder<'a> { + pub fn new(width: u32, height: u32, config: &'a WebPConfig) -> Self { + Self { + frames: vec![], + width, + height, + config, + muxparams: WebPMuxAnimParams { + bgcolor: 0, + loop_count: 0, + }, + } + } + pub fn set_bgcolor(&mut self, rgba: [u8; 4]) { + let bgcolor = (u32::from(rgba[3]) << 24) + + (u32::from(rgba[2]) << 16) + + (u32::from(rgba[1]) << 8) + + (u32::from(rgba[0])); + self.muxparams.bgcolor = bgcolor; + } + pub fn set_loop_count(&mut self, loop_count: i32) { + self.muxparams.loop_count = loop_count; + } + pub fn add_frame(&mut self, frame: AnimFrame<'a>) { + self.frames.push(frame); + } + pub fn encode(&self) -> WebPMemory { + self.try_encode().unwrap() + } + pub fn try_encode(&self) -> Result { + unsafe { anim_encode(&self) } + } +} + +#[derive(Debug)] +pub enum AnimEncodeError { + WebPEncodingError(WebPEncodingError), + WebPMuxError(WebPMuxError), + WebPAnimEncoderGetError(String), +} +unsafe fn anim_encode(all_frame: &AnimEncoder) -> Result { + let width = all_frame.width; + let height = all_frame.height; + let mut uninit = std::mem::MaybeUninit::::uninit(); + + let mux_abi_version = WebPGetMuxABIVersion(); + WebPAnimEncoderOptionsInitInternal(uninit.as_mut_ptr(), mux_abi_version); + let encoder = WebPAnimEncoderNewInternal( + width as i32, + height as i32, + uninit.as_ptr(), + mux_abi_version, + ); + let mut frame_pictures = vec![]; + for frame in all_frame.frames.iter() { + let mut pic = crate::new_picture(frame.image, frame.layout, width, height); + let config = frame.config.unwrap_or(all_frame.config); + let ok = WebPAnimEncoderAdd( + encoder, + &mut *pic as *mut _, + frame.timestamp as std::os::raw::c_int, + config, + ); + if ok == 0 { + //ok == false + WebPAnimEncoderDelete(encoder); + return Err(AnimEncodeError::WebPEncodingError(pic.error_code)); + } + frame_pictures.push(pic); + } + WebPAnimEncoderAdd(encoder, std::ptr::null_mut(), 0, std::ptr::null()); + + let mut webp_data = std::mem::MaybeUninit::::uninit(); + let ok = WebPAnimEncoderAssemble(encoder, webp_data.as_mut_ptr()); + if ok == 0 { + //ok == false + let cstring = WebPAnimEncoderGetError(encoder); + let cstring = CString::from_raw(cstring as *mut _); + let string = cstring.to_string_lossy().to_string(); + WebPAnimEncoderDelete(encoder); + return Err(AnimEncodeError::WebPAnimEncoderGetError(string)); + } + WebPAnimEncoderDelete(encoder); + let mux = WebPMuxCreateInternal(webp_data.as_ptr(), 1, mux_abi_version); + let mux_error = WebPMuxSetAnimationParams(mux, &all_frame.muxparams); + if mux_error != WebPMuxError::WEBP_MUX_OK { + return Err(AnimEncodeError::WebPMuxError(mux_error)); + } + let mut raw_data: WebPData = webp_data.assume_init(); + WebPDataClear(&mut raw_data); + let mut webp_data = std::mem::MaybeUninit::::uninit(); + WebPMuxAssemble(mux, webp_data.as_mut_ptr()); + WebPMuxDelete(mux); + let raw_data: WebPData = webp_data.assume_init(); + Ok(WebPMemory(raw_data.bytes as *mut u8, raw_data.size)) +} diff --git a/src/encoder.rs b/src/encoder.rs index fb5cc29..e61324f 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -93,176 +93,18 @@ impl<'a> Encoder<'a> { pub fn encode_advanced(&self, config: &WebPConfig) -> Result { unsafe { let mut picture = new_picture(self.image, self.layout, self.width, self.height); - let res = encode(picture, config); - libwebp_sys::WebPPictureFree(&mut picture as *mut _); + let res = encode(&mut *picture, config); res } } } -pub struct AnimFrame<'a> { - image: &'a [u8], +pub(crate) unsafe fn new_picture( + image: &[u8], layout: PixelLayout, width: u32, height: u32, - timestamp: i32, - config: Option<&'a WebPConfig>, -} -impl<'a> AnimFrame<'a> { - pub fn new( - image: &'a [u8], - layout: PixelLayout, - width: u32, - height: u32, - timestamp: i32, - config: Option<&'a WebPConfig>, - ) -> Self { - Self { - image, - layout, - width, - height, - timestamp, - config, - } - } - #[cfg(feature = "img")] - pub fn from_image(image: &'a DynamicImage, timestamp: i32) -> Result { - match image { - DynamicImage::ImageLuma8(_) => Err("Unimplemented"), - DynamicImage::ImageLumaA8(_) => Err("Unimplemented"), - DynamicImage::ImageRgb8(image) => Ok(Self::from_rgb( - image.as_ref(), - image.width(), - image.height(), - timestamp, - )), - DynamicImage::ImageRgba8(image) => Ok(Self::from_rgba( - image.as_ref(), - image.width(), - image.height(), - timestamp, - )), - _ => Err("Unimplemented"), - } - } - /// 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, timestamp: i32) -> Self { - Self::new(image, PixelLayout::Rgb, width, height, timestamp, None) - } - /// 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, timestamp: i32) -> Self { - Self::new(image, PixelLayout::Rgba, width, height, timestamp, None) - } - pub fn get_image(&self) -> &[u8] { - &self.image - } - pub fn get_layout(&self) -> PixelLayout { - self.layout - } - pub fn get_time_ms(&self) -> i32 { - self.timestamp - } - pub fn width(&self) -> u32 { - self.width - } - pub fn height(&self) -> u32 { - self.height - } -} -impl<'a> From<&'a AnimFrame<'a>> for Encoder<'a> { - fn from(f: &'a AnimFrame) -> Self { - Encoder::new(f.get_image(), f.layout, f.width, f.height) - } -} -#[cfg(feature = "img")] -impl Into for &AnimFrame<'_> { - fn into(self) -> DynamicImage { - if self.layout.is_alpha() { - let image = - ImageBuffer::from_raw(self.width(), self.height(), self.get_image().to_owned()) - .expect("ImageBuffer couldn't be created"); - DynamicImage::ImageRgba8(image) - } else { - let image = - ImageBuffer::from_raw(self.width(), self.height(), self.get_image().to_owned()) - .expect("ImageBuffer couldn't be created"); - DynamicImage::ImageRgb8(image) - } - } -} -pub struct AnimEncoder<'a> { - frames: Vec>, - width: u32, - height: u32, - config: &'a WebPConfig, - muxparams: WebPMuxAnimParams, -} -impl<'a> AnimEncoder<'a> { - pub fn new(width: u32, height: u32, config: &'a WebPConfig) -> Self { - Self { - frames: vec![], - width, - height, - config, - muxparams: WebPMuxAnimParams { - bgcolor: 0, - loop_count: 0, - }, - } - } - pub fn set_bgcolor(&mut self, rgba: [u8; 4]) { - let bgcolor = (u32::from(rgba[3]) << 24) - + (u32::from(rgba[2]) << 16) - + (u32::from(rgba[1]) << 8) - + (u32::from(rgba[0])); - self.muxparams.bgcolor = bgcolor; - } - pub fn set_loop_count(&mut self, loop_count: i32) { - self.muxparams.loop_count = loop_count; - } - pub fn add_frame(&mut self, frame: AnimFrame<'a>) { - self.frames.push(frame); - } - pub fn encode(&self) -> WebPMemory { - unsafe { anim_encode(&self) } - } -} -unsafe fn anim_encode(all_frame: &AnimEncoder) -> WebPMemory { - let width = all_frame.width; - let height = all_frame.height; - let mut uninit = std::mem::MaybeUninit::::uninit(); - - let mux_abi_version = WebPGetMuxABIVersion(); - WebPAnimEncoderOptionsInitInternal(uninit.as_mut_ptr(), mux_abi_version); - let encoder = WebPAnimEncoderNewInternal( - width as i32, - height as i32, - uninit.as_ptr(), - mux_abi_version, - ); - for frame in all_frame.frames.iter() { - let mut pic = new_picture(frame.image, frame.layout, width, height); - let config = frame.config.unwrap_or(all_frame.config); - WebPAnimEncoderAdd( - encoder, - &mut pic, - frame.timestamp as std::os::raw::c_int, - config, - ); - } - WebPAnimEncoderAdd(encoder, std::ptr::null_mut(), 0, std::ptr::null()); - - let mut webp_data = std::mem::MaybeUninit::::uninit(); - WebPAnimEncoderAssemble(encoder, webp_data.as_mut_ptr()); - let mux = WebPMuxCreateInternal(webp_data.as_ptr(), 1, mux_abi_version); - WebPMuxSetAnimationParams(mux, &all_frame.muxparams); - WebPMuxAssemble(mux, webp_data.as_mut_ptr()); - let raw_data: WebPData = webp_data.assume_init(); - WebPMemory(raw_data.bytes as *mut u8, raw_data.size) -} - -unsafe fn new_picture(image: &[u8], layout: PixelLayout, width: u32, height: u32) -> WebPPicture { +) -> ManageedPicture { let mut picture = WebPPicture::new().unwrap(); picture.use_argb = 1; picture.width = width as i32; @@ -275,10 +117,10 @@ unsafe fn new_picture(image: &[u8], layout: PixelLayout, width: u32, height: u32 WebPPictureImportRGB(&mut picture, image.as_ptr(), width as i32 * 3); } } - picture + ManageedPicture(picture) } unsafe fn encode( - mut picture: WebPPicture, + picture: &mut WebPPicture, config: &WebPConfig, ) -> Result { if WebPValidateConfig(config) == 0 { @@ -288,7 +130,7 @@ unsafe fn encode( WebPMemoryWriterInit(ww.as_mut_ptr()); picture.writer = Some(WebPMemoryWrite); picture.custom_ptr = ww.as_mut_ptr() as *mut std::ffi::c_void; - let status = libwebp_sys::WebPEncode(config, &mut picture); + let status = libwebp_sys::WebPEncode(config, picture); let ww = ww.assume_init(); let mem = WebPMemory(ww.mem, ww.size as usize); if status != VP8StatusCode::VP8_STATUS_OK as i32 { diff --git a/src/lib.rs b/src/lib.rs index 3348cec..4b7c853 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,10 @@ //! Currently only a subset of the features supported by libwebp are available. //! The simple encoding and decoding apis are implemented which use the default configuration of libwebp. +mod animation_encoder; +#[doc(inline)] +pub use animation_encoder::*; + mod decoder; #[doc(inline)] pub use decoder::*; diff --git a/src/shared.rs b/src/shared.rs index f54bbdc..39c3082 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -3,7 +3,7 @@ use std::ops::{Deref, DerefMut}; #[cfg(feature = "img")] use image::*; -use libwebp_sys::WebPFree; +use libwebp_sys::{WebPFree, WebPPicture, WebPPictureFree}; /// This struct represents a safe wrapper around memory owned by libwebp. /// Its data contents can be accessed through the Deref and DerefMut traits. @@ -35,6 +35,29 @@ impl DerefMut for WebPMemory { } } +#[derive(Debug)] +pub(crate) struct ManageedPicture(pub(crate) WebPPicture); + +impl Drop for ManageedPicture { + fn drop(&mut self) { + unsafe { WebPPictureFree(&mut self.0 as _) } + } +} + +impl Deref for ManageedPicture { + type Target = WebPPicture; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ManageedPicture { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + /// This struct represents a decoded image. /// Its data contents can be accessed through the Deref and DerefMut traits. /// It is also possible to create an image::DynamicImage from this struct. From c5f0a46753608035040ae462c9b81b9f73b863db Mon Sep 17 00:00:00 2001 From: Kozakura913 <98575220+kozakura913@users.noreply.github.com> Date: Thu, 13 Jul 2023 15:02:18 +0900 Subject: [PATCH 2/2] remove unused import --- src/encoder.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/encoder.rs b/src/encoder.rs index e61324f..ee1f915 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -2,9 +2,6 @@ use image::DynamicImage; use libwebp_sys::*; -#[cfg(feature = "img")] -use image::*; - use crate::shared::*; /// An encoder for WebP images. It uses the default configuration of libwebp.