visual_cryptography/
utils.rs

1//! Utility functions for visual cryptography
2
3use image::{DynamicImage, ImageBuffer, Luma, Rgb};
4
5/// Convert an image to binary (black and white)
6pub 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    // Use Otsu's method or simple threshold
12    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
22/// Calculate threshold for binary conversion using simple mean
23fn 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
29/// Apply halftone to an image
30pub 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    // Simple ordered dithering matrix (Bayer matrix)
36    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
50/// Expand a pixel into a block
51pub fn expand_pixel(value: u8, block_size: usize) -> Vec<Vec<u8>> {
52    vec![vec![value; block_size]; block_size]
53}
54
55/// Resize an image to match target dimensions
56pub 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
72/// Create a random noise image
73pub 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
88/// Apply a pattern to a block of pixels
89pub 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
107/// Calculate the contrast between two regions
108pub 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
118/// Combine RGB channels into a color image
119pub 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
142/// Extract a color channel from an RGB image
143pub 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        // All pixels should be either 0 or 255
168        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        // Should have both black and white pixels
187        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}