Capítulo 7 API Canvas -4

Capítulo 7 API Canvas -4

7.3    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 anchol y altol 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.

function iniciar(){

var elemento=document.getElementById(‘lienzo’);

lienzo=elemento.getContext(‘2d’);

var imagen=new Image();

imagen.src=»http://www.minkbooks.com/content/snow.jpg«; imagen.addEventListener(«load», function(){ lienzo.drawImage(imagen,20,20)

}, false);

}

window.addEventListener(«load», iniciar, false);

Listado 7-22. Trabajando con imágenes.

Comencemos con un simple ejemplo. El código del Listado 7-22 lo único 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.

Presto

Conceptos básicos: En el Listado 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. Para aprender más sobre este tema, vaya a nuestro sitio web y visite los enlaces correspondientes a este capítulo.

function iniciar(){

var elemento=document.getElementByld(‘lienzo’); lienzo=elemento.getContext(‘2d’);

var imagen=new Image();

imagen.src=»http://www.minkbooks.com/content/snow.jpg«; imagen.addEventListener(«load», function(){

lienzo.drawImage(imagen,0,0,elemento.width,elemento.height) }, false);

}

window.addEventListener(«load», iniciar, false);

Listado 7-23. Ajustando la imagen al tamaño del lienzo.

En el Listado 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.

function iniciar(){

var elemento=document.getElementById(‘lienzo’); lienzo=elemento.getContext(‘2d’);

var imagen=new Image();

imagen.src=»http://www.minkbooks.com/content/snow.jpg«; imagen.addEventListener(«load», function(){

lienzo.drawImage(imagen,135,30,50,50,0,0,200,200)

}, false);

}

window.addEventListener(«load», iniciar, false);

Listado 7-24. Extrayendo, cambiando el tamaño y dibujando.

En el Listado 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, mentimos. Existen unos poderosos métodos para procesar imágenes en esta API que además pueden dibujarlas 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. ¿Pero 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 que puede ser usado luego para generar una imagen. La API Canvas ofrece tres 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*4xy)+(x*4)+1 para verde, (ancho*4xy) + (x*4)+2 para azul, y (ancho*4xy) + (xx4)+3 para el valor alpha (transparencia). Veamos esto en práctica:

IMPORTANTE: Debido a restricciones de seguridad, no se puede extraer información del elemento <canvas> luego de que una imagen tomada desde una fuente externa fue dibujada en el lienzo. Solo cuando el documento y la imagen corresponden a la misma fuente (URL) el método getimageData() trabajará adecuadamente. Por este motivo, para probar este ejemplo tendrá que descargar la imagen desde nuestro servidor en www.minkbooks.com/ content/snow.jpg (o usar una imagen propia), y luego subir esta imagen, el archivo HTML y el archivo con el código Javascript a su propio servidor. Si simplemente trata de ejecutar el siguiente ejemplo en su ordenador sin seguir los pasos previos, no funcionará.

function iniciar(){

var elemento=document.getElementByld(‘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.drawlmage(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);

Listado 7-25. Generando un negativo de la imagen.

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. No hay nada inusual en esta parte del código, pero eso es algo que pronto va a cambiar.

IMPORTANTE: Los archivos para este ejemplo deben ser subidos a su propio servidor para trabajar correctamente (incluyendo la imagen snow.jpg que puede descargar desde www.minkbooks.com/content/snowjpg).

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,2 62 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).

Más adelante en este libro veremos algunos ejemplos de cómo usar toDataURL() y cómo puede ayudarnos a integrar esta API con otras.

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>). Para mayor información, visite nuestro sitio web y siga los enlaces correspondientes a este capítulo.

Patrones

Los patrones son simples adiciones que pueden mejorar nuestros trazados. Con esta herramienta podemos agregar textura a nuestras figuras utilizando una 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 fuesen un color.

createPattern(imágen, tipo) El atributo imágen es una referencia a la imagen que vamos a usar como patrón, y tipo configura el patrón por medio de cuatro valores: repeat, repeat-x, repeat-y y no-repeat.

function iniciar(){

var elemento=document.getElementById(‘lienzo’);

lienzo=elemento.getContext(‘2d’);

var imagen=new Image();

imagen.src=»http://www.minkbooks.com/content/bricks.jpg«;

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);

Listado 7-26. Agregando un patrón para nuestro trazado.

Hágalo usted mismo: Experimente con los diferentes valores disponibles para createPattern () y también utilizando otras figuras.

7.4   Animaciones en el lienzo

