Capítulo 7 API Canvas -3
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);
Listado 7-14. 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.
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);
Listado 7-15. 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);
Listado 7-16. 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.
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);
Listado 7-17. 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.
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 transformo 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);
Listado 7-18. 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);
Listado 7-19. 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.
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.getElementByld(‘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);
Listado 7-20. 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.getElementByld(‘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);
Listado 7-21. 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 destination- atop 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.