use std::sync::OnceLock;
const N_RANDOM: usize = 10000;
const N_RESERVED_VALUES: f64 = 10.0;
const INT_MAX: f64 = 2147483647.0;
pub(super) const NULL_VALUE: i32 = -2147483647;
pub(super) const ZERO_VALUE: i32 = -2147483646;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum DitherMethod {
None,
Subtractive1,
Subtractive2,
}
impl DitherMethod {
fn dithered(self) -> bool {
!matches!(self, DitherMethod::None)
}
}
pub(super) fn random_values() -> &'static [f32] {
static VALUES: OnceLock<Vec<f32>> = OnceLock::new();
VALUES.get_or_init(|| {
let a = 16807.0f64;
let m = 2147483647.0f64;
let mut seed = 1.0f64;
let mut v = Vec::with_capacity(N_RANDOM);
for _ in 0..N_RANDOM {
let temp = a * seed;
seed = temp - m * ((temp / m) as i64 as f64);
v.push((seed / m) as f32);
}
debug_assert_eq!(seed as i64, 1_043_618_065);
v
})
}
pub(super) struct Dither {
rand: &'static [f32],
iseed: usize,
nextrand: usize,
}
impl Dither {
pub(super) fn new(irow: i64) -> Self {
let rand = random_values();
let iseed = (irow - 1).rem_euclid(N_RANDOM as i64) as usize;
let nextrand = (rand[iseed] * 500.0) as usize;
Dither {
rand,
iseed,
nextrand,
}
}
pub(super) fn next(&mut self) -> f64 {
let r = self.rand[self.nextrand] as f64;
self.nextrand += 1;
if self.nextrand == N_RANDOM {
self.iseed += 1;
if self.iseed == N_RANDOM {
self.iseed = 0;
}
self.nextrand = (self.rand[self.iseed] * 500.0) as usize;
}
r
}
}
pub(super) fn dequantize_into(
ints: &[i64],
scale: f64,
zero: f64,
method: DitherMethod,
irow: i64,
zblank: Option<i64>,
out: &mut Vec<f64>,
) {
let dither2 = method == DitherMethod::Subtractive2;
let mut d = method.dithered().then(|| Dither::new(irow));
out.clear();
out.reserve(ints.len());
out.extend(ints.iter().map(|&v| {
let r = d.as_mut().map_or(0.0, Dither::next);
if zblank == Some(v) {
f64::NAN
} else if dither2 && v == ZERO_VALUE as i64 {
0.0
} else if method.dithered() {
(v as f64 - r + 0.5) * scale + zero
} else {
scale * v as f64 + zero
}
}));
}
fn nint(x: f64) -> i32 {
if x >= 0.0 {
(x + 0.5) as i32
} else {
(x - 0.5) as i32
}
}
struct Noise {
min: f64,
max: f64,
noise: f64,
any_good: bool,
}
fn noise3(data: &[f64], nx_in: usize, ny_in: usize) -> Noise {
let (mut nx, mut ny) = (nx_in.max(1), ny_in.max(1));
if nx < 5 {
nx *= ny;
ny = 1;
}
let mut xmin = f64::MAX;
let mut xmax = f64::MIN;
let mut any_good = false;
let mut row_meds: Vec<f64> = Vec::with_capacity(ny);
for jj in 0..ny {
let good: Vec<f64> = data[jj * nx..jj * nx + nx]
.iter()
.copied()
.filter(|v| v.is_finite())
.collect();
for &v in &good {
xmin = xmin.min(v);
xmax = xmax.max(v);
any_good = true;
}
if good.len() < 5 || nx < 5 {
continue; }
let (mut v1, mut v2, mut v3, mut v4) = (good[0], good[1], good[2], good[3]);
let mut diffs: Vec<f64> = Vec::with_capacity(good.len());
for &v5 in &good[4..] {
if !(v1 == v2 && v2 == v3 && v3 == v4 && v4 == v5) {
diffs.push((2.0 * v3 - v1 - v5).abs());
}
v1 = v2;
v2 = v3;
v3 = v4;
v4 = v5;
}
if !diffs.is_empty() {
row_meds.push(lower_median(&mut diffs));
}
}
let noise = if row_meds.is_empty() {
0.0
} else {
0.6052697 * proper_median(&mut row_meds)
};
Noise {
min: xmin,
max: xmax,
noise,
any_good,
}
}
fn lower_median(v: &mut [f64]) -> f64 {
let k = (v.len() - 1) / 2;
*v.select_nth_unstable_by(k, |a, b| a.partial_cmp(b).unwrap())
.1
}
fn proper_median(v: &mut [f64]) -> f64 {
let n = v.len();
let (below, &mut upper, _) = v.select_nth_unstable_by(n / 2, |a, b| a.partial_cmp(b).unwrap());
if n % 2 == 1 {
upper
} else {
let lower = below.iter().copied().fold(f64::NEG_INFINITY, f64::max);
(lower + upper) / 2.0
}
}
pub(super) struct Quantized {
pub(super) idata: Vec<i32>,
pub(super) bscale: f64,
pub(super) bzero: f64,
pub(super) has_null: bool,
}
pub(super) fn quantize_tile(
fdata: &[f64],
nx: usize,
ny: usize,
qlevel: f64,
method: DitherMethod,
irow: i64,
) -> Option<Quantized> {
let n = nx * ny;
if n <= 1 {
return None;
}
let est = noise3(fdata, nx, ny);
if !est.any_good {
return None; }
let delta = if qlevel == 0.0 {
est.noise / 4.0
} else {
est.noise / qlevel
};
if delta == 0.0 {
return None;
}
if (est.max - est.min) / delta > 2.0 * INT_MAX - N_RESERVED_VALUES {
return None;
}
let has_null = fdata.iter().take(n).any(|v| !v.is_finite());
let dither2 = method == DitherMethod::Subtractive2;
let zeropt = if has_null || dither2 {
est.min - delta * (NULL_VALUE as f64 + N_RESERVED_VALUES)
} else if (est.max - est.min) / delta < INT_MAX - N_RESERVED_VALUES {
let iqfactor = (est.min / delta + 0.5) as i64;
iqfactor as f64 * delta
} else {
(est.min + est.max) / 2.0
};
let mut idata = vec![0i32; n];
let mut d = method.dithered().then(|| Dither::new(irow));
for (i, &f) in fdata.iter().enumerate().take(n) {
let r = d.as_mut().map_or(0.0, Dither::next);
idata[i] = if !f.is_finite() {
NULL_VALUE
} else if dither2 && f == 0.0 {
ZERO_VALUE
} else if method.dithered() {
nint((f - zeropt) / delta + r - 0.5)
} else {
nint((f - zeropt) / delta)
};
}
Some(Quantized {
idata,
bscale: delta,
bzero: zeropt,
has_null,
})
}