wandres.dev
COLOR I · sRGB, gamma y el problema

El degradado que pasa por gris sucio

El experimento del azul al amarillo con los números exactos: qué color sale en el punto medio, cuál saldría mezclando luz, y dónde más aparece el mismo error.

⏱ 16 min

Escribe un degradado de azul puro a amarillo puro y mira el centro. Sale un gris apagado que no está en ninguno de los dos colores y que nadie querría. No es un bug del navegador ni un defecto de tu pantalla: es la consecuencia aritmética exacta de interpolar entre dos números que no son cantidades de luz. Antes de entender por qué, conviene medir cuánto se desvía el resultado, porque el número es mayor de lo que la intuición sugiere.

🎯 Al terminar esta lección sabrás
  • Calcular el color del punto medio de un degradado en sRGB.
  • Comparar ese punto con el que corresponde a mezclar luz y con el perceptual.
  • Identificar las cuatro operaciones de CSS afectadas por el mismo fenómeno.
  • Separar este problema del artefacto de transparencia, que ya está resuelto.

El experimento

.franja {
  background: linear-gradient(to right, #0000ff, #ffff00);
  block-size: 6rem;
}

Los extremos son azul puro, rgb(0 0 255), y amarillo puro, rgb(255 255 0). El navegador interpola componente a componente, así que en el punto medio cada canal vale la media de los dos extremos:

R: (0 + 255) / 2 = 128
G: (0 + 255) / 2 = 128
B: (255 + 0) / 2 = 128

El centro es rgb(128 128 128), es decir #808080, el gris que todo el mundo llamaría “el gris medio”. Y ahí está el problema: no lo es.

Los números

La luminancia relativa de un color se calcula sobre sus componentes lineales, con los coeficientes de sRGB: 0.2126 para el rojo, 0.7152 para el verde y 0.0722 para el azul.

azul    #0000ff -> lineal (0, 0, 1)   -> Y = 0.0722
amarillo #ffff00 -> lineal (1, 1, 0)  -> Y = 0.2126 + 0.7152 = 0.9278

El amarillo emite casi trece veces más luz que el azul. Si el punto medio del degradado fuese una mezcla física de esas dos luces, tendría Y = 0.5.

Ahora la luminancia del gris que ha salido. El valor 128 sobre 255 se decodifica con la función de transferencia de sRGB y da:

#808080 -> lineal 0.2159 -> Y = 0.2159

El centro del degradado tiene un 21.6% de la luz, cuando debería tener el 50%. Y en la otra dirección: el color cuya luminancia es 0.5 se codifica como el valor 188, es decir #bcbcbc, un gris claro claramente distinto del que se ve.

Los tres candidatos a “el color del medio”, cada uno con su criterio:

criterio resultado luminancia
media de los valores codificados, lo que hace CSS #808080 0.216
punto medio perceptual, misma distancia visual a cada extremo #9d9d9d 0.337
media de la luz, lo que haría mezclar dos focos #bcbcbc 0.500

Sesenta niveles de diferencia entre el primero y el último. Eso es lo que se ve como una banda oscura y sucia atravesando el degradado.

Y hay una segunda razón de que se vea mal, además del brillo: el punto medio ha perdido todo el color. Los dos extremos son saturadísimos y el centro es un gris neutro perfecto, porque el camino recto entre azul y amarillo en el cubo RGB pasa exactamente por la diagonal de los grises. El degradado no atraviesa colores intermedios, atraviesa el desagüe.

Dónde más aparece

El mismo cálculo se aplica a todas las operaciones que combinan colores componente a componente en el espacio codificado.

Mezclas. color-mix() sin espacio explícito y cualquier interpolación entre dos colores. La paleta generada mezclando la marca con blanco tiene pasos que no son perceptualmente uniformes.

Transparencias. Un velo negro al 50% sobre un fondo blanco produce el mismo 128, con su 21.6% de luz. Físicamente, un cristal que dejara pasar la mitad de la luz daría 188. Por eso los velos de CSS oscurecen más de lo que la intuición espera, y por eso hay que bajar el alfa más de lo que uno cree para conseguir un oscurecimiento suave.

Desenfoques y escalados. Promediar píxeles vecinos es exactamente la misma operación. Un tablero de ajedrez blanco y negro reducido a la mitad debería dar un gris del 50% de luz y da uno del 21.6%: la imagen se oscurece al reducirla, y es un artefacto conocido en cualquier canal de procesado de imagen.

Composición y fusión. Los modos de fusión operan sobre los valores codificados, así que multiply y compañía dan resultados que no corresponden a la física que sus nombres sugieren.

Lo que no es este problema

Merece la pena descartar un artefacto que se confunde con este y que ya está resuelto: el degradado hacia transparent.

.velo { background: linear-gradient(to bottom, #000, transparent); }

Durante años eso producía una franja gris sucia, porque transparent es rgb(0 0 0 / 0) —negro con alfa cero— y la interpolación arrastraba el negro. La especificación de imágenes de CSS establece que la interpolación se haga con alfa premultiplicado, con lo cual el color de un extremo totalmente transparente no contamina el camino, y todos los motores modernos lo cumplen. Ese artefacto pertenece al pasado.

Lo que queda, y no se arregla con la premultiplicación, es el problema de la función de transferencia. Son dos cosas distintas y conviene no mezclarlas al diagnosticar.

El punto medio de un degradado no lo define ni la física ni la percepción, sino la codificación

Lo que hace este tema difícil de aprender no es la aritmética, que es de bachillerato: es que la pregunta “¿qué color hay en el medio?” no tiene una respuesta única, y hasta que uno acepta eso sigue buscando el número correcto. Hay al menos tres respuestas defendibles —la media de la luz, el punto medio perceptual y la media de los valores codificados— y las tres son la respuesta correcta a preguntas distintas: cómo se suman dos focos, qué color parece estar a igual distancia de los dos, y qué sale de promediar dos bytes. CSS eligió la tercera, y la eligió por la única razón por la que se eligen estas cosas en la web: es lo que hacían los navegadores en 1996 y cambiarlo habría alterado el aspecto de millones de páginas existentes. Todo sistema gráfico ha tomado esta decisión exactamente una vez, al principio, y no ha podido revisarla nunca: por eso los motores de videojuegos trabajan en luz lineal y componen en el último paso, por eso las herramientas de edición de imagen tienen una casilla escondida de “mezclar en RGB lineal”, y por eso CSS ha tenido que introducir la elección de espacio como argumento de cada función en lugar de arreglar el comportamiento por defecto. Cuando llegues a una API gráfica nueva, la primera pregunta útil no es qué formatos de color admite, sino en qué espacio hace la aritmética; la respuesta explica de antemano casi todos los resultados que te van a extrañar.