turing 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +138 -0
- data/COPYING +340 -0
- data/COPYRIGHT +19 -0
- data/README +26 -0
- data/Rakefile +75 -0
- data/TODO +1 -0
- data/lib/turing.rb +38 -0
- data/lib/turing/cgi_handler.rb +251 -0
- data/lib/turing/challenge.rb +214 -0
- data/lib/turing/image.rb +245 -0
- data/lib/turing/image_plugins/__squaring_helper.rb +66 -0
- data/lib/turing/image_plugins/black_squaring.rb +23 -0
- data/lib/turing/image_plugins/blending.rb +44 -0
- data/lib/turing/image_plugins/random_noise.rb +32 -0
- data/lib/turing/image_plugins/spiral.rb +56 -0
- data/lib/turing/image_plugins/white_squaring.rb +23 -0
- data/rdoc.jamis.rb +591 -0
- data/shared/README +9 -0
- data/shared/bgs/04.jpeg +0 -0
- data/shared/bgs/06.jpeg +0 -0
- data/shared/bgs/07.jpeg +0 -0
- data/shared/bgs/08.jpeg +0 -0
- data/shared/bgs/09.jpeg +0 -0
- data/shared/bgs/13.jpeg +0 -0
- data/shared/bgs/18.jpeg +0 -0
- data/shared/bgs/19.jpeg +0 -0
- data/shared/bgs/21.jpeg +0 -0
- data/shared/bgs/26.jpeg +0 -0
- data/shared/bgs/28.jpeg +0 -0
- data/shared/bgs/29.jpeg +0 -0
- data/shared/dictionary +411 -0
- data/shared/fonts/cour.ttf +0 -0
- data/shared/fonts/georgiai.ttf +0 -0
- data/shared/templates/challenge.rhtml +40 -0
- data/shared/templates/error.rhtml +32 -0
- data/shared/templates/success.rhtml +26 -0
- metadata +102 -0
data/lib/turing/image.rb
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
#
|
|
3
|
+
# Turing -- Ruby implementation of Captcha
|
|
4
|
+
#
|
|
5
|
+
# Copyright © 2005 Michal Šafránek <wejn@box.cz>
|
|
6
|
+
#
|
|
7
|
+
# This file is part of http://turing.rubyforge.org/
|
|
8
|
+
#
|
|
9
|
+
# See turing.rb in lib/ directory for license terms.
|
|
10
|
+
#
|
|
11
|
+
require 'rubygems'
|
|
12
|
+
require_gem 'gd2'
|
|
13
|
+
require 'turing'
|
|
14
|
+
require 'pathname'
|
|
15
|
+
|
|
16
|
+
# == Simple obfuscated image generator with plugin design
|
|
17
|
+
#
|
|
18
|
+
# Generates obfuscated image containing given word by using
|
|
19
|
+
# one of its registered plugins.
|
|
20
|
+
#
|
|
21
|
+
# For it's operation it requires gd2 gem, available from http://gd2.rubyforge.org/.
|
|
22
|
+
#
|
|
23
|
+
# Example of use:
|
|
24
|
+
# ti = Turing::Image.new(:width => 280, :height => 115)
|
|
25
|
+
# ti.generate(File.join(Dir.getwd, 'a.jpg'), "randomword")
|
|
26
|
+
# In this case we generate image using random plugin containing
|
|
27
|
+
# word "randomword". It is saved as <tt>`pwd`/a.jpg</tt>.
|
|
28
|
+
#
|
|
29
|
+
# === A word about plugins
|
|
30
|
+
# All plugins are "registered" by subclassing Turing::Image (which is
|
|
31
|
+
# implemented using <tt>self.inherited</tt>). It makes sense to subclass
|
|
32
|
+
# Turing::Image because that way you'll also get goodies like #write_string.
|
|
33
|
+
#
|
|
34
|
+
# Plugins are auto-loaded by require from <tt>lib/turing/image_plugins</tt>
|
|
35
|
+
# after Turing::Image is created but you're free to manually load any plugin
|
|
36
|
+
# you like.
|
|
37
|
+
#
|
|
38
|
+
# For inspiration on how to write new plugin visit any of the existing
|
|
39
|
+
# plugins in +image_plugins+ dir, minimal template would be:
|
|
40
|
+
# class MyCoolPlugin < Turing::Image
|
|
41
|
+
# def initialize(opts = {})
|
|
42
|
+
# super(opts)
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# def generate(img, word)
|
|
46
|
+
# write_string(img, 'cour.ttf', GD2::Color[0, 0, 0], word, 48)
|
|
47
|
+
# end
|
|
48
|
+
# end
|
|
49
|
+
class Turing::Image # {{{
|
|
50
|
+
# All content generating plugins
|
|
51
|
+
@@plugins = []
|
|
52
|
+
|
|
53
|
+
# Configure instance using options hash.
|
|
54
|
+
#
|
|
55
|
+
# *Warning*: Keys of this hash must be symbols.
|
|
56
|
+
#
|
|
57
|
+
# Accepted options:
|
|
58
|
+
# * +fontdir+: Directory containing .ttf fonts required by plugins. Default: gem's <tt>shared/fonts</tt> directory.
|
|
59
|
+
# * +bgdir+: Directory containing .jpeg files used as background by plugins. Default: gem's <tt>shared/bgs</tt> directory.
|
|
60
|
+
# * +outdir+: Output directory where to put image in case absolute path wasn't specified.
|
|
61
|
+
# * +width+: Width of the image.
|
|
62
|
+
# * +height+: Height of the image.
|
|
63
|
+
# * +method+: Use specified plugin instead of randomly selected. You must give class that implements +generate+ instance method. Default: nil.
|
|
64
|
+
def initialize(opts = {}) # {{{
|
|
65
|
+
raise ArgumentError, "Opts must be hash!" unless opts.kind_of? Hash
|
|
66
|
+
|
|
67
|
+
base = File.join(File.dirname(__FILE__), '..', '..', 'shared')
|
|
68
|
+
@options = {
|
|
69
|
+
:fontdir => File.join(base, 'fonts'),
|
|
70
|
+
:bgdir => File.join(base, 'bgs'),
|
|
71
|
+
:outdir => ENV["TMPDIR"] || '/tmp',
|
|
72
|
+
:width => 280,
|
|
73
|
+
:height => 115,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@options.merge!(opts)
|
|
77
|
+
end # }}}
|
|
78
|
+
|
|
79
|
+
# Generate image into +outname+ containing +word+ (using +method+).
|
|
80
|
+
#
|
|
81
|
+
# *Warning*: If you pass absolute filename as +outname+, +outdir+ will have no effect.
|
|
82
|
+
#
|
|
83
|
+
# *Warning*: There's no way to reset +method+ to random if it was specified upon instance creation.
|
|
84
|
+
def generate(outname, word, method = nil) # {{{
|
|
85
|
+
# select appropriate output plugin # {{{
|
|
86
|
+
m = method || @options[:method]
|
|
87
|
+
if m.nil?
|
|
88
|
+
if @@plugins.empty?
|
|
89
|
+
raise RuntimeError, "no generators plugins available!"
|
|
90
|
+
end
|
|
91
|
+
m = @@plugins[rand(@@plugins.size)]
|
|
92
|
+
end
|
|
93
|
+
unless m.instance_methods.include?("generate")
|
|
94
|
+
raise ArgumentError, "plugin #{m} doesn't have generate method"
|
|
95
|
+
end
|
|
96
|
+
# }}}
|
|
97
|
+
|
|
98
|
+
# prepend outname with outdir, if no absolute path given
|
|
99
|
+
unless Pathname.new(outname).absolute?
|
|
100
|
+
outname = File.join(@options[:outdir], outname)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
img = GD2::Image.new(@options[:width], @options[:height])
|
|
104
|
+
|
|
105
|
+
img.draw do |canvas|
|
|
106
|
+
canvas.color = GD2::Color[255, 255, 255]
|
|
107
|
+
canvas.rectangle(0, 0, img.width - 1, img.height - 1, true)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
m.new(@options).generate(img, word)
|
|
111
|
+
|
|
112
|
+
img.draw do |canvas|
|
|
113
|
+
canvas.color = GD2::Color[0, 0, 0]
|
|
114
|
+
canvas.rectangle(0, 0, img.width - 1, img.height - 1)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
begin
|
|
118
|
+
File.open(outname, 'w') { |f| f.write(img.jpeg(90)) }
|
|
119
|
+
rescue
|
|
120
|
+
raise "Unable to write challenge: #{$!}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
true
|
|
124
|
+
end # }}}
|
|
125
|
+
|
|
126
|
+
# Private methods
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
# Write +string+ to +img+ using color +fg+ and +font+
|
|
130
|
+
# (with size +req_size+, if possible) at random coordinates
|
|
131
|
+
# and using random angle.
|
|
132
|
+
#
|
|
133
|
+
# Method checks bounding box so the string is guaranteed to stay
|
|
134
|
+
# within image's dimensions.
|
|
135
|
+
#
|
|
136
|
+
# May raise RuntimeError if it's completely impossible to find suitable
|
|
137
|
+
# fontsize for given dimensions.
|
|
138
|
+
def write_string(img, font, fg, string, req_size = nil) # {{{ # :doc:
|
|
139
|
+
# prepend fontname with fontdir, unless absolute path given
|
|
140
|
+
unless Pathname.new(font).absolute?
|
|
141
|
+
font = File.join(@options[:fontdir], font)
|
|
142
|
+
end
|
|
143
|
+
sizes = (16..42).to_a.reverse
|
|
144
|
+
turbulence = 5 # x%
|
|
145
|
+
mult = 0.85 * ((rand(turbulence*2 + 1) - turbulence) / 100.0 + 1.0)
|
|
146
|
+
|
|
147
|
+
# select angle
|
|
148
|
+
angle = -5 + rand(11)
|
|
149
|
+
|
|
150
|
+
# font size determination ...
|
|
151
|
+
chosen = nil # {{{
|
|
152
|
+
sizes.unshift(req_size) unless req_size.nil?
|
|
153
|
+
sizes.each do |size|
|
|
154
|
+
bounds = nil
|
|
155
|
+
begin
|
|
156
|
+
bounds = GD2::Font::TrueType.new(font, size).
|
|
157
|
+
bounding_rectangle(string, angle.degrees)
|
|
158
|
+
rescue
|
|
159
|
+
raise "Unable to detect bounding box: #{$!}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
minx, maxx = bounds.values.map { |x| x[0] }.sort.values_at(0, -1)
|
|
163
|
+
miny, maxy = bounds.values.map { |x| x[1] }.sort.values_at(0, -1)
|
|
164
|
+
|
|
165
|
+
bb_width = maxx - minx
|
|
166
|
+
bb_height = maxy - miny
|
|
167
|
+
|
|
168
|
+
if img.width * mult > bb_width && img.height * mult > bb_height
|
|
169
|
+
chosen = {
|
|
170
|
+
:size => size,
|
|
171
|
+
:width => bb_width,
|
|
172
|
+
:height => bb_height,
|
|
173
|
+
}
|
|
174
|
+
break
|
|
175
|
+
end
|
|
176
|
+
end # }}}
|
|
177
|
+
|
|
178
|
+
raise "Unable to select size" if chosen.nil?
|
|
179
|
+
|
|
180
|
+
x_base, y_base = 1, img.height / 2
|
|
181
|
+
|
|
182
|
+
x_offset = rand(((img.width - chosen[:width])*mult).to_i)
|
|
183
|
+
y_offset = rand((((img.height - chosen[:height]) / 2)*mult).to_i)
|
|
184
|
+
|
|
185
|
+
x = x_base + x_offset
|
|
186
|
+
y = y_base + y_offset
|
|
187
|
+
|
|
188
|
+
img.draw do |canvas|
|
|
189
|
+
canvas.move_to(x, y)
|
|
190
|
+
canvas.color = fg
|
|
191
|
+
canvas.font = GD2::Font::TrueType.new(font, chosen[:size])
|
|
192
|
+
canvas.text(string, angle.degrees)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
img
|
|
196
|
+
end # }}}
|
|
197
|
+
|
|
198
|
+
# Record all plugins available
|
|
199
|
+
# (subclasses which implement class method generate)
|
|
200
|
+
def self.inherited(other) # {{{
|
|
201
|
+
(@@plugins ||= []) << other
|
|
202
|
+
end # }}}
|
|
203
|
+
end # }}}
|
|
204
|
+
|
|
205
|
+
# Load all image plugins ...
|
|
206
|
+
# {{{
|
|
207
|
+
plugin_dir = File.join(File.dirname(__FILE__), 'image_plugins')
|
|
208
|
+
if FileTest.directory?(plugin_dir)
|
|
209
|
+
Dir[File.join(plugin_dir, '*')].each do |f|
|
|
210
|
+
bn = File.basename(f)
|
|
211
|
+
begin
|
|
212
|
+
require 'turing/image_plugins/' + bn
|
|
213
|
+
rescue Object
|
|
214
|
+
raise RuntimeError, "Exception while loading plugin: #{bn}: #{$!}"
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
# }}}
|
|
219
|
+
|
|
220
|
+
# Test it out, when executed directly
|
|
221
|
+
if __FILE__ == $0 # {{{
|
|
222
|
+
ti = Turing::Image.new(:width => 280, :height => 115)
|
|
223
|
+
|
|
224
|
+
# debug loop
|
|
225
|
+
begin # {{{
|
|
226
|
+
loop do
|
|
227
|
+
dict_fn = File.join(File.dirname(__FILE__), '..', '..', 'shared',
|
|
228
|
+
'dictionary')
|
|
229
|
+
dict = File.open(dict_fn).readlines.map! { |x| x.chomp }
|
|
230
|
+
ti.generate(File.join(Dir.getwd, 'a.jpg'),
|
|
231
|
+
dict[rand(dict.size)])
|
|
232
|
+
pid = fork do
|
|
233
|
+
exec("xv", "a.jpg")
|
|
234
|
+
end
|
|
235
|
+
Process.detach(pid)
|
|
236
|
+
sleep 2
|
|
237
|
+
Process.kill(9, pid)
|
|
238
|
+
end
|
|
239
|
+
rescue Interrupt
|
|
240
|
+
ensure
|
|
241
|
+
File.unlink('a.jpg') rescue nil
|
|
242
|
+
end # }}}
|
|
243
|
+
end # }}}
|
|
244
|
+
|
|
245
|
+
# vim: set ts=4 sw=4 :
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Turing -- Ruby implementation of Captcha
|
|
3
|
+
#
|
|
4
|
+
# Copyright © 2005 Michal Šafránek <wejn@box.cz>
|
|
5
|
+
#
|
|
6
|
+
# This file is part of http://turing.rubyforge.org/
|
|
7
|
+
#
|
|
8
|
+
# See turing.rb in lib/ directory for license terms.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
# == Squaring helper
|
|
12
|
+
# Skeleton for Turing::Image::*Squaring (mixin, actually).
|
|
13
|
+
module Turing::Image::SquaringHelper # {{{
|
|
14
|
+
# contract method - generate the challenge
|
|
15
|
+
def generate(img, word, bg = nil) # {{{
|
|
16
|
+
if bg.nil?
|
|
17
|
+
possible = Dir[File.join(@options[:bgdir], '*')]
|
|
18
|
+
bg = possible[rand(possible.size)]
|
|
19
|
+
else
|
|
20
|
+
unless FileTest.exists?(bg)
|
|
21
|
+
raise ArgumentError, "Wrong background!"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
img_tmp = GD2::Image.load(File.open(bg, 'r'))
|
|
26
|
+
|
|
27
|
+
if img_tmp.width < img.width || img_tmp.height < img.height
|
|
28
|
+
raise "Background has insufficient dimensions"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
img.merge_from(img_tmp, 0, 0, 0, 0, img.width, img.height, 30.percent)
|
|
32
|
+
|
|
33
|
+
# XXX: is this equivalent to "img_tmp.destroy" ?
|
|
34
|
+
img_tmp = nil
|
|
35
|
+
|
|
36
|
+
fg = GD2::Color[0, 0, 0]
|
|
37
|
+
|
|
38
|
+
write_string(img, 'cour.ttf', fg, word, 35)
|
|
39
|
+
|
|
40
|
+
raise RuntimeError, "no squaring color selected!" if @squaring_color.nil?
|
|
41
|
+
fg = @squaring_color
|
|
42
|
+
|
|
43
|
+
img.draw do |canvas|
|
|
44
|
+
canvas.color = fg
|
|
45
|
+
if rand(2).zero?
|
|
46
|
+
delta = word.size > 8 ? 6 : 4
|
|
47
|
+
0.step(img.width, delta) { |x| canvas.line(x, 0, x, img.height) }
|
|
48
|
+
0.step(img.height, delta) { |y| canvas.line(0, y, img.width, y) }
|
|
49
|
+
else
|
|
50
|
+
i = 0
|
|
51
|
+
while i <= img.width
|
|
52
|
+
canvas.line(i, 0, i, img.height)
|
|
53
|
+
i += 3 + rand(4)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
i = 0
|
|
57
|
+
while i <= img.height
|
|
58
|
+
canvas.line(0, i, img.width, i)
|
|
59
|
+
i += 3 + rand(4)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end # }}}
|
|
64
|
+
end # }}}
|
|
65
|
+
|
|
66
|
+
# vim: set ts=4 sw=4 :
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Turing -- Ruby implementation of Captcha
|
|
3
|
+
#
|
|
4
|
+
# Copyright © 2005 Michal Šafránek <wejn@box.cz>
|
|
5
|
+
#
|
|
6
|
+
# This file is part of http://turing.rubyforge.org/
|
|
7
|
+
#
|
|
8
|
+
# See turing.rb in lib/ directory for license terms.
|
|
9
|
+
#
|
|
10
|
+
require 'turing/image_plugins/__squaring_helper'
|
|
11
|
+
|
|
12
|
+
# == BlackSquaring Turing test
|
|
13
|
+
# Blends text, background and makes square pattern over image.
|
|
14
|
+
class Turing::Image::BlackSquaring < Turing::Image # {{{
|
|
15
|
+
def initialize(opts = {})
|
|
16
|
+
super(opts)
|
|
17
|
+
@squaring_color = GD2::Color[0, 0, 0]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
include Turing::Image::SquaringHelper
|
|
21
|
+
end # }}}
|
|
22
|
+
|
|
23
|
+
# vim: set ts=4 sw=4 :
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Turing -- Ruby implementation of Captcha
|
|
3
|
+
#
|
|
4
|
+
# Copyright © 2005 Michal Šafránek <wejn@box.cz>
|
|
5
|
+
#
|
|
6
|
+
# This file is part of http://turing.rubyforge.org/
|
|
7
|
+
#
|
|
8
|
+
# See turing.rb in lib/ directory for license terms.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
# == Blending Turing test
|
|
12
|
+
# Blends text with background (highly imperfect for some backgrounds).
|
|
13
|
+
class Turing::Image::Blending < Turing::Image # {{{
|
|
14
|
+
# contract method - generate the challenge
|
|
15
|
+
def generate(img, word, bg = nil) # {{{
|
|
16
|
+
if bg.nil?
|
|
17
|
+
possible = Dir[File.join(@options[:bgdir], '*')]
|
|
18
|
+
bg = possible[rand(possible.size)]
|
|
19
|
+
else
|
|
20
|
+
unless FileTest.exists?(bg)
|
|
21
|
+
raise ArgumentError, "Wrong background!"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
img_tmp = GD2::Image.load(File.open(bg, 'r'))
|
|
26
|
+
|
|
27
|
+
if img_tmp.width < img.width || img_tmp.height < img.height
|
|
28
|
+
raise "Background has insufficient dimensions"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
img.copy_from(img_tmp, 0, 0, 0, 0, img.width, img.height)
|
|
32
|
+
|
|
33
|
+
# XXX: equivalent of img_tmp.destroy ?
|
|
34
|
+
img_tmp = nil
|
|
35
|
+
|
|
36
|
+
r = rand(32)
|
|
37
|
+
fg = GD2::Color[r, r, r, 40.percent]
|
|
38
|
+
|
|
39
|
+
write_string(img, 'georgiai.ttf', fg, word, 40)
|
|
40
|
+
|
|
41
|
+
end # }}}
|
|
42
|
+
end # }}}
|
|
43
|
+
|
|
44
|
+
# vim: set ts=4 sw=4 :
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Turing -- Ruby implementation of Captcha
|
|
3
|
+
#
|
|
4
|
+
# Copyright © 2005 Michal Šafránek <wejn@box.cz>
|
|
5
|
+
#
|
|
6
|
+
# This file is part of http://turing.rubyforge.org/
|
|
7
|
+
#
|
|
8
|
+
# See turing.rb in lib/ directory for license terms.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
# == Random Noise turing test
|
|
12
|
+
# Generates random black noise, then text, then random white noise.
|
|
13
|
+
class Turing::Image::RandomNoise < Turing::Image # {{{
|
|
14
|
+
# contract method - generate the challenge
|
|
15
|
+
def generate(img, word, bg = nil) # {{{
|
|
16
|
+
fg = GD2::Color[255, 255, 255]
|
|
17
|
+
bg = GD2::Color[0, 0, 0]
|
|
18
|
+
|
|
19
|
+
0.upto(img.width * img.height * 0.4) do |i|
|
|
20
|
+
img[rand(img.width), rand(img.height)] = bg
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
write_string(img, 'georgiai.ttf', bg, word, 48)
|
|
24
|
+
|
|
25
|
+
quant = word.size > 7 ? 0.1 : 0.2
|
|
26
|
+
0.upto(img.width * img.height * quant) do |i|
|
|
27
|
+
img[rand(img.width), rand(img.height)] = fg
|
|
28
|
+
end
|
|
29
|
+
end # }}}
|
|
30
|
+
end # }}}
|
|
31
|
+
|
|
32
|
+
# vim: set ts=4 sw=4 :
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Turing -- Ruby implementation of Captcha
|
|
3
|
+
#
|
|
4
|
+
# Copyright © 2005 Michal Šafránek <wejn@box.cz>
|
|
5
|
+
#
|
|
6
|
+
# This file is part of http://turing.rubyforge.org/
|
|
7
|
+
# and is based on PHP captcha implementation "Session captcha"
|
|
8
|
+
# which can be found at http://sourceforge.net/projects/session-captcha/
|
|
9
|
+
#
|
|
10
|
+
# See turing.rb in lib/ directory for license terms.
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
# == Spiral Turing test
|
|
14
|
+
# Renders spirals plus text over them.
|
|
15
|
+
class Turing::Image::Spiral < Turing::Image # {{{
|
|
16
|
+
# contract method - generate the challenge
|
|
17
|
+
def generate(img, word) # {{{
|
|
18
|
+
fg = GD2::Color[185, 140, 140]
|
|
19
|
+
sfg = GD2::Color[225, 190, 190]
|
|
20
|
+
|
|
21
|
+
spiral(img, (img.width/3) + rand(img.width/3), rand(img.height), sfg)
|
|
22
|
+
|
|
23
|
+
write_string(img, 'cour.ttf', fg, word)
|
|
24
|
+
|
|
25
|
+
spiral(img, rand(img.width / 3), rand(img.height), sfg)
|
|
26
|
+
spiral(img, 2*(img.width/3) + rand(img.width/3), rand(img.height), sfg)
|
|
27
|
+
end # }}}
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# renders spiral with center at [originx, originy] using color
|
|
32
|
+
def spiral(img, originx, originy, color) # {{{
|
|
33
|
+
theta = 1.0
|
|
34
|
+
thetac = 6.0
|
|
35
|
+
radius = 15.0
|
|
36
|
+
circles = 10.0
|
|
37
|
+
points = 35.0
|
|
38
|
+
img.draw do |canvas|
|
|
39
|
+
canvas.color = color
|
|
40
|
+
0.upto(circles * points - 1) do |i|
|
|
41
|
+
theta += thetac
|
|
42
|
+
rad = radius * (i.to_f / points)
|
|
43
|
+
x = rad * Math.cos(theta) + originx
|
|
44
|
+
y = rad * Math.sin(theta) + originy
|
|
45
|
+
theta += thetac
|
|
46
|
+
rad1 = radius * ((i.to_f + 1.to_f) / points)
|
|
47
|
+
x1 = rad1 * Math.cos(theta) + originx
|
|
48
|
+
y1 = rad1 * Math.sin(theta) + originy
|
|
49
|
+
canvas.line(x.round, y.round, x1.round, y1.round)
|
|
50
|
+
theta -= thetac
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end # }}}
|
|
54
|
+
end # }}}
|
|
55
|
+
|
|
56
|
+
# vim: set ts=4 sw=4 :
|