1use super::stats::LayerStats;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum Kind {
14 Opaque,
15 Veil,
16}
17
18#[derive(Clone, Debug)]
19pub struct Object {
20 pub kind: Kind,
21 pub bx: usize,
23 pub by: usize,
24 pub bw: usize,
25 pub bh: usize,
26 pub mask: Vec<bool>,
28 pub tpl: Vec<[u8; 3]>,
30 pub stable: Vec<u32>,
32 pub coarse: Vec<u32>,
34 pub textured: bool,
36 pub last: (i32, i32),
38 pub flat: Option<[u8; 3]>,
41}
42
43impl Object {
44 pub fn area(&self) -> usize {
45 self.mask.iter().filter(|&&m| m).count()
46 }
47
48 pub fn zone(&self, cw: usize, ch: usize) -> (usize, usize, usize, usize) {
51 let (bx0, bx1) = (self.bx.saturating_sub(1), (self.bx + self.bw + 1).min(cw));
52 let (by0, by1) = (self.by.saturating_sub(1), (self.by + self.bh + 1).min(ch));
53 if self.bh >= 3 * self.bw {
54 (bx0, bx1, 0, ch)
55 } else if self.bw >= 3 * self.bh {
56 (0, cw, by0, by1)
57 } else {
58 (bx0, bx1, by0, by1)
59 }
60 }
61}
62
63impl LayerModel {
64 pub fn overlay_zone(&self) -> Vec<bool> {
67 let mut z = vec![false; self.cw * self.ch];
68 for o in self.objects.iter().filter(|o| o.kind == Kind::Opaque) {
69 let (x0, x1, y0, y1) = o.zone(self.cw, self.ch);
70 for y in y0..y1 {
71 for x in x0..x1 {
72 z[y * self.cw + x] = true;
73 }
74 }
75 }
76 z
77 }
78}
79
80#[derive(Clone, Debug)]
81pub struct LayerModel {
82 pub x: i64,
83 pub y: i64,
84 pub cw: usize,
85 pub ch: usize,
86 pub objects: Vec<Object>,
87}
88
89const ENOUGH: u16 = 8;
91const POOL: usize = 1; fn pooled(v: &[u32], cw: usize, ch: usize) -> Vec<u64> {
94 let mut rows = vec![0u64; cw * ch];
96 for y in 0..ch {
97 for x in 0..cw {
98 let (a, b) = (x.saturating_sub(POOL), (x + POOL).min(cw - 1));
99 rows[y * cw + x] = (a..=b).map(|i| v[y * cw + i] as u64).sum();
100 }
101 }
102 let mut out = vec![0u64; cw * ch];
103 for y in 0..ch {
104 let (a, b) = (y.saturating_sub(POOL), (y + POOL).min(ch - 1));
105 for x in 0..cw {
106 out[y * cw + x] = (a..=b).map(|j| rows[j * cw + x]).sum();
107 }
108 }
109 out
110}
111
112fn dilate(m: &[bool], cw: usize, ch: usize) -> Vec<bool> {
113 let mut out = vec![false; m.len()];
114 for y in 0..ch {
115 for x in 0..cw {
116 if !m[y * cw + x] {
117 continue;
118 }
119 for yy in y.saturating_sub(1)..=(y + 1).min(ch - 1) {
120 for xx in x.saturating_sub(1)..=(x + 1).min(cw - 1) {
121 out[yy * cw + xx] = true;
122 }
123 }
124 }
125 }
126 out
127}
128
129fn erode(m: &[bool], cw: usize, ch: usize) -> Vec<bool> {
130 let inv: Vec<bool> = m.iter().map(|&b| !b).collect();
131 dilate(&inv, cw, ch).iter().map(|&b| !b).collect()
132}
133
134fn components(m: &[bool], cw: usize, ch: usize) -> (Vec<u32>, u32) {
136 let mut label = vec![0u32; m.len()];
137 let mut next = 0;
138 let mut stack = Vec::new();
139 for start in 0..m.len() {
140 if !m[start] || label[start] != 0 {
141 continue;
142 }
143 next += 1;
144 label[start] = next;
145 stack.push(start);
146 while let Some(i) = stack.pop() {
147 let (x, y) = (i % cw, i / cw);
148 for yy in y.saturating_sub(1)..=(y + 1).min(ch - 1) {
149 for xx in x.saturating_sub(1)..=(x + 1).min(cw - 1) {
150 let j = yy * cw + xx;
151 if m[j] && label[j] == 0 {
152 label[j] = next;
153 stack.push(j);
154 }
155 }
156 }
157 }
158 }
159 (label, next)
160}
161
162pub fn build(stats: &LayerStats, tau: i32) -> LayerModel {
164 let (cw, ch) = (stats.cw, stats.ch);
165 let len = cw * ch;
166 let n = pooled(&stats.field(|a| a.n as u32), cw, ch);
167 let bad = pooled(&stats.field(|a| a.bad as u32), cw, ch);
168 let sr = pooled(&stats.field(|a| a.sr), cw, ch);
169 let so = pooled(&stats.field(|a| a.so), cw, ch);
170 let srr = pooled(&stats.field(|a| a.srr), cw, ch);
171 let sro = pooled(&stats.field(|a| a.sro), cw, ch);
172 let soo = pooled(&stats.field(|a| a.soo), cw, ch);
173 let mut opaque = vec![false; len];
174 let mut veil = vec![false; len];
175 for i in 0..len {
176 if stats.cells[i].n < ENOUGH {
177 continue;
178 }
179 let nn = n[i].max(1) as f64;
180 let frac = bad[i] as f64 / nn;
181 let (mr, mo) = (sr[i] as f64 / nn, so[i] as f64 / nn);
182 let vr = srr[i] as f64 / nn - mr * mr;
183 let cov = sro[i] as f64 / nn - mr * mo;
184 let vo = soo[i] as f64 / nn - mo * mo;
185 let hi = if vr > 1.0 {
187 let slope = cov / vr;
188 let resid = (vo - cov * cov / vr).max(0.0);
189 let se = ((resid + 4.0) / (nn * vr)).sqrt();
190 slope + 2.0 * se
191 } else {
192 f64::INFINITY
193 };
194 if frac > 0.3 || hi < 0.15 {
195 opaque[i] = true;
196 } else if hi < 0.85 {
197 veil[i] = true;
198 }
199 }
200 let enough: Vec<bool> = stats.cells.iter().map(|a| a.n >= ENOUGH).collect();
201 let core: Vec<bool> = erode(&dilate(&opaque, cw, ch), cw, ch)
202 .iter()
203 .zip(&enough)
204 .map(|(&a, &b)| a && b)
205 .collect();
206 let grown: Vec<bool> = dilate(&core, cw, ch)
207 .iter()
208 .zip(&enough)
209 .map(|(&a, &b)| a && b)
210 .collect();
211 let veil: Vec<bool> = erode(&dilate(&veil, cw, ch), cw, ch)
212 .iter()
213 .zip(&enough)
214 .zip(&grown)
215 .map(|((&a, &b), &g)| a && b && !g)
216 .collect();
217 let mut objects = Vec::new();
218 for (kind, mask) in [(Kind::Opaque, &grown), (Kind::Veil, &veil)] {
219 let (label, count) = components(mask, cw, ch);
220 let mut boxes = vec![(usize::MAX, usize::MAX, 0usize, 0usize, 0usize); count as usize];
221 for (i, &l) in label.iter().enumerate() {
222 if l == 0 {
223 continue;
224 }
225 let b = &mut boxes[l as usize - 1];
226 let (x, y) = (i % cw, i / cw);
227 b.0 = b.0.min(x);
228 b.1 = b.1.min(y);
229 b.2 = b.2.max(x);
230 b.3 = b.3.max(y);
231 b.4 += 1;
232 }
233 for (k, &(x0, y0, x1, y1, area)) in boxes.iter().enumerate() {
234 if area < 3 {
236 continue;
237 }
238 let (bw, bh) = (x1 - x0 + 1, y1 - y0 + 1);
239 let mut m = vec![false; bw * bh];
240 let mut tpl = vec![[0u8; 3]; bw * bh];
241 let mut stable = Vec::new();
242 for yy in 0..bh {
243 for xx in 0..bw {
244 let i = (y0 + yy) * cw + x0 + xx;
245 if label[i] != k as u32 + 1 {
246 continue;
247 }
248 let j = yy * bw + xx;
249 m[j] = true;
250 let a = stats.cells[i];
251 let nb = a.bad as u32;
252 if nb == 0 {
253 continue;
254 }
255 tpl[j] = [
256 ((a.br + nb / 2) / nb) as u8,
257 ((a.bg + nb / 2) / nb) as u8,
258 ((a.bb + nb / 2) / nb) as u8,
259 ];
260 let ml = a.bl as f64 / nb as f64;
261 let spread = (a.bll as f64 / nb as f64 - ml * ml).max(0.0).sqrt();
262 if kind == Kind::Opaque && core[i] && nb >= 4 && spread <= tau as f64 / 2.0 {
264 stable.push(j as u32);
265 }
266 }
267 }
268 let edges = stable
269 .iter()
270 .filter(|&&j| {
271 let j = j as usize;
272 let (xx, yy) = (j % bw, j / bw);
273 let l = |a: usize| {
274 let t = tpl[a];
275 super::luma(t[0], t[1], t[2]) as i32
276 };
277 let c = l(j);
278 (xx > 0 && (c - l(j - 1)).abs() > 16) || (yy > 0 && (c - l(j - bw)).abs() > 16)
279 })
280 .count();
281 let step = (stable.len() / 2000).max(1);
282 let coarse = stable.iter().step_by(step).copied().collect();
283 let flat = (stable.len() >= 4)
285 .then(|| {
286 let mut ls: Vec<(u32, [u8; 3])> = stable
287 .iter()
288 .map(|&j| {
289 let t = tpl[j as usize];
290 (super::luma(t[0], t[1], t[2]), t)
291 })
292 .collect();
293 ls.sort_by_key(|p| p.0);
294 let med = ls[ls.len() / 2].1;
295 let near = ls
296 .iter()
297 .filter(|(_, t)| (0..3).all(|k| (t[k] as i32 - med[k] as i32).abs() <= tau))
298 .count();
299 (near * 10 >= ls.len() * 6).then_some(med)
300 })
301 .flatten();
302 objects.push(Object {
303 kind,
304 bx: x0,
305 by: y0,
306 bw,
307 bh,
308 mask: m,
309 tpl,
310 textured: edges >= 10,
311 stable,
312 coarse,
313 last: (0, 0),
314 flat,
315 });
316 }
317 }
318 LayerModel {
319 x: stats.x,
320 y: stats.y,
321 cw,
322 ch,
323 objects,
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330 use crate::kernels::reveal::locate::tiers;
331 use crate::kernels::reveal::stats::CellRef;
332 use crate::kernels::reveal::{Footprint, Frame, TIER_CLEAN};
333
334 struct Page(u8);
335 impl CellRef for Page {
336 fn cell(&self, _: i64, _: i64) -> Option<[u8; 3]> {
337 Some([self.0; 3])
338 }
339 fn cells(&self) -> (i64, i64, i64, i64) {
340 (0, 0, 48, 32)
341 }
342 }
343
344 fn button(page: u8, ink: u8) -> Vec<u8> {
345 let mut rgba = vec![255; 96 * 64 * 4];
346 for y in 0..64 {
347 for x in 0..96 {
348 let v = if (16..80).contains(&x) && (16..48).contains(&y) {
349 if (32..64).contains(&x) && (24..40).contains(&y) {
350 ink
351 } else {
352 240
353 }
354 } else {
355 page
356 };
357 let i = (y * 96 + x) * 4;
358 rgba[i..i + 3].fill(v);
359 }
360 }
361 rgba
362 }
363
364 #[test]
367 fn a_changed_label_on_an_opaque_button_is_not_clean_page_content() {
368 let mut stats = LayerStats::new(0, 0, 96, 64);
369 let mask = vec![1; 96 * 64];
370 let footprint = Footprint {
371 x: 0,
372 y: 0,
373 width: 96,
374 height: 64,
375 mask: &mask,
376 };
377 for background in (0..=240).step_by(16) {
378 let rgba = button(background, 0);
379 stats.accumulate_cells(
380 &Frame {
381 rgba: &rgba,
382 width: 96,
383 height: 64,
384 },
385 &footprint,
386 &Page(background),
387 (0, 0),
388 24,
389 );
390 }
391 let mut model = build(&stats, 24);
392 let rgba = button(128, 80);
393 let tier = tiers(
394 &Frame {
395 rgba: &rgba,
396 width: 96,
397 height: 64,
398 },
399 &mut model,
400 24,
401 );
402 for y in 10..22 {
403 for x in 10..38 {
404 assert!(
405 tier[y * 48 + x] < TIER_CLEAN,
406 "button cell ({x}, {y}) treated as page"
407 );
408 }
409 }
410 assert_eq!(tier[0], TIER_CLEAN);
411 }
412}