Eficiencia del captcha
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...
Fernan2 dijo
Un artículo muy interesante y una idea muy buena, muchas gracias
26 Marzo 2007 | 10:24 AM