Los modos de fusión
Usar los dieciséis modos de mezcla separables y no separables que el canvas comparte con CSS y con los programas de diseño, y saber qué hace cada familia.
Además de los operadores de Porter-Duff, globalCompositeOperation acepta los modos de fusión que cualquiera reconoce de un programa de diseño: multiplicar, trama, superponer, luz suave. No deciden qué capa sobrevive sino cómo se mezclan los colores donde ambas coinciden, y se agrupan en familias con un comportamiento predecible que hace innecesario probarlos uno a uno.
- Agrupar los modos de fusión en sus cuatro familias y decir qué hace cada una.
- Elegir el modo adecuado para oscurecer, aclarar, aumentar contraste o comparar.
- Usar los modos no separables que operan sobre componentes de color.
- Combinar modos de fusión con capas para efectos compuestos.
Las cuatro familias
Los que oscurecen: multiply, darken, color-burn. El resultado nunca es más claro que ninguna de las dos capas. El blanco es neutro: mezclar con blanco no cambia nada.
Los que aclaran: screen, lighten, color-dodge. El resultado nunca es más oscuro. El negro es neutro.
Los de contraste: overlay, hard-light, soft-light. Combinan los dos anteriores según el valor del píxel base: oscurecen las zonas oscuras y aclaran las claras. El gris medio es neutro.
Los de comparación y componentes: difference, exclusion, hue, saturation, color, luminosity.
Los doce primeros son separables: operan canal a canal, de forma independiente. Los cuatro últimos son no separables: descomponen el color en tono, saturación y luminosidad, y toman unos componentes de una capa y otros de la otra.
Los que se usan
multiply multiplica los valores normalizados. El resultado es como superponer dos transparencias. Es el modo de las sombras y del papel: cualquier zona blanca desaparece y las oscuras se refuerzan.
// Sombra proyectada realista sobre un fondo con textura
ctx.globalCompositeOperation = 'multiply';
ctx.fillStyle = 'rgba(0,0,0,0.35)';
ctx.beginPath();
ctx.ellipse(cx, cy + 60, 70, 18, 0, 0, Math.PI * 2);
ctx.fill();
ctx.globalCompositeOperation = 'source-over';
Frente a dibujar la sombra con source-over y alfa, multiply conserva la textura del fondo debajo, que es lo que hace que una sombra se vea integrada y no pegada.
screen es el inverso: multiplica los complementarios. Es el modo de la luz añadida y de los reflejos, y a diferencia de lighter no satura tan rápido, con lo que da un resultado más suave.
overlay y soft-light son los modos de gradación de color. Aplicar una capa de color con soft-light sobre una fotografía es la forma estándar de virar una imagen sin destruir su rango tonal.
// Virado calido sobre una foto
ctx.drawImage(foto, 0, 0);
ctx.globalCompositeOperation = 'soft-light';
const g = ctx.createLinearGradient(0, 0, 0, alto);
g.addColorStop(0, '#f9e2af');
g.addColorStop(1, '#89b4fa');
ctx.fillStyle = g;
ctx.fillRect(0, 0, ancho, alto);
ctx.globalCompositeOperation = 'source-over';
difference da el valor absoluto de la diferencia entre ambos. Su uso más interesante no es estético sino de diagnóstico: comparar dos imágenes. Las zonas idénticas quedan negras y las distintas se iluminan.
// Diff visual entre dos renderizados
ctx.drawImage(referencia, 0, 0);
ctx.globalCompositeOperation = 'difference';
ctx.drawImage(actual, 0, 0);
ctx.globalCompositeOperation = 'source-over';
// Todo lo que no sea negro es una diferencia
Es la técnica que está detrás de las herramientas de regresión visual, y en canvas cuesta tres líneas.
luminosity y color son la pareja no separable más útil. luminosity toma la luminosidad del origen y el tono y la saturación del destino; color hace lo contrario. Juntos permiten recolorear una imagen conservando su modelado de luz:
// Recolorear conservando el detalle de luz
ctx.drawImage(objeto, 0, 0);
ctx.globalCompositeOperation = 'color';
ctx.fillStyle = '#cba6f7';
ctx.fillRect(0, 0, ancho, alto);
ctx.globalCompositeOperation = 'source-over';
Tabla de referencia
| Modo | Familia | Neutro | Uso típico |
|---|---|---|---|
multiply |
Oscurece | Blanco | Sombras, tinta, papel |
darken |
Oscurece | Blanco | Quedarse con el más oscuro por canal |
color-burn |
Oscurece | Blanco | Oscurecer con más contraste |
screen |
Aclara | Negro | Luces, reflejos, brillos |
lighten |
Aclara | Negro | Quedarse con el más claro por canal |
color-dodge |
Aclara | Negro | Destellos intensos |
overlay |
Contraste | Gris medio | Aumentar contraste general |
hard-light |
Contraste | Gris medio | Como overlay con las capas invertidas |
soft-light |
Contraste | Gris medio | Virado suave, gradación de color |
difference |
Comparación | Negro | Comparar imágenes, inversión |
exclusion |
Comparación | Negro | Como difference con menos contraste |
hue |
Componentes | — | Cambiar el tono conservando el resto |
saturation |
Componentes | — | Ajustar saturación desde una capa |
color |
Componentes | — | Recolorear conservando la luz |
luminosity |
Componentes | — | Aplicar el modelado de luz de otra capa |
Todos los modos de fusión del canvas —igual que los de CSS y los de la mayoría de los programas de diseño— operan sobre los valores codificados de sRGB, no sobre cantidades de luz. Eso significa que screen de dos grises al 50 % no da el 75 % de luz sino algo bastante más oscuro, y que multiply oscurece más de lo que la física predeciría. Es una decisión histórica: los modos se definieron en los años ochenta y noventa sobre los valores que había en el búfer, y toda la industria se estandarizó sobre ese comportamiento. Cambiarlo ahora rompería el aspecto de millones de documentos. Las tres consecuencias que importan en la práctica. Una: si intentas simular iluminación física con multiply y screen, el resultado será sistemáticamente distinto del que da un motor de renderizado que trabaja en luz lineal, y no es un error tuyo. Dos: un mismo modo produce resultados distintos según el espacio de color del canvas, porque la curva de codificación de sRGB y la de Display P3 son la misma, pero los primarios no, así que los valores numéricos representan colores distintos. Tres, y es la más útil: si quieres el comportamiento físicamente correcto, tienes que hacerlo tú, linealizando con una tabla de consulta, mezclando y volviendo a codificar, lo cual con ImageData es perfectamente viable para efectos estáticos y demasiado caro para tiempo real. La conclusión honesta es que los modos de fusión son herramientas de diseño, no simulaciones. Elígelos por cómo se ven, no por lo que la ecuación sugiere, y prueba varios en lugar de razonar cuál debería funcionar.
Combinar modos y capas
Los efectos interesantes casi nunca son un modo suelto: son varias capas con modos distintos. Y como cada modo afecta a todo el canvas, la composición por capas es obligatoria.
/** Devuelve un canvas con la foto virada y con un brillo superpuesto. */
function componer(foto, ancho, alto) {
const base = document.createElement('canvas');
base.width = ancho; base.height = alto;
const c = base.getContext('2d');
// Capa 1: la foto
c.drawImage(foto, 0, 0, ancho, alto);
// Capa 2: virado con soft-light
c.globalCompositeOperation = 'soft-light';
const virado = c.createLinearGradient(0, 0, ancho, alto);
virado.addColorStop(0, '#f9e2af');
virado.addColorStop(1, '#585b70');
c.fillStyle = virado;
c.fillRect(0, 0, ancho, alto);
// Capa 3: vinetado con multiply
c.globalCompositeOperation = 'multiply';
const vineta = c.createRadialGradient(
ancho / 2, alto / 2, Math.min(ancho, alto) * 0.3,
ancho / 2, alto / 2, Math.max(ancho, alto) * 0.75);
vineta.addColorStop(0, '#ffffff');
vineta.addColorStop(1, '#6c6f85');
c.fillStyle = vineta;
c.fillRect(0, 0, ancho, alto);
// Capa 4: destello con screen
c.globalCompositeOperation = 'screen';
const destello = c.createRadialGradient(
ancho * 0.25, alto * 0.2, 0, ancho * 0.25, alto * 0.2, ancho * 0.5);
destello.addColorStop(0, 'rgba(249,226,175,0.5)');
destello.addColorStop(1, 'rgba(249,226,175,0)');
c.fillStyle = destello;
c.fillRect(0, 0, ancho, alto);
c.globalCompositeOperation = 'source-over';
return base;
}
Ese es el aspecto real de un filtro fotográfico: cuatro capas, cuatro modos, cero manipulación de píxeles. Todo lo hace el compositor, que además puede acelerarlo por hardware, y el coste es de cuatro rellenos en lugar de millones de iteraciones en JavaScript.
Implementa el mismo virado de dos formas: con soft-light y una capa de degradado, y con una tabla de consulta por canal aplicada sobre ImageData. Cronometra las dos con una imagen de 2000 por 1500. La diferencia te dirá por qué las bibliotecas de filtros de imagen en la web usan composición y no aritmética de píxeles siempre que pueden.