Dibujar píxeles en un Canvas de HTML5

En la red existen diversos tutoriales, cursos, trucos, ejemplos, y demás sobre HTML5. Básicamente, HTML5 es el último standard de HTML que está diseñado especialmente para contener información como animación de gráficos, música, vídeos y otros más complejos. Todo esto sin emplear ningún plugin adicional sobre un browser. Igualmente, HTML5 es soportado por diferentes plataformas, pudiendo funcionar en PC, tablet, smartphone o smartTV.

HTML5 logo

HTML5 inserta nuevos elementos y atributos a los existentes en HTML. La idea de estos elementos es ajustarse a los sitios web modernos, teniendo elementos como <video>, <audio> y <canvas>. Particularmente, el elemento <canvas> permite hacer render de elementos 2D y 3D de forma dinámica. Un Canvas es un elemento donde se puede dibujar dentro de una página en HTML, donde empleando JavaScript se puede realizar todo el conjunto de funciones de dibujado. De hecho, al mezclar HTML5 + JavaScript + CSS3 se puede producir grandes aplicaciones interactivas en tiempo real.

El API de dibujado sobre el Canvas ofrece diversas funciones que pueden ser consultadas en diversas páginas con información bastante detallada [here, here, here and here]. En este post describiré sobre lo más básico en el dibujado 2D: pintar un píxel. En HTML5 no existe una función setPixel como tal para pintar en una posición x,y. Entonces, utilizaremos el enfoque más sencillo basado en pintar un rectángulo de dimensión 1×1 con la función fillRect, y emplearemos la estructura básica en HTML, JavaScript y CSS3.

HTML

Para empezar, debemos conocer la estructura de trabajo donde se alojará el Canvas. Primero tendremos un archivo HTML llamado ElNombreQueQuierasColocarle.html y otro llamado drawFigures.js (JavaScript). El archivo HTML, luce algo como:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>This is my test</title>
    </head>
    <body onload="main()">
        <canvas id="canvas2D" width="400" height="400">
            Please use a browser
        </canvas>
        <script src="drawFigures.js"></script>
    </body>
</html>

La línea 1 es una instrucción que indica la versión del browser para soportar HTML5. Luego, se indica el idioma a utilizar en la página basado en un código de lenguaje que se extrae del ISO 639-1. Dentro del tag <head> se especifica la codificación de los caracteres y el título de la página (nombre de tú preferencia). Posteriormente, se define el cuerpo de la página HTML dentro de los tags <body>.

Utilizando JavaScript, es posible definir una función que ejecute acciones justo cuando se han terminado de «cargar» el contenido, recursos, assets de una página. La función (de hecho el evento) onload es la encargada de esta tarea. En la línea 7 se muestra que el nombre de dicha función (escrita en JavaScript) es main. Dentro del cuerpo de la página, se define el componente Canvas (línea 8) bajo un id y atributos de ancho (width) y alto (height). La línea 9 es un mensaje que aparecerá en caso de que un browser no soporte dicho componente. Por último, se indica en dónde estará el código en JavaScript que se utilizará para el dibujado (ver línea 11). Generalmente, la etiqueta script se suele colocar dentro del head para que se cargue al comenzar a cargar la página.

JavaScript

Como fue definido en la función onload, se necesita declarar la función main. Para probar que todo marcha correctamente hasta este punto, podemos definir una función main como sigue:

function main()
{
  var canvas = document.getElementById("canvas2D");
  if (!canvas)
  {
    console.log("Failed to retrieve the <canvas> element");
    return;
  }
  var context = canvas.getContext("2d");
  // the magic begin here
}

Igual que HTML, existen diversos tutoriales para aprender a programar usando JavaScript [codeacademy, codeschool, eloquent JS]. La manera de obtener el primer elemento de un componente (especificado bajo un id) presente en la página HTML, es empleando la función getElementById. Así, en la línea 3 se muestra como se obtiene la variable que corresponde al Canvas. Si no fue posible obtener el elemento entonces no se sigue ejecutando el código. Posterior, se obtiene el objeto que provee los métodos y propiedades para dibujar en el canvas. A este punto, debería mostrarse de forma adecuada un canvas (fondo blanco) sin nada dibujado.

En este ejemplo, aplicaremos el dibujado de una línea donde requeriremos emplear el mouse para indicar el punto inicial (variable pStart) y final (variable pEnd) de ésta. Utilizaremos el enfoque naive que se puede ver en Wikipedia, pero cualquier enfoque es válido. Para ello, es necesario definir los eventos del mouse asociados al componente Canvas. Existen dos formas clásicas de hacerlo: con la definición de funciones inline (elemento.onclick) y con event listener. Simplemente, tomaremos la opción de event listener asociados al componente Canvas. Usando event listener, es posible definir eventos como mousedown, mouseup, mousemove, mouseout y mouseover. El evento de mouse down luciría algo como:

canvas.addEventListener("mousedown", function (e)
{
  console.log("Click in (" + e.clientX + "," + e.clientY + ")");
});

En este ejemplo, se mostraría por consola la posición del mouse al hacer click (ver la consola en Mozilla, Opera y Chrome). La posición (x,y) donde se realiza el click es relativo a la ventana del browser completo y no relativo al canvas. Para realizar que la posición del mouse sea relativo al componente Canvas y no a la ventana del browser, se requiere realizar una conversión de las coordenadas. La conversión es simple dado que se tiene la información de la ventana de despliegue (browser) y de la ventana de interés (canvas). La función windowToCanvasCoord a continuación se encarga de hacer esta tarea.

/// Convert from window coordinates to canvas coordinates
/// @param context the context of the canvas
/// @param x, y position in window/browser/viewport coordinates
function windowToCanvasCoord(canvas, x, y)
{
  var bbox = canvas.getBoundingClientRect();
  return { x: x - bbox.left * (canvas.width / bbox.width),
           y: y - bbox.top * (canvas.height / bbox.height)
  };
}