Las animaciones son creadas por código Javascript convencional. No existen métodos para ayudarnos a animar figuras en el lienzo, y tampoco existe un procedimiento predeterminado para hacerlo. Básicamente, debemos borrar el área del lienzo que queremos animar, dibujar las figuras y repetir el proceso una y otra vez. Una vez que las figuras son dibujadas no se pueden mover. Solo borrando el área y dibujando las figuras nuevamente podemos construir una animación. Por esta razón, en juegos o aplicaciones que requieren grandes cantidades de objetos a ser animados, es mejor usar imágenes en lugar de figuras construidas con trazados complejos (por ejemplo, juegos normalmente utilizan imágenes PNG, que además son útiles por su capacidad de transparencia).

Existen múltiples técnicas para lograr animaciones en el mundo de la programación. Algunas son simples y otras tan complejas como las aplicaciones para las que fueron creadas. Vamos a ver un ejemplo simple utilizando el método clearRect () para limpiar el lienzo y dibujar nuevamente, generando una animación con solo una función, pero siempre recuerde que si su intención es crear elaborados efectos probablemente deberá adquirir un libro de programación avanzada en Javascript antes de siquiera intentarlo.

function iniciar(){

var elemento=document.getElementById(‘lienzo’);

lienzo=elemento.getContext(‘2d’) ;

window.addEventListener(‘mousemove’, animacion, false);

}

function animacion(e){

lienzo.clearRect(0,0,300,500);

var xraton=e.clientX;

var yraton=e.clientY;

var xcentro=220;

var ycentro=150;

var angulo=Math.atan2(xraton-xcentro,yraton-ycentro); var x=xcentro+Math.round(Math.sin(angulo)*10); var y=ycentro+Math.round(Math.cos(angulo)*10);

lienzo.beginPath();

lienzo.arc(xcentro,ycentro,20,0,Math.PI*2, false);

lienzo.moveTo(xcentro+70,150);

lienzo.arc(xcentro+50,150,20,0,Math.PI*2, false);

lienzo.stroke();

lienzo.beginPath();

lienzo.moveTo(x+10,y);

lienzo.arc(x,y,10,0,Math.PI*2, false);

lienzo.moveTo(x+60,y);

lienzo.arc(x+50,y,10,0,Math.PI*2, false);

lienzo.fill();

}

window.addEventListener(«load», iniciar, false);

Listado 7-27. Nuestra primera animación.

El código en el Listado 7-27 mostrará dos ojos en pantalla que miran al puntero del ratón todo el tiempo. Para mover los ojos, debemos actualizar su posición cada vez que el ratón es movido. Por este motivo agregamos una escucha para el evento mousemove en la función iniciar (). Cada vez que el puntero del ratón cambia de posición, el evento es disparado y la función animacion () es llamada.

La función animacion() comienza limpiando el lienzo con la instrucción clearRect (0,0,300,500). Luego, la posición del puntero del ratón es capturada (usando las viejas propiedades clientX y clientY) y la posición del primer ojo es grabada en las variables xcentro e ycentro.

Luego de que estas variables son inicializadas, es tiempo de comenzar con las matemáticas. Usando los valores de la posición del ratón y el centro del ojo izquierdo, calculamos el ángulo de la línea invisible que va desde un punto al otro usando el método predefinido atan2. Este ángulo es usado en el siguiente paso para calcular el punto exacto del centro del iris del ojo izquierdo con la fórmula xcentro                                                                     +

Math.round (Math. sin (angulo) * 10). El número 10 en la fórmula representa la distancia desde el centro del ojo al centro del iris (porque el iris no está en el centro del ojo, está siempre sobre el borde).

Con todos estos valores podemos finalmente comenzar a dibujar nuestros ojos en el lienzo. El primer trazado es para los dos círculos representando los ojos. El primer método arc () para el primer ojo es posicionado en los valores xcentro y ycentro, y el círculo para el segundo ojo es generado 50 pixeles hacia la derecha usando la instrucción arc(xcentro+50, 150, 20, 0, Math.PI*2, false).

La parte animada del gráfico es creada a continuación con el segundo trazado. Este trazado usa las variables x e y con la posición calculada previamente a partir del ángulo. Ambos iris son dibujados como un círculo negro sólido usando fill ().

El proceso será repetido y los valores recalculados cada vez que el evento mousemove es disparado.

Hágalo usted mismo: Copie el código del Listado 7-27 en el archivo Javascript canvas.js y abra el archivo HTML con la plantilla del Listado 7-1 en su navegador.

Publicaciones Similares