Skip to content

Instantly share code, notes, and snippets.

@randrews
Created June 16, 2025 05:08
Show Gist options
  • Save randrews/2adcdbf1b57a62becdd160e886f12572 to your computer and use it in GitHub Desktop.
Save randrews/2adcdbf1b57a62becdd160e886f12572 to your computer and use it in GitHub Desktop.
Atkinson dithering in Rust (with the Shepard Fairey Obama poster palette)
fn main() {
let input = image::load_from_memory(include_bytes!("atkinson.png")).unwrap().into_luma16();
let (width, height) = (input.width(), input.height());
let mut output = image::RgbImage::new(width, height);
let mut error_rows = vec![vec![0.0; width as usize]; 3];
let palette: Vec<image::Rgb<u8>> = vec![
[0xe4, 0x2b, 0x19].into(),
[0, 0x40, 0x62].into(),
[0x82, 0xa7, 0xaf].into(),
[0xbe, 0xc3, 0xb6].into(),
[0xfb, 0xe8, 0xbb].into()
];
for (x, y, p) in input.enumerate_pixels() {
let xu = x as usize;
// We're at the start of a new row, so we need to roll the error buffers:
if x == 0 && y > 0 {
let mut v = error_rows.remove(0);
v.fill(0.0);
error_rows.push(v);
}
let sh = palette.len() as f32 - 1.0; // number of divisions to quantize with
let old_val = ((p.0[0] as f32 / 65535.0) + error_rows[0][xu]).clamp(0.0, 1.0);
let new_val = (old_val * sh).floor() / sh; // quantize the current value + error
let error = old_val - new_val;
// Find the color in the palette and write it to output
let color = palette[(new_val * (palette.len() - 1) as f32) as usize];
output.put_pixel(x, y, color);
// spread the error around
if x + 2 < width {
error_rows[0][xu + 2] += error / 8.0
}
if x + 1 < width {
error_rows[0][xu + 1] += error / 8.0;
error_rows[1][xu + 1] += error / 8.0
}
error_rows[1][xu] += error / 8.0;
error_rows[2][xu] += error / 8.0;
if x > 0 {
error_rows[1][xu - 1] += error / 8.0
}
}
output.save_with_format("atkinson-dithered.png", image::ImageFormat::Png).unwrap()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment