Dibujar a mano alzada

facebook-svg gplus-svg twitter-svg

Podemos utilizar el canvas de HTML5 para dibujar a mano alzada. Esta aplicación puede ser útil si queremos dar al usuario la posibilidad de firmar un documento, o poner bigotes a una foto, o todo lo que se nos ocurra. Veamos lo básico:

Qué necesitamos

Primero necesitamos poder detectar la posición del ratón que es "la punta del lápiz" con el cual dibujamos.


function oMousePos(canvas, evt) {
      var ClientRect = canvas.getBoundingClientRect();
           return { //objeto
           x: Math.round(evt.clientX - ClientRect.left),
           y: Math.round(evt.clientY - ClientRect.top)
}

También necesitamos utilizar algunos eventos del ratón:

  • 1. Al presionar el botón del ratón empezamos a dibujar. El evento del ratón involucrado en este caso es mousedown ( literalmente : ratón abajo ).
  • 2. Al mover el ratón dibujamos. El evento del ratón involucrado en este caso es mousemove ( literalmente : ratón moviéndose ).
  • 3. Al soltar el botón del ratón dejamos de dibujar. El evento del ratón involucrado en este caso es mouseup. ( literalmente : ratón arriba ).
  • 4. Si queremos podemos también tomar en consideración la salida del ratón del canvas: mouseout ( literalmente : ratón fuera ).

Vamos por partes:

Primero iniciamos el canvas, en este caso un canvas de 300 por 300 pixeles:


var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width = 300,
    cx = cw / 2;
var ch = canvas.height = 300,
    cy = ch / 2;

Al iniciar el canvas todavía no podemos dibujar.

var dibujar  = false;

Una vez que hayamos presionado el botón del ratón ( mousedown ) podemos dibujar.

var dibujar  = true;

canvas.addEventListener('mousedown', function(evt) {
      dibujar = true; // ya podemos dibujar
      ctx.beginPath(); //empezamos a dibujar
}, false);

Al mover el ratón ( mousemove ) si podemos dibujar, o sea: si ya hemos presionado el botón del ratón:

if(dibujar){. . .

detectamos la posición del ratón (en cada momento).

var m = oMousePos(canvas, evt);

y trazamos una línea ( la verdad: una línea muy cortita ) desde el punto anterior, hasta el punto actual:

ctx.lineTo(m.x, m.y);
ctx.stroke();

canvas.addEventListener("mousemove", function(evt) {
      if (dibujar) {
        var m = oMousePos(canvas, evt);
        ctx.lineTo(m.x, m.y);
        ctx.stroke();
      }
}, false);

Al soltar el botón del ratón ( mouseup ) o al salir del canvas ( mouseout ) tenemos que cambiar el valor de la variable dibujar a false. De esta manera podemos mover el ratón encima del canvas sin que pase nada.

dibujar = false;

canvas.addEventListener('mouseup', function(evt) {
      dibujar = false;
    }, false);

canvas.addEventListener("mouseout", function(evt) {   dibujar = false; }, false);

Y ya está! Pruebe dibujar en el canvas.

See the Pen Dibujar a mano alzada #1 by Gabi (@enxaneta) on CodePen.

Cómo podemos mejorar esta aplicación

Por lastima el trazado resultante tiene un aspecto áspero e irregular. Esto puede mejorarse reduciendo el número de puntos del trazado, y conectando los nuevos puntos con curvas de Bézier.

1. Reducir el número de puntos

En el siguiente ejemplo la variable factorDeAlisamiento = 5, lo que quiere decir que de cada 5 puntos del trazado nos quedamos con uno solo. La función reducirArray() se encarga de esto:

function reducirArray(n, elArray) {
   var nuevoArray = [];// el nuevo array reducido
   // el primer elemento del nuevo array es el primer elemento del array que queremos reducir
   nuevoArray[0] = elArray[0]; 
   // de cada n puntos nos quedamos solo con uno
   // donde n representa el factor de alisamiento
   for (var i = 1; i < elArray.length; i++) {
        if (i % n == 0) {
          nuevoArray[nuevoArray.length] = elArray[i];
        }
   }
   // el último elemento del nuevo array es el último elemento del array que queremos reducir
   nuevoArray[nuevoArray.length - 1] = elArray[elArray.length - 1];
   // la función devuelve el array reducido.
   return nuevoArray;
}
2. Calcular los puntos de control

Para trazar una curva cuadrática de Bézier necesitamos conocer la posición del punto de control. Dados dos puntos cualquiera, la función calcularPuntoDeControl() calcula la posición del punto de control.


function calcularPuntoDeControl(a, b) {
      var pc = {}
      pc.x = (a.x + b.x) / 2;
      pc.y = (a.y + b.y) / 2;
      return pc;
}
3. Alisar el trazado

La función alisarTrazado() recorre el array reducido, y para cada dos puntos calcula el punto de control y dibuja una curva cuadrática de Bézier.


function alisarTrazado(ry) {
      if (ry.length > 1) {
        var ultimoPunto = ry.length - 1;
        ctx.beginPath();
        ctx.moveTo(ry[0].x, ry[0].y);
        for (i = 1; i < ry.length - 2; i++) {
          var pc = calcularPuntoDeControl(ry[i], ry[i + 1]);
          ctx.quadraticCurveTo(ry[i].x, ry[i].y, pc.x, pc.y);
        }
        ctx.quadraticCurveTo(ry[ultimoPunto - 1].x, ry[ultimoPunto - 1].y, ry[ultimoPunto].x, ry[ultimoPunto].y);
        ctx.stroke();
      }
}
4. Redibujar el trazado

Al soltar el botón del ratón o al salir del canvas borramos el trazado áspero e irregular y dibujamos el nuevo trazado.


function redibujarTrazado() {
      dibujar = false;
      ctx.clearRect(0, 0, cw, ch);
      var nuevoArray = reducirArray(factorDeAlisamiento, puntos);
      alisarTrazado(nuevoArray);
}

See the Pen Dibujar a mano alzada #2 by Gabi (@enxaneta) on CodePen.

Lo podemos hacer aún mejor! En los ejemplos anteriores, al presionar el botón del ratón ( mousedown ) limpiamos el canvas. Pero ¿que pasa si la firma del usuario tiene varios trazados?

See the Pen Dibujar a mano alzada #3 by Gabi (@enxaneta) on CodePen.

Artículos relacionados