wandres.dev
COLOR II · OKLCH y los espacios perceptuales

Uniformidad perceptual: qué mide de verdad un espacio de color

Por qué la distancia numérica entre dos colores casi nunca coincide con la distancia que ves, qué demostraron las elipses de MacAdam, y qué problema concreto vino a resolver Oklab en 2020.

⏱ 19 min

Un espacio de color es un sistema de coordenadas, y como todo sistema de coordenadas trae implícita una noción de distancia. El problema es que en sRGB, en hex y en HSL esa distancia no significa nada: dos colores separados por diez unidades pueden parecerte idénticos o pueden parecerte de familias distintas, según dónde caigan. Un espacio perceptualmente uniforme es un intento de arreglar exactamente eso, y entender qué promete y qué no es la diferencia entre usar OKLCH bien y usarlo como un hex más largo.

🎯 Al terminar esta lección sabrás
  • Distinguir entre codificar un color y modelar cómo se percibe.
  • Explicar qué demuestran las elipses de MacAdam sobre el diagrama de cromaticidad.
  • Demostrar con números por qué la L de HSL no mide claridad.
  • Situar Oklab respecto a CIELAB y saber qué defecto vino a corregir.

Codificar no es modelar

Un hex codifica tres números que acabarán siendo tres voltajes en tres subpíxeles. Esa es toda su ambición y la cumple perfectamente. #3364db no dice nada sobre cómo se ve el color: dice cuánta señal mandar a cada primario de la pantalla, con la codificación gamma de sRGB por delante.

HSL tampoco modela nada. Es una reordenación geométrica de esos mismos tres números: coge el cubo RGB, lo pone de pie sobre el vértice negro y lo describe en coordenadas cilíndricas. Su H es el ángulo alrededor del eje gris, su S es lo lejos que estás del eje, y su L es la media entre el canal más alto y el más bajo. Los tres se calculan sobre los valores ya codificados en gamma, sin pasar por ningún modelo de visión humana. HSL es una interfaz más cómoda sobre sRGB, y nada más.

La consecuencia práctica es que la aritmética sobre esas coordenadas no tiene sentido perceptual. Sumar diez al canal verde, subir la L un 10%, girar el tono 30 grados: cada una de esas operaciones produce un cambio visual cuya magnitud depende completamente de dónde partías. No hay ninguna garantía de que dos operaciones del mismo tamaño numérico produzcan dos cambios del mismo tamaño visual, porque nadie diseñó esas coordenadas con esa intención.

Un espacio perceptualmente uniforme sí se diseña con esa intención. Su promesa es concreta: que la distancia euclídea entre dos puntos sea proporcional a la diferencia que percibe un observador humano estándar. Si la promesa se cumple, todo lo demás cae por su propio peso. Una rampa de claridad con pasos iguales se ve regular. Un degradado se ve limpio. Dos colores con la misma coordenada de claridad pesan lo mismo en la página. Una paleta se puede generar en vez de dibujarse a mano.

La distancia numérica no es la distancia percibida

La demostración clásica de que las coordenadas ingenuas no valen es de 1942 y se la debemos a David MacAdam. Sentó observadores frente a un color de referencia y les pidió que ajustaran un segundo color hasta que dejaran de distinguirlos. Repitiendo el experimento en muchos puntos del diagrama de cromaticidad CIE xy, cartografió las regiones dentro de las cuales dos colores son indistinguibles.

Si el diagrama fuese perceptualmente uniforme, esas regiones serían círculos del mismo tamaño en todas partes. Lo que salió fueron elipses, con orientaciones distintas y con áreas que varían en un factor enorme: en la zona de los verdes son gigantescas, en la de los azules son diminutas. Es decir, en la región verde puedes moverte mucho sin que nadie note nada, y en la azul un desplazamiento diez veces menor produce un color claramente distinto.

Ese resultado es el origen de todo el trabajo posterior. La CIE respondió en 1976 con CIELAB, un espacio construido a propósito para que la distancia euclídea aproximase la diferencia percibida. De ahí nace la métrica delta E: la distancia entre dos colores en coordenadas Lab. La versión de 1976 es literalmente la raíz de la suma de cuadrados de las diferencias de L*, a* y b*, y su umbral de perceptibilidad se sitúa alrededor de 2.3 unidades. Las revisiones posteriores, CIE94 y CIEDE2000, existen precisamente porque delta E 76 no era suficientemente uniforme y hubo que meter términos correctores dependientes de la posición.

Ahí está la clave de todo el asunto y conviene dejarla clara antes de seguir: la uniformidad perceptual no es una propiedad matemática que se demuestre, es un ajuste empírico a datos de experimentos con personas. Ningún espacio la cumple exactamente. Los buenos la cumplen lo bastante bien como para que la aritmética sencilla dé resultados que no te sorprenden.

HSL no mide claridad

Vamos a los números, porque este punto se explica mal casi siempre y se demuestra en dos líneas.

Toma amarillo puro y azul puro. En HSL los dos declaran exactamente la misma claridad:

.amarillo { background: hsl(60 100% 50%); }  /* #ffff00 */
.azul     { background: hsl(240 100% 50%); } /* #0000ff */

La luminancia relativa, que es la magnitud física que aproxima cuánta luz sale de la pantalla ponderada por la sensibilidad del ojo, vale 0.928 para el amarillo y 0.072 para el azul. Un factor de casi trece. Los dos dicen 50% y uno de ellos es prácticamente blanco mientras el otro es prácticamente negro.

En OKLCH esos mismos dos colores se describen así:

