rucaptcha 3.1.4 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9abb0e5b8aa7d18c9d35262ee5d6c07e625fa70e215980a2f2e6622e10d6b66b
4
- data.tar.gz: b599d194a27a5434ef79b53ee5adf10893a96c40f1a795b47313a51c0bc7e322
3
+ metadata.gz: 17ac95708dbda9b6245714405a31f1f9958692c3bc6d2b8d0956e3716a755a40
4
+ data.tar.gz: b3bfcf8d2be6b81030e6f226ba855c44c20284393453a861d6bd60e832d4b60c
5
5
  SHA512:
6
- metadata.gz: f0c316a599ffbf7dbcfcba6a3fc89928633d4ba8f3f5e737bd137d2317518c23276e755dc63a7ea600c95ddef9c45974157cda7e90196f30a763c5b3aebb52b8
7
- data.tar.gz: b86d81032b44ccef793b82c6ca37bd20c60dec3948034e70a31a16ace0f38480285d2df8f8b16836a40dd8e584ccac019bb1a1aafd3ca2ca0e19a8f3ec784548
6
+ metadata.gz: 644f9a58788af95d68767af7003fad4a204f2ec452fc9bcd5502b9e07c2995467e958d6aa0dd9636b29fe648fe53e6d1f6e2d7bbbc6bd4e718119bac252a8011
7
+ data.tar.gz: df7ae75db0eae30213b785bbef10758e9fe1ff1f9821f9eda6104a11362053d1bd1f5631992d760cc3ae57b60643fe41b86b07cb52097855954722be9f579b13
data/README.md CHANGED
@@ -37,6 +37,7 @@ Create `config/initializers/rucaptcha.rb`
37
37
  RuCaptcha.configure do
38
38
  # Custom captcha code expire time if you need, default: 2 minutes
39
39
  # self.expires_in = 120
40
+
40
41
   # [Requirement / 重要]
41
42
  # Store Captcha code where, this config more like Rails config.cache_store
42
43
  # default: Read config info from `Rails.application.config.cache_store`
@@ -44,10 +45,22 @@ RuCaptcha.configure do
44
45
   # 默认:会从 Rails 配置的 cache_store 里面读取相同的配置信息,并尝试用可以运行的方式,用于存储验证码字符
45
46
   # 但如果是 [:null_store, :memory_store, :file_store] 之类的,你可以通过下面的配置项单独给 RuCaptcha 配置 cache_store
46
47
   self.cache_store = :mem_cache_store
48
+
49
+ # If you wants disable `cache_store` check warning, you can do it, default: false
47
50
  # 如果想要 disable cache_store 的 warning,就设置为 true,default false
48
51
  # self.skip_cache_store_check = true
52
+
49
53
  # Chars length, default: 5, allows: [3 - 7]
50
54
  # self.length = 5
55
+
56
+ # Enable or disable Strikethrough, default: true
57
+ # self.line = true
58
+
59
+ # Enable or disable noise, default: false
60
+ # self.noise = false
61
+
62
+ # Set the image format, default: png, allows: [jpeg, png, webp]
63
+ # self.format = 'png'
51
64
  end
