Hace rato que quería poner en consideración el tema del captcha. (El captcha es esto )

Así que vamos al código:

Antes que nada cree un controlador para el captcha, se necesita instalar la gema RMagick (perdí el enlace al blog de donde me basé para esto):

 class CaptchaController < ApplicationController
 
   require_gem 'rmagick'
   
   # generar el captcha y enviarlo al browser
   def mostrar
     # generar los caracteres aleatorios
     captcha_text = Utilidades::GeneradorAleatorio.generar_numero(4)
     
     # generar la capa del texto
     text_layer = Magick::Draw.new
     text_layer.pointsize = 25
     text_layer.fill = '#000'
     text_layer.gravity = Magick::CenterGravity
     
     # obtener las medidas del texto
     medidas = text_layer.get_type_metrics(captcha_text)
     
     # definir lienzo
     canvas = Magick::ImageList.new
     
     # poner imagen de fondo al canvas
     canvas << Magick::Image.new(medidas.width, medidas.height){
       self.background_color = '#fff'
     }
     
     # poner texto al canvas
     canvas << Magick::Image.new(medidas.width, medidas.height){
       self.background_color = '#000F'
     }.annotate(text_layer, 0, 0, 0, 0, captcha_text).wave(3, 100)
 
     # opacar la imagen y ponerle ruido
     canvas << Magick::Image.new(medidas.width, medidas.height){
       p = Magick::Pixel.from_color('#fff')
       p.opacity = Magick::MaxRGB/2
       self.background_color = p
     }
 
     # crear image en bytes
     image = canvas.flatten_images.blur_image(1)
     image.format = "JPG"
     captchablob = image.to_blob
 
     # poner texto del captcha en session
     session[:captcha] = captcha_text
     
     # enviar la imagen al browser
     send_data(captchablob,
               :filename => 'captcha.jpg',
               :type => 'image/jpeg',
               :disposition => 'inline')
   end
 end 
 
 

En la primera línea utilizo el generador de aleatorios que ya postee en este mismo blog en otro momento.

Luego de esto en la vista llamo al captcha con una imagen normal, colocándole el src, apuntando al controlador.. algo así como:

 
 <img src="#{url_for(:controller => 'captcha', :action => 'generar', :time => Time.now)}" />
 
 

Esto funciona, pero comencé a cuestionarme sobre el rendimiento de esta alternativa, ya que por cada petición se debía volver a crear la imagen... eso me pareció como demasiado pesado.
El captcha que necesito tiene solamente 4 dígitos... va desde el 0000 hasta el 9999. Entonces, mas bien me dije... que tal si pre-generamos las imágenes posibles, las guardamos en el directorio de imágenes de la aplicación y en lugar de construir la imagen en cada petición, mas bien la leemos del disco y la enviamos... entonces adicioné una acción nueva al controlador... y quedó así (una consideración importante es que el nombre del archivo de imagen
concuerda con la imagen del número, entonces allí en el directorio
encontraremos archivos como: 0000.jpg, 2345.jpg, etc.

) :

 class CaptchaController < ApplicationController
   def mostrar
     # generar los caracteres aleatorios
     captcha_text = Utilidades::GeneradorAleatorio.generar_numero(4)
     imagen = Magick::Image::read("#{RAILS_ROOT}/public/images/captcha/#{captcha_text}.jpg").first
     
     # poner texto del captcha en session
     session[:captcha] = captcha_text
     
     # enviar la imagen al browser
     send_data(imagen.to_blob,
               :filename => 'captcha.jpg',
               :type => 'image/jpeg',
               :disposition => 'inline')
     
   end
 end
 
 

Esto además de evitar el "peso" de crear una imagen en cada petición, evita que en el servidor de producción se tenga instalada la gema Rmagick. En contraste en el directorio public/images/captcha quedaron algo así como 10 MB de imágenes.

Este es el script en Ruby utilizado para pre-generar las imágenes, obviamente se reutiliza el mismo código que había en la acción original del controlador:

 require_gem 'rmagick'
   
   # generar la imagen del número y guardarlo en el disco
   def generar_imagen(texto)
     # generar la capa del texto
     text_layer = Magick::Draw.new
     text_layer.pointsize = 25
     text_layer.fill = '#000'
     text_layer.gravity = Magick::CenterGravity
     
     # obtener las medidas del texto
     medidas = text_layer.get_type_metrics(texto)
     
     # definir lienzo
     canvas = Magick::ImageList.new
     
     # poner imagen de fondo al canvas
     canvas << Magick::Image.new(medidas.width, medidas.height){
       self.background_color = '#fff'
     }
     
     # poner texto al canvas
     canvas << Magick::Image.new(medidas.width, medidas.height){
       self.background_color = '#000F'
     }.annotate(text_layer, 0, 0, 0, 0, texto).wave(3, 100)
 
     # opacar la imagen y ponerle ruido
     canvas << Magick::Image.new(medidas.width, medidas.height){
       p = Magick::Pixel.from_color('#fff')
       p.opacity = Magick::MaxRGB/2
       self.background_color = p
     }
 
     # crear image en bytes
     image = canvas.flatten_images.blur_image(1)
 
     # guardar imagen en archivo
     image.write("#{texto}.jpg")
   end
 
 0.upto(999) do |x|
   texto = x.to_s
   texto = "000" << texto if x < 10
   texto = "00" << texto if x >= 10 and x < 100
   texto = "0" << texto if x >= 100 and x < 1000
   generar_imagen(texto)
 end
 
 

Como siempre, saludos y espero sus comentarios...