visual_cryptography/
algorithms.rs

1//! Visual cryptography algorithms implementation
2
3use crate::{
4    error::{Result, VCError},
5    matrix::{
6        generate_basic_matrices, generate_color_mixing_matrices, generate_dispatching_matrices,
7        generate_proper_sharing_matrices, generate_xor_matrices, select_dispatching_row,
8        ColorMixingMatrices, XorMatrices,
9    },
10    share::{stack_shares, Share},
11    utils::{apply_halftone, convert_to_binary, expand_pixel},
12    VCConfig,
13};
14use image::{DynamicImage, ImageBuffer, Luma, Rgb};
15use rand::{seq::SliceRandom, Rng};
16
17/// Available visual cryptography algorithms
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Algorithm {
20    /// Basic (k,n) threshold scheme
21    BasicThreshold,
22    /// Progressive visual cryptography
23    Progressive,
24    /// Extended visual cryptography with meaningful shares
25    ExtendedMeaningful,
26    /// Naor-Shamir original scheme (1994)
27    NaorShamir,
28    /// Taghaddos-Latif for grayscale
29    TaghaddosLatif,
30    /// Dhiman-Kasana for color images
31    DhimanKasana,
32    /// XOR-based scheme for better contrast
33    XorBased,
34}
35
36/// Trait for visual cryptography schemes
37pub trait VCScheme {
38    /// Encrypt an image into shares
39    fn encrypt(
40        &self,
41        image: &DynamicImage,
42        config: &VCConfig,
43        cover_images: Option<Vec<DynamicImage>>,
44    ) -> Result<Vec<Share>>;
45
46    /// Decrypt shares back into an image
47    fn decrypt(&self, shares: &[Share], config: &VCConfig) -> Result<DynamicImage>;
48}
49
50/// Main encryption function that dispatches to the appropriate algorithm
51pub fn encrypt(
52    image: &DynamicImage,
53    config: &VCConfig,
54    cover_images: Option<Vec<DynamicImage>>,
55) -> Result<Vec<Share>> {
56    match config.algorithm {
57        Algorithm::BasicThreshold => basic_threshold_encrypt(image, config),
58        Algorithm::Progressive => progressive_encrypt(image, config, cover_images),
59        Algorithm::ExtendedMeaningful => extended_meaningful_encrypt(image, config, cover_images),
60        Algorithm::NaorShamir => naor_shamir_encrypt(image, config),
61        Algorithm::TaghaddosLatif => taghaddos_latif_encrypt(image, config),
62        Algorithm::DhimanKasana => dhiman_kasana_encrypt(image, config, cover_images),
63        Algorithm::XorBased => xor_based_encrypt(image, config),
64    }
65}
66
67/// Main decryption function that dispatches to the appropriate algorithm
68pub fn decrypt(shares: &[Share], config: &VCConfig) -> Result<DynamicImage> {
69    match config.algorithm {
70        Algorithm::BasicThreshold => basic_threshold_decrypt(shares, config),
71        Algorithm::Progressive => progressive_decrypt(shares, config),
72        Algorithm::ExtendedMeaningful => extended_meaningful_decrypt(shares, config),
73        Algorithm::NaorShamir => naor_shamir_decrypt(shares, config),
74        Algorithm::TaghaddosLatif => taghaddos_latif_decrypt(shares, config),
75        Algorithm::DhimanKasana => dhiman_kasana_decrypt(shares, config),
76        Algorithm::XorBased => xor_based_decrypt(shares, config),
77    }
78}
79
80/// XOR-based encryption for better contrast
81fn xor_based_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
82    let binary = convert_to_binary(image);
83    let (width, height) = (binary.width(), binary.height());
84
85    // Generate XOR matrices
86    let xor_matrices = generate_xor_matrices(config.num_shares)?;
87
88    let mut shares = Vec::new();
89    for i in 0..config.num_shares {
90        shares.push(ImageBuffer::new(width, height));
91    }
92
93    let mut rng = rand::thread_rng();
94
95    // Process each pixel
96    for y in 0..height {
97        for x in 0..width {
98            let pixel = binary.get_pixel(x, y)[0];
99            let is_black = pixel == 0;
100
101            // Select appropriate matrix
102            let matrix = if is_black {
103                &xor_matrices.black_pixel
104            } else {
105                &xor_matrices.white_pixel
106            };
107
108            // Select random column
109            let col = rng.gen_range(0..matrix.ncols());
110
111            // Distribute values to shares
112            for share_idx in 0..config.num_shares {
113                let value = matrix[(share_idx, col)];
114                let pixel_value = if value == 1 { 0u8 } else { 255u8 };
115                shares[share_idx].put_pixel(x, y, Luma([pixel_value]));
116            }
117        }
118    }
119
120    // Convert to Share objects
121    let result: Vec<Share> = shares
122        .into_iter()
123        .enumerate()
124        .map(|(i, img)| {
125            Share::new(
126                DynamicImage::ImageLuma8(img),
127                i + 1,
128                config.num_shares,
129                width,
130                height,
131                1,
132                false,
133            )
134        })
135        .collect();
136
137    Ok(result)
138}
139
140/// XOR-based decryption
141fn xor_based_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
142    if shares.is_empty() {
143        return Err(VCError::InsufficientShares {
144            required: 1,
145            provided: 0,
146        });
147    }
148
149    let (width, height) = shares[0].dimensions();
150    let mut result = ImageBuffer::new(width, height);
151
152    for y in 0..height {
153        for x in 0..width {
154            let mut xor_value = 0u8;
155
156            for share in shares {
157                if let DynamicImage::ImageLuma8(img) = &share.image {
158                    let pixel = img.get_pixel(x, y)[0];
159                    let bit = if pixel == 0 { 1 } else { 0 };
160                    xor_value ^= bit;
161                }
162            }
163
164            // XOR result: 1 = black, 0 = white
165            let pixel_value = if xor_value == 1 { 0u8 } else { 255u8 };
166            result.put_pixel(x, y, Luma([pixel_value]));
167        }
168    }
169
170    Ok(DynamicImage::ImageLuma8(result))
171}
172
173/// Basic (k,n) threshold scheme encryption
174fn basic_threshold_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
175    // Convert to binary image
176    let binary = convert_to_binary(image);
177    let (width, height) = (binary.width(), binary.height());
178
179    // Generate sharing matrices
180    let matrices = generate_basic_matrices(config.threshold, config.num_shares, config.block_size)?;
181
182    // Create shares with expanded dimensions
183    let share_width = width * config.block_size as u32;
184    let share_height = height * config.block_size as u32;
185
186    let mut shares = Vec::new();
187    for i in 0..config.num_shares {
188        shares.push(ImageBuffer::new(share_width, share_height));
189    }
190
191    let mut rng = rand::thread_rng();
192
193    // Process each pixel
194    for y in 0..height {
195        for x in 0..width {
196            let pixel = binary.get_pixel(x, y)[0];
197            let matrix_idx = if pixel == 0 { 1 } else { 0 }; // black = 0, white = 255
198            let matrix = &matrices[matrix_idx];
199
200            // Select a random column from the matrix
201            let col = rng.gen_range(0..matrix.ncols());
202
203            // Distribute the column values to shares
204            for share_idx in 0..config.num_shares {
205                let value = matrix[(share_idx, col)];
206                let block_value = if value == 1 { 0u8 } else { 255u8 };
207
208                // Expand pixel to block
209                let base_x = x * config.block_size as u32;
210                let base_y = y * config.block_size as u32;
211
212                for dy in 0..config.block_size as u32 {
213                    for dx in 0..config.block_size as u32 {
214                        shares[share_idx].put_pixel(base_x + dx, base_y + dy, Luma([block_value]));
215                    }
216                }
217            }
218        }
219    }
220
221    // Convert to Share objects
222    let result: Vec<Share> = shares
223        .into_iter()
224        .enumerate()
225        .map(|(i, img)| {
226            Share::new(
227                DynamicImage::ImageLuma8(img),
228                i + 1,
229                config.num_shares,
230                width,
231                height,
232                config.block_size,
233                false,
234            )
235        })
236        .collect();
237
238    Ok(result)
239}
240
241/// Basic threshold scheme decryption
242fn basic_threshold_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
243    if let Some(stacked) = stack_shares(shares) {
244        Ok(DynamicImage::ImageLuma8(stacked))
245    } else {
246        Err(VCError::DecryptionError(
247            "Failed to stack shares".to_string(),
248        ))
249    }
250}
251
252/// Progressive visual cryptography encryption
253fn progressive_encrypt(
254    image: &DynamicImage,
255    config: &VCConfig,
256    cover_images: Option<Vec<DynamicImage>>,
257) -> Result<Vec<Share>> {
258    // Convert to binary
259    let binary = convert_to_binary(image);
260    let (width, height) = (binary.width(), binary.height());
261
262    // Use meaningful shares if cover images are provided
263    if let Some(covers) = cover_images {
264        if covers.len() != config.num_shares {
265            return Err(VCError::CoverImageError(format!(
266                "Number of cover images ({}) must match number of shares ({})",
267                covers.len(),
268                config.num_shares
269            )));
270        }
271
272        // Generate dispatching matrices
273        let i = config.num_shares; // Use n for maximum contrast
274        let matrices = generate_dispatching_matrices(config.num_shares, i)?;
275
276        let mut shares = Vec::new();
277
278        for share_idx in 0..config.num_shares {
279            let mut share_img = ImageBuffer::new(width, height);
280            let cover_binary = convert_to_binary(&covers[share_idx]);
281
282            for y in 0..height {
283                for x in 0..width {
284                    let secret_pixel = binary.get_pixel(x, y)[0] == 0; // true if black
285                    let cover_pixel = cover_binary.get_pixel(x, y)[0] == 0; // true if black
286
287                    let row = select_dispatching_row(&matrices, secret_pixel, cover_pixel);
288                    let value = if row[share_idx] == 1 { 0u8 } else { 255u8 };
289
290                    share_img.put_pixel(x, y, Luma([value]));
291                }
292            }
293
294            shares.push(Share::new(
295                DynamicImage::ImageLuma8(share_img),
296                share_idx + 1,
297                config.num_shares,
298                width,
299                height,
300                1,    // No pixel expansion
301                true, // Meaningful share
302            ));
303        }
304
305        Ok(shares)
306    } else {
307        // Use proper progressive matrix construction
308        progressive_matrix_based_encrypt(image, config)
309    }
310}
311
312/// Proper progressive encryption using matrix construction
313fn progressive_matrix_based_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
314    let binary = convert_to_binary(image);
315    let (width, height) = (binary.width(), binary.height());
316
317    // Generate proper sharing matrices for progressive revelation
318    let (white_matrix, black_matrix) = generate_proper_sharing_matrices(2, config.num_shares)?;
319
320    let mut shares = Vec::new();
321    for i in 0..config.num_shares {
322        shares.push(ImageBuffer::new(width, height));
323    }
324
325    let mut rng = rand::thread_rng();
326
327    // Process each pixel
328    for y in 0..height {
329        for x in 0..width {
330            let pixel = binary.get_pixel(x, y)[0];
331            let is_black = pixel == 0;
332
333            // Select appropriate matrix
334            let matrix = if is_black {
335                &black_matrix
336            } else {
337                &white_matrix
338            };
339
340            // For progressive schemes, weight column selection by share index
341            let total_cols = matrix.ncols();
342            let mut col_weights = Vec::new();
343
344            for col in 0..total_cols {
345                // Calculate weight based on how many participants have black pixels
346                let black_count = (0..config.num_shares)
347                    .map(|row| matrix[(row, col)] as usize)
348                    .sum::<usize>();
349
350                // Higher weight for patterns that progressively reveal
351                let weight = if is_black {
352                    (config.num_shares - black_count) + 1
353                } else {
354                    black_count + 1
355                };
356
357                col_weights.push(weight);
358            }
359
360            // Weighted random selection
361            let total_weight: usize = col_weights.iter().sum();
362            let mut random_weight = rng.gen_range(0..total_weight);
363            let mut selected_col = 0;
364
365            for (col, &weight) in col_weights.iter().enumerate() {
366                if random_weight < weight {
367                    selected_col = col;
368                    break;
369                }
370                random_weight -= weight;
371            }
372
373            // Apply selected column to shares
374            for share_idx in 0..config.num_shares {
375                let value = matrix[(share_idx, selected_col)];
376                let pixel_value = if value == 1 { 0u8 } else { 255u8 };
377                shares[share_idx].put_pixel(x, y, Luma([pixel_value]));
378            }
379        }
380    }
381
382    // Convert to Share objects
383    let result: Vec<Share> = shares
384        .into_iter()
385        .enumerate()
386        .map(|(i, img)| {
387            Share::new(
388                DynamicImage::ImageLuma8(img),
389                i + 1,
390                config.num_shares,
391                width,
392                height,
393                1,
394                false,
395            )
396        })
397        .collect();
398
399    Ok(result)
400}
401
402/// Progressive decryption
403fn progressive_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
404    basic_threshold_decrypt(shares, _config)
405}
406
407/// Extended meaningful shares encryption
408fn extended_meaningful_encrypt(
409    image: &DynamicImage,
410    config: &VCConfig,
411    cover_images: Option<Vec<DynamicImage>>,
412) -> Result<Vec<Share>> {
413    if cover_images.is_none() {
414        return Err(VCError::CoverImageError(
415            "Extended meaningful scheme requires cover images".to_string(),
416        ));
417    }
418
419    // Use proper extended VCS algorithm
420    let binary = convert_to_binary(image);
421    let (width, height) = (binary.width(), binary.height());
422    let covers = cover_images.unwrap();
423
424    // Generate dispatching matrices for extended scheme
425    let matrices = generate_dispatching_matrices(config.num_shares, config.num_shares)?;
426
427    let mut shares = Vec::new();
428
429    for share_idx in 0..config.num_shares {
430        let mut share_img = ImageBuffer::new(width, height);
431        let cover_binary = convert_to_binary(&covers[share_idx]);
432
433        for y in 0..height {
434            for x in 0..width {
435                let secret_pixel = binary.get_pixel(x, y)[0] == 0;
436                let cover_pixel = cover_binary.get_pixel(x, y)[0] == 0;
437
438                // Use extended algorithm logic
439                let row = select_dispatching_row(&matrices, secret_pixel, cover_pixel);
440                let value = if row[share_idx] == 1 { 0u8 } else { 255u8 };
441
442                share_img.put_pixel(x, y, Luma([value]));
443            }
444        }
445
446        shares.push(Share::new(
447            DynamicImage::ImageLuma8(share_img),
448            share_idx + 1,
449            config.num_shares,
450            width,
451            height,
452            1,
453            true,
454        ));
455    }
456
457    Ok(shares)
458}
459
460/// Extended meaningful decryption
461fn extended_meaningful_decrypt(shares: &[Share], config: &VCConfig) -> Result<DynamicImage> {
462    basic_threshold_decrypt(shares, config)
463}
464
465/// Naor-Shamir original scheme encryption
466fn naor_shamir_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
467    // The original Naor-Shamir is a (2,2) scheme with 2x2 pixel expansion
468    if config.num_shares != 2 || config.threshold != 2 {
469        return Err(VCError::InvalidConfiguration(
470            "Original Naor-Shamir scheme requires exactly 2 shares with threshold 2".to_string(),
471        ));
472    }
473
474    let binary = convert_to_binary(image);
475    let (width, height) = (binary.width(), binary.height());
476
477    // Fixed matrices for Naor-Shamir as per the original paper
478    // For white pixels: both shares get identical patterns
479    let white_matrix = vec![vec![1, 1, 0, 0], vec![1, 1, 0, 0]];
480    // For black pixels: shares get complementary patterns
481    let black_matrix = vec![vec![1, 1, 0, 0], vec![0, 0, 1, 1]];
482
483    let share_width = width * 2;
484    let share_height = height * 2;
485
486    let mut share1 = ImageBuffer::new(share_width, share_height);
487    let mut share2 = ImageBuffer::new(share_width, share_height);
488
489    let mut rng = rand::thread_rng();
490
491    for y in 0..height {
492        for x in 0..width {
493            let pixel = binary.get_pixel(x, y)[0];
494
495            // Select appropriate matrix based on pixel value
496            let matrix = if pixel == 0 {
497                // black pixel
498                &black_matrix
499            } else {
500                // white pixel
501                &white_matrix
502            };
503
504            // Randomly permute columns (this is the key step in Naor-Shamir)
505            let mut columns: Vec<usize> = (0..4).collect();
506            columns.as_mut_slice().shuffle(&mut rng);
507
508            // Create permuted patterns for both shares
509            let share1_pattern = vec![
510                matrix[0][columns[0]],
511                matrix[0][columns[1]],
512                matrix[0][columns[2]],
513                matrix[0][columns[3]],
514            ];
515            let share2_pattern = vec![
516                matrix[1][columns[0]],
517                matrix[1][columns[1]],
518                matrix[1][columns[2]],
519                matrix[1][columns[3]],
520            ];
521
522            // Apply 2x2 patterns to shares
523            // Share 1
524            share1.put_pixel(
525                x * 2,
526                y * 2,
527                Luma([if share1_pattern[0] == 1 { 0 } else { 255 }]),
528            );
529            share1.put_pixel(
530                x * 2 + 1,
531                y * 2,
532                Luma([if share1_pattern[1] == 1 { 0 } else { 255 }]),
533            );
534            share1.put_pixel(
535                x * 2,
536                y * 2 + 1,
537                Luma([if share1_pattern[2] == 1 { 0 } else { 255 }]),
538            );
539            share1.put_pixel(
540                x * 2 + 1,
541                y * 2 + 1,
542                Luma([if share1_pattern[3] == 1 { 0 } else { 255 }]),
543            );
544
545            // Share 2
546            share2.put_pixel(
547                x * 2,
548                y * 2,
549                Luma([if share2_pattern[0] == 1 { 0 } else { 255 }]),
550            );
551            share2.put_pixel(
552                x * 2 + 1,
553                y * 2,
554                Luma([if share2_pattern[1] == 1 { 0 } else { 255 }]),
555            );
556            share2.put_pixel(
557                x * 2,
558                y * 2 + 1,
559                Luma([if share2_pattern[2] == 1 { 0 } else { 255 }]),
560            );
561            share2.put_pixel(
562                x * 2 + 1,
563                y * 2 + 1,
564                Luma([if share2_pattern[3] == 1 { 0 } else { 255 }]),
565            );
566        }
567    }
568
569    Ok(vec![
570        Share::new(
571            DynamicImage::ImageLuma8(share1),
572            1,
573            2,
574            width,
575            height,
576            2,
577            false,
578        ),
579        Share::new(
580            DynamicImage::ImageLuma8(share2),
581            2,
582            2,
583            width,
584            height,
585            2,
586            false,
587        ),
588    ])
589}
590
591/// Naor-Shamir decryption
592fn naor_shamir_decrypt(shares: &[Share], config: &VCConfig) -> Result<DynamicImage> {
593    basic_threshold_decrypt(shares, config)
594}
595
596/// Taghaddos-Latif grayscale scheme following the original paper exactly
597fn taghaddos_latif_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
598    if config.num_shares != 2 {
599        return Err(VCError::InvalidConfiguration(
600            "Taghaddos-Latif scheme requires exactly 2 shares".to_string(),
601        ));
602    }
603
604    let gray = image.to_luma8();
605    let (width, height) = (gray.width(), gray.height());
606
607    // The 6 specific patterns from the original paper
608    let patterns = [
609        [1u8, 1u8, 0u8, 0u8],
610        [1u8, 0u8, 1u8, 0u8],
611        [1u8, 0u8, 0u8, 1u8],
612        [0u8, 1u8, 1u8, 0u8],
613        [0u8, 1u8, 0u8, 1u8],
614        [0u8, 0u8, 1u8, 1u8],
615    ];
616
617    // Pixel expansion by 2 (2x2 blocks)
618    let share_width = width * 2;
619    let share_height = height * 2;
620
621    let mut share_a = ImageBuffer::new(share_width, share_height);
622    let mut share_b = ImageBuffer::new(share_width, share_height);
623
624    let mut rng = rand::thread_rng();
625
626    // Process each pixel
627    for y in 0..height {
628        for x in 0..width {
629            let pixel_value = gray.get_pixel(x, y)[0];
630            let mut share_a_colors = [0u8; 4];
631            let mut share_b_colors = [0u8; 4];
632
633            // Process each bit plane (0-7)
634            for bit_pos in 0..8 {
635                let bit = (pixel_value >> bit_pos) & 1;
636
637                // Randomly select one of the 6 patterns
638                let pattern = patterns[rng.gen_range(0..6)];
639
640                if bit == 1 {
641                    // White pixel (bit = 1): both shares get identical patterns
642                    for i in 0..4 {
643                        share_a_colors[i] |= (pattern[i] << bit_pos);
644                        share_b_colors[i] = share_a_colors[i];
645                    }
646                } else {
647                    // Black pixel (bit = 0): share B gets complement of share A
648                    for i in 0..4 {
649                        share_a_colors[i] |= (pattern[i] << bit_pos);
650                        share_b_colors[i] |= ((1 - pattern[i]) << bit_pos);
651                    }
652                }
653            }
654
655            // Draw 2x2 blocks for each share
656            let base_x = x * 2;
657            let base_y = y * 2;
658
659            // Share A 2x2 block
660            share_a.put_pixel(base_x, base_y, Luma([share_a_colors[0]]));
661            share_a.put_pixel(base_x + 1, base_y, Luma([share_a_colors[1]]));
662            share_a.put_pixel(base_x, base_y + 1, Luma([share_a_colors[2]]));
663            share_a.put_pixel(base_x + 1, base_y + 1, Luma([share_a_colors[3]]));
664
665            // Share B 2x2 block
666            share_b.put_pixel(base_x, base_y, Luma([share_b_colors[0]]));
667            share_b.put_pixel(base_x + 1, base_y, Luma([share_b_colors[1]]));
668            share_b.put_pixel(base_x, base_y + 1, Luma([share_b_colors[2]]));
669            share_b.put_pixel(base_x + 1, base_y + 1, Luma([share_b_colors[3]]));
670        }
671    }
672
673    Ok(vec![
674        Share::new(
675            DynamicImage::ImageLuma8(share_a),
676            1,
677            2,
678            width,
679            height,
680            2, // pixel expansion = 2
681            false,
682        ),
683        Share::new(
684            DynamicImage::ImageLuma8(share_b),
685            2,
686            2,
687            width,
688            height,
689            2, // pixel expansion = 2
690            false,
691        ),
692    ])
693}
694
695/// Taghaddos-Latif decryption using AND operation as per original paper
696fn taghaddos_latif_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
697    if shares.len() < 2 {
698        return Err(VCError::InsufficientShares {
699            required: 2,
700            provided: shares.len(),
701        });
702    }
703
704    let (expanded_width, expanded_height) = shares[0].dimensions();
705
706    // Original dimensions (accounting for pixel expansion)
707    let width = expanded_width / 2;
708    let height = expanded_height / 2;
709
710    let mut result = ImageBuffer::new(width, height);
711
712    // Extract share images
713    let share_a = if let DynamicImage::ImageLuma8(img) = &shares[0].image {
714        img
715    } else {
716        return Err(VCError::DecryptionError(
717            "Share A is not grayscale".to_string(),
718        ));
719    };
720
721    let share_b = if let DynamicImage::ImageLuma8(img) = &shares[1].image {
722        img
723    } else {
724        return Err(VCError::DecryptionError(
725            "Share B is not grayscale".to_string(),
726        ));
727    };
728
729    // Process each original pixel (reconstructing from 2x2 blocks)
730    for y in 0..height {
731        for x in 0..width {
732            let base_x = x * 2;
733            let base_y = y * 2;
734
735            // Get the 2x2 block values from both shares
736            let share_a_block = [
737                share_a.get_pixel(base_x, base_y)[0],
738                share_a.get_pixel(base_x + 1, base_y)[0],
739                share_a.get_pixel(base_x, base_y + 1)[0],
740                share_a.get_pixel(base_x + 1, base_y + 1)[0],
741            ];
742
743            let share_b_block = [
744                share_b.get_pixel(base_x, base_y)[0],
745                share_b.get_pixel(base_x + 1, base_y)[0],
746                share_b.get_pixel(base_x, base_y + 1)[0],
747                share_b.get_pixel(base_x + 1, base_y + 1)[0],
748            ];
749
750            // Reconstruct pixel value using AND operation
751            let mut reconstructed_value = 0u8;
752
753            for bit_pos in 0..8 {
754                let mut reconstructed_bits = [0u8; 4];
755
756                // Apply AND operation for each sub-pixel in the 2x2 block
757                for i in 0..4 {
758                    let bit_a = (share_a_block[i] >> bit_pos) & 1;
759                    let bit_b = (share_b_block[i] >> bit_pos) & 1;
760                    reconstructed_bits[i] = bit_a & bit_b;
761                }
762
763                // Average the 4 sub-pixels to get the final bit
764                // (this follows the HVS perception model from the paper)
765                let sum = reconstructed_bits.iter().map(|&x| x as u32).sum::<u32>();
766                let average_bit = if sum >= 2 { 1 } else { 0 }; // majority voting for sub-pixels
767
768                reconstructed_value |= (average_bit as u8) << bit_pos;
769            }
770
771            result.put_pixel(x, y, Luma([reconstructed_value]));
772        }
773    }
774
775    Ok(DynamicImage::ImageLuma8(result))
776}
777
778/// Proper Dhiman-Kasana EVCT(3,3) color scheme
779fn dhiman_kasana_encrypt(
780    image: &DynamicImage,
781    config: &VCConfig,
782    cover_images: Option<Vec<DynamicImage>>,
783) -> Result<Vec<Share>> {
784    if config.num_shares != 3 {
785        return Err(VCError::InvalidConfiguration(
786            "Dhiman-Kasana EVCT(3,3) scheme requires exactly 3 shares".to_string(),
787        ));
788    }
789
790    let rgb = image.to_rgb8();
791    let (width, height) = (rgb.width(), rgb.height());
792
793    // Generate color mixing matrices
794    let color_matrices = generate_color_mixing_matrices();
795
796    let mut shares = Vec::new();
797    for _ in 0..3 {
798        shares.push(ImageBuffer::new(width, height));
799    }
800
801    let mut rng = rand::thread_rng();
802
803    // Process each pixel
804    for y in 0..height {
805        for x in 0..width {
806            let pixel = rgb.get_pixel(x, y);
807            let [r, g, b] = pixel.0;
808
809            // Convert RGB to binary representation for each channel
810            let r_binary = convert_channel_to_binary(r);
811            let g_binary = convert_channel_to_binary(g);
812            let b_binary = convert_channel_to_binary(b);
813
814            // Process each color channel separately
815            let mut final_shares = [Rgb([0u8, 0u8, 0u8]); 3];
816
817            for channel in 0..3 {
818                let binary_value = match channel {
819                    0 => r_binary,
820                    1 => g_binary,
821                    2 => b_binary,
822                    _ => 0,
823                };
824
825                // Select appropriate basis matrix
826                let matrix_idx = binary_value as usize % color_matrices.basis_matrices.len();
827                let matrix = &color_matrices.basis_matrices[matrix_idx];
828
829                // Select random column
830                let col = rng.gen_range(0..matrix.ncols());
831
832                // Apply color mixing to shares
833                for share_idx in 0..3 {
834                    let intensity = matrix[(share_idx, col)];
835                    let color_value = if intensity == 1 {
836                        match channel {
837                            0 => r,
838                            1 => g,
839                            2 => b,
840                            _ => 0,
841                        }
842                    } else {
843                        0
844                    };
845
846                    match channel {
847                        0 => {
848                            final_shares[share_idx].0[0] =
849                                final_shares[share_idx].0[0].saturating_add(color_value)
850                        }
851                        1 => {
852                            final_shares[share_idx].0[1] =
853                                final_shares[share_idx].0[1].saturating_add(color_value)
854                        }
855                        2 => {
856                            final_shares[share_idx].0[2] =
857                                final_shares[share_idx].0[2].saturating_add(color_value)
858                        }
859                        _ => {}
860                    }
861                }
862            }
863
864            // Set pixel values in shares
865            for share_idx in 0..3 {
866                shares[share_idx].put_pixel(x, y, final_shares[share_idx]);
867            }
868        }
869    }
870
871    // Convert to Share objects
872    let result: Vec<Share> = shares
873        .into_iter()
874        .enumerate()
875        .map(|(i, img)| {
876            Share::new(
877                DynamicImage::ImageRgb8(img),
878                i + 1,
879                3,
880                width,
881                height,
882                1,
883                false,
884            )
885        })
886        .collect();
887
888    Ok(result)
889}
890
891/// Convert color channel value to binary representation
892fn convert_channel_to_binary(value: u8) -> u8 {
893    if value > 127 {
894        1
895    } else {
896        0
897    }
898}
899
900/// Proper Dhiman-Kasana decryption
901fn dhiman_kasana_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
902    if shares.len() < 3 {
903        return Err(VCError::InsufficientShares {
904            required: 3,
905            provided: shares.len(),
906        });
907    }
908
909    let (width, height) = shares[0].dimensions();
910    let mut result = ImageBuffer::new(width, height);
911
912    for y in 0..height {
913        for x in 0..width {
914            let mut final_pixel = [0u8; 3];
915
916            // Reconstruct each color channel
917            for channel in 0..3 {
918                let mut channel_sum = 0u16;
919                let mut count = 0;
920
921                for share in shares {
922                    if let DynamicImage::ImageRgb8(img) = &share.image {
923                        let pixel = img.get_pixel(x, y);
924                        channel_sum += pixel.0[channel] as u16;
925                        count += 1;
926                    }
927                }
928
929                // Average the channel values (could use other reconstruction methods)
930                final_pixel[channel] = if count > 0 {
931                    (channel_sum / count as u16).min(255) as u8
932                } else {
933                    0
934                };
935            }
936
937            result.put_pixel(x, y, Rgb(final_pixel));
938        }
939    }
940
941    Ok(DynamicImage::ImageRgb8(result))
942}