.amarillo { background: oklch(96.8% 0.211 109.77); }
.azul     { background: oklch(45.2%  0.313 264.05); }

96.8% frente a 45.2%, con los tres ejes que la lección siguiente desmenuza. El espacio te está diciendo lo que ves: el amarillo saturado de sRGB es un color clarísimo y el azul saturado es un color oscuro. No hay nada que puedas hacer al respecto, es física de los primarios; lo que cambia es que el sistema de coordenadas te lo cuenta en vez de escondértelo.

El experimento completo, con la misma S y la misma L de HSL en ocho tonos, produce esta tabla. La última columna es el contraste WCAG contra blanco, que es lo que de verdad determina si un texto se lee.

Color en HSL Claridad OKLCH real Contraste sobre blanco
hsl(0 90% 50%) 60.7% 4.33
hsl(50 90% 50%) 85.3% 1.56
hsl(110 90% 50%) 83.8% 1.51
hsl(200 90% 50%) 69.1% 2.71
hsl(220 90% 50%) 53.0% 5.62
hsl(264 90% 50%) 49.6% 7.15
hsl(300 90% 50%) 67.7% 3.43

Siete colores que en HSL son “el mismo tono al 50% de claridad” y que en la práctica van de 1.51 a 7.15 de contraste. Esa dispersión es la razón por la que una paleta escrita en HSL exige que alguien la retoque a mano tono por tono, y es la razón por la que las paletas generadas mecánicamente en HSL se ven mal.

De CIELAB a Oklab

CIELAB lleva desde 1976 haciendo su trabajo y sigue siendo el idioma común de la colorimetría industrial. Pero arrastra un defecto que en pantalla se nota mucho: su predicción del tono azul es mala. Las líneas de tono constante en la zona azul no son rectas, con lo que si coges un azul y le subes L* manteniendo a* y b*, el color se te va hacia el morado. Cualquiera que haya intentado generar una rampa de azules en LCH lo ha visto: el paso claro no es el mismo azul más claro, es otro color.

En diciembre de 2020 Björn Ottosson publicó Oklab con un objetivo acotado: conservar la estructura simple de CIELAB —tres coordenadas, una de claridad y dos de oponencia cromática— pero ajustar los coeficientes contra conjuntos de datos mejores. La construcción es corta: de XYZ se pasa a un espacio LMS que aproxima la respuesta de los tres tipos de cono, se aplica una raíz cúbica a cada componente, y se combinan linealmente los resultados. Sin funciones a trozos, sin casos especiales.

El resultado predice mejor el tono y mejor la claridad, y en particular arregla el desvío azul. Eso es todo lo que promete. No es un modelo de apariencia completo: no tiene en cuenta la adaptación al entorno, ni el tamaño del estímulo, ni el fondo sobre el que miras el color. Para eso están CAM16 y familia, que son mucho más caros y no caben en un motor de CSS.

OKLCH es simplemente Oklab en coordenadas polares. Se convierte a la ida con C igual a la distancia al origen en el plano a-b y H igual al ángulo, y a la vuelta con seno y coseno. Es la misma información con otro sistema de coordenadas, elegido porque “claridad, cuánto color y qué color” es como piensa una persona, mientras que “cuánto verde-rojo y cuánto azul-amarillo” no lo es.

La uniformidad es local, es un ajuste, y no es contraste

Tres matices que separan a quien usa OKLCH de quien lo entiende. Primero: la uniformidad es local. Los datos con los que se ajustan estos espacios provienen de experimentos de discriminación entre colores parecidos. Lo que el modelo garantiza razonablemente es que un delta E pequeño se corresponde con una diferencia pequeña. Para colores muy separados la métrica sigue siendo útil como ordenación, pero no como medida: que dos pares tengan delta E de 40 no significa que se vean igual de distintos. Segundo: es un ajuste, no una ley. Oklab está optimizado contra los conjuntos de datos que Ottosson eligió, con un observador estándar, en condiciones de visualización concretas. Con otro fondo, otro brillo de pantalla o un observador con visión del color atípica, las predicciones se degradan. Es una herramienta de ingeniería excelente, no una verdad sobre el sistema visual. Y tercero, el que más caro sale: la claridad de OKLCH y el contraste de WCAG 2 son cosas distintas. L modela claridad percibida, aproximadamente la raíz cúbica de la luminancia. El contraste WCAG 2 es un cociente de luminancias relativas más un desplazamiento. Dos colores con la misma L de OKLCH tienen contrastes parecidos, pero no idénticos: a croma bajo la dispersión es de unas décimas, y a croma alto se abre bastante más. Si tu diseño depende de cruzar un umbral de 4.5, iguala la L para que la paleta parezca coherente y después mide el contraste de verdad, uno por uno. Son dos requisitos distintos y ninguno de los dos sustituye al otro.

⚔️ Comprueba la falta de uniformidad
  1. Pinta seis cuadrados con hsl(H 90% 50%) para H igual a 0, 50, 110, 200, 264 y 300, y ordénalos a ojo de más claro a más oscuro. Compara tu orden con la columna de claridad OKLCH de la tabla.
  2. Repite con oklch(60% 0.1 H) para los mismos seis tonos y comprueba que ya no puedes ordenarlos.
  3. Coge un azul en lch() y súbele L en tres pasos manteniendo C y H. Haz lo mismo en oklch(). Mira dónde aparece el morado.
  4. Convierte tres colores de tu proyecto a OKLCH con las herramientas de tu navegador y anota qué claridad tienen realmente.