unit RGBCIEUtils;

interface

uses
  SysUtils, LCLIntf, Graphics, Math;

const
  {// Observer= 2°, Illuminant= D65    - Daylignt
  ref_X =  95.047;
  ref_Z = 108.883;
  // Observer= 10°, Illuminant= D65   - Daylight
  ref_X =  94.811;
  ref_Z = 35.2;

  // Observer= 2°, Illuminant= A      - Incadescent
  ref_X =  109.850;
  ref_Z = 35.585;
  // Observer= 10°, Illuminant= A     - Incadescent
  ref_X =  111.144;
  ref_Z = 35.2;

  // Observer= 2°, Illuminant= C
  ref_X =  98.074;
  ref_Z = 118.232;
  // Observer= 10°, Illuminant= C
  ref_X =  97.285;
  ref_Z = 116.145;
                }
  // Observer= 2°, Illuminant= D50
  ref_X =  96.422;
  ref_Z = 82.521;{
  // Observer= 10°, Illuminant= D50 - Photoshop
  ref_X =  96.72;
  ref_Z = 81.427; }

  {// Observer= 2°, Illuminant= D55
  ref_X =  95.682;
  ref_Z = 92.149;
  // Observer= 10°, Illuminant= D55
  ref_X =  95.799;
  ref_Z = 90.926;

  // Observer= 2°, Illuminant= D75
  ref_X =  94.972;
  ref_Z = 122.638;
  // Observer= 10°, Illuminant= D75
  ref_X =  94.416;
  ref_Z = 12.641;

  // Observer= 2°, Illuminant= F2     - Fluorescent
  ref_X =  99.187;
  ref_Z = 67.395;
  // Observer= 10°, Illuminant= F2    - Fluorescent
  ref_X =  103.28;
  ref_Z = 69.026;

  // Observer= 2°, Illuminant= F7
  ref_X =  95.044;
  ref_Z = 108.755;
  // Observer= 10°, Illuminant= F7
  ref_X =  95.792;
  ref_Z = 107.678;

  // Observer= 2°, Illuminant= F11
  ref_X =  100.966;
  ref_Z = 64.370;
  // Observer= 10°, Illuminant= F11
  ref_X =  103.866;
  ref_Z = 65.627;   }

type
  xyz = record
    x: Double;
    y: Double;
    z: Double;
  end;

function LabToXYZ(l, a, b: double): xyz;
function XYZToRGB(space: xyz): TColor;
function LabToRGB(l, a, b: double): TColor;
function RGBToXYZ(c: TColor): xyz;
procedure RGBToLab(clr: TColor; var l, a, b: double);
procedure XYZToLab(space: xyz; var l, a, b: double);
procedure LCHToLab(lum, c, h: double; var l, a, b: double);
procedure LabToLCH(l, a, b: double; var lum, c, h: double);
function LCHToRGB(l, c, h: double): TColor;
procedure RGBToLCH(clr: TColor; var l, c, h: double);
function GetCIEXValue(c: TColor): double;
function GetCIEYValue(c: TColor): double;
function GetCIEZValue(c: TColor): double;
function GetCIELValue(c: TColor): double;
function GetCIEAValue(c: TColor): double;
function GetCIEBValue(c: TColor): double;
function GetCIECValue(c: TColor): double;
function GetCIEHValue(c: TColor): double;

implementation

uses
  mbUtils;

function LabToXYZ(l, a, b: double): xyz;
var
  x, y, z: double;
begin
  y := (l + 16)/116;
  x := a/500 + y;
  z := y - b/200;
  if y > 0.2069 then
    y := IntPower(y, 3)
  else
    y := (y - 0.138)/7.787;
  if x > 0.2069 then
    x := IntPower(x, 3)
  else
    x := (x - 0.138)/7.787;
  if z > 0.2069 then
    z := IntPower(z, 3)
  else
    z := (z - 0.138)/7.787;
  Result.x := ref_X * x;
  Result.y := 100 * y;
  Result.z := ref_Z * z;
end;

function XYZToRGB(space: xyz): TColor;
// see:
// https://de.mathworks.com/matlabcentral/fileexchange/28790-colorspace-transformations/content/colorspace/colorspace.html?requestedDomain=www.mathworks.com
var
  r, g, b, x, y, z: double;
begin
  x := space.x/100;
  y := space.y/100;
  z := space.z/100;
  r := x * 3.2406 + y * (-1.5372) + z * (-0.49);
  g := x * (-0.969) + y * 1.8758 + z * 0.0415;
  b := x * 0.0557 + y * (-0.2040) + z * 1.0570;
  if r > 0.00313 then
    r := 1.055 * Power(r, 1/2.4) - 0.055
  else
    r := 12.92 * r;
  if g > 0.00313 then
    g := 1.055 * Power(g, 1/2.4) - 0.055
  else
    g := 12.92 * g;
  if b > 0.00313 then
    b := 1.055 * Power(b, 1/2.4) - 0.055
  else
    b := 12.92 * b;

  Clamp(r, 0, 1);
  Clamp(g, 0, 1);
  Clamp(b, 0, 1);
  Result := RGB(Round(r*255), Round(g*255), Round(b*255));
