use crate::compat::*;
use crate::convolution::{self, FilterType};
use crate::crop_box::CroppedSrcImageView;
use crate::image_view::{try_pixel_type, ImageView, ImageViewMut, IntoImageView, IntoImageViewMut};
use crate::images::TypedImage;
use crate::pixels::{self, InnerPixel};
use crate::{
CpuExtensions, CropBox, DifferentDimensionsError, MulDiv, PixelTrait, PixelType, ResizeError,
};
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[non_exhaustive]
pub enum ResizeAlg {
Nearest,
Convolution(FilterType),
Interpolation(FilterType),
SuperSampling(FilterType, u8),
}
impl Default for ResizeAlg {
fn default() -> Self {
Self::Convolution(FilterType::Lanczos3)
}
}
#[derive(Debug, Default, Clone, Copy)]
#[non_exhaustive]
pub enum SrcCropping {
#[default]
None,
Crop(CropBox),
FitIntoDestination((f64, f64)),
}
#[derive(Debug, Clone, Copy)]
pub struct ResizeOptions {
pub algorithm: ResizeAlg,
pub cropping: SrcCropping,
pub mul_div_alpha: bool,
}
impl Default for ResizeOptions {
fn default() -> Self {
Self {
algorithm: ResizeAlg::Convolution(FilterType::Lanczos3),
cropping: SrcCropping::None,
mul_div_alpha: true,
}
}
}
impl ResizeOptions {
pub fn new() -> Self {
Default::default()
}
pub fn resize_alg(&self, resize_alg: ResizeAlg) -> Self {
let mut options = *self;
options.algorithm = resize_alg;
options
}
pub fn crop(&self, left: f64, top: f64, width: f64, height: f64) -> Self {
let mut options = *self;
options.cropping = SrcCropping::Crop(CropBox {
left,
top,
width,
height,
});
options
}
pub fn fit_into_destination(&self, centering: Option<(f64, f64)>) -> Self {
let mut options = *self;
options.cropping = SrcCropping::FitIntoDestination(centering.unwrap_or((0.5, 0.5)));
options
}
pub fn use_alpha(&self, v: bool) -> Self {
let mut options = *self;
options.mul_div_alpha = v;
options
}
fn get_crop_box<P: PixelTrait>(
&self,
src_view: &impl ImageView<Pixel = P>,
dst_view: &impl ImageView<Pixel = P>,
) -> CropBox {
match self.cropping {
SrcCropping::None => CropBox {
left: 0.,
top: 0.,
width: src_view.width() as _,
height: src_view.height() as _,
},
SrcCropping::Crop(crop_box) => crop_box,
SrcCropping::FitIntoDestination(centering) => CropBox::fit_src_into_dst_size(
src_view.width(),
src_view.height(),
dst_view.width(),
dst_view.height(),
Some(centering),
),
}
}
}
#[derive(Default, Debug, Clone)]
pub struct Resizer {
cpu_extensions: CpuExtensions,
mul_div: MulDiv,
alpha_buffer: Vec<u8>,
convolution_buffer: Vec<u8>,
super_sampling_buffer: Vec<u8>,
}
impl Resizer {
pub fn new() -> Self {
Default::default()
}
pub fn resize<'o>(
&mut self,
src_image: &impl IntoImageView,
dst_image: &mut impl IntoImageViewMut,
options: impl Into<Option<&'o ResizeOptions>>,
) -> Result<(), ResizeError> {
let src_pixel_type = try_pixel_type(src_image)?;
let dst_pixel_type = try_pixel_type(dst_image)?;
if src_pixel_type != dst_pixel_type {
return Err(ResizeError::PixelTypesAreDifferent);
}
use PixelType as PT;
macro_rules! match_img {
(
$src_image: ident,
$dst_image: ident,
$(($p: path, $pt: path),)*
) => (
match src_pixel_type {
$(
$p => {
match (
$src_image.image_view::<$pt>(),
$dst_image.image_view_mut::<$pt>(),
) {
(Some(src), Some(mut dst)) => self.resize_typed(&src, &mut dst, options),
_ => Err(ResizeError::PixelTypesAreDifferent),
}
}
)*
_ => Err(ResizeError::PixelTypesAreDifferent),
}
)
}
#[cfg(not(feature = "only_u8x4"))]
#[allow(unreachable_patterns)]
let result = match_img!(
src_image,
dst_image,
(PT::U8, pixels::U8),
(PT::U8x2, pixels::U8x2),
(PT::U8x3, pixels::U8x3),
(PT::U8x4, pixels::U8x4),
(PT::U16, pixels::U16),
(PT::U16x2, pixels::U16x2),
(PT::U16x3, pixels::U16x3),
(PT::U16x4, pixels::U16x4),
(PT::I32, pixels::I32),
(PT::F32, pixels::F32),
(PT::F32x2, pixels::F32x2),
(PT::F32x3, pixels::F32x3),
(PT::F32x4, pixels::F32x4),
);
#[cfg(feature = "only_u8x4")]
let result = match_img!(src_image, dst_image, (PT::U8x4, pixels::U8x4),);
result
}
pub fn resize_typed<'o, P: PixelTrait>(
&mut self,
src_view: &impl ImageView<Pixel = P>,
dst_view: &mut impl ImageViewMut<Pixel = P>,
options: impl Into<Option<&'o ResizeOptions>>,
) -> Result<(), ResizeError> {
let default_options = ResizeOptions::default();
let options = options.into().unwrap_or(&default_options);
let crop_box = options.get_crop_box(src_view, dst_view);
if crop_box.width == 0.
|| crop_box.height == 0.
|| dst_view.width() == 0
|| dst_view.height() == 0
{
return Ok(());
}
let cropped_src_view = CroppedSrcImageView::crop(src_view, crop_box)?;
if copy_image(&cropped_src_view, dst_view).is_ok() {
return Ok(());
}
match options.algorithm {
ResizeAlg::Nearest => resample_nearest(&cropped_src_view, dst_view),
ResizeAlg::Convolution(filter_type) => self.resample_convolution(
&cropped_src_view,
dst_view,
filter_type,
true,
options.mul_div_alpha,
),
ResizeAlg::Interpolation(filter_type) => self.resample_convolution(
&cropped_src_view,
dst_view,
filter_type,
false,
options.mul_div_alpha,
),
ResizeAlg::SuperSampling(filter_type, multiplicity) => self.resample_super_sampling(
&cropped_src_view,
dst_view,
filter_type,
multiplicity,
options.mul_div_alpha,
),
}
Ok(())
}
pub fn size_of_internal_buffers(&self) -> usize {
(self.alpha_buffer.capacity()
+ self.convolution_buffer.capacity()
+ self.super_sampling_buffer.capacity())
* size_of::<u8>()
}
pub fn reset_internal_buffers(&mut self) {
if self.alpha_buffer.capacity() > 0 {
self.alpha_buffer = Vec::new();
}
if self.convolution_buffer.capacity() > 0 {
self.convolution_buffer = Vec::new();
}
if self.super_sampling_buffer.capacity() > 0 {
self.super_sampling_buffer = Vec::new();
}
}
#[inline(always)]
pub fn cpu_extensions(&self) -> CpuExtensions {
self.cpu_extensions
}
pub unsafe fn set_cpu_extensions(&mut self, extensions: CpuExtensions) {
self.cpu_extensions = extensions;
self.mul_div.set_cpu_extensions(extensions);
}
fn resample_convolution<P: PixelTrait>(
&mut self,
cropped_src_view: &CroppedSrcImageView<impl ImageView<Pixel = P>>,
dst_view: &mut impl ImageViewMut<Pixel = P>,
filter_type: FilterType,
adaptive_kernel_size: bool,
use_alpha: bool,
) {
if use_alpha && self.mul_div.is_supported(P::pixel_type()) {
let src_view = cropped_src_view.image_view();
let mut alpha_buffer = core::mem::take(&mut self.alpha_buffer);
let mut premultiplied_src =
get_temp_image_from_buffer(&mut alpha_buffer, src_view.width(), src_view.height());
if self
.mul_div
.multiply_alpha_typed(src_view, &mut premultiplied_src)
.is_ok()
{
let cropped_premultiplied_src = unsafe {
CroppedSrcImageView::crop_unchecked(
&premultiplied_src,
cropped_src_view.crop_box(),
)
};
self.do_convolution(
&cropped_premultiplied_src,
dst_view,
filter_type,
adaptive_kernel_size,
);
self.mul_div.divide_alpha_inplace_typed(dst_view).unwrap();
self.alpha_buffer = alpha_buffer;
return;
}
self.alpha_buffer = alpha_buffer;
}
self.do_convolution(
cropped_src_view,
dst_view,
filter_type,
adaptive_kernel_size,
);
}
fn do_convolution<P: PixelTrait>(
&mut self,
cropped_src_view: &CroppedSrcImageView<impl ImageView<Pixel = P>>,
dst_view: &mut impl ImageViewMut<Pixel = P>,
filter_type: FilterType,
adaptive_kernel_size: bool,
) {
let src_view = cropped_src_view.image_view();
let crop_box = cropped_src_view.crop_box();
let (dst_width, dst_height) = (dst_view.width(), dst_view.height());
if dst_width == 0 || dst_height == 0 || crop_box.width <= 0. || crop_box.height <= 0. {
return;
}
let (filter_fn, filter_support) = convolution::get_filter_func(filter_type);
let need_horizontal =
dst_width 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_view.width(),
crop_box.left,
crop_box.left + crop_box.width,
dst_width,
filter_fn,
filter_support,
adaptive_kernel_size,
)
});
let need_vertical =
dst_height 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_view.height(),
crop_box.top,
crop_box.top + crop_box.height,
dst_height,
filter_fn,
filter_support,
adaptive_kernel_size,
)
});
match (horiz_coeffs, vert_coeffs) {
(Some(mut horiz_coeffs), Some(mut vert_coeffs)) => {
if P::components_is_u8() {
let x_first = horiz_coeffs.bounds[0].start;
let last_x_bound = horiz_coeffs.bounds.last().unwrap();
let x_last = last_x_bound.start + last_x_bound.size;
let temp_width = x_last - x_first;
let mut temp_image = get_temp_image_from_buffer(
&mut self.convolution_buffer,
temp_width,
dst_height,
);
P::vert_convolution(
src_view,
&mut temp_image,
x_first,
vert_coeffs,
self.cpu_extensions,
);
horiz_coeffs
.bounds
.iter_mut()
.for_each(|b| b.start -= x_first);
P::horiz_convolution(
&temp_image,
dst_view,
0,
horiz_coeffs,
self.cpu_extensions,
);
} else {
let y_first = vert_coeffs.bounds[0].start;
let last_y_bound = vert_coeffs.bounds.last().unwrap();
let y_last = last_y_bound.start + last_y_bound.size;
let temp_height = y_last - y_first;
let mut temp_image = get_temp_image_from_buffer(
&mut self.convolution_buffer,
dst_width,
temp_height,
);
P::horiz_convolution(
src_view,
&mut temp_image,
y_first,
horiz_coeffs,
self.cpu_extensions,
);
vert_coeffs
.bounds
.iter_mut()
.for_each(|b| b.start -= y_first);
P::vert_convolution(&temp_image, dst_view, 0, vert_coeffs, self.cpu_extensions);
}
}
(Some(horiz_coeffs), None) => {
P::horiz_convolution(
src_view,
dst_view,
crop_box.top as u32, horiz_coeffs,
self.cpu_extensions,
);
}
(None, Some(vert_coeffs)) => {
P::vert_convolution(
src_view,
dst_view,
crop_box.left as u32, vert_coeffs,
self.cpu_extensions,
);
}
_ => {}
}
}
fn resample_super_sampling<P: PixelTrait>(
&mut self,
cropped_src_view: &CroppedSrcImageView<impl ImageView<Pixel = P>>,
dst_view: &mut impl ImageViewMut<Pixel = P>,
filter_type: FilterType,
multiplicity: u8,
use_alpha: bool,
) {
let crop_box = cropped_src_view.crop_box();
let dst_width = dst_view.width();
let dst_height = dst_view.height();
if dst_width == 0 || dst_height == 0 || crop_box.width <= 0. || crop_box.height <= 0. {
return;
}
let width_scale = crop_box.width / dst_width as f64;
let height_scale = crop_box.height / dst_height as f64;
let factor = width_scale.min(height_scale) / multiplicity as f64;
if factor > 1.2 {
let tmp_width = (crop_box.width / factor).round() as u32;
let tmp_height = (crop_box.height / factor).round() as u32;
let mut super_sampling_buffer = core::mem::take(&mut self.super_sampling_buffer);
let mut tmp_img =
get_temp_image_from_buffer(&mut super_sampling_buffer, tmp_width, tmp_height);
resample_nearest(cropped_src_view, &mut tmp_img);
let cropped_tmp_img = CroppedSrcImageView::new(&tmp_img);
self.resample_convolution(&cropped_tmp_img, dst_view, filter_type, true, use_alpha);
self.super_sampling_buffer = super_sampling_buffer;
} else {
self.resample_convolution(cropped_src_view, dst_view, filter_type, true, use_alpha);
}
}
}
fn get_temp_image_from_buffer<P: PixelTrait>(
buffer: &mut Vec<u8>,
width: u32,
height: u32,
) -> TypedImage<'_, P> {
let pixels_count = width as usize * height as usize;
let buf_size = pixels_count * P::size() + P::size();
if buffer.len() < buf_size {
buffer.resize(buf_size, 0);
}
let pixels = unsafe { buffer.align_to_mut::<P>().1 };
TypedImage::from_pixels_slice(width, height, &mut pixels[0..pixels_count]).unwrap()
}
fn resample_nearest<P: InnerPixel>(
cropped_src_view: &CroppedSrcImageView<impl ImageView<Pixel = P>>,
dst_view: &mut impl ImageViewMut<Pixel = P>,
) {
let (dst_width, dst_height) = (dst_view.width(), dst_view.height());
let src_view = cropped_src_view.image_view();
let crop_box = cropped_src_view.crop_box();
if dst_width == 0 || dst_height == 0 || crop_box.width <= 0. || crop_box.height <= 0. {
return;
}
let x_scale = crop_box.width / dst_width as f64;
let y_scale = crop_box.height / dst_height as f64;
let x_in_start = crop_box.left + x_scale * 0.5;
let max_src_x = src_view.width() 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 + y_scale * 0.5;
let src_rows = src_view.iter_rows_with_step(y_in_start, y_scale, dst_height);
let dst_rows = dst_view.iter_rows_mut(0);
#[cfg(feature = "rayon")]
{
use rayon::prelude::*;
let mut row_refs: Vec<(&mut [P], &[P])> = dst_rows.zip(src_rows).collect();
row_refs.par_iter_mut().for_each(|(out_row, in_row)| {
for (&x_in, out_pixel) in x_in_tab.iter().zip(out_row.iter_mut()) {
*out_pixel = unsafe { *in_row.get_unchecked(x_in) };
}
});
}
#[cfg(not(feature = "rayon"))]
{
for (out_row, in_row) in dst_rows.zip(src_rows) {
for (&x_in, out_pixel) in x_in_tab.iter().zip(out_row.iter_mut()) {
*out_pixel = unsafe { *in_row.get_unchecked(x_in) };
}
}
}
}
pub(crate) fn copy_image<S, P: PixelTrait>(
cropped_src_view: &CroppedSrcImageView<S>,
dst_view: &mut impl ImageViewMut<Pixel = P>,
) -> Result<(), DifferentDimensionsError>
where
S: ImageView<Pixel = P>,
{
let crop_box = cropped_src_view.crop_box();
if crop_box.left != crop_box.left.round()
|| crop_box.top != crop_box.top.round()
|| crop_box.width != crop_box.width.round()
|| crop_box.height != crop_box.height.round()
{
return Err(DifferentDimensionsError);
}
if dst_view.width() != crop_box.width as u32 || dst_view.height() != crop_box.height as u32 {
return Err(DifferentDimensionsError);
}
if dst_view.width() > 0 && dst_view.height() > 0 {
dst_view
.iter_rows_mut(0)
.zip(iter_cropped_rows(cropped_src_view))
.for_each(|(d, s)| d.copy_from_slice(s));
}
Ok(())
}
fn iter_cropped_rows<'a, S: ImageView>(
cropped_src_view: &'a CroppedSrcImageView<S>,
) -> impl Iterator<Item = &'a [S::Pixel]> {
let crop_box = cropped_src_view.crop_box();
let rows = cropped_src_view
.image_view()
.iter_rows(crop_box.top.max(0.) as u32)
.take(crop_box.height.max(0.) as usize);
let first_col = crop_box.left.max(0.) as usize;
let last_col = first_col + crop_box.width.max(0.) as usize;
rows.map(move |row| unsafe { row.get_unchecked(first_col..last_col) })
}