HTML5 : Guardar el contenido de <canvas> en el servidor

April 13th, 2010 | by | programación

Apr
13

Hoy tenía ganas de hacer algo distinto (vamos, no quería trabajar :P ) así que me puse a jugar con HTML5, entre otras cosas con el tag <canvas> para poder dibujar. Luego de bajar varios ejemplos y tener un ‘paint’ andando lo que quería era guardar la imagen, y llegué a este post donde el autor deja guardar en PNG, JPG hasta incluso en BMP el contenido del canvas.

Pero que el usuario se pueda guardar la imagen no era mi idea, era más bien guardarle en el servidor. Por ejemplo para hacer una firma digital y usarla después en el sitio para firmar los posts como si fueran documentos :P .

Al principio pensé que iba a ser super complicado, pero me encontré que todo fue mucho más fácil de lo que imaginé. Así que vamos por partes.

Vamos a empezar por el server. Como era muy poco código para este ejemplo, hice una aplicación en Sinatra que me permite mostrar un canvas y luego hacer un POST con los datos. Es ideal en este caso, ya que el server queda de tan solo unas 20 líneas de código :

require 'sinatra'
require 'RMagick'
 
get '/' do
   erb :index
end
 
post '/' do
  # Ya completaremos ...
end

Como se puede ver tenemos una vista en el root y el POST lo manejamos también en la raiz. Para procesar la imagen decidí usar la única gema que conozco, RMagick, que buscando en la documentación de ImageMagick me encontré con grata sorpresa que sale leer ‘Base64-encoded inline image’ , que resulta que es justo el formato en que se obtienen los datos desde el canvas :) . La única diferencia es que hay que preceder el string con ‘inline:’ y ya estamos, nuestro método POST queda así :

post '/' do
  data = 'inline:' + request.body.read
 
  begin
    image = Magick::Image.read(data).first
 
    image.write('image.png')
    "alert('Image saved successfully')"
  rescue
    "alert('Image format not recognized')"
  end
end

Habría que revisar la documentación y bugs de ImageMagick para ver que tipos de implicancia puede llegar a tener en la seguridad del sitio o del sistema, pero son cosas que no vienen al caso :) .

La vista (reducida para el ejemplo) puede resumirse en el siguiente HTML :

  <canvas id="example" width="200" height="200">
    This text is displayed if your browser does not support HTML5 Canvas.
  </canvas>
 
  <input type="button" id="submit" value="Submit" />

Y un poco de javascript :

<script type="text/javascript">
  $(function () {
    $("input").click(function () {
      // jQuery todavía no habla bien con HTML5, hack para
      // no usar un plugin.
      var c = $("#example").get(0);
      $.post('/', c.toDataURL(), null, "script");
    });
 
    // Un dibujito estático para probar
    var example = document.getElementById('example');
    var context = example.getContext('2d');
    context.fillStyle = "rgb(255,0,0)";
    context.fillRect(30, 30, 50, 50);
  })
</script>

Y ya estamos, al presionar el botón se envían los datos de la imagen dibujada, si está todo bien ImageMagick creará el archivo en el filesystem y por último devolverá algo de javascript para informar al usuario como salió todo. Obviamente si tuviéramos un sistema de usuarios en el server podríamos guardar la imagen para cada uno, pero de nuevo, no viene al caso.

El resultado final :

$ identify -verbose image.png 
Image: image.png
  Format: PNG (Portable Network Graphics)
  Class: DirectClass
  Geometry: 200x200+0+0
  Resolution: 72x72
  Print size: 2.77778x2.77778
  Colorspace: RGB
  Depth: 8/1-bit
  Histogram:
     37500: (  0,  0,  0,  0) #00000000 none
      2500: (255,  0,  0,255) #FF0000 red
  Page geometry: 200x200+0+0
  Compression: Zip
  Properties:
    create-date: 2010-04-13T00:20:15-03:00
    modify-date: 2010-04-13T00:20:15-03:00
    signature: 0a42f3c85d6364e38f14dac554d26c62a90b84b06ac7f22264c9d964657ba8d6
  Tainted: False
  Filesize: 503b
  Number pixels: 39.1kb
  Version: ImageMagick 6.5.1-0 2009-08-27 Q16 OpenMP http://www.imagemagick.org

