1use crate::kernels::pool::SyncPtr;
4
5#[inline]
7pub fn luma(r: u8, g: u8, b: u8) -> u8 {
8 ((r as u32 * 77 + g as u32 * 150 + b as u32 * 29) >> 8) as u8
9}
10
11pub fn grayscale(rgba: &[u8], out: &mut [u8]) {
12 let n = out.len().min(rgba.len() / 4);
13 let chunks = crate::kernels::pool::chunks_for(n, 256 * 1024);
14 let dst = SyncPtr(out.as_mut_ptr());
15 crate::kernels::pool::par_for(chunks, |c| {
16 let (a, b) = (
17 crate::kernels::pool::split(n, chunks, c),
18 crate::kernels::pool::split(n, chunks, c + 1),
19 );
20 let out = unsafe { std::slice::from_raw_parts_mut(dst.get().add(a), b - a) };
22 let mut sums = [0u32; 64];
23 let mut i = a;
24 while i < b {
25 let m = (b - i).min(64);
26 weighted_luma(&rgba[i * 4..(i + m) * 4], &mut sums[..m]);
27 for (o, s) in out[i - a..i - a + m].iter_mut().zip(&sums[..m]) {
28 *o = (s >> 8) as u8;
29 }
30 i += m;
31 }
32 });
33}
34
35#[inline]
37pub fn weighted_luma(rgba: &[u8], out: &mut [u32]) {
38 let n = out.len();
39 #[allow(unused_mut)]
40 let mut i = 0;
41 #[cfg(target_feature = "simd128")]
42 {
43 use core::arch::wasm32::*;
44 let weights = i16x8(77, 150, 29, 0, 77, 150, 29, 0);
46 while i + 4 <= n {
47 let v = unsafe { v128_load(rgba.as_ptr().add(i * 4) as *const v128) };
49 let lo = i32x4_dot_i16x8(u16x8_extend_low_u8x16(v), weights);
50 let hi = i32x4_dot_i16x8(u16x8_extend_high_u8x16(v), weights);
51 let sums = i32x4_add(
52 i32x4_shuffle::<0, 2, 4, 6>(lo, hi),
53 i32x4_shuffle::<1, 3, 5, 7>(lo, hi),
54 );
55 unsafe { v128_store(out.as_mut_ptr().add(i) as *mut v128, sums) };
57 i += 4;
58 }
59 }
60 for k in i..n {
61 let p = &rgba[k * 4..k * 4 + 3];
62 out[k] = p[0] as u32 * 77 + p[1] as u32 * 150 + p[2] as u32 * 29;
63 }
64}
65
66#[inline]
68pub fn downscaled_size(width: usize, height: usize, factor: usize) -> (usize, usize) {
69 (
70 width.div_ceil(factor).max(1),
71 height.div_ceil(factor).max(1),
72 )
73}
74
75pub fn downscale_gray(rgba: &[u8], width: usize, height: usize, factor: usize, out: &mut [u8]) {
81 let (ow, oh) = downscaled_size(width, height, factor);
82 debug_assert!(out.len() >= ow * oh);
83 if factor == 1 {
84 grayscale(&rgba[..width * height * 4], &mut out[..width * height]);
85 return;
86 }
87 let chunks = crate::kernels::pool::chunks_for(oh * width * factor, 128 * 1024);
90 let mut scratch = vec![0u64; chunks * ow];
91 let mut sums = vec![0u32; chunks * width];
92 let (acc_ptr, sum_ptr, dst) = (
93 SyncPtr(scratch.as_mut_ptr()),
94 SyncPtr(sums.as_mut_ptr()),
95 SyncPtr(out.as_mut_ptr()),
96 );
97 crate::kernels::pool::par_for(chunks, |c| {
98 let (acc, line) = unsafe {
100 (
101 std::slice::from_raw_parts_mut(acc_ptr.get().add(c * ow), ow),
102 std::slice::from_raw_parts_mut(sum_ptr.get().add(c * width), width),
103 )
104 };
105 let full = width / factor;
106 for y in crate::kernels::pool::split(oh, chunks, c)
107 ..crate::kernels::pool::split(oh, chunks, c + 1)
108 {
109 let bh = factor.min(height - y * factor);
110 acc.fill(0);
111 for row in 0..bh {
112 let start = (y * factor + row) * width * 4;
113 weighted_luma(&rgba[start..start + width * 4], line);
114 for (bx, cell) in line[..full * factor].chunks_exact(factor).enumerate() {
116 acc[bx] += cell.iter().sum::<u32>() as u64;
117 }
118 if full < ow {
119 acc[full] += line[full * factor..].iter().sum::<u32>() as u64;
120 }
121 }
122 let out_row = unsafe { std::slice::from_raw_parts_mut(dst.get().add(y * ow), ow) };
123 for (x, o) in out_row.iter_mut().enumerate() {
124 let bw = factor.min(width - x * factor);
125 *o = ((acc[x] / (bw * bh) as u64) >> 8) as u8;
126 }
127 }
128 });
129}
130
131#[inline]
133fn clamp_u8(value: f64) -> u8 {
134 if value.is_nan() || value <= 0.0 {
135 0
136 } else if value >= 255.0 {
137 255
138 } else {
139 value.round_ties_even() as u8
140 }
141}
142
143pub fn halve_rgba(rgba: &[u8], width: usize, height: usize, out: &mut [u8]) -> (usize, usize) {
145 let ow = (width >> 1).max(1);
146 let oh = (height >> 1).max(1);
147 for y in 0..oh {
148 for x in 0..ow {
149 let (mut r, mut g, mut b, mut a) = (0u32, 0u32, 0u32, 0u32);
150 for j in 0..2 {
151 for k in 0..2 {
152 let sx = (x * 2 + k).min(width - 1);
153 let sy = (y * 2 + j).min(height - 1);
154 let i = (sy * width + sx) * 4;
155 let w = rgba[i + 3] as u32;
156 r += rgba[i] as u32 * w;
157 g += rgba[i + 1] as u32 * w;
158 b += rgba[i + 2] as u32 * w;
159 a += w;
160 }
161 }
162 let o = (y * ow + x) * 4;
163 if a > 0 {
164 out[o] = clamp_u8(r as f64 / a as f64);
165 out[o + 1] = clamp_u8(g as f64 / a as f64);
166 out[o + 2] = clamp_u8(b as f64 / a as f64);
167 out[o + 3] = clamp_u8(a as f64 / 4.0);
168 } else {
169 out[o..o + 4].fill(0);
170 }
171 }
172 }
173 (ow, oh)
174}
175
176pub fn mean_difference(a: &[u8], b: &[u8]) -> f64 {
178 if a.len() != b.len() || a.is_empty() {
179 return 255.0;
180 }
181 let sum: u64 = a.iter().zip(b).map(|(x, y)| x.abs_diff(*y) as u64).sum();
182 sum as f64 / a.len() as f64
183}
184
185#[allow(clippy::too_many_arguments)]
189pub fn fixed_update(
190 saved: &mut [u8],
191 rgba: &[u8],
192 labels: &[u8],
193 width: usize,
194 height: usize,
195 x0: i64,
196 y0: i64,
197 rw: usize,
198 rh: usize,
199 code: u8,
200) -> bool {
201 let (w, h) = (width as i64, height as i64);
202 let (lo, hi) = (x0.max(0), (x0 + rw as i64).min(w));
203 if hi <= lo {
204 return false;
205 }
206 let chunks = crate::kernels::pool::chunks_for(rh * rw, 64 * 1024);
207 let mut changed = vec![false; chunks];
208 let (dst, flags) = (SyncPtr(saved.as_mut_ptr()), SyncPtr(changed.as_mut_ptr()));
209 crate::kernels::pool::par_for(chunks, |c| {
210 let mut any = false;
211 for y in crate::kernels::pool::split(rh, chunks, c)
212 ..crate::kernels::pool::split(rh, chunks, c + 1)
213 {
214 let ny = y0 + y as i64;
215 if ny < 0 || ny >= h {
216 continue;
217 }
218 let row = unsafe { std::slice::from_raw_parts_mut(dst.get().add(y * rw * 4), rw * 4) };
220 let base = ny as usize * width;
221 for nx in lo..hi {
222 let i = base + nx as usize;
223 if labels[i] != code {
224 continue;
225 }
226 let j = (nx - x0) as usize * 4;
227 let src = &rgba[i * 4..i * 4 + 4];
228 if row[j..j + 4] != *src {
229 row[j..j + 4].copy_from_slice(src);
230 any = true;
231 }
232 }
233 }
234 unsafe { *flags.get().add(c) = any };
236 });
237 changed.iter().any(|&c| c)
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn downscale_keeps_partial_cells() {
246 let (w, h) = (5usize, 3usize);
247 let rgba: Vec<u8> = (0..w * h)
248 .flat_map(|i| [(i * 40) as u8, 0, 0, 255])
249 .collect();
250 let (ow, oh) = downscaled_size(w, h, 2);
251 assert_eq!((ow, oh), (3, 2));
252 let mut out = vec![0; ow * oh];
253 downscale_gray(&rgba, w, h, 2, &mut out);
254 let cell = |a: u64, b: u64| (((a * 77 + b * 77) / 2) >> 8) as u8;
257 assert_eq!(out[2], cell(160, 104));
258 assert_eq!(out[3], cell((10 * 40) % 256, (11 * 40) % 256));
259 assert_eq!(out[5], ((((14 * 40) % 256) as u64 * 77) >> 8) as u8);
260 }
261
262 #[test]
263 fn clamped_rounding_is_half_to_even() {
264 assert_eq!(clamp_u8(0.5), 0);
265 assert_eq!(clamp_u8(1.5), 2);
266 assert_eq!(clamp_u8(2.5), 2);
267 assert_eq!(clamp_u8(300.0), 255);
268 }
269}