mirror of
https://github.com/Cykooz/fast_image_resize.git
synced 2026-10-08 01:11:09 +00:00
- **BREAKING**: The type of the CropBox fields has been changed to f64. Now you can use
fractional size and position of crop box. - **BREAKING**: The type of `centering` argument of `ImageView::set_crop_box_to_fit_dst_size()` and `DynamicImageView::set_crop_box_to_fit_dst_size()` methods has been changed to `Optional<(f64, f64)>`. - **BREAKING**: The `crop_box` argument of `ImageViewMut::crop()` and `DynamicImageViewMut::crop()` methods has been replaced with separate `left`, `top`, `width` and `height` arguments.
This commit is contained in:
@@ -14,6 +14,12 @@
|
||||
in native Rust for `U8`, `U8x2`, `U8x3` and `U8x4` images.
|
||||
- Changed order of convolution passes for `U8`, `U8x2`, `U8x3` and `U8x4` images.
|
||||
Now the vertical pass is the first and the horizontal pass is the second.
|
||||
- **BREAKING**: The type of the `CropBox` fields has been changed to `f64`. Now you can use
|
||||
fractional size and position of crop box.
|
||||
- **BREAKING**: The type of `centering` argument of `ImageView::set_crop_box_to_fit_dst_size()`
|
||||
and `DynamicImageView::set_crop_box_to_fit_dst_size()` methods has been changed to `Optional<(f64, f64)>`.
|
||||
- **BREAKING**: The `crop_box` argument of `ImageViewMut::crop()` and `DynamicImageViewMut::crop()`
|
||||
methods has been replaced with separate `left`, `top`, `width` and `height` arguments.
|
||||
|
||||
## [2.7.3] - 2023-05-07
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ impl<'a> DynamicImageView<'a> {
|
||||
&mut self,
|
||||
dst_width: NonZeroU32,
|
||||
dst_height: NonZeroU32,
|
||||
centering: Option<(f32, f32)>,
|
||||
centering: Option<(f64, f64)>,
|
||||
) {
|
||||
dynamic_map!(self, |typed_image| typed_image
|
||||
.set_crop_box_to_fit_dst_size(dst_width, dst_height, centering))
|
||||
@@ -148,8 +148,17 @@ impl<'a> DynamicImageViewMut<'a> {
|
||||
}
|
||||
|
||||
/// Create cropped version of the view.
|
||||
pub fn crop(self, crop_box: CropBox) -> Result<Self, CropBoxError> {
|
||||
Ok(dynamic_mut_map!(self, typed_image => typed_image.crop(crop_box)?))
|
||||
pub fn crop(
|
||||
self,
|
||||
left: u32,
|
||||
top: u32,
|
||||
width: NonZeroU32,
|
||||
height: NonZeroU32,
|
||||
) -> Result<Self, CropBoxError> {
|
||||
Ok(dynamic_mut_map!(
|
||||
self,
|
||||
typed_image => typed_image.crop(left, top, width, height)?
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+75
-52
@@ -6,14 +6,14 @@ use std::slice;
|
||||
use crate::pixels::{GetCount, IntoPixelComponent, PixelComponent, PixelExt};
|
||||
use crate::{CropBoxError, DifferentDimensionsError, ImageBufferError, ImageRowsError, PixelType};
|
||||
|
||||
/// Parameters of crop box that may be used with [`ImageView`]
|
||||
/// A crop box parameters that may be used with [`ImageView`]
|
||||
/// and [`DynamicImageView`](crate::DynamicImageView)
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CropBox {
|
||||
pub left: u32,
|
||||
pub top: u32,
|
||||
pub width: NonZeroU32,
|
||||
pub height: NonZeroU32,
|
||||
pub left: f64,
|
||||
pub top: f64,
|
||||
pub width: f64,
|
||||
pub height: f64,
|
||||
}
|
||||
|
||||
/// Generic immutable image view.
|
||||
@@ -42,10 +42,10 @@ where
|
||||
width,
|
||||
height,
|
||||
crop_box: CropBox {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width,
|
||||
height,
|
||||
left: 0.,
|
||||
top: 0.,
|
||||
width: width.get() as _,
|
||||
height: height.get() as _,
|
||||
},
|
||||
rows,
|
||||
})
|
||||
@@ -70,10 +70,10 @@ where
|
||||
width,
|
||||
height,
|
||||
crop_box: CropBox {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width,
|
||||
height,
|
||||
left: 0.,
|
||||
top: 0.,
|
||||
width: width.get() as _,
|
||||
height: height.get() as _,
|
||||
},
|
||||
rows,
|
||||
})
|
||||
@@ -97,10 +97,10 @@ where
|
||||
width,
|
||||
height,
|
||||
crop_box: CropBox {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width,
|
||||
height,
|
||||
left: 0.,
|
||||
top: 0.,
|
||||
width: width.get() as _,
|
||||
height: height.get() as _,
|
||||
},
|
||||
rows,
|
||||
})
|
||||
@@ -123,12 +123,19 @@ where
|
||||
}
|
||||
|
||||
pub fn set_crop_box(&mut self, crop_box: CropBox) -> Result<(), CropBoxError> {
|
||||
if crop_box.left >= self.width.get() || crop_box.top >= self.height.get() {
|
||||
let width_f = self.width().get() as f64;
|
||||
let height_f = self.height().get() as f64;
|
||||
|
||||
if crop_box.left < 0.
|
||||
|| crop_box.top < 0.
|
||||
|| crop_box.left >= width_f
|
||||
|| crop_box.top >= height_f
|
||||
{
|
||||
return Err(CropBoxError::PositionIsOutOfImageBoundaries);
|
||||
}
|
||||
let right = crop_box.left + crop_box.width.get();
|
||||
let bottom = crop_box.top + crop_box.height.get();
|
||||
if right > self.width.get() || bottom > self.height.get() {
|
||||
let right = crop_box.left + crop_box.width;
|
||||
let bottom = crop_box.top + crop_box.height;
|
||||
if right > width_f || bottom > height_f {
|
||||
return Err(CropBoxError::SizeIsOutOfImageBoundaries);
|
||||
}
|
||||
self.crop_box = crop_box;
|
||||
@@ -152,7 +159,7 @@ where
|
||||
&mut self,
|
||||
dst_width: NonZeroU32,
|
||||
dst_height: NonZeroU32,
|
||||
centering: Option<(f32, f32)>,
|
||||
centering: Option<(f64, f64)>,
|
||||
) {
|
||||
// This function based on code of ImageOps.fit() from Pillow package.
|
||||
// https://github.com/python-pillow/Pillow/blob/master/src/PIL/ImageOps.py
|
||||
@@ -163,15 +170,15 @@ where
|
||||
};
|
||||
|
||||
// calculate aspect ratios
|
||||
let width = self.width.get() as f32;
|
||||
let height = self.height.get() as f32;
|
||||
let width = self.width.get() as f64;
|
||||
let height = self.height.get() as f64;
|
||||
let image_ratio = width / height;
|
||||
let required_ration = dst_width.get() as f32 / dst_height.get() as f32;
|
||||
let required_ration = dst_width.get() as f64 / dst_height.get() as f64;
|
||||
|
||||
let crop_width;
|
||||
let crop_height;
|
||||
// figure out if the sides or top/bottom will be cropped off
|
||||
if (image_ratio - required_ration).abs() < f32::EPSILON {
|
||||
if (image_ratio - required_ration).abs() < f64::EPSILON {
|
||||
// The image is already the needed ratio
|
||||
crop_width = width;
|
||||
crop_height = height;
|
||||
@@ -189,10 +196,10 @@ where
|
||||
let crop_top = (height - crop_height) * centering.1;
|
||||
|
||||
self.set_crop_box(CropBox {
|
||||
left: crop_left.round() as u32,
|
||||
top: crop_top.round() as u32,
|
||||
width: NonZeroU32::new(crop_width.round() as u32).unwrap(),
|
||||
height: NonZeroU32::new(crop_height.round() as u32).unwrap(),
|
||||
left: crop_left,
|
||||
top: crop_top,
|
||||
width: crop_width,
|
||||
height: crop_height,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -259,11 +266,11 @@ where
|
||||
#[inline(always)]
|
||||
pub(crate) fn iter_cropped_rows<'s>(&'s self) -> impl Iterator<Item = &'a [P]> + 's {
|
||||
let first_row = self.crop_box.top as usize;
|
||||
let last_row = first_row + self.crop_box.height.get() as usize;
|
||||
let last_row = first_row + self.crop_box.height as usize;
|
||||
let rows = unsafe { self.rows.get_unchecked(first_row..last_row) };
|
||||
|
||||
let first_col = self.crop_box.left as usize;
|
||||
let last_col = first_col + self.crop_box.width.get() as usize;
|
||||
let last_col = first_col + self.crop_box.width as usize;
|
||||
rows.iter()
|
||||
// Safety guaranteed by method 'set_crop_box'
|
||||
.map(move |row| unsafe { row.get_unchecked(first_col..last_col) })
|
||||
@@ -379,7 +386,17 @@ where
|
||||
src_view: &ImageView<P>,
|
||||
) -> Result<(), DifferentDimensionsError> {
|
||||
let src_crop_box = src_view.crop_box();
|
||||
if self.width != src_crop_box.width || self.height != src_crop_box.height {
|
||||
if src_crop_box.left != src_crop_box.left.round()
|
||||
|| src_crop_box.top != src_crop_box.top.round()
|
||||
|| src_crop_box.width != src_crop_box.width.round()
|
||||
|| src_crop_box.height != src_crop_box.height.round()
|
||||
{
|
||||
// The crop box has fractional part in some his part
|
||||
return Err(DifferentDimensionsError);
|
||||
}
|
||||
if self.width.get() != src_crop_box.width as u32
|
||||
|| self.height.get() != src_crop_box.height as u32
|
||||
{
|
||||
return Err(DifferentDimensionsError);
|
||||
}
|
||||
self.rows
|
||||
@@ -390,26 +407,32 @@ where
|
||||
}
|
||||
|
||||
/// Create cropped version of the view.
|
||||
pub fn crop(self, crop_box: CropBox) -> Result<Self, CropBoxError> {
|
||||
if crop_box.left >= self.width.get() || crop_box.top >= self.height.get() {
|
||||
pub fn crop(
|
||||
self,
|
||||
left: u32,
|
||||
top: u32,
|
||||
width: NonZeroU32,
|
||||
height: NonZeroU32,
|
||||
) -> Result<Self, CropBoxError> {
|
||||
if left >= self.width.get() || top >= self.height.get() {
|
||||
return Err(CropBoxError::PositionIsOutOfImageBoundaries);
|
||||
}
|
||||
let right = crop_box.left + crop_box.width.get();
|
||||
let bottom = crop_box.top + crop_box.height.get();
|
||||
let right = left + width.get();
|
||||
let bottom = top + height.get();
|
||||
if right > self.width.get() || bottom > self.height.get() {
|
||||
return Err(CropBoxError::SizeIsOutOfImageBoundaries);
|
||||
}
|
||||
let row_range = (crop_box.left as usize)..(right as usize);
|
||||
let row_range = (left as usize)..(right as usize);
|
||||
let rows = self
|
||||
.rows
|
||||
.into_iter()
|
||||
.skip(crop_box.top as usize)
|
||||
.take(crop_box.height.get() as usize)
|
||||
.skip(top as usize)
|
||||
.take(height.get() as usize)
|
||||
.map(|row| unsafe { row.get_unchecked_mut(row_range.clone()) })
|
||||
.collect();
|
||||
Ok(Self {
|
||||
width: crop_box.width,
|
||||
height: crop_box.height,
|
||||
width,
|
||||
height,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
@@ -430,10 +453,10 @@ where
|
||||
width: view.width,
|
||||
height: view.height,
|
||||
crop_box: CropBox {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: view.width,
|
||||
height: view.height,
|
||||
left: 0.,
|
||||
top: 0.,
|
||||
width: view.width.get() as _,
|
||||
height: view.height.get() as _,
|
||||
},
|
||||
rows,
|
||||
}
|
||||
@@ -511,12 +534,12 @@ mod tests {
|
||||
let image_view: ImageViewMut<crate::pixels::U8> =
|
||||
ImageViewMut::from_buffer(image.width(), image.height(), image.buffer_mut()).unwrap();
|
||||
let cropped_view = image_view
|
||||
.crop(CropBox {
|
||||
left: 10,
|
||||
top: 10,
|
||||
width: NonZeroU32::new(44).unwrap(),
|
||||
height: NonZeroU32::new(12).unwrap(),
|
||||
})
|
||||
.crop(
|
||||
10,
|
||||
10,
|
||||
NonZeroU32::new(44).unwrap(),
|
||||
NonZeroU32::new(12).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(cropped_view.width().get(), 44);
|
||||
assert_eq!(cropped_view.height().get(), 12);
|
||||
|
||||
+19
-19
@@ -266,17 +266,17 @@ where
|
||||
{
|
||||
let crop_box = src_image.crop_box();
|
||||
let dst_width = dst_image.width().get();
|
||||
let x_scale = crop_box.width.get() as f64 / dst_width as f64;
|
||||
let y_scale = crop_box.height.get() as f64 / dst_image.height().get() as f64;
|
||||
let x_scale = crop_box.width / dst_width as f64;
|
||||
let y_scale = crop_box.height / dst_image.height().get() as f64;
|
||||
|
||||
// Pretabulate horizontal pixel positions
|
||||
let x_in_start = crop_box.left as f64 + x_scale * 0.5;
|
||||
let x_in_start = crop_box.left + x_scale * 0.5;
|
||||
let max_src_x = src_image.width().get() as usize;
|
||||
let x_in_tab: Vec<usize> = (0..dst_width)
|
||||
.map(|x| ((x_in_start + x_scale * x as f64) as usize).min(max_src_x))
|
||||
.collect();
|
||||
|
||||
let y_in_start = crop_box.top as f64 + y_scale * 0.5;
|
||||
let y_in_start = crop_box.top + y_scale * 0.5;
|
||||
|
||||
let src_rows =
|
||||
src_image.iter_rows_with_step(y_in_start, y_scale, dst_image.height().get() as usize);
|
||||
@@ -303,26 +303,28 @@ fn resample_convolution<P>(
|
||||
let dst_height = dst_image.height();
|
||||
let (filter_fn, filter_support) = convolution::get_filter_func(filter_type);
|
||||
|
||||
let need_horizontal = dst_width != crop_box.width;
|
||||
let need_horizontal =
|
||||
dst_width.get() as f64 != crop_box.width || crop_box.left != crop_box.left.round();
|
||||
let horiz_coeffs = need_horizontal.then(|| {
|
||||
test_log!("compute horizontal convolution coefficients");
|
||||
convolution::precompute_coefficients(
|
||||
src_image.width(),
|
||||
crop_box.left as f64,
|
||||
crop_box.left as f64 + crop_box.width.get() as f64,
|
||||
crop_box.left,
|
||||
crop_box.left + crop_box.width,
|
||||
dst_width,
|
||||
filter_fn,
|
||||
filter_support,
|
||||
)
|
||||
});
|
||||
|
||||
let need_vertical = dst_height != crop_box.height;
|
||||
let need_vertical =
|
||||
dst_height.get() as f64 != crop_box.height || crop_box.top != crop_box.top.round();
|
||||
let vert_coeffs = need_vertical.then(|| {
|
||||
test_log!("compute vertical convolution coefficients");
|
||||
convolution::precompute_coefficients(
|
||||
src_image.height(),
|
||||
crop_box.top as f64,
|
||||
crop_box.top as f64 + crop_box.height.get() as f64,
|
||||
crop_box.top,
|
||||
crop_box.top + crop_box.height,
|
||||
dst_height,
|
||||
filter_fn,
|
||||
filter_support,
|
||||
@@ -397,7 +399,7 @@ fn resample_convolution<P>(
|
||||
P::horiz_convolution(
|
||||
src_image,
|
||||
dst_image,
|
||||
crop_box.top,
|
||||
crop_box.top as u32, // crop_box.top is exactly an integer if vertical pass is not required
|
||||
horiz_coeffs,
|
||||
cpu_extensions,
|
||||
);
|
||||
@@ -406,7 +408,7 @@ fn resample_convolution<P>(
|
||||
P::vert_convolution(
|
||||
src_image,
|
||||
dst_image,
|
||||
crop_box.left,
|
||||
crop_box.left as u32, // crop_box.left is exactly an integer if horizontal pass is not required
|
||||
vert_coeffs,
|
||||
cpu_extensions,
|
||||
);
|
||||
@@ -429,19 +431,17 @@ fn resample_super_sampling<P>(
|
||||
let crop_box = src_image.crop_box();
|
||||
let dst_width = dst_image.width().get();
|
||||
let dst_height = dst_image.height().get();
|
||||
let width_scale = crop_box.width.get() as f32 / dst_width as f32;
|
||||
let height_scale = crop_box.height.get() as f32 / dst_height as f32;
|
||||
let width_scale = crop_box.width / dst_width as f64;
|
||||
let height_scale = crop_box.height / dst_height as f64;
|
||||
// It makes sense to resize the image in two steps only if the image
|
||||
// size is greater than the required size by multiplicity times.
|
||||
let factor = width_scale.min(height_scale) / multiplicity as f32;
|
||||
let factor = width_scale.min(height_scale) / multiplicity as f64;
|
||||
if factor > 1.2 {
|
||||
// First step is resizing the source image by fastest algorithm.
|
||||
// The temporary image will be about ``multiplicity`` times larger
|
||||
// than required.
|
||||
let tmp_width =
|
||||
NonZeroU32::new((crop_box.width.get() as f32 / factor).round() as u32).unwrap();
|
||||
let tmp_height =
|
||||
NonZeroU32::new((crop_box.height.get() as f32 / factor).round() as u32).unwrap();
|
||||
let tmp_width = NonZeroU32::new((crop_box.width / factor).round() as u32).unwrap();
|
||||
let tmp_height = NonZeroU32::new((crop_box.height / factor).round() as u32).unwrap();
|
||||
|
||||
let mut tmp_img = get_temp_image_from_buffer(temp_buffer, tmp_width, tmp_height);
|
||||
resample_nearest(src_image, &mut tmp_img.dst_view());
|
||||
|
||||
@@ -69,12 +69,7 @@ fn crop_view_mut() {
|
||||
|
||||
let mut cropped_dst_view = dst_image
|
||||
.view_mut()
|
||||
.crop(fr::CropBox {
|
||||
left: 10,
|
||||
top: 10,
|
||||
width: nonzero(44),
|
||||
height: nonzero(12),
|
||||
})
|
||||
.crop(10, 10, nonzero(44), nonzero(12))
|
||||
.unwrap();
|
||||
assert_eq!(cropped_dst_view.width().get(), 44);
|
||||
assert_eq!(cropped_dst_view.height().get(), 12);
|
||||
|
||||
+56
-12
@@ -61,10 +61,10 @@ fn resize_to_same_size_after_cropping() {
|
||||
let mut src_view = src_image.view();
|
||||
src_view
|
||||
.set_crop_box(CropBox {
|
||||
top: 10,
|
||||
left: 10,
|
||||
width,
|
||||
height,
|
||||
top: 10.,
|
||||
left: 10.,
|
||||
width: width.get() as _,
|
||||
height: height.get() as _,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -110,10 +110,10 @@ fn resize_to_same_width<const C: usize>(
|
||||
let mut src_view = src_image.view();
|
||||
src_view
|
||||
.set_crop_box(CropBox {
|
||||
left: 10,
|
||||
top: 0,
|
||||
width,
|
||||
height: src_height,
|
||||
left: 10.,
|
||||
top: 0.,
|
||||
width: width.get() as _,
|
||||
height: src_height.get() as _,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -164,10 +164,10 @@ fn resize_to_same_height<const C: usize>(
|
||||
let mut src_view = src_image.view();
|
||||
src_view
|
||||
.set_crop_box(CropBox {
|
||||
left: 0,
|
||||
top: 10,
|
||||
width: src_width,
|
||||
height,
|
||||
left: 0.,
|
||||
top: 10.,
|
||||
width: src_width.get() as _,
|
||||
height: height.get() as _,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -811,6 +811,50 @@ mod not_u8x4 {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fractional_cropping() {
|
||||
let mut src_buf = [0, 0, 0, 0, 255, 0, 0, 0, 0];
|
||||
let src_image =
|
||||
Image::from_slice_u8(nonzero(3), nonzero(3), &mut src_buf, PixelType::U8).unwrap();
|
||||
let mut dst_image = Image::new(nonzero(1), nonzero(1), PixelType::U8);
|
||||
let mut resizer = Resizer::new(ResizeAlg::Convolution(FilterType::Box));
|
||||
|
||||
// Resize without cropping
|
||||
resizer
|
||||
.resize(&src_image.view(), &mut dst_image.view_mut())
|
||||
.unwrap();
|
||||
assert_eq!(dst_image.buffer()[0], (255.0f32 / 9.0).round() as u8);
|
||||
|
||||
// Resize with fractional cropping
|
||||
let mut src_view = src_image.view();
|
||||
src_view
|
||||
.set_crop_box(CropBox {
|
||||
left: 0.5,
|
||||
top: 0.5,
|
||||
width: 2.,
|
||||
height: 2.,
|
||||
})
|
||||
.unwrap();
|
||||
resizer
|
||||
.resize(&src_view, &mut dst_image.view_mut())
|
||||
.unwrap();
|
||||
assert_eq!(dst_image.buffer()[0], (255.0f32 / 4.0).round() as u8);
|
||||
|
||||
// Resize with integer cropping
|
||||
src_view
|
||||
.set_crop_box(CropBox {
|
||||
left: 1.,
|
||||
top: 1.,
|
||||
width: 1.,
|
||||
height: 1.,
|
||||
})
|
||||
.unwrap();
|
||||
resizer
|
||||
.resize(&src_view, &mut dst_image.view_mut())
|
||||
.unwrap();
|
||||
assert_eq!(dst_image.buffer()[0], 255);
|
||||
}
|
||||
}
|
||||
|
||||
mod u8x4 {
|
||||
|
||||
Reference in New Issue
Block a user