visual_cryptography/
qr_code.rs

1//! QR code integration for visual cryptography (QEVCS)
2//!
3//! This module implements the QR code-based Expansion-free Extended Visual
4//! Cryptography Scheme (QEVCS) as described in the research paper.
5
6use crate::{
7    error::{Result, VCError},
8    share::Share,
9    utils::{convert_to_binary, resize_to_match},
10};
11use image::{DynamicImage, GenericImageView, ImageBuffer, Luma, Rgb};
12use qrcode::{EcLevel, QrCode};
13
14/// QR code error correction levels
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum QrErrorCorrection {
17    Low,      // ~7%
18    Medium,   // ~15%
19    Quartile, // ~25%
20    High,     // ~30%
21}
22
23impl From<QrErrorCorrection> for EcLevel {
24    fn from(level: QrErrorCorrection) -> Self {
25        match level {
26            QrErrorCorrection::Low => EcLevel::L,
27            QrErrorCorrection::Medium => EcLevel::M,
28            QrErrorCorrection::Quartile => EcLevel::Q,
29            QrErrorCorrection::High => EcLevel::H,
30        }
31    }
32}
33
34/// Configuration for QR code-based visual cryptography
35#[derive(Debug, Clone)]
36pub struct QrVcConfig {
37    /// Error correction level for QR codes
38    pub error_correction: QrErrorCorrection,
39    /// Size of each QR code module in pixels
40    pub module_size: u32,
41    /// Border size around QR code
42    pub border: u32,
43    /// Whether to embed shares in error correction area
44    pub use_error_correction_embedding: bool,
45    /// Maximum data capacity utilization (0.0 to 1.0)
46    pub data_capacity_ratio: f32,
47}
48
49impl Default for QrVcConfig {
50    fn default() -> Self {
51        Self {
52            error_correction: QrErrorCorrection::Medium,
53            module_size: 4,
54            border: 4,
55            use_error_correction_embedding: true,
56            data_capacity_ratio: 0.8,
57        }
58    }
59}
60
61/// QR code with embedded visual cryptography share
62#[derive(Debug, Clone)]
63pub struct QrShare {
64    /// The visual cryptography share
65    pub share: Share,
66    /// The QR code data
67    pub qr_data: String,
68    /// The combined QR code image
69    pub qr_image: DynamicImage,
70    /// Metadata about the embedding
71    pub metadata: QrEmbeddingMetadata,
72}
73
74/// Metadata about QR code embedding
75#[derive(Debug, Clone)]
76pub struct QrEmbeddingMetadata {
77    /// Original QR code dimensions
78    pub original_qr_size: (u32, u32),
79    /// Share dimensions
80    pub share_size: (u32, u32),
81    /// Number of bits embedded in error correction area
82    pub embedded_bits: usize,
83    /// QR code version
84    pub qr_version: String,
85    /// Data capacity utilization
86    pub capacity_used: f32,
87}
88
89/// QR code-based visual cryptography processor
90pub struct QrVisualCryptography {
91    config: QrVcConfig,
92}
93
94impl QrVisualCryptography {
95    /// Create a new QR-based visual cryptography processor
96    pub fn new(config: QrVcConfig) -> Self {
97        Self { config }
98    }
99
100    /// Generate QR codes with embedded visual cryptography shares
101    pub fn generate_qr_shares(&self, shares: &[Share], qr_data: &[String]) -> Result<Vec<QrShare>> {
102        if shares.len() != qr_data.len() {
103            return Err(VCError::InvalidConfiguration(
104                "Number of shares must match number of QR data strings".to_string(),
105            ));
106        }
107
108        let mut qr_shares = Vec::new();
109
110        for (share, data) in shares.iter().zip(qr_data.iter()) {
111            let qr_share = self.embed_share_in_qr(share, data)?;
112            qr_shares.push(qr_share);
113        }
114
115        Ok(qr_shares)
116    }
117
118    /// Embed a visual cryptography share into a QR code
119    fn embed_share_in_qr(&self, share: &Share, qr_data: &str) -> Result<QrShare> {
120        // Generate base QR code
121        let qr_code =
122            QrCode::with_error_correction_level(qr_data, self.config.error_correction.into())
123                .map_err(|e| VCError::QrCodeError(format!("Failed to generate QR code: {}", e)))?;
124
125        // Convert QR code to image
126        let qr_image = self.qr_to_image(&qr_code);
127        let (qr_width, qr_height) = qr_image.dimensions();
128
129        // Resize share to match QR code dimensions
130        let resized_share = resize_to_match(&share.image, qr_width, qr_height);
131        let share_binary = convert_to_binary(&resized_share);
132
133        // Embed share data using the QEVCS algorithm
134        let embedded_image = if self.config.use_error_correction_embedding {
135            self.embed_using_error_correction(&qr_image, &share_binary, &qr_code)?
136        } else {
137            self.embed_using_module_replacement(&qr_image, &share_binary)?
138        };
139
140        let metadata = QrEmbeddingMetadata {
141            original_qr_size: (qr_width, qr_height),
142            share_size: share.dimensions(),
143            embedded_bits: self.count_embedded_bits(&share_binary),
144            qr_version: format!("{:?}", qr_code.version()),
145            capacity_used: self.calculate_capacity_usage(&qr_code, &share_binary),
146        };
147
148        Ok(QrShare {
149            share: share.clone(),
150            qr_data: qr_data.to_string(),
151            qr_image: embedded_image,
152            metadata,
153        })
154    }
155
156    /// Embed share using error correction area (QEVCS method)
157    fn embed_using_error_correction(
158        &self,
159        qr_image: &DynamicImage,
160        share_binary: &ImageBuffer<Luma<u8>, Vec<u8>>,
161        qr_code: &QrCode,
162    ) -> Result<DynamicImage> {
163        let qr_gray = qr_image.to_luma8();
164        let (width, height) = qr_gray.dimensions();
165        let mut result = qr_gray.clone();
166
167        // Get QR code module information
168        let modules = qr_code.to_vec();
169        let module_count = qr_code.width();
170        let scale_x = width as f32 / module_count as f32;
171        let scale_y = height as f32 / module_count as f32;
172
173        // Create a map of error correction modules
174        let error_correction_modules =
175            self.identify_error_correction_modules(&modules, module_count);
176
177        // Embed share data in error correction modules
178        for y in 0..height {
179            for x in 0..width {
180                let module_x = (x as f32 / scale_x) as usize;
181                let module_y = (y as f32 / scale_y) as usize;
182
183                if module_x < module_count && module_y < module_count {
184                    let module_idx = module_y * module_count + module_x;
185
186                    if error_correction_modules.contains(&module_idx) {
187                        // This is an error correction module, we can modify it
188                        let share_pixel = share_binary
189                            .get_pixel(x % share_binary.width(), y % share_binary.height())[0];
190
191                        // Blend QR code and share data
192                        let qr_pixel = qr_gray.get_pixel(x, y)[0];
193                        let blended = self.blend_pixels(qr_pixel, share_pixel);
194                        result.put_pixel(x, y, Luma([blended]));
195                    }
196                }
197            }
198        }
199
200        Ok(DynamicImage::ImageLuma8(result))
201    }
202
203    /// Embed share using direct module replacement
204    fn embed_using_module_replacement(
205        &self,
206        qr_image: &DynamicImage,
207        share_binary: &ImageBuffer<Luma<u8>, Vec<u8>>,
208    ) -> Result<DynamicImage> {
209        let qr_gray = qr_image.to_luma8();
210        let (width, height) = qr_gray.dimensions();
211        let mut result = qr_gray.clone();
212
213        // Simple alpha blending approach
214        for y in 0..height {
215            for x in 0..width {
216                let qr_pixel = qr_gray.get_pixel(x, y)[0];
217                let share_pixel =
218                    share_binary.get_pixel(x % share_binary.width(), y % share_binary.height())[0];
219
220                // Weighted blend: prioritize QR code structure but include share information
221                let alpha = self.config.data_capacity_ratio;
222                let blended = ((1.0 - alpha) * qr_pixel as f32 + alpha * share_pixel as f32) as u8;
223                result.put_pixel(x, y, Luma([blended]));
224            }
225        }
226
227        Ok(DynamicImage::ImageLuma8(result))
228    }
229
230    /// Extract visual cryptography shares from QR codes
231    pub fn extract_shares_from_qr(&self, qr_shares: &[QrShare]) -> Result<Vec<Share>> {
232        let mut extracted_shares = Vec::new();
233
234        for qr_share in qr_shares {
235            let share = self.extract_share_from_qr_image(&qr_share.qr_image, &qr_share.metadata)?;
236            extracted_shares.push(share);
237        }
238
239        Ok(extracted_shares)
240    }
241
242    /// Extract a single share from a QR code image
243    fn extract_share_from_qr_image(
244        &self,
245        qr_image: &DynamicImage,
246        metadata: &QrEmbeddingMetadata,
247    ) -> Result<Share> {
248        let qr_gray = qr_image.to_luma8();
249        let (qr_width, qr_height) = qr_gray.dimensions();
250
251        // Extract the embedded share data
252        let mut extracted_data = ImageBuffer::new(metadata.share_size.0, metadata.share_size.1);
253
254        for y in 0..metadata.share_size.1 {
255            for x in 0..metadata.share_size.0 {
256                // Map share coordinates to QR code coordinates
257                let qr_x = (x * qr_width) / metadata.share_size.0;
258                let qr_y = (y * qr_height) / metadata.share_size.1;
259
260                let pixel = qr_gray.get_pixel(qr_x.min(qr_width - 1), qr_y.min(qr_height - 1))[0];
261
262                // Apply extraction filter to recover share data
263                let extracted_pixel = self.extract_pixel(pixel);
264                extracted_data.put_pixel(x, y, Luma([extracted_pixel]));
265            }
266        }
267
268        Ok(Share::new(
269            DynamicImage::ImageLuma8(extracted_data),
270            1, // Index would need to be tracked separately
271            1, // Total shares would need to be tracked separately
272            metadata.share_size.0,
273            metadata.share_size.1,
274            1,
275            true, // QR shares are meaningful
276        ))
277    }
278
279    /// Decode QR code data from QR share
280    pub fn decode_qr_data(&self, qr_share: &QrShare) -> Result<String> {
281        // For now, return the stored QR data
282        // In a full implementation, this would decode the QR image
283        Ok(qr_share.qr_data.clone())
284    }
285
286    /// Convert QR code to image with proper scaling
287    fn qr_to_image(&self, qr_code: &QrCode) -> DynamicImage {
288        let modules = qr_code.to_vec();
289        let module_count = qr_code.width();
290
291        let image_size = (module_count as u32 + 2 * self.config.border) * self.config.module_size;
292        let mut img = ImageBuffer::new(image_size, image_size);
293
294        // Fill with white background
295        for pixel in img.pixels_mut() {
296            *pixel = Luma([255u8]);
297        }
298
299        // Draw QR modules
300        for y in 0..module_count {
301            for x in 0..module_count {
302                if modules[y * module_count + x] {
303                    // Black module
304                    let start_x = (x as u32 + self.config.border) * self.config.module_size;
305                    let start_y = (y as u32 + self.config.border) * self.config.module_size;
306
307                    for dy in 0..self.config.module_size {
308                        for dx in 0..self.config.module_size {
309                            img.put_pixel(start_x + dx, start_y + dy, Luma([0u8]));
310                        }
311                    }
312                }
313            }
314        }
315
316        DynamicImage::ImageLuma8(img)
317    }
318
319    /// Identify error correction modules in QR code
320    fn identify_error_correction_modules(
321        &self,
322        modules: &[bool],
323        module_count: usize,
324    ) -> Vec<usize> {
325        let mut error_correction_modules = Vec::new();
326
327        // This is a simplified approach - in a full implementation,
328        // you would need to properly identify the error correction areas
329        // based on QR code specification
330
331        // For now, identify modules that are not part of finder patterns,
332        // timing patterns, or format information
333        for y in 0..module_count {
334            for x in 0..module_count {
335                if !self.is_function_module(x, y, module_count) {
336                    error_correction_modules.push(y * module_count + x);
337                }
338            }
339        }
340
341        error_correction_modules
342    }
343
344    /// Check if a module is a function module (finder pattern, timing, etc.)
345    fn is_function_module(&self, x: usize, y: usize, size: usize) -> bool {
346        // Finder patterns (top-left, top-right, bottom-left)
347        if (x < 9 && y < 9) || (x >= size - 8 && y < 9) || (x < 9 && y >= size - 8) {
348            return true;
349        }
350
351        // Timing patterns
352        if (x == 6 && y >= 8 && y < size - 8) || (y == 6 && x >= 8 && x < size - 8) {
353            return true;
354        }
355
356        // Dark module (always at position (4*version + 9, 8))
357        // This is simplified - would need proper version detection
358
359        false
360    }
361
362    /// Blend QR code pixel with share pixel
363    fn blend_pixels(&self, qr_pixel: u8, share_pixel: u8) -> u8 {
364        // Use different blending strategies based on configuration
365        let alpha = self.config.data_capacity_ratio;
366
367        if self.config.use_error_correction_embedding {
368            // More sophisticated blending for error correction embedding
369            if qr_pixel == 0 && share_pixel == 0 {
370                0 // Both black - keep black
371            } else if qr_pixel == 255 && share_pixel == 255 {
372                255 // Both white - keep white
373            } else {
374                // Mixed - use weighted average
375                ((1.0 - alpha) * qr_pixel as f32 + alpha * share_pixel as f32) as u8
376            }
377        } else {
378            // Simple weighted blend
379            ((1.0 - alpha) * qr_pixel as f32 + alpha * share_pixel as f32) as u8
380        }
381    }
382
383    /// Extract pixel from blended QR/share data
384    fn extract_pixel(&self, blended_pixel: u8) -> u8 {
385        // This would use the inverse of the blending function
386        // For now, use simple thresholding
387        if blended_pixel > 127 {
388            255
389        } else {
390            0
391        }
392    }
393
394    /// Count number of bits embedded in share
395    fn count_embedded_bits(&self, share_binary: &ImageBuffer<Luma<u8>, Vec<u8>>) -> usize {
396        share_binary.pixels().filter(|pixel| pixel[0] == 0).count()
397    }
398
399    /// Calculate capacity usage
400    fn calculate_capacity_usage(
401        &self,
402        qr_code: &QrCode,
403        share_binary: &ImageBuffer<Luma<u8>, Vec<u8>>,
404    ) -> f32 {
405        let total_modules = (qr_code.width() * qr_code.width()) as f32;
406        let embedded_bits = self.count_embedded_bits(share_binary) as f32;
407        embedded_bits / total_modules
408    }
409}
410
411/// Create a simple QR share for testing
412pub fn create_test_qr_share(data: &str, share_data: Vec<u8>) -> Result<QrShare> {
413    let config = QrVcConfig::default();
414    let qr_processor = QrVisualCryptography::new(config);
415
416    // Create a dummy share
417    let width = 100;
418    let height = 100;
419    let mut share_img = ImageBuffer::new(width, height);
420
421    for (i, pixel) in share_img.pixels_mut().enumerate() {
422        let value = if i < share_data.len() && share_data[i] == 1 {
423            0
424        } else {
425            255
426        };
427        *pixel = Luma([value]);
428    }
429
430    let share = Share::new(
431        DynamicImage::ImageLuma8(share_img),
432        1,
433        1,
434        width,
435        height,
436        1,
437        true,
438    );
439
440    qr_processor.embed_share_in_qr(&share, data)
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446
447    #[test]
448    fn test_qr_vc_config_default() {
449        let config = QrVcConfig::default();
450        assert_eq!(config.error_correction, QrErrorCorrection::Medium);
451        assert_eq!(config.module_size, 4);
452    }
453
454    #[test]
455    fn test_create_test_qr_share() {
456        let share_data = vec![1, 0, 1, 0, 1];
457        let qr_share = create_test_qr_share("Hello World", share_data);
458        assert!(qr_share.is_ok());
459
460        let qr_share = qr_share.unwrap();
461        assert_eq!(qr_share.qr_data, "Hello World");
462    }
463
464    #[test]
465    fn test_qr_visual_cryptography_creation() {
466        let config = QrVcConfig::default();
467        let qr_vc = QrVisualCryptography::new(config);
468
469        // Test that we can create the processor
470        assert_eq!(qr_vc.config.error_correction, QrErrorCorrection::Medium);
471    }
472}