2011-01-04 31 views
13

¿Cómo puedo presentar el objeto Color en el modelo de color CIE Lab?Java: cómo convertir el color RGB a CIE Lab

Color c = ... 
float[] lab = {0,0,0}; 
... 
c.getColorComponents(ColorSpace.getInstance(???), lab); 

Pero no fue capaz de forzar este trabajo con CIE Lab (a pesar del hecho de que TYPE_Lab se presenta en clase ColorSpace)

Thx por la atención.

Respuesta

19

Aquí está mi aplicación:

import java.awt.color.ColorSpace; 

public class CIELab extends ColorSpace { 

    public static CIELab getInstance() { 
     return Holder.INSTANCE; 
    } 

    @Override 
    public float[] fromCIEXYZ(float[] colorvalue) { 
     double l = f(colorvalue[1]); 
     double L = 116.0 * l - 16.0; 
     double a = 500.0 * (f(colorvalue[0]) - l); 
     double b = 200.0 * (l - f(colorvalue[2])); 
     return new float[] {(float) L, (float) a, (float) b}; 
    } 

    @Override 
    public float[] fromRGB(float[] rgbvalue) { 
     float[] xyz = CIEXYZ.fromRGB(rgbvalue); 
     return fromCIEXYZ(xyz); 
    } 

    @Override 
    public float getMaxValue(int component) { 
     return 128f; 
    } 

    @Override 
    public float getMinValue(int component) { 
     return (component == 0)? 0f: -128f; 
    }  

    @Override 
    public String getName(int idx) { 
     return String.valueOf("Lab".charAt(idx)); 
    } 

    @Override 
    public float[] toCIEXYZ(float[] colorvalue) { 
     double i = (colorvalue[0] + 16.0) * (1.0/116.0); 
     double X = fInv(i + colorvalue[1] * (1.0/500.0)); 
     double Y = fInv(i); 
     double Z = fInv(i - colorvalue[2] * (1.0/200.0)); 
     return new float[] {(float) X, (float) Y, (float) Z}; 
    } 

    @Override 
    public float[] toRGB(float[] colorvalue) { 
     float[] xyz = toCIEXYZ(colorvalue); 
     return CIEXYZ.toRGB(xyz); 
    } 

    CIELab() { 
     super(ColorSpace.TYPE_Lab, 3); 
    } 

    private static double f(double x) { 
     if (x > 216.0/24389.0) { 
      return Math.cbrt(x); 
     } else { 
      return (841.0/108.0) * x + N; 
     } 
    } 

    private static double fInv(double x) { 
     if (x > 6.0/29.0) { 
      return x*x*x; 
     } else { 
      return (108.0/841.0) * (x - N); 
     } 
    } 

    private Object readResolve() { 
     return getInstance(); 
    } 

    private static class Holder { 
     static final CIELab INSTANCE = new CIELab(); 
    } 

    private static final long serialVersionUID = 5027741380892134289L; 

    private static final ColorSpace CIEXYZ = 
     ColorSpace.getInstance(ColorSpace.CS_CIEXYZ); 

    private static final double N = 4.0/29.0; 

} 
+0

cualquier restricción en el uso de? Es este código con licencia? –

+0

