use std::error::Error;
pub(crate) const SEQUENCE_LENGTH: usize = 10000;
const ZERO_VALUE: i64 = -2147483646;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Quantization {
#[default]
NoDither,
SubtractiveDither1,
SubtractiveDither2,
}
impl Quantization {
pub(crate) fn from_card(value: Option<&str>) -> Result<Self, Box<dyn Error + Send + Sync>> {
match value.map(str::trim) {
None | Some("") | Some("NONE") | Some("NO_DITHER") => Ok(Quantization::NoDither),
Some("SUBTRACTIVE_DITHER_1") => Ok(Quantization::SubtractiveDither1),
Some("SUBTRACTIVE_DITHER_2") => Ok(Quantization::SubtractiveDither2),
Some(other) => Err(format!(
"ZQUANTIZ {:?} is not a quantisation method this crate implements; it reads \
NO_DITHER, SUBTRACTIVE_DITHER_1 and SUBTRACTIVE_DITHER_2",
other
)
.into()),
}
}
pub(crate) fn card_value(self) -> &'static str {
match self {
Quantization::NoDither => "NO_DITHER",
Quantization::SubtractiveDither1 => "SUBTRACTIVE_DITHER_1",
Quantization::SubtractiveDither2 => "SUBTRACTIVE_DITHER_2",
}
}
pub(crate) fn dithers(self) -> bool {
!matches!(self, Quantization::NoDither)
}
}
pub(crate) fn sequence() -> &'static [f32; SEQUENCE_LENGTH] {
use std::sync::OnceLock;
static SEQUENCE: OnceLock<[f32; SEQUENCE_LENGTH]> = OnceLock::new();
SEQUENCE.get_or_init(|| {
let mut values = [0.0_f32; SEQUENCE_LENGTH];
for (value, seed) in values.iter_mut().zip(seeds()) {
*value = (seed / MODULUS) as f32;
}
values
})
}
fn seeds() -> impl Iterator<Item = f64> {
let mut seed = 1.0_f64;
std::iter::repeat_with(move || {
let temp = MULTIPLIER * seed;
seed = temp - MODULUS * (temp / MODULUS).floor();
seed
})
}
const MULTIPLIER: f64 = 16807.0;
const MODULUS: f64 = 2147483647.0;
#[derive(Debug, Clone, Copy)]
pub(crate) struct Dither {
start: usize,
next: usize,
}
impl Dither {
pub(crate) fn for_tile(seed: i64, tile: usize) -> Self {
let start = (seed - 1).rem_euclid(SEQUENCE_LENGTH as i64) as usize;
let start = (start + tile) % SEQUENCE_LENGTH;
Self {
start,
next: Self::first(start),
}
}
fn first(start: usize) -> usize {
(sequence()[start] * 500.0) as usize % SEQUENCE_LENGTH
}
fn next_value(&mut self) -> f64 {
self.take() as f64
}
fn take(&mut self) -> f32 {
let value = sequence()[self.next];
self.next += 1;
if self.next == SEQUENCE_LENGTH {
self.start = (self.start + 1) % SEQUENCE_LENGTH;
self.next = Self::first(self.start);
}
value
}
}
pub(crate) fn unquantize(
values: &[f64],
scale: f64,
zero: f64,
method: Quantization,
blank: Option<f64>,
mut dither: Dither,
) -> Vec<f64> {
values
.iter()
.map(|value| {
let random = if method.dithers() {
dither.next_value()
} else {
0.5
};
if Some(*value) == blank {
return f64::NAN;
}
if method == Quantization::SubtractiveDither2 && *value == ZERO_VALUE as f64 {
return 0.0;
}
zero + scale * (*value - random + 0.5)
})
.collect()
}
pub(crate) fn quantize(
values: &[f64],
scale: f64,
zero: f64,
method: Quantization,
blank: Option<i64>,
mut dither: Dither,
) -> Vec<i64> {
values
.iter()
.map(|value| {
let random = if method.dithers() {
dither.next_value()
} else {
0.5
};
if !value.is_finite() {
return blank.unwrap_or(0);
}
if method == Quantization::SubtractiveDither2 && *value == 0.0 {
return ZERO_VALUE;
}
((value - zero) / scale + random - 0.5).round() as i64
})
.collect()
}
#[cfg(test)]
mod tests {
use super::{Dither, Quantization, SEQUENCE_LENGTH, quantize, seeds, sequence, unquantize};
fn as_values(quantised: &[i64]) -> Vec<f64> {
quantised.iter().map(|value| *value as f64).collect()
}
#[test]
fn the_sequence_is_the_one_the_convention_fixes() {
let sequence = sequence();
assert!((sequence[0] as f64 - 16807.0 / 2147483647.0).abs() < 1e-7);
assert!((sequence[1] as f64 - 282475249.0 / 2147483647.0).abs() < 1e-7);
assert!((sequence[2] as f64 - 1622650073.0 / 2147483647.0).abs() < 1e-7);
assert_eq!(seeds().nth(SEQUENCE_LENGTH - 1), Some(1043618065.0));
assert!(sequence.iter().all(|value| (0.0..1.0).contains(value)));
}
#[test]
fn a_quantisation_method_is_read_from_its_card() {
assert_eq!(
Quantization::from_card(None).unwrap(),
Quantization::NoDither
);
assert_eq!(
Quantization::from_card(Some("SUBTRACTIVE_DITHER_1")).unwrap(),
Quantization::SubtractiveDither1
);
assert!(Quantization::from_card(Some("SOMETHING_ELSE")).is_err());
}
#[test]
fn two_tiles_do_not_dither_alike() {
let values = [100.0_f64; 8];
let first = unquantize(
&values,
0.5,
0.0,
Quantization::SubtractiveDither1,
None,
Dither::for_tile(1, 0),
);
let second = unquantize(
&values,
0.5,
0.0,
Quantization::SubtractiveDither1,
None,
Dither::for_tile(1, 1),
);
assert_ne!(first, second);
}
#[test]
fn quantising_and_undoing_it_lands_within_one_step() {
let values: Vec<f64> = (0..64).map(|index| 10.0 + index as f64 * 0.017).collect();
let scale = 0.01;
for method in [
Quantization::NoDither,
Quantization::SubtractiveDither1,
Quantization::SubtractiveDither2,
] {
let quantised = as_values(&quantize(
&values,
scale,
10.0,
method,
None,
Dither::for_tile(7, 3),
));
let back = unquantize(
&quantised,
scale,
10.0,
method,
None,
Dither::for_tile(7, 3),
);
for (original, returned) in values.iter().zip(&back) {
assert!(
(original - returned).abs() <= scale,
"{original} came back as {returned}, further than one step of {scale}"
);
}
}
}
#[test]
fn dithering_keeps_the_average_of_a_flat_patch_where_it_was() {
let scale = 0.01;
let value = 10.0 + 0.4 * scale;
let values = vec![value; 2000];
let mean = |method| {
let quantised = as_values(&quantize(
&values,
scale,
0.0,
method,
None,
Dither::for_tile(1, 0),
));
let back = unquantize(&quantised, scale, 0.0, method, None, Dither::for_tile(1, 0));
back.iter().sum::<f64>() / back.len() as f64
};
let plain = mean(Quantization::NoDither);
let dithered = mean(Quantization::SubtractiveDither1);
assert!(
(plain - value).abs() > 0.3 * scale,
"plain rounding should lose the offset, got {plain}"
);
assert!(
(dithered - value).abs() < 0.05 * scale,
"dithering should keep the average at {value}, got {dithered}"
);
}
#[test]
fn dither_two_keeps_zero_exactly_zero() {
let quantised = as_values(&quantize(
&[0.0, 1.0],
0.5,
0.0,
Quantization::SubtractiveDither2,
None,
Dither::for_tile(1, 0),
));
let back = unquantize(
&quantised,
0.5,
0.0,
Quantization::SubtractiveDither2,
None,
Dither::for_tile(1, 0),
);
assert_eq!(back[0], 0.0);
}
#[test]
fn a_blank_value_comes_back_undefined() {
let back = unquantize(
&[5.0, -32768.0, 7.0],
1.0,
0.0,
Quantization::NoDither,
Some(-32768.0),
Dither::for_tile(1, 0),
);
assert!(back[1].is_nan(), "got {back:?}");
assert!(back[0].is_finite() && back[2].is_finite(), "got {back:?}");
}
}