7- API Canvas
Preparando el lienzo
Esta API ofrece una de las más poderosas características de HTML5. Permite a
desarrolladores trabajar con un medio visual e interactivo para proveer capacidades de
aplicaciones de escritorio para la web.
Al comienzo del libro hablamos sobre cómo HTML5 está reemplazando previos
complementos o plug-ins, como Flash o Java applets, por ejemplo. Había dos cosas
importantes a considerar para independizar a la web de tecnologías desarrolladas por
terceros: procesamiento de video y aplicaciones gráficas. El elemento <video> y la API para
medios cubren el primer aspecto muy bien, pero no hacen nada acerca de los gráficos. La
API Canvas se hace cargo del aspecto gráfico y lo hace de una forma extremadamente
efectiva. Canvas nos permite dibujar, presentar gráficos en pantalla, animar y procesar
imágenes y texto, y trabaja junto con el resto de la especificación para crear aplicaciones
completas e incluso video juegos en 2 y 3 dimensiones para la web.
El elemento <canvas>
Este elemento genera un espacio rectangular vacío en la página web (lienzo) en el cual
serán mostrados los resultados de ejecutar los métodos provistos por la API. Cuando es
creado, produce sólo un espacio en blanco, como un elemento <div> vacío, pero con un
propósito totalmente diferente.
<!DOCTYPE html>
<html lang="es">
<head>
<title>Canvas API</title>
<script src="canvas.js"></script>
</head>
<body>
<section id="cajalienzo">
<canvas id="lienzo" width="500" height="300">
Su navegador no soporta el elemento canvas
</canvas>
</section>
</body>
</html>
Sintaxis del elemento <canvas>.
Solo es necesario especificar unos pocos atributos para este elemento, como puede
ver en el Listado 7-1. Los atributos width (ancho) y height (alto) declaran el tamaño del
lienzo en pixeles. Estos atributos son necesarios debido a que todo lo que sea dibujado
sobre el elemento tendrá esos valores como referencia. Al atributo id, como en otros
casos, nos facilita el acceso al elemento desde el código Javascript.
Eso es básicamente todo lo que el elemento <canvas> hace. Simplemente crea una
caja vacía en la pantalla. Es solo a través de Javascript y los nuevos métodos y propiedades
introducidos por la API que esta superficie se transforma en algo práctico.
IMPORTANTE: Por razones de compatibilidad, en caso de que Canvas API no se
encuentre disponible en el navegador, el contenido entre las etiquetas <canvas>
será mostrado en pantalla.
getContext()
El método getContext() es el primer método que tenemos que llamar para dejar al
elemento <canvas> listo para trabajar. Genera un contexto de dibujo que será asignado
al lienzo. A través de la referencia que retorna podremos aplicar el resto de la API.
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
}
window.addEventListener("load", iniciar, false);
Creando el contexto de dibujo para el lienzo.
En el Listado 7-2, una referencia al elemento <canvas> fue almacenada en la variable
elemento y el contexto de dibujo fue creado por getContext('2d'). El método puede
tomar dos valores: 2d y 3d. Esto es, por supuesto, para ambientes de 2 dimensiones y 3
dimensiones. Por el momento solo el contexto 2d está disponible, pero serios esfuerzos
están siendo volcados en el desarrollo de una API estable en 3 dimensiones.
El contexto de dibujo del lienzo será una grilla de pixeles listados en filas y columnas de
arriba a abajo e izquierda a derecha, con su origen (el pixel 0,0) ubicado en la esquina
superior izquierda del lienzo.
Hágalo usted mismo: Copie el documento HTML del Listado 7-1 dentro de un
nuevo archivo vacío. También necesitará crear un archivo llamado canvas.js y
copiar el código del Listado 7-2 en su interior. Cada código presentado en este
capítulo es independiente y reemplaza al anterior.
Conceptos básicos: Cuando una variable es declarada dentro de una función sin
la palabra clave var, será global. Esto significa que la variable será accesible
desde otras partes del código, incluido el interior de funciones. En el código del
Listado 7-2, declaramos la variable lienzo como global para poder tener
siempre acceso al contexto del lienzo.
Dibujando en el lienzo
Luego de que el elemento <canvas> y su contexto han sido inicializados podemos
finalmente comenzar a crear y manipular gráficos. La lista de herramientas provista por la
API para este propósito es extensa, desde la creación de simples formas y métodos de dibujo
hasta texto, sombras o transformaciones complejas. Vamos a estudiarlas una por una.
Dibujando rectángulos
Normalmente el desarrollador deberá preparar la figura a ser dibujada en el contexto
(como veremos pronto), pero existen algunos métodos que nos permiten dibujar
directamente en el lienzo, sin preparación previa. Estos métodos son específicos para
formas rectangulares y son los únicos que generan una forma primitiva (para obtener
otras formas tendremos que combinar otras técnicas de dibujo y trazados complejos). Los
métodos disponibles son los siguientes:
fillRect(x, y, ancho, alto) Este método dibuja un rectángulo sólido. La esquina superior
izquierda será ubicada en la posición especificada por los atributos x e y. Los atributos
ancho y alto declaran el tamaño.
strokeRect(x, y, ancho, alto) Similar al método anterior, éste dibujará un rectángulo vacío
(solo su contorno).
clearRect(x, y, ancho, alto) Esta método es usado para substraer pixeles del área
especificada por sus atributos. Es un borrador rectangular.
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.strokeRect(100,100,120,120);
lienzo.fillRect(110,110,100,100);
lienzo.clearRect(120,120,80,80);
}
window.addEventListener("load", iniciar, false);
Dibujando rectángulos.
Esta es la misma función del Listado 7-2, pero incorpora los nuevos métodos
estudiados para dibujar una figura en el lienzo. Como puede ver, el contexto fue asignado
a la variable global lienzo, y ahora esta variable es usada para referenciar el contexto en
cada método.
El primer método usado en la función, strokeRect(100,100,120,120), dibuja un
rectángulo vacío con la esquina superior izquierda en la posición 100,100 y un tamaño de 120
pixeles (este es un cuadrado de 120 pixeles). El segundo método, fillRect(110,110,
100,100), dibuja un rectángulo sólido, esta vez comenzando desde la posición 110,110 del
lienzo. Y finalmente, con el último método, clearRect(120,120,80,80), un recuadro de 80
pixeles es substraído del centro de la figura.
La Figura 7-1 es solo una representación de lo que verá en la pantalla luego de ejecutar
el código del Listado 7-3. El elemento <canvas> es como una grilla, con su origen en la
esquina superior izquierda y el tamaño especificado en sus atributos. Los rectángulos son
dibujados en el lienzo en la posición declarada por los atributos x e y, y uno sobre el otro
de acuerdo al orden en el código (el primero en aparecer en el código será dibujado
primero, el segundo será dibujado por encima del anterior, y así sucesivamente). Existe un
método para personalizar cómo las figuras son dibujadas en pantalla, pero lo veremos más
adelante.
Colores
Hasta el momento hemos usado el color otorgado por defecto, negro sólido, pero
podemos especificar el color que queremos aplicar mediante sintaxis CSS utilizando las
siguientes propiedades:
strokeStyle Esta propiedad declara el color para el contorno de la figura.
fillStyle Esta propiedad declara el color para el interior de la figura.
globalAlpha Esta propiedad no es para definir color sino transparencia. Especifica la
transparencia para todas las figuras dibujadas en el lienzo.
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.fillStyle="#000099";
lienzo.strokeStyle="#990000";
lienzo.strokeRect(100,100,120,120);
lienzo.fillRect(110,110,100,100);
lienzo.clearRect(120,120,80,80);
}
window.addEventListener("load", iniciar, false);
Aplicando color.
Los colores en el Listado 7-4 fueron declarados usando números hexadecimales.
Podemos también usar funciones como rgb() o incluso especificar transparencia para la
figura aprovechando la función rgba(). Estos métodos deben ser siempre escritos entre
comillas (por ejemplo, strokeStyle="rgba(255,165,0,1)").
Cuando un nuevo color es especificado se vuelve el color por defecto para el resto de
los dibujos, a menos que volvamos a cambiarlo más adelante.
A pesar de que el uso de la función rgba() es posible, existe otra propiedad más
específica para declarar el nivel de transparencia: globalAlpha. Su sintaxis es
globalAlpha=valor, donde valor es un número entre 0.0 (totalmente opaco) y 1.0
(totalmente transparente).
Gradientes
Gradientes son una herramienta esencial en cualquier programa de dibujo estos días, y
esta API no es la excepción. Así como en CSS3, los gradientes en la API Canvas pueden ser
lineales o radiales, y pueden incluir puntos de terminación para combinar colores.
createLinearGradient(x1, y1, x2, y2) Este método crea un objeto que luego será usado
para aplicar un gradiente lineal al lienzo.
createRadialGradient(x1, y1, r1, x2, y2, r2) Este método crea un objeto que luego será
usado para aplicar un gradiente circular o radial al lienzo usando dos círculos. Los
valores representan la posición del centro de cada círculo y sus radios.
addColorStop(posición, color) Este método especifica los colores a ser usados por el
gradiente. El atributo posición es un valor entre 0.0 y 1.0 que determina dónde la
degradación comenzará para ese color en particular.
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
var gradiente=lienzo.createLinearGradient(0,0,10,100);
gradiente.addColorStop(0.5, '#0000FF');
gradiente.addColorStop(1, '#000000');
lienzo.fillStyle=gradiente;
lienzo.fillRect(10,10,100,100);
lienzo.fillRect(150,10,200,100);
}
window.addEventListener("load", iniciar, false);
Aplicando un gradiente lineal al lienzo.
En el Listado 7-5, creamos el objeto gradiente desde la posición 0,0 a la 10,100,
otorgando una leve inclinación hacia la izquierda. Los colores fueron declarados por el
método addColorStop() y el gradiente logrado fue finalmente aplicado a la propiedad
fillStyle, como un color regular.
Note que las posiciones del gradiente son correspondientes al lienzo, no a las figuras
que queremos afectar. El resultado es que si movemos los rectángulos dibujados al final
de la función hacia una nueva posición, el gradiente para esos triángulos cambiará.
Hágalo usted mismo: El gradiente radial es similar al de CSS3. Intente reemplazar
el gradiente lineal en el código del Listado 7-5 por un gradiente radial usando una
expresión como createRadialGradient(0,0,30,0,0,300). También puede
experimentar moviendo los rectángulos de posición para ver cómo el gradiente
es aplicado a estas figuras.
Creando trazados
Los métodos estudiados hasta el momento dibujan directamente en el lienzo, pero ese no es
siempre el caso. Normalmente tendremos que procesar figuras en segundo plano y una vez
que el trabajo esté hecho enviar el resultado al contexto para que sea dibujado. Con este
propósito, API Canvas introduce varios métodos con los que podremos generar trazados.
Un trazado es como un mapa a ser seguido por el lápiz. Una vez declarado, el trazado
será enviado al contexto y dibujado de forma permanente en el lienzo. El trazado puede
incluir diferentes tipos de líneas, como líneas rectas, arcos, rectángulos, entre otros, para
crear figuras complejas.
Existen dos métodos para comenzar y cerrar el trazado:
beginPath() Este método comienza la descripción de una nueva figura. Es llamado en
primer lugar, antes de comenzar a crear el trazado.
closePath() Este método cierra el trazado generando una línea recta desde el último
punto hasta el punto de origen. Puede ser ignorado cuando utilizamos el método
fill() para dibujar el trazado en el lienzo.
También contamos con tres métodos para dibujar el trazado en el lienzo:
stroke() Este método dibuja el trazado como una figura vacía (solo el contorno).
fill() Este método dibuja el trazado como una figura sólida. Cuando usamos este método
no necesitamos cerrar el trazado con closePath(), el trazado es automáticamente
cerrado con una línea recta trazada desde el punto final hasta el origen.
clip() Este método declara una nueva área de corte para el contexto. Cuando el contexto
es inicializado, el área de corte es el área completa ocupada por el lienzo. El método
clip() cambiará el área de corte a una nueva forma creando de este modo una
máscara. Todo lo que caiga fuera de esa máscara no será dibujado.
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.beginPath();
// aquí va el trazado
lienzo.stroke();
}
window.addEventListener("load", iniciar, false);
Reglas básicas para trabajar con trazados.
El código del Listado 7-6 no crea absolutamente nada, solo incorpora los métodos
necesarios para iniciar y luego dibujar el trazado en pantalla. Para crear el trazado y la
figura real que será enviada al contexto y dibujada en el lienzo, contamos con varios
métodos disponibles:
moveTo(x, y) Este método mueve el lápiz a una posición específica para continuar con el
trazado. Nos permite comenzar o continuar el trazado desde diferentes puntos,
evitando líneas continuas.
lineTo(x, y) Este método genera una línea recta desde la posición actual del lápiz hasta la
nueva declarada por los atributos x e y.
rect(x, y, ancho, alto) Este método genera un rectángulo. A diferencia de los métodos
estudiados anteriormente, éste generará un rectángulo que formará parte del trazado
(no directamente dibujado en el lienzo). Los atributos tienen la misma función.
arc(x, y, radio, ángulo inicio, ángulo final, dirección) Este método genera un arco o un
círculo en la posición x e y, con un radio y desde un ángulo declarado por sus atributos.
El último valor es un valor booleano (falso o verdadero) para indicar la dirección a favor o
en contra de las agujas del reloj.
quadraticCurveTo(cpx, cpy, x, y) Este método genera una curva Bézier cuadrática desde la
posición actual del lápiz hasta la posición declarada por los atributos x e y. Los
atributos cpx y cpy indican el punto que dará forma a la curva.
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) Este método es similar al anterior pero agrega
dos atributos más para generar una curva Bézier cúbica. Ahora disponemos de dos
puntos para moldear la curva, declarados por los atributos cp1x, cp1y, cp2x y cp2y.
Veamos un trazado sencillo para entender cómo funcionan:
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.beginPath();
lienzo.moveTo(100,100);
lienzo.lineTo(200,200);
lienzo.lineTo(100,200);
lienzo.stroke();
}
window.addEventListener("load", iniciar, false);
Nuestro primer trazado.
Recomendamos siempre establecer la posición inicial del lápiz inmediatamente
después de iniciar el trazado con beginPath(). En el código del Listado 7-7 el primer
paso fue mover el lápiz a la posición 100,100 y luego generar una línea desde ese punto
hasta el punto 200,200. Ahora la posición del lápiz es 200,200 y la siguiente línea será
generada desde aquí hasta el punto 100,200. Finalmente, el trazado es dibujado en el
lienzo como una forma vacía con el método stroke().
Si prueba el código en su navegador, verá un triángulo abierto en la pantalla. Este
triángulo puede ser cerrado o incluso rellenado y transformado en una figura sólida
usando diferentes métodos, como vemos en el siguiente ejemplo:
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.beginPath();
lienzo.moveTo(100,100);
lienzo.lineTo(200,200);
lienzo.lineTo(100,200);
lienzo.closePath();
lienzo.stroke();
}
window.addEventListener("load", iniciar, false);
Completando el triángulo.
El método closePath() simplemente agrega una línea recta al trazado, desde el
último al primer punto, cerrando la figura.
Usando el método stroke() al final de nuestro trazado dibujamos un triángulo vacío
en el lienzo. Para lograr una figura sólida, este método debe ser reemplazado por fill():
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.beginPath();
lienzo.moveTo(100,100);
lienzo.lineTo(200,200);
lienzo.lineTo(100,200);
lienzo.fill();
}
window.addEventListener("load", iniciar, false);
Triángulo sólido.
Ahora la figura en la pantalla será un triángulo sólido. El método fill() cierra el
trazado automáticamente, por lo que ya no tenemos que usar closePath() para
lograrlo.
Uno de los métodos mencionados anteriormente para dibujar un trazado en el lienzo
fue clip(). Este método en realidad no dibuja nada, lo que hace es crear una máscara
con la forma del trazado para seleccionar qué será dibujado y qué no. Todo lo que caiga
fuera de la máscara no se dibujará en el lienzo. Veamos un ejemplo:
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.beginPath();
lienzo.moveTo(100,100);
lienzo.lineTo(200,200);
lienzo.lineTo(100,200);
lienzo.clip();
lienzo.beginPath();
for(f=0; f<300; f=f+10){
lienzo.moveTo(0,f);
lienzo.lineTo(500,f);
}
lienzo.stroke();
}
window.addEventListener("load", iniciar, false);
Usando el triángulo anterior como una máscara.
Para mostrar exactamente cómo funciona el método clip(), en el Listado 7-10
utilizamos un bucle for para crear líneas horizontales cada 10 pixeles. Estas líneas van
desde el lado izquierdo al lado derecho del lienzo, pero solo las partes de las líneas que
caen dentro de la máscara (el triángulo) serán dibujadas.
Ahora que ya sabemos cómo dibujar trazados, es tiempo de ver el resto de las
alternativas con las que contamos para crearlos. Hasta el momento hemos estudiado
cómo generar líneas rectas y formas rectangulares. Para figuras circulares, la API provee
tres métodos: arc(), quadraticCurveTo() y bezierCurveTo(). El primero es
relativamente sencillo y puede generar círculos parciales o completos, como mostramos
en el siguiente ejemplo:
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.beginPath();
lienzo.arc(100,100,50,0,Math.PI*2, false);
lienzo.stroke();
}
window.addEventListener("load", iniciar, false);
Círculos con arc().
Lo primero que seguramente notará en el método arc() en nuestro ejemplo es el uso
del valor PI. Este método usa radianes en lugar de grados para los valores del ángulo. En
radianes, el valor PI representa 180 grados, por lo que la formula PI*2 multiplica PI por
2 obteniendo un ángulo de 360 grados.
El código en el Listado 7-11 genera un arco con centro en el punto 100,100 y un radio
de 50 pixeles, comenzando a 0 grados y terminando a Math.PI*2 grados, lo que
representa un círculo completo. El uso de la propiedad PI del objeto Math nos permite
obtener el valor preciso de PI.
Si necesitamos calcular el valor en radianes de cualquier ángulo en grados usamos la
fórmula: Math.PI / 180 × grados, como en el próximo ejemplo:
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.beginPath();
var radianes=Math.PI/180*45;
lienzo.arc(100,100,50,0,radianes, false);
lienzo.stroke();
}
window.addEventListener("load", iniciar, false);
Un arco de 45 grados.
Con el código del Listado 7-12 obtenemos un arco que cubre 45 grados de un círculo.
Intente cambiar el valor de la dirección a true (verdadero). En este caso, el arco será
generado desde 0 grados a 315, creando un círculo abierto.
Una cosa importante a considerar es que si continuamos construyendo el trazado
luego del arco, el actual punto de comienzo será el final del arco. Si no deseamos que esto
pase tendremos que usar el método moveTo() para cambiar la posición del lápiz, como
hicimos anteriormente. Sin embargo, si la próxima figura es otro arco (por ejemplo, un
círculo completo) siempre recuerde que el método moveTo() mueve el lápiz virtual hacia
el punto en el cual el círculo comenzará a ser dibujado, no el centro del círculo. Digamos
que el centro del círculo que queremos dibujar se encuentra en el punto 300,150 y su
radio es de 50. El método moveTo() debería mover el lápiz a la posición 350,150 para
comenzar a dibujar el círculo.
Además de arc(), existen dos métodos más para dibujar curvas, en este caso curvas
complejas. El método quadraticCurveTo() genera una curva Bézier cuadrática, y el
método bezierCurveTo() es para curvas Bézier cúbicas. La diferencia entre estos dos
métodos es que el primero cuenta con un solo punto de control y el segundo con dos,
creando de este modo diferentes tipos de curvas.
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.beginPath();
lienzo.moveTo(50,50);
lienzo.quadraticCurveTo(100,125, 50,200);
lienzo.moveTo(250,50);
lienzo.bezierCurveTo(200,125, 300,125, 250,200);
lienzo.stroke();
}
window.addEventListener("load", iniciar, false);
Curvas complejas.
Para la curva cuadrática movimos el lápiz virtual a la posición 50,50 y finalizamos la
curva en el punto 50,200. El punto de control para esta curva fue ubicado en la posición
100,125.
La curva generada por el método bezierCurveTo() es un poco más compleja. Hay
dos puntos de control para esta curva, el primero en la posición 200,125 y el segundo en
la posición 300,125.
Los valores en la Figura 7-2 indican los puntos de control para las curvas. Moviendo
estos puntos cambiamos la forma de la curva.
Hágalo usted mismo: Puede agregar tantas curvas como necesite para construir su
figura. Intente cambiar los valores de los puntos de control en el Listado 7-13 para ver
cómo afectan a las curvas. Construya figuras más complejas combinando curvas y
líneas para entender cómo la construcción del trazado es realizada.
Estilos de línea
Hasta esta parte del capítulo hemos usado siempre los mismos estilos de líneas. El ancho,
la terminación y otros aspectos de la línea pueden ser modificados para obtener
exactamente el tipo de línea que necesitamos para nuestros dibujos.
Existen cuatro propiedades específicas para este propósito:
lineWidth Esta propiedad determina el grosor de la línea. Por defecto el valor es 1.0
unidades.
lineCap Esta propiedad determina la forma de la terminación de la línea. Puede recibir uno
de estos tres valores: butt, round y square.
lineJoin Esta propiedad determina la forma de la conexión entre dos líneas. Los valores
posibles son: round, bevel y miter.
miterLimit Trabajando en conjunto con lineJoin, esta propiedad determina cuánto la
conexión de dos líneas será extendida cuando la propiedad lineJoin es declarada
con el valor miter.
Las propiedades afectarán el trazado completo. Cada vez que tenemos que cambiar las
características de las líneas debemos crear un nuevo trazado.
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.beginPath();
lienzo.arc(200,150,50,0,Math.PI*2, false);
lienzo.stroke();
lienzo.lineWidth=10;
lienzo.lineCap="round";
lienzo.beginPath();
lienzo.moveTo(230,150);
lienzo.arc(200,150,30,0,Math.PI, false);
lienzo.stroke();
lienzo.lineWidth=5;
lienzo.lineJoin="miter";
lienzo.beginPath();
lienzo.moveTo(195,135);
lienzo.lineTo(215,155);
lienzo.lineTo(195,155);
lienzo.stroke();
}
window.addEventListener("load", iniciar, false);
Propiedades para probar diferentes estilos de línea.
Comenzamos el dibujo en el código del Listado 7-14 creando un trazado para un círculo
completo con propiedades por defecto. Luego, usando lineWith, cambiamos el ancho de la
línea a 10 y definimos la propiedad lineCap como round. Esto hará que el siguiente trazado
sea más grueso y con terminaciones redondeadas. Para crear un trazado con estas
características, primero movimos el lápiz a la posición 230,150 y luego generamos un
semicírculo. Los extremos redondeados nos ayudarán a simular una boca sonriente.
Finalmente, agregamos un trazado creado con dos líneas para lograr una forma similar
a una nariz. Las líneas para este trazado fueron configuradas con un ancho de 5 y serán
unidas de acuerdo a la propiedad lineJoin y su valor miter. Esta propiedad hará a la
nariz lucir puntiaguda, expandiendo las puntas de las líneas en la unión hasta que ambas
alcancen un punto en común.
Hágalo usted mismo: Experimente con las líneas para la nariz modificando la
propiedad miterLimit (por ejemplo, con la instrucción miterLimit=2). Cambie
el valor de la propiedad lineJoin a round o bevel. También puede modificar la
forma de la boca probando diferentes valores para la propiedad lineCap.
Texto
Escribir texto en el lienzo es tan simple como definir unas pocas propiedades y llamar al
método apropiado. Tres propiedades son ofrecidas para configurar texto:
font Esta propiedad tiene una sintaxis similar a la propiedad font de CSS, y acepta los
mismos valores.
textAlign Esta propiedad alinea el texto. Existen varios valores posibles: start
(comienzo), end (final), left (izquierda), right (derecha) y center (centro).
textBaseline Esta propiedad es para alineamiento vertical. Establece diferentes posiciones
para el texto (incluyendo texto Unicode). Los posibles valores son: top, hanging,
middle, alphabetic, ideographic y bottom.
Dos métodos están disponibles para dibujar texto en el lienzo:
strokeText(texto, x, y) Del mismo modo que el método stroke() para el trazado, este
método dibujará el texto especificado en la posición x,y como una figura vacía (solo
los contornos). Puede también incluir un cuarto valor para declarar el tamaño máximo.
Si el texto es más extenso que este último valor, será encogido para caber dentro del
espacio establecido.
fillText(texto, x, y) Este método es similar al método anterior excepto que esta vez el
texto dibujado será sólido (igual que la función para el trazado).
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.font="bold 24px verdana, sans-serif";
lienzo.textAlign="start";
lienzo.fillText("Mi mensaje", 100,100);
}
window.addEventListener("load", iniciar, false);
Dibujando texto.
Como podemos ver en el Listado 7-15, la propiedad font puede tomar varios valores a
la vez, usando exactamente la misma sintaxis que CSS. La propiedad textAling hace que
el texto sea dibujado desde la posición 100,100 (si el valor de esta propiedad fuera end,
por ejemplo, el texto terminaría en la posición 100,100). Finalmente, el método
fillText dibuja un texto sólido en el lienzo.
Además de los previamente mencionados, la API provee otro método importante para
trabajar con texto:
measureText() Este método retorna información sobre el tamaño de un texto específico.
Puede ser útil para combinar texto con otras formas en el lienzo y calcular posiciones o
incluso colisiones en animaciones.
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.font="bold 24px verdana, sans-serif";
lienzo.textAlign="start";
lienzo.textBaseline="bottom";
lienzo.fillText("Mi mensaje", 100,124);
var tamano=lienzo.measureText("Mi mensaje");
lienzo.strokeRect(100,100,tamano.width,24);
}
window.addEventListener("load", iniciar, false);
Midiendo texto.
En este ejemplo comenzamos con el mismo código del Listado 7-15, pero agregamos
un alineamiento vertical. La propiedad textBaseline fue establecida como bottom
(inferior), lo que significa que la base o parte inferior del texto estará ubicada en la
posición 124. Esto nos ayudará a conocer la posición vertical exacta del texto en el lienzo.
Usando el método measureText() y la propiedad width (ancho) obtenemos el
tamaño horizontal del texto. Con esta medida estamos listos para dibujar un rectángulo
que rodeará al texto.
Hágalo usted mismo: Utilizando el código del Listado 7-16, pruebe diferentes valores
para las propiedades textAlign y textBaseline. Use el rectángulo como
referencia para comprobar cómo estas propiedades trabajan. Escriba un texto
diferente para ver cómo el rectángulo se adapta automáticamente a su tamaño.
Sombras
Por supuesto, sombras son también una parte importante de Canvas API. Podemos
generar sombras para cada trazado e incluso textos. La API provee cuatro propiedades
para hacerlo:
shadowColor Esta propiedad declara el color de la sombra usando sintaxis CSS.
shadowOffsetX Esta propiedad recibe un número para determinar qué tan lejos la sombra
estará ubicada del objeto (dirección horizontal).
shadowOffsetY Esta propiedad recibe un número para determinar qué tan lejos la sombra
estará ubicada del objeto (dirección vertical).
shadowBlur Esta propiedad produce un efecto de difuminación para la sombra.
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.shadowColor="rgba(0,0,0,0.5)";
lienzo.shadowOffsetX=4;
lienzo.shadowOffsetY=4;
lienzo.shadowBlur=5;
lienzo.font="bold 50px verdana, sans-serif";
lienzo.fillText("Mi mensaje ", 100,100);
}
window.addEventListener("load", iniciar, false);
Aplicando sombras.
La sombra creada en el Listado 7-17 usa la función rgba() para obtener un color
negro semitransparente. Es desplazada 4 pixeles del objeto y tiene un valor de
difuminación de 5.
Hágalo usted mismo: Aplique sombras a otra figura en lugar de texto. Por
ejemplo, pruebe generar sombras para figuras vacías y sólidas, usando
rectángulos o círculos.
Transformaciones
LA API Canvas ofrece operaciones complejas que es posible aplicar sobre el lienzo para afectar
los gráficos que luego son dibujados en él. Estas operaciones son realizadas utilizando cinco
métodos de transformación diferentes, cada uno para un propósito específico.
translate(x, y) Este método de transformación es usado para mover el origen del lienzo.
Cada lienzo comienza en el punto 0,0 localizado en la esquina superior izquierda, y los
valores se incrementan en cualquier dirección dentro del lienzo. Valores negativos
caen fuera del lienzo. A veces es bueno poder usar valores negativos para crear figuras
complejas. El método translate() nos permite mover el punto 0,0 a una posición
específica para usar el origen como referencia para nuestros dibujos o para aplicar
otras transformaciones.
rotate(ángulo) Este método de transformación rotará el lienzo alrededor del origen tantos
ángulos como sean especificados.
scale(x, y) Este método de transformación incrementa o disminuye las unidades de la
grilla para reducir o ampliar todo lo que esté dibujado en el lienzo. La escala puede ser
cambiada independientemente para el valor horizontal o vertical usando los atributos
x e y. Los valores pueden ser negativos, produciendo un efecto de espejo. Por defecto
los valores son iguales a 1.0.
transform(m1, m2, m3, m4, dx, dy) El lienzo contiene una matriz de valores que
especifican sus propiedades. El método transform() aplica una nueva matriz sobre
la actual para modificar el lienzo.
setTransform(m1, m2, m3, m4, dx, dy) Este método reinicializa la actual matriz de
transformación y establece una nueva desde los valores provistos en sus atributos.
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.font="bold 20px verdana, sans-serif";
lienzo.fillText("PRUEBA",50,20);
lienzo.translate(50,70);
lienzo.rotate(Math.PI/180*45);
lienzo.fillText("PRUEBA",0,0);
lienzo.rotate(-Math.PI/180*45);
lienzo.translate(0,100);
lienzo.scale(2,2);
lienzo.fillText("PRUEBA",0,0);
}
window.addEventListener("load", iniciar, false);
Moviendo, rotando y escalando.
No hay mejor forma de entender cómo funcionan las transformaciones que usarlas en
nuestro código. En el Listado 7-18, aplicamos los métodos translate(), rotate() y
scale() al mismo texto. Primero dibujamos un texto en el lienzo con la configuración por
defecto. El texto aparecerá en la posición 50,20 con un tamaño de 20 pixeles. Luego de
esto, usando translate(), el origen del lienzo es movido a la posición 50,70 y el lienzo
completo es rotado 45 grados con el método rotate(). Otro texto es dibujado en el nuevo
origen, con una inclinación de 45 grados. Las transformaciones aplicadas se vuelven los
valores por defecto, por lo tanto antes de aplicar el siguiente método scale() rotamos el
lienzo 45 grados negativos para ubicarlo en su posición original. Realizamos una
transformación más moviendo el origen otros 100 pixeles hacia abajo. Finalmente, la escala
del lienzo es duplicada y un nuevo texto es dibujado al doble del tamaño de los anteriores.
Cada transformación es acumulativa. Si realizamos dos transformaciones usando
scale(), por ejemplo, el segundo método realizará el escalado considerando el estado
actual del lienzo. Una orden scale(2,2) luego de otra scale(2,2) cuadruplicará la
escala del lienzo. Y para los métodos de transformación de la matriz, esta no es una
excepción. Es por esto que contamos con dos métodos para realizar esta clase de
transformaciones: transform() y setTransform().
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.transform(3,0,0,1,0,0);
lienzo.font="bold 20px verdana, sans-serif";
lienzo.fillText("PRUEBA",20,20);
lienzo.transform(1,0,0,10,0,0);
lienzo.font="bold 20px verdana, sans-serif";
lienzo.fillText("PRUEBA",100,20);
}
window.addEventListener("load", iniciar, false);
Transformaciones acumulativas sobre la matriz.
Al igual que en el código anterior, en el Listado 7-19 aplicamos varios métodos de
transformación sobre el mismo texto para comparar efectos. Los valores por defecto de la
matriz del lienzo son 1,0,0,1,0,0. Cambiando el primer valor a 3, en la primera
transformación de nuestro ejemplo arriba, estiramos el lienzo horizontalmente. El texto
dibujado luego de esta transformación será más ancho que en condiciones por defecto.
Con la siguiente transformación, el lienzo fue estirado verticalmente cambiando el
cuarto valor a 10 y preservando los anteriores.
Un detalle importante a recordar es que las transformaciones son aplicadas sobre la
matriz declarada en previas transformaciones, por lo que el segundo texto mostrado por
el código del Listado 7-19 será igual de ancho que el anterior (es estirado horizontal y
verticalmente). Para reinicializar la matriz y declarar nuevos valores de transformación,
podemos usar el método setTransform().
Hágalo usted mismo: Reemplace el último método transform() en el ejemplo
por setTransform() y compruebe los resultados. Usando solo un texto, cambie
cada valor en el método transform() para conocer la clase de transformación
realizada en el lienzo por cada uno de ellos.
Restaurando el estado
La acumulación de transformaciones hace realmente difícil volver a anteriores estados. En
el código del Listado 7-18, por ejemplo, tuvimos que recordar el valor de rotación usado
previamente para poder realizar una nueva rotación y volver el lienzo al estado original.
Considerando situaciones como ésta, Canvas API provee dos métodos para grabar y
recuperar el estado del lienzo.
save() Este método graba el estado del lienzo, incluyendo transformaciones ya aplicadas,
valores de propiedades de estilo y la actual máscara (el área creada por el método
clip(), si existe).
restore() Este método recupera el último estado grabado.
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.save();
lienzo.translate(50,70);
lienzo.font="bold 20px verdana, sans-serif";
lienzo.fillText("PRUEBA1",0,30);
lienzo.restore();
lienzo.fillText("PRUEBA2",0,30);
}
window.addEventListener("load", iniciar, false);
Grabando el estado del lienzo.
Si ejecuta el código del Listado 7-20 en su navegador, verá el texto “PRUEBA1” en
grandes letras al centro del lienzo, y el texto “PRUEBA2” en letras pequeñas, cercano al
origen. Lo que hicimos fue grabar el estado por defecto del lienzo y luego establecer una
nueva posición para el origen y estilos para el texto. El primer texto es dibujado con esta
configuración, pero antes de dibujar el segundo texto el estado original es restaurado, por
lo que este texto es mostrado con los estilos por defecto, no con los declarados para el
primero.
No importa cuántas transformaciones hayamos realizado, luego de llamar al método
restore() la configuración del lienzo será retornada exactamente a su estado anterior
(el último grabado).
globalCompositeOperation
Cuando hablamos de trazados dijimos que existe una propiedad para determinar cómo
una figura es posicionada y combinada con figuras dibujadas previamente en el lienzo. La
propiedad es globalCompositeOperation y su valor por defecto es source-over, lo
que significa que la nueva figura será dibujada sobre las que ya existen en el lienzo. La
propiedad ofrece 11 valores más:
source-in Solo la parte de la nueva figura que se sobrepone a las figuras previas es
dibujada. El resto de la figura, e incluso el resto de las figuras previas, se vuelven
transparentes.
source-out Solo la parte de la nueva figura que no se sobrepone a las figuras previas es
dibujada. El resto de la figura, e incluso el resto de las figuras previas, se vuelven
transparentes.
source-atop Solo la parte de la nueva figura que se superpone con las figuras previas es
dibujada. Las figuras previas son preservadas, pero el resto de la nueva figura se vuelve
transparente.
lighter Ambas figuras son dibujadas (nueva y vieja), pero el color de las partes que se
superponen es obtenido adicionando los valores de los colores de cada figura.
xor Ambas figuras son dibujadas (nueva y vieja), pero las partes que se superponen se
vuelven transparentes.
destination-over Este es el opuesto del valor por defecto. Las nuevas figuras son dibujadas
detrás de las viejas que ya se encuentran en el lienzo.
destination-in Las partes de las figuras existentes en el lienzo que se superponen con la
nueva figura son preservadas. El resto, incluyendo la nueva figura, se vuelven
transparentes.
destination-out Las partes de las figuras existentes en el lienzo que no se superponen con
la nueva figura son preservadas. El resto, incluyendo la nueva figura, se vuelven
transparentes.
destination-atop Las figuras existentes y la nueva son preservadas solo en la parte en la
que se superponen.
darker Ambas figuras son dibujadas, pero el color de las partes que se superponen es
determinado substrayendo los valores de los colores de cada figura.
copy Solo la nueva figura es dibujada. Las ya existentes se vuelven transparentes.
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.fillStyle="#990000";
lienzo.fillRect(100,100,300,100);
lienzo.globalCompositeOperation="destination-atop";
lienzo.fillStyle="#AAAAFF";
lienzo.font="bold 80px verdana, sans-serif";
lienzo.textAlign="center";
lienzo.textBaseline="middle";
lienzo.fillText("PRUEBA",250,110);
}
window.addEventListener("load", iniciar, false);
Probando la propiedad globalCompositeOperation.
Solo representaciones visuales de cada posible valor para la propiedad
globalCompositeOperation le ayudarán a comprender cómo funcionan. Con este
propósito, preparamos el código del Listado 7-21. Cuando este código es ejecutado, un
rectángulo rojo es dibujado en el medio del lienzo, pero gracias al valor destinationatop
solo la parte del rectángulo que se superpone con el texto es dibujada.
Hágalo usted mismo: Reemplace el valor destination-atop con cualquiera de
los demás valores posibles para esta propiedad y compruebe el resultado en su
navegador. Pruebe el código en distintos navegadores.
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.
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
var imagen=new Image();
imagen.src="https://www.minkbooks.com/content/snow.jpg";
imagen.addEventListener("load", function(){
lienzo.drawImage(imagen,20,20)
}, false);
}
window.addEventListener("load", iniciar, false);
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.
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.getElementById('lienzo');
lienzo=elemento.getContext('2d');
var imagen=new Image();
imagen.src="https://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);
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="https://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);
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×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:
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.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);
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/snow.jpg).
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).
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="https://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);
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.
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);
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.
Procesando video en el lienzo
Al igual que para animaciones, no hay ningún método especial para mostrar video en el
elemento <canvas>. La única manera de hacerlo es tomando cada cuadro del video
desde el elemento <video> y dibujarlo como una imagen en el lienzo usando
drawImage(). Así que básicamente, el procesamiento de video en el lienzo es hecho con
la combinación de técnicas ya estudiadas.
Construyamos una nueva plantilla y los códigos para ver de qué estamos hablando.
<!DOCTYPE html>
<html lang="es">
<head>
<title>Video en el Lienzo</title>
<style>
.cajas{
display: inline-block;
margin: 10px;
padding: 5px;
border: 1px solid #999999;
}
</style>
<script src="canvasvideo.js"></script>
</head>
<body>
<section class="cajas">
<video id="medio" width="483" height="272">
<source src="https://www.minkbooks.com/content/trailer2.mp4">
<source src="https://www.minkbooks.com/content/trailer2.ogg">
</video>
</section>
<section class="cajas">
<canvas id="lienzo" width="483" height="272">
Su navegador no soporta el elemento canvas
</canvas>
</section>
</body>
</html>
Plantilla para reproducir video en el lienzo.
La plantilla en el Listado 7-28 incluye dos componentes específicos: el elemento
<video> y el elemento <canvas>. Con la combinación de ambos vamos a procesar y
mostrar video en el lienzo.
La plantilla también incluye estilos CSS embebidos para las cajas y un archivo Javascript
llamado canvasvideo.js para el siguiente código:
function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
video=document.getElementById('medio');
video.addEventListener('click', presionar, false);
}
function presionar(){
if(!video.paused && !video.ended){
video.pause();
window.clearInterval(bucle);
}else{
video.play();
bucle=setInterval(procesarCuadros, 33);
}
}
function procesarCuadros(){
lienzo.drawImage(video,0,0);
var info=lienzo.getImageData(0,0,483,272);
var pos;
var gris;
for(x=0;x<=483;x++){
for(y=0;y<=272;y++){
pos=(info.width*4*y)+(x*4);
gris=parseInt(info.data[pos]*0.2989 +
info.data[pos+1]*0.5870 + info.data[pos+2]*0.1140);
info.data[pos]=gris;
info.data[pos+1]=gris;
info.data[pos+2]=gris;
}
}
lienzo.putImageData(info,0,0);
}
window.addEventListener("load", iniciar, false);
Video color convertido en blanco y negro.
Hágalo usted mismo: Cree un nuevo archivo HTML con el código del Listado 7-28
y un archivo Javascript llamado canvasvideo.js con el código del Listado 7-29.
Para comenzar a reproducir el video, haga clic en la caja izquierda en la pantalla.
IMPORTANTE: Este ejemplo usa los métodos getImageData() y putImageData()
para procesar datos de imagen. Como explicamos anteriormente, estos métodos
extraen información del lienzo. Debido a restricciones de seguridad, la extracción de
información desde el lienzo es desactivada luego de que el elemento recibe
contenido desde un origen que no es el origen del documento que lo creó (el
documento pertenece a un dominio y el video a otro). Por lo tanto, para probar este
ejemplo, deberá descargar el video desde nuestro sitio web (o usar el suyo propio) y
luego subir cada uno de los archivos a su servidor.
Estudiemos por un momento el código del Listado 7-29. Como dijimos previamente,
para procesar video en el lienzo, simplemente debemos recurrir a códigos y técnicas ya
vistas. En este código estamos usando la función presionar() tomada del Capítulo 5
para comenzar y detener la reproducción del video haciendo clic sobre el mismo. También
creamos una función llamada procesarCuadros() que está usando el mismo código del
Listado 7-25 de este capítulo, excepto que esta vez en lugar de invertir la imagen estamos
usando una fórmula para transformar todos los colores de cada cuadro del video en el
correspondiente gris. Esto convertirá nuestro video color en un video blanco y negro.
La función presionar() cumple con dos propósitos: comenzar o detener la
reproducción del video e iniciar un intervalo que ejecutará la función
procesarCuadros() cada 33 milisegundos. Esta función toma un cuadro del elemento
<video> y lo dibuja en el lienzo con la instrucción drawImage(video,0,0). Luego los
datos son extraídos del lienzo con el método getImageData() y cada pixel de ese cuadro
es procesado por medio de dos bucles for (como lo hicimos en un ejemplo anterior).
El proceso utilizado para convertir cada uno de los colores que integran cada pixel en
su correspondiente gris es uno de los más populares y fáciles de encontrar en Internet. La
fórmula es la siguiente: rojo × 0.2989 + verde × 0.5870 + azul × 0.1140.
Luego de que la fórmula es calculada, el resultado debe ser asignado a cada color del pixel
(rojo, verde y azul), como lo hicimos en el ejemplo usando la variable gris.
El proceso termina cuando dibujamos nuevamente el cuadro modificado en el lienzo
usando el método putImageData().
IMPORTANTE: Este ejemplo es con propósitos didácticos. Procesar video en
tiempo real del modo en que lo hicimos no es una práctica recomendada.
Dependiendo de la configuración de su ordenador y el navegador que use para
correr la aplicación, probablemente note algunas demoras en el proceso. Para
crear aplicaciones Javascript útiles, siempre debe considerar su rendimiento.
Referencia rápida
La API Canvas es probablemente la más compleja y extensa de todas las APIs incluidas
dentro de la especificación HTML5. Provee varios métodos y propiedades para crear
aplicaciones gráficas sobre el elemento <canvas>.
Métodos
Estos métodos son específicos de la API Canvas:
getContext(contexto) Este método crea el contexto para el lienzo. Puede tomar dos
valores: 2d y 3d para gráficos en 2 y 3 dimensiones.
fillRect(x, y, ancho, alto) Este método dibujará un rectángulo sólido directamente en el
lienzo en la posición indicada por x,y y el tamaño ancho,alto.
strokeRect(x, y, ancho, alto) Este método dibujará un rectángulo vacío (solo el contorno)
directamente en el lienzo en la posición indicada por x,y y el tamaño ancho,alto.
clearRect(x, y, ancho, alto) Este método borra un área en el lienzo usando una figura
rectangular declarada por los valores de sus atributos.
createLinearGradient(x1, y1, x2, y2) Este método crea un gradiente lineal para asignarlo a
una figura como si fuese un color usando la propiedad fillStyle. Sus atributos solo
especifican las posiciones de comienzo y final del gradiente (relativas al lienzo). Para
declarar los colores involucrados en el gradiente, este método debe ser usado en
combinación con addColorStop().
createRadialGradient(x1, y1, r1, x2, y2, r2) Este método crea un gradiente radial para
asignarlo a una figura como si fuese un color usando la propiedad fillStyle. El
gradiente es construido por medio de dos círculos. Los atributos solo especifican la
posición y radio de los círculos (relativos al lienzo). Para declarar los colores
involucrados en el gradiente, este método debe ser usado en combinación con
addColorStop().
addColorStop(posición, color) Este método es usado para declarar los colores para el
gradiente. El atributo posición es un valor entre 0.0 y 1.0, usado para determinar
dónde el color comenzará la degradación.
beginPath() Este método es requerido para comenzar un nuevo trazado.
closePath() Este método puede ser usado al final de un trazado para cerrarlo. Generará
una línea recta desde la última posición del lápiz hasta el punto donde el trazado
comenzó. No es necesario usar este método cuando el trazado debe permanecer
abierto o es dibujado en el lienzo usando fill().
stroke() Este método es usado para dibujar un trazado como una figura vacía (solo el
contorno).
fill() Este método es usado para dibujar un trazado como una figura sólida.
clip() Este método es usado para crear una máscara a partir de un trazado. Todo lo que
sea enviado al lienzo luego de que este método es declarado será dibujado sólo si cae
dentro de la máscara.
moveTo(x, y) Este método mueve el lápiz virtual a una nueva posición para continuar el
trazado desde ese punto.
lineTo(x, y) Este método agrega líneas rectas al trazado desde la posición actual del lápiz
hasta el punto indicado por los atributos x e y.
rect(x, y, ancho, alto) Este método agrega un rectángulo al trazado en la posición x,y y
con un tamaño determinado por ancho,alto.
arc(x, y, radio, ángulo inicio, ángulo final, dirección) Este método agrega un arco al trazado.
El centro del arco es determinado por x e y, los ángulos son definidos en radianes, y la
dirección es un valor booleano para determinar si el arco será dibujado en el mismo
sentido o el opuesto a las agujas del reloj. Para convertir grados en radianes, use la
fórmula: Math.PI/180×grados.
quadraticCurveTo(cpx, cpy, x, y) Este método agrega una curva Bézier cuadrática al trazado.
Comienza desde la posición actual del lápiz y termina en el punto x,y. Los atributos cpx y
cpy especifican la posición del punto de control que dará forma a la curva.
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) Este método agrega una curva Bézier cúbica al
trazado. Comienza desde la posición actual del lápiz y termina en el punto x,y. Los
atributos cp1x, cp1y, cp2x, y cp2y especifican la posición de los dos puntos de
control que darán forma a la curva.
strokeText(texto, x, y, máximo) Este método dibuja un texto vacío (solo el contorno)
directamente en el lienzo. El atributo máximo es opcional y determina el máximo
tamaño del texto en pixeles.
fillText(texto, x, y, máximo) Este método dibuja un texto sólido directamente en el lienzo.
El atributo máximo es opcional y determina el máximo tamaño del texto en pixeles.
measureText(texto) Este método calcula el tamaño del área que un texto ocupará en el
lienzo usando los estilos vigentes. La propiedad width es usada para retornar el valor.
translate(x, y) Este método mueve el origen del lienzo al punto x,y. La posición inicial del
origen (0,0) es la esquina superior izquierda del área generada por el elemento <canvas>.
rotate(angle) Este método es usado para rotar el lienzo alrededor del origen. El ángulo
debe ser declarado en radianes. Para convertir grados en radianes, use la fórmula:
Math.PI/180×grados.
scale(x, y) Este método cambia la escala del lienzo. Los valores por defecto son (1.0, 1.0).
Los valores provistos pueden ser negativos.
transform(m1, m2, m3, m4, dx, dy) Este método modifica la matriz de transformación del
lienzo. La nueva matriz es calculada sobre la anterior.
setTransform(m1, m2, m3, m4, dx, dy) Este método modifica la matriz de transformación
del lienzo. Reinicia los valores anteriores y declara los nuevos.
save() Este método graba el estado del lienzo, incluyendo la matriz de transformación,
propiedades de estilo y la máscara.
restore() Este método restaura el último estado del lienzo grabado, incluyendo la matriz
de transformación, propiedades de estilo y la máscara.
drawImage() Esta método dibujará una imagen en el lienzo. Existen tres sintaxis posibles.
La sintaxis drawImage(imagen,x,y) dibuja la imagen en la posición x,y. La sintaxis
drawImage(imagen,x,y,ancho,alto) dibuja la imagen en la posición x,y con un
nuevo tamaño declarado por ancho,alto. Y la sintaxis drawImage(imagen, x1,
y1, ancho1, alto1, x2, y2, ancho2, alto2) toma una porción de la imagen
original determinada por x1,y1,ancho1,alto1 y la dibuja en el lienzo en la posición
x2,y2 y el nuevo tamaño ancho2,alto2.
getImageData(x, y, ancho, alto) Este método toma una porción del lienzo y la graba como
datos en un objeto. Los valores del objeto son accesibles a través de las propiedades
width, height y data. Las primeras dos propiedades retornan el tamaño de la
porción de la imagen tomada, y data retorna la información como un array con
valores representando los colores de cada pixel. Este valor puede ser accedido usando
la fórmula (ancho×4×y)+(x×4).
putImageData(datosImagen, x, y) Este método dibuja en el lienzo la imagen representada
por la información en datosImagen.
createImageData(ancho, alto) Este método crea una nueva imagen en formato de datos.
Todos los pixeles son inicializados en color negro transparente. Puede tomar datos de
imagen como atributo en lugar de ancho y alto. En este caso la nueva imagen tendrá
el tamaño determinado por los datos provistos.
createPattern(imagen, tipo) Este método crea un patrón desde una imagen que luego
podrá ser asignado a una figura usando la propiedad fillStyle. Los valores posibles
para el atributo tipo son repeat, repeat-x, repeat-y y no-repeat.
Propiedades
La siguiente lista de propiedades es específica para la API Canvas:
strokeStyle Esta propiedad declara el color para las líneas de las figuras. Puede recibir
cualquier valor CSS, incluidas funciones como rgb() y rgba().
fillStyle Esta propiedad declara el color para el interior de figuras sólidas. Puede recibir
cualquier valor CSS, incluidas funciones como rgb() y rgba(). Es también usada para
asignar gradientes y patrones a figuras (estos estilos son primero asignados a una
variable y luego esa variable es declarada como el valor de esta propiedad).
globalAlpha Esta propiedad es usada para determinar el nivel de transparencia de las
figuras. Recibe valores entre 0.0 (completamente opaco) y 1.0 (completamente
transparente).
lineWidth Esta propiedad especifica el grosor de la línea. Por defecto el valor es 1.0.
lineCap - Esta propiedad determina la forma de la terminación de las líneas. Se pueden
utilizar tres valores: butt (terminación normal), round (termina la línea con un
semicírculo) y square (termina la línea con un cuadrado).
lineJoin Esta propiedad determina la forma de la conexión entre líneas. Se pueden utilizar
tres valores: round (la unión es redondeada), bevel (la unión es cortada) y miter (la
unión es extendida hasta que ambas líneas alcanzan un punto en común).
miterLimit Esta propiedad determina cuánto se extenderán las líneas cuando la propiedad
lineJoin es declarada como miter.
font Esta propiedad es similar a la propiedad font de CSS y utiliza la misma sintaxis para
declarar los estilos del texto.
textAlign Esta propiedad determina cómo el texto será alineado. Los posibles valores son
start, end, left, right y center.
textBaseline Esta propiedad determina el alineamiento vertical para el texto. Los posibles
valores son: top, hanging, middle, alphabetic, ideographic y bottom.
shadowColor Esta propiedad establece el color para la sombra. Utiliza valores CSS.
shadowOffsetX Esta propiedad declara la distancia horizontal entre la sombra y el objeto.
shadowOffsetY Esta propiedad declara la distancia vertical entre la sombra y el objeto.
shadowBlur Esta propiedad recibe un valor numérico para generar un efecto de
difuminación para la sombra.
globalCompositeOperation Esta propiedad determina cómo las nuevas figuras serán
dibujadas en el lienzo considerando las figuras ya existentes. Puede recibir varios valores:
source-over, source-in, source-out, source-atop, lighter, xor,
destination-over, destination-in, destination-out, destination-atop,
darker y copy. El valor por defecto es source-over, lo que significa que las nuevas
formas son dibujadas sobre las anteriores.