From 99409537ebb91bcec417e9c47c47ce45692241db Mon Sep 17 00:00:00 2001 From: Veykril Date: Fri, 3 Jan 2020 21:58:03 +0100 Subject: [PATCH 1/9] implement copy_within on GenericImage, specialized on ImageBuffer --- src/buffer.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++- src/image.rs | 131 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 249 insertions(+), 2 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 5f358a9185..37e663cf34 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -921,6 +921,35 @@ where self.get_pixel_mut(x, y).blend(&p) } + fn copy_within(&mut self, from: (u32, u32), to: (u32, u32), width: u32, height: u32) -> bool { + let (fx, fy) = from; + let (tx, ty) = to; + if tx.max(fx) + width > self.width() || ty.max(fy) + height > self.height() { + return false; + } + + let px_size =

::CHANNEL_COUNT as usize * std::mem::size_of::<

::Subpixel>(); + let width = px_size * width as usize; + if from.1 < to.1 { + for y in (0..height).rev() { + let sy = fy + y; + let dy = ty + y; + let src = px_size * (sy * self.width + fx) as usize; + let dst = px_size * (dy * self.width + tx) as usize; + (&mut **self).copy_within(src..src + width, dst); + } + } else { + for y in 0..height { + let sy = fy + y; + let dy = ty + y; + let src = px_size * (sy * self.width + fx) as usize; + let dst = px_size * (dy * self.width + tx) as usize; + (&mut **self).copy_within(src..src + width, dst); + } + } + true + } + fn inner_mut(&mut self) -> &mut Self::InnerImage { self } @@ -1081,7 +1110,8 @@ pub(crate) type BgraImage = ImageBuffer, Vec>; #[cfg(test)] mod test { - use super::{ImageBuffer, RgbImage}; + use super::{ImageBuffer, RgbImage, GrayImage}; + use super::GenericImage; use color; #[cfg(feature = "benchmarks")] use test; @@ -1194,4 +1224,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((0, 0), (0, 0), 5, 4)); + assert!(!image.copy_within((0, 0), (0, 0), 4, 5)); + assert!(!image.copy_within((1, 0), (0, 0), 4, 4)); + assert!(!image.copy_within((0, 0), (1, 0), 4, 4)); + assert!(!image.copy_within((0, 1), (0, 0), 4, 4)); + assert!(!image.copy_within((0, 0), (0, 1), 4, 4)); + assert!(!image.copy_within((1, 1), (0, 0), 4, 4)); + } + + #[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((0, 0), (1, 1), 3, 3)); + 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((1, 0), (0, 1), 3, 3)); + 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((0, 1), (1, 0), 3, 3)); + 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((1, 1), (0, 0), 3, 3)); + assert_eq!(&image.into_raw(), &expected); + } } diff --git a/src/image.rs b/src/image.rs index 0998b5b82b..e4ac19acb6 100644 --- a/src/image.rs +++ b/src/image.rs @@ -632,6 +632,47 @@ pub trait GenericImage: GenericImageView { true } + /// Copies all of the pixels from one part of this image to another part of this image. + /// + /// The from parameter defines the top left corner of the source rectangle, while the + /// to parameter defines the top left corner of the destination rectangle. These together + /// with width and height construct the rectangles of the copy operation. + /// + /// # Returns + /// `true` if the copy was successful, `false` if the image could not + /// be copied due to size constraints. + fn copy_within(&mut self, from: (u32, u32), to: (u32, u32), width: u32, height: u32) -> bool { + let (fx, fy) = from; + let (tx, ty) = to; + if tx.max(fx) + width > self.width() || ty.max(fy) + height > self.height() { + return false; + } + // since `.rev()` creates a new type 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 = fy + y; + let dy = ty + y; + for x in $xiter { + let sx = fx + x; + let dx = tx + 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 (from.0 < to.0, from.1 < to.1) { + (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; @@ -786,7 +827,7 @@ mod tests { use std::path::Path; use super::{ColorType, ImageDecoder, ImageResult, GenericImage, GenericImageView, load_rect, ImageFormat}; - use buffer::ImageBuffer; + use buffer::{ImageBuffer, GrayImage}; use color::Rgba; #[test] @@ -942,4 +983,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((0, 0), (0, 0), 5, 4)); + assert!(!image.sub_image(0, 0, 4, 4).copy_within((0, 0), (0, 0), 4, 5)); + assert!(!image.sub_image(0, 0, 4, 4).copy_within((1, 0), (0, 0), 4, 4)); + assert!(!image.sub_image(0, 0, 4, 4).copy_within((0, 0), (1, 0), 4, 4)); + assert!(!image.sub_image(0, 0, 4, 4).copy_within((0, 1), (0, 0), 4, 4)); + assert!(!image.sub_image(0, 0, 4, 4).copy_within((0, 0), (0, 1), 4, 4)); + assert!(!image.sub_image(0, 0, 4, 4).copy_within((1, 1), (0, 0), 4, 4)); + } + + #[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((0, 0), (1, 1), 3, 3)); + 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((1, 0), (0, 1), 3, 3)); + 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((0, 1), (1, 0), 3, 3)); + 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((1, 1), (0, 0), 3, 3)); + assert_eq!(&image.into_raw(), &expected); + } } From 0119e592e355ca0bae53d8fe9b0e446aa7b5264d Mon Sep 17 00:00:00 2001 From: Veykril Date: Sat, 4 Jan 2020 18:50:58 +0100 Subject: [PATCH 2/9] fix possible arithmetic overflow in copy_within --- src/buffer.rs | 20 +++++++++++--------- src/image.rs | 4 +++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 37e663cf34..07b059b035 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -924,27 +924,29 @@ where fn copy_within(&mut self, from: (u32, u32), to: (u32, u32), width: u32, height: u32) -> bool { let (fx, fy) = from; let (tx, ty) = to; - if tx.max(fx) + width > self.width() || ty.max(fy) + height > self.height() { + assert!(fx < self.width() && tx < self.width()); + assert!(fy < self.height() && ty < self.height()); + if self.width() - tx.max(fx) < width || self.height() - ty.max(fy) < height { return false; } - let px_size =

