1#[cfg(target_arch = "aarch64")]
16use std::arch::aarch64::*;
17
18#[cfg(target_arch = "x86_64")]
19use std::arch::x86_64::*;
20
21pub fn nearest_neighbor_downsample_rgba(
29 pixels: &[u8],
30 src_width: u32,
31 src_height: u32,
32 dst_width: u32,
33 dst_height: u32,
34) -> Vec<u8> {
35 let src_size = (src_width as usize)
36 .checked_mul(src_height as usize)
37 .and_then(|n| n.checked_mul(4))
38 .expect("source dimensions overflow");
39 assert!(pixels.len() >= src_size, "pixels buffer too small for source dimensions");
40
41 let dst_size = (dst_width as usize)
42 .checked_mul(dst_height as usize)
43 .and_then(|n| n.checked_mul(4))
44 .expect("destination dimensions overflow");
45
46 let mut result: Vec<u8> = vec![0u8; dst_size];
47 let src_stride = src_width as usize * 4;
48
49 let x_map: Vec<usize> = (0..dst_width)
51 .map(|tx| (tx as u64 * src_width as u64 / dst_width as u64) as usize * 4)
52 .collect();
53
54 #[cfg(target_arch = "aarch64")]
55 downsample_neon(pixels, &mut result, src_stride, src_height, dst_width, dst_height, &x_map);
56
57 #[cfg(target_arch = "x86_64")]
58 downsample_sse2(pixels, &mut result, src_stride, src_height, dst_width, dst_height, &x_map);
59
60 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
61 downsample_scalar(pixels, &mut result, src_stride, src_height, dst_width, dst_height, &x_map);
62
63 result
64}
65
66#[cfg(target_arch = "aarch64")]
72fn downsample_neon(
73 src: &[u8], dst: &mut [u8], src_stride: usize,
74 src_height: u32, dst_width: u32, dst_height: u32, x_map: &[usize],
75) {
76 unsafe {
77 let src_ptr = src.as_ptr();
78 let dst_ptr = dst.as_mut_ptr() as *mut u32;
79 let mut dst_idx = 0usize;
80 let chunks = dst_width as usize / 4;
81 let remainder = dst_width as usize % 4;
82
83 for ty in 0..dst_height {
84 let sy = (ty as u64 * src_height as u64 / dst_height as u64) as usize;
85 let row = src_ptr.add(sy * src_stride);
86
87 for cx in 0..chunks {
88 let base = cx * 4;
89 let p0 = std::ptr::read_unaligned(row.add(x_map[base]) as *const u32);
90 let p1 = std::ptr::read_unaligned(row.add(x_map[base + 1]) as *const u32);
91 let p2 = std::ptr::read_unaligned(row.add(x_map[base + 2]) as *const u32);
92 let p3 = std::ptr::read_unaligned(row.add(x_map[base + 3]) as *const u32);
93
94 let mut v = vdupq_n_u32(p0);
95 v = vsetq_lane_u32(p1, v, 1);
96 v = vsetq_lane_u32(p2, v, 2);
97 v = vsetq_lane_u32(p3, v, 3);
98 vst1q_u32(dst_ptr.add(dst_idx) as *mut u32, v);
99 dst_idx += 4;
100 }
101
102 for rx in 0..remainder {
103 std::ptr::write_unaligned(
104 dst_ptr.add(dst_idx),
105 std::ptr::read_unaligned(row.add(x_map[chunks * 4 + rx]) as *const u32),
106 );
107 dst_idx += 1;
108 }
109 }
110 }
111}
112
113#[cfg(target_arch = "x86_64")]
119fn downsample_sse2(
120 src: &[u8], dst: &mut [u8], src_stride: usize,
121 src_height: u32, dst_width: u32, dst_height: u32, x_map: &[usize],
122) {
123 unsafe {
124 let src_ptr = src.as_ptr();
125 let dst_ptr = dst.as_mut_ptr();
126 let mut dst_idx = 0usize;
127 let chunks = dst_width as usize / 4;
128 let remainder = dst_width as usize % 4;
129
130 for ty in 0..dst_height {
131 let sy = (ty as u64 * src_height as u64 / dst_height as u64) as usize;
132 let row = src_ptr.add(sy * src_stride);
133
134 for cx in 0..chunks {
135 let base = cx * 4;
136 let p0 = std::ptr::read_unaligned(row.add(x_map[base]) as *const i32);
137 let p1 = std::ptr::read_unaligned(row.add(x_map[base + 1]) as *const i32);
138 let p2 = std::ptr::read_unaligned(row.add(x_map[base + 2]) as *const i32);
139 let p3 = std::ptr::read_unaligned(row.add(x_map[base + 3]) as *const i32);
140
141 let v = _mm_set_epi32(p3, p2, p1, p0);
142 _mm_storeu_si128(dst_ptr.add(dst_idx * 4) as *mut __m128i, v);
143 dst_idx += 4;
144 }
145
146 for rx in 0..remainder {
147 let sx = x_map[chunks * 4 + rx];
148 std::ptr::write_unaligned(
149 dst_ptr.add(dst_idx * 4) as *mut u32,
150 std::ptr::read_unaligned(row.add(sx) as *const u32),
151 );
152 dst_idx += 1;
153 }
154 }
155 }
156}
157
158#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
164fn downsample_scalar(
165 src: &[u8], dst: &mut [u8], src_stride: usize,
166 src_height: u32, dst_width: u32, dst_height: u32, x_map: &[usize],
167) {
168 unsafe {
169 let src_ptr = src.as_ptr();
170 let dst_ptr = dst.as_mut_ptr() as *mut u32;
171 let mut dst_idx = 0usize;
172
173 for ty in 0..dst_height {
174 let sy = (ty as u64 * src_height as u64 / dst_height as u64) as usize;
175 let row = src_ptr.add(sy * src_stride);
176
177 for tx in 0..dst_width as usize {
178 std::ptr::write_unaligned(
179 dst_ptr.add(dst_idx),
180 std::ptr::read_unaligned(row.add(x_map[tx]) as *const u32),
181 );
182 dst_idx += 1;
183 }
184 }
185 }
186}
187
188#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn downsample_basic_red() {
198 let pixels: Vec<u8> = vec![255, 0, 0, 255].repeat(16);
200 let result = nearest_neighbor_downsample_rgba(&pixels, 4, 4, 2, 2);
201 assert_eq!(result.len(), 2 * 2 * 4);
202 for chunk in result.chunks(4) {
203 assert_eq!(chunk, &[255, 0, 0, 255]);
204 }
205 }
206
207 #[test]
208 fn downsample_identity() {
209 let pixels: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
211 let result = nearest_neighbor_downsample_rgba(&pixels, 2, 2, 2, 2);
212 assert_eq!(result, pixels);
213 }
214
215 #[test]
216 fn downsample_checkerboard() {
217 let red = [255u8, 0, 0, 255];
219 let blue = [0u8, 0, 255, 255];
220 let mut pixels = Vec::with_capacity(4 * 4 * 4);
221 for y in 0..4 {
222 for x in 0..4 {
223 if (x + y) % 2 == 0 { pixels.extend_from_slice(&red); }
224 else { pixels.extend_from_slice(&blue); }
225 }
226 }
227 let result = nearest_neighbor_downsample_rgba(&pixels, 4, 4, 2, 2);
228 assert_eq!(result.len(), 2 * 2 * 4);
229 assert_eq!(&result[0..4], &red);
231 assert_eq!(&result[4..8], &red);
233 }
234
235 #[test]
236 fn downsample_large_to_small() {
237 let pixels: Vec<u8> = vec![128, 64, 32, 255].repeat(100 * 100);
239 let result = nearest_neighbor_downsample_rgba(&pixels, 100, 100, 10, 10);
240 assert_eq!(result.len(), 10 * 10 * 4);
241 }
242
243 #[test]
244 fn downsample_non_power_of_two() {
245 let pixels: Vec<u8> = (0..7 * 5 * 4).map(|i| (i % 256) as u8).collect();
247 let result = nearest_neighbor_downsample_rgba(&pixels, 7, 5, 3, 2);
248 assert_eq!(result.len(), 3 * 2 * 4);
249 }
250
251 #[test]
252 #[should_panic(expected = "pixels buffer too small")]
253 fn downsample_undersized_buffer() {
254 let pixels = vec![0u8; 10]; nearest_neighbor_downsample_rgba(&pixels, 4, 4, 2, 2);
256 }
257}