Representation conversions
ToRange:
PROC [v:
REAL]
RETURNS [
REAL] =
INLINE {
RETURN[IF v<0 THEN 0 ELSE IF v>1 THEN 1 ELSE v];
};
ensures that v is in [0..1]
These algorithms use the hexacone model described in
"Color Gamut Transform Pairs" by Alvy Ray Smith
Siggraph 1978, p. 12.
Algorithms from Foley and van Dam
IntensityFromRGB:
PROC [val:
RGB]
RETURNS [
REAL] ~ {
Y: REAL ~ 0.30*val.R+0.59*val.G+0.11*val.B;
RETURN [ToRange[Y]];
};
Invalid:
PUBLIC
ERROR ~
CODE;
HSLFromRGB:
PUBLIC
PROC [val:
RGB]
RETURNS [
HSL] ~ {
red: REAL ~ ToRange[val.R];
green: REAL ~ ToRange[val.G];
blue: REAL ~ ToRange[val.B];
max: REAL ~ MAX[red, green, blue];
min: REAL ~ MIN[red, green, blue];
lightness: REAL ~ (max+min)/2;
del: REAL ~ max-min;
IF del = 0
THEN RETURN[[0, 0, lightness]] --gray
ELSE {
saturation: REAL ~ IF lightness <= 0.5 THEN del/(max+min) ELSE del/(2-max-min);
rc: REAL ~ (max-red)/del;
gc: REAL ~ (max-green)/del;
bc: REAL ~ (max-blue)/del;
hue:
REAL ¬ (
SELECT max
FROM
red => bc-gc, --between yellow and magenta
green => 2+rc-bc, --between cyan and yellow
blue => 4+gc-rc, --between magenta and cyan
ENDCASE => ERROR Invalid
)/6.0;
IF hue < 0 THEN hue ¬ hue+1;
RETURN[[hue, saturation, lightness]];
};
};
RGBFromHSL:
PUBLIC
PROC [val:
HSL]
RETURNS [
RGB] ~ {
m1, m2, hue, saturation, lightness, r, g, b: REAL;
Value:
PROC [n1, n2, h1:
REAL]
RETURNS [v:
REAL] = {
IF h1 > 360 THEN h1 ¬ h1-360;
IF h1 < 0 THEN h1 ¬ h1+360;
v ¬
SELECT
TRUE
FROM
h1 IN [0..60) => n1+(n2-n1)*h1/60,
h1 IN [60..180) => n2,
h1 IN [180..240) => n1+(n2-n1)*(240-h1)/60,
ENDCASE => n1;
};
IF val.S=0 THEN RETURN[[val.L, val.L, val.L]];
saturation ¬ ToRange[val.S];
lightness ¬ ToRange[val.L];
hue ¬ 360*ToRange[val.H];
m2 ¬
IF lightness <= 0.5
THEN lightness*(1+saturation)
ELSE lightness+saturation-lightness*saturation;
m1 ¬ 2*lightness-m2;
r ¬ Value[m1, m2, hue+120];
g ¬ Value[m1, m2, hue];
b ¬ Value[m1, m2, hue-120];
RETURN[[r, g, b]];
};
HSVFromRGB:
PUBLIC
PROC [val:
RGB]
RETURNS [
HSV] ~ {
r: REAL ~ ToRange[val.R];
g: REAL ~ ToRange[val.G];
b: REAL ~ ToRange[val.B];
min: REAL ~ MIN[r, g, b]; -- amount of white
max: REAL ~ MAX[r, g, b]; -- maximum "brightness"
value: REAL ~ max;
saturation: REAL ~ IF max#0 THEN (max-min)/max ELSE 0;
IF saturation = 0
THEN RETURN[[0, 0, value]] --gray
ELSE {
rc: REAL ~ (max - r)/(max - min);
gc: REAL ~ (max - g)/(max - min);
bc: REAL ~ (max - b)/(max - min);
hue:
REAL ¬ (
SELECT max
FROM
r => bc-gc,
g => 2+rc-bc,
b => 4+gc-rc,
ENDCASE => ERROR)/6.0;
IF hue<0 THEN hue ¬ hue + 1;
RETURN[[hue, saturation, value]];
};
};
RGBFromHSV:
PUBLIC
PROC [val:
HSV]
RETURNS [
RGB] ~ {
hue, saturation, value: REAL;
ihue: INT;
fhue, m, n, k: REAL;
IF val.V=0 OR val.S=0 THEN RETURN[[val.V, val.V, val.V]];
hue ¬ ToRange[val.H];
saturation ¬ ToRange[val.S];
value ¬ ToRange[val.V];
hue ¬ hue*6;
ihue ¬ Real.Fix[hue]; --integer hue
fhue ¬ hue-ihue; --fractional hue
IF ihue=6 THEN ihue ¬ 0;
m ¬ value*(1-saturation);
n ¬ value*(1-(saturation*fhue));
k ¬ value*(1-(saturation*(1-fhue)));
SELECT ihue
FROM
0 => RETURN[[value,k,m]];
1 => RETURN[[n,value,m]];
2 => RETURN[[m,value,k]];
3 => RETURN[[m,n,value]];
4 => RETURN[[k,m,value]];
5 => RETURN[[value,m,n]];
ENDCASE => RETURN[[0,0,0]];
};
YIQFromRGB:
PUBLIC
PROC [val:
RGB]
RETURNS [
YIQ] ~ {
0.3, 0.59, 0.11
0.6, -0.28, -0.32
0.21, -0.52, 0.31
RETURN[[
Y: 0.30*val.R+0.59*val.G+0.11*val.B,
I: 0.60*val.R-0.28*val.G-0.32*val.B,
Q: 0.21*val.R-0.52*val.G+0.31*val.B
]];
};
RGBFromYIQ:
PUBLIC
PROC [val:
YIQ]
RETURNS [
RGB] ~ {
1.0, 0.9482623, 0.6240127
1.0, -0.2760664, -0.6398104
1.0, -1.10545, 1.729858
RETURN[[
R: 1.0*val.Y+0.9482623*val.I+0.6240127*val.Q,
G: 1.0*val.Y-0.2760664*val.I-0.6398104*val.Q,
B: 1.0*val.Y-1.10545*val.I+1.729858*val.Q
]];
};
RGBFromYES:
PUBLIC
PROC[val:
YES]
RETURNS[
RGB] = {
Previous values were:
1.0, 1.422, 0.124
1.0, -0.578, 0.124
1.0, 0.422, -1.876
New values from Rob Buckley, December 6, 1988:
RETURN[[
R: 1.0*val.Y+1.432*val.E+0.125*val.S,
G: 1.0*val.Y-0.568*val.E+0.125*val.S,
B: 1.0*val.Y+0.432*val.E-1.875*val.S
]];
};
YESFromRGB:
PUBLIC
PROC[val:
RGB]
RETURNS[
YES] = {
Previous values were:
0.258, 0.680, 0.062
0.5, -0.5, 0
0.25, 0.25, -0.5
New values from Rob Buckley, December 6, 1988:
RETURN[[
Y: 0.253*val.R+0.684*val.G+0.063*val.B,
E: 0.5*val.R-0.5*val.G,
S: 0.25*val.R+0.25*val.G-0.5*val.B
]];
};
Simplest implementation produces R=1-C, G=1-M, B=1-Y.
CMYFromRGB:
PUBLIC
PROC [val:
RGB]
RETURNS [
CMY] = {
RETURN[[C: 1-val.R, M: 1-val.G, Y: 1-val.B]];
};
RGBFromCMY:
PUBLIC
PROC [val:
CMY]
RETURNS [
RGB] = {
RETURN[[R: 1-val.C, G: 1-val.M, B: 1-val.Y]];
};
RGBFromCMYK: PROC [val: CMYK] RETURNS [RGB] = {
This uses a simple-minded algorithm for approximating the effect of the black printer
darken: REAL ~ 1-val.K;
R: REAL ~ (1-val.C)*darken;
G: REAL ~ (1-val.M)*darken;
B: REAL ~ (1-val.Y)*darken;
RETURN[[R, G, B]];
};
Functions involving XYZ and its descendants
Transf: TYPE ~ ARRAY [0 .. 3) OF ARRAY [0 .. 3) OF REAL;
Triple: TYPE ~ RECORD [r, s, t: REAL];
xYZtoXeroxRGB: Transf ← [[2.944, -1.461, -0.457], [-1.095, 2.026, 0.036], [0.078, -0.272, 1.452]];
xYZtoYES: Transf ~ [[0.000, 1.000, 0.000], [2.019, -1.743, -0.246], [0.423, 0.277, -0.831]];
xeroxRGBtoXYZ: Transf ~ [[0.469, 0.357, 0.139], [0.253, 0.684, 0.063], [0.022, 0.109, 0.693]];
xeroxRGBtoYES: Transf ~ [[0.253, 0.684, 0.063], [0.500, -0.500, 0.000], [0.250, 0.250, -0.500]];
yEStoXYZ: Transf ~ [[0.964, 0.528, -0.157], [1.000, 0.000, 0.000], [0.825, 0.269, -1.283]];
yEStoXeroxRGB: Transf ~ [[1.000, 1.432, 0.125], [1.000, -0.568, 0.125], [1.000, 0.432, -1.875]];
The values above are those specified in the Standard. Those below are more precise values published later in document X9000020.
xYZtoXeroxRGB: Transf ←
[[2.943758, -1.460787, -0.456810],
[-1.094978, 2.025751, 0.036025],
[0.077858, -0.272368, 1.451821]];
xYZtoYES: Transf ~
[[0.000000, 1.000000, 0.000000],
[2.019368, -1.743269, -0.246417],
[0.423266, 0.277425, -0.831107]];
xeroxRGBtoXYZ: Transf ~
[[0.468682, 0.356608, 0.138620],
[0.252939, 0.684458, 0.062603],
[0.022318, 0.109283, 0.693101]];
xeroxRGBtoYES: Transf ~
[[0.252939, 0.684458, 0.062603],
[0.500000, -0.500000, 0.000000],
[0.250000, 0.250000, -0.500000]];
yEStoXYZ: Transf ~
[[0.963910, 0.528019, -0.156554],
[1.000000, 0.000000, 0.000000],
[0.824702, 0.268909, -1.282945]];
yEStoXeroxRGB: Transf ~
[[1.000000, 1.431519, 0.125205],
[1.000000, -0.568481, 0.125205],
[1.000000, 0.431519, -1.874795]];
xYZtoNTSCRGB: Transf ~ [[1.910, -0.532, -0.288], [-0.985, 2.000, -0.028], [0.058, -0.118, 0.898]];
xYZtoYIQ: Transf ~ [[0.000, 1.000, 0.000], [1.389, -0.827, -0.453], [0.937, -1.194, 0.233]];
nTSCRGBtoXYZ: Transf ~ [[0.607, 0.174, 0.200], [0.299, 0.587, 0.114], [0.000, 0.066, 1.116]];
nTSCRGBtoYIQ: Transf ~ [[0.299, 0.587, 0.114], [0.596, -0.274, -0.322], [0.211, -0.523, 0.311]];
yIQtoXYZ: Transf ~ [[0.981, 0.312, 0.605], [1.000, 0.000, 0.000], [1.182, -1.252, 1.857]];
yIQtoNTSCRGB: Transf ~ [[1.000, 0.956, 0.621], [1.000, -0.272, -0.649], [1.000, -1.106, 1.703]];
yEStoNTSCRGB: Transf ~ [[1.000, 0.816, 0.029], [1.000, -0.538, 0.219], [1.000, 0.409, -1.769]];
yEStoYIQ: Transf ~ [[1.000, -0.025, -0.065], [0.000, 0.502, 0.526], [0.000, 0.581, -0.659]];
nTSCRGBtoYES: Transf ~ [[0.343, 0.579, 0.077], [0.791, -0.715, -0.076], [0.377, 0.162, -0.539]];
yIQtoYES: Transf ~ [[1.000, 0.085, -0.031], [0.000, 1.035, 0.826], [0.000, 0.913, -0.789]];
d65ToD50, yesD65ToD50: Transf;
referenceWhite: XYZ ← [0.963910, 1.000000, 0.824702];
maxLuminance: REAL ← 1.0;
Transform:
PROC [t: Transf, v: Triple]
RETURNS [Triple] ~
BEGIN
Fast inline version for internal use. See below for exported version with type checking.
RETURN [[t[0][0]*v.r + t[0][1]*v.s + t[0][2]*v.t,
t[1][0]*v.r + t[1][1]*v.s + t[1][2]*v.t,
t[2][0]*v.r + t[2][1]*v.s + t[2][2]*v.t]]
END; -- Transform
Multiply:
PROC [a, b: Transf]
RETURNS [Transf] ~
BEGIN
Fast inline version for internal use. See below for exported version with type checking.
RETURN [[[a[0][0]*b[0][0] + a[0][1]*b[1][0] + a[0][2]*b[2][0],
a[0][0]*b[0][1] + a[0][1]*b[1][1] + a[0][2]*b[2][1],
a[0][0]*b[0][2] + a[0][1]*b[1][2] + a[0][2]*b[2][2]],
[a[1][0]*b[0][0] + a[1][1]*b[1][0] + a[1][2]*b[2][0],
a[1][0]*b[0][1] + a[1][1]*b[1][1] + a[1][2]*b[2][1],
a[1][0]*b[0][2] + a[1][1]*b[1][2] + a[1][2]*b[2][2]],
[a[2][0]*b[0][0] + a[2][1]*b[1][0] + a[2][2]*b[2][0],
a[2][0]*b[0][1] + a[2][1]*b[1][1] + a[2][2]*b[2][1],
a[2][0]*b[0][2] + a[2][1]*b[1][2] + a[2][2]*b[2][2]]]]
END; -- Multiply
D65toD50:
PROC
RETURNS [Transf] ~
BEGIN
Correcting transformation from D65 to D50.
D65ToRgb:
PROC
RETURNS [d65: Transf] ~
BEGIN
Normalizing transformation from tristimulus measured under D65 to RGB.
d65[0][0] ← 3.508; d65[0][1] ← -1.741; d65[0][2] ← -0.544;
d65[1][0] ← -1.069; d65[1][1] ← 1.977; d65[1][2] ← 0.035;
d65[2][0] ← 0.056; d65[2][1] ← -0.197; d65[2][2] ← 1.051
END; -- D65ToRgb
RgbToD50:
PROC
RETURNS [d50: Transf] ~
BEGIN
Inverse transformation from tristimulus measured under D50 to RGB.
d50[0][0] ← 0.469; d50[0][1] ← 0.357; d50[0][2] ← 0.139;
d50[1][0] ← 0.253; d50[1][1] ← 0.684; d50[1][2] ← 0.063;
d50[2][0] ← 0.022; d50[2][1] ← 0.109; d50[2][2] ← 0.693
END; -- RgbToD65
RETURN [Multiply [RgbToD50[], D65ToRgb[]]]
END; -- D65toD50
IlluminantChange:
PUBLIC
PROC [xyz65:
XYZ]
RETURNS [
XYZ] ~
BEGIN
Illuminant correction from D65 to D50 for a tristimulus.
RETURN [LOOPHOLE [Transform [d65ToD50, LOOPHOLE [xyz65]]]];
END; -- IlluminantChange
YesIlluminantChange:
PUBLIC
PROC [yes65:
YES]
RETURNS [
YES] ~
BEGIN
Illuminant correction from D65 to D50 for a YES color.
RETURN [LOOPHOLE [Transform [yesD65ToD50, LOOPHOLE [yes65]]]];
END; -- YesIlluminantChange
XeroxRGBFromXYZ:
PUBLIC
PROC [xyz:
XYZ]
RETURNS [rgb:
RGB] ~
BEGIN
rgb ← LOOPHOLE [Transform [xYZtoXeroxRGB, LOOPHOLE [xyz]]];
END;
XeroxRGBFromYES:
PUBLIC
PROC [yes:
YES]
RETURNS [rgb:
RGB] ~
BEGIN
rgb ← LOOPHOLE [Transform [yEStoXeroxRGB, LOOPHOLE [yes]]];
END;
YESFromXYZ:
PUBLIC
PROC [xyz:
XYZ]
RETURNS [yes:
YES] ~
BEGIN
yes ← LOOPHOLE [Transform [xYZtoYES, LOOPHOLE [xyz]]];
END;
YESFromXeroxRGB:
PUBLIC
PROC [rgb:
RGB]
RETURNS [yes:
YES] ~
BEGIN
yes ← LOOPHOLE [Transform [xeroxRGBtoYES, LOOPHOLE [rgb]]];
END;
XYZFromXeroxRGB:
PUBLIC
PROC [rgb:
RGB]
RETURNS [xyz:
XYZ] ~
BEGIN
xyz ← LOOPHOLE [Transform [xeroxRGBtoXYZ, LOOPHOLE [rgb]]]
END;
XYZFromYES:
PUBLIC
PROC [yes:
YES]
RETURNS [xyz:
XYZ] ~
BEGIN
xyz ← LOOPHOLE [Transform [yEStoXYZ, LOOPHOLE [yes]]];
END;
NTSCRGBFromXYZ:
PUBLIC
PROC [xyz:
XYZ]
RETURNS [rgb:
RGB] ~
BEGIN
rgb ← LOOPHOLE [Transform [xYZtoNTSCRGB, LOOPHOLE [xyz]]];
END;
NTSCRGBFromYIQ:
PUBLIC
PROC [yiq:
YIQ]
RETURNS [rgb:
RGB] ~
BEGIN
rgb ← LOOPHOLE [Transform [yIQtoNTSCRGB, LOOPHOLE [yiq]]];
END;
YIQFromXYZ:
PUBLIC
PROC [xyz:
XYZ]
RETURNS [yiq:
YIQ] ~
BEGIN
yiq ← LOOPHOLE [Transform [xYZtoYIQ, LOOPHOLE [xyz]]];
END;
YIQFromNTSCRGB:
PUBLIC
PROC [rgb:
RGB]
RETURNS [yiq:
YIQ] ~
BEGIN
yiq ← LOOPHOLE [Transform [nTSCRGBtoYIQ, LOOPHOLE [rgb]]];
END;
XYZFromNTSCRGB:
PUBLIC
PROC [rgb:
RGB]
RETURNS [xyz:
XYZ] ~
BEGIN
xyz ← LOOPHOLE [Transform [nTSCRGBtoXYZ, LOOPHOLE [rgb]]];
END;
XYZFromYIQ:
PUBLIC
PROC [yiq:
YIQ]
RETURNS [xyz:
XYZ] ~
BEGIN
xyz ← LOOPHOLE [Transform [yIQtoXYZ, LOOPHOLE [yiq]]];
END;
NTSCRGBFromYES:
PUBLIC
PROC [yes:
YES]
RETURNS [rgb:
RGB] ~
BEGIN
rgb ← LOOPHOLE [Transform [yEStoNTSCRGB, LOOPHOLE [yes]]];
END;
YIQFromYES:
PUBLIC
PROC [yes:
YES]
RETURNS [yiq:
YIQ] ~
BEGIN
yiq ← LOOPHOLE [Transform [yEStoYIQ, LOOPHOLE [yes]]];
END;
YESFromNTSCRGB:
PUBLIC
PROC [rgb:
RGB]
RETURNS [yes:
YES] ~
BEGIN
yes ← LOOPHOLE [Transform [nTSCRGBtoYES, LOOPHOLE [rgb]]];
END;
YESFromYIQ:
PUBLIC
PROC [yiq:
YIQ]
RETURNS [yes:
YES] ~
BEGIN
yes ← LOOPHOLE [Transform [yIQtoYES, LOOPHOLE [yiq]]];
END;
CIELABFromXYZ:
PUBLIC
PROC [xyz, white:
XYZ]
RETURNS [
CIELAB] ~
BEGIN
fy: REAL;
IF (white.Y = 0) THEN white ← [referenceWhite.X * maxLuminance, maxLuminance, referenceWhite.Z * maxLuminance];
fy ← F [xyz.Y / white.Y];
RETURN [[Lightness [xyz.Y, white.Y], (500.0*(F[xyz.X/white.X]-fy)), (200.0*(fy-F[xyz.Z/white.Z]))]];
END; -- CIELABFromXYZ
XYZFromCIELAB:
PUBLIC
PROC [lab:
CIELAB, white:
XYZ]
RETURNS [
XYZ] ~
BEGIN
xyz: XYZ; fY: REAL ~ (lab.lStar + 16.0) / 116.0;
x: REAL ~ lab.aStar / 500.0 + fY; z: REAL ~ -lab.bStar / 200.0 + fY;
IF (white.Y = 0) THEN white ← [referenceWhite.X * maxLuminance, maxLuminance, referenceWhite.Z * maxLuminance];
xyz.Y ← Luminance [lab.lStar, white.Y];
IF (lab.lStar > 7.999554) THEN {xyz.X ← white.X * x*x*x; xyz.Z ← white.Z * z*z*z}
ELSE {xyz.X ← white.X/7.787*(x-16.0/116.0); xyz.Z ← white.Z/7.787*(z-16.0/116.0)};
RETURN [xyz]
END; -- CIELABFromXYZ
NormXYZFromCIELAB:
PROC [lab:
CIELAB]
RETURNS [
XYZ] ~
BEGIN
white: XYZ ~ referenceWhite;
xyz: XYZ; fY: REAL ~ (lab.lStar + 16.0) / 116.0;
x: REAL ~ lab.aStar / 500.0 + fY; z: REAL ~ -lab.bStar / 200.0 + fY;
xyz.Y ← NormLuminance [lab.lStar];
IF (lab.lStar > 7.999554) THEN {xyz.X ← white.X * x*x*x; xyz.Z ← white.Z * z*z*z}
ELSE {xyz.X ← white.X/7.787*(x-16.0/116.0); xyz.Z ← white.Z/7.787*(z-16.0/116.0)};
RETURN [xyz]
END; -- NormXYZFromCIELAB
F:
PROC [t:
REAL]
RETURNS [
REAL] ~
BEGIN
RETURN [IF t > 0.008856 THEN RealFns.Power [t, 1.0/3.0] ELSE 7.787*t + (16.0/116.0)]
END; -- F
Lightness:
PUBLIC
PROC [Y, whiteY:
REAL]
RETURNS [
REAL] ~
BEGIN
Returns L* from luminance Y.
t: REAL;
IF (whiteY = 0) THEN whiteY ← maxLuminance;
t ← Y / whiteY;
IF (t <= 0.008856) THEN RETURN [903.292 * t]
ELSE RETURN [116.0 * F[t] - 16.0]
END; -- Lightness
Luminance:
PUBLIC
PROC [lStar, whiteY:
REAL]
RETURNS [
REAL] ~
BEGIN
Returns luminance Y from L*. 903.292 * 0.008856 = 7.999554.
IF (whiteY = 0) THEN whiteY ← maxLuminance;
IF (lStar <= 7.999554) THEN RETURN [whiteY*lStar/903.292]
ELSE {p: REAL ~ (lStar+16.0)/116.0; RETURN [p*p*p * whiteY]}
END; -- Luminance
NormLuminance:
PROC [lStar:
REAL]
RETURNS [
REAL] ~
BEGIN
Returns normalized luminance 0 d Y d 1 from L*. 903.292 * 0.008856 = 7.999554.
IF (lStar <= 7.999554) THEN RETURN [lStar/903.292]
ELSE {p: REAL ~ (lStar+16.0)/116.0; RETURN [p*p*p]}
END; -- NormLuminance
MetricChromaticity: TYPE ~ RECORD [uPrime, vPrime: REAL];
UVFromXYZ:
PROC [xyz:
XYZ]
RETURNS [MetricChromaticity] ~
BEGIN
u' = 4X / (X + 15Y + 3Z) and v' = 9Y / (X + 15Y + 3Z)
norm: REAL ~ 1.0 / (xyz.X + 15*xyz.Y + 3*xyz.Z);
RETURN [[4 * xyz.X * norm, 9 * xyz.Y * norm]]
END; -- UVFromXYZ
CIELUVFromXYZ:
PUBLIC
PROC [xyz, white:
XYZ]
RETURNS [
CIELUV] ~
BEGIN
luv: CIELUV; whiteChr, uv: MetricChromaticity;
IF (white.Y = 0) THEN white ← [referenceWhite.X * maxLuminance, maxLuminance, referenceWhite.Z * maxLuminance];
whiteChr ← UVFromXYZ [white];
uv ← IF (xyz.X + 15*xyz.Y + 3*xyz.Z > 0) THEN UVFromXYZ [xyz] ELSE whiteChr;
luv.lStar ← Lightness [xyz.Y, white.Y];
luv.uStar ← 13 * luv.lStar * (uv.uPrime - whiteChr.uPrime);
luv.vStar ← 13 * luv.lStar * (uv.vPrime - whiteChr.vPrime);
RETURN [luv]
END;
XYZFromCIELUV:
PUBLIC
PROC [luv:
CIELUV, white:
XYZ]
RETURNS [
XYZ] ~
BEGIN
whiteChr, uv: MetricChromaticity; chr: Yxy; norm: REAL;
IF (white.Y = 0) THEN white ← [referenceWhite.X * maxLuminance, maxLuminance, referenceWhite.Z * maxLuminance];
whiteChr ← UVFromXYZ [white];
uv ← [luv.uStar/(13*luv.lStar)+whiteChr.uPrime, luv.vStar/(13*luv.lStar)+whiteChr.vPrime];
chr.Y ← Luminance [luv.lStar, white.Y]; norm ← 1.0 / (18*uv.uPrime - 48*uv.vPrime + 36);
chr.x ← 27 * uv.uPrime * norm; chr.y ← 12 * uv.vPrime * norm;
RETURN [Tristimulus [chr]]
END; -- CIELUVFromXYZ
Colorimetry Utilities
Tristimulus:
PUBLIC
PROC [yxy: Yxy]
RETURNS [xyz:
XYZ] ~
BEGIN
Converts from the CIE 1931 Yxy color system to tristimuls values.
IF (yxy.y = 0) THEN RETURN [[0, 0, 0]];
xyz.X ← yxy.Y * (yxy.x / yxy.y); xyz.Y ← yxy.Y;
xyz.Z ← yxy.Y * (1.0 - yxy.x - yxy.y) / yxy.y
END; -- Tristimulus
ABDifference:
PUBLIC
PROC [c1, c2:
CIELAB]
RETURNS [
REAL] ~
BEGIN
CIE 1976 color stimulus difference.
d: RECORD [r, s, t: REAL] ~ [(c1.lStar - c2.lStar), (c1.aStar - c2.aStar), ( c1.bStar - c2.bStar)];
RETURN [RealFns.SqRt [d.r*d.r + d.s*d.s + d.t*d.t]]
END; -- ABDifference
CMCDifference:
PUBLIC
PROC [c1, c2:
CIELAB]
RETURNS [
REAL] ~
BEGIN
CMC(l:c) color stimulus difference. Uses a formula which has been shown to give a better correlation for small color differences in the colorant industries. The relative weightings of the contributions of the differences in L*, C*ab, H*ab, are varied according to the position of the color in the CIELAB space.
cStar: REAL ~ ABChroma [c1]; -- difference is small, so c1 or c2 does not matter
hStar: REAL ~ ABHueAngle [c1]; -- difference is small, so c1 or c2 does not matter
f, t, sl, sc, sh: REAL; cStar4: REAL ~ RealFns.Power [cStar, 4]; d: RECORD [l, c, h: REAL];
t ←
IF (hStar > 164)
AND (hStar < 345)
THEN 0.56 +
ABS [0.2 * RealFns.CosDeg [hStar + 168]]
ELSE 0.36 + ABS [0.4 * RealFns.CosDeg [hStar + 35]];
f ← RealFns.SqRt [cStar4 / (cStar4 + 1900)];
sl ← IF (c1.lStar < 16) THEN 0.511 ELSE 0.040975 * c1.lStar / (1.0 + 0.01765 * c1.lStar);
sc ← 0.0638 * cStar / (1.0 + 0.0131 * cStar) + 0.638;
sh ← (f * t + 1 - f ) * sc;
d ← [(c1.lStar - c2.lStar) / sl, (c1.aStar - c2.aStar) / sc, ( c1.bStar - c2.bStar) / sh];
RETURN [RealFns.SqRt [d.l*d.l + d.c*d.c + d.h*d.h]]
END; -- CMCDifference
HCLDifferenceCIELAB:
PUBLIC
PROC [c1, c2:
CIELAB]
RETURNS [hue, chroma, lightness:
REAL] ~
BEGIN
For compatibility with CalibratedColorFns.
diff: REAL ← ABDifference [c1, c2];
chroma ← ABChroma [c1] - ABChroma [c2]; lightness ← c1.lStar - c2.lStar;
hue ← RealFns.SqRt [diff*diff - lightness*lightness - chroma*chroma];
END; -- HCLDifferenceCIELAB
ABChroma:
PUBLIC
PROC [c:
CIELAB]
RETURNS [
REAL] ~
BEGIN
CIE 1976 a,b chroma.
RETURN [RealFns.SqRt[c.aStar*c.aStar + c.bStar*c.bStar]]
END; -- ABChroma
ABHueAngle:
PUBLIC
PROC [c:
CIELAB]
RETURNS [
REAL] ~
BEGIN
CIE 1976 a,b hue-angle.
a: REAL ~ RealFns.ArcTanDeg [y: c.bStar, x: c.aStar];
IF (a < 0) THEN RETURN [360 + a] ELSE RETURN [a]
END; -- ABHueAngle
ABHueDifference:
PUBLIC
PROC [c1, c2:
CIELAB]
RETURNS [
REAL] ~
BEGIN
CIE 1976 a,b hue-difference.
h1: REAL ~ ABHueAngle [c1]; h2: REAL ~ ABHueAngle [c2];
e: REAL ~ ABDifference [c1, c2];
l: REAL ~ c1.lStar - c2.lStar; c: REAL ~ ABChroma [c1] - ABChroma [c2];
hDiff: REAL ~ RealFns.SqRt [e*e - l*l - c*c];
IF (h2 > h1) THEN RETURN [hDiff] ELSE RETURN [- hDiff]
END; -- ABHueDifference
The following is for convenience of ``cylindrical coordinate clients'' and not part of the Standard.
LabToLch:
PUBLIC
PROC [lab:
CIELAB]
RETURNS [LCh] ~
BEGIN
Transforms from cartesian to cylindrical CIELAB coordinates.
RETURN [[lab.lStar, ABChroma [lab], ABHueAngle [lab]]]
END; -- LabToLch
LchToLab:
PUBLIC
PROC [lch: LCh]
RETURNS [
CIELAB] ~
BEGIN
Transforms from cartesian to cylindrical CIELAB coordinates.
RETURN [[lch.lStar, lch.cStar * RealFns.CosDeg [lch.hStar], lch.cStar * RealFns.SinDeg [lch.hStar]]]
END; -- LchToLab
UVDifference:
PUBLIC
PROC [c1, c2:
CIELUV]
RETURNS [
REAL] ~
BEGIN
CIE 1976 color stimulus difference.
d: RECORD [r, s, t: REAL] ~ [(c1.lStar - c2.lStar), (c1.uStar - c2.uStar), (c1.vStar - c2.vStar)];
RETURN [RealFns.SqRt [d.r*d.r + d.s*d.s + d.t*d.t]]
END; -- UVDifference
HCLDifferenceCIELUV:
PUBLIC
PROC [c1, c2:
CIELUV]
RETURNS [hue, chroma, lightness:
REAL] ~
BEGIN
For compatibility with CalibratedColorFns.
diff: REAL ← UVDifference [c1, c2];
chroma ← UVChroma [c1] - UVChroma [c2]; lightness ← c1.lStar - c2.lStar;
hue ← RealFns.SqRt [diff*diff - lightness*lightness - chroma*chroma];
END; -- HCLDifferenceCIELUV
UVChroma:
PUBLIC
PROC [c:
CIELUV]
RETURNS [
REAL] ~
BEGIN
CIE 1976 u,v chroma.
RETURN [RealFns.SqRt [c.uStar*c.uStar + c.vStar*c.vStar]]
END; -- UVChroma
UVSaturation:
PUBLIC
PROC [c:
CIELUV]
RETURNS [
REAL] ~
BEGIN
CIE 1976 u,v chroma.
RETURN [RealFns.SqRt [c.uStar*c.uStar + c.vStar*c.vStar] / c.lStar]
END; -- UVSaturation
UVHueAngle:
PUBLIC
PROC [c:
CIELUV]
RETURNS [
REAL] ~
BEGIN
CIE 1976 u,v hue-angle.
a: REAL ~ RealFns.ArcTanDeg [y: c.vStar, x: c.uStar];
IF (a < 0) THEN RETURN [360 + a] ELSE RETURN [a]
END; -- UVHueAngle
UVHueDifference:
PUBLIC
PROC [c1, c2:
CIELUV]
RETURNS [
REAL] ~
BEGIN
CIE 1976 u,v hue-difference.
h1: REAL ~ UVHueAngle [c1]; h2: REAL ~ UVHueAngle [c2];
e: REAL ~ UVDifference [c1, c2];
l: REAL ~ c1.lStar - c2.lStar; c: REAL ~ UVChroma [c1] - UVChroma [c2];
hDiff: REAL ~ RealFns.SqRt [e*e - l*l - c*c];
IF (h2 > h1) THEN RETURN [hDiff] ELSE RETURN [- hDiff]
END; -- UVHueDifference
d65ToD50 ← D65toD50 [];
yesD65ToD50 ← Multiply [Multiply [yEStoXYZ, d65ToD50], xYZtoYES];