::CHANNEL_COUNT as usize * std::mem::size_of::<

::Subpixel>(); - let width = px_size * width as usize; if from.1 < to.1 { for y in (0..height).rev() { let sy = fy + y; let dy = ty + y; - let src = px_size * (sy * self.width + fx) as usize; - let dst = px_size * (dy * self.width + tx) as usize; - (&mut **self).copy_within(src..src + width, dst); + let Range { start, .. } = self.pixel_indices_unchecked(fx, sy); + let Range { end, .. } = self.pixel_indices_unchecked(fx + width - 1, sy); + let dst = self.pixel_indices_unchecked(tx, dy).start; + (&mut **self).copy_within(start..end, dst); } } else { for y in 0..height { let sy = fy + y; let dy = ty + y; - let src = px_size * (sy * self.width + fx) as usize; - let dst = px_size * (dy * self.width + tx) as usize; - (&mut **self).copy_within(src..src + width, dst); + let Range { start, .. } = self.pixel_indices_unchecked(fx, sy); + let Range { end, .. } = self.pixel_indices_unchecked(fx + width - 1, sy); + let dst = self.pixel_indices_unchecked(tx, dy).start; + (&mut **self).copy_within(start..end, dst); } } true diff --git a/src/image.rs b/src/image.rs index e4ac19acb6..2f4d78a210 100644 --- a/src/image.rs +++ b/src/image.rs @@ -644,7 +644,9 @@ pub trait GenericImage: GenericImageView { fn copy_within(&mut self, from: (u32, u32), to: (u32, u32), width: u32, height: u32) -> bool { let (fx, fy) = from; let (tx, ty) = to; - if tx.max(fx) + width > self.width() || ty.max(fy) + height > self.height() { + assert!(fx < self.width() && tx < self.width()); + assert!(fy < self.height() && ty < self.height()); + if self.width() - tx.max(fx) < width || self.height() - ty.max(fy) < height { return false; } // since `.rev()` creates a new type we would either have to go with dynamic dispatch for the ranges From 1361472bab547803fbd4ac58865ed9919e8495ba Mon Sep 17 00:00:00 2001 From: Veykril Date: Sat, 4 Jan 2020 19:05:30 +0100 Subject: [PATCH 3/9] use custom slice::copy_within implementation in ImageBuffer::copy_within --- src/buffer.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 07b059b035..19480f003d 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -937,7 +937,7 @@ where let Range { start, .. } = self.pixel_indices_unchecked(fx, sy); let Range { end, .. } = self.pixel_indices_unchecked(fx + width - 1, sy); let dst = self.pixel_indices_unchecked(tx, dy).start; - (&mut **self).copy_within(start..end, dst); + slice_copy_within(self, start..end, dst); } } else { for y in 0..height { @@ -946,7 +946,7 @@ where let Range { start, .. } = self.pixel_indices_unchecked(fx, sy); let Range { end, .. } = self.pixel_indices_unchecked(fx + width - 1, sy); let dst = self.pixel_indices_unchecked(tx, dy).start; - (&mut **self).copy_within(start..end, dst); + slice_copy_within(self, start..end, dst); } } true @@ -957,6 +957,24 @@ where } } +// 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 From 8fec83128f24cb39a71c2ab570b2297918afbc8f Mon Sep 17 00:00:00 2001 From: Veykril Date: Thu, 23 Jan 2020 20:21:16 +0100 Subject: [PATCH 4/9] change copy_within to use Rect struct --- src/buffer.rs | 61 ++++++++++++++++++++++++---------------------- src/image.rs | 63 +++++++++++++++++++++++++----------------------- src/math/mod.rs | 3 +++ src/math/rect.rs | 12 +++++++++ 4 files changed, 80 insertions(+), 59 deletions(-) create mode 100644 src/math/rect.rs diff --git a/src/buffer.rs b/src/buffer.rs index 19480f003d..6b436ec61b 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -9,6 +9,7 @@ use color::{ColorType, FromColor, Luma, LumaA, Rgb, Rgba, Bgr, Bgra}; use flat::{FlatSamples, SampleLayout}; use dynimage::{save_buffer, save_buffer_with_format}; use image::{GenericImage, GenericImageView, ImageFormat}; +use math::Rect; use traits::Primitive; use utils::expand_packed; @@ -921,31 +922,32 @@ where self.get_pixel_mut(x, y).blend(&p) } - fn copy_within(&mut self, from: (u32, u32), to: (u32, u32), width: u32, height: u32) -> bool { - let (fx, fy) = from; - let (tx, ty) = to; - assert!(fx < self.width() && tx < self.width()); - assert!(fy < self.height() && ty < self.height()); - if self.width() - tx.max(fx) < width || self.height() - ty.max(fy) < height { + 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 from.1 < to.1 { + if sy < dy { for y in (0..height).rev() { - let sy = fy + y; - let dy = ty + y; - let Range { start, .. } = self.pixel_indices_unchecked(fx, sy); - let Range { end, .. } = self.pixel_indices_unchecked(fx + width - 1, sy); - let dst = self.pixel_indices_unchecked(tx, dy).start; + 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 = fy + y; - let dy = ty + y; - let Range { start, .. } = self.pixel_indices_unchecked(fx, sy); - let Range { end, .. } = self.pixel_indices_unchecked(fx + width - 1, sy); - let dst = self.pixel_indices_unchecked(tx, dy).start; + 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); } } @@ -961,7 +963,7 @@ where // 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) { +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; @@ -1133,6 +1135,7 @@ mod test { use super::{ImageBuffer, RgbImage, GrayImage}; use super::GenericImage; use color; + use math::Rect; #[cfg(feature = "benchmarks")] use test; @@ -1248,13 +1251,13 @@ mod test { #[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((0, 0), (0, 0), 5, 4)); - assert!(!image.copy_within((0, 0), (0, 0), 4, 5)); - assert!(!image.copy_within((1, 0), (0, 0), 4, 4)); - assert!(!image.copy_within((0, 0), (1, 0), 4, 4)); - assert!(!image.copy_within((0, 1), (0, 0), 4, 4)); - assert!(!image.copy_within((0, 0), (0, 1), 4, 4)); - assert!(!image.copy_within((1, 1), (0, 0), 4, 4)); + 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] @@ -1272,7 +1275,7 @@ mod test { 12, 08, 09, 10, ]; let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); - assert!(image.copy_within((0, 0), (1, 1), 3, 3)); + assert!(image.copy_within(Rect { x: 0, y: 0, width: 3, height: 3 }, 1, 1)); assert_eq!(&image.into_raw(), &expected); } @@ -1291,7 +1294,7 @@ mod test { 09, 10, 11, 15 ]; let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); - assert!(image.copy_within((1, 0), (0, 1), 3, 3)); + assert!(image.copy_within(Rect { x: 1, y: 0, width: 3, height: 3 }, 0, 1)); assert_eq!(&image.into_raw(), &expected); } @@ -1310,7 +1313,7 @@ mod test { 12, 13, 14, 15 ]; let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); - assert!(image.copy_within((0, 1), (1, 0), 3, 3)); + assert!(image.copy_within(Rect { x: 0, y: 1, width: 3, height: 3 }, 1, 0)); assert_eq!(&image.into_raw(), &expected); } @@ -1329,7 +1332,7 @@ mod test { 12, 13, 14, 15 ]; let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); - assert!(image.copy_within((1, 1), (0, 0), 3, 3)); + 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/image.rs b/src/image.rs index 2f4d78a210..c2bee8502b 100644 --- a/src/image.rs +++ b/src/image.rs @@ -4,12 +4,13 @@ use std::error::Error; use std::fmt; use std::io; use std::io::Read; -use std::path::Path; use std::ops::{Deref, DerefMut}; +use std::path::Path; use buffer::{ImageBuffer, Pixel}; use color; use color::ColorType; +use math::Rect; use animation::Frames; @@ -634,39 +635,38 @@ pub trait GenericImage: GenericImageView { /// Copies all of the pixels from one part of this image to another part of this image. /// - /// The from parameter defines the top left corner of the source rectangle, while the - /// to parameter defines the top left corner of the destination rectangle. These together - /// with width and height construct the rectangles of the copy operation. + /// 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, from: (u32, u32), to: (u32, u32), width: u32, height: u32) -> bool { - let (fx, fy) = from; - let (tx, ty) = to; - assert!(fx < self.width() && tx < self.width()); - assert!(fy < self.height() && ty < self.height()); - if self.width() - tx.max(fx) < width || self.height() - ty.max(fy) < height { + 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 type we would either have to go with dynamic dispatch for the ranges + // 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 = fy + y; - let dy = ty + y; + let sy = sy + y; + let dy = dy + y; for x in $xiter { - let sx = fx + x; - let dx = tx + x; + 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 (from.0 < to.0, from.1 < to.1) { + 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()), @@ -831,6 +831,7 @@ mod tests { use super::{ColorType, ImageDecoder, ImageResult, GenericImage, GenericImageView, load_rect, ImageFormat}; use buffer::{ImageBuffer, GrayImage}; use color::Rgba; + use math::Rect; #[test] /// Test that alpha blending works as expected @@ -922,7 +923,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)]); @@ -989,13 +992,13 @@ mod tests { #[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((0, 0), (0, 0), 5, 4)); - assert!(!image.sub_image(0, 0, 4, 4).copy_within((0, 0), (0, 0), 4, 5)); - assert!(!image.sub_image(0, 0, 4, 4).copy_within((1, 0), (0, 0), 4, 4)); - assert!(!image.sub_image(0, 0, 4, 4).copy_within((0, 0), (1, 0), 4, 4)); - assert!(!image.sub_image(0, 0, 4, 4).copy_within((0, 1), (0, 0), 4, 4)); - assert!(!image.sub_image(0, 0, 4, 4).copy_within((0, 0), (0, 1), 4, 4)); - assert!(!image.sub_image(0, 0, 4, 4).copy_within((1, 1), (0, 0), 4, 4)); + 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] @@ -1013,7 +1016,7 @@ mod tests { 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((0, 0), (1, 1), 3, 3)); + 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); } @@ -1032,7 +1035,7 @@ mod tests { 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((1, 0), (0, 1), 3, 3)); + 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); } @@ -1051,7 +1054,7 @@ mod tests { 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((0, 1), (1, 0), 3, 3)); + 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); } @@ -1070,7 +1073,7 @@ mod tests { 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((1, 1), (0, 0), 3, 3)); + 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, +} From 87baf4e6c4766fe079eeed0cf8245b0f306b4a84 Mon Sep 17 00:00:00 2001 From: Kenneth Uildriks Date: Fri, 24 Jan 2020 22:43:37 -0600 Subject: [PATCH 5/9] Use read_into_buffer, which deinterlaces as needed --- src/gif.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gif.rs b/src/gif.rs index e85608270c..48fe5a1457 100644 --- a/src/gif.rs +++ b/src/gif.rs @@ -181,7 +181,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(err.into())); } From 46ef6fa9d871b0009bf500a4382383b1438c46bd Mon Sep 17 00:00:00 2001 From: Kenneth Uildriks Date: Fri, 31 Jan 2020 19:30:14 -0600 Subject: [PATCH 6/9] Add interlaced GIF test case --- tests/images/gif/anim/interlaced.gif | Bin 0 -> 1526 bytes .../gif/anim/interlaced.gif.anim_01_ad3a4823.png | Bin 0 -> 794 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/images/gif/anim/interlaced.gif create mode 100644 tests/reference/gif/anim/interlaced.gif.anim_01_ad3a4823.png diff --git a/tests/images/gif/anim/interlaced.gif b/tests/images/gif/anim/interlaced.gif new file mode 100644 index 0000000000000000000000000000000000000000..09ff3129133e9752a2ce21f797e3d48cd35f0712 GIT binary patch literal 1526 zcmWkt3pCVu6#k1u^ze#mY6nFlPOY7g)vl~($)h*DJPMUsAzB+XMIl9c5M>F;qY;BV zriN%dqlZlog@(|y2t}Fy@7w8~bMHC#p6~n4{q8x}&cW7fwI`EeQvDK&nnuaW%gE21 zuBfb_JV$Ac%4}726*Ubt4NY~;`I-wD^BId7ixt*qBt+ggJnb`G}o>+IJ#I&5%u zbar)e-Q)rlvT4i4E!#Fj1KZrTLJMv?wqqf7>~i0=XQzkP9kq|T)qG^~*VDou=q?HhfbT8t9 z5wuAepQY&zcmoH=C&oIY9RlfbDL0G;M}p|CACp)h0bgkmB+?@SX%gfUWKVvdkbp-} z@KFi}52&Cal%4~m^}FQTC;^RDGKH}p3hlrfkO2t>2;PGXOUM)3Cp@>J6*#p4sL*PVl2oHAb6 z6sJ|R+dk-(yTSUa`8m0c*Phvy=9&0!=Xu=`mM-6Rz@zrm4clA(ZY%wsNAx@_GfFZH z=-FLioXvf^C1>>G+vd>AU4oN#Q41I|FJG*+iCi{sG_xqa-aNRsfslSf!01k)E0y z6WP@X1Ry6TwbM_Ab#v+$DZE>2$2h|8@mM&wtVn)=`b}41ly|^gjy%JwTU%aprw3=c z#$|h%>1vKlj+{!#WVP(H~_&PZT!7tpJ zZe&O6#Ofa(&5hAZmWyp%66us!pAr2!!*5YsYtKOD;qn?`mVcJ#V75mm&fsAj2p=H;%<%F8?2SNie5Uk%JU193rdh_`rE`G=H)Z2su$mcqwO-A{LGXX`45 zH@w@p-l&Kz{=|FC)%mZaeVtNM=}RA_CcVxO@tufowEj?eZ|uRDtbvf5hxM=R4Q#AR zvQ}->9XIhR)QQ!)S@__T!n6i0@5r7<)jQM+qULS)@Ql*f%yzU4x?v`6KpxWt>cQsS7Q;3HGR!KEs^F$Ze#rv6kkXC+Q4LMTY`^WlBn8YU=`TTqvAm zR?@~y1wqWG>DFeP6^dROXl8Zld-jGUfRhw}k~hc9?QJBmAmbS8utbjFc@aQ{DyhuW z4M(ohzeENeR|DwbztTlK)kL$*9ON#fA3~n*)vSubx%B_TPiea$M{v6ufQ8&Q>2IFC z4m5sWremnh4^;?_^pU2MF2NzoxHijJ=Ijuk#R{|35Kv<Mr-6f% zA{xHxTO>u&N35~Zkiwhk>O@7s4XKI|zm6FgVH8vtG+Kp9)-g~{Sy5_%_V(q5h>s=$ zSdW(gGSyTv)ia^Tq64ZK=4UB@RU9mA?wo2h$Esc0i-^shrAal4_7 z{Nz=jU(!_|zEuQp9lkF&B@a2ChDe}sJ*Z}Wt0kvbEzz0vL~1*?=jbzMQ|VY{nUXOk zdUgqDB>{DA-_hGryWmpVF6=4?F2uP)$LuEqXyxYySYj9~{e00Y$-tk%yR?ko3v|Vm z^qCdOGjPbo>I^e)(|{k+c7^UP2;eF{F35&LQtp;(-5}dK5Yp#ZN2T-mTkHMsOB8&R zwK7<N{AZ+hI9RLUdT`NbWlmj|PMr<~8osM3< z=mP+y5a>E{zOt|Ax~}O6rUHR&#rmM9hfn$dRM?JaKu4hSas=5iOVP3GaULYg!0yw- zM-LbRx(U>fqUVTX0@F_8S~&vEmMuA5I&M0+bI?D8HUKael$`?vX4i17_~cyiT@C{L Y1E8facLKDgC;$Ke07*qoM6N<$f?twt=Kufz literal 0 HcmV?d00001 From 601cf619e5930a56a2daa90226d8dfae630482f1 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Tue, 4 Feb 2020 19:07:34 +0100 Subject: [PATCH 7/9] Fix a deprecation warning See https://github.com/rust-lang/rust/issues/66145 --- examples/scaledown/main.rs | 2 +- examples/scaleup/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/scaledown/main.rs b/examples/scaledown/main.rs index eed4dad70f..a05a31e908 100644 --- a/examples/scaledown/main.rs +++ b/examples/scaledown/main.rs @@ -33,7 +33,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 f74272652f..98cada5508 100644 --- a/examples/scaleup/main.rs +++ b/examples/scaleup/main.rs @@ -33,7 +33,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); From 263f826990b489aa820767a726efc57896a7a7ae Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Sat, 11 Jan 2020 01:50:37 +0100 Subject: [PATCH 8/9] Update the quickcheck dev-dependency It has become quite outdated at this point but luckily is maintained with a commitment to MSRV of 1.30. The main point of the update are some reported issues with rand and the dependency rdrand. Not that any of those are actually relevant but this reduces the burden of proof for verifications (crev, etc.) of code bases using the image crate. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2be64d7bca..978f3574cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ optional = true crc32fast = "1.2.0" num-complex = "0.2.0" glob = "0.3" -quickcheck = "0.6.2" +quickcheck = "0.9" [features] default = ["gif_codec", "jpeg", "ico", "png_codec", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "jpeg_rayon"] From 85f163e8821518a0e50e775b5e6d34e0584ab17c Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Wed, 5 Feb 2020 05:35:06 +0100 Subject: [PATCH 9/9] Update meta data for 0.22.5 --- CHANGES.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 0381aaeb17..00fee8f461 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,12 @@ Rust image aims to be a pure-Rust implementation of various popular image format ## Changes +### 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/Cargo.toml b/Cargo.toml index 978f3574cb..8e417e85fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "image" -version = "0.22.4" +version = "0.22.5" license = "MIT" description = "Imaging library written in Rust. Provides basic filters and decoders for the most common image formats." authors = [