mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-02 18:13:39 +00:00
9cc8be4392
warp() computed the source-centre fallback as (x0 + x1) in i32 and the inverse-mapped tile footprint as `floor() as i32 - 3` / `ceil() as i32 + 3`. With a strongly skewed finite affine (for example Homography [1, 1e12, 0, 0, 1, 0, 0, 0, 1] on a 32x32 clip) the float-to-int cast saturates at i32::MIN/MAX and the following -3/+3 overflows, panicking in debug builds and wrapping in release. A source rect with coordinates near 1.5e9 would overflow the centre sum the same way. Widen the centre sum to f64 and use saturating_sub/saturating_add for the footprint, which is intersected with src_rect afterwards anyway. Add crates/algo/tests/warp_clipped.rs covering clipped-vs-full equivalence, huge scale allocation, the extreme-affine footprint, and the far-origin source centre. Refs #3446
60 lines
3.2 KiB
Rust
60 lines
3.2 KiB
Rust
use photocraft_algo::transform::{Homography, Interp, warp_surface, warp_surface_clipped};
|
|
use photocraft_color::{ColorMode, PixelFormat, SampleType};
|
|
use photocraft_geom::Rect;
|
|
use photocraft_raster::Surface;
|
|
|
|
#[test]
|
|
fn clipped_warp_matches_the_existing_kernel_at_every_depth() {
|
|
for sample in [SampleType::U8, SampleType::U16, SampleType::F32] {
|
|
let mut source = Surface::new(PixelFormat::new(ColorMode::Rgb, sample, true));
|
|
source.fill_rect(Rect::new(8, 12, 40, 44), &[0.8, 0.2, 0.4, 0.7]);
|
|
source.fill_rect(Rect::new(20, 24, 32, 36), &[0.1, 0.7, 0.3, 0.4]);
|
|
let clip = Rect::new(10, 10, 52, 52);
|
|
for interpolation in [Interp::Nearest, Interp::Bilinear, Interp::Bicubic] {
|
|
for h in [Homography([1.2, -0.3, 7.0, 0.3, 1.2, -5.0, 0.0, 0.0, 1.0]), Homography([0.125, 0.0, 24.0, 0.0, 0.125, 30.0, 0.0, 0.0, 1.0])] {
|
|
let full = warp_surface(&source, source.content_bounds(), &h, interpolation).unwrap();
|
|
let bounded = warp_surface_clipped(&source, source.content_bounds(), &h, interpolation, clip).unwrap();
|
|
assert_eq!(full.read_region(clip), bounded.read_region(clip));
|
|
assert!(clip.contains_rect(&bounded.content_bounds()));
|
|
assert!(bounded.tiles().count() <= clip.tiles().count());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn huge_scaled_warp_allocates_only_the_destination_clip() {
|
|
let mut source = Surface::new(PixelFormat::RGBA8);
|
|
source.fill_rect(Rect::new(8, 8, 12, 12), &[0.2, 0.7, 0.4, 1.0]);
|
|
let clip = Rect::new(0, 0, 64, 64);
|
|
let h = Homography([10_000.0, 0.0, -99_968.0, 0.0, 10_000.0, -99_968.0, 0.0, 0.0, 1.0]);
|
|
let output = warp_surface_clipped(&source, source.content_bounds(), &h, Interp::Bicubic, clip).unwrap();
|
|
assert!(clip.contains_rect(&output.content_bounds()));
|
|
assert!(output.tiles().count() <= clip.tiles().count());
|
|
assert_eq!(output.pixel(32, 32), source.pixel(10, 10));
|
|
assert!(warp_surface_clipped(&source, source.content_bounds(), &h, Interp::Bicubic, Rect::EMPTY).unwrap().tiles().next().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn inverse_footprint_handles_extreme_finite_affines() {
|
|
let mut source = Surface::new(PixelFormat::RGBA8);
|
|
source.fill_rect(Rect::new(0, 0, 8, 8), &[1.0, 0.0, 0.0, 1.0]);
|
|
// A strongly skewed inverse maps a small visible tile beyond i32 limits.
|
|
let h = Homography([1.0, 1e12, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]);
|
|
let clip = Rect::new(0, 0, 32, 32);
|
|
let output = warp_surface_clipped(&source, source.content_bounds(), &h, Interp::Nearest, clip).unwrap();
|
|
assert!(clip.contains_rect(&output.content_bounds()));
|
|
assert!(output.tiles().count() <= clip.tiles().count());
|
|
}
|
|
|
|
#[test]
|
|
fn source_center_does_not_add_extreme_coordinates_in_i32() {
|
|
let mut source = Surface::new(PixelFormat::RGBA8);
|
|
let origin = 1_500_000_000;
|
|
let bounds = Rect::new(origin, origin, origin + 4, origin + 4);
|
|
source.fill_rect(bounds, &[0.2, 0.7, 0.4, 1.0]);
|
|
let h = Homography([1.0, 0.0, -f64::from(origin), 0.0, 1.0, -f64::from(origin), 0.0, 0.0, 1.0]);
|
|
let output = warp_surface_clipped(&source, bounds, &h, Interp::Nearest, Rect::new(0, 0, 8, 8)).unwrap();
|
|
assert_eq!(output.pixel(2, 2), source.pixel(origin + 2, origin + 2));
|
|
}
|