1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
//! Defines dithering trait and implementations.
/// Simple xor shuffled dither.
///
/// A 256 element list is generated using xorshift PRNG, somewhat uniformly distributing dither
/// shifts in the range of `[-0.5; 0.5]`.
///
/// During the dither call, index of the list is taken using `((x + 1) * (y + 1) * (z + 1)) % 256` calculation.
/// This results in a non-repeating dither pattern, which may be undesired in some artistic cases.
///
/// Optionally, user may enable frame counter, which will randomize dithering from frame to frame.
#[derive(Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct XorShufDither {
count_frames: bool,
frame_cnt: usize,
}
impl XorShufDither {
pub fn set_count_frames(&mut self, count_frames: bool) {
self.count_frames = count_frames;
}
}
impl XorShufDither {
const MASK: ([f32; 256], [u8; 256]) = {
let mut state = (0, 1, 1, 1);
const fn xorshift((mut a, mut x, mut y, mut z): (u8, u8, u8, u8)) -> (u8, u8, u8, u8) {
let t = x ^ (x << 4);
x = y;
y = z;
z = a;
a = z ^ t ^ (z >> 1) ^ (t << 1);
(a, x, y, z)
}
// We need to
state = xorshift(state);
state = xorshift(state);
state = xorshift(state);
let mut arr = [0.0; 256];
let mut arr2 = [0; 256];
let mut i = 0;
while i < 256 {
state = xorshift(state);
arr2[i] = state.0;
arr[i] = (state.0 as f32 / 256.0) - 0.5;
i += 1;
}
(arr, arr2)
};
}
impl Dithering for XorShufDither {
fn new_frame(&mut self, _: usize, _h: usize) {
self.frame_cnt += 1;
}
fn dither(&self, interp: f32, x: usize, y: usize, z: usize) -> f32 {
let idx =
((x + 1) * (y + 1) * (z + 1) * if self.count_frames { self.frame_cnt } else { 1 })
% 256;
interp + Self::MASK.0[idx]
}
}
/// Color dithering.
///
/// In limited color outputs (such as ascii rendered screens), direct nearest color conversion may
/// lead to excessive color banding. Diterhing aims to hide the limits of the output space by
/// injecting noise that would nudge value rounding to one way or the other.
///
/// Practically, dithering aims to make rounding probabilistic, rather than uniform among pixels.
/// Doing so leads to smaller overall error and perceptually better looking image.
pub trait Dithering {
/// Prepares start of a new frame for the ditherer.
///
/// This may be used to modify state in preparation.
fn new_frame(&mut self, w: usize, h: usize);
/// Takes in a 0-1 value and applies dithering to it.
///
/// `interp` value indicates the weight between 2 integer values. The `Dithering` object may
/// apply an offset in the `-0.5..=0.5` range to nudge the integer cast one way or the other.
fn dither(&self, interp: f32, x: usize, y: usize, z: usize) -> f32;
}
impl Dithering for () {
fn new_frame(&mut self, _: usize, _h: usize) {}
fn dither(&self, interp: f32, _: usize, _: usize, _: usize) -> f32 {
interp
}
}