use std::sync::OnceLock;
const N_RANDOM: usize = 10_000;
const N_RESERVED: f64 = 10.0;
fn rand_table() -> &'static [f32; N_RANDOM] {
static TABLE: OnceLock<[f32; N_RANDOM]> = OnceLock::new();
TABLE.get_or_init(|| {
let a = 16807.0f64;
let m = 2_147_483_647.0f64;
let mut seed = 1.0f64;
let mut t = [0.0f32; N_RANDOM];
for slot in t.iter_mut() {
let temp = a * seed;
seed = temp - m * (temp / m).trunc();
*slot = (seed / m) as f32;
}
debug_assert_eq!(seed as i64, 1_043_618_065);
t
})
}
fn nint(x: f64) -> i32 {
if x >= 0.0 {
(x + 0.5) as i32
} else {
(x - 0.5) as i32
}
}
struct Dither {
iseed: usize,
nextrand: usize,
}
impl Dither {
fn new(row: usize) -> Self {
let table = rand_table();
let iseed = row % N_RANDOM;
let nextrand = (table[iseed] * 500.0) as usize;
Self { iseed, nextrand }
}
fn value(&self) -> f64 {
rand_table()[self.nextrand] as f64
}
fn advance(&mut self) {
self.nextrand += 1;
if self.nextrand == N_RANDOM {
self.iseed += 1;
if self.iseed == N_RANDOM {
self.iseed = 0;
}
self.nextrand = (rand_table()[self.iseed] * 500.0) as usize;
}
}
}
pub(super) fn dither_seed(first_tile: &[f32]) -> u32 {
let sum: u64 = first_tile
.iter()
.flat_map(|f| f.to_ne_bytes())
.map(u64::from)
.sum();
(sum % 10_000) as u32 + 1
}
pub(super) fn noise3(data: &[f32], mut nx: usize, mut ny: usize) -> (f64, f64, f64) {
if nx < 5 {
nx *= ny;
ny = 1;
}
let mut minv = f32::INFINITY;
let mut maxv = f32::NEG_INFINITY;
if nx < 5 {
for &v in data {
minv = minv.min(v);
maxv = maxv.max(v);
}
return (minv as f64, maxv as f64, 0.0);
}
let mut row_medians: Vec<f64> = Vec::with_capacity(ny);
let mut diffs: Vec<f32> = Vec::with_capacity(nx);
for row in data.chunks_exact(nx).take(ny) {
for &v in row {
minv = minv.min(v);
maxv = maxv.max(v);
}
let (mut v1, mut v2, mut v3, mut v4) = (row[0], row[1], row[2], row[3]);
diffs.clear();
for &v5 in &row[4..] {
if !(v1 == v2 && v2 == v3 && v3 == v4 && v4 == v5) {
diffs.push(((2.0 * v3 as f64) - v1 as f64 - v5 as f64).abs() as f32);
}
v1 = v2;
v2 = v3;
v3 = v4;
v4 = v5;
}
match diffs.len() {
0 => {}
1 => row_medians.push(diffs[0] as f64),
_ => row_medians.push(quick_select(&mut diffs) as f64),
}
}
let noise = match row_medians.len() {
0 => 0.0,
1 => row_medians[0],
n => {
row_medians.sort_by(|a, b| a.partial_cmp(b).unwrap());
(row_medians[(n - 1) / 2] + row_medians[n / 2]) / 2.0
}
};
(minv as f64, maxv as f64, 0.605_269_7 * noise)
}
fn quick_select(arr: &mut [f32]) -> f32 {
let n = arr.len();
let (mut low, mut high) = (0usize, n - 1);
let median = n / 2;
loop {
if high <= low {
return arr[median];
}
if high == low + 1 {
if arr[low] > arr[high] {
arr.swap(low, high);
}
return arr[median];
}
let middle = (low + high) / 2;
if arr[middle] > arr[high] {
arr.swap(middle, high);
}
if arr[low] > arr[high] {
arr.swap(low, high);
}
if arr[middle] > arr[low] {
arr.swap(middle, low);
}
arr.swap(middle, low + 1);
let mut ll = low + 1;
let mut hh = high;
loop {
loop {
ll += 1;
if arr[low] <= arr[ll] {
break;
}
}
loop {
hh -= 1;
if arr[hh] <= arr[low] {
break;
}
}
if hh < ll {
break;
}
arr.swap(ll, hh);
}
arr.swap(low, hh);
if hh <= median {
low = ll;
}
if hh >= median {
high = hh - 1;
}
}
}
pub(super) struct QuantTile {
pub idata: Vec<i32>,
pub zzero: f64,
}
pub(super) fn quantize_tile(
fdata: &[f32],
delta: f64,
tile_index: usize,
zdither0: u32,
) -> QuantTile {
let minval = fdata.iter().copied().fold(f32::INFINITY, f32::min) as f64;
let iqfactor = (minval / delta + 0.5) as i64;
let zzero = iqfactor as f64 * delta;
let row = tile_index + zdither0 as usize - 1;
let mut dither = Dither::new(row);
let idata = fdata
.iter()
.map(|&f| {
let q = nint((f as f64 - zzero) / delta + dither.value() - 0.5);
dither.advance();
q
})
.collect();
QuantTile { idata, zzero }
}
pub(super) fn global_delta(data: &[f32], nx: usize, ny: usize, qlevel: f64) -> f64 {
let (minv, maxv, noise) = noise3(data, nx, ny);
let delta = if noise > 0.0 {
noise / qlevel
} else if maxv > minv {
(maxv - minv) / (2.0 * i32::MAX as f64 - N_RESERVED - 1.0)
} else {
1.0
};
delta.max(f64::MIN_POSITIVE)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rand_table_matches_cfitsio_checksum() {
let t = rand_table();
assert!((0.0..1.0).contains(&t[0]));
assert_eq!(t.len(), N_RANDOM);
}
#[test]
fn quantize_dequantize_is_close() {
let n = 64usize;
let fdata: Vec<f32> = (0..n * n)
.map(|i| ((i as f32) * 0.013).sin() * 100.0 + 500.0)
.collect();
let delta = global_delta(&fdata, n, n, 4.0);
assert!(delta > 0.0);
for (t, row) in fdata.chunks_exact(n).enumerate() {
let zdither0 = 137u32;
let q = quantize_tile(row, delta, t, zdither0);
let seed_row = t + zdither0 as usize - 1;
let mut d = Dither::new(seed_row);
for (k, &iv) in q.idata.iter().enumerate() {
let back = (iv as f64 - d.value() + 0.5) * delta + q.zzero;
d.advance();
assert!(
(back - row[k] as f64).abs() <= delta,
"row {t} px {k}: {back} vs {}",
row[k]
);
}
}
}
}