Implementación de CIEDE2000 en JavaScript

Versión de la función: v1.0.0
Estadísticas del sitio
Número de visitas699
Número de archivos consultados766 + 321

Esta página presenta una implementación de referencia de la fórmula de diferencia de color CIEDE2000 en JavaScript. Si desea obtener una coincidencia exacta con implementaciones de terceros de hasta 10 decimales, es posible que tenga que realizar algunos cambios en el código fuente, en particular comentando y descomentando algunas líneas, que pueden aplicarse automáticamente a través del siguiente enlace.

Diagrama de la fórmula CIEDE2000 completa con los componentes L*a*b* y los ajustes

La función ΔE2000 en JavaScript

Consideremos la más común y académica (Sharma, 2005) de las dos formulaciones.

// Esta función escrita en JavaScript es de dominio público y no
// está afiliada a la CIE (Comisión Internacional de Iluminación).

// La implementación clásica de CIEDE2000 que recibe dos colores L*a*b* y devuelve su diferencia.
// El componente "L" varía de 0 a 100. "a" y "b" normalmente se proyectan entre -128 y 127.
function ciede_2000(l_1, a_1, b_1, l_2, a_2, b_2, k_l, k_c, k_h, canonical) {
	"use strict"
	// Ejecuta el cálculo de diferencia de color CIEDE2000 en JavaScript.
	// k_l, k_c y k_h son factores paramétricos a ajustar según
	// sus necesidades de visualización (texturas, fondos).
	var n = (Math.sqrt(a_1 * a_1 + b_1 * b_1) + Math.sqrt(a_2 * a_2 + b_2 * b_2)) * 0.5;
	n = n * n * n * n * n * n * n;
	// Un factor que implica la croma elevada a la potencia 7,
	// diseñado para tener más en cuenta la influencia de la croma.
	n = 1.0 + 0.5 * (1.0 - Math.sqrt(n / (n + 6103515625.0)));
	// Aplicación del factor de corrección de la croma para compensar su no linealidad.
	var c_1 = Math.sqrt(a_1 * a_1 * n * n + b_1 * b_1);
	var c_2 = Math.sqrt(a_2 * a_2 * n * n + b_2 * b_2);
	// La función atan2 es preferible a atan porque calcula el ángulo de un
	// punto (x, y) en todos los cuadrantes, teniendo en cuenta el signo de x e y.
	var h_1 = Math.atan2(b_1, a_1 * n), h_2 = Math.atan2(b_2, a_2 * n);
	h_1 += 2.0 * Math.PI * (h_1 < 0.0);
	h_2 += 2.0 * Math.PI * (h_2 < 0.0);
	n = Math.abs(h_2 - h_1);
	// Evita que la rama dependa del RoundingMode del lenguaje de programación.
	if (Math.PI - 1E-14 < n && n < Math.PI + 1E-14)
		n = Math.PI;
	// Cuando los ángulos de tono se encuentran en diferentes cuadrantes,
	// la media aritmética simple puede dar un ángulo incorrecto, las
	// líneas siguientes aplican la corrección angular necesaria.
	var h_m = (h_1 + h_2) * 0.5, h_d = (h_2 - h_1) * 0.5;
	if (Math.PI < n) {
		h_d += Math.PI;
		if (canonical) // Sharma’s implementation, OpenJDK, ...
			h_m += h_m < Math.PI ? Math.PI : -Math.PI;
		else // Lindbloom’s implementation, Netflix’s VMAF, ...
			h_m += Math.PI;
	}
	var p = 36.0 * h_m - 55.0 * Math.PI;
	n = (c_1 + c_2) * 0.5;
	n = n * n * n * n * n * n * n;
	// El término de corrección de la rotación del tono mejora la precisión
	// del algoritmo, especialmente cuando la comparación se refiere a azules.
	var r_t = -2.0 * Math.sqrt(n / (n + 6103515625.0))
		* Math.sin(Math.PI / 3.0 * Math.exp(p * p / (-25.0 * Math.PI * Math.PI)));
	n = (l_1 + l_2) * 0.5;
	n = (n - 50.0) * (n - 50.0);
	//  Luminosidad .
	var l = (l_2 - l_1) / ((k_l || 1.0) * (1.0 + 0.015 * n / Math.sqrt(20.0 + n)));
	// Estos coeficientes se utilizan para modular la influencia de
	// los componentes armónicos en el cálculo de la diferencia de tono.
	var t = 1.0	+ 0.24 * Math.sin(2.0 * h_m + Math.PI * 0.5)
			+ 0.32 * Math.sin(3.0 * h_m + 8.0 * Math.PI / 15.0)
			- 0.17 * Math.sin(h_m + Math.PI / 3.0)
			- 0.20 * Math.sin(4.0 * h_m + 3.0 * Math.PI / 20.0);
	n = c_1 + c_2;
	// Tono.
	var h = 2.0 * Math.sqrt(c_1 * c_2) * Math.sin(h_d) / ((k_h || 1.0) * (1.0 + 0.0075 * n * t));
	// Croma.
	var c = (c_2 - c_1) / ((k_c || 1.0) * (1.0 + 0.0225 * n));
	// Devolver la raíz cuadrada garantiza que dE00 representa una distancia
	// geométrica (que va de 0 a aproximadamente 185) en el espacio CIELAB.
	return Math.sqrt(l * l + h * h + c * c + c * h * r_t);
}

