use crate::util::FastRound;
pub(crate) trait AqLuma: Copy {
fn to_f32(self) -> f32;
}
impl AqLuma for f32 {
#[inline(always)]
fn to_f32(self) -> f32 {
self
}
}
impl AqLuma for i32 {
#[inline(always)]
fn to_f32(self) -> f32 {
self as f32
}
}
pub(crate) fn sb_octile_variance(subvars: &mut [f32; 64], octile: u8) -> f32 {
subvars.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let o = octile.clamp(1, 8) as usize;
let idx = (o * 8 - 1).min(63);
subvars[idx]
}
pub(crate) fn variance_boost_delta(
picked_var: f32,
ref_log: f32,
strength: f32,
boost_only: bool,
) -> i32 {
let v_log = (1.0 + picked_var).ln();
const LOW_LOG: f32 = 5.549_076; const MAX_BOOST: f32 = 18.0; const MAX_CUT: f32 = 10.0; const BOOST_SLOPE: f32 = 5.0;
const CUT_SLOPE: f32 = 3.0;
if v_log < LOW_LOG {
let d = ((LOW_LOG - v_log) * BOOST_SLOPE * strength).min(MAX_BOOST);
-(d.fast_round() as i32)
} else if boost_only {
0
} else {
let over = (v_log - ref_log.max(LOW_LOG)).max(0.0);
let d = (over * CUT_SLOPE * strength).min(MAX_CUT);
d.fast_round() as i32
}
}
#[derive(Clone, Copy, Debug)]
pub struct DarkAq {
pub enabled: bool,
pub min_q: i32,
pub mean_floor: f32,
pub dark_ref: f32,
pub gamma: f32,
pub max_weight: f32,
pub scale: f32,
pub max_qidx: i32,
}
impl Default for DarkAq {
fn default() -> Self {
DarkAq {
enabled: false,
min_q: 150,
mean_floor: 16.0,
dark_ref: 56.0,
gamma: 1.2,
max_weight: 4.5,
scale: 4.0,
max_qidx: 16,
}
}
}
impl DarkAq {
pub(crate) fn off() -> Self {
DarkAq {
enabled: false,
..DarkAq::default()
}
}
pub(crate) fn on() -> Self {
DarkAq {
enabled: true,
..DarkAq::default()
}
}
}
pub(crate) fn dark_structure_stats<T: AqLuma>(
yp: &[T],
pw: usize,
sb_y: usize,
sb_x: usize,
width: usize,
height: usize,
scale: f32,
) -> (f32, f32) {
let h = height.saturating_sub(sb_y).min(64);
let w = width.saturating_sub(sb_x).min(64);
if h == 0 || w == 0 {
return (0.0, 0.0);
}
let mut buf = [[0f32; 64]; 64];
let mut sum = 0f32;
for (r, row) in buf.iter_mut().enumerate().take(h) {
let base = (sb_y + r) * pw + sb_x;
for c in 0..w {
let v = yp[base + c].to_f32() * scale;
row[c] = v;
sum += v;
}
}
let mean = sum / (h * w) as f32;
if h < 3 || w < 3 {
return (mean, 0.0);
}
let mut lap_full = 0f32;
let mut nf = 0u32;
for r in 1..h - 1 {
for c in 1..w - 1 {
let l = 4.0 * buf[r][c] - buf[r - 1][c] - buf[r + 1][c] - buf[r][c - 1] - buf[r][c + 1];
lap_full += l.abs();
nf += 1;
}
}
let lap_full = lap_full / nf as f32;
let (hh, ww) = (h / 2, w / 2);
if hh < 3 || ww < 3 {
return (mean, 0.0);
}
let mut half = [[0f32; 32]; 32];
for r in 0..hh {
for c in 0..ww {
half[r][c] = 0.25
* (buf[2 * r][2 * c]
+ buf[2 * r][2 * c + 1]
+ buf[2 * r + 1][2 * c]
+ buf[2 * r + 1][2 * c + 1]);
}
}
let mut lap_half = 0f32;
let mut nh = 0u32;
for r in 1..hh - 1 {
for c in 1..ww - 1 {
let l = 4.0 * half[r][c]
- half[r - 1][c]
- half[r + 1][c]
- half[r][c - 1]
- half[r][c + 1];
lap_half += l.abs();
nh += 1;
}
}
let lap_half = lap_half / nh as f32;
(mean, (lap_full * lap_half).sqrt())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn dark_protection<T: AqLuma>(
d: &DarkAq,
base_q: i32,
yp: &[T],
pw: usize,
sb_y: usize,
sb_x: usize,
width: usize,
height: usize,
scale: f32,
) -> i32 {
if !d.enabled || base_q < d.min_q {
return 0;
}
let (mean, mid_energy) = dark_structure_stats(yp, pw, sb_y, sb_x, width, height, scale);
if mid_energy <= 0.0 {
return 0;
}
let dark_weight = ((d.mean_floor + d.dark_ref) / (d.mean_floor + mean))
.powf(d.gamma)
.clamp(1.0, d.max_weight);
let darkness = dark_weight - 1.0;
if darkness <= 0.0 {
return 0;
}
let dark_structure = (mid_energy * darkness).ln_1p();
(dark_structure * d.scale)
.min(d.max_qidx as f32)
.max(0.0)
.fast_round() as i32
}