Unexpected result in color conversion from LAB to RGB

Hello,

My goal is to create high-resolution images of color charts in LAB. However processing works only in RGB or HSB so I have to convert LAB to RGB in order to display it.

I used the formulas found on the web (LAB to XYZ and XYZ to RGB) I included them in my code, then I use a "for" loop to determine the color for each pixels.

I saw a few topics on color conversion, but I'm kind of stuck as I don't know where my problem is coming from ....

So here it is : for a fixed value of L = 100, everything is working perfectly, I'm getting this image as expected : https://drive.google.com/file/d/0ByjuuWpChE01X3otSFRQNFUyVjA/edit?usp=sharing

But when I try to make another image for a fixed value of a = 0 , I get an horizontal line in the bottom, as if there was a problem with lower values of L ... here it is : https://drive.google.com/file/d/0ByjuuWpChE01RzJWUVZnR2U3VW8/edit?usp=sharing

Here is my code, I hope it will be clear, ask me if you need anything, ans thank you very much for your help

// parameters for the code execution
void setup() {
  noLoop();
  size(10,10,P2D);

  nuancier = createGraphics(taille,taille);

}

// final image file and size
PGraphics nuancier ;
int taille = 1000 ;


// Arrays for color values
float[] colorLAB = new float[3];
float[] colorXYZ = new float[3];
float[] colorRGB = new float[3];

// colors
float X;
float Y;
float Z;
float L;
float a;
float b;
float R;
float G;
float B;

// pixels
int x ;
int y ;

// function to convert Lab to XYZ
float[] LABtoXYZ() {

  L = colorLAB[0];
  a = colorLAB[1];
  b = colorLAB[2];


  float ntY = ( L + 16 ) / 116 ;
  float ntX = a / 500 + ntY ;
  float ntZ = ntY - b / 200 ;
  if ( (pow(ntY,3)) > 0.008856 ) {
    ntY = (pow(ntY,3)) ;
  } else { ntY = ( ntY - 16 / 116 ) / 7.787 ; }
  if ( (pow(ntX,3)) > 0.008856 ) {
    ntX = (pow(ntX,3)) ;
  } else { ntX = ( ntX - 16 / 116 ) / 7.787 ; }
  if ( (pow(ntZ,3)) > 0.008856 ) {
    ntZ = (pow(ntZ,3)) ;
  } else { ntZ = ( ntZ - 16 / 116 ) / 7.787 ; }
  X = 95.047 * ntX ;     //ref_X =  95.047      Observateur= 2°, Illuminant= D65
  Y = 100 * ntY ;     //ref_Y = 100.000
  Z = 108.883 * ntZ ;     //ref_Z = 108.883


  colorXYZ[0] = X ;
  colorXYZ[1] = Y ;
  colorXYZ[2] = Z ;
  return colorXYZ ;

}

// function to convert  XYZ to RGB
float[] XYZtoRGB() {

  X = colorXYZ[0];
  Y = colorXYZ[1];
  Z = colorXYZ[2];


  float ntX = X / 100 ;         //X compris entre 0 et  95.047      ( Observateur = 2°, Illuminant = D65 )
  float ntY = Y / 100 ;         //Y compris entre 0 et 100.000
  float ntZ = Z / 100 ;         //Z compris entre 0 et 108.883

  float ntR = ntX *  3.2406 + ntY * (-1.5372) + ntZ * (-0.4986) ;
  float ntG = ntX * (-0.9689) + ntY *  1.8758 + ntZ *  0.0415 ;
  float ntB = ntX *  0.0557 + ntY * (-0.2040) + ntZ *  1.0570 ;

  if ( ntR > 0.0031308 ) {
    ntR = 1.055 * ( pow(ntR,( 1 / 2.4 )) ) - 0.055 ;
  } else { ntR = 12.92 * ntR ; }
  if ( ntG > 0.0031308 ) {
    ntG = 1.055 * ( pow(ntG,( 1 / 2.4 )) ) - 0.055 ;
  } else { ntG = 12.92 * ntG ; }
  if ( ntB > 0.0031308 ) {
    ntB = 1.055 * ( pow(ntB,( 1 / 2.4 )) ) - 0.055 ;
  } else { ntB = 12.92 * ntB ; }

  R = ntR * 255 ;
  G = ntG * 255 ;
  B = ntB * 255 ;

  colorRGB[0] = R ;
  colorRGB[1] = G ;
  colorRGB[2] = B ;
  return colorRGB ;

}

