Estos experimentos culminaron con la creación del primer Mugtug, junto a mi viejo amigo Charles Pritchard. Estamos desarrollando Darkroom en <canvas> con HTML5. Darkroom es una aplicación no destructiva para compartir fotos que combina la potencia de filtros basados en píxeles con el dibujo y la tipografía basados en vectores.
<canvas> ofrece a los programadores de JavaScript un control absoluto de los colores, los vectores y los píxeles en sus pantallas: la composición visual del monitor.
Los siguientes ejemplos tratan un área de <canvas> a la que no se le ha prestado mucha atención: la creación de efectos de texto. La gran variedad de efectos de texto que se pueden crear en <canvas> es tan amplia como se podría imaginar. Estas demos cubren una subsección de lo que es posible hacer. Aunque en este tutorial vamos a hablar sobre “texto”, los métodos se pueden aplicar a cualquier objeto vector, lo que permite crear increíbles efectos visuales en juegos y en otras aplicaciones:
- Sombras de texto en <canvas>
- Efectos de texto similares a CSS en <canvas> que permiten crear máscaras de recorte, buscar estadísticas en <canvas> y utilizar la propiedad de sombra
- Efectos combinados: neón-arcoíris, cebra-reflejo
- Efectos de texto similares a los de Photoshop en ejemplos de <canvas> sobre el uso de globalCompositeOperation, createLinearGradient o createPattern
- Sombras internas y externas en <canvas>
- Una función poco conocida: el uso de rebobinado en el sentido y en el sentido contrario de las agujas del reloj para crear el inverso de una sombra (la sombra interior)
- Spaceage: efecto generativo
- Efecto de texto generativo en <canvas> que utiliza ciclos de color hsl() y
window.requestAnimationFrame
para crear la sensación de movimiento
Sombras de texto en Canvas
Una de las novedades de las especificaciones de CSS3 que más me gustan (junto con el radio del borde y los gradientes web, entre otros), es la capacidad de crear sombras. Es importante conocer las diferencias entre las sombras de <canvas> y de CSS, concretamente:
CSS utiliza dos métodos: sombra de cuadro para elementos de cuadro, como div, span, etc., y sombra de texto para contenido de texto.
<canvas> dispone de un tipo de sombra que se utiliza para todos los objetos vector: ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText, etc. Para crear una sombra en <canvas>, toca estas cuatro propiedades:
- ctx.shadowColor = “red” // cadena
- Color de la sombra: RGB, RGBA, HSL, HEX y otras entradas son válidas
- ctx.shadowOffsetX = 0; // número entero
- Distancia horizontal de la sombra en relación con el texto
- ctx.shadowOffsetY = 0; // número entero
- Distancia vertical de la sombra en relación con el texto
- ctx.shadowBlur = 10; // número entero
- Efecto borroso en la sombra (cuanto mayor sea el valor, más borrosa será la sombra)
Para empezar, veamos cómo <canvas> puede emular los efectos CSS. Al buscar en Google Imágenes “sombra de texto css”, obtuvimos algunas demos increíbles que podíamos emular: Line25, Stereoscopic y Shadow 3D.
El efecto 3D estereoscópico (consulta este artículo sobre imágenes de anaglifo) es un ejemplo del gran uso que se puede hacer de una simple línea de código. Con esta línea de CSS, podemos crear la ilusión de profundidad si utilizamos gafas rojo/cian de 3D (con las que se ven las películas 3D):
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
Hay que tener en cuenta dos cosas al convertir esta cadena a <canvas>:
(1) No hay efecto borroso de sombra (el tercer valor), por lo que no hay motivo para ejecutar la sombra, ya que fillText permite obtener el mismo resultado:
var text = “Hello world!” ctx.fillStyle = “#000” ctx.fillText(text, -7, 0); ctx.fillStyle = “red” ctx.fillText(text, 0, 0); ctx.fillStyle = “cyan” ctx.fillText(text, 7, 0);
(2) <canvas> no admite EM, por lo que se deben convertir a PX. Para encontrar el índice de conversión entre PT, PC, EM, EX, PX, etc., podemos crear un elemento con las mismas propiedades de fuente en DOM y establecer el ancho según el formato que vayamos a medir; o, por ejemplo, para capturar la conversión EM -> PX, podemos medir el elemento DOM con “height: 1em”: el resultado de offsetHeight sería el número de PX que hay en cada EM.
var font = “20px sans-serif” var d = document.createElement(”span”); d.style.cssText = “font: “ + font + “ height: 1em; display: block” // the value to multiply PX’s by to convert to EM’s var EM2PX = 1 / d.offsetHeight;
Cómo prevenir la multiplicación de alfa
En un ejemplo más complejo, como el efecto neón de Line25, se debe utilizar la propiedad shadowBlur para emular el efecto correctamente. Dado que el efecto neón depende de varias sombras, nos encontramos con un problema; en <canvas> cada objeto vector solo puede tener una sombra. Por tanto, para dibujar varias sombras, es necesario dibujar varias versiones superpuestas del texto. Esto tiene como resultado una multiplicación de alfa y, como consecuencia, bordes irregulares.
Intenté ejecutar
ctx.fillStyle = “rgba(0,0,0,0)”
o “transparent”
para ocultar el texto, al mismo tiempo que mostraba la sombra... sin embargo, este intento fue inútil, ya que la sombra nunca puede ser más opaca que fillStyle porque la sombra es una multiplicación del valor alfa de fillStyle.
Afortunadamente, hay una forma de solucionarlo. Podemos dibujar la sombra alejada del texto, manteniendo ambos separados (de forma que no se superpongan) y ocultando así el texto que quede fuera de la pantalla.
var text = “Hello world!” var blur = 10; var width = ctx.measureText(text).width + blur * 2; ctx.textBaseline = “top” ctx.shadowColor = “#000” ctx.shadowOffsetX = width; ctx.shadowOffsetY = 0; ctx.shadowBlur = blur; ctx.fillText(text, -width, 0);
Cómo recortar alrededor del bloque de texto
Para limpiar esto un poco, podemos añadir una ruta de recorte para evitar que fillText se dibuje en primer lugar (al mismo tiempo que permitimos que se dibuje la sombra). Para poder crear una ruta de recorte que rodee el texto, necesitamos conocer el ancho y la altura del texto (llamada “altura Mt” por ser históricamente la altura de la letra “M” en las imprentas). Podemos obtener el ancho con
ctx.measureText().width
, sin embargo, ctx.measureText().height
no existe.
Afortunadamente, gracias a este truco CSS (consulta este artículo sobre medidas tipográficas para conocer más formas de corregir antiguas implementaciones de <canvas> con medidas CSS), podemos calcular la altura del texto midiendo el valor de
offsetHeight
de un <intervalo> con las mismas propiedades de fuente:var d = document.createElement(”span”); d.font = “20px arial” d.textContent = “Hello world!” var emHeight = d.offsetHeight;
A partir de aquí, podemos crear un rectángulo para utilizarlo como ruta de recorte, encuadrando la “sombra” mientras eliminamos la forma simulada.
ctx.rect(0, 0, width, emHeight); ctx.clip();
Si lo intentamos todo a la vez y lo optimizamos (si una sombra no tiene efecto borroso, se puede utilizar fillText para conseguir el mismo efecto, ahorrándonos el tener que configurar la máscara de recorte):
var width = ctx.measureText(text).width; var style = shadowStyles[text]; // add a background to the current effect ctx.fillStyle = style.background; ctx.fillRect(0, offsetY, ctx.canvas.width, textHeight - 1) // parse text-shadows from css var shadows = parseShadow(style.shadow); // loop through the shadow collection var n = shadows.length; while(n--) { var shadow = shadows[n]; var totalWidth = width + shadow.blur * 2; ctx.save(); ctx.beginPath(); ctx.rect(offsetX - shadow.blur, offsetY, offsetX + totalWidth, textHeight); ctx.clip(); if (shadow.blur) { // just run shadow (clip text) ctx.shadowColor = shadow.color; ctx.shadowOffsetX = shadow.x + totalWidth; ctx.shadowOffsetY = shadow.y; ctx.shadowBlur = shadow.blur; ctx.fillText(text, -totalWidth + offsetX, offsetY + metrics.top); } else { // just run pseudo-shadow ctx.fillStyle = shadow.color; ctx.fillText(text, offsetX + (shadow.x||0), offsetY - (shadow.y||0) + metrics.top); } ctx.restore(); } // drawing the text in the foreground if (style.color) { ctx.fillStyle = style.color; ctx.fillText(text, offsetX, offsetY + metrics.top); } // jump to next em-line ctx.translate(0, textHeight);
Introducir todos estos comandos de <canvas> manualmente es muy tedioso, por lo que he incluido un sencillo analizador de sombra de texto en el código fuente de la demo que permite convertir los comandos CSS a comandos <canvas>. Ahora, nuestros elementos <canvas> tienen toda una gama de estilos que se pueden utilizar. Estos mismos efectos de sombra se pueden utilizar en cualquier objeto vector, desde WebFonts hasta formas complejas importadas de SVG, para generar formas de vector, etc.
Ver efectos de sombra de texto en <canvas>
Imágenes 3D
Al escribir esta sección del artículo, el ejemplo estereoscópico despertó mi curiosidad. ¿Sería muy difícil crear un efecto de pantalla de película 3D usando <canvas> y dos imágenes tomadas desde perspectivas ligeramente diferentes? Aparentemente, no. El siguiente kernel combina el canal rojo de la primera imagen (data) con el canal cian de la segunda imagen (data2):
data[i] = data[i] * 255 / 0xFF; data[i+1] = 255 * data2[i+1] / 0xFF; data[i+2] = 255 * data2[i+2] / 0xFF;
Visita la demo de Stereoscopic para ver cómo crear imágenes para mejorarlas con gafas 3D (cian/magenta). Ya solo faltaría que alguien se pegase dos iPhones con cinta adhesiva en la frente y pulsara el botón de “grabar vídeo” al mismo tiempo para poder hacer una película 3D con HTML5. ¿Algún voluntario?
Efectos combinados: neón-arcoíris, cebra-reflejo
Combinar varios efectos en <canvas> es fácil, pero es necesario disponer de algunos conocimientos básicos de globalCompositeOperation (GCO). En comparación con las operaciones de GIMP (o Photoshop), existen 12 GCO en <canvas>. Dos de ellos, darker y lighter se pueden considerar modos de fusión de capas; las otras 10 operaciones se aplican a las capas como máscaras alfa (una capa elimina los píxeles de la otra). globalCompositeOperation une las “capas” (o, en nuestro caso, las cadenas de código), combinándolas de formas nuevas y emocionantes:
No hay comentarios:
Publicar un comentario