1#![doc(html_root_url = "https://docs.rs/captcha-rs/latest")]
2
3use image::DynamicImage;
22use imageproc::noise::{gaussian_noise_mut, salt_and_pepper_noise_mut};
23use rand::Rng;
24
25use crate::captcha::{
26 cyclic_write_character, draw_interference_ellipse, draw_interference_line, get_image,
27 to_base64_str,
28};
29
30mod captcha;
31
32pub struct Captcha {
33 pub text: String,
34 pub image: DynamicImage,
35 pub compression: u8,
36 pub dark_mode: bool,
37}
38
39impl Captcha {
40 pub fn to_base64(&self) -> String {
41 to_base64_str(&self.image, self.compression)
42 }
43}
44
45#[derive(Default)]
46pub struct CaptchaBuilder {
47 text: Option<String>,
48 width: Option<u32>,
49 height: Option<u32>,
50 dark_mode: Option<bool>,
51 complexity: Option<u32>,
52 compression: Option<u8>,
53}
54
55impl CaptchaBuilder {
56 pub fn new() -> Self {
57 CaptchaBuilder {
58 text: None,
59 width: None,
60 height: None,
61 dark_mode: None,
62 complexity: None,
63 compression: Some(40),
64 }
65 }
66
67 pub fn text(mut self, text: String) -> Self {
68 self.text = Some(text);
69 self
70 }
71
72 pub fn length(mut self, length: usize) -> Self {
73 let res = captcha::get_captcha(length);
75 self.text = Some(res.join(""));
76 self
77 }
78
79 pub fn width(mut self, width: u32) -> Self {
80 self.width = Some(width);
81 self
82 }
83
84 pub fn height(mut self, height: u32) -> Self {
85 self.height = Some(height);
86 self
87 }
88
89 pub fn dark_mode(mut self, dark_mode: bool) -> Self {
90 self.dark_mode = Some(dark_mode);
91 self
92 }
93
94 pub fn complexity(mut self, complexity: u32) -> Self {
95 let mut complexity = complexity;
96
97 if complexity > 10 {
98 complexity = 10;
99 }
100
101 if complexity < 1 {
102 complexity = 1;
103 }
104
105 self.complexity = Some(complexity);
106 self
107 }
108
109 pub fn compression(mut self, compression: u8) -> Self {
110 self.compression = Some(compression);
111 self
112 }
113
114 pub fn build(self) -> Captcha {
115 let text = self.text.unwrap_or(captcha::get_captcha(5).join(""));
116 let width = self.width.unwrap_or(130);
117 let height = self.height.unwrap_or(40);
118 let dark_mode = self.dark_mode.unwrap_or(false);
119 let complexity = self.complexity.unwrap_or(1);
120 let compression = self.compression.unwrap_or(40);
121
122 let mut image = get_image(width, height, dark_mode);
124
125 let res: Vec<String> = text.chars().map(|x| x.to_string()).collect();
126
127 cyclic_write_character(&res, &mut image, dark_mode);
129
130 draw_interference_line(&mut image, dark_mode);
132 draw_interference_line(&mut image, dark_mode);
133
134 draw_interference_ellipse(2, &mut image, dark_mode);
136 draw_interference_ellipse(2, &mut image, dark_mode);
137
138 if complexity > 1 {
139 let mut rng = rand::thread_rng();
140
141 gaussian_noise_mut(
142 &mut image,
143 (complexity - 1) as f64,
144 ((5 * complexity) - 5) as f64,
145 rng.gen(),
146 );
147
148 salt_and_pepper_noise_mut(&mut image, (0.002 * complexity as f64) - 0.002, rng.gen());
149 }
150
151 Captcha {
152 text,
153 image: DynamicImage::ImageRgb8(image),
154 compression,
155 dark_mode,
156 }
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use crate::CaptchaBuilder;
163
164 #[test]
165 fn it_generates_a_captcha() {
166 let _dark_mode = false;
167 let _text_length = 5;
168 let _width = 130;
169 let _height = 40;
170
171 let start = std::time::Instant::now();
172
173 let captcha = CaptchaBuilder::new()
174 .text(String::from("based"))
175 .width(200)
176 .height(70)
177 .dark_mode(false)
178 .build();
179
180 let duration = start.elapsed();
181 println!("Time elapsed in generating captcha() is: {:?}", duration);
182
183 assert_eq!(captcha.text.len(), 5);
184 let base_img = captcha.to_base64();
185 assert!(base_img.starts_with("data:image/jpeg;base64,"));
186 println!("text: {}", captcha.text);
187 println!("base_img: {}", base_img);
188 }
189
190 #[test]
191 fn it_generates_captcha_using_builder() {
192 let start = std::time::Instant::now();
193 let captcha = CaptchaBuilder::new()
194 .length(5)
195 .width(200)
196 .height(70)
197 .dark_mode(false)
198 .complexity(5)
199 .compression(40)
200 .build();
201
202 let duration = start.elapsed();
203 println!("Time elapsed in generating captcha() is: {:?}", duration);
204
205 assert_eq!(captcha.text.len(), 5);
206 let base_img = captcha.to_base64();
207 assert!(base_img.starts_with("data:image/jpeg;base64,"));
208 println!("text: {}", captcha.text);
209 println!("base_img: {}", base_img);
210 }
211}