// I know that with RGB not every visible color is possible
//so I just made this quick function, to bound RGB values between 0 and 255

float[] arrondirRGB () {
  for (int i=0;i<3;i++) {
    if (colorRGB[i]>255) {
      colorRGB[i]=255 ;
    }
    if (colorRGB[i]<0) {
      colorRGB[i]=0 ;
    }
  }
  return colorRGB;
}



// operating section
void draw () {

  nuancier.beginDraw();
  nuancier.noSmooth();
  nuancier.colorMode(RGB, 255);
  nuancier.endDraw();

  for (x=0;x<taille;x++) {
    for (y=0;y<taille;y++) {

      colorLAB[0] = (((taille-y)*100)/taille) ; // --------------------------------------------------------------- valeur 100  // formule ((x*100)/taille)
      colorLAB[1] = 0 ; // ----------------------------------------------------------- valeur 0    // formule ((x*256)/taille)-127
      colorLAB[2] = (((x)*256)/taille)-127 ; // -------------------------------------------------- valeur 0    // (((taille-y)*256)/taille)-127

      println(colorLAB[0]);

      LABtoXYZ () ;
      XYZtoRGB () ;
      arrondirRGB () ;

      nuancier.beginDraw();
      nuancier.stroke (colorRGB[0],colorRGB[1],colorRGB[2]);
      nuancier.point (x,y);
      nuancier.endDraw();


    }
  }

  nuancier.save("nuancier.tiff");
  println("done !");
}
Tagged:

