Guardando pngs en el servidor con HTML5

danii . martes 19 de noviembre de 2013. a las 09:02

guardando-pngs-servidor-javascript-html5

La idea de este post es replicar uno de nuestros primeros post de utilidades para Flash, guardando jpgs en servidor con ActionScript3 pero utilizando tecnologías web estándares, es decir HTML5, CSS y JavaScript. En particular vamos a centrarnos en el elemento canvas de HTML5 para dibujar sobre él, y su método toDataURL para capturar la imagen en un stream de datos binarios que posteriormente enviaremos al servidor y guardaremos en un archivo PNG.

Para replicar la parte de dibujar sobre un elemento canvas, nos basamos en este conciso ejemplo: How to draw on a HTML5 canvas with a mouse. Tras una pequeña refactorización de código para darle un lavado de cara (el cambio principal ha sido eliminar la totalmente innecesaria dependencia de JQuery), implementamos un botón SAVE que simplemente manda por Ajax mediante POST los datos binarios de la imagen a un fichero PHP, que se encargará de procesarla en el servidor. En nuestro caso, simplemente le asignamos un nombre aleatorio y la guardamos.

Demo: Dibujar con el mouse sobre el canvas y guardar fichero png en servidor

Ver código en Github

La parte del código que «pinta» sobre el elemento canvas es realmente simple, tan solo tenemos que asociar los event listeners apropiados. El único problema con el que nos podemos encontrar es que la propiedad el evento layerX y layerY no contenga los valores correctos. Puesto que queremos las coordenadas del mouse relativas al elemento canvas, es obligatorio que éste tenga un estilo position:relative;, de otra manera obtendremos posiciones absolutas a la página.

    // canvas events
    canvas.onmousedown = function(e) {
      draw(e.layerX, e.layerY);
      mousePressed = true;
    };

    canvas.onmousemove = function(e) {
      if (mousePressed) {
        draw(e.layerX, e.layerY);
      }
    };

    canvas.onmouseup = function(e) {
      mousePressed = false;
    };
    
    canvas.onmouseleave = function(e) {
      mousePressed = false;
    };

  function draw(x, y) {
    if (mousePressed) {
      ctx.beginPath();
      ctx.strokeStyle = document.getElementById('color').value;
      ctx.lineWidth = 1;
      ctx.lineJoin = 'round';
      ctx.moveTo(lastX, lastY);
      ctx.lineTo(x, y);
      ctx.closePath();
      ctx.stroke();
    }
    lastX = x; lastY = y;
  }

A la hora de mandar los datos al servidor, simplemente creamos una petición XHR a la URL del servidor donde tenemos el script que se encargará de procesarla. En nuestro caso, tras almacenar la imagen en una carpeta del servidor respondemos con el nombre aleatorio que se le ha sido asignado. A continuación, y para demostrar que no mentimos y que de hecho la imagen .png ha sido almacenada en el servidor, cuando recibimos el callback de que la petición ha sido completada abrimos una ventana nueva para que podáis contemplar vuestra obra de arte 😉

function sendToServer() {
    var data = canvas.toDataURL('image/png');
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      // request complete
      if (xhr.readyState == 4) {
        window.open('http://www.lostiemposcambian.com/blog/posts/guardando-pngs-html5/snapshots/'+xhr.responseText,'_blank');
      }
    }
    xhr.open('POST','http://www.lostiemposcambian.com/blog/posts/guardando-pngs-html5/snapshot.php',true);
    xhr.setRequestHeader('Content-Type', 'application/upload');
    xhr.send(data);
  }

A continuación mostramos el código PHP del servidor que se encargará de almacenar el archivo. Utilizaremos el input stream de PHP para leer los datos binarios, que es preferible al $HTTP_RAW_POST_DATA al ser más eficiente y no precisar de configuración adicional en el servidor. Atención porque antes de almacenar este stream de datos debemos hacer un pequeño preproceso (este es un paso adicional que con Flash ActionScript3 no hacía falta), ya que los datos binarios vienen con un header data: que hemos de eliminar, y además codificados en base64, con lo cual debemos decodificarlos antes de guardar el archivo.

<?php
  // read input stream
	$data = file_get_contents("php://input");
	
	// filtering and decoding code adapted from
        // http://stackoverflow.com/questions/11843115/uploading-canvas-context-as-image-using-ajax-and-php?lq=1
	// Filter out the headers (data:,) part.
	$filteredData=substr($data, strpos($data, ",")+1);
	// Need to decode before saving since the data we received is already base64 encoded
	$decodedData=base64_decode($filteredData);

	// store in server
	$fic_name = 'snapshot'.rand(1000,9999).'.png';
	$fp = fopen('./snapshots/'.$fic_name, 'wb');
	$ok = fwrite( $fp, $decodedData);
	fclose( $fp );
	if($ok)
		echo $fic_name;
	else
		echo "ERROR";
?>

