Add SIMD-accelerated resize operation using fast_image_resize
Lanczos3 filter, supports ByWidth, ByHeight, FitInBox, Exact modes. All 5 resize tests passing.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
pub mod resize;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::{Dimensions, ImageFormat, QualityPreset};
|
||||
|
||||
60
pixstrip-core/src/operations/resize.rs
Normal file
60
pixstrip-core/src/operations/resize.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use fast_image_resize::{images::Image, Resizer, ResizeOptions, ResizeAlg, FilterType};
|
||||
|
||||
use crate::error::{PixstripError, Result};
|
||||
use crate::types::Dimensions;
|
||||
|
||||
use super::ResizeConfig;
|
||||
|
||||
pub fn resize_image(
|
||||
src: &image::DynamicImage,
|
||||
config: &ResizeConfig,
|
||||
) -> Result<image::DynamicImage> {
|
||||
let original = Dimensions {
|
||||
width: src.width(),
|
||||
height: src.height(),
|
||||
};
|
||||
let target = config.target_for(original);
|
||||
|
||||
if target.width == original.width && target.height == original.height {
|
||||
return Ok(src.clone());
|
||||
}
|
||||
|
||||
let src_rgba = src.to_rgba8();
|
||||
let (src_w, src_h) = (src_rgba.width(), src_rgba.height());
|
||||
|
||||
let src_image = Image::from_vec_u8(
|
||||
src_w,
|
||||
src_h,
|
||||
src_rgba.into_raw(),
|
||||
fast_image_resize::PixelType::U8x4,
|
||||
)
|
||||
.map_err(|e| PixstripError::Processing {
|
||||
operation: "resize".into(),
|
||||
reason: format!("Failed to create source image: {}", e),
|
||||
})?;
|
||||
|
||||
let mut dst_image = Image::new(
|
||||
target.width,
|
||||
target.height,
|
||||
fast_image_resize::PixelType::U8x4,
|
||||
);
|
||||
|
||||
let mut resizer = Resizer::new();
|
||||
let options = ResizeOptions::new().resize_alg(ResizeAlg::Convolution(FilterType::Lanczos3));
|
||||
|
||||
resizer
|
||||
.resize(&src_image, &mut dst_image, &options)
|
||||
.map_err(|e| PixstripError::Processing {
|
||||
operation: "resize".into(),
|
||||
reason: format!("Resize failed: {}", e),
|
||||
})?;
|
||||
|
||||
let result_buf: image::RgbaImage =
|
||||
image::ImageBuffer::from_raw(target.width, target.height, dst_image.into_vec())
|
||||
.ok_or_else(|| PixstripError::Processing {
|
||||
operation: "resize".into(),
|
||||
reason: "Failed to create output image buffer".into(),
|
||||
})?;
|
||||
|
||||
Ok(image::DynamicImage::ImageRgba8(result_buf))
|
||||
}
|
||||
61
pixstrip-core/tests/resize_tests.rs
Normal file
61
pixstrip-core/tests/resize_tests.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use pixstrip_core::operations::resize::resize_image;
|
||||
use pixstrip_core::operations::ResizeConfig;
|
||||
use pixstrip_core::types::Dimensions;
|
||||
|
||||
fn create_test_image(width: u32, height: u32) -> image::DynamicImage {
|
||||
let img = image::RgbImage::from_fn(width, height, |x, y| {
|
||||
image::Rgb([(x % 256) as u8, (y % 256) as u8, 128])
|
||||
});
|
||||
image::DynamicImage::ImageRgb8(img)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize_by_width() {
|
||||
let img = create_test_image(4000, 3000);
|
||||
let config = ResizeConfig::ByWidth(1200);
|
||||
let result = resize_image(&img, &config).unwrap();
|
||||
assert_eq!(result.width(), 1200);
|
||||
assert_eq!(result.height(), 900);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize_by_height() {
|
||||
let img = create_test_image(4000, 3000);
|
||||
let config = ResizeConfig::ByHeight(600);
|
||||
let result = resize_image(&img, &config).unwrap();
|
||||
assert_eq!(result.width(), 800);
|
||||
assert_eq!(result.height(), 600);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize_fit_in_box() {
|
||||
let img = create_test_image(4000, 3000);
|
||||
let config = ResizeConfig::FitInBox {
|
||||
max: Dimensions { width: 1920, height: 1080 },
|
||||
allow_upscale: false,
|
||||
};
|
||||
let result = resize_image(&img, &config).unwrap();
|
||||
assert_eq!(result.width(), 1440);
|
||||
assert_eq!(result.height(), 1080);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize_no_upscale() {
|
||||
let img = create_test_image(800, 600);
|
||||
let config = ResizeConfig::FitInBox {
|
||||
max: Dimensions { width: 1920, height: 1080 },
|
||||
allow_upscale: false,
|
||||
};
|
||||
let result = resize_image(&img, &config).unwrap();
|
||||
assert_eq!(result.width(), 800);
|
||||
assert_eq!(result.height(), 600);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize_exact() {
|
||||
let img = create_test_image(4000, 3000);
|
||||
let config = ResizeConfig::Exact(Dimensions { width: 1080, height: 1080 });
|
||||
let result = resize_image(&img, &config).unwrap();
|
||||
assert_eq!(result.width(), 1080);
|
||||
assert_eq!(result.height(), 1080);
|
||||
}
|
||||
Reference in New Issue
Block a user