1#[derive(Clone, Debug)]
8pub struct DebandConfig {
9 pub enabled: bool,
11 pub threshold: f32,
13 pub range: u32,
15 pub seed: u32,
17}
18
19impl Default for DebandConfig {
20 fn default() -> Self {
21 Self {
22 enabled: true,
23 threshold: 64.0,
24 range: 15,
25 seed: 0x1337,
26 }
27 }
28}
29
30pub fn apply_deband(rgba: &[u8], width: usize, height: usize, config: &DebandConfig) -> Vec<u8> {
34 if !config.enabled || width == 0 || height == 0 {
35 return rgba.to_vec();
36 }
37
38 let mut output = vec![0u8; rgba.len()];
39 let range = config.range as i32;
40
41 for y in 0..height {
42 for x in 0..width {
43 let idx = (y * width + x) * 4;
44 let src = read_pixel(rgba, idx);
45
46 if src[3] == 0 {
48 write_pixel(&mut output, idx, src);
49 continue;
50 }
51
52 let (ox, oy) = sample_offset(config.seed, x as u32, y as u32, range);
54
55 let refs = sample_cross(rgba, width, height, x, y, ox, oy);
57
58 let blended = blend_deband(src, refs, config.threshold);
60 write_pixel(&mut output, idx, blended);
61 }
62 }
63
64 output
65}
66
67#[inline]
69fn read_pixel(rgba: &[u8], idx: usize) -> [u8; 4] {
70 [rgba[idx], rgba[idx + 1], rgba[idx + 2], rgba[idx + 3]]
71}
72
73#[inline]
75fn write_pixel(output: &mut [u8], idx: usize, pixel: [u8; 4]) {
76 output[idx..idx + 4].copy_from_slice(&pixel);
77}
78
79fn sample_offset(seed: u32, x: u32, y: u32, range: i32) -> (i32, i32) {
81 let hash = seed
83 .wrapping_mul(0x9E3779B9)
84 .wrapping_add(x.wrapping_mul(0x85EBCA6B))
85 .wrapping_add(y.wrapping_mul(0xC2B2AE35));
86
87 let ox = ((hash & 0xFFFF) as i32 % (range * 2 + 1)) - range;
88 let oy = (((hash >> 16) & 0xFFFF) as i32 % (range * 2 + 1)) - range;
89
90 (ox.max(1), oy.max(1)) }
92
93fn sample_cross(
96 rgba: &[u8],
97 width: usize,
98 height: usize,
99 x: usize,
100 y: usize,
101 ox: i32,
102 oy: i32,
103) -> [[u8; 4]; 4] {
104 let sample_at = |dx: i32, dy: i32| -> [u8; 4] {
105 let nx = (x as i32 + dx).clamp(0, width as i32 - 1) as usize;
106 let ny = (y as i32 + dy).clamp(0, height as i32 - 1) as usize;
107 let idx = (ny * width + nx) * 4;
108 read_pixel(rgba, idx)
109 };
110
111 [
112 sample_at(0, -oy), sample_at(0, oy), sample_at(-ox, 0), sample_at(ox, 0), ]
117}
118
119fn blend_deband(src: [u8; 4], refs: [[u8; 4]; 4], threshold: f32) -> [u8; 4] {
121 let mut result = [0u8; 4];
122
123 for c in 0..3 {
125 let s = src[c] as f32;
126 let r: [f32; 4] = [
127 refs[0][c] as f32,
128 refs[1][c] as f32,
129 refs[2][c] as f32,
130 refs[3][c] as f32,
131 ];
132
133 let avg = (r[0] + r[1] + r[2] + r[3]) * 0.25;
135
136 let avg_dif = (avg - s).abs();
138 let max_dif = r.iter().map(|&v| (v - s).abs()).fold(0.0f32, f32::max);
139 let mid_dif_v = ((r[0] + r[1]) * 0.5 - s).abs(); let mid_dif_h = ((r[2] + r[3]) * 0.5 - s).abs(); let factor = compute_factor(avg_dif, max_dif, mid_dif_v, mid_dif_h, threshold);
144
145 let blended = s + (avg - s) * factor;
147 result[c] = blended.clamp(0.0, 255.0) as u8;
148 }
149
150 result[3] = src[3];
152 result
153}
154
155#[inline]
157fn compute_factor(avg_dif: f32, max_dif: f32, mid_v: f32, mid_h: f32, thresh: f32) -> f32 {
158 let saturate = |x: f32| x.clamp(0.0, 1.0);
159
160 let t1 = thresh;
161 let t2 = thresh * 0.75; let f1 = saturate(3.0 * (1.0 - avg_dif / t1));
164 let f2 = saturate(3.0 * (1.0 - max_dif / t1));
165 let f3 = saturate(3.0 * (1.0 - mid_v / t2));
166 let f4 = saturate(3.0 * (1.0 - mid_h / t2));
167
168 (f1 * f2 * f3 * f4).powf(0.1)
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_default_config() {
178 let config = DebandConfig::default();
179 assert!(config.enabled);
180 assert_eq!(config.threshold, 64.0);
181 assert_eq!(config.range, 15);
182 }
183
184 #[test]
185 fn test_disabled_passthrough() {
186 let rgba = vec![255, 0, 0, 255, 0, 255, 0, 255];
187 let config = DebandConfig {
188 enabled: false,
189 ..Default::default()
190 };
191 let result = apply_deband(&rgba, 2, 1, &config);
192 assert_eq!(result, rgba);
193 }
194
195 #[test]
196 fn test_transparent_skip() {
197 let rgba = vec![255, 0, 0, 0]; let config = DebandConfig {
199 enabled: true,
200 ..Default::default()
201 };
202 let result = apply_deband(&rgba, 1, 1, &config);
203 assert_eq!(result[3], 0); }
205
206 #[test]
207 fn test_sample_offset_deterministic() {
208 let (ox1, oy1) = sample_offset(0x1337, 10, 20, 15);
209 let (ox2, oy2) = sample_offset(0x1337, 10, 20, 15);
210 assert_eq!((ox1, oy1), (ox2, oy2)); }
212}