skip to Main Content

I’m trying to convert sRGB values (in Photoshop) to L*ab values (also for use in Photoshop) I’m in the ballpark, but not getting the same results as EasyRGB which is where I got the conversion formulas from. I expect there’s rounding errors (not at the end, where I round to three places). Only I can’t see them.

  • Input: sRGB 255, 0, 255
  • Output:L*ab: 60.32 , 98.254, -60.843
  • Expected Output L*ab: 60.324 98.234 -60.825

To make it easier I’ve lifted out any Photoshop code, so it’s just JavaScript

var msg = "";

// RGB
// var Red = foregroundColor.rgb.red;
// var Green = foregroundColor.rgb.green;
// var Blue = foregroundColor.rgb.blue;

var Red = 255;
var Green = 0;
var Blue = 255;

msg = "RGB: " + Red + ", " + Green + ", " + Blue + "<br>";
printy(msg);

// user colour converted to XYZ space
var myColXYZ = RGB_to_XYZ(Red, Green, Blue)
var colX = myColXYZ[0];
var colY = myColXYZ[1];
var colZ = myColXYZ[2];

// using CIE L-ab* colour space
var myLab = XYZ_to_LAB(colX, colY, colZ)

//msg = "L*ab: " + myLab[0] + ", " + myLab[1] + ", " + myLab[2] + "<br>";

// b4 rounding: 60.319933664076004, 98.25421868616108, -60.84298422386232


// round to three places
for (var i = 0; i < 3; i++) {
  myLab[i] = round_nicely(myLab[i], 3);
}
msg = "L*ab: " + myLab[0] + ", " + myLab[1] + ", " + myLab[2] + "<br>";
printy(msg);

// results
// RGB: 255, 0, 255
// L*ab: 60.32, 98.254, -60.843


// should be
// CIE-L*ab     =   60.324   98.234  -60.825

// function RGB TO XYZ (R, G, B)
// --------------------------------------------------------
// http://www.easyrgb.com/index.php?X=MATH&H=02#text2
function RGB_to_XYZ(r, g, b) {
  var_R = parseFloat(r / 255); //r from 0 to 255
  var_G = parseFloat(g / 255); //g from 0 to 255
  var_B = parseFloat(b / 255); //b from 0 to 255

  if (var_R > 0.04045) var_R = Math.pow(((var_R + 0.055) / 1.055), 2.4)
  else var_R = var_R / 12.92
  if (var_G > 0.04045) var_G = Math.pow(((var_G + 0.055) / 1.055), 2.4)
  else var_G = var_G / 12.92
  if (var_B > 0.04045) var_B = Math.pow(((var_B + 0.055) / 1.055), 2.4)
  else var_B = var_B / 12.92

  var_R = var_R * 100;
  var_G = var_G * 100;
  var_B = var_B * 100;

  // Observer. = 2°, Illuminant = D65
  X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805;
  Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722;
  Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505;
  return [X, Y, Z]
}

// function XYZ TO LAB (x, y, z)
// --------------------------------------------------------
// http://www.easyrgb.com/index.php?X=MATH&H=16#text16
function XYZ_to_LAB(x, y, z) {
  var ref_X = 95.047;
  var ref_Y = 100.000;
  var ref_Z = 108.883;

  var_X = x / ref_X; //ref_X =  95.047   Observer= 2°, Illuminant= D65
  var_Y = y / ref_Y; //ref_Y = 100.000
  var_Z = z / ref_Z; //ref_Z = 108.883

  if (var_X > 0.008856) var_X = Math.pow(var_X, (1 / 3))
  else var_X = (7.787 * var_X) + (16 / 116)
  if (var_Y > 0.008856) var_Y = Math.pow(var_Y, (1 / 3))
  else var_Y = (7.787 * var_Y) + (16 / 116)
  if (var_Z > 0.008856) var_Z = Math.pow(var_Z, (1 / 3))
  else var_Z = (7.787 * var_Z) + (16 / 116)

  CIE_L = (116 * var_Y) - 16;
  CIE_a = 500 * (var_X - var_Y);
  CIE_b = 200 * (var_Y - var_Z);

  return [CIE_L, CIE_a, CIE_b]
}

// function ROUND NICELY (num number, number of decimal places)
// --------------------------------------------------------
function round_nicely(num, places) {
  // if(! places) places = 3;
  var p = Math.pow(10, places)
  return Math.round(num * p) / p
}

// function PRINTY (str)
// --------------------------------------------------------
function printy(message) {
  var outputDiv = document.getElementById('myPage');
  outputDiv.innerHTML += message;
}
<!-- page content -->
<p id="myPage"></p>

3

