From ed5685123a9f525dfaf2331e9d6e0c766fce238c Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Wed, 12 Feb 2025 15:37:41 +0800 Subject: [PATCH] Add `circle` option to contrl the circle draw. --- Rakefile | 2 +- ext/rucaptcha/src/captcha.rs | 130 ++++++++++++++++++--------------- ext/rucaptcha/src/lib.rs | 4 +- lib/rucaptcha.rb | 5 +- lib/rucaptcha/configuration.rb | 2 + 5 files changed, 81 insertions(+), 62 deletions(-) diff --git a/Rakefile b/Rakefile index 0c75d3d..0952091 100644 --- a/Rakefile +++ b/Rakefile @@ -30,7 +30,7 @@ task default: :spec def create_captcha(length = 5, difficulty = 5) require "rucaptcha" - RuCaptchaCore.create(length, difficulty, false, false, "png") + RuCaptchaCore.create(length, difficulty, false, false, false, "png") end task :preview do diff --git a/ext/rucaptcha/src/captcha.rs b/ext/rucaptcha/src/captcha.rs index bff7496..c371f7a 100644 --- a/ext/rucaptcha/src/captcha.rs +++ b/ext/rucaptcha/src/captcha.rs @@ -74,63 +74,6 @@ fn get_next(min: f32, max: u32) -> f32 { min + rand_num(max as usize - min as usize) as f32 } -fn cyclic_write_character(captcha: &str, image: &mut ImageBuffer, Vec>, lines: bool) { - let c = (image.width() - 20) / captcha.len() as u32; - let y = image.height() / 3 - 15; - - let h = image.height() as f32; - - let scale = match captcha.len() { - 1..=3 => SCALE_LG, - 4..=5 => SCALE_MD, - _ => SCALE_SM, - } as f32; - - let colors = get_colors(captcha.len()); - let line_colors = get_colors(captcha.len()); - - let xscale = scale - rand_num((scale * 0.2) as usize) as f32; - let yscale = h - rand_num((h * 0.2) as usize) as f32; - - // Draw line, ellipse first as background - (0..captcha.len()).for_each(|i| { - let line_color = line_colors[i]; - - if lines { - draw_interference_line(1, image, line_color); - } - draw_interference_ellipse(1, image, line_color); - }); - - let font = match rand_num(2) { - 0 => &FONT_0, - 1 => &FONT_1, - _ => &FONT_1, - }; - - // Draw text - for (i, ch) in captcha.chars().enumerate() { - let color = colors[i]; - - for j in 0..(rand_num(3) + 1) as i32 { - // Draw text again with offset - let offset = j * (rand_num(2) as i32); - imageproc::drawing::draw_text_mut( - image, - color, - 10 + offset + (i as u32 * c) as i32, - y as i32, - Scale { - x: xscale + offset as f32, - y: yscale as f32, - }, - font, - &ch.to_string(), - ); - } - } -} - fn draw_interference_line(num: usize, image: &mut ImageBuffer, Vec>, color: Rgba) { for _ in 0..num { let width = image.width(); @@ -185,6 +128,7 @@ pub struct CaptchaBuilder { complexity: usize, line: bool, noise: bool, + circle: bool, format: image::ImageFormat, } @@ -197,6 +141,7 @@ impl Default for CaptchaBuilder { complexity: 5, line: true, noise: false, + circle: true, format: image::ImageFormat::Png, } } @@ -222,6 +167,11 @@ impl CaptchaBuilder { self } + pub fn circle(mut self, circle: bool) -> Self { + self.circle = circle; + self + } + pub fn format(mut self, format: &str) -> Self { self.format = match format { "png" => image::ImageFormat::Png, @@ -238,6 +188,70 @@ impl CaptchaBuilder { self } + fn cyclic_write_character( + &self, + captcha: &str, + image: &mut ImageBuffer, Vec>, + lines: bool, + ) { + let c = (image.width() - 20) / captcha.len() as u32; + let y = image.height() / 3 - 15; + + let h = image.height() as f32; + + let scale = match captcha.len() { + 1..=3 => SCALE_LG, + 4..=5 => SCALE_MD, + _ => SCALE_SM, + } as f32; + + let colors = get_colors(captcha.len()); + let line_colors = get_colors(captcha.len()); + + let xscale = scale - rand_num((scale * 0.2) as usize) as f32; + let yscale = h - rand_num((h * 0.2) as usize) as f32; + + // Draw line, ellipse first as background + if self.circle { + (0..captcha.len()).for_each(|i| { + let line_color = line_colors[i]; + + if lines { + draw_interference_line(1, image, line_color); + } + draw_interference_ellipse(1, image, line_color); + }); + } + + let font = match rand_num(2) { + 0 => &FONT_0, + 1 => &FONT_1, + _ => &FONT_1, + }; + + // Draw text + for (i, ch) in captcha.chars().enumerate() { + let color = colors[i]; + + for j in 0..(rand_num(3) + 1) as i32 { + // Draw text again with offset + let offset = j * (rand_num(2) as i32); + imageproc::drawing::draw_text_mut( + image, + color, + 10 + offset + (i as u32 * c) as i32, + y as i32, + Scale { + x: xscale + offset as f32, + y: yscale as f32, + }, + font, + &ch.to_string(), + ); + } + } + } + pub fn build(self) -> Captcha { // Generate an array of captcha characters let text = rand_captcha(self.length); @@ -248,7 +262,7 @@ impl CaptchaBuilder { }); // Loop to write the verification code string into the background image - cyclic_write_character(&text, &mut buf, self.line); + self.cyclic_write_character(&text, &mut buf, self.line); if self.noise { gaussian_noise_mut( diff --git a/ext/rucaptcha/src/lib.rs b/ext/rucaptcha/src/lib.rs index 67293c4..a771b83 100644 --- a/ext/rucaptcha/src/lib.rs +++ b/ext/rucaptcha/src/lib.rs @@ -7,6 +7,7 @@ pub fn create( difficulty: usize, line: bool, noise: bool, + circle: bool, format: String, ) -> (String, Vec) { let c = captcha::CaptchaBuilder::new(); @@ -15,6 +16,7 @@ pub fn create( .length(len) .line(line) .noise(noise) + .circle(circle) .format(&format) .build(); @@ -24,7 +26,7 @@ pub fn create( #[magnus::init] fn init() -> Result<(), Error> { let class = define_class("RuCaptchaCore", magnus::class::object())?; - class.define_singleton_method("create", function!(create, 5))?; + class.define_singleton_method("create", function!(create, 6))?; Ok(()) } diff --git a/lib/rucaptcha.rb b/lib/rucaptcha.rb index 944e88a..7b75686 100644 --- a/lib/rucaptcha.rb +++ b/lib/rucaptcha.rb @@ -29,7 +29,8 @@ def config @config.expires_in = 2.minutes @config.skip_cache_store_check = false @config.line = true - @config.noise = true + @config.noise = false + @config.circle = true @config.format = "png" @config.mount_path = "/rucaptcha" @config.case_sensitive = false @@ -52,7 +53,7 @@ def generate raise RuCaptcha::Errors::Configuration, "length config error, value must in 3..7" unless length.in?(3..7) - result = RuCaptchaCore.create(length, config.difficulty || 5, config.line, config.noise, config.format) + result = RuCaptchaCore.create(length, config.difficulty || 5, config.line, config.noise, config.circle, config.format) unless config.case_sensitive result[0].downcase! end diff --git a/lib/rucaptcha/configuration.rb b/lib/rucaptcha/configuration.rb index 584b5cd..9fe6b4b 100644 --- a/lib/rucaptcha/configuration.rb +++ b/lib/rucaptcha/configuration.rb @@ -13,6 +13,8 @@ class Configuration attr_accessor :line # Enable or disable noise on captcha image, default: false attr_accessor :noise + # Enable or disable circle background on captcha image, default: true + attr_accessor :circle # Image format allow: ['jpeg', 'png', 'webp'], default: 'png' attr_accessor :format # skip_cache_store_check, default: false