Procesando imágenes

API Canvas no sería nada sin la capacidad de procesar imágenes. Pero incluso cuando las imágenes son un elemento tan importante para una aplicación gráfica, solo un método nativo fue provisto para trabajar con ellas.

drawImage()

El método drawImage() es el único a cargo de dibujar una imagen en el lienzo. Sin embargo, este método puede recibir un número de valores que producen diferentes resultados. Estudiemos estas posibilidades:

  • drawImage(imágen, x, y): esta sintaxis es para dibujar una imagen en el lienzo en la posición declarada por x e y. El primer valor es una referencia a la imagen que será dibujada.
  • drawImage(imágen, x, y, ancho, alto): esta sintaxis nos permite escalar la imagen antes de dibujarla en el lienzo, cambiando su tamaño con los valores de los atributos ancho y alto.
  • drawImage(imágen, x1, y1, ancho1, alto1, x2, y2, ancho2, alto2): esta es la sintaxis más compleja. Hay dos valores para cada parámetro. El propósito es cortar partes de la imagen y luego dibujarlas en el lienzo con un tamaño y una posición específica. Los valores x1 e y1 declaran la esquina superior izquierda de la parte de la imagen que será cortada. Los valores ancho1 y alto1 indican el tamaño de esta pieza. El resto de los valores (x2, y2, ancho2 y alto2) declaran el lugar donde la pieza será dibujada en el lienzo y su nuevo tamaño (el cual puede ser igual o diferente al original).

En cada caso, el primer atributo puede ser una referencia a una imagen en el mismo documento generada por métodos como getElementById(), o creando un nuevo objeto imagen usando métodos regulares de Javascript. No es posible usar una URL o cargar un archivo desde una fuente externa directamente con este método.

Ejm 7.22

function iniciar()
{
var elemento=document.getElementById('lienzo'),
lienzo=elemento.getContext('2d');

var imagen=new Image();
imagen.src="https://websarrolladores.com/content/imagen1.png";
imagen.addEventListener("load",function()
{
lienzo.drawImage(imagen,20,20)
},false);
}
window.addEventListener("load",iniciar,false);

El código del ejm 7.22 lo que hace es cargar la imagen y dibujarla en el lienzo. Debido a que el lienzo solo puede dibujar imágenes que ya están completamente cargadas, necesitamos controlar esta situación escuchando al evento load. Agregamos una escucha para este evento y declaramos una función anónima para responder al mismo. El método drawImage() dentro de esta función dibujará la imagen cuando fue completamente cargada.

Conceptos: En el ejm 7.22, dentro del método addEventListener(), usamos una función anónima en lugar de una referencia a una función normal. En casos como éste, cuando la función es pequeña, esta técnica vuelve al código más simple y fácil de entender.

Ejm 7.23

function iniciar()
{
var documento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');

var imagen=new Image();
imagen.src="https://www.websarrolladores.com/content/imagen1.png";
imagen.adEventListener("load",function()
{
lienzo.drawImage(imagen,0,0,elemento.width.elemento.height)
},false);
}
window.addEventListener("load",iniciar,false);

En el ejm 7.23, agregamos dos valores al método drawImage() utilizado previamente para cambiar el tamaño de la imagen. Las propiedades width y height retornan las medidas del lienzo, por lo que la imagen será estirada por este código hasta cubrir el lienzo por completo.

Ejm 7.24

function iniciar()
{
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContent('2d');

var imagen=new Image();
imagen.src="https://www.websarrolladores.com/content/imagen1.png";
imagen.addEventListener("load",function()
{
lienzo.drawImage(imagen,135,30,50,50,0,0,200,200)
},false);
}
window.addEventListener("load",iniciar,false);

En el ejm 7.24 el código presenta la sintaxis más compleja del método drawImage(). Nueve valores fueron provistos para obtener una parte de la imagen original, cambiar su tamaño y luego dibujarla en el lienzo. Tomamos un cuadrado de la imagen original desde  la posición 135,50, con un tamaño de 50,50 pixeles. Este bloque es redimensionado a 200,200 pixeles y finalmente dibujado en el lienzo en la posición 0,0.

Datos de imágenes

Cuando dijimos previamente que drawImage() era el único método disponible para dibujar imágenes en el lienzo, no es completamente cierto, existen unos cuantos poderosos métodos para procesar imágenes con esta API, los cuales además pueden dibujarlos en el lienzo. Debido a que estos métodos no trabajan con imágenes sino con datos, nuestra declaración previa sigue siendo legítima, entonces ¿por qué desearíamos procesar datos en lugar de imágenes?.

Toda imagen puede ser representada por una sucesión de números enteros representando valores rgba (cuatro valores para cada pixel). Un grupo de valores con esta información resultará en un array unidimensional el cual puede ser usado luego para genera una imagen.

La API Canvas ofrece 3 métodos para manipular datos y procesar imágenes de este modo.

  • getImageData(x, y, ancho, alto): este método toma un rectángulo del lienzo del tamaño declarado por sus atributos y lo convierte en datos. Retorna un objeto que puede ser luego accedido por sus propiedades width, height y data.
  • putImageData(datosImagen, x, y): este método convierte a los datos en datosImagen en una imagen y dibuja la imagen en el lienzo en la posición especificada por x e y. Este es el opuesto a getImageData().
  • createImageData(ancho, alto): este método crea datos para representar una imagen vacía. Todos sus pixeles serán de color negro transparente. Puede también recibir datos como atributo (en lugar de los atributos ancho y alto) y utilizar las dimensiones tomadas de los datos provistos para crear la imagen.

La posición de cada valor en el array es calculada con la fórmula (ancho×4×y)+(x×4). Éste será el primer valor del pixel (rojo); para el resto tenemos que agregar 1 al resultado (por  ejemplo, (ancho×4×y)+(x×4)+1 para verde, (ancho×4×y)+(x×4)+2 para azul, y (ancho×4×y)+(x×4)+3 para el valor alpha (transparencia). Veamos esto en práctica.

function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
var imagen=new Image();
imagen.src="snow.jpg";
imagen.addEventListener("load", modificarimagen, false);
}
function modificarimagen(e){
imagen=e.target;
lienzo.drawImage(imagen,0,0);
var info=lienzo.getImageData(0,0,175,262);
var pos;
for(x=0;x<=175;x++){
for(y=0;y<=262;y++){
pos=(info.width*4*y)+(x*4);
info.data[pos]=255-info.data[pos];
info.data[pos+1]=255-info.data[pos+1];
info.data[pos+2]=255-info.data[pos+2];
}
}
lienzo.putImageData(info,0,0);
}
window.addEventListener("load", iniciar, false);

Esta vez tuvimos que crear una nueva función (en lugar de utilizar una función anónima) para procesar la imagen luego de que es cargada. Primero, la función modificarimagen() genera una referencia a la imagen aprovechando la propiedad target usada en capítulos previos. En el siguiente paso, usando esta referencia y el método drawImage(), la imagen es dibujada en el lienzo en la posición 0,0.

La imagen utilizada en nuestro ejemplo tiene un tamaño de 350 pixeles de ancho por 262 pixeles de alto, por lo que usando el método getImageData() con los valores 0,0 para la esquina superior izquierda y 175,262 para el valor horizontal y vertical, estamos extrayendo solo la mitad izquierda de la imagen original. Estos datos son grabados dentro de la variable info.

Una vez que esta información fue recolectada, es momento de manipular cada pixel
para obtener el resultado que queremos (en nuestro ejemplo esto será un negativo de
este trozo de la imagen).

Debido a que cada color es declarado por un valor entre 0 y 255, el valor negativo es obtenido restando el valor real a 255 con la fórmula color=255-color. Para hacerlo con cada pixel de la imagen, debemos crear dos bucles for (uno para las columnas y otro  para las filas) para obtener cada color original y calcular el valor del negativo  correspondiente. El bucle for para los valores x va desde 0 a 175 (el ancho de la parte de la imagen que extrajimos del lienzo) y el for para los valores y va desde 0 a 262 (el tamaño vertical de la imagen y también el tamaño vertical del trozo de imagen que estamos procesando).

Luego de que cada pixel es procesado, la variable info con los datos de la imagen es enviada al lienzo como una imagen usando el método putImageData(). La imagen es ubicada en la misma posición que la original, reemplazando la mitad izquierda de la imagen original por el negativo que acabamos de crear.

El método getImageData() retorna un objeto que puede ser procesado a través de sus propiedades (width, height y data) o puede ser usado íntegro por el método putImageData().

Existe otra manera de extraer datos del lienzo que retorna el contenido en una cadena de texto codificada en base64. Esta cadena puede ser usada luego como fuente para otro lienzo, como fuente de un elemento HTML (por ejemplo, <img>), o incluso ser enviado al servidor o grabado en un archivo. El siguiente es el método incluido con este fin:

toDataURL(tipo): El elemento <canvas> tiene dos propiedades, width y height, y dos
métodos: getContext() y toDataURL(). Este último método retorna datos en el
formato data:url conteniendo una representación del contenido del lienzo en formato
PNG (o el formato de imagen especificado en el atributo tipo).

Conceptos básicos: Los datos del tipo data:url son datos que son presentados en
forma de cadena de texto y pueden ser incluidos en nuestros documentos como
si se tratara de datos tomados de fuentes externas (por ejemplo, la fuente para
imágenes insertadas con la etiqueta <img>).

Patrones

Los patrones son simples adiciones que pueden mejorar nuestros trazados. Con esta herramienta podemos agregar textura a nuestras figuras utilizando un  imagen. El procedimiento es similar a la creación de gradientes, los patrones son creados por el método createPattern() y luego aplicados al trazado como si fuese un color.

  • createPattern(imagen, tipo): el atributo imagen es una rfa a la imagen que utilizaremos como patrón, y tipo configura el patron por medio de 4 valores:
    • repeat
    • repeat-x
    • repeat-y
    • no-repeat

Ejm 7.26

function iniciar()
{
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');

var imagen=new Image();
imagen.src="https://www.websarrolladores.com/content/image1.png";
imagen.addEventListener("load",modificarimagen,false);
}

function modificarimagen(e)
{
imagen=e.target;
var patron=lienzo.createPattern(imagen,'repeat');
lienzo.fillStyle=patron;
lienzo.fillRect(0,0,500,300);
}
window.addEventListener("load",iniciar,false);