Answers


  1. Obviously the EasyRBG RGB to XYZ conversion is flawed. It doesn’t obey it’s own rules.
    Let’s do this step by step:

    The input data is

    R: 255 G: 0 B: 255

    The first step is to map those to the zero to one range by dividing by 255. yielding this

    var_R: 1 var_G: 0 var_B: 1

    The next step is some manipulations, which seem to be some Gamut adjustments. But those manipulations have fix points at the extremes, meaning input values 1 or 0 don’t change in this manipulation. So we are still at

    var_R: 1 var_G: 0 var_B: 1

    Next step is scale everything by 100

    var_R: 100 var_G: 0 var_B: 100

    Then there is a matrix multiplication of the vector (R,G,B) to (X,Y,Z) which is a weighted linear combination of RGB vector

    X = 42.24 + 18.05 = 60.29
    Y = 21.26 + 7.22 = 28.48
    Y = 1.93 + 95.05 = 96.98

    EasyRGB has some different result for this conversion from RGB to XYZ

    X: 59.289, Y: 28.485, Z: 96.964

    Login or Signup to reply.
  2. Consider utilizing Photoshop’s ExtendScript/JS API instead of EasyRGB for an accurate color transformation.

    The custom rgbToLab function in the gist below demonstrates how the SolidColor class can be utilized to firstly create a new RGB color (using the given color components; R, G, B) and how to subsequently obtain the corresponding Lab values.

    #target photoshop
    
    /**
     * Converts RGB color values to the Lab color model.
     * @param {Number} red - The red component value (0-255).
     * @param {Number} green - The green component value (0-255).
     * @param {Number} blue - The blue component value (0-255).
     * @returns {Array} The Lab values.
     */
    function rgbToLab(red, green, blue) {
      var color = new SolidColor;
    
      color.rgb.red = red;
      color.rgb.green = green;
      color.rgb.blue = blue;
    
      return [
        color.lab.l,
        color.lab.a,
        color.lab.b
      ]
    }
    
    var labValues = rgbToLab(255, 0, 255);
    
    $.writeln('L*ab: '+ labValues) // --> L*ab: 60.16845703125,92.6736755371094,-60.763671875
    
    Login or Signup to reply.
  3. This discrepancy occurs because Photoshop uses the D50 illuminant for Lab conversions (source) while the EasyRGB Color Converter uses D65 when converting from sRGB (notice the mention of "D65/2°" in the sRGB and XYZ rows of the conversion results, also the D65 illuminant is hardcoded in their pseudocode for this conversion). As far as I know there’s no way to make EasyRGB use a different illuminant when starting with sRGB.

    As for the provided JavaScript code, I can tell that you’re using values that correspond to a D65 illuminant, so your code will generate Lab values that match those given by EasyRGB but not those given by Photoshop.

    If you want to write code supporting both D50 and D65 illuminants, you need to use different values. The following values are from this source. In the JavaScript code above, the "D65 Illuminant, RGB to XYZ" set of these values is hardcoded into the RGB_to_XYZ function.

    D65 Illuminant, sRGB working space, RGB to XYZ:
    0.4124564 0.3575761 0.1804375
    0.2126729 0.7151522 0.0721750
    0.0193339 0.1191920 0.9503041

    D65 Illuminant, sRGB working space, XYZ to RGB:
    3.2404542 -1.5371385 -0.4985314
    -0.9692660 1.8760108 0.0415560
    0.0556434 -0.2040259 1.0572252

    D50 Illuminant, sRGB (Bradford-adapted) working space, RGB to XYZ:
    0.4360747 0.3850649 0.1430804
    0.2225045 0.7168786 0.0606169
    0.0139322 0.0971045 0.7141733

    D50 Illuminant, sRGB (Bradford-adapted) working space, XYZ to RGB:
    3.1338561 -1.6168667 -0.4906146
    -0.9787684 1.9161415 0.0334540
    0.0719453 -0.2289914 1.4052427

    There are also different white point values to use during the XYZ to Lab and Lab to XYZ conversions, depending on your illuminant. In the JavaScript code above these variables are named ref_X, ref_Y, and ref_Z.

    D65 Illuminant, used for XYZ to Lab and Lab to XYZ conversions (source)
    X: 95.047
    Y: 100.0
    Z: 108.883

    D50 Illuminant, used for XYZ to Lab and Lab to XYZ conversions (source 1, source 2)
    X: 96.42
    Y: 100.0
    Z: 82.49

    I have tested conversions with these values myself and they do resolve the discrepancy between EasyRGB’s conversion and Photoshop’s conversion that you noticed. Using either set of values, you can go from RGB -> XYZ -> Lab -> XYZ -> RGB successfully (the original RGB values are recovered after converting). If the illuminant is D65, the XYZ and Lab values will match those given by EasyRGB. If the illuminant is D50, the Lab values will match those given by Photoshop.

    Lastly, a little bit of context about the conversion algorithms I am referencing:

    • When converting from sRGB to Lab color, you must use XYZ as an intermediate format. There is no direct conversion.
    • There are pseudocode examples of conversion formulas on the EasyRGB site, but there’s almost no annotation. I found the formulas here easier to read. They are in C#.
    • I included the sets of values necessary when converting from Lab -> XYZ -> sRGB, although the code in the original question only goes from sRGB -> XYZ -> Lab. I think it’s better to be able to convert in both directions to confirm that your code is working correctly.
    • One thing to watch out for is that Photoshop’s Color Picker tool does not display decimal places for Lab values. So if you start by entering an RGB value, take note of the Lab values in the Color Picker tool, and enter those same Lab values again, you’ll probably get a slightly different set of RGB values than you started with.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search