pathland/src/graphics/dither.rs

56 lines
1.5 KiB
Rust

use image::*;
/// Generate Bayer matrix texture for ordered dithering.
/// TODO: better/alternative noise-based matrix to avoid grid pattern?
const DITHER_SIZE: u8 = 1 << 7;
/// Bitwise interleave two integers of length BIT_WIDTH into a single
/// 2*BIT_WIDTH integer.
///
/// example interleave:
///
/// x = 0 1 0 0 1
/// y = 1 0 0 1 1
/// ----------
/// r = 0110000111
///
/// actually also reverses bits, but I want that anyway.
/// (don't try to re-use this function!)
fn bit_interleave(mut x: u8, mut y: u8) -> u16 {
let mut acc: u16 = 0;
for _ in 0..8 {
acc <<= 1;
acc |= (x & 1) as u16;
x >>= 1;
acc <<= 1;
acc |= (y & 1) as u16;
y >>= 1;
}
acc
}
fn bayer(x: u8, y: u8) -> f32 {
// Magic bitwise formula from Wikipedia produces values from 0 to 2^16-1.
// FIXME: slight vertical lines when displaying dither texture
let magic = bit_interleave(x ^ y, x);
(magic as f32 + 1.) / (u8::MAX as u16 * u8::MAX as u16) as f32
}
fn bayer_bias(x: u8, y: u8) -> [f32; 4] {
[
// TODO: smarter way to re-tile color channels? if this isn't good enough.
bayer(x, y),
bayer(DITHER_SIZE.overflowing_sub(x).0, y),
bayer(x, DITHER_SIZE.overflowing_sub(y).0),
bayer(DITHER_SIZE.overflowing_sub(x).0, DITHER_SIZE.overflowing_sub(y).0)
]
}
pub fn bayer_texture() -> Rgba32FImage {
ImageBuffer::from_fn(u8::MAX as u32, u8::MAX as u32, |x, y| Rgba::from(bayer_bias(x as u8, y as u8)))
}