How to improve the image quality and generate random string image in the plugin validates_captcha

October 17, 2006 at 1:50 pm 22 comments

Hello Everyone !!
I have released a captcha plugin Simple Captcha. It is really simple to implement, and provides a cool feature of multiple styles of images.


Previous post about improving validates_captcha’s images


Validates captcha is a good plugin to implement captcha in your rails application.
However i found that there is repetition of the string of the image and the quality of image is not that good. To get a good quality image and random string
something like this

replace the code of the file /vendor/plugins/validates_captcha/lib/captcha_challenge.rb with the following code…


require 'digest/sha1'

module AngryMidgetPluginsInc #:nodoc:

	# CaptchaChallenge
	class CaptchaChallenge

		include CaptchaConfig
		extend CaptchaConfig

		DEFAULT_TTL = 1200#Lifetime in seconds. Default is 20 minutes.

		attr_reader :id, :created_at
		attr_accessor :ttl

		def initialize(options = {}) #:nodoc:
			generate_id

			options = {
				:ttl => config['default_ttl'] || DEFAULT_TTL
			}.update(options)

			self.ttl = options[:ttl]
			@created_at = Time.now

			self.class.prune
		end

		# Implement in subclasses.
		def correct? #:nodoc:
			raise NotImplementedError
		end

	private

		def generate_id #:nodoc:
			self.id = Digest::SHA1.hexdigest(Time.now.to_s+rand.to_s)
		end

		def id=(i) #:nodoc:
			@id = i
		end

		def write_to_store #:nodoc:
			store.transaction{
				store[:captchas] = Array.new unless store.root?(:captchas)
				store[:captchas] << self
			}
		end

	class << self

		# Removes old instances from PStore
		def prune
			store.transaction{
				if store.root?(:captchas)
					store[:captchas].each_with_index{|c,i|
						if Time.now > c.created_at+c.ttl
							store[:captchas].delete_at(i)
						end
					}
				end
			}
		end#prune
	end#class << self

	end

	# A CAPTCHA challenge where an image with text is
	# generated. A human can read the text with relative
	# ease, while most robots can not. There are accessibility
	# problems with this challenge, though, as people
	# with reduced or no vision are unlikely to pass the test.
	class CaptchaImageChallenge < CaptchaChallenge

		WORDS = 'gorilla costume, superman, banana bender, chuck norris, xray vision, ahoy me hearties,
				 chunky bacon, latex, rupert murdoch, clap your hands, year 2000,
				 sugar coated, coca cola, rastafarian, airbus a380'.split(/,s+/)
		DEFAULT_DIR = 'captcha'#public/images/captcha
		WRITE_DIR = File.join(RAILS_ROOT, 'public', 'images')
		DEFAULT_FILETYPE = 'jpg'

		attr_reader :image
		attr_accessor :string, :dir, :filename, :filetype

		# Creates an image challenge.
	  def initialize(options = {})
			super

			options = {
				:string => config['words'] ? config['words'][rand(config['words'].size)] : WORDS[rand(WORDS.size)],
				:dir => config['default_dir'] || DEFAULT_DIR,
				:filetype => config['default_filetype'] || DEFAULT_FILETYPE
			}.update(options)

            self.string = Digest::SHA1.hexdigest(Time.now.to_s)[0..4].upcase #options[:string]
            self.dir = options[:dir]
            self.filetype = options[:filetype]
            self.filename = options[:filename] || generate_filename
            write_to_store
          end

		# Generates the image.
		def generate(options = {})
			options = {
				:fontsize => 50,
				:padding => 20,
				:color => '#000',
				:background => '#fff',
				:fontweight => 'bolw',
				:rotate => true
			}.update(options)

			options[:fontweight] = case options[:fontweight]
				when 'bold' then 700
				else 400
			end

			#added
			text = Array.new
			0.upto(4) do |i|
				text[i] = Magick::Draw.new
				text[i].pointsize = options[:fontsize]
				text[i].font_weight = 600 #options[:fontweight]
				text[i].fill = 'black' #options[:color]
				text[i].gravity = Magick::CenterGravity
				text[i].rotation = (rand(2)==1 ? rand(30) : -rand(30)) if options[:rotate]
			end

                  metric = text[2].get_type_metrics(self.string)
                  #add bg
		  canvas = Magick::ImageList.new
		  fill = Magick::HatchFill.new('white','black')
		  x = metric.width+options[:padding]
		  y = metric.height+options[:padding]

		  #ADDING NOISE

		  img1 = Magick::Image.new(x,y,fill)
		  gc = Magick::Draw.new
		  gc.stroke_linejoin('round')
		  gc.stroke('black')
		  gc.stroke_width(1)
                  100.times do |i|
                    x1 = rand(x)
                    y1 = rand(y)
                    gc.circle(x1,y1,x1.next,y1)
                  end
                  gc.draw(img1)
                  canvas << img1
                  0.upto(4) do |i|
				canvas << Magick::Image.new(metric.width+options[:padding], metric.height+options[:padding]){
					self.background_color = '#000F'
					y_loc = rand(y)
				}.annotate(text[i], 0, 0, (i-2)*30, rand(y/4), self.string[i,1]) #.wave(5, 20)
		  end

		  canvas << Magick::Image.new(metric.width+options[:padding], metric.height+options[:padding]){
		    #changed
		    p = Magick::Pixel.from_color(options[:background])
		    p.opacity = Magick::MaxRGB
		    self.background_color = p
		  }  #.add_noise(Magick::LaplacianNoise)

			self.image = canvas.flatten_images.blur_image(1)
		end

		# Writes image to file.
		def write(dir = self.dir, filename = self.filename)
			self.image.write(File.join(WRITE_DIR, dir, filename))
		end

		# Determine if the supplied +string+ matches
		# that used when generating the image.
		def correct?(string)
			string.downcase == self.string.downcase
		end

		# The full path to the image file, relative
		# to <tt>public/images</tt>.
		def file_path
			File.join(dir,filename)
		end

	class << self

		# Deletes old image files. Also calls CaptchaChallenge.prune
		def prune
			store.transaction{
				if store.root?(:captchas)
					store[:captchas].each_with_index{|c,i|
						if Time.now > c.created_at+c.ttl
							if File.exists?(File.join(WRITE_DIR, c.file_path))
								begin
									File.unlink(File.join(WRITE_DIR, c.file_path))
								rescue Exception
								end
							end
						end
					}
				end
			}
			super
		end#prune
	end#class << self

	private

		def generate_filename #:nodoc:
			self.id+'.'+self.filetype
		end

		def image=(i) #:nodoc:
			@image = i
		end

	end