52
65
  ```
53
66
 
data/Rakefile CHANGED
@@ -27,9 +27,14 @@ end
27
27
  RSpec::Core::RakeTask.new(:spec)
28
28
  task default: :spec
29
29
 
30
+ def create_captcha(length = 5, difficulty = 5)
31
+ require "rucaptcha"
32
+ RuCaptchaCore.create(length, difficulty, false, false, "png")
33
+ end
34
+
30
35
  task :preview do
31
36
  require "rucaptcha"
32
- res = RuCaptchaCore.create(5, 5)
37
+ res = create_captcha()
33
38
  warn "-------------------------\n#{res[0]}"
34
39
  puts res[1].pack("c*")
35
40
  end
@@ -38,11 +43,11 @@ task :memory do
38
43
  require "rucaptcha"
39
44
  require "memory_profiler"
40
45
 
41
- RuCaptchaCore.create(5, 5)
46
+ create_captcha()
42
47
 
43
48
  report = MemoryProfiler.report do
44
49
  1000.times do
45
- RuCaptchaCore.create(5, 5)
50
+ create_captcha()
46
51
  end
47
52
  end
48
53
 
@@ -57,10 +62,10 @@ task :benchmark do
57
62
  require "rucaptcha"
58
63
  require "benchmark/ips"
59
64
 
60
- RuCaptchaCore.create(5, 5)
65
+ RuCaptchaCore.create(5, 5, true, true, "png")
61
66
 
62
67
  Benchmark.ips do |x|
63
- x.report("Generate image") { RuCaptchaCore.create(5, 5) }
68
+ x.report("Generate image") { RuCaptchaCore.create(5, 5, true, true, "png") }
64
69
  x.compare!
65
70
  end
66
71
  end
@@ -1,6 +1,4 @@
1
- use image::{ImageBuffer, Rgb};
2
- use imageproc::drawing::{draw_cubic_bezier_curve_mut, draw_text_mut};
3
- use imageproc::noise::gaussian_noise_mut;
1
+ use image::{ImageBuffer, Rgba};
4
2
  use rand::{thread_rng, Rng};
5
3
  use rusttype::{Font, Scale};
6
4
  use std::io::Cursor;
@@ -15,21 +13,21 @@ static FONT_BYTES1: &[u8; 145008] = include_bytes!("../fonts/FuzzyBubbles-Regula
15
13
  static FONT_BYTES2: &[u8; 37792] = include_bytes!("../fonts/Handlee-Regular.ttf");
16
14
 
17
15
  // https://coolors.co/cc0b8f-7c0abe-5700c8-3c2ea4-3d56a8-3fa67e-45bb30-69d003-a0d003-d8db02
18
- static COLORS: [(u8, u8, u8); 14] = [
19
- (197, 166, 3),
20
- (187, 87, 5),
21
- (176, 7, 7),
22
- (186, 9, 56),
23
- (204, 11, 143),
24
- (124, 10, 190),
25
- (87, 0, 200),
26
- (61, 86, 168),
27
- (63, 166, 126),
28
- (69, 187, 48),
29
- (105, 208, 3),
30
- (160, 208, 3),
31
- (216, 219, 2),
32
- (50, 50, 50),
16
+ static COLORS: [(u8, u8, u8, u8); 14] = [
17
+ (197, 166, 3, 255),
18
+ (187, 87, 5, 255),
19
+ (176, 7, 7, 255),
20
+ (186, 9, 56, 255),
21
+ (204, 11, 143, 255),
22
+ (124, 10, 190, 255),
23
+ (87, 0, 200, 255),
24
+ (61, 86, 168, 255),
25
+ (63, 166, 126, 255),
26
+ (69, 187, 48, 255),
27
+ (105, 208, 3, 255),
28
+ (160, 208, 3, 255),
29
+ (216, 219, 2, 255),
30
+ (50, 50, 50, 255),
33
31
  ];
34
32
 
35
33
  static SCALE_SM: u32 = 32;
@@ -51,18 +49,18 @@ fn get_captcha(len: usize) -> Vec<String> {
51
49
  }
52
50
 
53
51
  #[allow(unused)]
54
- fn get_color() -> Rgb<u8> {
52
+ fn get_color() -> Rgba<u8> {
55
53
  let rnd = rand_num(COLORS.len() - 1);
56
54
  let c = COLORS[rnd];
57
- Rgb([c.0, c.1, c.2])
55
+ Rgba([c.0, c.1, c.2, c.3])
58
56
  }
59
57
 
60
- fn get_colors(num: usize) -> Vec<Rgb<u8>> {
58
+ fn get_colors(num: usize) -> Vec<Rgba<u8>> {
61
59
  let rnd = rand_num(COLORS.len());
62
60
  let mut out = vec![];
63
61
  for i in 0..num {
64
62
  let c = COLORS[(rnd + i) % COLORS.len()];
65
- out.push(Rgb([c.0, c.1, c.2]))
63
+ out.push(Rgba([c.0, c.1, c.2, c.3]))
66
64
  }
67
65
 
68
66
  out
@@ -80,13 +78,13 @@ fn get_font() -> Font<'static> {
80
78
  }
81
79
  }
82
80
 
83
- fn get_image(width: usize, height: usize) -> ImageBuffer<Rgb<u8>, Vec<u8>> {
81
+ fn get_image(width: usize, height: usize) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
84
82
  ImageBuffer::from_fn(width as u32, height as u32, |_, _| {
85
- image::Rgb([255, 255, 255])
83
+ image::Rgba([255, 255, 255, 255])
86
84
  })
87
85
  }
88
86
 
89
- fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>) {
87
+ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, lines: bool) {
90
88
  let c = (image.width() - 20) / res.len() as u32;
91
89
  let y = image.height() / 3 - 15;
92
90
 
@@ -107,7 +105,10 @@ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgb<u8>, Vec<u
107
105
  // Draw line, ellipse first as background
108
106
  for (i, _) in res.iter().enumerate() {
109
107
  let line_color = line_colors[i];
110
- draw_interference_line(1, image, line_color);
108
+
109
+ if lines {
110
+ draw_interference_line(1, image, line_color);
111
+ }
111
112
  draw_interference_ellipse(1, image, line_color);
112
113
  }
113
114
 
@@ -121,7 +122,7 @@ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgb<u8>, Vec<u
121
122
  for j in 0..(rand_num(3) + 1) as i32 {
122
123
  // Draw text again with offset
123
124
  let offset = j * (rand_num(2) as i32);
124
- draw_text_mut(
125
+ imageproc::drawing::draw_text_mut(
125
126
  image,
126
127
  color,
127
128
  10 + offset + (i as u32 * c) as i32,
@@ -137,7 +138,7 @@ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgb<u8>, Vec<u
137
138
  }
138
139
  }
139
140
 
140
- fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>, color: Rgb<u8>) {
141
+ fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, color: Rgba<u8>) {
141
142
  for _ in 0..num {
142
143
  let width = image.width();
143
144
  let height = image.height();
@@ -153,7 +154,7 @@ fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>,
153
154
  let ctrl_x2 = get_next((width / 12) as f32, width / 12 * 3);
154
155
  let ctrl_y2 = get_next(x1, height - 5);
155
156
  // Randomly draw bezier curves
156
- draw_cubic_bezier_curve_mut(
157
+ imageproc::drawing::draw_cubic_bezier_curve_mut(
157
158
  image,
158
159
  (x1, y1),
159
160
  (x2, y2),
@@ -166,8 +167,8 @@ fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>,
166
167
 
167
168
  fn draw_interference_ellipse(
168
169
  num: usize,
169
- image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>,
170
- color: Rgb<u8>,
170
+ image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
171
+ color: Rgba<u8>,
171
172
  ) {
172
173
  for _ in 0..num {
173
174
  // max cycle width 20px
@@ -189,6 +190,9 @@ pub struct CaptchaBuilder {
189
190
  width: usize,
190
191
  height: usize,
191
192
  complexity: usize,
193
+ line: bool,
194
+ noise: bool,
195
+ format: image::ImageFormat,
192
196
  }
193
197
 
194
198
  impl CaptchaBuilder {
@@ -198,6 +202,9 @@ impl CaptchaBuilder {
198
202
  width: 220,
199
203
  height: 70,
200
204
  complexity: 5,
205
+ line: true,
206
+ noise: false,
207
+ format: image::ImageFormat::Png,
201
208
  }
202
209
  }
203
210
 
@@ -206,15 +213,26 @@ impl CaptchaBuilder {
206
213
  self
207
214
  }
208
215
 
209
- // pub fn width(mut self, width: usize) -> Self {
210
- // self.width = width;
211
- // self
212
- // }
216
+ pub fn line(mut self, line: bool) -> Self {
217
+ self.line = line;
218
+ self
219
+ }
220
+
221
+ pub fn noise(mut self, noise: bool) -> Self {
222
+ self.noise = noise;
223
+ self
224
+ }
225
+
226
+ pub fn format(mut self, format: &str) -> Self {
227
+ self.format = match format {
228
+ "png" => image::ImageFormat::Png,
229
+ "jpg" | "jpeg" => image::ImageFormat::Jpeg,
230
+ "webp" => image::ImageFormat::WebP,
231
+ _ => image::ImageFormat::Png,
232
+ };
213
233
 
214
- // pub fn height(mut self, height: usize) -> Self {
215
- // self.height = height;
216
- // self
217
- // }
234
+ self
235
+ }
218
236
 
219
237
  pub fn complexity(mut self, complexity: usize) -> Self {
220
238
  let mut complexity = complexity;
@@ -238,20 +256,86 @@ impl CaptchaBuilder {
238
256
  let mut image = get_image(self.width, self.height);
239
257
 
240
258
  // Loop to write the verification code string into the background image
241
- cyclic_write_character(&res, &mut image);
242
-
243
- gaussian_noise_mut(
244
- &mut image,
245
- (self.complexity - 1) as f64,
246
- ((10 * self.complexity) - 10) as f64,
247
- ((5 * self.complexity) - 5) as u64,
248
- );
259
+ cyclic_write_character(&res, &mut image, self.line);
260
+
261
+ if self.noise {
262
+ imageproc::noise::gaussian_noise_mut(
263
+ &mut image,
264
+ (self.complexity - 1) as f64,
265
+ ((10 * self.complexity) - 10) as f64,
266
+ ((5 * self.complexity) - 5) as u64,
267
+ );
268
+ }
249
269
 
250
270
  let mut bytes: Vec<u8> = Vec::new();
251
271
  image
252
- .write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Jpeg)
272
+ .write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)
253
273
  .unwrap();
254
274
 
255
275
  Captcha { text, image: bytes }
256
276
  }
257
277
  }
278
+
279
+ #[cfg(test)]
280
+ mod tests {
281
+ use super::*;
282
+
283
+ #[test]
284
+ fn test_format() {
285
+ let mut builder = CaptchaBuilder::new();
286
+ assert_eq!(builder.format, image::ImageFormat::Png);
287
+
288
+ builder = builder.format("jpg");
289
+ assert_eq!(builder.format, image::ImageFormat::Jpeg);
290
+ builder = builder.format("jpeg");
291
+ assert_eq!(builder.format, image::ImageFormat::Jpeg);
292
+ builder = builder.format("webp");
293
+ assert_eq!(builder.format, image::ImageFormat::WebP);
294
+ builder = builder.format("png");
295
+ assert_eq!(builder.format, image::ImageFormat::Png);
296
+ builder = builder.format("gif");
297
+ assert_eq!(builder.format, image::ImageFormat::Png);
298
+ }
299
+
300
+ #[test]
301
+ fn test_line() {
302
+ let mut builder = CaptchaBuilder::new();
303
+ assert!(builder.line);
304
+
305
+ builder = builder.line(false);
306
+ assert!(!builder.line);
307
+ }
308
+
309
+ #[test]
310
+ fn test_noise() {
311
+ let mut builder = CaptchaBuilder::new();
312
+ assert!(!builder.noise);
313
+
314
+ builder = builder.noise(true);
315
+ assert!(builder.noise);
316
+ }
317
+
318
+ #[test]
319
+ fn test_difficulty() {
320
+ let mut builder = CaptchaBuilder::new();
321
+ assert_eq!(builder.complexity, 5);
322
+
323
+ builder = builder.complexity(10);
324
+ assert_eq!(builder.complexity, 10);
325
+
326
+ builder = builder.complexity(11);
327
+ assert_eq!(builder.complexity, 10);
328
+
329
+ builder = builder.complexity(0);
330
+ assert_eq!(builder.complexity, 1);
331
+ }
332
+
333
+ #[test]
334
+ fn test_length() {
335
+ let mut builder = CaptchaBuilder::new();
336
+ assert_eq!(builder.length, 4);
337
+
338
+ builder = builder.length(10);
339
+ assert_eq!(builder.length, 10);
340
+ }
341
+ }
@@ -2,9 +2,21 @@ use magnus::{define_class, function, Error, Object};
2
2
 
3
3
  mod captcha;
4
4
 
5
- pub fn create(len: usize, difficulty: usize) -> (String, Vec<u8>) {
5
+ pub fn create(
6
+ len: usize,
7
+ difficulty: usize,
8
+ line: bool,
9
+ noise: bool,
10
+ format: String,
11
+ ) -> (String, Vec<u8>) {
6
12
  let c = captcha::CaptchaBuilder::new();
7
- let out = c.complexity(difficulty).length(len).build();
13
+ let out = c
14
+ .complexity(difficulty)
15
+ .length(len)
16
+ .line(line)
17
+ .noise(noise)
18
+ .format(&format)
19
+ .build();
8
20
 
9
21
  (out.text, out.image)
10
22
  }
@@ -12,7 +24,7 @@ pub fn create(len: usize, difficulty: usize) -> (String, Vec<u8>) {
12
24
  #[magnus::init]
13
25
  fn init() -> Result<(), Error> {
14
26
  let class = define_class("RuCaptchaCore", Default::default())?;
15
- class.define_singleton_method("create", function!(create, 2))?;
27
+ class.define_singleton_method("create", function!(create, 5))?;
16
28
 
17
29
  Ok(())
18
30
  }
@@ -9,6 +9,12 @@ module RuCaptcha
9
9
  attr_accessor :length
10
10
  # Hard mode, default: 5, allows: [1..10]
11
11
  attr_accessor :difficulty
12
+ # Enable or disable strikethrough lines on captcha image, default: true
13
+ attr_accessor :line
14
+ # Enable or disable noise on captcha image, default: false
15
+ attr_accessor :noise
16
+ # Image format allow: ['jpeg', 'png', 'webp'], default: 'png'
17
+ attr_accessor :format
12
18
  # skip_cache_store_check, default: false
13
19
  attr_accessor :skip_cache_store_check
14
20
  end
@@ -1,3 +1,3 @@
1
1
  module RuCaptcha
2
- VERSION = "3.1.4"
2
+ VERSION = "3.2.0"
3
3
  end
data/lib/rucaptcha.rb CHANGED
@@ -28,6 +28,9 @@ module RuCaptcha
28
28
  @config.difficulty = 3
29
29
  @config.expires_in = 2.minutes
30
30
  @config.skip_cache_store_check = false
31
+ @config.line = true
32
+ @config.noise = true
33
+ @config.format = "png"
31
34
 
32
35
  @config.cache_store = if Rails.application
33
36
  Rails.application.config.cache_store
@@ -47,7 +50,7 @@ module RuCaptcha
47
50
 
48
51
  raise RuCaptcha::Errors::Configuration, "length config error, value must in 3..7" unless length.in?(3..7)
49
52
 
50
- result = RuCaptchaCore.create(length, config.difficulty || 5)
53
+ result = RuCaptchaCore.create(length, config.difficulty || 5, config.line, config.noise, config.format)
51
54
  [result[0].downcase, result[1].pack("c*")]
52
55
  end
53
56
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rucaptcha
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.4
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Lee
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-11 00:00:00.000000000 Z
11
+ date: 2023-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -38,7 +38,7 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.9.18
41
- description:
41
+ description:
42
42
  email: huacnlee@gmail.com
43
43
  executables: []
44
44
  extensions:
@@ -73,7 +73,7 @@ homepage: https://github.com/huacnlee/rucaptcha
73
73
  licenses:
74
74
  - MIT
75
75
  metadata: {}
76
- post_install_message:
76
+ post_install_message:
77
77
  rdoc_options: []
78
78
  require_paths:
79
79
  - lib
@@ -88,8 +88,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  requirements: []
91
- rubygems_version: 3.4.1
92
- signing_key:
91
+ rubygems_version: 3.4.13
92
+ signing_key:
93
93
  specification_version: 4
94
94
  summary: Captcha Gem for Rails, which generates captcha image by Rust.
95
95
  test_files: []