Modificando un poco el listener mousedown del canvas, podemos tener los clicks relativos a la posición del canvas. De esta forma es independiente la posición donde se ubique el componente dentro de la página HTML.

canvas.addEventListener("mousedown", function (e)
{
  dragging = !dragging;   //variable initialized on false
  pStart = windowToCanvasCoord(canvas, e.clientX, e.clientY);
  pEnd = pStart;
  console.log("Click in (" + pStart.x + "," + pStart.y + ")");
});

En el código anterior, añadimos una variable (línea 3) llamada dragging que es un flag que representa cuando se ha dado click al mouse, y se moverá el mouse con el click presionado. Igualmente, en la línea 4 y 5 se muestra la asignación de los valores a los puntos extremos de la línea a dibujar (pStart y pEnd).

El listener mouseup, se emplea para indicar cuando el click del mouse es liberado:

canvas.addEventListener("mouseup", function (e)
{
  dragging = false;
});

Ahora, para que se pueda mostrar la línea a medida que hacemos click y arrastramos la posición del mouse, activaremos el listener mousemove. La ejecución de ésta solo estará activa en caso de «arrastrar» el mouse con el click presionado. A continuación se muestra la función.

canvas.addEventListener("mousemove", function (e)
{
  if (dragging)
  {
    context.clearRect(0, 0, canvas.width, canvas.height);
    drawSomething(context, pStart, pEnd, color);
    pEnd = windowToCanvasCoord(canvas, e.clientX, e.clientY);
    //draw coordinates position into canvas
    context.font = "10pt Arial";
    context.fillStyle = "rgba(50,50,50,1.0)";
    var pos = windowToCanvasCoord(canvas, e.clientX, e.clientY);
    var str = "(" + pos.x + "," + pos.y + ")";
    context.fillText(str, 10, 10);
  }
});

Lo primero, es «limpiar» lo que se encuentre dibujado previamente dentro del canvas (línea 5). Luego se procede a dibujar la línea empleando la función drawSomething. Esta función recibe como parámetro el contexto para el dibujado, los puntos extremos de la línea, y el color que tendrá. Por último, se actualiza el valor del punto final de la línea para que sea actualizado cada vez que ocurra este evento. El código de las líneas 9-13 se ejecuta solo para mostrar dentro del canvas las posiciones en un formato (x,y).

Así, solo basta con modificar la función drawSomething y sus parámetros para poder dibujar elementos en el canvas de forma interactiva. Ahora como mencioné al principio, el componente Canvas no posee una función de dibujado de un píxel. Si deseamos emplear algún algoritmo de dibujado píxel a píxel sobre un lienzo (por ejemplo el algoritmo de Bresenham) se debe crear una función setPixel. Existen diversas formas de pintar un solo píxel que no genere aliasing o efectos no deseados. Una forma eficiente es empleando createImageData de 1×1 y actualizando el canvas con putImageData. Sin embargo, la función setPixel básica puede ser definida al pintar solo un rectángulo de 1×1:

function setPixel(context, x, y, r, g, b, a)
{
  context.fillStyle = "rgba("+r+","+g+","+b+","+a+")";
  context.fillRect( x, y, 1, 1 );
}

Todo el ejemplo aquí mostrado es empleando JavaScript «puro». Sin embargo, resulta interesante emplear alguna librería que facilite un poco el trabajo como JQuery y sobre todo bajo jCanvas.

CSS3

A este punto, es posible emplear el componente Canvas como un lienzo de dibujado. Sin embargo, es posible utilizar hojas de estilo en cascada (CSS) para mejorar la forma de «mostrar» los componentes presentes en una página HTML de forma muy sencilla. Los archivos CSS pueden ser definidos dentro de la página HTML o en un archivo aparte. Fondos y bordes, valores para las imágenes, animaciones, transformaciones 2D/3D, interfaces de usuario, y más funcionalidades son las que CSS3 nos permite realizar. Para nuestro ejemplo, utilizaremos el tag <style> dentro del <head> de la página. Definiremos el siguiente código para ello:

body
{
 background: #eeeeee;
}
#canvas2D
{
 margin: 10px;
 cursor: pointer;
 border: thin solid rgba(0.8, 0.8, 0.8, 0.6);
 user-select: none;
}
#canvas2D:active
{
 cursor:crosshair;
}

En el código, definimos un color para el fondo de la página completa (línea 3) y definimos atributos sobre el canvas creado. Atributos como cuántos píxeles estará alejado del borde de la ventana principal, el tipo de cursor a emplear dentro del canvas, un color de borde y deshabilitar la «incómoda» opción de selección por parte del usuario. Igualmente, se define que cuando este activo/en uso el componente canvas se muestre el cursor del tipo crosshair (punto de mira).

Es posible agregar muchas más acciones dentro del código CSS3 a ser utilizadas dentro del componente Canvas, pero ese es otro tema ;-). Por ahora, un ejemplo completo de este post lo pueden encontrar en template.html y drawFigures.js.

Resumen: Mezcla HTML5 + JavaScript + CSS3 y obtendrás resultados ¡asombrosos!

Acerca de smittynpro

Escribiendo algunas cosas de computación gráfica
Esta entrada fue publicada en Algoritmos, Código y etiquetada , , , , . Guarda el enlace permanente.

3 respuestas a Dibujar píxeles en un Canvas de HTML5

  1. Pingback: Modelo de Iluminación Phong en 2D | El Código Gráfico

  2. Claudio dijo:

    Una consulta, y como se hace para dejar las lineas dibujadas anteriormente?

    Me gusta

Deja un comentario