diff --git a/Cargo.toml b/Cargo.toml index 05e12aee5..9f857fa3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ criterion = "^0.4" image = "^0" imageproc = "^0.23" rusttype = "^0.9" +lazy_static = "^1.4" [[bench]] path = "benches/invert.rs" diff --git a/src/encodings/webp.rs b/src/encodings/webp.rs index 0513c2689..c5489d1e3 100644 --- a/src/encodings/webp.rs +++ b/src/encodings/webp.rs @@ -107,7 +107,8 @@ impl WebPEncoder { config.lossless = self.lossless as _; config.quality = self.quality; - let res = libwebp::WebPEncode(std::ptr::addr_of!(config), std::ptr::addr_of_mut!(picture)); + let res = + libwebp::WebPEncode(std::ptr::addr_of!(config), std::ptr::addr_of_mut!(picture)); if res == 0 { free(picture); return Err(Error::EncodingError("WebP encoding error".to_string())); @@ -163,7 +164,9 @@ impl Encoder for WebPEncoder { let mut final_image = std::mem::zeroed::(); let mut encoded_frames = Vec::new(); - let free = |mut final_image: libwebp::WebPData, encoded_frames: Vec, mux: *mut libwebp::WebPMux| { + let free = |mut final_image: libwebp::WebPData, + encoded_frames: Vec, + mux: *mut libwebp::WebPMux| { libwebp::WebPDataClear(std::ptr::addr_of_mut!(final_image)); for mut f in encoded_frames { libwebp::WebPDataClear(std::ptr::addr_of_mut!(f)); @@ -222,7 +225,10 @@ impl Encoder for WebPEncoder { } i32::MIN..=-5_i32 | 2_i32..=i32::MAX => { free(final_image, encoded_frames, mux); - return Err(Error::EncodingError(format!("WebP mux error {}", mux_error))); + return Err(Error::EncodingError(format!( + "WebP mux error {}", + mux_error + ))); } }; diff --git a/src/image.rs b/src/image.rs index 3a32bb1ba..939fe645a 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,14 +1,9 @@ #![allow(clippy::wildcard_imports)] -use crate::{ - draw::Draw, - error::{ - Error::{self, InvalidExtension}, - Result, - }, - pixel::*, - Dynamic, DynamicFrameIterator, -}; +use crate::{draw::Draw, error::{ + Error::{self, InvalidExtension}, + Result, +}, pixel::*, Dynamic, DynamicFrameIterator, Paste}; #[cfg(feature = "gif")] use crate::encodings::gif; @@ -901,6 +896,50 @@ impl Image

{ self } + /// Pads this image in place on each side with the given value. + /// + /// # Panics + /// * The new width or height would exceed [`u32::MAX`]. + /// * The padding on either side would exceed [`u32::MAX`]. + pub fn pad(&mut self, x1: u32, y1: u32, x2: u32, y2: u32, padding: P) { + let h_padding = x1 + .checked_add(x2) + .expect("new horizontal padding overflowed u32"); + let v_padding = y1 + .checked_add(y2) + .expect("new vertical padding overflowed u32"); + // Strange syntax to avoid if let, as it would bump the MSRV + let new_width = + if let Some(new) = self.width().checked_add(h_padding) {new} else { + panic!("new width overflowed u32") + }; + let new_height = + if let Some(new) = self.height().checked_add(v_padding) {new} else { + panic!("new height overflowed u32") + }; + let mut output = Self::new(new_width, new_height, padding); + output.draw( + &Paste::new(self) + .with_overlay_mode(OverlayMode::Replace) + .with_position(x1, y1) + ); + // Only copy some fields, to preserve metadata + self.data = output.data; + // These both are guaranteed to be non-zero as long as the values didn't overflow, + // which we checked for earlier + unsafe { + self.width = NonZeroU32::new_unchecked(new_width); + self.height = NonZeroU32::new_unchecked(new_height); + } + } + + /// Takes this image and pads it on each side. Useful for method chaining. + #[must_use] + pub fn padded(mut self, x1: u32, y1: u32, x2: u32, y2: u32, padding: P) -> Self { + self.pad(x1, y1, x2, y2, padding); + self + } + /// Mirrors, or flips this image horizontally (about the y-axis) in place. pub fn mirror(&mut self) { let width = self.width(); diff --git a/tests/sample.bin b/tests/sample.bin new file mode 100644 index 000000000..c2cf94918 Binary files /dev/null and b/tests/sample.bin differ diff --git a/tests/test_pad.rs b/tests/test_pad.rs new file mode 100644 index 000000000..4ec55291b --- /dev/null +++ b/tests/test_pad.rs @@ -0,0 +1,43 @@ +use lazy_static::lazy_static; +use ril::prelude::*; + +lazy_static! { + static ref TEST_IMAGE: Image = Image::from_fn( + 256, 256, |x, y| + Rgba::new(x as u8, y as u8, 255, 255) + ); +} + +//noinspection RsAssertEqual +#[test] +fn test_padding() -> ril::Result<()> { + let mut image = TEST_IMAGE.clone(); + image.pad(64, 32, 64, 32, Default::default()); + assert_eq!(image.dimensions(), (384, 320)); + // Using include_bytes here to prevent having to test with a feature enabled + let bytes = image.data + .iter().flat_map(|pixel| pixel.as_bytes()) + .collect::>(); + // Not using assert_eq here, as it causes a gigantic error message + // with the representations of each value + assert!( + bytes.as_ref() == include_bytes!("sample.bin"), + "padded image was not identical to sample" + ); + + Ok(()) +} + +#[test] +#[should_panic(expected = "width overflowed")] +fn test_overflow_width_check() { + let mut image: Image = TEST_IMAGE.clone(); + image.pad(u32::MAX, 0, 0, 0, Default::default()); +} + +#[test] +#[should_panic(expected = "padding overflowed")] +fn test_overflow_pad_check() { + let mut image: Image = TEST_IMAGE.clone(); + image.pad(u32::MAX, 0, 1, 0, Default::default()); +} \ No newline at end of file