1use crate::kernels::geometry::{js_ceil, js_floor, Rect};
12use crate::kernels::pool::SyncPtr;
13
14const CELL: usize = 8;
16const REACH: usize = 4;
19const PATCH_W: usize = 384;
23const PATCH: usize = 128;
24const HALO: usize = 2;
26
27#[inline]
28fn px(rgba: &[u8], i: usize) -> u32 {
29 u32::from_le_bytes([
30 rgba[i * 4],
31 rgba[i * 4 + 1],
32 rgba[i * 4 + 2],
33 rgba[i * 4 + 3],
34 ])
35}
36
37#[inline]
39fn distance(a: u32, b: u32) -> u32 {
40 let (a, b) = (a.to_le_bytes(), b.to_le_bytes());
41 a[0].abs_diff(b[0]) as u32 + a[1].abs_diff(b[1]) as u32 + a[2].abs_diff(b[2]) as u32
42}
43
44pub struct FixedHistory {
46 x0: i64,
47 y0: i64,
48 w: usize,
49 h: usize,
50 inside: Vec<bool>,
52 frame0: Vec<u32>,
53 current: Vec<u32>,
54 start: Vec<u32>,
55 longest: Vec<u32>,
56 length: Vec<u32>,
57 first_frame: Option<u32>,
59 closed: bool,
61}
62
63impl FixedHistory {
64 pub fn new(region: Rect) -> Self {
65 let (x0, y0) = (js_floor(region.x) as i64, js_floor(region.y) as i64);
66 let w = (js_ceil(region.x + region.width) as i64 - x0).max(0) as usize;
67 let h = (js_ceil(region.y + region.height) as i64 - y0).max(0) as usize;
68 let n = w * h;
69 Self {
70 x0,
71 y0,
72 w,
73 h,
74 inside: vec![false; n],
75 frame0: vec![0; n],
76 current: vec![0; n],
77 start: vec![0; n],
78 longest: vec![0; n],
79 length: vec![0; n],
80 first_frame: None,
81 closed: false,
82 }
83 }
84
85 pub fn bytes(region: Rect) -> f64 {
87 let w = (js_ceil(region.x + region.width) - js_floor(region.x)).max(0) as f64;
88 let h = (js_ceil(region.y + region.height) - js_floor(region.y)).max(0) as f64;
89 w * h * 21.0
90 }
91
92 pub fn first_frame(&self) -> Option<u32> {
94 self.first_frame
95 }
96
97 pub fn closed(&self) -> bool {
98 self.closed
99 }
100
101 pub fn frame0(&self) -> &[u32] {
103 &self.frame0
104 }
105
106 #[allow(clippy::too_many_arguments)]
112 pub fn update(
113 &mut self,
114 rgba: &[u8],
115 labels: &[u8],
116 consistent: &[u8],
117 width: usize,
118 height: usize,
119 code: u8,
120 frame: u32,
121 limit: u32,
122 ) -> usize {
123 if self.closed {
124 return 0;
125 }
126 let first = self.first_frame.is_none();
127 if first {
128 self.first_frame = Some(frame);
129 }
130 let (w, h, x0, y0) = (self.w, self.h, self.x0, self.y0);
131 let chunks = crate::kernels::pool::chunks_for(w * h, 64 * 1024);
132 let mut counts = vec![0usize; chunks];
133 let ptrs = (
134 SyncPtr(self.inside.as_mut_ptr()),
135 SyncPtr(self.frame0.as_mut_ptr()),
136 SyncPtr(self.current.as_mut_ptr()),
137 SyncPtr(self.start.as_mut_ptr()),
138 SyncPtr(self.longest.as_mut_ptr()),
139 SyncPtr(self.length.as_mut_ptr()),
140 SyncPtr(counts.as_mut_ptr()),
141 );
142 crate::kernels::pool::par_for(chunks, |c| {
143 let mut settled = 0usize;
144 for y in crate::kernels::pool::split(h, chunks, c)
145 ..crate::kernels::pool::split(h, chunks, c + 1)
146 {
147 let sy = y0 + y as i64;
148 if sy < 0 || sy >= height as i64 {
149 continue;
150 }
151 for x in 0..w {
152 let sx = x0 + x as i64;
153 if sx < 0 || sx >= width as i64 {
154 continue;
155 }
156 let src = sy as usize * width + sx as usize;
157 let i = y * w + x;
158 unsafe {
160 if first {
161 if labels[src] != code {
162 continue;
163 }
164 let v = px(rgba, src);
165 *ptrs.0.get().add(i) = true;
166 *ptrs.1.get().add(i) = v;
167 *ptrs.2.get().add(i) = v;
168 *ptrs.3.get().add(i) = frame;
169 *ptrs.4.get().add(i) = v;
170 continue;
171 }
172 if !*ptrs.0.get().add(i) {
173 continue;
174 }
175 let v = px(rgba, src);
176 let cur = *ptrs.2.get().add(i);
177 if distance(v, cur) > limit {
178 let run = frame - *ptrs.3.get().add(i);
179 if run > *ptrs.5.get().add(i) {
180 *ptrs.4.get().add(i) = cur;
181 *ptrs.5.get().add(i) = run;
182 }
183 *ptrs.2.get().add(i) = v;
184 *ptrs.3.get().add(i) = frame;
185 if consistent[src] != 0 {
186 settled += 1;
187 }
188 }
189 }
190 }
191 }
192 unsafe { *ptrs.6.get().add(c) = settled };
194 });
195 let settled: usize = counts.iter().sum();
196 let owned = self.inside.iter().filter(|&&b| b).count();
197 if !first && settled > (PATCH * PATCH).min(owned / 4) {
198 self.close(frame);
199 }
200 settled
201 }
202
203 pub fn close(&mut self, end: u32) {
206 if self.closed || self.first_frame.is_none() {
207 self.closed = true;
208 return;
209 }
210 for i in 0..self.inside.len() {
211 if !self.inside[i] {
212 continue;
213 }
214 let run = end.saturating_sub(self.start[i]);
215 if run > self.length[i] {
216 self.longest[i] = self.current[i];
217 self.length[i] = run;
218 }
219 }
220 self.closed = true;
221 }
222
223 pub fn first(&self, limit: u32) -> Vec<u32> {
228 let (w, h) = (self.w, self.h);
229 let mut out = self.frame0.clone();
230 let differs: Vec<bool> = (0..w * h)
231 .map(|i| self.inside[i] && distance(self.longest[i], self.frame0[i]) > limit)
232 .collect();
233 let (gw, gh) = (w.div_ceil(CELL), h.div_ceil(CELL));
234 let mut cells = vec![false; gw * gh];
235 for y in 0..h {
236 for x in 0..w {
237 if differs[y * w + x] {
238 cells[(y / CELL) * gw + x / CELL] = true;
239 }
240 }
241 }
242 let mut label = vec![usize::MAX; gw * gh];
244 let mut patches: Vec<(usize, usize, usize, usize)> = Vec::new();
245 for seed in 0..gw * gh {
246 if !cells[seed] || label[seed] != usize::MAX {
247 continue;
248 }
249 let id = patches.len();
250 let (sx, sy) = (seed % gw, seed / gw);
251 let mut bounds = (sx, sy, sx, sy);
252 label[seed] = id;
253 let mut queue = vec![seed];
254 while let Some(c) = queue.pop() {
255 let (cx, cy) = (c % gw, c / gw);
256 bounds = (
257 bounds.0.min(cx),
258 bounds.1.min(cy),
259 bounds.2.max(cx),
260 bounds.3.max(cy),
261 );
262 for ny in cy.saturating_sub(REACH)..(cy + REACH + 1).min(gh) {
263 for nx in cx.saturating_sub(REACH)..(cx + REACH + 1).min(gw) {
264 let n = ny * gw + nx;
265 if cells[n] && label[n] == usize::MAX {
266 label[n] = id;
267 queue.push(n);
268 }
269 }
270 }
271 }
272 patches.push(bounds);
273 }
274 let small: Vec<bool> = patches
275 .iter()
276 .map(|&(x0, y0, x1, y1)| {
277 (x1 - x0 + 1) * CELL <= PATCH_W && (y1 - y0 + 1) * CELL <= PATCH
278 })
279 .collect();
280 let mut fix = vec![false; gw * gh];
283 for c in 0..gw * gh {
284 if !cells[c] || !small[label[c]] {
285 continue;
286 }
287 let (cx, cy) = (c % gw, c / gw);
288 for ny in cy.saturating_sub(HALO)..(cy + HALO + 1).min(gh) {
289 for nx in cx.saturating_sub(HALO)..(cx + HALO + 1).min(gw) {
290 let n = ny * gw + nx;
291 if !cells[n] || small[label[n]] {
293 fix[n] = true;
294 }
295 }
296 }
297 }
298 for y in 0..h {
299 for x in 0..w {
300 let i = y * w + x;
301 if self.inside[i] && fix[(y / CELL) * gw + x / CELL] {
302 out[i] = self.longest[i];
303 }
304 }
305 }
306 out
307 }
308
309 pub fn paint(&self, first: &[u32], rgba: &mut [u8], width: usize, height: usize) -> bool {
312 let mut changed = false;
313 for y in 0..self.h {
314 let sy = self.y0 + y as i64;
315 if sy < 0 || sy >= height as i64 {
316 continue;
317 }
318 for x in 0..self.w {
319 let sx = self.x0 + x as i64;
320 let i = y * self.w + x;
321 if sx < 0 || sx >= width as i64 || !self.inside[i] {
322 continue;
323 }
324 let at = (sy as usize * width + sx as usize) * 4;
325 let v = first[i].to_le_bytes();
326 if rgba[at..at + 4] != v {
327 rgba[at..at + 4].copy_from_slice(&v);
328 changed = true;
329 }
330 }
331 }
332 changed
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339
340 const W: usize = 480;
341 const H: usize = 160;
342
343 fn region() -> Rect {
344 Rect {
345 x: 0.0,
346 y: 0.0,
347 width: W as f64,
348 height: H as f64,
349 }
350 }
351
352 fn frame(v: u8) -> Vec<u8> {
353 [v, v, v, 255].repeat(W * H)
354 }
355
356 fn fill(mut f: Vec<u8>, x: usize, y: usize, w: usize, h: usize, v: u8) -> Vec<u8> {
357 for yy in y..y + h {
358 for xx in x..x + w {
359 let i = (yy * W + xx) * 4;
360 f[i..i + 3].copy_from_slice(&[v, v, v]);
361 }
362 }
363 f
364 }
365
366 fn run(frames: &[Vec<u8>], labels: &[u8], consistent: u8) -> Vec<u32> {
368 let mut hist = FixedHistory::new(region());
369 let mask = vec![consistent; W * H];
370 for (i, f) in frames.iter().enumerate() {
371 hist.update(f, labels, &mask, W, H, 1, i as u32, 0);
372 }
373 hist.close(frames.len() as u32);
374 hist.first(0)
375 }
376
377 fn at(first: &[u32], x: usize, y: usize) -> u8 {
378 first[y * W + x].to_le_bytes()[0]
379 }
380
381 #[test]
382 fn a_pointer_parked_in_the_first_frame_is_replaced_by_what_it_covered() {
383 let pointer = || fill(frame(100), 20, 10, 18, 27, 250);
385 let mut frames = vec![pointer(), pointer(), pointer()];
386 frames.extend((0..10).map(|_| frame(100)));
387 let first = run(&frames, &[1; W * H], 1);
388 assert_eq!(at(&first, 25, 20), 100);
389 assert_eq!(at(&first, 100, 20), 100);
390 }
391
392 #[test]
393 fn a_change_after_the_first_epoch_keeps_the_first_frame() {
394 let mut frames: Vec<_> = (0..4).map(|_| frame(30)).collect();
398 frames.extend((0..20).map(|_| frame(50)));
399 let first = run(&frames, &[1; W * H], 1);
400 assert_eq!(at(&first, 100, 20), 30);
401 }
402
403 #[test]
404 fn a_large_difference_is_content_and_keeps_the_first_frame() {
405 let mut frames = vec![frame(0)];
408 frames.extend((0..20).map(|_| fill(frame(0), 0, 0, 400, 40, 200)));
409 let first = run(&frames, &[1; W * H], 0);
410 assert_eq!(at(&first, 50, 20), 0);
411 }
412
413 #[test]
414 fn text_glyphs_close_together_form_one_patch() {
415 let mut text = frame(255);
417 for k in 0..30 {
418 text = fill(text, 10 + k * 14, 15, 6, 10, 0);
419 }
420 let mut frames = vec![text.clone(), text];
421 frames.extend((0..20).map(|_| frame(255)));
422 let first = run(&frames, &[1; W * H], 0);
423 assert_eq!(at(&first, 12, 20), 0);
424 }
425
426 #[test]
427 fn pixels_the_region_does_not_own_are_left_alone() {
428 let labels: Vec<u8> = (0..W * H)
429 .map(|i| if i % W < 100 { 1 } else { 2 })
430 .collect();
431 let mut frames = vec![fill(frame(100), 150, 10, 10, 10, 250)];
432 frames.extend((0..10).map(|_| frame(100)));
433 let mut hist = FixedHistory::new(region());
434 for (i, f) in frames.iter().enumerate() {
435 hist.update(f, &labels, &[1; W * H], W, H, 1, i as u32, 0);
436 }
437 hist.close(frames.len() as u32);
438 let mut out = frames[0].clone();
439 let first = hist.first(0);
440 hist.paint(&first, &mut out, W, H);
441 assert_eq!(out, frames[0]);
442 }
443}