diff --git a/CHANGES.md b/CHANGES.md index 231bd29dfd..252284871b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -56,6 +56,12 @@ version. - Introduced `PixelDensity` and `PixelDensityUnit` to store DPI information in formats that support encoding this form of metadata (e.g. in `jpeg`). +### Version 0.22.5 + +- Added `GenericImage::copy_within`, specialized for `ImageBuffer` +- Fixed decoding of interlaced `gif` files +- Prepare for future compatibility of array `IntoIterator` in example code + ### Version 0.22.4 - Added in-place variants for flip and rotate operations. diff --git a/examples/scaledown/main.rs b/examples/scaledown/main.rs index 322a928e41..416096bf7b 100644 --- a/examples/scaledown/main.rs +++ b/examples/scaledown/main.rs @@ -34,7 +34,7 @@ fn main() { ("cmr", FilterType::CatmullRom), ("gauss", FilterType::Gaussian), ("lcz2", FilterType::Lanczos3), - ].into_iter() + ].iter() { let timer = Instant::now(); let scaled = img.resize(400, 400, filter); diff --git a/examples/scaleup/main.rs b/examples/scaleup/main.rs index e9687e82da..67de1f40ec 100644 --- a/examples/scaleup/main.rs +++ b/examples/scaleup/main.rs @@ -34,7 +34,7 @@ fn main() { ("xcmr", FilterType::CatmullRom), ("ygauss", FilterType::Gaussian), ("zlcz2", FilterType::Lanczos3), - ].into_iter() + ].iter() { let timer = Instant::now(); let scaled = tiny.resize(32, 32, filter); diff --git a/src/buffer.rs b/src/buffer.rs index 4c64fc7909..051fb2af25 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -9,6 +9,7 @@ use crate::flat::{FlatSamples, SampleLayout}; use crate::dynimage::{save_buffer, save_buffer_with_format}; use crate::error::ImageResult; use crate::image::{GenericImage, GenericImageView, ImageFormat}; +use crate::math::Rect; use crate::traits::{EncodableLayout, Primitive}; use crate::utils::expand_packed; @@ -923,11 +924,61 @@ where self.get_pixel_mut(x, y).blend(&p) } + fn copy_within(&mut self, source: Rect, x: u32, y: u32) -> bool { + let Rect { x: sx, y: sy, width, height } = source; + let dx = x; + let dy = y; + assert!(sx < self.width() && dx < self.width()); + assert!(sy < self.height() && dy < self.height()); + if self.width() - dx.max(sx) < width || self.height() - dy.max(sy) < height { + return false; + } + + if sy < dy { + for y in (0..height).rev() { + let sy = sy + y; + let dy = dy + y; + let Range { start, .. } = self.pixel_indices_unchecked(sx, sy); + let Range { end, .. } = self.pixel_indices_unchecked(sx + width - 1, sy); + let dst = self.pixel_indices_unchecked(dx, dy).start; + slice_copy_within(self, start..end, dst); + } + } else { + for y in 0..height { + let sy = sy + y; + let dy = dy + y; + let Range { start, .. } = self.pixel_indices_unchecked(sx, sy); + let Range { end, .. } = self.pixel_indices_unchecked(sx + width - 1, sy); + let dst = self.pixel_indices_unchecked(dx, dy).start; + slice_copy_within(self, start..end, dst); + } + } + true + } + fn inner_mut(&mut self) -> &mut Self::InnerImage { self } } +// FIXME non-generic `core::slice::copy_within` implementation used by `ImageBuffer::copy_within`. The implementation is rewritten +// here due to minimum rust version support(MSRV). Image has a MSRV of 1.34 as of writing this while `core::slice::copy_within` +// has been stabilized in 1.37. +#[inline(always)] +fn slice_copy_within(slice: &mut [T], Range { start: src_start, end: src_end }: Range, dest: usize) { + assert!(src_start <= src_end, "src end is before src start"); + assert!(src_end <= slice.len(), "src is out of bounds"); + let count = src_end - src_start; + assert!(dest <= slice.len() - count, "dest is out of bounds"); + unsafe { + std::ptr::copy( + slice.as_ptr().add(src_start), + slice.as_mut_ptr().add(dest), + count, + ); + } +} + // concrete implementation for `Vec`-backed buffers // TODO: I think that rustc does not "see" this impl any more: the impl with // Container meets the same requirements. At least, I got compile errors that @@ -1091,8 +1142,10 @@ pub(crate) type GrayAlpha16Image = ImageBuffer, Vec>; #[cfg(test)] mod test { - use super::{ImageBuffer, RgbImage}; + use super::{GrayImage, ImageBuffer, RgbImage}; + use crate::image::GenericImage; use crate::color; + use crate::math::Rect; #[cfg(feature = "benchmarks")] use test; @@ -1204,4 +1257,92 @@ mod test { b.bytes = 1000 * 1000 * 3; } + + #[test] + fn test_image_buffer_copy_within_oob() { + let mut image: GrayImage = ImageBuffer::from_raw(4, 4, vec![0u8; 16]).unwrap(); + assert!(!image.copy_within(Rect { x: 0, y: 0, width: 5, height: 4 }, 0, 0)); + assert!(!image.copy_within(Rect { x: 0, y: 0, width: 4, height: 5 }, 0, 0)); + assert!(!image.copy_within(Rect { x: 1, y: 0, width: 4, height: 4 }, 0, 0)); + assert!(!image.copy_within(Rect { x: 0, y: 0, width: 4, height: 4 }, 1, 0)); + assert!(!image.copy_within(Rect { x: 0, y: 1, width: 4, height: 4 }, 0, 0)); + assert!(!image.copy_within(Rect { x: 0, y: 0, width: 4, height: 4 }, 0, 1)); + assert!(!image.copy_within(Rect { x: 1, y: 1, width: 4, height: 4 }, 0, 0)); + } + + #[test] + fn test_image_buffer_copy_within_tl() { + let data = &[ + 00, 01, 02, 03, + 04, 05, 06, 07, + 08, 09, 10, 11, + 12, 13, 14, 15 + ]; + let expected = [ + 00, 01, 02, 03, + 04, 00, 01, 02, + 08, 04, 05, 06, + 12, 08, 09, 10, + ]; + let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); + assert!(image.copy_within(Rect { x: 0, y: 0, width: 3, height: 3 }, 1, 1)); + assert_eq!(&image.into_raw(), &expected); + } + + #[test] + fn test_image_buffer_copy_within_tr() { + let data = &[ + 00, 01, 02, 03, + 04, 05, 06, 07, + 08, 09, 10, 11, + 12, 13, 14, 15 + ]; + let expected = [ + 00, 01, 02, 03, + 01, 02, 03, 07, + 05, 06, 07, 11, + 09, 10, 11, 15 + ]; + let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); + assert!(image.copy_within(Rect { x: 1, y: 0, width: 3, height: 3 }, 0, 1)); + assert_eq!(&image.into_raw(), &expected); + } + + #[test] + fn test_image_buffer_copy_within_bl() { + let data = &[ + 00, 01, 02, 03, + 04, 05, 06, 07, + 08, 09, 10, 11, + 12, 13, 14, 15 + ]; + let expected = [ + 00, 04, 05, 06, + 04, 08, 09, 10, + 08, 12, 13, 14, + 12, 13, 14, 15 + ]; + let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); + assert!(image.copy_within(Rect { x: 0, y: 1, width: 3, height: 3 }, 1, 0)); + assert_eq!(&image.into_raw(), &expected); + } + + #[test] + fn test_image_buffer_copy_within_br() { + let data = &[ + 00, 01, 02, 03, + 04, 05, 06, 07, + 08, 09, 10, 11, + 12, 13, 14, 15 + ]; + let expected = [ + 05, 06, 07, 03, + 09, 10, 11, 07, + 13, 14, 15, 11, + 12, 13, 14, 15 + ]; + let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); + assert!(image.copy_within(Rect { x: 1, y: 1, width: 3, height: 3 }, 0, 0)); + assert_eq!(&image.into_raw(), &expected); + } } diff --git a/src/gif.rs b/src/gif.rs index 5b4f21704b..d191d41977 100644 --- a/src/gif.rs +++ b/src/gif.rs @@ -200,7 +200,7 @@ impl Iterator for GifFrameIterator { } let mut vec = vec![0; self.reader.buffer_size()]; - if let Err(err) = self.reader.fill_buffer(&mut vec) { + if let Err(err) = self.reader.read_into_buffer(&mut vec) { return Some(Err(ImageError::from_gif(err))); } diff --git a/src/image.rs b/src/image.rs index aba42974a7..f761902b2a 100644 --- a/src/image.rs +++ b/src/image.rs @@ -2,12 +2,13 @@ use std::convert::TryFrom; use std::io; use std::io::Read; -use std::path::Path; use std::ops::{Deref, DerefMut}; +use std::path::Path; use crate::buffer::{ImageBuffer, Pixel}; use crate::color::{ColorType, ExtendedColorType}; use crate::error::{ImageError, ImageResult}; +use crate::math::Rect; use crate::animation::Frames; @@ -640,6 +641,48 @@ pub trait GenericImage: GenericImageView { Ok(()) } + /// Copies all of the pixels from one part of this image to another part of this image. + /// + /// The destination rectangle of the copy is specified with the top-left corner placed at (x, y). + /// + /// # Returns + /// `true` if the copy was successful, `false` if the image could not + /// be copied due to size constraints. + fn copy_within(&mut self, source: Rect, x: u32, y: u32) -> bool { + let Rect { x: sx, y: sy, width, height } = source; + let dx = x; + let dy = y; + assert!(sx < self.width() && dx < self.width()); + assert!(sy < self.height() && dy < self.height()); + if self.width() - dx.max(sx) < width || self.height() - dy.max(sy) < height { + return false; + } + // since `.rev()` creates a new dype we would either have to go with dynamic dispatch for the ranges + // or have quite a lot of code bloat. A macro gives us static dispatch with less visible bloat. + macro_rules! copy_within_impl_ { + ($xiter:expr, $yiter:expr) => { + for y in $yiter { + let sy = sy + y; + let dy = dy + y; + for x in $xiter { + let sx = sx + x; + let dx = dx + x; + let pixel = self.get_pixel(sx, sy); + self.put_pixel(dx, dy, pixel); + } + } + }; + } + // check how target and source rectangles relate to each other so we dont overwrite data before we copied it. + match (sx < dx, sy < dy) { + (true, true) => copy_within_impl_!((0..width).rev(), (0..height).rev()), + (true, false) => copy_within_impl_!((0..width).rev(), 0..height), + (false, true) => copy_within_impl_!(0..width, (0..height).rev()), + (false, false) => copy_within_impl_!(0..width, 0..height), + } + true + } + /// Returns a mutable reference to the underlying image. fn inner_mut(&mut self) -> &mut Self::InnerImage; @@ -794,8 +837,9 @@ mod tests { use std::path::Path; use super::{ColorType, ImageDecoder, ImageResult, GenericImage, GenericImageView, load_rect, ImageFormat}; - use crate::buffer::ImageBuffer; + use crate::buffer::{GrayImage, ImageBuffer}; use crate::color::Rgba; + use crate::math::Rect; #[test] /// Test that alpha blending works as expected @@ -887,7 +931,9 @@ mod tests { } fn read_scanline(m: &mut MockDecoder, buf: &mut [u8]) -> io::Result { let bytes_read = m.scanline_number * m.scanline_bytes; - if bytes_read >= 25 { return Ok(0); } + if bytes_read >= 25 { + return Ok(0); + } let len = m.scanline_bytes.min(25 - bytes_read); buf[..(len as usize)].copy_from_slice(&DATA[(bytes_read as usize)..][..(len as usize)]); @@ -951,4 +997,92 @@ mod tests { assert!(from_path("./a.txt").is_err()); assert!(from_path("./a").is_err()); } + + #[test] + fn test_generic_image_copy_within_oob() { + let mut image: GrayImage = ImageBuffer::from_raw(4, 4, vec![0u8; 16]).unwrap(); + assert!(!image.sub_image(0, 0, 4, 4).copy_within(Rect { x: 0, y: 0, width: 5, height: 4 }, 0, 0)); + assert!(!image.sub_image(0, 0, 4, 4).copy_within(Rect { x: 0, y: 0, width: 4, height: 5 }, 0, 0)); + assert!(!image.sub_image(0, 0, 4, 4).copy_within(Rect { x: 1, y: 0, width: 4, height: 4 }, 0, 0)); + assert!(!image.sub_image(0, 0, 4, 4).copy_within(Rect { x: 0, y: 0, width: 4, height: 4 }, 1, 0)); + assert!(!image.sub_image(0, 0, 4, 4).copy_within(Rect { x: 0, y: 1, width: 4, height: 4 }, 0, 0)); + assert!(!image.sub_image(0, 0, 4, 4).copy_within(Rect { x: 0, y: 0, width: 4, height: 4 }, 0, 1)); + assert!(!image.sub_image(0, 0, 4, 4).copy_within(Rect { x: 1, y: 1, width: 4, height: 4 }, 0, 0)); + } + + #[test] + fn test_generic_image_copy_within_tl() { + let data = &[ + 00, 01, 02, 03, + 04, 05, 06, 07, + 08, 09, 10, 11, + 12, 13, 14, 15 + ]; + let expected = [ + 00, 01, 02, 03, + 04, 00, 01, 02, + 08, 04, 05, 06, + 12, 08, 09, 10, + ]; + let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); + assert!(image.sub_image(0, 0, 4, 4).copy_within(Rect { x: 0, y: 0, width: 3, height: 3 }, 1, 1)); + assert_eq!(&image.into_raw(), &expected); + } + + #[test] + fn test_generic_image_copy_within_tr() { + let data = &[ + 00, 01, 02, 03, + 04, 05, 06, 07, + 08, 09, 10, 11, + 12, 13, 14, 15 + ]; + let expected = [ + 00, 01, 02, 03, + 01, 02, 03, 07, + 05, 06, 07, 11, + 09, 10, 11, 15 + ]; + let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); + assert!(image.sub_image(0, 0, 4, 4).copy_within(Rect { x: 1, y: 0, width: 3, height: 3 }, 0, 1)); + assert_eq!(&image.into_raw(), &expected); + } + + #[test] + fn test_generic_image_copy_within_bl() { + let data = &[ + 00, 01, 02, 03, + 04, 05, 06, 07, + 08, 09, 10, 11, + 12, 13, 14, 15 + ]; + let expected = [ + 00, 04, 05, 06, + 04, 08, 09, 10, + 08, 12, 13, 14, + 12, 13, 14, 15 + ]; + let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); + assert!(image.sub_image(0, 0, 4, 4).copy_within(Rect { x: 0, y: 1, width: 3, height: 3 }, 1, 0)); + assert_eq!(&image.into_raw(), &expected); + } + + #[test] + fn test_generic_image_copy_within_br() { + let data = &[ + 00, 01, 02, 03, + 04, 05, 06, 07, + 08, 09, 10, 11, + 12, 13, 14, 15 + ]; + let expected = [ + 05, 06, 07, 03, + 09, 10, 11, 07, + 13, 14, 15, 11, + 12, 13, 14, 15 + ]; + let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); + assert!(image.sub_image(0, 0, 4, 4).copy_within(Rect { x: 1, y: 1, width: 3, height: 3 }, 0, 0)); + assert_eq!(&image.into_raw(), &expected); + } } diff --git a/src/math/mod.rs b/src/math/mod.rs index b250aca4bf..4b862b5a48 100644 --- a/src/math/mod.rs +++ b/src/math/mod.rs @@ -1,3 +1,6 @@ //! Mathematical helper functions and types. pub mod nq; pub mod utils; + +mod rect; +pub use self::rect::Rect; diff --git a/src/math/rect.rs b/src/math/rect.rs new file mode 100644 index 0000000000..74696be238 --- /dev/null +++ b/src/math/rect.rs @@ -0,0 +1,12 @@ +/// A Rectangle defined by its top left corner, width and height. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct Rect { + /// The x coordinate of the top left corner. + pub x: u32, + /// The y coordinate of the top left corner. + pub y: u32, + /// The rectangle's width. + pub width: u32, + /// The rectangle's height. + pub height: u32, +} diff --git a/tests/images/gif/anim/interlaced.gif b/tests/images/gif/anim/interlaced.gif new file mode 100644 index 0000000000..09ff312913 Binary files /dev/null and b/tests/images/gif/anim/interlaced.gif differ diff --git a/tests/reference/gif/anim/interlaced.gif.anim_01_ad3a4823.png b/tests/reference/gif/anim/interlaced.gif.anim_01_ad3a4823.png new file mode 100644 index 0000000000..f432537bef Binary files /dev/null and b/tests/reference/gif/anim/interlaced.gif.anim_01_ad3a4823.png differ