1use image::{DynamicImage, ImageBuffer, Luma, Rgb};
4
5pub fn convert_to_binary(image: &DynamicImage) -> ImageBuffer<Luma<u8>, Vec<u8>> {
7 let gray = image.to_luma8();
8 let (width, height) = (gray.width(), gray.height());
9 let mut binary = ImageBuffer::new(width, height);
10
11 let threshold = calculate_threshold(&gray);
13
14 for (x, y, pixel) in gray.enumerate_pixels() {
15 let value = if pixel[0] > threshold { 255 } else { 0 };
16 binary.put_pixel(x, y, Luma([value]));
17 }
18
19 binary
20}
21
22fn calculate_threshold(gray: &ImageBuffer<Luma<u8>, Vec<u8>>) -> u8 {
24 let sum: u64 = gray.pixels().map(|p| p[0] as u64).sum();
25 let count = (gray.width() * gray.height()) as u64;
26 (sum / count) as u8
27}
28
29pub fn apply_halftone(image: &DynamicImage) -> ImageBuffer<Luma<u8>, Vec<u8>> {
31 let gray = image.to_luma8();
32 let (width, height) = (gray.width(), gray.height());
33 let mut halftone = ImageBuffer::new(width, height);
34
35 let dither_matrix = [[0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1, 9], [15, 7, 13, 5]];
37
38 for y in 0..height {
39 for x in 0..width {
40 let pixel = gray.get_pixel(x, y)[0];
41 let threshold = dither_matrix[(y % 4) as usize][(x % 4) as usize] * 16;
42 let value = if pixel as u16 > threshold { 255 } else { 0 };
43 halftone.put_pixel(x, y, Luma([value]));
44 }
45 }
46
47 halftone
48}
49
50pub fn expand_pixel(value: u8, block_size: usize) -> Vec<Vec<u8>> {
52 vec![vec![value; block_size]; block_size]
53}
54
55pub fn resize_to_match(
57 image: &DynamicImage,
58 target_width: u32,
59 target_height: u32,
60) -> DynamicImage {
61 if image.width() == target_width && image.height() == target_height {
62 image.clone()
63 } else {
64 image.resize_exact(
65 target_width,
66 target_height,
67 image::imageops::FilterType::Lanczos3,
68 )
69 }
70}
71
72pub fn create_noise_image(width: u32, height: u32) -> ImageBuffer<Luma<u8>, Vec<u8>> {
74 use rand::Rng;
75 let mut rng = rand::thread_rng();
76 let mut img = ImageBuffer::new(width, height);
77
78 for y in 0..height {
79 for x in 0..width {
80 let value = if rng.gen_bool(0.5) { 0 } else { 255 };
81 img.put_pixel(x, y, Luma([value]));
82 }
83 }
84
85 img
86}
87
88pub fn apply_pattern_to_block(
90 image: &mut ImageBuffer<Luma<u8>, Vec<u8>>,
91 x: u32,
92 y: u32,
93 block_size: usize,
94 pattern: &[u8],
95) {
96 for dy in 0..block_size {
97 for dx in 0..block_size {
98 let idx = dy * block_size + dx;
99 if idx < pattern.len() {
100 let value = if pattern[idx] == 1 { 0 } else { 255 };
101 image.put_pixel(x + dx as u32, y + dy as u32, Luma([value]));
102 }
103 }
104 }
105}
106
107pub fn calculate_contrast(image: &ImageBuffer<Luma<u8>, Vec<u8>>) -> f32 {
109 let (width, height) = (image.width(), image.height());
110 let total_pixels = (width * height) as f32;
111
112 let black_pixels = image.pixels().filter(|p| p[0] == 0).count() as f32;
113 let white_pixels = total_pixels - black_pixels;
114
115 (white_pixels - black_pixels).abs() / total_pixels
116}
117
118pub fn combine_rgb_channels(
120 r: &ImageBuffer<Luma<u8>, Vec<u8>>,
121 g: &ImageBuffer<Luma<u8>, Vec<u8>>,
122 b: &ImageBuffer<Luma<u8>, Vec<u8>>,
123) -> ImageBuffer<Rgb<u8>, Vec<u8>> {
124 assert_eq!(r.dimensions(), g.dimensions());
125 assert_eq!(r.dimensions(), b.dimensions());
126
127 let (width, height) = r.dimensions();
128 let mut rgb = ImageBuffer::new(width, height);
129
130 for y in 0..height {
131 for x in 0..width {
132 let r_val = r.get_pixel(x, y)[0];
133 let g_val = g.get_pixel(x, y)[0];
134 let b_val = b.get_pixel(x, y)[0];
135 rgb.put_pixel(x, y, Rgb([r_val, g_val, b_val]));
136 }
137 }
138
139 rgb
140}
141
142pub fn extract_channel(image: &DynamicImage, channel: usize) -> ImageBuffer<Luma<u8>, Vec<u8>> {
144 let rgb = image.to_rgb8();
145 let (width, height) = rgb.dimensions();
146 let mut channel_img = ImageBuffer::new(width, height);
147
148 for y in 0..height {
149 for x in 0..width {
150 let pixel = rgb.get_pixel(x, y);
151 channel_img.put_pixel(x, y, Luma([pixel[channel]]));
152 }
153 }
154
155 channel_img
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn test_convert_to_binary() {
164 let gray = DynamicImage::new_luma8(10, 10);
165 let binary = convert_to_binary(&gray);
166
167 for pixel in binary.pixels() {
169 assert!(pixel[0] == 0 || pixel[0] == 255);
170 }
171 }
172
173 #[test]
174 fn test_expand_pixel() {
175 let expanded = expand_pixel(255, 3);
176 assert_eq!(expanded.len(), 3);
177 assert_eq!(expanded[0].len(), 3);
178 assert_eq!(expanded[0][0], 255);
179 }
180
181 #[test]
182 fn test_create_noise_image() {
183 let noise = create_noise_image(100, 100);
184 assert_eq!(noise.dimensions(), (100, 100));
185
186 let has_black = noise.pixels().any(|p| p[0] == 0);
188 let has_white = noise.pixels().any(|p| p[0] == 255);
189 assert!(has_black);
190 assert!(has_white);
191 }
192}