end

visit Here to view the customized use of the plugin validates_captcha in RoR.

Advertisements

Entry filed under: captcha, image, Ruby on Rails.

Rake task to remove the image and audio files created by captcha Rake task to prepare test database from migrations

22 Comments

  • 1. Jerry  |  October 27, 2006 at 12:51 am

    Hello,Sur
    Can you send a mail about this correct source code to me?
    When i put above code to my ruby editor,it show some error.
    I think it has some syntax error.Maybe it be bring by html editor.
    So,if you correct source code please send a copy to me.
    Thank you so much!!!

    wangchangxiao@hotmail.com

  • 2. Sur Max  |  October 27, 2006 at 5:09 am

    Hi Jerry !!
    Sorry if there is any mistake residing in the above code. I have also done some more modifications in the source code of this file to make the images more practically implementable. I will upload the new code here today itself, side by i will mail it to you also.
    ——————————————————————————
    Hello everybody !!
    Everyone who is reading this post, as jerry suggested… if you are getting any problem with the code provided here then let me know via comment(or else) and provide your email… so that i can mail you that modified file itself.

  • 3. Chintan Shah  |  November 14, 2006 at 2:13 pm

    hi ,

    it is a great thing and thnx a lot

    can u help me for doing some changes in yr code.

    1) how can i change the size of the image ie height and width?

    2)how can i remove the black dots from the image that u r generating for the word?

  • 4. Sur Max  |  November 14, 2006 at 2:37 pm

    Hi Chintan !!
    Although i would recommend to use the dots coz there are some smart bots available which are based on pixel recognition and can even read the string from images if images are clean(without noise) and easy to read.
    However remove this code from the code above given to remove the dots

    100.times do |i|
    x1 = rand(x)
    y1 = rand(y)
    gc.circle(x1,y1,x1.next,y1)
    end

    Do always remember to restart the server whenever you change the code of any plugin.

  • 5. Sur Max  |  November 14, 2006 at 2:39 pm

    I havent explored the resizing yet…
    hopefully i will tell you soon… and will upload the code also.

  • 6. Chintan Shah  |  November 14, 2006 at 3:15 pm

    thnx Sur

  • 7. Laurent  |  December 2, 2006 at 2:20 pm

    Hi,

    I still have problems with the image (i have a lot of noise and no characters) even with the modified code of the class from this page.
    Could you please send me the source code at laurent.bois@gmail.com

  • 8. Dirk  |  December 12, 2006 at 2:29 pm

    I would like to use this too.

    Could you please send me the source code at

    diddek@gmail.com

    Thanks in advance

    Dirk

  • 9. Sur Max  |  December 12, 2006 at 4:39 pm

    Hi Dirk !!
    The modified code for the file capcha_challenge.rb is right there in the post itself. If you wanna see the usage of this plugin then see this post to implement captcha and then add the code given above, to improve the image quality !!

  • 10. Dirk  |  December 13, 2006 at 11:38 am

    Dear ‘Sur Max’

    I am sorry bu thte code provided above does not run ( `const_missing’: uninitialized constant CaptchaConfig (NameError))

    The posts here mention the same, so I wondered if you could provide me with the working version 🙂

    Can you either update the source in the post or mail me?

    Dirk

  • 11. Sur Max  |  December 13, 2006 at 11:49 am

    Hi Dirk !
    I have mailed the captcha plugin which i am using with all the modified code. Try it by just putting it into vendor/plugin directory and restarting the server… Assuming u have written all the code in model and controller correctly it would work fine.

  • 12. Dirk  |  December 13, 2006 at 12:05 pm

    Hi Sur Max

    I am sorry if I dont get it, but what’s the difference with the official version?

    The word list is the same in your version and the ‘official’ one:

    WORDS = ‘gorilla costume, superman, chuck norris, xray vision, ahoy me hearties,
    chunky bacon, latex, rupert murdoch, clap your hands, year 2000, disco rocks,
    sugar coated, staple my ears, rastafarian, airbus a380, good old days’.split(/,\s+/)

    I dont see a random text or the image like you display on top of this page.

    Differences I do see is that the code is arranged differently and ‘namespaces’ have been changed (AngryMidget … instead of FleksPlugin) – what am I missing here?

    Dirk

  • 13. Sur Max  |  December 13, 2006 at 12:09 pm

    Yes u didnt get it !! lol …
    The words written now are only text in the modified code and not being used anywhere, i am wandering if you are still getting the repeated words and not random string… probably u havnt restarted the server or u have not implemented it and just looking at code only …

  • 14. Chintan Shah  |  December 13, 2006 at 3:07 pm

    Hi Sur,

    i m in big problem, u have to help me at any how? check yr captcha code with the mongrel? it creates big problems so pls reply me soon i m waiting for reply

    it gives me an error like “pid…….” and when i tried to run the method which contains the captcha code it just displays as blank page and when i go for the page source it showm me blank there also, and it also clear the clusters of the mongrel, from there mongrel will be stoped so when i tried to go some where else it gives me an error of 503…. so pls help me as soon as possible.

    bye and thnx

  • 15. Matt  |  December 15, 2006 at 3:34 pm

    Could you send me the code?

  • 16. Stabilo  |  January 1, 2007 at 5:14 pm

    Got the same error as Dirk from above ( `const_missing’: uninitialized constant CaptchaConfig (NameError))

    Any ideas? Seems to be some namespace problem

    Regards

    Stabilo

  • […] However image is too noisy and it contains repeated strings. To improve the quality of images generated by the plugin validates_captcha visit Here. […]

  • 18. Eduard Martini  |  February 2, 2007 at 11:03 am

    As you maybe saw, this code is not working. I made some adjustments and is working perfectly for me. I modified to not use words and use random chains of letters.
    First in the CaptchaImageChallenge class replace constant ‘WORDS’ with this one:

    LETTERS = %w[a b c d e f g h i j k l m n o p q r s t u v x z 1 2 3 4 5 6 7 8 9 0]

    then replace all code from def generate(options = {}) with:


    options.reverse_merge!(
    :fontsize => 35,
    :padding => 20,
    :color => '#000',
    :background => '#fff',
    :fontweight => '500',
    :rotate => true,
    :font => config['font']
    ).symbolize_keys!

    options[:fontweight] = case options[:fontweight]
    when 'bold' then 700
    else options[:fontweight].to_i
    end

    text = Magick::Draw.new
    text.pointsize = options[:fontsize]
    text.font_weight = options[:fontweight]
    text.fill = options[:color]
    text.gravity = Magick::CenterGravity
    text.font = options[:font] if options[:font]

    #rotate text 5 degrees left or right
    text.rotation = (rand(2)==1 ? 5 : -5) if options[:rotate]

    metric = text.get_type_metrics(self.string)

    #add bg
    canvas = Magick::ImageList.new
    canvas

    then add a private function to the class:


    def make_string(arr)
    size=rand(3)+4
    str=""
    1.upto(size) do |n|
    str+=arr[rand(arr.size)].to_s
    end
    return str.upcase
    end


    Thats all

  • 19. Sur  |  February 2, 2007 at 11:11 am

    Hi Eduard !!
    Actually my code was running very fine with the previous version of this plugin…. and i am using the same version since then…. people started getting errors with the new version…. so as you have given the patch for the newer version it should run fine i suppose.
    Thanks.

    I am not sure if you have read the latest post here on this blog that i am moving my blog and also releasing my own captcha plugin that is really very simple to use and provides some cool image styles. I have also given sample images in the post above

  • 20. Neeraj  |  February 7, 2007 at 4:43 am

    In my view unless you are the next yahoo, a simple image without much dots and distortion will serve the purpose. I downloaded your code and I like what I see.

    I don’t want to put user through all this rigor if a simple text works. So I am making most of distortion , dots etc configurable. So next time if I am beaten by a blog you can just tune the configuration file without actually touching the code.

    If time permits and if you like the idea I would suggest you to make some of the parameters configurable. That would be a great help.

    Either way. Good work. Keep it up.

  • 21. Sur Max  |  February 7, 2007 at 4:47 am

    Hey Neeraj !!
    Check out my captcha plugin buddy.
    I have just launched it today on my new blog….. its almost the same way u are suggesting… It provides multiple image styles to chose from..
    Check this out here

  • 22. buy cialis online  |  March 8, 2007 at 5:36 am

    buy cialis online


Contact

sur.max(at)gmail.com
October 2006
M T W T F S S
« Sep   Nov »
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

Power Shots

Tokyo

Moke Lake

Lower Kentucky Falls (Mapleton, OR)

Glorious Morning

Three of a kind

Peace, quiet and tranquility

Stag Beetle

In het gras/Into the grass

Detached.



More Photos

Subscribe


%d bloggers like this: