use core::fmt::Debug;
use embedded_graphics_core::pixelcolor::{Rgb565, RgbColor};
#[derive(Debug, Clone, Copy)]
pub struct DitherConfig {
pub intensity: u8,
}
impl DitherConfig {
const BAYER_MATRIX: [[u8; 4]; 4] =
[[0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1, 9], [15, 7, 13, 5]];
pub fn new(intensity: u8) -> Self {
Self { intensity }
}
#[inline]
pub fn apply(&self, color: Rgb565, x: i32, y: i32) -> Rgb565 {
if self.intensity == 0 {
return color;
}
let matrix_x = (x & 3) as usize;
let matrix_y = (y & 3) as usize;
let threshold = Self::BAYER_MATRIX[matrix_y][matrix_x];
let scaled_threshold = ((threshold as u16 * self.intensity as u16) / 15) as u8;
let r = color.r();
let g = color.g();
let b = color.b();
let r = if r > scaled_threshold {
r.saturating_sub(scaled_threshold / 2)
} else {
r.saturating_add(scaled_threshold / 2)
};
let g = if g > scaled_threshold {
g.saturating_sub(scaled_threshold / 2)
} else {
g.saturating_add(scaled_threshold / 2)
};
let b = if b > scaled_threshold {
b.saturating_sub(scaled_threshold / 2)
} else {
b.saturating_add(scaled_threshold / 2)
};
Rgb565::new(r, g, b)
}
}
#[derive(Debug, Clone, Copy)]
pub struct DitherShader<'a, S> {
pub inner: S,
pub dither: &'a DitherConfig,
}
impl<'a, S: super::FragmentShader> super::FragmentShader for DitherShader<'a, S> {
type Interpolants = S::Interpolants;
#[inline(always)]
fn shade(&self, x: i32, y: i32, z: crate::ZDepth, interps: Self::Interpolants) -> Rgb565 {
let base = self.inner.shade(x, y, z, interps);
self.dither.apply(base, x, y)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::shader::{FlatColorShader, FragmentShader};
#[test]
fn test_dither_intensity_zero_is_identity() {
let dither = DitherConfig::new(0);
let color = Rgb565::RED;
assert_eq!(dither.apply(color, 0, 0), color);
assert_eq!(dither.apply(color, 12, 15), color);
}
#[test]
fn test_dither_pattern_varies_with_pixel_coordinates() {
let dither = DitherConfig::new(128);
let color = Rgb565::new(16, 32, 16);
let c0 = dither.apply(color, 0, 0);
let c1 = dither.apply(color, 1, 1);
assert_ne!(c0, c1);
}
#[test]
fn test_dither_shader_decorator() {
let dither = DitherConfig::new(64);
let base_shader = FlatColorShader {
color: Rgb565::GREEN,
};
let dither_shader = DitherShader {
inner: base_shader,
dither: &dither,
};
let shaded = dither_shader.shade(2, 3, crate::to_zdepth(100), ());
assert!(shaded.g() > 0);
}
}