1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Algorithm {
20 BasicThreshold,
22 Progressive,
24 ExtendedMeaningful,
26 NaorShamir,
28 TaghaddosLatif,
30 DhimanKasana,
32 XorBased,
34 YamaguchiNakajima,
36}
37
38pub trait VCScheme {
40 fn encrypt(
42 &self,
43 image: &DynamicImage,
44 config: &VCConfig,
45 cover_images: Option<Vec<DynamicImage>>,
46 ) -> Result<Vec<Share>>;
47
48 fn decrypt(&self, shares: &[Share], config: &VCConfig) -> Result<DynamicImage>;
50}
51
52pub fn encrypt(
54 image: &DynamicImage,
55 config: &VCConfig,
56 cover_images: Option<Vec<DynamicImage>>,
57) -> Result<Vec<Share>> {
58 match config.algorithm {
59 Algorithm::BasicThreshold => basic_threshold_encrypt(image, config),
60 Algorithm::Progressive => progressive_encrypt(image, config, cover_images),
61 Algorithm::ExtendedMeaningful => extended_meaningful_encrypt(image, config, cover_images),
62 Algorithm::NaorShamir => naor_shamir_encrypt(image, config),
63 Algorithm::TaghaddosLatif => taghaddos_latif_encrypt(image, config),
64 Algorithm::DhimanKasana => dhiman_kasana_encrypt(image, config, cover_images),
65 Algorithm::XorBased => xor_based_encrypt(image, config),
66 Algorithm::YamaguchiNakajima => yamaguchi_nakajima_encrypt(image, config, cover_images),
67 }
68}
69
70pub fn decrypt(shares: &[Share], config: &VCConfig) -> Result<DynamicImage> {
72 match config.algorithm {
73 Algorithm::BasicThreshold => basic_threshold_decrypt(shares, config),
74 Algorithm::Progressive => progressive_decrypt(shares, config),
75 Algorithm::ExtendedMeaningful => extended_meaningful_decrypt(shares, config),
76 Algorithm::NaorShamir => naor_shamir_decrypt(shares, config),
77 Algorithm::TaghaddosLatif => taghaddos_latif_decrypt(shares, config),
78 Algorithm::DhimanKasana => dhiman_kasana_decrypt(shares, config),
79 Algorithm::XorBased => xor_based_decrypt(shares, config),
80 Algorithm::YamaguchiNakajima => yamaguchi_nakajima_decrypt(shares, config),
81 }
82}
83
84fn xor_based_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
86 let binary = convert_to_binary(image);
87 let (width, height) = (binary.width(), binary.height());
88
89 let xor_matrices = generate_xor_matrices(config.num_shares)?;
91
92 let mut shares = Vec::new();
93 for i in 0..config.num_shares {
94 shares.push(ImageBuffer::new(width, height));
95 }
96
97 let mut rng = rand::thread_rng();
98
99 for y in 0..height {
101 for x in 0..width {
102 let pixel = binary.get_pixel(x, y)[0];
103 let is_black = pixel == 0;
104
105 let matrix = if is_black {
107 &xor_matrices.black_pixel
108 } else {
109 &xor_matrices.white_pixel
110 };
111
112 let col = rng.gen_range(0..matrix.ncols());
114
115 for share_idx in 0..config.num_shares {
117 let value = matrix[(share_idx, col)];
118 let pixel_value = if value == 1 { 0u8 } else { 255u8 };
119 shares[share_idx].put_pixel(x, y, Luma([pixel_value]));
120 }
121 }
122 }
123
124 let result: Vec<Share> = shares
126 .into_iter()
127 .enumerate()
128 .map(|(i, img)| {
129 Share::new(
130 DynamicImage::ImageLuma8(img),
131 i + 1,
132 config.num_shares,
133 width,
134 height,
135 1,
136 false,
137 )
138 })
139 .collect();
140
141 Ok(result)
142}
143
144fn xor_based_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
146 if shares.is_empty() {
147 return Err(VCError::InsufficientShares {
148 required: 1,
149 provided: 0,
150 });
151 }
152
153 let (width, height) = shares[0].dimensions();
154 let mut result = ImageBuffer::new(width, height);
155
156 for y in 0..height {
157 for x in 0..width {
158 let mut xor_value = 0u8;
159
160 for share in shares {
161 if let DynamicImage::ImageLuma8(img) = &share.image {
162 let pixel = img.get_pixel(x, y)[0];
163 let bit = if pixel == 0 { 1 } else { 0 };
164 xor_value ^= bit;
165 }
166 }
167
168 let pixel_value = if xor_value == 1 { 0u8 } else { 255u8 };
170 result.put_pixel(x, y, Luma([pixel_value]));
171 }
172 }
173
174 Ok(DynamicImage::ImageLuma8(result))
175}
176
177fn basic_threshold_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
179 let binary = convert_to_binary(image);
181 let (width, height) = (binary.width(), binary.height());
182
183 let matrices = generate_basic_matrices(config.threshold, config.num_shares, config.block_size)?;
185
186 let share_width = width * config.block_size as u32;
188 let share_height = height * config.block_size as u32;
189
190 let mut shares = Vec::new();
191 for i in 0..config.num_shares {
192 shares.push(ImageBuffer::new(share_width, share_height));
193 }
194
195 let mut rng = rand::thread_rng();
196
197 for y in 0..height {
199 for x in 0..width {
200 let pixel = binary.get_pixel(x, y)[0];
201 let matrix_idx = if pixel == 0 { 1 } else { 0 }; let matrix = &matrices[matrix_idx];
203
204 let col = rng.gen_range(0..matrix.ncols());
206
207 for share_idx in 0..config.num_shares {
209 let value = matrix[(share_idx, col)];
210 let block_value = if value == 1 { 0u8 } else { 255u8 };
211
212 let base_x = x * config.block_size as u32;
214 let base_y = y * config.block_size as u32;
215
216 for dy in 0..config.block_size as u32 {
217 for dx in 0..config.block_size as u32 {
218 shares[share_idx].put_pixel(base_x + dx, base_y + dy, Luma([block_value]));
219 }
220 }
221 }
222 }
223 }
224
225 let result: Vec<Share> = shares
227 .into_iter()
228 .enumerate()
229 .map(|(i, img)| {
230 Share::new(
231 DynamicImage::ImageLuma8(img),
232 i + 1,
233 config.num_shares,
234 width,
235 height,
236 config.block_size,
237 false,
238 )
239 })
240 .collect();
241
242 Ok(result)
243}
244
245fn basic_threshold_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
247 if let Some(stacked) = stack_shares(shares) {
248 Ok(DynamicImage::ImageLuma8(stacked))
249 } else {
250 Err(VCError::DecryptionError(
251 "Failed to stack shares".to_string(),
252 ))
253 }
254}
255
256fn progressive_encrypt(
258 image: &DynamicImage,
259 config: &VCConfig,
260 cover_images: Option<Vec<DynamicImage>>,
261) -> Result<Vec<Share>> {
262 let binary = convert_to_binary(image);
264 let (width, height) = (binary.width(), binary.height());
265
266 if let Some(covers) = cover_images {
268 if covers.len() != config.num_shares {
269 return Err(VCError::CoverImageError(format!(
270 "Number of cover images ({}) must match number of shares ({})",
271 covers.len(),
272 config.num_shares
273 )));
274 }
275
276 let i = config.num_shares; let matrices = generate_dispatching_matrices(config.num_shares, i)?;
279
280 let mut shares = Vec::new();
281
282 for share_idx in 0..config.num_shares {
283 let mut share_img = ImageBuffer::new(width, height);
284 let cover_binary = convert_to_binary(&covers[share_idx]);
285
286 for y in 0..height {
287 for x in 0..width {
288 let secret_pixel = binary.get_pixel(x, y)[0] == 0; let cover_pixel = cover_binary.get_pixel(x, y)[0] == 0; let row = select_dispatching_row(&matrices, secret_pixel, cover_pixel);
292 let value = if row[share_idx] == 1 { 0u8 } else { 255u8 };
293
294 share_img.put_pixel(x, y, Luma([value]));
295 }
296 }
297
298 shares.push(Share::new(
299 DynamicImage::ImageLuma8(share_img),
300 share_idx + 1,
301 config.num_shares,
302 width,
303 height,
304 1, true, ));
307 }
308
309 Ok(shares)
310 } else {
311 progressive_matrix_based_encrypt(image, config)
313 }
314}
315
316fn progressive_matrix_based_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
318 let binary = convert_to_binary(image);
319 let (width, height) = (binary.width(), binary.height());
320
321 let (white_matrix, black_matrix) = generate_proper_sharing_matrices(2, config.num_shares)?;
323
324 let mut shares = Vec::new();
325 for i in 0..config.num_shares {
326 shares.push(ImageBuffer::new(width, height));
327 }
328
329 let mut rng = rand::thread_rng();
330
331 for y in 0..height {
333 for x in 0..width {
334 let pixel = binary.get_pixel(x, y)[0];
335 let is_black = pixel == 0;
336
337 let matrix = if is_black {
339 &black_matrix
340 } else {
341 &white_matrix
342 };
343
344 let total_cols = matrix.ncols();
346 let mut col_weights = Vec::new();
347
348 for col in 0..total_cols {
349 let black_count = (0..config.num_shares)
351 .map(|row| matrix[(row, col)] as usize)
352 .sum::<usize>();
353
354 let weight = if is_black {
356 (config.num_shares - black_count) + 1
357 } else {
358 black_count + 1
359 };
360
361 col_weights.push(weight);
362 }
363
364 let total_weight: usize = col_weights.iter().sum();
366 let mut random_weight = rng.gen_range(0..total_weight);
367 let mut selected_col = 0;
368
369 for (col, &weight) in col_weights.iter().enumerate() {
370 if random_weight < weight {
371 selected_col = col;
372 break;
373 }
374 random_weight -= weight;
375 }
376
377 for share_idx in 0..config.num_shares {
379 let value = matrix[(share_idx, selected_col)];
380 let pixel_value = if value == 1 { 0u8 } else { 255u8 };
381 shares[share_idx].put_pixel(x, y, Luma([pixel_value]));
382 }
383 }
384 }
385
386 let result: Vec<Share> = shares
388 .into_iter()
389 .enumerate()
390 .map(|(i, img)| {
391 Share::new(
392 DynamicImage::ImageLuma8(img),
393 i + 1,
394 config.num_shares,
395 width,
396 height,
397 1,
398 false,
399 )
400 })
401 .collect();
402
403 Ok(result)
404}
405
406fn progressive_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
408 basic_threshold_decrypt(shares, _config)
409}
410
411fn extended_meaningful_encrypt(
413 image: &DynamicImage,
414 config: &VCConfig,
415 cover_images: Option<Vec<DynamicImage>>,
416) -> Result<Vec<Share>> {
417 if cover_images.is_none() {
418 return Err(VCError::CoverImageError(
419 "Extended meaningful scheme requires cover images".to_string(),
420 ));
421 }
422
423 let binary = convert_to_binary(image);
425 let (width, height) = (binary.width(), binary.height());
426 let covers = cover_images.unwrap();
427
428 let matrices = generate_dispatching_matrices(config.num_shares, config.num_shares)?;
430
431 let mut shares = Vec::new();
432
433 for share_idx in 0..config.num_shares {
434 let mut share_img = ImageBuffer::new(width, height);
435 let cover_binary = convert_to_binary(&covers[share_idx]);
436
437 for y in 0..height {
438 for x in 0..width {
439 let secret_pixel = binary.get_pixel(x, y)[0] == 0;
440 let cover_pixel = cover_binary.get_pixel(x, y)[0] == 0;
441
442 let row = select_dispatching_row(&matrices, secret_pixel, cover_pixel);
444 let value = if row[share_idx] == 1 { 0u8 } else { 255u8 };
445
446 share_img.put_pixel(x, y, Luma([value]));
447 }
448 }
449
450 shares.push(Share::new(
451 DynamicImage::ImageLuma8(share_img),
452 share_idx + 1,
453 config.num_shares,
454 width,
455 height,
456 1,
457 true,
458 ));
459 }
460
461 Ok(shares)
462}
463
464fn extended_meaningful_decrypt(shares: &[Share], config: &VCConfig) -> Result<DynamicImage> {
466 basic_threshold_decrypt(shares, config)
467}
468
469fn naor_shamir_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
471 if config.num_shares != 2 || config.threshold != 2 {
473 return Err(VCError::InvalidConfiguration(
474 "Original Naor-Shamir scheme requires exactly 2 shares with threshold 2".to_string(),
475 ));
476 }
477
478 let binary = convert_to_binary(image);
479 let (width, height) = (binary.width(), binary.height());
480
481 let white_matrix = vec![vec![1, 1, 0, 0], vec![1, 1, 0, 0]];
484 let black_matrix = vec![vec![1, 1, 0, 0], vec![0, 0, 1, 1]];
486
487 let share_width = width * 2;
488 let share_height = height * 2;
489
490 let mut share1 = ImageBuffer::new(share_width, share_height);
491 let mut share2 = ImageBuffer::new(share_width, share_height);
492
493 let mut rng = rand::thread_rng();
494
495 for y in 0..height {
496 for x in 0..width {
497 let pixel = binary.get_pixel(x, y)[0];
498
499 let matrix = if pixel == 0 {
501 &black_matrix
503 } else {
504 &white_matrix
506 };
507
508 let mut columns: Vec<usize> = (0..4).collect();
510 columns.as_mut_slice().shuffle(&mut rng);
511
512 let share1_pattern = vec![
514 matrix[0][columns[0]],
515 matrix[0][columns[1]],
516 matrix[0][columns[2]],
517 matrix[0][columns[3]],
518 ];
519 let share2_pattern = vec![
520 matrix[1][columns[0]],
521 matrix[1][columns[1]],
522 matrix[1][columns[2]],
523 matrix[1][columns[3]],
524 ];
525
526 share1.put_pixel(
529 x * 2,
530 y * 2,
531 Luma([if share1_pattern[0] == 1 { 0 } else { 255 }]),
532 );
533 share1.put_pixel(
534 x * 2 + 1,
535 y * 2,
536 Luma([if share1_pattern[1] == 1 { 0 } else { 255 }]),
537 );
538 share1.put_pixel(
539 x * 2,
540 y * 2 + 1,
541 Luma([if share1_pattern[2] == 1 { 0 } else { 255 }]),
542 );
543 share1.put_pixel(
544 x * 2 + 1,
545 y * 2 + 1,
546 Luma([if share1_pattern[3] == 1 { 0 } else { 255 }]),
547 );
548
549 share2.put_pixel(
551 x * 2,
552 y * 2,
553 Luma([if share2_pattern[0] == 1 { 0 } else { 255 }]),
554 );
555 share2.put_pixel(
556 x * 2 + 1,
557 y * 2,
558 Luma([if share2_pattern[1] == 1 { 0 } else { 255 }]),
559 );
560 share2.put_pixel(
561 x * 2,
562 y * 2 + 1,
563 Luma([if share2_pattern[2] == 1 { 0 } else { 255 }]),
564 );
565 share2.put_pixel(
566 x * 2 + 1,
567 y * 2 + 1,
568 Luma([if share2_pattern[3] == 1 { 0 } else { 255 }]),
569 );
570 }
571 }
572
573 Ok(vec![
574 Share::new(
575 DynamicImage::ImageLuma8(share1),
576 1,
577 2,
578 width,
579 height,
580 2,
581 false,
582 ),
583 Share::new(
584 DynamicImage::ImageLuma8(share2),
585 2,
586 2,
587 width,
588 height,
589 2,
590 false,
591 ),
592 ])
593}
594
595fn naor_shamir_decrypt(shares: &[Share], config: &VCConfig) -> Result<DynamicImage> {
597 basic_threshold_decrypt(shares, config)
598}
599
600fn taghaddos_latif_encrypt(image: &DynamicImage, config: &VCConfig) -> Result<Vec<Share>> {
602 if config.num_shares != 2 {
603 return Err(VCError::InvalidConfiguration(
604 "Taghaddos-Latif scheme requires exactly 2 shares".to_string(),
605 ));
606 }
607
608 let gray = image.to_luma8();
609 let (width, height) = (gray.width(), gray.height());
610
611 let patterns = [
613 [1u8, 1u8, 0u8, 0u8],
614 [1u8, 0u8, 1u8, 0u8],
615 [1u8, 0u8, 0u8, 1u8],
616 [0u8, 1u8, 1u8, 0u8],
617 [0u8, 1u8, 0u8, 1u8],
618 [0u8, 0u8, 1u8, 1u8],
619 ];
620
621 let share_width = width * 2;
623 let share_height = height * 2;
624
625 let mut share_a = ImageBuffer::new(share_width, share_height);
626 let mut share_b = ImageBuffer::new(share_width, share_height);
627
628 let mut rng = rand::thread_rng();
629
630 for y in 0..height {
632 for x in 0..width {
633 let pixel_value = gray.get_pixel(x, y)[0];
634 let mut share_a_colors = [0u8; 4];
635 let mut share_b_colors = [0u8; 4];
636
637 for bit_pos in 0..8 {
639 let bit = (pixel_value >> bit_pos) & 1;
640
641 let pattern = patterns[rng.gen_range(0..6)];
643
644 if bit == 1 {
645 for i in 0..4 {
647 share_a_colors[i] |= (pattern[i] << bit_pos);
648 share_b_colors[i] = share_a_colors[i];
649 }
650 } else {
651 for i in 0..4 {
653 share_a_colors[i] |= (pattern[i] << bit_pos);
654 share_b_colors[i] |= ((1 - pattern[i]) << bit_pos);
655 }
656 }
657 }
658
659 let base_x = x * 2;
661 let base_y = y * 2;
662
663 share_a.put_pixel(base_x, base_y, Luma([share_a_colors[0]]));
665 share_a.put_pixel(base_x + 1, base_y, Luma([share_a_colors[1]]));
666 share_a.put_pixel(base_x, base_y + 1, Luma([share_a_colors[2]]));
667 share_a.put_pixel(base_x + 1, base_y + 1, Luma([share_a_colors[3]]));
668
669 share_b.put_pixel(base_x, base_y, Luma([share_b_colors[0]]));
671 share_b.put_pixel(base_x + 1, base_y, Luma([share_b_colors[1]]));
672 share_b.put_pixel(base_x, base_y + 1, Luma([share_b_colors[2]]));
673 share_b.put_pixel(base_x + 1, base_y + 1, Luma([share_b_colors[3]]));
674 }
675 }
676
677 Ok(vec![
678 Share::new(
679 DynamicImage::ImageLuma8(share_a),
680 1,
681 2,
682 width,
683 height,
684 2, false,
686 ),
687 Share::new(
688 DynamicImage::ImageLuma8(share_b),
689 2,
690 2,
691 width,
692 height,
693 2, false,
695 ),
696 ])
697}
698
699fn taghaddos_latif_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
701 if shares.len() < 2 {
702 return Err(VCError::InsufficientShares {
703 required: 2,
704 provided: shares.len(),
705 });
706 }
707
708 let (expanded_width, expanded_height) = shares[0].dimensions();
709
710 let width = expanded_width / 2;
712 let height = expanded_height / 2;
713
714 let mut result = ImageBuffer::new(width, height);
715
716 let share_a = if let DynamicImage::ImageLuma8(img) = &shares[0].image {
718 img
719 } else {
720 return Err(VCError::DecryptionError(
721 "Share A is not grayscale".to_string(),
722 ));
723 };
724
725 let share_b = if let DynamicImage::ImageLuma8(img) = &shares[1].image {
726 img
727 } else {
728 return Err(VCError::DecryptionError(
729 "Share B is not grayscale".to_string(),
730 ));
731 };
732
733 for y in 0..height {
735 for x in 0..width {
736 let base_x = x * 2;
737 let base_y = y * 2;
738
739 let share_a_block = [
741 share_a.get_pixel(base_x, base_y)[0],
742 share_a.get_pixel(base_x + 1, base_y)[0],
743 share_a.get_pixel(base_x, base_y + 1)[0],
744 share_a.get_pixel(base_x + 1, base_y + 1)[0],
745 ];
746
747 let share_b_block = [
748 share_b.get_pixel(base_x, base_y)[0],
749 share_b.get_pixel(base_x + 1, base_y)[0],
750 share_b.get_pixel(base_x, base_y + 1)[0],
751 share_b.get_pixel(base_x + 1, base_y + 1)[0],
752 ];
753
754 let mut reconstructed_value = 0u8;
756
757 for bit_pos in 0..8 {
758 let mut reconstructed_bits = [0u8; 4];
759
760 for i in 0..4 {
762 let bit_a = (share_a_block[i] >> bit_pos) & 1;
763 let bit_b = (share_b_block[i] >> bit_pos) & 1;
764 reconstructed_bits[i] = bit_a & bit_b;
765 }
766
767 let sum = reconstructed_bits.iter().map(|&x| x as u32).sum::<u32>();
770 let average_bit = if sum >= 2 { 1 } else { 0 }; reconstructed_value |= (average_bit as u8) << bit_pos;
773 }
774
775 result.put_pixel(x, y, Luma([reconstructed_value]));
776 }
777 }
778
779 Ok(DynamicImage::ImageLuma8(result))
780}
781
782fn dhiman_kasana_encrypt(
784 image: &DynamicImage,
785 config: &VCConfig,
786 cover_images: Option<Vec<DynamicImage>>,
787) -> Result<Vec<Share>> {
788 if config.num_shares != 3 {
789 return Err(VCError::InvalidConfiguration(
790 "Dhiman-Kasana EVCT(3,3) scheme requires exactly 3 shares".to_string(),
791 ));
792 }
793
794 let rgb = image.to_rgb8();
795 let (width, height) = (rgb.width(), rgb.height());
796
797 let components = [
799 [
801 (4, 4),
802 (4, 2),
803 (3, 1),
804 (2, 3),
805 (2, 0),
806 (1, 4),
807 (1, 2),
808 (0, 1),
809 ],
810 [
812 (4, 3),
813 (3, 4),
814 (3, 2),
815 (2, 1),
816 (1, 3),
817 (1, 0),
818 (0, 4),
819 (0, 2),
820 ],
821 [
823 (4, 1),
824 (3, 3),
825 (3, 0),
826 (2, 4),
827 (2, 2),
828 (1, 1),
829 (0, 3),
830 (0, 0),
831 ],
832 ];
833
834 let share_width = width * 5;
836 let share_height = height * 5;
837
838 let mut shares = Vec::new();
839 for _ in 0..3 {
840 shares.push(ImageBuffer::new(share_width, share_height));
841 }
842
843 let has_cover_images = cover_images.is_some();
845
846 let covers = if let Some(covers) = cover_images {
848 if covers.len() != 3 {
849 return Err(VCError::CoverImageError(
850 "Dhiman-Kasana requires exactly 3 cover images".to_string(),
851 ));
852 }
853 covers.into_iter().map(|img| img.to_rgb8()).collect()
854 } else {
855 vec![
857 ImageBuffer::from_pixel(width, height, Rgb([255, 255, 255])),
858 ImageBuffer::from_pixel(width, height, Rgb([255, 255, 255])),
859 ImageBuffer::from_pixel(width, height, Rgb([255, 255, 255])),
860 ]
861 };
862
863 for y in 0..height {
865 for x in 0..width {
866 let secret_pixel = rgb.get_pixel(x, y);
867 let [r, g, b] = secret_pixel.0;
868
869 for share_idx in 0..3 {
871 let cover_pixel = covers[share_idx].get_pixel(x, y);
872
873 let mut block = ImageBuffer::from_pixel(5, 5, *cover_pixel);
875
876 for (channel_idx, &channel_value) in [r, g, b].iter().enumerate() {
878 let bit_positions = &components[channel_idx];
879
880 for (bit_idx, &(bit_y, bit_x)) in bit_positions.iter().enumerate() {
882 let bit = (channel_value >> bit_idx) & 1;
883
884 let pixel_color = if bit == 1 {
886 Rgb([0, 0, 0]) } else {
888 Rgb([30, 30, 30]) };
890
891 block.put_pixel(bit_x, bit_y, pixel_color);
892 }
893 }
894
895 let base_x = x * 5;
897 let base_y = y * 5;
898 for block_y in 0..5 {
899 for block_x in 0..5 {
900 let pixel = block.get_pixel(block_x, block_y);
901 shares[share_idx].put_pixel(base_x + block_x, base_y + block_y, *pixel);
902 }
903 }
904 }
905 }
906 }
907
908 let result: Vec<Share> = shares
910 .into_iter()
911 .enumerate()
912 .map(|(i, img)| {
913 Share::new(
914 DynamicImage::ImageRgb8(img),
915 i + 1,
916 3,
917 width,
918 height,
919 5, has_cover_images,
921 )
922 })
923 .collect();
924
925 Ok(result)
926}
927
928fn dhiman_kasana_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
930 if shares.len() < 3 {
931 return Err(VCError::InsufficientShares {
932 required: 3,
933 provided: shares.len(),
934 });
935 }
936
937 let (expanded_width, expanded_height) = shares[0].dimensions();
939
940 let width = expanded_width / 5;
942 let height = expanded_height / 5;
943
944 let mut result = ImageBuffer::new(width, height);
945
946 let components = [
948 [
950 (4, 4),
951 (4, 2),
952 (3, 1),
953 (2, 3),
954 (2, 0),
955 (1, 4),
956 (1, 2),
957 (0, 1),
958 ],
959 [
961 (4, 3),
962 (3, 4),
963 (3, 2),
964 (2, 1),
965 (1, 3),
966 (1, 0),
967 (0, 4),
968 (0, 2),
969 ],
970 [
972 (4, 1),
973 (3, 3),
974 (3, 0),
975 (2, 4),
976 (2, 2),
977 (1, 1),
978 (0, 3),
979 (0, 0),
980 ],
981 ];
982
983 let share_images: Vec<&ImageBuffer<Rgb<u8>, Vec<u8>>> = shares
985 .iter()
986 .map(|share| {
987 if let DynamicImage::ImageRgb8(img) = &share.image {
988 img
989 } else {
990 panic!("Share is not RGB format");
991 }
992 })
993 .collect();
994
995 for y in 0..height {
997 for x in 0..width {
998 let mut reconstructed_pixel = [0u8; 3];
999
1000 for channel_idx in 0..3 {
1002 let bit_positions = &components[channel_idx];
1003 let mut channel_value = 0u8;
1004
1005 for (bit_idx, &(bit_y, bit_x)) in bit_positions.iter().enumerate() {
1007 let base_x = x * 5;
1008 let base_y = y * 5;
1009
1010 let pixel = share_images[channel_idx].get_pixel(base_x + bit_x, base_y + bit_y);
1012
1013 let bit = if pixel.0 == [0, 0, 0] { 1 } else { 0 };
1015
1016 channel_value |= bit << bit_idx;
1017 }
1018
1019 reconstructed_pixel[channel_idx] = channel_value;
1020 }
1021
1022 result.put_pixel(x, y, Rgb(reconstructed_pixel));
1023 }
1024 }
1025
1026 Ok(DynamicImage::ImageRgb8(result))
1027}
1028
1029fn yamaguchi_nakajima_encrypt(
1032 image: &DynamicImage,
1033 config: &VCConfig,
1034 cover_images: Option<Vec<DynamicImage>>,
1035) -> Result<Vec<Share>> {
1036 if config.num_shares != 2 {
1037 return Err(VCError::InvalidConfiguration(
1038 "Yamaguchi-Nakajima scheme requires exactly 2 shares".to_string(),
1039 ));
1040 }
1041
1042 let cover_images = cover_images.ok_or_else(|| {
1043 VCError::CoverImageError(
1044 "Yamaguchi-Nakajima scheme requires 2 cover images (sheet images)".to_string(),
1045 )
1046 })?;
1047
1048 if cover_images.len() != 2 {
1049 return Err(VCError::CoverImageError(
1050 "Yamaguchi-Nakajima scheme requires exactly 2 cover images".to_string(),
1051 ));
1052 }
1053
1054 let target = image.to_luma8();
1055 let sheet1 = cover_images[0].to_luma8();
1056 let sheet2 = cover_images[1].to_luma8();
1057 let (width, height) = (target.width(), target.height());
1058
1059 let sheet1 = image::imageops::resize(
1061 &sheet1,
1062 width,
1063 height,
1064 image::imageops::FilterType::Lanczos3,
1065 );
1066 let sheet2 = image::imageops::resize(
1067 &sheet2,
1068 width,
1069 height,
1070 image::imageops::FilterType::Lanczos3,
1071 );
1072
1073 let m = config.block_size.max(4) as usize;
1075 let sub_size = (m as f64).sqrt() as usize;
1076
1077 let contrast = 0.6;
1079 let l = (1.0 - contrast) / 2.0; let u = l + contrast; let sheet1_processed = apply_contrast_and_halftone(&sheet1, contrast, l);
1084 let sheet2_processed = apply_contrast_and_halftone(&sheet2, contrast, l);
1085 let target_processed = apply_halftone_target(&target, contrast);
1086
1087 let out_width = width * sub_size as u32;
1089 let out_height = height * sub_size as u32;
1090 let mut out_sheet1 = ImageBuffer::new(out_width, out_height);
1091 let mut out_sheet2 = ImageBuffer::new(out_width, out_height);
1092
1093 let mut rng = rand::thread_rng();
1094
1095 for y in 0..height {
1097 for x in 0..width {
1098 let t1 = sheet1_processed.get_pixel(x, y)[0] as f64 / 255.0;
1100 let t2 = sheet2_processed.get_pixel(x, y)[0] as f64 / 255.0;
1101 let tt = target_processed.get_pixel(x, y)[0] as f64 / 255.0;
1102
1103 let (t1, t2, tt) = adjust_triplet(t1, t2, tt);
1105
1106 let s1 = (t1 * m as f64).round() as usize;
1108 let s2 = (t2 * m as f64).round() as usize;
1109 let st = (tt * m as f64).round() as usize;
1110
1111 let matrices = generate_boolean_matrices(s1, s2, st, m);
1113
1114 if !matrices.is_empty() {
1115 let matrix = &matrices[rng.gen_range(0..matrices.len())];
1117
1118 for i in 0..sub_size {
1120 for j in 0..sub_size {
1121 let idx = i * sub_size + j;
1122 if idx < m {
1123 let pixel_val1 = if matrix[0][idx] == 1 { 255 } else { 0 };
1124 let pixel_val2 = if matrix[1][idx] == 1 { 255 } else { 0 };
1125
1126 out_sheet1.put_pixel(
1127 x * sub_size as u32 + j as u32,
1128 y * sub_size as u32 + i as u32,
1129 Luma([pixel_val1]),
1130 );
1131 out_sheet2.put_pixel(
1132 x * sub_size as u32 + j as u32,
1133 y * sub_size as u32 + i as u32,
1134 Luma([pixel_val2]),
1135 );
1136 }
1137 }
1138 }
1139 } else {
1140 for i in 0..sub_size {
1142 for j in 0..sub_size {
1143 let val1 = if rng.gen_bool(0.5) { 255 } else { 0 };
1144 let val2 = if rng.gen_bool(0.5) { 255 } else { 0 };
1145
1146 out_sheet1.put_pixel(
1147 x * sub_size as u32 + j as u32,
1148 y * sub_size as u32 + i as u32,
1149 Luma([val1]),
1150 );
1151 out_sheet2.put_pixel(
1152 x * sub_size as u32 + j as u32,
1153 y * sub_size as u32 + i as u32,
1154 Luma([val2]),
1155 );
1156 }
1157 }
1158 }
1159 }
1160 }
1161
1162 Ok(vec![
1163 Share::new(
1164 DynamicImage::ImageLuma8(out_sheet1),
1165 1,
1166 2,
1167 width,
1168 height,
1169 sub_size,
1170 true,
1171 ),
1172 Share::new(
1173 DynamicImage::ImageLuma8(out_sheet2),
1174 2,
1175 2,
1176 width,
1177 height,
1178 sub_size,
1179 true,
1180 ),
1181 ])
1182}
1183
1184fn yamaguchi_nakajima_decrypt(shares: &[Share], _config: &VCConfig) -> Result<DynamicImage> {
1186 if shares.len() < 2 {
1187 return Err(VCError::InsufficientShares {
1188 required: 2,
1189 provided: shares.len(),
1190 });
1191 }
1192
1193 let (expanded_width, expanded_height) = shares[0].dimensions();
1194
1195 let sheet1 = if let DynamicImage::ImageLuma8(img) = &shares[0].image {
1197 img
1198 } else {
1199 return Err(VCError::DecryptionError(
1200 "Share 1 is not grayscale".to_string(),
1201 ));
1202 };
1203
1204 let sheet2 = if let DynamicImage::ImageLuma8(img) = &shares[1].image {
1205 img
1206 } else {
1207 return Err(VCError::DecryptionError(
1208 "Share 2 is not grayscale".to_string(),
1209 ));
1210 };
1211
1212 let mut result = ImageBuffer::new(expanded_width, expanded_height);
1214
1215 for y in 0..expanded_height {
1217 for x in 0..expanded_width {
1218 let pixel1 = sheet1.get_pixel(x, y)[0];
1219 let pixel2 = sheet2.get_pixel(x, y)[0];
1220
1221 let result_pixel = if pixel1 == 255 && pixel2 == 255 {
1223 255
1224 } else {
1225 0
1226 };
1227 result.put_pixel(x, y, Luma([result_pixel]));
1228 }
1229 }
1230
1231 Ok(DynamicImage::ImageLuma8(result))
1232}
1233
1234fn apply_contrast_and_halftone(
1236 image: &ImageBuffer<Luma<u8>, Vec<u8>>,
1237 contrast: f64,
1238 l: f64,
1239) -> ImageBuffer<Luma<u8>, Vec<u8>> {
1240 let (width, height) = (image.width(), image.height());
1241 let mut working_image = ImageBuffer::new(width, height);
1242
1243 for y in 0..height {
1245 for x in 0..width {
1246 let pixel = image.get_pixel(x, y)[0] as f64 / 255.0;
1247 let adjusted = l + pixel * contrast;
1248 working_image.put_pixel(x, y, Luma([(adjusted * 255.0) as u8]));
1249 }
1250 }
1251
1252 floyd_steinberg_dithering(&working_image)
1254}
1255
1256fn apply_halftone_target(
1258 image: &ImageBuffer<Luma<u8>, Vec<u8>>,
1259 contrast: f64,
1260) -> ImageBuffer<Luma<u8>, Vec<u8>> {
1261 let (width, height) = (image.width(), image.height());
1262 let mut working_image = ImageBuffer::new(width, height);
1263
1264 for y in 0..height {
1266 for x in 0..width {
1267 let pixel = image.get_pixel(x, y)[0] as f64 / 255.0;
1268 let adjusted = pixel * contrast;
1269 working_image.put_pixel(x, y, Luma([(adjusted * 255.0) as u8]));
1270 }
1271 }
1272
1273 floyd_steinberg_dithering(&working_image)
1275}
1276
1277fn floyd_steinberg_dithering(
1279 image: &ImageBuffer<Luma<u8>, Vec<u8>>,
1280) -> ImageBuffer<Luma<u8>, Vec<u8>> {
1281 let (width, height) = (image.width(), image.height());
1282 let mut working = vec![vec![0.0; width as usize]; height as usize];
1283 let mut result = ImageBuffer::new(width, height);
1284
1285 for y in 0..height {
1287 for x in 0..width {
1288 working[y as usize][x as usize] = image.get_pixel(x, y)[0] as f64 / 255.0;
1289 }
1290 }
1291
1292 for y in 0..height {
1294 for x in 0..width {
1295 let old_pixel = working[y as usize][x as usize];
1296 let new_pixel = if old_pixel > 0.5 { 1.0 } else { 0.0 };
1297 result.put_pixel(x, y, Luma([(new_pixel * 255.0) as u8]));
1298
1299 let error = old_pixel - new_pixel;
1300
1301 if x + 1 < width {
1303 working[y as usize][(x + 1) as usize] += error * 7.0 / 16.0;
1304 }
1305 if y + 1 < height {
1306 if x > 0 {
1307 working[(y + 1) as usize][(x - 1) as usize] += error * 3.0 / 16.0;
1308 }
1309 working[(y + 1) as usize][x as usize] += error * 5.0 / 16.0;
1310 if x + 1 < width {
1311 working[(y + 1) as usize][(x + 1) as usize] += error * 1.0 / 16.0;
1312 }
1313 }
1314 }
1315 }
1316
1317 result
1318}
1319
1320fn check_constraint(t1: f64, t2: f64, tt: f64) -> bool {
1322 let min_tt = (0.0_f64).max(t1 + t2 - 1.0);
1323 let max_tt = t1.min(t2);
1324 min_tt <= tt && tt <= max_tt
1325}
1326
1327fn adjust_triplet(t1: f64, t2: f64, tt: f64) -> (f64, f64, f64) {
1329 let min_tt = (0.0_f64).max(t1 + t2 - 1.0);
1330 let max_tt = t1.min(t2);
1331
1332 let tt_adj = if tt < min_tt {
1333 min_tt
1334 } else if tt > max_tt {
1335 max_tt
1336 } else {
1337 tt
1338 };
1339
1340 (t1, t2, tt_adj)
1341}
1342
1343fn generate_boolean_matrices(s1: usize, s2: usize, st: usize, m: usize) -> Vec<Vec<Vec<u8>>> {
1345 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 {
1353 return vec![];
1354 }
1355
1356 let mut base_patterns = Vec::new();
1358
1359 for _ in 0..p11 {
1361 base_patterns.push([1, 1]);
1362 }
1363 for _ in 0..p10 {
1365 base_patterns.push([1, 0]);
1366 }
1367 for _ in 0..p01 {
1369 base_patterns.push([0, 1]);
1370 }
1371 for _ in 0..p00 {
1373 base_patterns.push([0, 0]);
1374 }
1375
1376 let mut rng = rand::thread_rng();
1378 base_patterns.shuffle(&mut rng);
1379
1380 let matrix = vec![
1381 base_patterns
1382 .iter()
1383 .map(|pair| pair[0])
1384 .collect::<Vec<u8>>(),
1385 base_patterns
1386 .iter()
1387 .map(|pair| pair[1])
1388 .collect::<Vec<u8>>(),
1389 ];
1390
1391 vec![matrix]
1392}