1use crate::error::ImageError;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Interpolation {
8 Nearest,
10 Bilinear,
12 Bicubic,
14 Lanczos,
16}
17
18pub fn resize(
24 image: &[f32],
25 src_w: usize,
26 src_h: usize,
27 dst_w: usize,
28 dst_h: usize,
29 method: Interpolation,
30) -> Result<Vec<f32>, ImageError> {
31 if image.len() != src_w * src_h {
32 return Err(ImageError::BufferLengthMismatch {
33 expected: src_w * src_h,
34 got: image.len(),
35 width: src_w,
36 height: src_h,
37 });
38 }
39 if dst_w == 0 || dst_h == 0 {
40 return Err(ImageError::ZeroDimension {
41 width: dst_w,
42 height: dst_h,
43 });
44 }
45
46 let mut output = vec![0.0f32; dst_w * dst_h];
47
48 let scale_x = src_w as f32 / dst_w as f32;
49 let scale_y = src_h as f32 / dst_h as f32;
50
51 for dy in 0..dst_h {
52 for dx in 0..dst_w {
53 let sx = (dx as f32 + 0.5) * scale_x - 0.5;
54 let sy = (dy as f32 + 0.5) * scale_y - 0.5;
55
56 output[dy * dst_w + dx] = match method {
57 Interpolation::Nearest => {
58 let ix = (sx + 0.5) as usize;
59 let iy = (sy + 0.5) as usize;
60 let ix = ix.min(src_w - 1);
61 let iy = iy.min(src_h - 1);
62 image[iy * src_w + ix]
63 }
64 Interpolation::Bilinear => bilinear_sample(image, src_w, src_h, sx, sy),
65 Interpolation::Bicubic => bicubic_sample(image, src_w, src_h, sx, sy),
66 Interpolation::Lanczos => lanczos_sample(image, src_w, src_h, sx, sy),
67 };
68 }
69 }
70
71 Ok(output)
72}
73
74#[inline]
76fn clamp_idx(i: isize, size: usize) -> usize {
77 i.clamp(0, size as isize - 1) as usize
78}
79
80fn bilinear_sample(image: &[f32], w: usize, h: usize, x: f32, y: f32) -> f32 {
82 let x0 = (x.floor() as isize).max(0) as usize;
83 let y0 = (y.floor() as isize).max(0) as usize;
84 let x1 = (x0 + 1).min(w - 1);
85 let y1 = (y0 + 1).min(h - 1);
86
87 let fx = (x - x0 as f32).clamp(0.0, 1.0);
88 let fy = (y - y0 as f32).clamp(0.0, 1.0);
89
90 let p00 = image[y0 * w + x0];
91 let p10 = image[y0 * w + x1];
92 let p01 = image[y1 * w + x0];
93 let p11 = image[y1 * w + x1];
94
95 p00 * (1.0 - fx) * (1.0 - fy) + p10 * fx * (1.0 - fy) + p01 * (1.0 - fx) * fy + p11 * fx * fy
96}
97
98#[inline]
100fn cubic_weight(t: f32) -> f32 {
101 let t = t.abs();
102 if t <= 1.0 {
103 (1.5 * t - 2.5) * t * t + 1.0
104 } else if t < 2.0 {
105 ((-0.5 * t + 2.5) * t - 4.0) * t + 2.0
106 } else {
107 0.0
108 }
109}
110
111fn bicubic_sample(image: &[f32], w: usize, h: usize, x: f32, y: f32) -> f32 {
113 let ix = x.floor() as isize;
114 let iy = y.floor() as isize;
115 let fx = x - ix as f32;
116 let fy = y - iy as f32;
117
118 let mut sum = 0.0f64;
119 for j in -1..=2_isize {
120 let wy = cubic_weight(fy - j as f32) as f64;
121 let cy = clamp_idx(iy + j, h);
122 for i in -1..=2_isize {
123 let wx = cubic_weight(fx - i as f32) as f64;
124 let cx = clamp_idx(ix + i, w);
125 sum += wy * wx * f64::from(image[cy * w + cx]);
126 }
127 }
128 sum as f32
129}
130
131#[inline]
133fn lanczos_weight(t: f32) -> f32 {
134 let t = t.abs();
135 if t < 1e-7 {
136 1.0
137 } else if t < 3.0 {
138 let pi_t = std::f32::consts::PI * t;
139 let pi_t_over_a = pi_t / 3.0;
140 (pi_t.sin() * pi_t_over_a.sin()) / (pi_t * pi_t_over_a)
141 } else {
142 0.0
143 }
144}
145
146fn lanczos_sample(image: &[f32], w: usize, h: usize, x: f32, y: f32) -> f32 {
148 let ix = x.floor() as isize;
149 let iy = y.floor() as isize;
150 let fx = x - ix as f32;
151 let fy = y - iy as f32;
152
153 let mut sum = 0.0f64;
154 let mut weight_sum = 0.0f64;
155 for j in -2..=3_isize {
156 let wy = lanczos_weight(fy - j as f32) as f64;
157 let cy = clamp_idx(iy + j, h);
158 for i in -2..=3_isize {
159 let wx = lanczos_weight(fx - i as f32) as f64;
160 let cx = clamp_idx(ix + i, w);
161 let w_total = wy * wx;
162 sum += w_total * f64::from(image[cy * w + cx]);
163 weight_sum += w_total;
164 }
165 }
166 if weight_sum.abs() > 1e-12 {
167 (sum / weight_sum) as f32
168 } else {
169 0.0
170 }
171}