use super::{stat_at, Rgba};
use crate::kernels::chrome::{stationary_boundary, Axis, Choose};
use crate::kernels::geometry::{js_ceil, js_floor, js_round};
pub struct Bands {
pub top: i64,
pub bottom: i64,
pub left: i64,
pub right: i64,
pub exact_left: Option<f64>,
pub exact_right: Option<f64>,
}
fn middle_mean(width: i64, top: i64, bottom: i64, factor: f64, reference: &Rgba) -> Vec<f64> {
let mut out = vec![0.0; width.max(0) as usize];
for x in 0..width {
let (mut sum, mut count): (f64, f64) = (0.0, 0.0);
let mut y = top + 2;
while y < bottom - 2 {
let iy = js_floor(y as f64 * factor)
.min(reference.height as i32 - 1)
.max(0) as usize;
let ix = js_floor(x as f64 * factor)
.min(reference.width as i32 - 1)
.max(0) as usize;
let i = (iy * reference.width + ix) * 4;
sum += (reference.data[i] as f64
+ reference.data[i + 1] as f64
+ reference.data[i + 2] as f64)
/ 3.0;
count += 1.0;
y += 3;
}
out[x as usize] = sum / count.max(1.0);
}
out
}
#[allow(clippy::too_many_arguments)]
fn textured(
from: i64,
to: i64,
informative_frames: f64,
reference: Option<&Rgba>,
middle_mean: &[f64],
col_mean: &[f64],
top: i64,
bottom: i64,
factor: f64,
) -> bool {
if to - from < 6 || informative_frames == 0.0 {
return false;
}
let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
for x in from..to {
let mean = if reference.is_some() {
middle_mean[x as usize]
} else {
col_mean[x as usize] / informative_frames
};
lo = lo.min(mean);
hi = hi.max(mean);
}
if reference.is_none() {
return hi - lo > 6.0;
}
let img = reference.unwrap();
let (sx, sy) = (factor, factor);
let (mut structure, mut samples) = (0u32, 0u32);
let mut y = top + 3;
while y < bottom - 3 {
let mut x = from + 1;
while x < to - 1 {
let nx = js_floor(x as f64 * sx) as i64;
let ny = js_floor(y as f64 * sy) as i64;
let step = js_round(sy).max(1) as i64;
let i = (ny * img.width as i64 + nx) * 4;
let a = i - step * img.width as i64 * 4;
let b = i + step * img.width as i64 * 4;
if a >= 0 && (b as usize) < img.data.len() {
if (img.data[a as usize] as i32 - img.data[b as usize] as i32).abs() > 12 {
structure += 1;
}
samples += 1;
}
x += 2;
}
y += 2;
}
structure >= 8 && structure as f64 / (samples.max(1) as f64) > 0.003
}
const STATIONARY_COLUMN_SHARE: f64 = 0.25;
const STATIONARY_VOTE_RATIO: f64 = 1.5;
const STATIONARY_VOTE_SHARE: f64 = 0.03;
const STATIONARY_STRUCTURE_SHARE: f64 = 0.2;
const FIXED_PIXEL_SHARE: f64 = 0.25;
const FIXED_ROW_SAMPLES: usize = 5;
const FIXED_ROW_GAP: f64 = 20.0;
const FIXED_ROW_RUN: f64 = 60.0;
fn fixed_pixels(votes: (&[f64], &[f64]), y: i64, width: i64, height: i64, frames: f64) -> usize {
let row = y.clamp(2, height - 3) * width;
let (fixed, moving) = votes;
let mut count = 0;
let mut x = 2;
while x < width - 2 {
let f = stat_at(fixed, row + x);
if f / frames >= FIXED_PIXEL_SHARE && f >= STATIONARY_VOTE_RATIO * stat_at(moving, row + x)
{
count += 1;
}
x += 4;
}
count
}
#[allow(clippy::too_many_arguments)]
fn last_row_of_fixed_objects(
votes: (&[f64], &[f64]),
edge: i64,
step: i64,
within: impl Fn(i64) -> bool,
width: i64,
height: i64,
frames: f64,
factor: f64,
) -> Option<i64> {
let gap_rows = js_ceil(FIXED_ROW_GAP / factor) as i64;
let (mut last, mut gap, mut y) = (None, 0, edge);
while within(y) {
if fixed_pixels(votes, y, width, height, frames) >= FIXED_ROW_SAMPLES {
last = Some(y);
gap = 0;
} else {
gap += 1;
if gap > gap_rows {
let run_rows = js_ceil(FIXED_ROW_RUN / factor) as i64;
return last.filter(|&l| (l - edge) * step + 1 >= run_rows);
}
}
y += step;
}
None
}
fn stationary_by_votes(
fixed: &[f64],
moving: &[f64],
y: i64,
width: i64,
height: i64,
informative_frames: f64,
) -> bool {
if height < 5 || width < 5 {
return false;
}
let f = vote_share(fixed, y, width, height, informative_frames);
let m = vote_share(moving, y, width, height, informative_frames);
f >= STATIONARY_VOTE_SHARE && f >= STATIONARY_VOTE_RATIO * m
}
fn vote_share(votes: &[f64], y: i64, width: i64, height: i64, informative_frames: f64) -> f64 {
let row = y.clamp(2, height - 3);
let samples = ((width - 4 + 3) / 4) as f64;
stat_at(votes, row) / informative_frames / samples
}
fn holds_structure(
fixed: &[f64],
from: i64,
to: i64,
width: i64,
height: i64,
frames: f64,
) -> bool {
(from..to).any(|y| vote_share(fixed, y, width, height, frames) >= STATIONARY_STRUCTURE_SHARE)
}
#[allow(clippy::too_many_arguments)]
pub fn detect_bands(
width: i64,
height: i64,
informative_frames: f64,
row_change: &[f64],
row_votes: Option<(&[f64], &[f64])>,
pixel_votes: (&[f64], &[f64]),
col_moving: &[f64],
col_mean: &[f64],
reference: Option<&Rgba>,
native_width: f64,
native_height: f64,
factor: f64,
) -> Bands {
let (mut top, mut bottom) = (0i64, height);
if informative_frames >= 2.0 {
while (top as f64) < height as f64 * 0.45
&& stat_at(row_change, top) / informative_frames < 0.9
{
top += 1;
}
while bottom as f64 > height as f64 * 0.55
&& stat_at(row_change, bottom - 1) / informative_frames < 0.9
{
bottom -= 1;
}
if top < 6 || top as f64 >= height as f64 * 0.45 {
top = 0;
}
if height - bottom < 6 || bottom as f64 <= height as f64 * 0.55 {
bottom = height;
}
if let Some((fixed, moving)) = row_votes {
let still = |y: i64| {
stat_at(row_change, y) / informative_frames < 0.9
|| stationary_by_votes(fixed, moving, y, width, height, informative_frames)
};
if top == 0 {
while (top as f64) < height as f64 * 0.45 && still(top) {
top += 1;
}
if top < 6
|| top as f64 >= height as f64 * 0.45
|| !holds_structure(fixed, 0, top, width, height, informative_frames)
{
top = 0;
} else if let Some(last) = last_row_of_fixed_objects(
pixel_votes,
top,
1,
|y| (y as f64) < height as f64 * 0.45,
width,
height,
informative_frames,
factor,
) {
top = last + 1;
}
}
if bottom == height {
while bottom as f64 > height as f64 * 0.55 && still(bottom - 1) {
bottom -= 1;
}
if height - bottom < 6
|| bottom as f64 <= height as f64 * 0.55
|| !holds_structure(fixed, bottom, height, width, height, informative_frames)
{
bottom = height;
} else if let Some(last) = last_row_of_fixed_objects(
pixel_votes,
bottom - 1,
-1,
|y| y as f64 > height as f64 * 0.55,
width,
height,
informative_frames,
factor,
) {
bottom = last;
}
}
}
}
let mm = if let Some(img) = reference {
middle_mean(width, top, bottom, factor, img)
} else {
Vec::new()
};
let (mut left, mut right) = (0i64, width);
let (mut exact_left, mut exact_right) = (None, None);
if informative_frames >= 2.0 {
while (left as f64) < width as f64 * 0.45
&& stat_at(col_moving, left) / informative_frames < STATIONARY_COLUMN_SHARE
{
left += 1;
}
while right as f64 > width as f64 * 0.55
&& stat_at(col_moving, right - 1) / informative_frames < STATIONARY_COLUMN_SHARE
{
right -= 1;
}
if let Some(img) = reference {
let scale = factor;
let cross_from = native_height.min((top as f64 * factor).ceil());
let cross_to = native_height.min((bottom as f64 * factor).floor());
exact_left = if left > 0 {
stationary_boundary(
img.data,
img.width,
img.height,
Axis::X,
0.0,
(left as f64 * scale).ceil(),
cross_from,
cross_to,
Choose::Last,
)
} else {
None
};
exact_right = if right < width {
stationary_boundary(
img.data,
img.width,
img.height,
Axis::X,
(right as f64 * scale).floor(),
native_width,
cross_from,
cross_to,
Choose::First,
)
} else {
None
};
if let Some(v) = exact_left {
left = js_floor(v / scale) as i64;
}
if let Some(v) = exact_right {
right = js_ceil(v / scale) as i64;
}
}
if left < 6
|| (left as f64) >= width as f64 * 0.45
|| !textured(
0,
left,
informative_frames,
reference,
&mm,
col_mean,
top,
bottom,
factor,
)
{
left = 0;
exact_left = None;
}
if width - right < 6
|| (right as f64) <= width as f64 * 0.55
|| !textured(
right,
width,
informative_frames,
reference,
&mm,
col_mean,
top,
bottom,
factor,
)
{
right = width;
exact_right = None;
}
}
Bands {
top,
bottom,
left,
right,
exact_left,
exact_right,
}
}
pub fn strongest_cut(g: &[f64], informative_frames: f64) -> i64 {
let mut best = 0i64;
let hi = g.len() as i64 - 2;
let mut k = 2i64;
while k < hi {
if g[k as usize] > g[best as usize] {
best = k;
}
k += 1;
}
if informative_frames >= 4.0
&& g.get(best as usize).copied().unwrap_or(0.0) / informative_frames > 0.12
{
best
} else {
0
}
}