//    Proyecto GitHub : https://github.com/michel-leonard/ciede2000-color-matching
//   Pruebas en línea : https://michel-leonard.github.io/ciede2000-color-matching

// L1 = 54.8   a1 = 23.5   b1 = 2.0
// L2 = 53.3   a2 = 29.5   b2 = -2.2
// CIE ΔE00 = 4.1622955128 (Bruce Lindbloom, Netflix’s VMAF, ...)
// CIE ΔE00 = 4.1622807178 (Gaurav Sharma, OpenJDK, ...)
// Desviación entre implementaciones ≈ 1.5e-5

// Consulte los comentarios del código fuente para pasar de una de estas variantes de implementación ΔE*00 a la otra.

Su prueba de rendimiento

Precisión y fiabilidad del código fuente

La diferencia entre las formulaciones de Sharma y Lindbloom nunca excede ±0,0003 en el ΔE2000 final, lo que corresponde a la diferencia habitual medida entre dos implementaciones de 32 bits y es imperceptible al ojo humano. Nuestras implementaciones de 64 bits, todas coherentes entre sí, garantizan al menos 10 decimales correctos, por lo que elegir una formulación u otra depende principalmente de la interoperabilidad deseada. La formulación que aparece por defecto en esta página es la más utilizada (su microventaja es que se basa en la comunidad y es más ligera que su análoga cuando se vectoriza).

Si encuentra un comentario en el código fuente que no corresponde a otro idioma, informe al autor del sitio, que estudiará su sugerencia y la incorporará al código fuente.

¿Cómo se convierten los colores RGB a L*a*b*?

Deberá utilizar el espacio de color intermedio XYZ para la conversión y, si necesita ayuda, al final de esta página encontrará el código fuente (que utiliza el punto blanco D65 formalizado en 1964).

Rangos de valores en CIELAB e interpretación del ΔE2000

En el espacio de color CIELAB, el componente L* representa la luminosidad y suele variar de 0 (negro) a 100 (blanco). Los componentes a* y b* definen los ejes de color: a* va del verde al rojo, y b* del azul al amarillo. En la práctica, a* y b* se encuentran normalmente entre -128 y +127, aunque pueden superar ligeramente estos límites según la conversión de color.

Ejemplo de dos colores que presentan una diferencia apenas perceptible (JND) según CIEDE2000
Color 1Color 2Valor de ΔE2000
1
2
3
Ejemplos de valores CIEDE2000 calculados entre dos colores distintos
Color 1Color 2Valor de ΔE2000
5
10
15

ΔE2000 (CIEDE2000) cuantifica la diferencia perceptible entre dos colores: 0 significa que son idénticos, y valores más altos (hasta aproximadamente 185 en casos extremos) indican una diferencia más notable. Por ejemplo, un ΔE2000 alrededor de 5 indica colores cercanos, mientras que alrededor de 15 indica colores claramente distintos.

