use crate::core::image::RgbImage;
pub fn apply_warp_rectilinear(image: &mut RgbImage, k: [f64; 4], cx: f64, cy: f64) {
apply_warp_rectilinear_tangential(image, k, [0.0, 0.0], cx, cy);
}
pub fn apply_warp_rectilinear_tangential(
image: &mut RgbImage,
k: [f64; 4],
p: [f64; 2],
cx: f64,
cy: f64,
) {
let width = image.width() as usize;
let height = image.height() as usize;
if width == 0 || height == 0 {
return;
}
let norm = 0.5 * width.min(height) as f64;
let cx_px = cx * width as f64;
let cy_px = cy * height as f64;
let src = image.data.clone();
for y in 0..height {
for x in 0..width {
let xn = (x as f64 - cx_px) / norm;
let yn = (y as f64 - cy_px) / norm;
let r2 = xn * xn + yn * yn;
let r4 = r2 * r2;
let r6 = r4 * r2;
let r8 = r4 * r4;
let factor = 1.0 + k[0] * r2 + k[1] * r4 + k[2] * r6 + k[3] * r8;
let xs = xn * factor + 2.0 * p[0] * xn * yn + p[1] * (r2 + 2.0 * xn * xn);
let ys = yn * factor + p[0] * (r2 + 2.0 * yn * yn) + 2.0 * p[1] * xn * yn;
let src_x = xs * norm + cx_px;
let src_y = ys * norm + cy_px;
let dst = (y * width + x) * 3;
for ch in 0..3usize {
image.data[dst + ch] =
bilinear_sample(&src, width, height, ch, src_x as f32, src_y as f32);
}
}
}
}
#[inline]
fn bilinear_sample(
data: &[u16],
width: usize,
height: usize,
channel: usize,
sx: f32,
sy: f32,
) -> u16 {
let sx = sx.clamp(0.0, (width as f32) - 1.0);
let sy = sy.clamp(0.0, (height as f32) - 1.0);
let x0 = sx.floor() as usize;
let y0 = sy.floor() as usize;
let x1 = (x0 + 1).min(width - 1);
let y1 = (y0 + 1).min(height - 1);
let fx = sx - sx.floor();
let fy = sy - sy.floor();
let p00 = data[(y0 * width + x0) * 3 + channel] as f32;
let p10 = data[(y0 * width + x1) * 3 + channel] as f32;
let p01 = data[(y1 * width + x0) * 3 + channel] as f32;
let p11 = data[(y1 * width + x1) * 3 + channel] as f32;
let top = p00 * (1.0 - fx) + p10 * fx;
let bot = p01 * (1.0 - fx) + p11 * fx;
let result = top * (1.0 - fy) + bot * fy;
result.round() as u16
}
#[cfg(test)]
mod tests {
use super::*;
fn make_rgb(width: u32, height: u32, fill: u16) -> RgbImage {
let n = (width as usize) * (height as usize) * 3;
RgbImage::new(width, height, vec![fill; n])
}
fn make_gradient(width: u32, height: u32) -> RgbImage {
let n = (width as usize) * (height as usize) * 3;
let data: Vec<u16> = (0..n).map(|i| (i as u16).wrapping_mul(7)).collect();
RgbImage::new(width, height, data)
}
#[test]
fn test_warp_zero_coefficients_unchanged() {
let w = 8u32;
let h = 8u32;
let original = make_gradient(w, h);
let mut img = original.clone();
apply_warp_rectilinear(&mut img, [0.0; 4], 0.5, 0.5);
assert_eq!(
img.data, original.data,
"zero coefficients must leave image unchanged"
);
}
#[test]
fn test_warp_output_size_unchanged() {
let w = 16u32;
let h = 10u32;
let mut img = make_gradient(w, h);
apply_warp_rectilinear(&mut img, [-0.01, 0.0, 0.0, 0.0], 0.5, 0.5);
assert_eq!(img.width(), w, "width must not change after warp");
assert_eq!(img.height(), h, "height must not change after warp");
assert_eq!(img.data.len(), (w as usize) * (h as usize) * 3);
}
#[test]
fn test_warp_center_pixel_unchanged() {
let w = 11u32; let h = 11u32;
let mut img = make_rgb(w, h, 32768);
let cx_idx = (h as usize / 2) * (w as usize) + (w as usize / 2);
apply_warp_rectilinear(&mut img, [-0.05, 0.002, 0.0, 0.0], 0.5, 0.5);
assert_eq!(img.data[cx_idx * 3], 32768, "R of centre pixel");
assert_eq!(img.data[cx_idx * 3 + 1], 32768, "G of centre pixel");
assert_eq!(img.data[cx_idx * 3 + 2], 32768, "B of centre pixel");
}
#[test]
fn test_warp_no_crash_small_image() {
let mut img = make_rgb(2, 2, 1000);
apply_warp_rectilinear(&mut img, [-0.1, 0.05, -0.01, 0.001], 0.5, 0.5);
assert_eq!(img.width(), 2);
assert_eq!(img.height(), 2);
}
#[test]
fn test_warp_no_crash_1x1_image() {
let mut img = make_rgb(1, 1, 5000);
apply_warp_rectilinear(&mut img, [-0.1, 0.0, 0.0, 0.0], 0.5, 0.5);
assert_eq!(img.data[0], 5000);
}
#[test]
fn test_warp_tangential_zero_unchanged() {
let original = make_gradient(8, 8);
let mut img = original.clone();
apply_warp_rectilinear_tangential(&mut img, [0.0; 4], [0.0; 2], 0.5, 0.5);
assert_eq!(
img.data, original.data,
"all-zero tangential coefficients must leave image unchanged"
);
}
#[test]
fn test_warp_tangential_output_size_unchanged() {
let w = 12u32;
let h = 8u32;
let mut img = make_gradient(w, h);
apply_warp_rectilinear_tangential(
&mut img,
[-0.01, 0.0, 0.0, 0.0],
[0.001, -0.001],
0.5,
0.5,
);
assert_eq!(img.width(), w);
assert_eq!(img.height(), h);
}
#[test]
fn test_warp_uniform_image_stays_uniform() {
let mut img = make_rgb(10, 10, 8000);
apply_warp_rectilinear(&mut img, [-0.05, 0.02, -0.005, 0.001], 0.5, 0.5);
assert!(
img.data.iter().all(|&v| v == 8000),
"uniform image must remain uniform"
);
}
}