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.