1use crate::{
4    error::{Result, VCError},
5    matrix::{generate_basic_matrices, generate_xor_matrices},
6    share::{stack_shares, Share},
7    utils::convert_to_binary,
8    VCConfig,
9};
10use image::{DynamicImage, ImageBuffer, Luma, Rgb};
11use rand::{seq::SliceRandom, Rng};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Algorithm {
16    BasicThreshold,
18    NaorShamir,
20    TaghaddosLatif,
22    DhimanKasana,
24    XorBased,
26    YamaguchiNakajima,
28}
29
30pub trait VCScheme {
32    fn encrypt(
34        &self,
35        image: &DynamicImage,
36        config: &VCConfig,
37        cover_images: Option<Vec<DynamicImage>>,
38    ) -> Result<Vec<Share>>;
39
40    fn decrypt(&self, shares: &[Share], config: &VCConfig) -> Result<DynamicImage>;
42}
43
44pub fn encrypt(
46    image: &DynamicImage,
47    config: &VCConfig,
48    cover_images: Option<Vec<DynamicImage>>,
49) -> Result<Vec<Share>> {
50    match config.algorithm {
51        Algorithm::BasicThreshold => basic_threshold_encrypt(image, config),
52        Algorithm::NaorShamir => naor_shamir_encrypt(image, config),
53        Algorithm::TaghaddosLatif => taghaddos_latif_encrypt(image, config),
54        Algorithm::DhimanKasana => dhiman_kasana_encrypt(image, config, cover_images),
55        Algorithm::XorBased => xor_based_encrypt(image, config),
56        Algorithm::YamaguchiNakajima => yamaguchi_nakajima_encrypt(image, config, cover_images),
57    }
58}
59
60pub fn decrypt(shares: &[Share], config: &VCConfig) -> Result<DynamicImage> {
62    match config.algorithm {
63        Algorithm::BasicThreshold => basic_threshold_decrypt(shares, config),
64        Algorithm::NaorShamir => naor_shamir_decrypt(shares, config),
65        Algorithm::TaghaddosLatif => taghaddos_latif_decrypt(shares, config),
66        Algorithm::DhimanKasana => dhiman_kasana_decrypt(shares, config),
67        Algorithm::XorBased => xor_based_decrypt(shares, config),
68        Algorithm::YamaguchiNakajima => yamaguchi_nakajima_decrypt(shares, config),
69    }
70}
71
72fn xor_based_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
74    let binary = convert_to_binary(image);
75    let (width, height) = (binary.width(), binary.height());
76
77    let xor_matrices = generate_xor_matrices(config.num_shares)?;
79
80    let mut shares = vec![ImageBuffer::new(width, height); config.num_shares];
81
82    let mut rng = rand::rng();
83
84    for y in 0..height {
86        for x in 0..width {
87            let pixel = binary.get_pixel(x, y)[0];
88            let is_black = pixel == 0;
89
90            let matrix = if is_black {
92                &xor_matrices.black_pixel
93            } else {
94                &xor_matrices.white_pixel
95            };
96
97            let col = rng.random_range(0..matrix.ncols());
99
100            for share_idx in 0..config.num_shares {
102                let value = matrix[(share_idx, col)];
103                let pixel_value = if value == 1 { 0u8 } else { 255u8 };
104                shares[share_idx].put_pixel(x, y, Luma([pixel_value]));
105            }
106        }
107    }
108
109    let result: Vec<Share> = shares
111        .into_iter()
112        .enumerate()
113        .map(|(i, img)| {
114            Share::new(
115                DynamicImage::ImageLuma8(img),
116                i + 1,
117                config.num_shares,
118                width,
119                height,
120                1,
121                false,
122            )
123        })
124        .collect();
125
126    Ok(result)
127}
128
129fn xor_based_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
131    if shares.is_empty() {
132        return Err(VCError::InsufficientShares {
133            required: 1,
134            provided: 0,
135        });
136    }
137
138    let (width, height) = shares[0].dimensions();
139    let mut result = ImageBuffer::new(width, height);
140
141    for y in 0..height {
142        for x in 0..width {
143            let mut xor_value = 0u8;
144
145            for share in shares {
146                if let DynamicImage::ImageLuma8(img) = &share.image {
147                    let pixel = img.get_pixel(x, y)[0];
148                    let bit = if pixel == 0 { 1 } else { 0 };
149                    xor_value ^= bit;
150                }
151            }
152
153            let pixel_value = if xor_value == 1 { 0u8 } else { 255u8 };
155            result.put_pixel(x, y, Luma([pixel_value]));
156        }
157    }
158
159    Ok(DynamicImage::ImageLuma8(result))
160}
161
162fn basic_threshold_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
164    let binary = convert_to_binary(image);
166    let (width, height) = (binary.width(), binary.height());
167
168    let matrices = generate_basic_matrices(config.threshold, config.num_shares, config.block_size)?;
170
171    let share_width = width * config.block_size as u32;
173    let share_height = height * config.block_size as u32;
174
175    let mut shares = vec![ImageBuffer::new(share_width, share_height); config.num_shares];
176
177    let mut rng = rand::rng();
178
179    for y in 0..height {
181        for x in 0..width {
182            let pixel = binary.get_pixel(x, y)[0];
183            let matrix_idx = if pixel == 0 { 1 } else { 0 }; let matrix = &matrices[matrix_idx];
185
186            let col = rng.random_range(0..matrix.ncols());
188
189            for share_idx in 0..config.num_shares {
191                let value = matrix[(share_idx, col)];
192                let block_value = if value == 1 { 0u8 } else { 255u8 };
193
194                let base_x = x * config.block_size as u32;
196                let base_y = y * config.block_size as u32;
197
198                for dy in 0..config.block_size as u32 {
199                    for dx in 0..config.block_size as u32 {
200                        shares[share_idx].put_pixel(base_x + dx, base_y + dy, Luma([block_value]));
201                    }
202                }
203            }
204        }
205    }
206
207    let result: Vec<Share> = shares
209        .into_iter()
210        .enumerate()
211        .map(|(i, img)| {
212            Share::new(
213                DynamicImage::ImageLuma8(img),
214                i + 1,
215                config.num_shares,
216                width,
217                height,
218                config.block_size,
219                false,
220            )
221        })
222        .collect();
223
224    Ok(result)
225}
226
227fn basic_threshold_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
229    if let Some(stacked) = stack_shares(shares) {
230        Ok(DynamicImage::ImageLuma8(stacked))
231    } else {
232        Err(VCError::DecryptionError(
233            "Failed to stack shares".to_string(),
234        ))
235    }
236}
237
238fn naor_shamir_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
240    if config.num_shares != 2 || config.threshold != 2 {
242        return Err(VCError::InvalidConfiguration(
243            "Original Naor-Shamir scheme requires exactly 2 shares with threshold 2".to_string(),
244        ));
245    }
246
247    let binary = convert_to_binary(image);
248    let (width, height) = (binary.width(), binary.height());
249
250    let white_matrix = vec![vec![1, 1, 0, 0], vec![1, 1, 0, 0]];
253    let black_matrix = vec![vec![1, 1, 0, 0], vec![0, 0, 1, 1]];
255
256    let share_width = width * 2;
257    let share_height = height * 2;
258
259    let mut share1 = ImageBuffer::new(share_width, share_height);
260    let mut share2 = ImageBuffer::new(share_width, share_height);
261
262    let mut rng = rand::rng();
263
264    for y in 0..height {
265        for x in 0..width {
266            let pixel = binary.get_pixel(x, y)[0];
267
268            let matrix = if pixel == 0 {
270                &black_matrix
272            } else {
273                &white_matrix
275            };
276
277            let mut columns: Vec<usize> = (0..4).collect();
279            columns.as_mut_slice().shuffle(&mut rng);
280
281            let share1_pattern = [
283                matrix[0][columns[0]],
284                matrix[0][columns[1]],
285                matrix[0][columns[2]],
286                matrix[0][columns[3]],
287            ];
288            let share2_pattern = [
289                matrix[1][columns[0]],
290                matrix[1][columns[1]],
291                matrix[1][columns[2]],
292                matrix[1][columns[3]],
293            ];
294
295            share1.put_pixel(
298                x * 2,
299                y * 2,
300                Luma([if share1_pattern[0] == 1 { 0 } else { 255 }]),
301            );
302            share1.put_pixel(
303                x * 2 + 1,
304                y * 2,
305                Luma([if share1_pattern[1] == 1 { 0 } else { 255 }]),
306            );
307            share1.put_pixel(
308                x * 2,
309                y * 2 + 1,
310                Luma([if share1_pattern[2] == 1 { 0 } else { 255 }]),
311            );
312            share1.put_pixel(
313                x * 2 + 1,
314                y * 2 + 1,
315                Luma([if share1_pattern[3] == 1 { 0 } else { 255 }]),
316            );
317
318            share2.put_pixel(
320                x * 2,
321                y * 2,
322                Luma([if share2_pattern[0] == 1 { 0 } else { 255 }]),
323            );
324            share2.put_pixel(
325                x * 2 + 1,
326                y * 2,
327                Luma([if share2_pattern[1] == 1 { 0 } else { 255 }]),
328            );
329            share2.put_pixel(
330                x * 2,
331                y * 2 + 1,
332                Luma([if share2_pattern[2] == 1 { 0 } else { 255 }]),
333            );
334            share2.put_pixel(
335                x * 2 + 1,
336                y * 2 + 1,
337                Luma([if share2_pattern[3] == 1 { 0 } else { 255 }]),
338            );
339        }
340    }
341
342    Ok(vec![
343        Share::new(
344            DynamicImage::ImageLuma8(share1),
345            1,
346            2,
347            width,
348            height,
349            2,
350            false,
351        ),
352        Share::new(
353            DynamicImage::ImageLuma8(share2),
354            2,
355            2,
356            width,
357            height,
358            2,
359            false,
360        ),
361    ])
362}
363
364fn naor_shamir_decrypt(shares: &[Share], config: &VCConfig) -> Result<DynamicImage> {
366    basic_threshold_decrypt(shares, config)
367}
368
369fn taghaddos_latif_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
371    if config.num_shares != 2 {
372        return Err(VCError::InvalidConfiguration(
373            "Taghaddos-Latif scheme requires exactly 2 shares".to_string(),
374        ));
375    }
376
377    let gray = image.to_luma8();
378    let (width, height) = (gray.width(), gray.height());
379
380    let patterns = [
382        [1u8, 1u8, 0u8, 0u8],
383        [1u8, 0u8, 1u8, 0u8],
384        [1u8, 0u8, 0u8, 1u8],
385        [0u8, 1u8, 1u8, 0u8],
386        [0u8, 1u8, 0u8, 1u8],
387        [0u8, 0u8, 1u8, 1u8],
388    ];
389
390    let share_width = width * 2;
392    let share_height = height * 2;
393
394    let mut share_a = ImageBuffer::new(share_width, share_height);
395    let mut share_b = ImageBuffer::new(share_width, share_height);
396
397    let mut rng = rand::rng();
398
399    for y in 0..height {
401        for x in 0..width {
402            let pixel_value = gray.get_pixel(x, y)[0];
403            let mut share_a_colors = [0u8; 4];
404            let mut share_b_colors = [0u8; 4];
405
406            for bit_pos in 0..8 {
408                let bit = (pixel_value >> bit_pos) & 1;
409
410                let pattern = patterns[rng.random_range(0..6)];
412
413                if bit == 1 {
414                    for i in 0..4 {
416                        share_a_colors[i] |= pattern[i] << bit_pos;
417                    }
418                    share_b_colors.copy_from_slice(&share_a_colors);
419                } else {
420                    for i in 0..4 {
422                        share_a_colors[i] |= pattern[i] << bit_pos;
423                        share_b_colors[i] |= (1 - pattern[i]) << bit_pos;
424                    }
425                }
426            }
427
428            let base_x = x * 2;
430            let base_y = y * 2;
431
432            share_a.put_pixel(base_x, base_y, Luma([share_a_colors[0]]));
434            share_a.put_pixel(base_x + 1, base_y, Luma([share_a_colors[1]]));
435            share_a.put_pixel(base_x, base_y + 1, Luma([share_a_colors[2]]));
436            share_a.put_pixel(base_x + 1, base_y + 1, Luma([share_a_colors[3]]));
437
438            share_b.put_pixel(base_x, base_y, Luma([share_b_colors[0]]));
440            share_b.put_pixel(base_x + 1, base_y, Luma([share_b_colors[1]]));
441            share_b.put_pixel(base_x, base_y + 1, Luma([share_b_colors[2]]));
442            share_b.put_pixel(base_x + 1, base_y + 1, Luma([share_b_colors[3]]));
443        }
444    }
445
446    Ok(vec![
447        Share::new(
448            DynamicImage::ImageLuma8(share_a),
449            1,
450            2,
451            width,
452            height,
453            2, false,
455        ),
456        Share::new(
457            DynamicImage::ImageLuma8(share_b),
458            2,
459            2,
460            width,
461            height,
462            2, false,
464        ),
465    ])
466}
467
468fn taghaddos_latif_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
470    if shares.len() < 2 {
471        return Err(VCError::InsufficientShares {
472            required: 2,
473            provided: shares.len(),
474        });
475    }
476
477    let (expanded_width, expanded_height) = shares[0].dimensions();
478
479    let width = expanded_width / 2;
481    let height = expanded_height / 2;
482
483    let mut result = ImageBuffer::new(width, height);
484
485    let share_a = if let DynamicImage::ImageLuma8(img) = &shares[0].image {
487        img
488    } else {
489        return Err(VCError::DecryptionError(
490            "Share A is not grayscale".to_string(),
491        ));
492    };
493
494    let share_b = if let DynamicImage::ImageLuma8(img) = &shares[1].image {
495        img
496    } else {
497        return Err(VCError::DecryptionError(
498            "Share B is not grayscale".to_string(),
499        ));
500    };
501
502    for y in 0..height {
504        for x in 0..width {
505            let base_x = x * 2;
506            let base_y = y * 2;
507
508            let share_a_block = [
510                share_a.get_pixel(base_x, base_y)[0],
511                share_a.get_pixel(base_x + 1, base_y)[0],
512                share_a.get_pixel(base_x, base_y + 1)[0],
513                share_a.get_pixel(base_x + 1, base_y + 1)[0],
514            ];
515
516            let share_b_block = [
517                share_b.get_pixel(base_x, base_y)[0],
518                share_b.get_pixel(base_x + 1, base_y)[0],
519                share_b.get_pixel(base_x, base_y + 1)[0],
520                share_b.get_pixel(base_x + 1, base_y + 1)[0],
521            ];
522
523            let mut reconstructed_value = 0u8;
525
526            for bit_pos in 0..8 {
527                let mut reconstructed_bits = [0u8; 4];
528
529                for i in 0..4 {
531                    let bit_a = (share_a_block[i] >> bit_pos) & 1;
532                    let bit_b = (share_b_block[i] >> bit_pos) & 1;
533                    reconstructed_bits[i] = bit_a & bit_b;
534                }
535
536                let sum = reconstructed_bits.iter().map(|&x| x as u32).sum::<u32>();
539                let average_bit = if sum >= 2 { 1 } else { 0 }; reconstructed_value |= (average_bit as u8) << bit_pos;
542            }
543
544            result.put_pixel(x, y, Luma([reconstructed_value]));
545        }
546    }
547
548    Ok(DynamicImage::ImageLuma8(result))
549}
550
551fn dhiman_kasana_encrypt(
553    image: &DynamicImage,
554    config: &VCConfig,
555    cover_images: Option<Vec<DynamicImage>>,
556) -> Result<Vec<Share>> {
557    if config.num_shares != 3 {
558        return Err(VCError::InvalidConfiguration(
559            "Dhiman-Kasana EVCT(3,3) scheme requires exactly 3 shares".to_string(),
560        ));
561    }
562
563    let rgb = image.to_rgb8();
564    let (width, height) = (rgb.width(), rgb.height());
565
566    let components = [
568        [
570            (4, 4),
571            (4, 2),
572            (3, 1),
573            (2, 3),
574            (2, 0),
575            (1, 4),
576            (1, 2),
577            (0, 1),
578        ],
579        [
581            (4, 3),
582            (3, 4),
583            (3, 2),
584            (2, 1),
585            (1, 3),
586            (1, 0),
587            (0, 4),
588            (0, 2),
589        ],
590        [
592            (4, 1),
593            (3, 3),
594            (3, 0),
595            (2, 4),
596            (2, 2),
597            (1, 1),
598            (0, 3),
599            (0, 0),
600        ],
601    ];
602
603    let share_width = width * 5;
605    let share_height = height * 5;
606
607    let mut shares = Vec::new();
608    for _ in 0..3 {
609        shares.push(ImageBuffer::new(share_width, share_height));
610    }
611
612    let has_cover_images = cover_images.is_some();
614
615    let covers = if let Some(covers) = cover_images {
617        if covers.len() != 3 {
618            return Err(VCError::CoverImageError(
619                "Dhiman-Kasana requires exactly 3 cover images".to_string(),
620            ));
621        }
622        covers.into_iter().map(|img| img.to_rgb8()).collect()
623    } else {
624        vec![
626            ImageBuffer::from_pixel(width, height, Rgb([255, 255, 255])),
627            ImageBuffer::from_pixel(width, height, Rgb([255, 255, 255])),
628            ImageBuffer::from_pixel(width, height, Rgb([255, 255, 255])),
629        ]
630    };
631
632    for y in 0..height {
634        for x in 0..width {
635            let secret_pixel = rgb.get_pixel(x, y);
636            let [r, g, b] = secret_pixel.0;
637
638            for share_idx in 0..3 {
640                let cover_pixel = covers[share_idx].get_pixel(x, y);
641
642                let mut block = ImageBuffer::from_pixel(5, 5, *cover_pixel);
644
645                for (channel_idx, &channel_value) in [r, g, b].iter().enumerate() {
647                    let bit_positions = &components[channel_idx];
648
649                    for (bit_idx, &(bit_y, bit_x)) in bit_positions.iter().enumerate() {
651                        let bit = (channel_value >> bit_idx) & 1;
652
653                        let pixel_color = if bit == 1 {
655                            Rgb([0, 0, 0]) } else {
657                            Rgb([30, 30, 30]) };
659
660                        block.put_pixel(bit_x, bit_y, pixel_color);
661                    }
662                }
663
664                let base_x = x * 5;
666                let base_y = y * 5;
667                for block_y in 0..5 {
668                    for block_x in 0..5 {
669                        let pixel = block.get_pixel(block_x, block_y);
670                        shares[share_idx].put_pixel(base_x + block_x, base_y + block_y, *pixel);
671                    }
672                }
673            }
674        }
675    }
676
677    let result: Vec<Share> = shares
679        .into_iter()
680        .enumerate()
681        .map(|(i, img)| {
682            Share::new(
683                DynamicImage::ImageRgb8(img),
684                i + 1,
685                3,
686                width,
687                height,
688                5, has_cover_images,
690            )
691        })
692        .collect();
693
694    Ok(result)
695}
696
697fn dhiman_kasana_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
699    if shares.len() < 3 {
700        return Err(VCError::InsufficientShares {
701            required: 3,
702            provided: shares.len(),
703        });
704    }
705
706    let (expanded_width, expanded_height) = shares[0].dimensions();
708
709    let width = expanded_width / 5;
711    let height = expanded_height / 5;
712
713    let mut result = ImageBuffer::new(width, height);
714
715    let components = [
717        [
719            (4, 4),
720            (4, 2),
721            (3, 1),
722            (2, 3),
723            (2, 0),
724            (1, 4),
725            (1, 2),
726            (0, 1),
727        ],
728        [
730            (4, 3),
731            (3, 4),
732            (3, 2),
733            (2, 1),
734            (1, 3),
735            (1, 0),
736            (0, 4),
737            (0, 2),
738        ],
739        [
741            (4, 1),
742            (3, 3),
743            (3, 0),
744            (2, 4),
745            (2, 2),
746            (1, 1),
747            (0, 3),
748            (0, 0),
749        ],
750    ];
751
752    let share_images: Vec<&ImageBuffer<Rgb<u8>, Vec<u8>>> = shares
754        .iter()
755        .map(|share| {
756            if let DynamicImage::ImageRgb8(img) = &share.image {
757                img
758            } else {
759                panic!("Share is not RGB format");
760            }
761        })
762        .collect();
763
764    for y in 0..height {
766        for x in 0..width {
767            let mut reconstructed_pixel = [0u8; 3];
768
769            for channel_idx in 0..3 {
771                let bit_positions = &components[channel_idx];
772                let mut channel_value = 0u8;
773
774                for (bit_idx, &(bit_y, bit_x)) in bit_positions.iter().enumerate() {
776                    let base_x = x * 5;
777                    let base_y = y * 5;
778
779                    let pixel = share_images[channel_idx].get_pixel(base_x + bit_x, base_y + bit_y);
781
782                    let bit = if pixel.0 == [0, 0, 0] { 1 } else { 0 };
784
785                    channel_value |= bit << bit_idx;
786                }
787
788                reconstructed_pixel[channel_idx] = channel_value;
789            }
790
791            result.put_pixel(x, y, Rgb(reconstructed_pixel));
792        }
793    }
794
795    Ok(DynamicImage::ImageRgb8(result))
796}
797
798fn yamaguchi_nakajima_encrypt(
801    image: &DynamicImage,
802    config: &VCConfig,
803    cover_images: Option<Vec<DynamicImage>>,
804) -> Result<Vec<Share>> {
805    if config.num_shares != 2 {
806        return Err(VCError::InvalidConfiguration(
807            "Yamaguchi-Nakajima scheme requires exactly 2 shares".to_string(),
808        ));
809    }
810
811    let cover_images = cover_images.ok_or_else(|| {
812        VCError::CoverImageError(
813            "Yamaguchi-Nakajima scheme requires 2 cover images (sheet images)".to_string(),
814        )
815    })?;
816
817    if cover_images.len() != 2 {
818        return Err(VCError::CoverImageError(
819            "Yamaguchi-Nakajima scheme requires exactly 2 cover images".to_string(),
820        ));
821    }
822
823    let target = image.to_luma8();
824    let sheet1 = cover_images[0].to_luma8();
825    let sheet2 = cover_images[1].to_luma8();
826    let (width, height) = (target.width(), target.height());
827
828    let sheet1 = image::imageops::resize(
830        &sheet1,
831        width,
832        height,
833        image::imageops::FilterType::Lanczos3,
834    );
835    let sheet2 = image::imageops::resize(
836        &sheet2,
837        width,
838        height,
839        image::imageops::FilterType::Lanczos3,
840    );
841
842    let m = config.block_size.max(4);
844    let sub_size = (m as f64).sqrt() as usize;
845
846    let contrast = 0.6;
848    let l = (1.0 - contrast) / 2.0; let sheet1_processed = apply_contrast_and_halftone(&sheet1, contrast, l);
852    let sheet2_processed = apply_contrast_and_halftone(&sheet2, contrast, l);
853    let target_processed = apply_halftone_target(&target, contrast);
854
855    let out_width = width * sub_size as u32;
857    let out_height = height * sub_size as u32;
858    let mut out_sheet1 = ImageBuffer::new(out_width, out_height);
859    let mut out_sheet2 = ImageBuffer::new(out_width, out_height);
860
861    let mut rng = rand::rng();
862
863    for y in 0..height {
865        for x in 0..width {
866            let t1 = sheet1_processed.get_pixel(x, y)[0] as f64 / 255.0;
868            let t2 = sheet2_processed.get_pixel(x, y)[0] as f64 / 255.0;
869            let tt = target_processed.get_pixel(x, y)[0] as f64 / 255.0;
870
871            let (t1, t2, tt) = adjust_triplet(t1, t2, tt);
873
874            let s1 = (t1 * m as f64).round() as usize;
876            let s2 = (t2 * m as f64).round() as usize;
877            let st = (tt * m as f64).round() as usize;
878
879            let matrices = generate_boolean_matrices(s1, s2, st, m);
881
882            if !matrices.is_empty() {
883                let matrix = &matrices[rng.random_range(0..matrices.len())];
885
886                for i in 0..sub_size {
888                    for j in 0..sub_size {
889                        let idx = i * sub_size + j;
890                        if idx < m {
891                            let pixel_val1 = if matrix[0][idx] == 1 { 255 } else { 0 };
892                            let pixel_val2 = if matrix[1][idx] == 1 { 255 } else { 0 };
893
894                            out_sheet1.put_pixel(
895                                x * sub_size as u32 + j as u32,
896                                y * sub_size as u32 + i as u32,
897                                Luma([pixel_val1]),
898                            );
899                            out_sheet2.put_pixel(
900                                x * sub_size as u32 + j as u32,
901                                y * sub_size as u32 + i as u32,
902                                Luma([pixel_val2]),
903                            );
904                        }
905                    }
906                }
907            } else {
908                for i in 0..sub_size {
910                    for j in 0..sub_size {
911                        let val1 = if rng.random_bool(0.5) { 255 } else { 0 };
912                        let val2 = if rng.random_bool(0.5) { 255 } else { 0 };
913
914                        out_sheet1.put_pixel(
915                            x * sub_size as u32 + j as u32,
916                            y * sub_size as u32 + i as u32,
917                            Luma([val1]),
918                        );
919                        out_sheet2.put_pixel(
920                            x * sub_size as u32 + j as u32,
921                            y * sub_size as u32 + i as u32,
922                            Luma([val2]),
923                        );
924                    }
925                }
926            }
927        }
928    }
929
930    Ok(vec![
931        Share::new(
932            DynamicImage::ImageLuma8(out_sheet1),
933            1,
934            2,
935            width,
936            height,
937            sub_size,
938            true,
939        ),
940        Share::new(
941            DynamicImage::ImageLuma8(out_sheet2),
942            2,
943            2,
944            width,
945            height,
946            sub_size,
947            true,
948        ),
949    ])
950}
951
952fn yamaguchi_nakajima_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
954    if shares.len() < 2 {
955        return Err(VCError::InsufficientShares {
956            required: 2,
957            provided: shares.len(),
958        });
959    }
960
961    let (expanded_width, expanded_height) = shares[0].dimensions();
962
963    let sheet1 = if let DynamicImage::ImageLuma8(img) = &shares[0].image {
965        img
966    } else {
967        return Err(VCError::DecryptionError(
968            "Share 1 is not grayscale".to_string(),
969        ));
970    };
971
972    let sheet2 = if let DynamicImage::ImageLuma8(img) = &shares[1].image {
973        img
974    } else {
975        return Err(VCError::DecryptionError(
976            "Share 2 is not grayscale".to_string(),
977        ));
978    };
979
980    let mut result = ImageBuffer::new(expanded_width, expanded_height);
982
983    for y in 0..expanded_height {
985        for x in 0..expanded_width {
986            let pixel1 = sheet1.get_pixel(x, y)[0];
987            let pixel2 = sheet2.get_pixel(x, y)[0];
988
989            let result_pixel = if pixel1 == 255 && pixel2 == 255 {
991                255
992            } else {
993                0
994            };
995            result.put_pixel(x, y, Luma([result_pixel]));
996        }
997    }
998
999    Ok(DynamicImage::ImageLuma8(result))
1000}
1001
1002fn apply_contrast_and_halftone(
1004    image: &ImageBuffer<Luma<u8>, Vec<u8>>,
1005    contrast: f64,
1006    l: f64,
1007) -> ImageBuffer<Luma<u8>, Vec<u8>> {
1008    let (width, height) = (image.width(), image.height());
1009    let mut working_image = ImageBuffer::new(width, height);
1010
1011    for y in 0..height {
1013        for x in 0..width {
1014            let pixel = image.get_pixel(x, y)[0] as f64 / 255.0;
1015            let adjusted = l + pixel * contrast;
1016            working_image.put_pixel(x, y, Luma([(adjusted * 255.0) as u8]));
1017        }
1018    }
1019
1020    floyd_steinberg_dithering(&working_image)
1022}
1023
1024fn apply_halftone_target(
1026    image: &ImageBuffer<Luma<u8>, Vec<u8>>,
1027    contrast: f64,
1028) -> ImageBuffer<Luma<u8>, Vec<u8>> {
1029    let (width, height) = (image.width(), image.height());
1030    let mut working_image = ImageBuffer::new(width, height);
1031
1032    for y in 0..height {
1034        for x in 0..width {
1035            let pixel = image.get_pixel(x, y)[0] as f64 / 255.0;
1036            let adjusted = pixel * contrast;
1037            working_image.put_pixel(x, y, Luma([(adjusted * 255.0) as u8]));
1038        }
1039    }
1040
1041    floyd_steinberg_dithering(&working_image)
1043}
1044
1045fn floyd_steinberg_dithering(
1047    image: &ImageBuffer<Luma<u8>, Vec<u8>>,
1048) -> ImageBuffer<Luma<u8>, Vec<u8>> {
1049    let (width, height) = (image.width(), image.height());
1050    let mut working = vec![vec![0.0; width as usize]; height as usize];
1051    let mut result = ImageBuffer::new(width, height);
1052
1053    for y in 0..height {
1055        for x in 0..width {
1056            working[y as usize][x as usize] = image.get_pixel(x, y)[0] as f64 / 255.0;
1057        }
1058    }
1059
1060    for y in 0..height {
1062        for x in 0..width {
1063            let old_pixel = working[y as usize][x as usize];
1064            let new_pixel = if old_pixel > 0.5 { 1.0 } else { 0.0 };
1065            result.put_pixel(x, y, Luma([(new_pixel * 255.0) as u8]));
1066
1067            let error = old_pixel - new_pixel;
1068
1069            if x + 1 < width {
1071                working[y as usize][(x + 1) as usize] += error * 7.0 / 16.0;
1072            }
1073            if y + 1 < height {
1074                if x > 0 {
1075                    working[(y + 1) as usize][(x - 1) as usize] += error * 3.0 / 16.0;
1076                }
1077                working[(y + 1) as usize][x as usize] += error * 5.0 / 16.0;
1078                if x + 1 < width {
1079                    working[(y + 1) as usize][(x + 1) as usize] += error * 1.0 / 16.0;
1080                }
1081            }
1082        }
1083    }
1084
1085    result
1086}
1087
1088fn adjust_triplet(t1: f64, t2: f64, tt: f64) -> (f64, f64, f64) {
1090    let min_tt = (0.0_f64).max(t1 + t2 - 1.0);
1091    let max_tt = t1.min(t2);
1092
1093    let tt_adj = if tt < min_tt {
1094        min_tt
1095    } else if tt > max_tt {
1096        max_tt
1097    } else {
1098        tt
1099    };
1100
1101    (t1, t2, tt_adj)
1102}
1103
1104fn generate_boolean_matrices(s1: usize, s2: usize, st: usize, m: usize) -> Vec<Vec<Vec<u8>>> {
1106    let p11 = st; let p10 = s1.saturating_sub(st); let p01 = s2.saturating_sub(st); let p00 = m.saturating_sub(p11 + p10 + p01); if p11 + p10 + p01 + p00 != m {
1114        return vec![];
1115    }
1116
1117    let mut base_patterns = Vec::new();
1119
1120    for _ in 0..p11 {
1122        base_patterns.push([1, 1]);
1123    }
1124    for _ in 0..p10 {
1126        base_patterns.push([1, 0]);
1127    }
1128    for _ in 0..p01 {
1130        base_patterns.push([0, 1]);
1131    }
1132    for _ in 0..p00 {
1134        base_patterns.push([0, 0]);
1135    }
1136
1137    let mut rng = rand::rng();
1139    base_patterns.shuffle(&mut rng);
1140
1141    let matrix = vec![
1142        base_patterns
1143            .iter()
1144            .map(|pair| pair[0])
1145            .collect::<Vec<u8>>(),
1146        base_patterns
1147            .iter()
1148            .map(|pair| pair[1])
1149            .collect::<Vec<u8>>(),
1150    ];
1151
1152    vec![matrix]
1153}