Answers

  • edited December 2018

    I guess I've made it. Check it out: o=>

    /**
     * LABtoXYZtoRGB (v2.03)
     * by  PabloParis (2014/Aug)
     * mod GoToLoop
     *
     * Forum.Processing.org/two/discussion/6903/
     * unexpected-result-in-color-conversion-from-lab-to-rgb#Item_1
     */
    
    void setup() {
      size(800, 600, JAVA2D);
      noLoop();
    
      int gw = width, gh = height;
      PImage nuancier = createImage(gw, gh, RGB);
      color[] dots = nuancier.pixels;
      double[] XYZ = new double[3];
    
      for (int x = 0; x != gw; ++x)  for (int y = 0; y != gh;)
        dots[y++*gw + x] = XYZtoRGB( LABtoXYZ
          (XYZ, 100*(gh-y)/gh, 0, 256*x/gw - 127) );
    
      nuancier.updatePixels();
      background(nuancier);
      save(dataPath("nuancier.tiff"));
      println("Done!");
    }
    
    static final double[] LABtoXYZ(double[] XYZ, color... LAB) {
      double y = (LAB[0] + 16)/116d;
      double x = y + LAB[1]/500d;
      double z = y - LAB[2]/200d;
    
      x = x*x*x > 8856e-6d? x*x*x : (x - 16/116d)/7.787d;
      y = y*y*y > 8856e-6d? y*y*y : (y - 16/116d)/7.787d;
      z = z*z*z > 8856e-6d? z*z*z : (z - 16/116d)/7.787d;
    
      XYZ[0] = 95.047d*x;
      XYZ[1] = 100d*y;
      XYZ[2] = 108.883d*z;
    
      return XYZ;
    }
    
    static final color XYZtoRGB(double... XYZ) {
      double x = XYZ[0]/100d, y = XYZ[1]/100d, z = XYZ[2]/100d;
    
      double r = x*3.2406d - y*1.5372d - z*.4986d;
      double g = x*-.9689d + y*1.8758d + z*.0415d;
      double b = x*.0557d  - y*.204d   + z*1.057d;
    
      r = r > 31308e-7d? Math.pow(r, 1/2.4d)*1.055d - .055d : r*12.92d;
      g = g > 31308e-7d? Math.pow(g, 1/2.4d)*1.055d - .055d : g*12.92d;
      b = b > 31308e-7d? Math.pow(b, 1/2.4d)*1.055d - .055d : b*12.92d;
    
      color R = constrain((color) Math.round(r*255d), 0, 0xFF);
      color G = constrain((color) Math.round(g*255d), 0, 0xFF);
      color B = constrain((color) Math.round(b*255d), 0, 0xFF);
    
      return R<<020 | G<<010 | B | PImage.ALPHA_MASK;
    }
    
  • edited December 2018 Answer ✓

    Oh, I thought the problem was that float wouldn't be enough!
    Just replaced double w/ float now and it seems to work alright too!
    Perhaps the original bug was about some integer division or whatever!

    /**
     * LABtoXYZtoRGB (v2.26)
     * by  PabloParis (2014/Aug)
     * mod GoToLoop
     *
     * https://Forum.Processing.org/two/discussion/6903/
     * unexpected-result-in-color-conversion-from-lab-to-rgb#Item_2
     */
    
    void setup() {
      size(800, 600);
      noLoop();
    
      final int gw = width, gh = height;
      final PImage nuancier = createImage(gw, gh, RGB);
      final color[] dots = nuancier.pixels;
      final float[] XYZ  = new float[3];
    
      for (int x = 0; x != gw; ++x)  for (int y = 0; y != gh; )
        dots[y++*gw + x] = XYZtoRGB(
          LABtoXYZ(XYZ, 100*(gh-y)/gh, 0, 0400*x/gw - 0200));
    
      nuancier.updatePixels();
      background(nuancier);
    
      save(dataPath("nuancier.tiff"));
      println("Done!");
    }
    
    @SafeVarargs static final float[] LABtoXYZ(final float[] XYZ, final color... LAB) {
      float y = (LAB[0] + 16) / 116.0, y3 = y*y*y;
      float x = y + LAB[1] / 500.0, x3 = x*x*x;
      float z = y - LAB[2] / 200.0, z3 = z*z*z;
    
      x = x3 > 8856e-6? x3 : (-16.0/116.0 + x)/7.787;
      y = y3 > 8856e-6? y3 : (-16.0/116.0 + y)/7.787;
      z = z3 > 8856e-6? z3 : (-16.0/116.0 + z)/7.787;
    
      XYZ[0] = x * 95.047;
      XYZ[1] = y * 100.0;
      XYZ[2] = z * 108.883;
    
      return XYZ;
    }
    
    @SafeVarargs static final color XYZtoRGB(final float... XYZ) {
      final float x = XYZ[0]*.01, y = XYZ[1]*.01, z = XYZ[2]*.01;
    
      float r = x*3.2406 - y*1.5372 - z*.4986;
      float g = x*-.9689 + y*1.8758 + z*.0415;
      float b = x*.0557  - y*.204   + z*1.057;
    
      r = r > 31308e-7? pow(r, 1.0/2.4)*1.055 - .055 : r*12.92;
      g = g > 31308e-7? pow(g, 1.0/2.4)*1.055 - .055 : g*12.92;
      b = b > 31308e-7? pow(b, 1.0/2.4)*1.055 - .055 : b*12.92;
    
      final color R = constrain(round(r*255.0), 0, 0xFF);
      final color G = constrain(round(g*255.0), 0, 0xFF);
      final color B = constrain(round(b*255.0), 0, 0xFF);
    
      return R<<020 | G<<010 | B | PImage.ALPHA_MASK;
    }
    
  • pablo, the problem is things like this

    float ntZ = ntY - b / 200 ;
    

    dividing by an integer like that doesn't do what you expect. you need

    float ntZ = ntY - b / 200.0 ;
    
  • Answer ✓

    (the usual link to the faq article describing this seems to be broken

    http://wiki.processing.org/w/Troubleshooting#Why_does_2_.2F_5_.3D_0_instead_of_0.4.3F

    the text is still there but the link anchor doesn't work.)

  • Thanks a lot, that was it, division by integers. Very helpful and good to know for futur use ... ;)

Sign In or Register to comment.