Saltar al contenido

El pipeline de primitivas: in y result

Un filtro es un grafo de operaciones sobre imágenes. Las entradas implícitas, el encadenamiento por nombre, las ramas paralelas, y las dos primitivas que vuelven a juntarlas.

El modelo: imágenes que entran y salen

Un filter contiene una secuencia de primitivas, cada una de las cuales toma una o dos imágenes de entrada, hace una operación por píxel o por vecindad, y produce una imagen de salida. La última primitiva de la lista es la que se pinta.

Cada primitiva tiene:

  • in: el nombre de la imagen de entrada. Si se omite, la regla es doble: en la primera primitiva vale SourceGraphic; en cualquier otra, vale el resultado de la primitiva anterior.
  • in2: la segunda entrada, en las primitivas que necesitan dos.
  • result: el nombre con el que se guarda la salida, para poder referenciarla más adelante. Si se omite, el resultado solo está disponible como entrada implícita de la siguiente.

Con esas tres reglas se construye cualquier filtro.

HTML
<filter id="cadena">  <feGaussianBlur stdDeviation="4" />           <!-- in implicito = SourceGraphic -->  <feOffset dx="4" dy="4" />                     <!-- in implicito = el desenfoque -->  <feColorMatrix type="saturate" values="0" />   <!-- in implicito = el desplazado --></filter>

Tres primitivas, ninguna con in ni result, y funciona: es una cadena lineal. La mayoría de los filtros útiles, sin embargo, no son lineales.

Las entradas predefinidas

Nombre Qué es Estado
SourceGraphic El elemento tal y como se habría pintado Implementado
SourceAlpha Solo el canal alfa del elemento, en negro Implementado
BackgroundImage Lo que hay pintado detrás Nunca implementado
BackgroundAlpha El alfa de lo de detrás Nunca implementado
FillPaint El relleno del elemento, extendido al infinito Soporte muy desigual
StrokePaint El trazo del elemento, extendido al infinito Soporte muy desigual

Merece la pena detenerse en BackgroundImage. La especificación lo definió para permitir efectos que leen el fondo, del tipo cristal esmerilado. Ningún motor lo implementó nunca, porque exige que el compositor tenga disponible el resultado de todo lo pintado hasta ese momento en el momento de aplicar el filtro, lo que rompe la posibilidad de paralelizar el pintado. La función existe hoy en CSS con backdrop-filter, que resuelve el mismo problema con otro modelo. Si encuentras un tutorial que usa BackgroundImage, es de 2005 y no funciona.

SourceAlpha es la que se usa constantemente y conviene tenerla clara: es la silueta del elemento, con el color a negro y el alfa original. Es la entrada correcta para una sombra, porque una sombra no debe tener el color del objeto.

Bifurcar y volver a juntar

El grafo se ramifica cuando dos primitivas usan la misma entrada, y se vuelve a juntar con una primitiva de dos entradas.

Ese grafo es el de un resplandor, y en XML se escribe así:

HTML
<filter id="halo" x="-50%" y="-50%" width="200%" height="200%"        color-interpolation-filters="sRGB">  <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="desenfoque" />  <feFlood flood-color="#89b4fa" flood-opacity="0.9" result="color" />  <feComposite in="color" in2="desenfoque" operator="in" result="halo" />  <feMerge>    <feMergeNode in="halo" />    <feMergeNode in="SourceGraphic" />  </feMerge></filter>

Léelo de arriba abajo con el grafo delante. Se desenfoca el original y se guarda como desenfoque. Se genera un rectángulo de color plano que llena toda la región del filtro. Se recorta ese color con la forma del desenfoque usando operator="in", lo que produce un halo del color elegido con la forma difusa del original. Y se apilan halo y original con feMerge, que pinta sus nodos en orden, el primero abajo.

La clave del patrón está en feComposite operator="in": es la manera de teñir una forma. in conserva la parte de la primera entrada que solapa con el alfa de la segunda, así que un color plano recortado por un alfa da ese alfa del color.

Las dos primitivas de unión

feComposite implementa los operadores de Porter y Duff, que son los del álgebra de composición de imágenes:

operator Resultado
over La primera encima de la segunda. Es el valor por omisión
in La primera, recortada al alfa de la segunda
out La primera, donde la segunda no tiene alfa
atop La primera sobre la segunda, recortada al alfa de la segunda
xor Donde solo una de las dos tiene alfa
arithmetic Una combinación lineal con cuatro coeficientes

arithmetic calcula, por canal y con los valores premultiplicados:

Texto
resultado = k1 * i1 * i2 + k2 * i1 + k3 * i2 + k4

Con k2="1" k3="1" es una suma; con k2="1" k3="-1" es una resta; con k1="1" es una multiplicación. Es la primitiva que resuelve los casos que ningún operador con nombre cubre, y aparece en filtros de iluminación y de contraste.

feBlend implementa los modos de fusión, los mismos de CSS: normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color y luminosity.

La diferencia entre los dos: feComposite opera sobre el alfa y decide qué píxeles sobreviven; feBlend opera sobre el color y decide cómo se mezclan. Cuando quieres recortar una imagen con la silueta de otra, feComposite. Cuando quieres multiplicar una textura sobre una forma, feBlend.

Depurar un filtro

Tres técnicas que ahorran horas.

Aislar la rama. Comenta las primitivas posteriores y deja que la que investigas sea la última. Como la última es la que se pinta, ves su salida directamente.

Pintar un resultado intermedio. Añade al final un feComposite operator="in" in="X" in2="X" que no cambie nada, con in apuntando al resultado que quieres ver. Más simple todavía: mueve un feMerge con un solo nodo al final.

Nombrar todo. Aunque la cadena sea lineal, poner result en cada primitiva y in explícito en cada una convierte un filtro ilegible en un grafo que se lee. Cuesta unos bytes y ahorra la mitad de los errores.