end;

function LabToRGB(l, a, b: double): TColor;
begin
  Result := XYZToRGB(LabToXYZ(l, a, b));
end;

function RGBToXYZ(c: TColor): xyz;
var
  r, g, b: double;
begin
  r := GetRValue(c)/255;
  g := GetGValue(c)/255;
  b := GetBValue(c)/255;
  if r > 0.04045 then
    r := Power((r + 0.055)/1.055, 2.4)
  else
    r := r/12.92;
  if g > 0.04045 then
    g := Power((g + 0.055)/1.055, 2.4)
  else
    g := g/12.92;
  if b > 0.04045 then
    b := Power((b + 0.055)/1.055, 2.4)
  else
    b := b/12.92;
  r := r * 100;
  g := g * 100;
  b := b * 100;
  // Observer= 2°, Illuminant= D65
  Result.x := r * 0.4124 + g * 0.3576 + b * 0.1805;
  Result.y := r * 0.2126 + g * 0.7152 + b * 0.0722;
  Result.z := r * 0.0193 + g * 0.1192 + b * 0.9505;
end;

procedure XYZToLab(space: xyz; var l, a, b: Double);
var
  x, y, z: double;
begin
  x := space.x/ref_X;
  y := space.y/100;
  z := space.z/ref_Z;
  if x > 0.008856 then
    x := Power(x, 1/3)
  else
    x := (7.787*x) + 0.138;
  if y > 0.008856 then
    y := Power(y, 1/3)
  else
    y := (7.787*y) + 0.138;
  if z > 0.008856 then
    z := Power(z, 1/3)
  else
    z := (7.787*z) + 0.138;
  l := (116*y) - 16;
  a := 500 * (x - y);
  b := 200 * (y - z);
  Clamp(l, 0, 100);
  Clamp(a, -128, 127);
  Clamp(b, -128, 127);
end;

procedure RGBToLab(clr: TColor; var l, a, b: Double);
var
  s: xyz;
begin
  s := RGBToXYZ(clr);
  XYZToLab(s, l, a, b);
end;

procedure LCHToLab(lum, c, h: double; var l, a, b: double);
begin
  l := lum;
  a := cos(DegToRad(h)) * c;
  b := sin(DegToRad(h)) * c;
end;

procedure LabToLCH(l, a, b: double; var lum, c, h: double);
begin
  h := ArcTan2(b, a);
  if h > 0 then
    h := (h/pi) * 180
  else
    h := 360 - (abs(h)/pi) * 180;
  lum := l;
  c := SQRT(a*a + b*b);
end;

procedure RGBToLCH(clr: TColor; var l, c, h: double);
var
  a: Double = 0;
  b: Double = 0;
begin
  RGBToLab(clr, l, a, b);
  LabToLCH(l, a, b, l, c, h);
end;

function LCHToRGB(l, c, h: double): TColor;
var
  lum: Double = 0;
  a: Double = 0;
  b: double = 0;
begin
  LCHToLab(l, c, h, lum, a, b);
  Result := LabToRGB(lum, a, b);
end;

function GetCIEXValue(c: TColor): double;
var
  d: xyz;
begin
  d := RGBToXYZ(c);
  Result := d.x;
end;

function GetCIEYValue(c: TColor): double;
var
  d: xyz;
begin
  d := RGBToXYZ(c);
  Result := d.y;
end;

function GetCIEZValue(c: TColor): double;
var
  d: xyz;
begin
  d := RGBToXYZ(c);
  Result := d.z;
end;

function GetCIELValue(c: TColor): double;
var
  d: Double = 0;
begin
  XYZToLab(RGBToXYZ(c), Result{%H-}, d, d);
end;

function GetCIEAValue(c: TColor): double;
var
  d: double = 0;
begin
  XYZToLab(RGBToXYZ(c), d, Result{%H-}, d);
end;

function GetCIEBValue(c: TColor): double;
var
  d: double = 0;
begin
  XYZToLab(RGBToXYZ(c), d, d, Result{%H-});
end;

function GetCIECValue(c: TColor): double;
var
  d: double = 0;
begin
  Result := 0.0;
  RGBToLCH(c, d, Result, d);
end;

function GetCIEHValue(c: TColor): double;
var
  d: double = 0;
begin
  Result := 0.0;
  RGBToLCH(c, d, d, Result);
end;

end.