Skip to main content

long_screen/kernels/
chrome.rs

1//! Screen-fixed chrome evidence on native frames (`src/core/layers.ts::stationaryBoundary`,
2//! `stickyOcclusions`): a persistent appearance edge within a stationary band, and the per-observation sticky
3//! header band whose native texture agrees with zero motion and disagrees with the accepted page translation.
4//! Thresholds, sampling steps and the "return the carried bands" fallbacks are the historical ones.
5
6use crate::kernels::geometry::{js_ceil, js_floor, js_round, Rect};
7
8#[derive(Clone, Copy, PartialEq, Eq)]
9pub enum Axis {
10    X,
11    Y,
12}
13
14#[derive(Clone, Copy, PartialEq, Eq)]
15pub enum Choose {
16    First,
17    Last,
18}
19
20/// Strong persistent appearance boundary within a stationary run: the first/last coordinate `v` in
21/// `[max(1, from), min(limit, to + 1))` whose row (or column) differs from its predecessor over more than
22/// 68 % of sampled cross positions with mean difference > 3. `None` when the image offers no boundary.
23#[allow(clippy::too_many_arguments)]
24pub fn stationary_boundary(
25    rgba: &[u8],
26    width: usize,
27    height: usize,
28    axis: Axis,
29    from: f64,
30    to: f64,
31    cross_from: f64,
32    cross_to: f64,
33    choose: Choose,
34) -> Option<f64> {
35    let (w, h) = (width as i64, height as i64);
36    let limit = if axis == Axis::X { w } else { h };
37    let cross_limit = if axis == Axis::X { h } else { w };
38    // Math.floor((crossTo - crossFrom) / 160) on the real span, then at least 1.
39    let step = (((cross_to - cross_from) / 160.0).floor() as i64).max(1);
40    let mut found = None;
41    // `for (let v = Math.max(1, from); v < Math.min(limit, to + 1); v++)`: a fractional `from` keeps its
42    // fraction in the loop variable, and `Math.max(1, from)` is 1 or `from` itself.
43    let mut v = if from > 1.0 { from } else { 1.0 };
44    let v_end = (limit as f64).min(to + 1.0);
45    let c_start = (cross_from + 2.0).max(0.0);
46    let c_end = (cross_limit as f64).min(cross_to - 2.0);
47    while v < v_end {
48        let (mut strong, mut count, mut sum) = (0u32, 0u32, 0.0f64);
49        // JS indexed `data[(py * width + px) * 4]` with real-valued loop variables: an integral product reads
50        // that byte (a half-row offset when width is even), a fractional one reads `undefined`, whose NaN
51        // poisons the row's sum so it can never qualify. Both outcomes are reproduced exactly.
52        let mut poisoned = false;
53        let mut c = c_start;
54        while c < c_end {
55            let (px, py) = if axis == Axis::X { (v, c) } else { (c, v) };
56            let idx = (py * w as f64 + px) * 4.0;
57            if idx.fract() != 0.0 {
58                poisoned = true;
59                break;
60            }
61            let i = idx as usize;
62            let j = if axis == Axis::X {
63                i - 4
64            } else {
65                i - (w as usize) * 4
66            };
67            let d = ((rgba[i] as i32 - rgba[j] as i32).abs()
68                + (rgba[i + 1] as i32 - rgba[j + 1] as i32).abs()
69                + (rgba[i + 2] as i32 - rgba[j + 2] as i32).abs()) as f64
70                / 3.0;
71            if d > 3.0 {
72                strong += 1;
73            }
74            sum += d;
75            count += 1;
76            c += step as f64;
77        }
78        if !poisoned && count > 0 && strong as f64 / count as f64 > 0.68 && sum / count as f64 > 3.0
79        {
80            // `found = v` keeps the real-valued loop variable; callers only ever pass integers.
81            found = Some(v);
82            if choose == Choose::First {
83                break;
84            }
85        }
86        v += 1.0;
87    }
88    found
89}
90
91/// Sticky-band evidence for one observation. `carry` are the previous observation's bands, retained while
92/// every native pixel under them is unchanged. Returns the bands to exclude from the moving canvas.
93#[allow(clippy::too_many_arguments)]
94pub fn sticky_occlusions(
95    previous: &[u8],
96    current: &[u8],
97    width: usize,
98    height: usize,
99    region: Rect,
100    motion_x: f64,
101    motion_y: f64,
102    carry_in: &[Rect],
103) -> Vec<Rect> {
104    let (w, h) = (width as i64, height as i64);
105    // A clock/cursor elsewhere may change a paused frame. Retain an already-proven sticky band only while
106    // ALL its native RGBA pixels are unchanged; do not drop the mask just because the page stops scrolling.
107    let carry: Vec<Rect> = carry_in
108        .iter()
109        .copied()
110        .filter(|o| {
111            if o.x < 0.0 || o.y < 0.0 || o.x + o.width > w as f64 || o.y + o.height > h as f64 {
112                return false;
113            }
114            let y_end = o.y + o.height;
115            let mut y = o.y.ceil();
116            while y < y_end {
117                let start = ((y as i64 * w) as f64 + (o.x).ceil()) as usize * 4;
118                let end = ((y as i64 * w) as f64 + (o.x + o.width).ceil()) as usize * 4;
119                if previous[start..end] != current[start..end] {
120                    return false;
121                }
122                y += 1.0;
123            }
124            true
125        })
126        .collect();
127    if crate::kernels::geometry::js_hypot(motion_x, motion_y) < 2.0 {
128        return carry;
129    }
130    let r = region;
131    let (dx, dy) = (js_round(motion_x) as i64, js_round(motion_y) as i64);
132    let end = (h - 2).min(js_floor(r.y + (288.0f64).min(r.height * 0.23)) as i64);
133    let start = 2i64.max(js_floor(r.y) as i64);
134    let step = 1i64.max(js_floor(r.width / 700.0) as i64);
135    let (mut last_fixed, mut strong_rows, mut matched) = (-1i64, 0u32, 0u32);
136    let x_start = 2i64.max(js_ceil(r.x) as i64);
137    let x_end = (w - 2) as f64;
138    for y in start..end {
139        if y + dy < 2 || y + dy >= h - 2 {
140            continue;
141        }
142        let (mut n, mut zero, mut shifted) = (0u32, 0i64, 0i64);
143        let mut x = x_start;
144        // `x < Math.min(w - 2, r.x + r.width)` compares against a real bound.
145        while (x as f64) < x_end.min(r.x + r.width) {
146            if x + dx < 2 || x + dx >= w - 2 {
147                x += step;
148                continue;
149            }
150            let i = ((y * w + x) * 4) as usize;
151            if (current[i - 4] as i32 - current[i + 4] as i32).abs() < 24 {
152                x += step;
153                continue;
154            }
155            let j = (((y + dy) * w + x + dx) * 4) as usize;
156            zero += (previous[i] as i64 - current[i] as i64).abs();
157            shifted += (previous[j] as i64 - current[i] as i64).abs();
158            n += 1;
159            x += step;
160        }
161        let nf = n as f64;
162        if n >= 8 && (zero as f64) / nf < 6.0 && (shifted as f64) / nf > 18.0 {
163            last_fixed = y;
164            strong_rows += 1;
165            matched += n;
166        }
167    }
168    if strong_rows < 4 || matched < 64 || last_fixed < 0 {
169        return carry;
170    }
171    let edge = stationary_boundary(
172        current,
173        width,
174        height,
175        Axis::Y,
176        (last_fixed + 1) as f64,
177        (end.min(last_fixed + 80)) as f64,
178        r.x,
179        r.x + r.width,
180        Choose::First,
181    );
182    match edge {
183        Some(edge) if edge - start as f64 <= r.height * 0.23 => vec![Rect {
184            x: r.x,
185            y: r.y,
186            width: r.width,
187            height: edge - r.y + 1.0,
188        }],
189        _ => carry,
190    }
191}