Por supuesto, en el script servidor puede estar en cualquier lenguaje servidor que estemos utilizando. También podemos añadir toda la lógica que necesitemos, como por ejemplo almacenado en base de datos, asegurarnos que el nombre de archivo no existe ya, y un largo etcétera. El echo final final simplemente lo utilizamos para devolver a JavaScript el nombre del archivo que hemos almacenado, y de esta manera se muestre el navegador.

Etiquetas: , , , , ,

21 Comentarios
» Feed RSS de los Comentarios

  1. AnzOne dice:

    Muchas gracias, me ha sido muy útil.

  2. Rafael dice:

    He probado este tutorial para un proyecto que estoy realizando y funciona bien, el único problema que estoy teniendo es, que las imagenes tengo que re-nombrarlas para poder verlas, en la tab las veo, pero cuando acceso a la tab desde el ordenador, en esa carpeta es como si no hubiera nada, al re-nombrarlas, aparecen.
    He tratado de hacer un copy y upload con php pero no me reconoce la imagen, bueno si puedes ayudarme te lo agradecería
    Saludos

  3. Eduardo C dice:

    Yo tengo un problema parecido, me guarda la imagen pero a la hora de que abre una nueva ventana con esa imagen me la muestra vacía, reviso mi carpeta y si crea la imagen. Si abro la imagen en el explorador por mi cuenta si la muestra. ¿Alguna idea?

  4. Francisco garcia dice:

    se puede guardar la imagen sin usar php? unicamente jquery

  5. edwin Garcia dice:

    me parece excelente esta herramienta pero veo que no funciona en tablet, alguna ayuda, mucha gracias.

  6. Eduardo C dice:

    jquery es javascript, javascript es del lado del cliente por lo tanto se necesita un lenguaje del lado del servidor para guardar archivos en el servidor Francisco

  7. danii dice:

    Buenas Edwin,

    Efectivamente, no funciona en tablet debido a que los eventos con los que la demo trabaja son «onmousedown», «onmousemove» y «onmouseup», es decir eventos de ratón que no deben dispararse en los elementos canvas de las tablet. La verdad es que cuando construimos esta demo ni se nos ocurrió plantearnos el hacerla compatible con mobile o touch events. Sin embargo, debe ser trivial el añadir los eventos touch y hacerla funcionar! Te atreves? 😉

    Gracias por el apunte Eduardo C, efectivamente hace falta algún lenguaje del lado servidor para guardar el archivo, sin embargo no tiene porque ser PHP, cualquiera nos vale, desde Node.js hasta Java, pasando por Ruby, Python, etc. Jquery no es un lenguaje, es una librería o framework de JavaScript. Seamos puristas por favor!

  8. Cristian dice:

    Hola, alguien sabe como se hace en jsp?, serían de gran ayudar si alguien pudiera decirme

  9. Rubens dice:

    Como colocar uma imagem de fundo que possa ser salva junto com o desenho?

  10. Eduardo C dice:

    Pasando por aquí de nuevo, lo de las tables se arregla con los eventos touch de javascript.

  11. jose manuel dice:

    como colocar una imagen para que se pueda pintar sobre ella

  12. Amigox dice:

    Genial!!!
    Era lo que buscaba, y me has ahorrado mucho trabajo, el primer pie, porque aún tengo mucho trabajo que hacer, crear capas!!!

  13. Xavi dice:

    Podemos rizar un poquito más el rizo???
    El problema que tengo al dibujar sobre la tablet es que al apoyar la mano en la pantalla … no hace falta que cuente más, no? Ahí si que se puede decir que hay que dibujar a mano alzada.
    Y mi pregunta: Se le puede decir a la tablet que en el momento que haya un canvas en pantalla solo funcione el táctil de dentro de dicho canvas???
    Gracias de antemano y nunca mejor dicho «los tiempos cambian»

  14. Maria Jose dice:

    Me ha funcionado a la perfección pero…. he intentando que me genere una imagen en jpg en vez de png y no lo he conseguido.
    Qué tendría que hacer??

  15. Sickboy dice:

    Buenas tardes,

    ¿Tienes hecho el ejemplo para la tablet? Estaría genial…

    Un Saludo,

  16. Eduardo dice:

    Buenos días,

    ¿Hay alguna posibilidad de realizarlo pero con código para tablet pc a la vez? Seria de gran ayuda.

    Gracias!

  17. Sickboy dice:

    ¿Alguien podría ayudarme a realizar ésto mismo pero que pueda escribir desde la tablet?
    Gracias

  18. ricardo dice:

    si quisiera procesar la imagen en el servidor nodejs como seria?

  19. c_nerio dice:

    Saludos existe alguna forma utilizando el codigo php proporcionado aca de quitar el alpha channel de la imagen que se guarda ?

  20. dani dice:

    He conseguido que funcione en mi web de una forma sencilla… se adapta genial.

    Gracias y enhorabuena por el trabajo…
    Lo malo es que lo uso para capturar firmas, y los usuarios se quejan de que es difícil firmar con el raton, he intentado incluir los eventos touch para adaptar el código a pantallas táctiles, pero me doy por vencido. Si alguien lo consigue que lo comparta porfa!!
    Saludos a todos

  21. walter muller dice:

    muy bueno el ejemplo, gracias

Enviar comentario