Fixed implementation of TypedCroppedImage, TypedCroppedImageMut, CroppedImage and CroppedImageMut.

This commit is contained in:
Kirill Kuzminykh
2024-05-13 21:08:42 +03:00
parent ee2ec47e3c
commit f87f80e925
9 changed files with 419 additions and 102 deletions
+9 -3
View File
@@ -4,6 +4,14 @@
- Added Gaussian filter for convolution algorithm.
- Method `PixelType::size()` was made public.
- Added new image containers:
- `ImageRef`
- `TypedImageRef`
- `TypedImage`
- `TypedCroppedImage`
- `TypedCroppedImageMut`
- `CroppedImage`
- `CroppedImageMut`
### Fixed
@@ -11,7 +19,7 @@
### Changed
A lot of breaking changes have been done in this release:
A lot of **breaking changes** have been done in this release:
- Structures `ImageView` and `ImageViewMut` have been removed. They always
did unnecessary memory allocation to store references to image rows.
@@ -40,8 +48,6 @@ A lot of breaking changes have been done in this release:
Now you can create and use zero-sized images.
- `Image` (embedded implementation of image container) moved from root of
the crate into module `images`.
- Added new image containers: `ImageRef`, `TypedImageRef`, `TypedImage`,
`TypedCroppedImage` and `TypedCroppedImageMut`.
- Added optional feature "image".
It adds implementation of traits `IntoImageView` and `IntoImageViewMut` for the
[DynamicImage](https://docs.rs/image/latest/image/enum.DynamicImage.html)
+1
View File
@@ -141,6 +141,7 @@ codegen-units = 1
[profile.test]
opt-level = 3
incremental = true
[package.metadata.release]
+68 -81
View File
@@ -1,73 +1,37 @@
use crate::{CropBoxError, ImageView, ImageViewMut};
use crate::images::{check_crop_box, TypedCroppedImage, TypedCroppedImageMut};
use crate::{
CropBoxError, ImageView, ImageViewMut, IntoImageView, IntoImageViewMut, PixelTrait, PixelType,
};
fn check_crop_box(
image_view: &impl ImageView,
left: u32,
top: u32,
width: u32,
height: u32,
) -> Result<(), CropBoxError> {
let img_width = image_view.width();
let img_height = image_view.height();
if left >= img_width || top >= img_height {
return Err(CropBoxError::PositionIsOutOfImageBoundaries);
}
let right = left + width;
let bottom = top + height;
if right > img_width || bottom > img_height {
return Err(CropBoxError::SizeIsOutOfImageBoundaries);
}
Ok(())
}
macro_rules! image_view_impl {
($wrapper_name:ident<$view_trait:ident>) => {
unsafe impl<'a, V: $view_trait> ImageView for $wrapper_name<'a, V> {
type Pixel = V::Pixel;
fn width(&self) -> u32 {
self.width
}
fn height(&self) -> u32 {
self.height
}
fn iter_rows(&self, start_row: u32) -> impl Iterator<Item = &[Self::Pixel]> {
let left = self.left as usize;
let right = left + self.width as usize;
self.image_view
.iter_rows(self.top + start_row)
.take((self.height - start_row) as usize)
// SAFETY: correct values of the left and the right
// are guaranteed by new() method.
.map(move |row| unsafe { row.get_unchecked(left..right) })
}
}
};
}
/// It is a typed wrapper that provides [ImageView] for part of wrapped image.
pub struct TypedCroppedImage<'a, V: ImageView> {
image_view: &'a V,
/// It is a wrapper that provides [IntoImageView] for part of wrapped image.
pub struct CroppedImage<'a, V: IntoImageView> {
image: &'a V,
left: u32,
top: u32,
width: u32,
height: u32,
}
impl<'a, V: ImageView> TypedCroppedImage<'a, V> {
/// It is a wrapper that provides [IntoImageView] and [IntoImageViewMut] for part of wrapped image.
pub struct CroppedImageMut<'a, V: IntoImageView> {
image: &'a mut V,
left: u32,
top: u32,
width: u32,
height: u32,
}
impl<'a, V: IntoImageView> CroppedImage<'a, V> {
pub fn new(
image_view: &'a V,
image: &'a V,
left: u32,
top: u32,
width: u32,
height: u32,
) -> Result<Self, CropBoxError> {
check_crop_box(image_view, left, top, width, height)?;
check_crop_box(image.width(), image.height(), left, top, width, height)?;
Ok(Self {
image_view,
image,
left,
top,
width,
@@ -76,27 +40,17 @@ impl<'a, V: ImageView> TypedCroppedImage<'a, V> {
}
}
image_view_impl!(TypedCroppedImage<ImageView>);
/// It is a typed wrapper that provides [ImageView] and [ImageViewMut] for part of wrapped image.
pub struct TypedCroppedImageMut<'a, V: ImageViewMut> {
image_view: &'a mut V,
left: u32,
top: u32,
width: u32,
height: u32,
}
impl<'a, V: ImageViewMut> TypedCroppedImageMut<'a, V> {
impl<'a, V: IntoImageView> CroppedImageMut<'a, V> {
pub fn new(
image_view: &'a mut V,
image: &'a mut V,
left: u32,
top: u32,
width: u32,
height: u32,
) -> Result<Self, CropBoxError> {
check_crop_box(image_view, left, top, width, height)?;
check_crop_box(image.width(), image.height(), left, top, width, height)?;
Ok(Self {
image_view,
image,
left,
top,
width,
@@ -105,17 +59,50 @@ impl<'a, V: ImageViewMut> TypedCroppedImageMut<'a, V> {
}
}
image_view_impl!(TypedCroppedImageMut<ImageViewMut>);
impl<'a, V: IntoImageView> IntoImageView for CroppedImage<'a, V> {
fn pixel_type(&self) -> Option<PixelType> {
self.image.pixel_type()
}
unsafe impl<'a, V: ImageViewMut> ImageViewMut for TypedCroppedImageMut<'a, V> {
fn iter_rows_mut(&mut self, start_row: u32) -> impl Iterator<Item = &mut [Self::Pixel]> {
let left = self.left as usize;
let right = left + self.width as usize;
self.image_view
.iter_rows_mut(self.top + start_row)
.take((self.height - start_row) as usize)
// SAFETY: correct values of the left and the right
// are guaranteed by new() method.
.map(move |row| unsafe { row.get_unchecked_mut(left..right) })
fn width(&self) -> u32 {
self.width
}
fn height(&self) -> u32 {
self.height
}
fn image_view<P: PixelTrait>(&self) -> Option<impl ImageView<Pixel = P>> {
self.image.image_view().map(|v| {
TypedCroppedImage::new(v, self.left, self.top, self.width, self.height).unwrap()
})
}
}
impl<'a, V: IntoImageView> IntoImageView for CroppedImageMut<'a, V> {
fn pixel_type(&self) -> Option<PixelType> {
self.image.pixel_type()
}
fn width(&self) -> u32 {
self.width
}
fn height(&self) -> u32 {
self.height
}
fn image_view<P: PixelTrait>(&self) -> Option<impl ImageView<Pixel = P>> {
self.image.image_view().map(|v| {
TypedCroppedImage::new(v, self.left, self.top, self.width, self.height).unwrap()
})
}
}
impl<'a, V: IntoImageViewMut> IntoImageViewMut for CroppedImageMut<'a, V> {
fn image_view_mut<P: PixelTrait>(&mut self) -> Option<impl ImageViewMut<Pixel = P>> {
self.image.image_view_mut().map(|v| {
TypedCroppedImageMut::new(v, self.left, self.top, self.width, self.height).unwrap()
})
}
}
@@ -37,6 +37,18 @@ impl<'a> ImageRef<'a> {
})
}
pub fn from_pixels<P: PixelTrait>(
width: u32,
height: u32,
pixels: &'a [P],
) -> Result<Self, ImageBufferError> {
let (head, buffer, _) = unsafe { pixels.align_to::<u8>() };
if !head.is_empty() {
return Err(ImageBufferError::InvalidBufferAlignment);
}
Self::new(width, height, buffer, P::pixel_type())
}
#[inline]
pub fn pixel_type(&self) -> PixelType {
self.pixel_type
@@ -86,7 +98,7 @@ impl<'a> IntoImageView for ImageRef<'a> {
self.height
}
fn image_view<P: InnerPixel>(&self) -> Option<impl ImageView<Pixel = P>> {
fn image_view<P: PixelTrait>(&self) -> Option<impl ImageView<Pixel = P>> {
self.typed_image()
}
}
@@ -210,7 +222,7 @@ impl<'a> Image<'a> {
}
}
/// Get typed version of the image.
/// Get the typed version of the image.
pub fn typed_image<P: InnerPixel>(&self) -> Option<TypedImageRef<P>> {
if P::pixel_type() != self.pixel_type {
return None;
@@ -220,7 +232,7 @@ impl<'a> Image<'a> {
Some(typed_image)
}
/// Get typed mutable version of the image.
/// Get the typed mutable version of the image.
pub fn typed_image_mut<P: InnerPixel>(&mut self) -> Option<TypedImage<P>> {
if P::pixel_type() != self.pixel_type {
return None;
+3 -4
View File
@@ -5,8 +5,7 @@ use image::DynamicImage;
use crate::image_view::try_pixel_type;
use crate::images::{TypedImage, TypedImageRef};
use crate::pixels::InnerPixel;
use crate::{ImageView, ImageViewMut, IntoImageView, IntoImageViewMut, PixelType};
use crate::{ImageView, ImageViewMut, IntoImageView, IntoImageViewMut, PixelTrait, PixelType};
impl IntoImageView for DynamicImage {
fn pixel_type(&self) -> Option<PixelType> {
@@ -31,7 +30,7 @@ impl IntoImageView for DynamicImage {
self.height()
}
fn image_view<P: InnerPixel>(&self) -> Option<impl ImageView<Pixel = P>> {
fn image_view<P: PixelTrait>(&self) -> Option<impl ImageView<Pixel = P>> {
if let Ok(pixel_type) = try_pixel_type(self) {
if P::pixel_type() == pixel_type {
return TypedImageRef::<P>::from_buffer(
@@ -47,7 +46,7 @@ impl IntoImageView for DynamicImage {
}
impl IntoImageViewMut for DynamicImage {
fn image_view_mut<P: InnerPixel>(&mut self) -> Option<impl ImageViewMut<Pixel = P>> {
fn image_view_mut<P: PixelTrait>(&mut self) -> Option<impl ImageViewMut<Pixel = P>> {
if let Ok(pixel_type) = try_pixel_type(self) {
if P::pixel_type() == pixel_type {
return TypedImage::<P>::from_buffer(
+4 -2
View File
@@ -2,11 +2,13 @@
use std::fmt::Debug;
pub use cropped_image::*;
pub use dyn_image::*;
pub use image::*;
pub use typed_cropped_image::*;
pub use typed_image::*;
mod cropped_image;
mod dyn_image;
mod image;
mod typed_cropped_image;
mod typed_image;
#[cfg(feature = "image")]
+219
View File
@@ -0,0 +1,219 @@
use crate::{CropBoxError, ImageView, ImageViewMut};
pub(crate) fn check_crop_box(
img_width: u32,
img_height: u32,
left: u32,
top: u32,
width: u32,
height: u32,
) -> Result<(), CropBoxError> {
if left >= img_width || top >= img_height {
return Err(CropBoxError::PositionIsOutOfImageBoundaries);
}
let right = left + width;
let bottom = top + height;
if right > img_width || bottom > img_height {
return Err(CropBoxError::SizeIsOutOfImageBoundaries);
}
Ok(())
}
macro_rules! image_view_impl {
($wrapper_name:ident<$view_trait:ident>) => {
unsafe impl<'a, V: $view_trait> ImageView for $wrapper_name<'a, V> {
type Pixel = V::Pixel;
fn width(&self) -> u32 {
self.width
}
fn height(&self) -> u32 {
self.height
}
fn iter_rows(&self, start_row: u32) -> impl Iterator<Item = &[Self::Pixel]> {
let left = self.left as usize;
let right = left + self.width as usize;
self.image_view
.get_ref()
.iter_rows(self.top + start_row)
.take((self.height - start_row) as usize)
// SAFETY: correct values of the left and the right
// are guaranteed by new() method.
.map(move |row| unsafe { row.get_unchecked(left..right) })
}
}
};
}
enum View<'a, V: 'a> {
Borrowed(&'a V),
Owned(V),
}
impl<'a, V> View<'a, V> {
fn get_ref(&self) -> &V {
match self {
Self::Borrowed(v_ref) => v_ref,
Self::Owned(v_own) => v_own,
}
}
}
enum ViewMut<'a, V: 'a> {
Borrowed(&'a mut V),
Owned(V),
}
impl<'a, V> ViewMut<'a, V> {
fn get_ref(&self) -> &V {
match self {
Self::Borrowed(v_ref) => v_ref,
Self::Owned(v_own) => v_own,
}
}
fn get_mut(&mut self) -> &mut V {
match self {
Self::Borrowed(p_ref) => p_ref,
Self::Owned(vec) => vec,
}
}
}
/// It is a typed wrapper that provides [ImageView] for part of wrapped image.
pub struct TypedCroppedImage<'a, V: ImageView + 'a> {
image_view: View<'a, V>,
left: u32,
top: u32,
width: u32,
height: u32,
}
/// It is a typed wrapper that provides [ImageView] and [ImageViewMut] for part of wrapped image.
pub struct TypedCroppedImageMut<'a, V: ImageViewMut> {
image_view: ViewMut<'a, V>,
left: u32,
top: u32,
width: u32,
height: u32,
}
impl<'a, V: ImageView + 'a> TypedCroppedImage<'a, V> {
pub fn new(
image_view: V,
left: u32,
top: u32,
width: u32,
height: u32,
) -> Result<Self, CropBoxError> {
check_crop_box(
image_view.width(),
image_view.height(),
left,
top,
width,
height,
)?;
Ok(Self {
image_view: View::Owned(image_view),
left,
top,
width,
height,
})
}
pub fn from_ref(
image_view: &'a V,
left: u32,
top: u32,
width: u32,
height: u32,
) -> Result<Self, CropBoxError> {
check_crop_box(
image_view.width(),
image_view.height(),
left,
top,
width,
height,
)?;
Ok(Self {
image_view: View::Borrowed(image_view),
left,
top,
width,
height,
})
}
}
impl<'a, V: ImageViewMut> TypedCroppedImageMut<'a, V> {
pub fn new(
image_view: V,
left: u32,
top: u32,
width: u32,
height: u32,
) -> Result<Self, CropBoxError> {
check_crop_box(
image_view.width(),
image_view.height(),
left,
top,
width,
height,
)?;
Ok(Self {
image_view: ViewMut::Owned(image_view),
left,
top,
width,
height,
})
}
pub fn from_ref(
image_view: &'a mut V,
left: u32,
top: u32,
width: u32,
height: u32,
) -> Result<Self, CropBoxError> {
check_crop_box(
image_view.width(),
image_view.height(),
left,
top,
width,
height,
)?;
Ok(Self {
image_view: ViewMut::Borrowed(image_view),
left,
top,
width,
height,
})
}
}
image_view_impl!(TypedCroppedImage<ImageView>);
image_view_impl!(TypedCroppedImageMut<ImageViewMut>);
unsafe impl<'a, V: ImageViewMut> ImageViewMut for TypedCroppedImageMut<'a, V> {
fn iter_rows_mut(&mut self, start_row: u32) -> impl Iterator<Item = &mut [Self::Pixel]> {
let left = self.left as usize;
let right = left + self.width as usize;
self.image_view
.get_mut()
.iter_rows_mut(self.top + start_row)
.take((self.height - start_row) as usize)
// SAFETY: correct values of the left and the right
// are guaranteed by new() method.
.map(move |row| unsafe { row.get_unchecked_mut(left..right) })
}
}
+4
View File
@@ -33,6 +33,10 @@ impl<'a, P> TypedImageRef<'a, P> {
let pixels = align_buffer_to(buffer)?;
Self::new(width, height, pixels).map_err(|_| ImageBufferError::InvalidBufferSize)
}
pub fn pixels(&self) -> &[P] {
self.pixels
}
}
unsafe impl<'a, P: InnerPixel> ImageView for TypedImageRef<'a, P> {
+96 -9
View File
@@ -1,9 +1,10 @@
use fast_image_resize as fr;
use fast_image_resize::images::{
Image, ImageRef, TypedCroppedImage, TypedCroppedImageMut, TypedImage, TypedImageRef,
CroppedImage, CroppedImageMut, Image, ImageRef, TypedCroppedImage, TypedCroppedImageMut,
TypedImage, TypedImageRef,
};
use fast_image_resize::pixels::{U8x4, U8};
use fast_image_resize::{ImageView, ResizeOptions};
use fast_image_resize::{ImageView, IntoImageView, PixelType, ResizeOptions};
#[test]
fn create_image_ref_from_small_buffer() {
@@ -11,7 +12,7 @@ fn create_image_ref_from_small_buffer() {
let height = 32;
let buffer = vec![0; 64 * 30];
let res = ImageRef::new(width, height, &buffer, fr::PixelType::U8);
let res = ImageRef::new(width, height, &buffer, PixelType::U8);
assert_eq!(res.unwrap_err(), fr::ImageBufferError::InvalidBufferSize);
}
@@ -21,10 +22,10 @@ fn create_image_from_small_buffer() {
let height = 32;
let mut buffer = vec![0; 64 * 30];
let res = Image::from_slice_u8(width, height, &mut buffer, fr::PixelType::U8);
let res = Image::from_slice_u8(width, height, &mut buffer, PixelType::U8);
assert_eq!(res.unwrap_err(), fr::ImageBufferError::InvalidBufferSize);
let res = Image::from_vec_u8(width, height, buffer, fr::PixelType::U8);
let res = Image::from_vec_u8(width, height, buffer, PixelType::U8);
assert_eq!(res.unwrap_err(), fr::ImageBufferError::InvalidBufferSize);
}
@@ -34,10 +35,10 @@ fn create_image_from_big_buffer() {
let height = 32;
let mut buffer = vec![0; 65 * 32];
let res = Image::from_slice_u8(width, height, &mut buffer, fr::PixelType::U8);
let res = Image::from_slice_u8(width, height, &mut buffer, PixelType::U8);
assert!(res.is_ok());
let res = Image::from_vec_u8(width, height, buffer, fr::PixelType::U8);
let res = Image::from_vec_u8(width, height, buffer, PixelType::U8);
assert!(res.is_ok());
}
@@ -99,7 +100,7 @@ fn typed_cropped_image() {
// Black destination image
let mut dst_image = TypedImage::<U8x4>::new(40, 40);
let cropped_src_image = TypedCroppedImage::new(&src_image, 10, 10, 44, 44).unwrap();
let cropped_src_image = TypedCroppedImage::from_ref(&src_image, 10, 10, 44, 44).unwrap();
assert_eq!(cropped_src_image.width(), 44);
assert_eq!(cropped_src_image.height(), 44);
@@ -116,6 +117,47 @@ fn typed_cropped_image() {
assert_eq!(dst_image.pixels(), white_block);
}
#[test]
fn cropped_image() {
const BLACK: U8x4 = U8x4::new([0; 4]);
const WHITE: U8x4 = U8x4::new([255; 4]);
let mut source_pixels = Vec::with_capacity(64 * 64);
source_pixels.extend((0..64 * 64).map(|i| {
let y = i / 64;
if (10..54).contains(&y) {
let x = i % 64;
if (10..54).contains(&x) {
return WHITE;
}
}
BLACK
}));
// Black source image with white square inside
let src_image = ImageRef::from_pixels(64, 64, &source_pixels).unwrap();
// Black destination image
let mut dst_image = Image::new(40, 40, PixelType::U8x4);
let cropped_src_image = CroppedImage::new(&src_image, 10, 10, 44, 44).unwrap();
assert_eq!(cropped_src_image.width(), 44);
assert_eq!(cropped_src_image.height(), 44);
let mut resizer = fr::Resizer::new();
resizer
.resize(
&cropped_src_image,
&mut dst_image,
&ResizeOptions::new().resize_alg(fr::ResizeAlg::Nearest),
)
.unwrap();
let dst_typed_image = dst_image.typed_image().unwrap();
let dst_pixels: &[U8x4] = dst_typed_image.pixels();
let white_block = vec![WHITE; 40 * 40];
assert_eq!(dst_pixels, white_block);
}
#[test]
fn typed_cropped_image_mut() {
const BLACK: U8x4 = U8x4::new([0; 4]);
@@ -126,7 +168,8 @@ fn typed_cropped_image_mut() {
// Black destination image
let mut dst_image = TypedImage::<U8x4>::new(64, 32);
let mut cropped_dst_image = TypedCroppedImageMut::new(&mut dst_image, 10, 10, 44, 12).unwrap();
let mut cropped_dst_image =
TypedCroppedImageMut::from_ref(&mut dst_image, 10, 10, 44, 12).unwrap();
assert_eq!(cropped_dst_image.width(), 44);
assert_eq!(cropped_dst_image.height(), 12);
@@ -157,3 +200,47 @@ fn typed_cropped_image_mut() {
// Bottom border
assert_eq!(dst_pixels[22 * row_size..], black_block);
}
#[test]
fn cropped_image_mut() {
const BLACK: U8x4 = U8x4::new([0; 4]);
const WHITE: U8x4 = U8x4::new([255; 4]);
// White source image
let src_pixels = vec![WHITE; 64 * 32];
let src_image = ImageRef::from_pixels(64, 32, &src_pixels).unwrap();
// Black destination image
let mut dst_image = Image::new(64, 32, PixelType::U8x4);
let mut cropped_dst_image = CroppedImageMut::new(&mut dst_image, 10, 10, 44, 12).unwrap();
assert_eq!(cropped_dst_image.width(), 44);
assert_eq!(cropped_dst_image.height(), 12);
let mut resizer = fr::Resizer::new();
resizer
.resize(
&src_image,
&mut cropped_dst_image,
&ResizeOptions::new().resize_alg(fr::ResizeAlg::Nearest),
)
.unwrap();
let dst_typed_image = dst_image.typed_image().unwrap();
let dst_pixels: &[U8x4] = dst_typed_image.pixels();
let row_size: usize = 64;
let black_block = vec![BLACK; 10 * row_size];
// Top border
assert_eq!(dst_pixels[0..10 * row_size], black_block);
// Middle rows
let mut middle_row = vec![BLACK; 10];
middle_row.extend(vec![WHITE; 44]);
middle_row.extend(vec![BLACK; 10]);
for row in dst_pixels.chunks_exact(row_size).skip(10).take(12) {
assert_eq!(row, middle_row);
}
// Bottom border
assert_eq!(dst_pixels[22 * row_size..], black_block);
}