Ejemplo de uso en JavaScript

// Define color 1 in L*a*b* space
const l1 = 43.2, a1 = 31.2, b1 = 4.1;

// Define color 2 in L*a*b* space
const l2 = 43.0, a2 = 35.5, b2 = -4.3;

// Calculate ΔE2000 color difference
const deltaE = ciede_2000(l1, a1, b1, l2, a2, b2);
console.log(deltaE);

// .................................................. This shows a ΔE2000 of 5.2895865658
// As explained in the comments, compliance with Gaurav Sharma would display 5.2895721943

Los resultados de las pruebas

El controlador escrito en lenguaje C99, con 250 pruebas estáticas precisas, ha demostrado que esta función JavaScript es interoperable con la función CIEDE2000 disponible en otros lenguajes de programación.

CIEDE2000 Verification Summary :
  First Verified Line : 19.8,86.4,126,87,48.32,-70.5,86.66139766452334
             Duration : 36.48 s
            Successes : 10000000
               Errors : 0
      Average Delta E : 62.9463
    Average Deviation : 6.0717893379802488e-15
    Maximum Deviation : 2.2737367544323206e-13

⚡ Comparación de colores RGB y hexadecimales para la Web

Solo 3 KB – Simple. Rápido. Esta función JavaScript compacta acepta dos colores en formato RGB o hexadecimal y devuelve la diferencia de color CIE ΔE2000 utilizando el iluminante estándar D65.

// This function written in JavaScript is not affiliated with the CIE (International Commission on Illumination),
// and is released into the public domain. It is provided "as is" without any warranty, express or implied.

// This function accepts both RGB and hexadecimal color formats and computes the color difference using the CIE ΔE2000 formula.
// Examples of use :
// - ciede_2000('#00F', '#483D8B')
// - ciede_2000('#483d8b', 75,0,130)
// - ciede_2000(75, 0, 130, '#00008b')
// - ciede_2000(0, 0, 139, 0, 0, 128)

// GitHub : https://github.com/michel-leonard/ciede2000-color-matching

