ColorFnsImpl.mesa
Routines to compute CIELAB, CIELUV, Density, and Dot Area.
Copyright © 1986 by Xerox Corporation. All rights reserved.
Maureen Stone September 16, 1986 10:05:52 am PDT
DIRECTORY
ColorFns,
ImagerColor USING [CIEFromChromaticity],
RealFns USING [Power, Log, SqRt];
ColorFnsImpl: CEDAR PROGRAM
IMPORTS RealFns, ImagerColor
EXPORTS ColorFns
~ BEGIN OPEN ColorFns;
The CIE lightness function. This is controversial for Y/Yn < 0.01 (1%).
Note that such colors are very dark.
LStarFromLuminance: PUBLIC PROC [Y: REAL, whiteY: REAL] RETURNS[lStar: REAL] = {
L* = 116(Y/Yn)1/3 - 16 Y/Yn > 0.008856
L* = 903.29(Y/Yn) Y/Yn <= 0.008856
rel: REAL ← Y/whiteY;
IF rel > 0.008856 THEN
lStar ← 116.0*RealFns.Power[base: rel, exponent: 1.0/3.0] -16.0
ELSE lStar ← 903.29*rel;
};
LuminanceFromLStar: PUBLIC PROC [lStar: REAL, whiteY: REAL] RETURNS[Y: REAL] = {
Y = Yn ((L*+16)/116)3 Y/Yn > 0.008856
Y = YnL*/903.29
rel: REAL ← Y/whiteY;
IF lStar > 8.0 THEN
Y ← whiteY*RealFns.Power[base: ((lStar+16)/116.0), exponent: 3.0]
ELSE Y ← whiteY*lStar/903.29;
};
THE CIE 1976 Metric Chromaticity Coordinates.
These coordinates are supposed to be more evenly spaced perceptually than xy.
MetricChrFromXYZ: PUBLIC PROC[xyz: CIE] RETURNS[uv: MetricChromaticity] = {
u' = 4X/(X+15Y+3Z) v' = 9Y/(X+15Y+3Z)
The inverse function does not seem to be commonly used. That is, I don't know
what additional information (Y? L? L*?) goes with u',v' to convert back to XYZ
den: REAL ← xyz.X+15*xyz.Y+3*xyz.Z;
RETURN[[uPrime: 4*xyz.X/den, vPrime: 9*xyz.Y/den]];
};
MetricChrFromChr: PUBLIC PROC[xy: CIEChromaticity] RETURNS[uv: MetricChromaticity] = {
u' = 4x/(-2x+12y+3) v' = 9y/(-2x+12y+3)
den: REAL ← -2*xy.x+12*xy.y+3;
RETURN[[uPrime: 4*xy.x/den, vPrime: 9*xy.y/den]];
};
ChrFromMetricChr: PUBLIC PROC[uv: MetricChromaticity] RETURNS[xy: CIEChromaticity] = {
x = 27u'/(18u'-48v'+36)  y = 12v'/(18u'-48v'+36)
den: REAL ← 18*uv.uPrime-48*uv.vPrime+36;
RETURN[[x: 27*uv.uPrime/den, y: 12*uv.vPrime/den]];
};
In CIELAB or CIELUV, the perceptual difference between two colors is approximately the euclidian distance between the two points in the color space. One unit equals a "just noticeable difference," 2-3 units equals a "noticeable" color difference. Both use the same function for lightness, L*, defined above, with the same caveat for very dark colors. Both are defined relative to a "reference white". Both were standardized by the CIE as they both were in common use and were judged to work equally well.
CIELABFromXYZ: PUBLIC PROC [xyz: CIE, white: CIE] RETURNS[lab: CIELAB] = {
The equations for CIELAB are:
a* = 500[(X/Xn)1/3 - (Y/Yn)1/3]
b* = 200[(Z/Zn)1/3 - (Y/Yn)1/3]
where Xn, Yn, Zn are the tristimulus values of the reference white. For values of X/Xn, Y/Yn, or Z/Zn less than 0.008856 use ??
cubeRootY: REAL ← RealFns.Power[base: xyz.Y/white.Y, exponent: 1.0/3.0];
lab.lStar ← LStarFromLuminance[xyz.Y, white.Y];
lab.aStar ← 500*(RealFns.Power[base: xyz.X/white.X, exponent: 1.0/3.0]-cubeRootY);
lab.bStar ← 200*(cubeRootY-RealFns.Power[base: xyz.Z/white.Z, exponent: 1.0/3.0]);
};
XYZFromCIELAB: PUBLIC PROC [lab: CIELAB, white: CIE] RETURNS[xyz: CIE] = {
val: REAL ← (lab.lStar+16)/116.0;
xyz.Y ← LuminanceFromLStar[lab.lStar, white.Y];
xyz.X ← white.X*RealFns.Power[base: (val+lab.aStar/500.0), exponent: 3];
xyz.Z ← white.Z*RealFns.Power[base: (val-lab.bStar/200.0), exponent: 3];
};
CIELUVFromXYZ: PUBLIC PROC [xyz: CIE, white: CIE] RETURNS[luv: CIELUV] = {
The equations for CIELUV are:
u* = 13L*(u'-un')
v* = 13L*(v'-vn')
where u' and un' are the Metric Chromaticiy coordinates of the value and the reference white
note that the MetricChromaticity is undefined for X=Y=Z=0, but u* and v* will be dominated
by L*, hence the special case.
uvWhite: MetricChromaticity ← MetricChrFromXYZ[white];
luv.lStar ← LStarFromLuminance[xyz.Y, white.Y];
IF luv.lStar > 10E-8 THEN {
uv: MetricChromaticity ← MetricChrFromXYZ[xyz];
luv.uStar ← 13*luv.lStar*(uv.uPrime-uvWhite.uPrime);
luv.vStar ← 13*luv.lStar*(uv.vPrime-uvWhite.vPrime);
}
ELSE {luv.uStar ← luv.vStar ← 13*luv.lStar};
};
XYZFromCIELUV: PUBLIC PROC [luv: CIELUV, white: CIE] RETURNS[xyz: CIE] = {
u' ← u*/13L* + u'n
v' ← v*/13L* + v'n
Y ← LuminanceFromLStar[L*]
xy ← ChrFromMetricChr[uv]
XYZ ← CIEFromChromaticity[xy, Y]
uvWhite: MetricChromaticity ← MetricChrFromXYZ[white];
uv: MetricChromaticity ← [
uPrime: luv.uStar/(13*luv.lStar)+uvWhite.uPrime,
vPrime: luv.vStar/(13*luv.lStar)+uvWhite.vPrime];
xy: CIEChromaticity ← ChrFromMetricChr[uv];
Y: REAL ← LuminanceFromLStar[luv.lStar, white.Y];
xyz ← ImagerColor.CIEFromChromaticity[xy,Y];
};
DifferenceCIELAB: PUBLIC PROC [color1, color2: CIELAB] RETURNS [difference: REAL] = {
delL: REAL ← color2.lStar-color1.lStar;
delA: REAL ← color2.aStar-color1.aStar;
delB: REAL ← color2.bStar-color1.bStar;
RETURN[RealFns.SqRt[delL*delL+delA*delA+delB*delB]];
};
DifferenceCIELUV: PUBLIC PROC [color1, color2: CIELUV] RETURNS [difference: REAL] = {
delL: REAL ← color2.lStar-color1.lStar;
delU: REAL ← color2.uStar-color1.uStar;
delV: REAL ← color2.vStar-color1.vStar;
RETURN[RealFns.SqRt[delL*delL+delU*delU+delV*delV]];
};
For ideal reflectance, or for Transmittance=1-R.
Used for computing density from ideal dot area on halftoned films.
DensityFromReflectance: PUBLIC PROC[r: REAL] RETURNS[REAL] = {
d ← Log[1/R]. Reflectance of 0 will return a density of 5.0 (R=0.001)
Reflectance is percentage [0+..100].
RETURN[IF r=0 THEN 5.0 ELSE RealFns.Log[base: 10, arg: (100.0/r)]];
};
ReflectanceFromDensity: PUBLIC PROC[d: REAL] RETURNS[REAL] = {
Density is positive. To be consistant with procedure above, Density in [0..3]
RETURN[100.0/RealFns.Power[base: 10, exponent: d]];
};
Density measured from halftoned patterns on prints does not follow the equations above.
Here are equations relating density and dot area for reflective prints.
DensityFromDotArea: PUBLIC PROC[area: REAL, solidD: REAL ← 1.5, n: REAL ← 1.4] RETURNS[density: REAL] = {
area is percentage in [0..100], Density is positive. D = -n*log[1-a(1-10-solidD/n)]
If n=1 then reduces to the Murray-Davis equations.
density ← -n*RealFns.Log[
base: 10,
arg: (1-area*(1-RealFns.Power[base: 10, exponent: -solidD/n])/100.0)];
RETURN[density];
};
area is percentage in [0..100], Density is positive. D = -n*log[1-a(1-10-solidD/n)]
If n=1 then reduces to the Murray-Davis equations.
DotAreaFromDensity: PUBLIC PROC[density: REAL, solidD: REAL ← 1.5, n: REAL ← 1.4] RETURNS[area: REAL] = {
area is percentage in [0..100], Density is positive. Inverse of above
area ← 100.0*(1-RealFns.Power[base: 10, exponent: -density/n])/
(1-RealFns.Power[base: 10, exponent: -solidD/n]);
RETURN[area];
};
area is percentage in [0..100], Density is positive. Inverse of above
END.