summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorache <ache@ache.one>2025-04-03 02:18:38 +0200
committerache <ache@ache.one>2025-04-03 02:18:38 +0200
commit1abfb4faffd7f4e43166cbadbccbafbd570630f3 (patch)
tree93963510e620dcde7530dc12bfbacb51d26fecfd
Init commit
-rw-r--r--Cargo.toml7
-rw-r--r--david.pngbin0 -> 27218 bytes
-rw-r--r--src/main.rs80
3 files changed, 87 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..cb0a9de
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "dither"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+image = "0.25.6"
diff --git a/david.png b/david.png
new file mode 100644
index 0000000..6cfa884
--- /dev/null
+++ b/david.png
Binary files differ
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..46e2063
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,80 @@
+// Implement a simple dithering algorithm
+use image::{DynamicImage, ImageReader, Rgb, RgbImage};
+use std::env;
+use std::io::Cursor;
+
+fn dither_atkinson(image: &mut RgbImage) {
+ let (width, height) = image.dimensions();
+ let mut error: Vec<f32> = vec![0.; (3 * width) as usize];
+
+ for y in 0..height {
+ for x in 0..width {
+ let Rgb([r, g, b]) = image.get_pixel(x, y);
+ let pixel = (*r as u32 + *g as u32 + *b as u32) as f32 / 3.;
+ let err_pos: u32 = (y * width + x) % (3 * width);
+
+ let (pixel, err) = if (pixel + error[err_pos as usize]) > 127. {
+ (255, (pixel + error[err_pos as usize] - 255.) / 8.)
+ } else {
+ (0, (pixel + error[err_pos as usize]) / 8.)
+ };
+
+ error[err_pos as usize] = 0.;
+ error[((err_pos + 1) % (3 * width)) as usize] += err;
+ error[((err_pos + 2) % (3 * width)) as usize] += err;
+ error[((err_pos + width + 1) % (3 * width)) as usize] += err;
+ error[((err_pos + width - 1) % (3 * width)) as usize] += err;
+ error[((err_pos + width) % (3 * width)) as usize] += err;
+ error[((err_pos + 2 * width) % (3 * width)) as usize] += err;
+
+ image.put_pixel(x, y, image::Rgb([pixel, pixel, pixel]));
+ }
+ }
+}
+
+fn dither_atkinson_1_level(image: &mut RgbImage) {
+ let (width, height) = image.dimensions();
+ let mut error: Vec<f32> = vec![0.; (3 * width) as usize];
+
+ for y in 0..height {
+ for x in 0..width {
+ let Rgb([r, g, b]) = image.get_pixel(x, y);
+ let pixel = (*r as u32 + *g as u32 + *b as u32) as f32 / 3.;
+ let err_pos: u32 = (y * width + x) % (3 * width);
+
+ let (pixel, err) = if (pixel + error[err_pos as usize]) > 127. {
+ (255, (pixel - 255.) / 8.)
+ } else {
+ (0, pixel / 8.)
+ };
+
+ error[err_pos as usize] = 0.;
+ error[((err_pos + 1) % (3 * width)) as usize] += err;
+ error[((err_pos + 2) % (3 * width)) as usize] += err;
+ error[((err_pos + width + 1) % (3 * width)) as usize] += err;
+ error[((err_pos + width - 1) % (3 * width)) as usize] += err;
+ error[((err_pos + width) % (3 * width)) as usize] += err;
+ error[((err_pos + 2 * width) % (3 * width)) as usize] += err;
+
+ image.put_pixel(x, y, image::Rgb([pixel, pixel, pixel]));
+ }
+ }
+}
+
+fn main() -> Result<(), image::ImageError> {
+ let args: Vec<String> = env::args().collect();
+
+ let image_path = format!("{}.jpg", &args[1]);
+ let image_out = format!("{}_dithered.jpg", &args[1]);
+ let image_out_1l = format!("{}_dithered_1l.jpg", &args[1]);
+
+ let mut img = ImageReader::open(&image_path)?.decode()?.into_rgb8();
+ dither_atkinson(&mut img);
+ img.save(image_out)?;
+
+ let mut img = ImageReader::open(&image_path)?.decode()?.into_rgb8();
+ dither_atkinson_1_level(&mut img);
+ img.save(image_out_1l)?;
+
+ Ok(())
+}