@Peter Perháč, el [Creative Commons Reconocimiento-Compartir bajo la misma] (http://creativecommons.org/licenses/by-sa /2.5/) se aplica la licencia. – finnw

+1

Excelente - Lo usé sin ningún problema, sin embargo, hice un pequeño cambio. En mi aplicación convertí una imagen RGB al espacio de color CIELab, realicé algunos procesamientos en el canal L, luego volví a convertir RGB y notó artefactos feos. La solución que utilicé fue fijar los valores X, Y, Z calculados en el método anterior 'toCIEXYZ' al rango [0,1].No estoy seguro de si esta es la forma "correcta" de hacer las cosas, pero se deshizo de los artefactos. – James

2

Hay un TYPE_Lab, pero no el correspondiente CS_Lab. Necesitará extender ColourSpace y anular los métodos abstractos para convertir entre XYZ, RGB y Lab. Las conversiones requeridas se pueden encontrar en Lab color space (Wikipedia).

+0

Es una pena :( gracias de todos modos – user562527

2

Parece que CIELAB solo es compatible con el nombre en la biblioteca actual de Java: si mira el origen de java.awt.color.Colorspace, verá que solo se admiten unos pocos espacios de color con nombre.

10

he tenido algunos problemas utilizando el código en la respuesta de @ finw. Creo que eran sobre todo debido al hecho de que hay que hacer una conversión CIELab se debe especificar un iluminante:

http://en.wikipedia.org/wiki/Standard_illuminant

Uno de los estándares populares es D50, que es básicamente una luz estándar. Debido a que el código de @finw no tiene la corrección para la iluminación, los colores que se supone que son de color gris neutro salen ligeramente teñidos. Una forma de comprobar esto es intentar:

float[] g = { 50.0f, 0f, 0f }; 
CIELab.getInstance().toRGB(g); 
for (float f : g) System.out.println(f); 

Debe obtener aproximadamente el mismo número en los tres canales, pero que terminan con un perfil RGB que es notablemente (aunque ligeramente) azul. Estoy seguro de que es posible corregir esto en el código de @ finw, pero después de un poco de jugar con él y buscar alrededor, me encontré con un excelente código de conversión aquí:

http://www.f4.fhtw-berlin.de/~barthel/ImageJ/ColorInspector//HTMLHelp/farbraumJava.htm

Para completar, aquí está .

public void rgb2lab(int R, int G, int B, int[] lab) { 
    //http://www.brucelindbloom.com 

    float r, g, b, X, Y, Z, fx, fy, fz, xr, yr, zr; 
    float Ls, as, bs; 
    float eps = 216.f/24389.f; 
    float k = 24389.f/27.f; 

    float Xr = 0.964221f; // reference white D50 
    float Yr = 1.0f; 
    float Zr = 0.825211f; 

    // RGB to XYZ 
    r = R/255.f; //R 0..1 
    g = G/255.f; //G 0..1 
    b = B/255.f; //B 0..1 

    // assuming sRGB (D65) 
    if (r <= 0.04045) 
     r = r/12; 
    else 
     r = (float) Math.pow((r+0.055)/1.055,2.4); 

    if (g <= 0.04045) 
     g = g/12; 
    else 
     g = (float) Math.pow((g+0.055)/1.055,2.4); 

    if (b <= 0.04045) 
     b = b/12; 
    else 
     b = (float) Math.pow((b+0.055)/1.055,2.4); 


    X = 0.436052025f*r  + 0.385081593f*g + 0.143087414f *b; 
    Y = 0.222491598f*r  + 0.71688606f *g + 0.060621486f *b; 
    Z = 0.013929122f*r  + 0.097097002f*g + 0.71418547f *b; 

    // XYZ to Lab 
    xr = X/Xr; 
    yr = Y/Yr; 
    zr = Z/Zr; 

    if (xr > eps) 
     fx = (float) Math.pow(xr, 1/3.); 
    else 
     fx = (float) ((k * xr + 16.)/116.); 

    if (yr > eps) 
     fy = (float) Math.pow(yr, 1/3.); 
    else 
    fy = (float) ((k * yr + 16.)/116.); 

    if (zr > eps) 
     fz = (float) Math.pow(zr, 1/3.); 
    else 
     fz = (float) ((k * zr + 16.)/116); 

    Ls = (116 * fy) - 16; 
    as = 500*(fx-fy); 
    bs = 200*(fy-fz); 

    lab[0] = (int) (2.55*Ls + .5); 
    lab[1] = (int) (as + .5); 
    lab[2] = (int) (bs + .5);  
} 

En mis pruebas, produce valores de grises que son apropiadamente libres de croma, y ​​es mucho más rápido para arrancar.

+0

¿Estás seguro de que esta es la implementación correcta? Intenté encubrir el blanco (255, 255, 255) y el resultado fue (L = 255, a = 0, b = 0) en lugar de (L = 100, a = 0, b = 0). También compruebo http://colormine.org/convert/rgb-to-lab para comparar con otro color, como rojo, amarillo y verde. – BornToCode

+0

Parece que 'Ls' se escala para llenar el rango [0, 255]. Devolver solo 'Ls' es suficiente. También he omitido '+ .5's. Parecen redundantes ya que harán que los valores se desborden. Puedo redondear el valor, lo cual tiene más sentido. – akinuri

1

He utilizado este código y funcionó:

public double[] rgbToLab(int R, int G, int B) { 

    double r, g, b, X, Y, Z, xr, yr, zr; 

    // D65/2° 
    double Xr = 95.047; 
    double Yr = 100.0; 
    double Zr = 108.883; 


    // --------- RGB to XYZ ---------// 

    r = R/255.0; 
    g = G/255.0; 
    b = B/255.0; 

    if (r > 0.04045) 
     r = Math.pow((r+0.055)/1.055,2.4); 
    else 
     r = r/12.92; 

    if (g > 0.04045) 
     g = Math.pow((g+0.055)/1.055,2.4); 
    else 
     g = g/12.92; 

    if (b > 0.04045) 
     b = Math.pow((b+0.055)/1.055,2.4); 
    else 
     b = b/12.92 ; 

    r*=100; 
    g*=100; 
    b*=100; 

    X = 0.4124*r + 0.3576*g + 0.1805*b; 
    Y = 0.2126*r + 0.7152*g + 0.0722*b; 
    Z = 0.0193*r + 0.1192*g + 0.9505*b; 


    // --------- XYZ to Lab --------- // 

    xr = X/Xr; 
    yr = Y/Yr; 
    zr = Z/Zr; 

    if (xr > 0.008856) 
     xr = (float) Math.pow(xr, 1/3.); 
    else 
     xr = (float) ((7.787 * xr) + 16/116.0); 

    if (yr > 0.008856) 
     yr = (float) Math.pow(yr, 1/3.); 
    else 
     yr = (float) ((7.787 * yr) + 16/116.0); 

    if (zr > 0.008856) 
     zr = (float) Math.pow(zr, 1/3.); 
    else 
     zr = (float) ((7.787 * zr) + 16/116.0); 


    double[] lab = new double[3]; 

    lab[0] = (116*yr)-16; 
    lab[1] = 500*(xr-yr); 
    lab[2] = 200*(yr-zr); 

    return lab; 

} 

Para el código anterior utilicé las fórmulas proporcionadas here con el fin de convertir de RGB a XYZ y luego desde XYZ a CIELab. Los resultados que obtengo son los mismos con el convertidor en línea this.

Cuestiones relacionadas