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.

Dibujar rectángulos

Normalmente el desarrollador deberá preparar la figura a ser dibujada en el contexto, 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:

  • 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): este método es usado para substraer pixeles del área especificada por sus atributos. Es un borrador rectangular.

Ejm 7.3

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

Esta es la misma función del ejm 7.2, pero incorpora los nuevos métodos estudiados para dibujar una figura en el lienzo. 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.

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.

Ejm 7.4

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

Los colores en el ejm 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.

Ejm 7.5

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

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

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á.

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.

Ejm 7.6

function iniciar(){
var elemento=document.getElementById('lienzo');
lienzo=elemento.getContext('2d');
lienzo.beginPath();
// aquí va el trazado
lienzo.stroke();
}
window.addEventListener("load", iniciar, false);

El código del ejm 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:

Ejm 7.7

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

Recomendamos siempre establecer la posición inicial del lápiz inmediatamente después de iniciar el trazado con beginPath(). En el ejm 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 pruebas el código en tu navegador, verás 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:

Ejm 7.8

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

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():

Ejm 7.9

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

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:

Ejm 7.10

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

Para mostrar exactamente cómo funciona el método clip(), en el ejm 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:

Ejm 7.11

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

Lo primero que seguramente notarás 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 ejm 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:

Ejm 7.12

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

Con el ejm 7.12 obtenemos un arco que cubre 45 grados de un círculo. Intenta 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.

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 recuerda 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.

Ejm 7.13

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

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.

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.

Ejm 7.14

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

Comenzamos el dibujo en el ejm 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.

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.
  • illText(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).

Ejm 7.15

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

Como podemos ver en el ejm 7.5, 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.

Ejm 7.16

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

En este ejemplo comenzamos con el mismo código del ejm 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.

Sombras

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.

Ejm 7.17

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

La sombra creada en el ejm 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.

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.

Ejm 7.18

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

En el ejm 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().

Ejm 7.19

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

Al igual que en el código anterior, en el ejm 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.

Importante recordar es que las transformaciones son aplicadas sobre la matriz declarada en previas transformaciones, por lo que el segundo texto mostrado por el ejm 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().

Restaurando el estado

La acumulación de transformaciones hace realmente difícil volver a anteriores estados. En el ejm 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

Ejm 7.20

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

Si ejecutas el ejm en tu navegador, verás 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.

Ejm 7.21

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

Solo representaciones visuales de cada posible valor para la propiedad globalCompositeOperation te ayudarán a comprender cómo funcionan. Con este propósito, preparamos el código del ejm 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.