Probado en :

  • Chromium 5.0.372.0 (44042) Ubuntu
  • Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.9) Gecko/20100401 Ubuntu/9.10 (karmic) Firefox/3.5.9

Código completo : html5_canvas_submit

3 Comments »

Casa y Casamiento

April 9th, 2010 | by | general, yo estuve ahí

Apr
09

Y si, volví para seguir molestando con cosas que a nadie le interesa :) . En los últimos meses han pasado tantas cosas que ya ni encuentro un rato para sentarme a escribir :D .

Para empezar me volví a mudar, si, si, 4 mudanzas en 4 meses, espero que esta vez sea la definitiva. Ahora, y después de 12 años, volví a mi Cipolletti “natal“, el que me vio hacer tantas cagadas de chico. Y más aún, volví al barrio donde viví toda mi secundaria (a menos de 100 mts de la casa donde vivíamos con mis viejos por esa época), toda una regresión :) .

Pero la cosa siguió y finalmente llegamos vivos al casamiento con mi (ex) novia y me tuve que disfrazar de “empresario” para aparentar un poco frente a la familia. Siempre es bueno dar una buena impresión, pero a mi me suele alcanzar con que sea buena o mala :) .

La fiesta fue, bueno, una fiesta, si me conocen saben como soy cuando hay joda, así que el resto se lo pueden imaginar. No faltó ni comida y mucho menos para tomar, barra libre, etc, etc … más de uno se fue gateando (yo volví manejando, para variar, en piloto automático). Cuando arrancó la joda, luego de que Nacho Morán (imitador/cantante de Choele Choel) hiciera su gracia, se armó alto bailongo, donde nadie se quedó sentado.

Más entrada la noche una mesa dulce para cortar un poco el alcohol y si te agarra el control de alcoholemia poder mentir que fue por metabolizar los dulces :D , aunque hay que se realista, en Cipollettia las 8AM no hay controles :P .

Ya cuando los gallos empiezan a cantar empezamos a cerrar la joda con un foto de los valientes que duraron hasta el final.

Por último falta la luna de miel, que se da sobre fin de mes (nada de salir corriendo al otro día, hay que aprovechar las vacaciones :D ).

Ah, y por si todo fue muy sutil y no se notó en las fotos anteriores, pegue “altas llantas” esa noche :)

5 Comments »

Apple iPad

January 28th, 2010 | by | general

Jan
28

Bueno, no voy a decir lo que ya todo el mundo ya debe haber leído (no multitask, es un iPod Touch grandote, solo con AT&T bla bla bla) :) .

Creo que eso resume más que bien lo que la mayoría de los no-fan de Apple piensan cuando leen la ficha técnica :D .

No Comments »

Migrando datos desde PHP a Rails

January 22nd, 2010 | by | programación

Jan
22

Por esas cosas de la vida me encontré todo el día de hoy migrando datos viejos de un sistema hecho en PHP a uno hecho en Rails. Las cosas venían bastante simples definiendo modelos de ActiveRecord para las tablas de la base de datos vieja y reinsertando con modelos apuntando a las tablas nuevas. Pero … siempre hay un pero.

Resulta que el hermoso sistema anterior para evitar tener muchas tablas (o vaya a saber por qué) tenía en una parte un tabla donde cada field era un gran TEXT que contenía un array de PHP serializado.

class UserTextField < ActiveRecord::Base
  set_table_name "usertextfield"
  set_primary_key "userid"
 
  def folders
    field3
  end
end
 
$> user = UserTextField.find(3)
$> user.folders # => 'a:2:{i:0;s:4:"bkps";i:1;s:6:"listas";'

Justo cuando estaba por ponerme a parsear texto me encontré con php-serialize que permite serializar y deserializar estos string en cómodos tipos nativos de Ruby.

El código final queda entonces algo como :

class UserTextField < ActiveRecord::Base
  set_table_name "usertextfield"
  set_primary_key "userid"
 
  def folders
    PHP.unserialize(field3)
  end
end
 
$> user = UserTextField.find(3)
$> user.folders # => ["bkps", "listas"]

Y la migración de datos pudo continuar sin problemas :) .

2 Comments »