1use std::error::Error;
2
3use image::{DynamicImage, GenericImageView, Rgb, RgbImage};
4use rustdct::DctPlanner;
5
6type Result<T> = std::result::Result<T, Box<dyn Error>>;
7
8pub fn get_watermark_from_str(words: &str) -> Result<f32> {
9 let char_map =
10 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(),.<>/?; ";
11
12 let mut ret = 0.0;
13
14 for i in 0..words.len() {
15 let c = words.chars().nth(i).ok_or("invalid index")?;
16 let char_idx = char_map
17 .find(c)
18 .ok_or(format!("Invalid character; {}", c))?;
19 ret += char_idx as f32;
20 }
21
22 Ok(ret
23 * option_env!("WATERMARK_STRENGTH")
24 .unwrap_or("0.01")
25 .parse::<f32>()?)
26}
27
28pub fn embed_watermark_color(image: &DynamicImage, watermark: &str) -> Result<RgbImage> {
29 let watermark = get_watermark_from_str(watermark)?;
30
31 let (width, height) = image.dimensions();
32 let len = (width * height) as usize;
33 let mut cbcr_channel = vec![(0, 0); len];
34 let mut y_channel = vec![0.0; len];
35 let idx_fn = |x: u32, y: u32| (y * width + x) as usize;
36 let normalization_factor = (2.0 / len as f32).sqrt();
37
38 let image = image.to_rgb8();
39
40 for (x, y, pixel) in image.enumerate_pixels() {
41 let idx = idx_fn(x, y);
42
43 let (y, u, v) = rgb_to_ycbcr(&pixel);
44 cbcr_channel[idx] = (u, v);
45 y_channel[idx] = y as f32 + watermark;
46 }
47
48 let mut dct_planner: DctPlanner<f32> = DctPlanner::new();
49 let dct = dct_planner.plan_dct2(len);
50 dct.process_dct2(&mut y_channel);
51
52 for y in y_channel.iter_mut() {
53 *y *= normalization_factor;
54 }
55
56 let idct = dct_planner.plan_dct3(len);
57 idct.process_dct3(&mut y_channel);
58 for y in y_channel.iter_mut() {
59 *y *= normalization_factor;
60 }
61
62 let mut img_buffer = RgbImage::new(width, height);
63 for (x, y, pixel) in img_buffer.enumerate_pixels_mut() {
64 let index = idx_fn(x, y);
65 let y_ch = y_channel[index];
66 let (cb, cr) = cbcr_channel[index];
67
68 *pixel = ycbcr_to_rgb(y_ch, cb as f32, cr as f32);
69 }
70
71 Ok(img_buffer)
72}
73
74fn rgb_to_ycbcr(pixel: &Rgb<u8>) -> (u8, u8, u8) {
75 let r = pixel[0] as f64;
76 let g = pixel[1] as f64;
77 let b = pixel[2] as f64;
78
79 let y = (0.299 * r + 0.587 * g + 0.114 * b).round() as u8;
80 let cb = (-0.169 * r - 0.331 * g + 0.5 * b + 128.0).round() as u8;
81 let cr = (0.5 * r - 0.419 * g - 0.081 * b + 128.0).round() as u8;
82
83 (y, cb, cr)
84}
85
86fn ycbcr_to_rgb(y: f32, cb: f32, cr: f32) -> Rgb<u8> {
87 let r = (y + 1.402 * (cr as f32 - 128.0)).round() as u8;
88 let g = (y - 0.34414 * (cb as f32 - 128.0) - 0.71414 * (cr as f32 - 128.0)).round() as u8;
89 let b = (y + 1.772 * (cb as f32 - 128.0)).round() as u8;
90
91 Rgb([r, g, b])
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_get_watermark_from_str() {
100 let words = "Hello, World!";
101 let bytes = get_watermark_from_str(words).unwrap();
102 assert_eq!(bytes, 5.35);
103 }
104
105 #[test]
106 fn test_rgb_to_ycbcr() {
107 for (r, g, b) in vec![
109 (255, 255, 255),
110 (254, 0, 0),
111 (0, 255, 1),
112 (0, 0, 254),
113 (0, 0, 0),
114 (128, 128, 128),
115 ] {
116 let (y, cb, cr) = rgb_to_ycbcr(&Rgb([r, g, b]));
117 let color = ycbcr_to_rgb(y as f32, cb as f32, cr as f32);
118
119 assert_eq!(Rgb([r, g, b]), color, "rgb: {:?} {:?}", (r, g, b), color);
120 }
121 }
122
123 #[test]
124 fn test_watermark() {
125 let img = image::open("image.png").unwrap();
126 let watermark = "Hello, World!";
127 let watermarked_img = embed_watermark_color(&img, watermark);
128 assert!(watermarked_img.is_ok(), "Failed to embed watermark");
129 assert!(
130 watermarked_img.unwrap().save("output.png").is_ok(),
131 "Failed to save image"
132 )
133 }
134}