;function ciede_2000(a,b,c,d,e,f){"use strict";var k_l=1.0,k_c=1.0,k_h=1.0,g,h,i,j,k,l,m,n,o,p,q,r,s=0.040448236277105097;if(typeof a=='string'){g=parseInt((a.length===4?a[0]+a[1]+a[1]+a[2]+a[2]+a[3]+a[3]:a).substring(1),16);if(typeof b=='string'){h=parseInt((b.length===4?b[0]+b[1]+b[1]+b[2]+b[2]+b[3]+b[3]:b).substring(1),16);d=h>>16&0xff;e=h>>8&0xff;f=h&0xff;}else{f=d;e=c;d=b;}a=g>>16&0xff;b=g>>8&0xff;c=g&0xff}else if(typeof d=='string'){g=parseInt((d.length===4?d[0]+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]:d).substring(1),16);d=g>>16&0xff;e=g>>8&0xff;f=g&0xff;}a/=255.0;b/=255.0;c/=255.0;a=a<s?a/12.92:Math.pow((a+0.055)/1.055,2.4);b=b<s?b/12.92:Math.pow((b+0.055)/1.055,2.4);c=c<s?c/12.92:Math.pow((c+0.055)/1.055,2.4);g=a*41.24564390896921145+b*35.75760776439090507+c*18.04374830853290341;h=a*21.26728514056222474+b*71.51521552878181013+c*7.21749933075596513;i=a*1.93338955823293176+b*11.91919550818385936+c*95.03040770337479886;a=g/95.047;b=h/100.0;c=i/108.883;a=a<216.0/24389.0?((841.0/108.0)*a)+(4.0/29.0):Math.cbrt(a);b=b<216.0/24389.0?((841.0/108.0)*b)+(4.0/29.0):Math.cbrt(b);c=c<216.0/24389.0?((841.0/108.0)*c)+(4.0/29.0):Math.cbrt(c);g=(116.0*b)-16.0;h=500.0*(a-b);i=200.0*(b-c);d/=255.0;e/=255.0;f/=255.0;d=d<s?d/12.92:Math.pow((d+0.055)/1.055,2.4);e=e<s?e/12.92:Math.pow((e+0.055)/1.055,2.4);f=f<s?f/12.92:Math.pow((f+0.055)/1.055,2.4);j=d*41.24564390896921145+e*35.75760776439090507+f*18.04374830853290341;k=d*21.26728514056222474+e*71.51521552878181013+f*7.21749933075596513;l=d*1.93338955823293176+e*11.91919550818385936+f*95.03040770337479886;d=j/95.047;e=k/100.0;f=l/108.883;d=d<216.0/24389.0?((841.0/108.0)*d)+(4.0/29.0):Math.cbrt(d);e=e<216.0/24389.0?((841.0/108.0)*e)+(4.0/29.0):Math.cbrt(e);f=f<216.0/24389.0?((841.0/108.0)*f)+(4.0/29.0):Math.cbrt(f);j=(116.0*e)-16.0;k=500.0*(d-e);l=200.0*(e-f);d=(Math.sqrt(h*h+i*i)+Math.sqrt(k*k+l*l))*0.5;d=d*d*d*d*d*d*d;d=1.0+0.5*(1.0-Math.sqrt(d/(d+6103515625.0)));m=Math.sqrt(h*h*d*d+i*i);n=Math.sqrt(k*k*d*d+l*l);o=Math.atan2(i,h*d);p=Math.atan2(l,k*d);o+=2.0*Math.PI*(o<0.0);p+=2.0*Math.PI*(p<0.0);d=Math.abs(p-o);if(Math.PI-1E-14<d&&d<Math.PI+1E-14)d=Math.PI;q=(o+p)*0.5;r=(p-o)*0.5;if(Math.PI<d){r+=Math.PI;q+=Math.PI;}e=36.0*q-55.0*Math.PI;d=(m+n)*0.5;d=d*d*d*d*d*d*d;s=-2.0*Math.sqrt(d/(d+6103515625.0))*Math.sin(Math.PI/3.0*Math.exp(e*e/(-25.0*Math.PI*Math.PI)));d=(g+j)*0.5;d=(d-50.0)*(d-50.0);f=(j-g)/(k_l*(1.0+0.015*d/Math.sqrt(20.0+d)));a=1.0+0.24*Math.sin(2.0*q+Math.PI*0.5)+0.32*Math.sin(3.0*q+8.0*Math.PI/15.0)-0.17*Math.sin(q+Math.PI/3.0)-0.20*Math.sin(4.0*q+3.0*Math.PI/20.0);d=m+n;b=2.0*Math.sqrt(m*n)*Math.sin(r)/(k_h*(1.0+0.0075*d*a));c=(n-m)/(k_c*(1.0+0.0225*d));return Math.sqrt(f*f+b*b+c*c+c*b*s);};

Archivos para descargar

A continuación encontrará un archivo que permite realizar cálculos de precisión arbitraria en JavaScript (útil si trabaja con ΔE2000 en metrología). Siéntase libre de utilizar estos archivos puestos a disposición por Michel, incluso con fines comerciales.

Estadísticas del sitio : descargas de archivos
ArchivoTamañoNúmero de clics
ciede-2000.js4 KB106
ciede-2000-driver.js7 KB84
ciede-2000-multiprecision.js37 KB82
ciede-2000-random.js6 KB75
compare-hex-colors.js8 KB77
compare-rgb-colors.js8 KB77
test-js-multiprecision.yml5 KB68
test-js.yml3 KB66
vs-chroma.yml4 KB66
vs-npm-delta-e.yml4 KB65
reference-dataset.txt4 KB321
Haga clic en js.zip para descargar todos estos archivos en un archivo.

Comunidad

Si quieres dejar tu opinión sobre este código fuente JavaScript o sobre CIEDE2000 en general, el libro de visitas ya contiene 1 mensajes en español, y 9 mensajes en total, así que dinos lo que piensas.