ColorModelsImpl.mesa - Conversion routines between color models
Last edited by Maureen Stone, February 10, 1984 3:48:09 pm PST
Last Edited by: Pier, January 18, 1984 3:50 pm
Last Edited by: Crow, April 17, 1984 10:27:25 am PST
DIRECTORY
ColorModels,
Real    USING [FixI],
RuntimeError USING [BoundsFault],
Atom   USING [GetProp, PutProp];
ColorModelsImpl: CEDAR PROGRAM
IMPORTS Real, RuntimeError, Atom
EXPORTS ColorModels
~ BEGIN
OPEN ColorModels;
InvalidColor: PUBLIC SIGNAL=CODE;
Uninitialized: PUBLIC SIGNAL=CODE;
ToRange: PROC[v: REAL] RETURNS[REAL] = INLINE { IF v IN[0..1] THEN RETURN[v]
ELSE ERROR RuntimeError.BoundsFault };
ensures that v is in [0..1]; raises BoundsFault if not
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
HSVToRGB: PUBLIC PROC[h,s,v: REAL] RETURNS[r,g,b: REAL] = {
hue: REAL;
saturation: REAL ← ToRange[s];
value: REAL ← ToRange[v];
ihue: INTEGER;
fhue,m,n,k: REAL;
IF h=undefined THEN
IF saturation=0 THEN {r ← g ← b ← value; RETURN}
ELSE SIGNAL InvalidColor
ELSE hue ← ToRange[h];
hue ← hue*6;
ihue ← Real.FixI[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];
};
RGBToHSV: PUBLIC PROC[r,g,b: REAL] RETURNS[h,s,v: REAL] = {
max,min,rc,gc,bc: REAL;
r ← ToRange[r]; g ← ToRange[g]; b ← ToRange[b];
min ← MIN[MIN[r,g],b]; --amount of white
v ← max ← MAX[MAX[r,g],b];--maximum "brightness"
IF max#0 THEN s ← (max-min)/max
ELSE s ← 0;
IF s=0 THEN RETURN[undefined,0,v]; --gray
rc ← (max - r)/(max - min);
gc ← (max - g)/(max - min);
bc ← (max - b)/(max - min);
IF r=max THEN h�-gc
ELSE IF g=max THEN h𡤂+rc-bc
ELSE IF b=max THEN h𡤄+gc-rc;
h ← h / 6.0;
IF h<0 THEN h←h+1;
RETURN[h, s, v];
};
HSLToRGB: PUBLIC PROC[h, s, l: REAL] RETURNS[r, g, b: REAL] = {
m1,m2,hue,saturation, lightness: 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;
};
saturation ← ToRange[s];
lightness ← ToRange[l];
IF h=undefined THEN {
IF saturation#0.0 THEN SIGNAL InvalidColor;
r ← g ← b ← lightness;
}
ELSE {
hue ← 360*ToRange[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];
};
};
RGBToHSL: PUBLIC PROC[r, g, b: REAL] RETURNS[h, s, l: REAL] = {
max,min,rc,gc,bc,del: REAL;
red: REAL ← ToRange[r];
green: REAL ← ToRange[g];
blue: REAL ← ToRange[b];
max ← MAX[red,MAX[green,blue]];
min ← MIN[red,MIN[green,blue]];
l ← (max+min)/2;
IF max=min THEN RETURN[undefined,0,l]; --gray
del ← max-min;
s ← IF l <= 0.5 THEN del/(max+min) ELSE del/(2-max-min);
rc ← (max-red)/del;
gc ← (max-green)/del;
bc ← (max-blue)/del;
IF max = red THEN h ← bc-gc --between yellow and magenta
ELSE IF max = green THEN h ← 2+rc-bc --between cyan and yellow
ELSE IF max = blue THEN h ← 4+gc-rc --between magenta and cyan
ELSE SIGNAL InvalidColor;
h ← h/6.0;
IF h < 0 THEN h ← h+1;
};
InitCIE: PUBLIC PROC[xr,yr,xg,yg,xb,yb: REAL, whiteY: REAL ← 1] RETURNS [calibration: Calibration] = {
calibration ← NEW[CalibrationRec];
calibration.toCIE[0][0] ←xr; calibration.toCIE[1][0] ←yr; calibration.toCIE[2][0] 𡤁-(xr+yr);
calibration.toCIE[0][1] ←xg; calibration.toCIE[1][1] ←yg; calibration.toCIE[2][1] 𡤁-(xg+yg);
calibration.toCIE[0][2] ←xb; calibration.toCIE[1][2] ←yb; calibration.toCIE[2][2] 𡤁-(xb+yb);
calibration.yScale ← whiteY/(yr+yg+yb);
calibration.toRGB ← Invert3[calibration.toCIE];
};
default CIE coordinates for calibration
longXR: REAL ← 0.6;  longXG: REAL ← 0.22;  longXB: REAL ← 0.23;
longYR: REAL ← 0.325;  longYG: REAL ← 0.62;  longYB: REAL ← 0.2;
normalXR: REAL ← 0.615; normalXG: REAL ← 0.3;  normalXB: REAL ← 0.15; 
normalYR: REAL ← 0.34; normalYG: REAL ← 0.59; normalYB: REAL ← 0.065;
Hitachi's CIE coordinates (from catalog # CE-E500R, Jan. 1982)
HitachiLPxR: REAL ← 0.603; HitachiLPxG: REAL ← 0.220; HitachiLPxB: REAL ← 0.151;
HitachiLPyR: REAL ← 0.327; HitachiLPyG: REAL ← 0.619; HitachiLPyB: REAL ← 0.064;
HitachiNPxR: REAL ← 0.610; HitachiNPxG: REAL ← 0.298; HitachiNPxB: REAL ← 0.151; 
HitachiNPyR: REAL ← 0.342; HitachiNPyG: REAL ← 0.588; HitachiNPyB: REAL ← 0.064;
phosphorRegistrationKey: ATOM ~ $ImagerPhosphorSpec;
GetPhosphorCalibration: PUBLIC PROC[type: PhosphorType ← $DefaultLP] RETURNS [calibration: Calibration] = {
calibration ← NARROW[Atom.GetProp[type, phosphorRegistrationKey]];
};
RegisterPhosphorCalibration: PUBLIC PROC[xr,yr,xg,yg,xb,yb: REAL, type: PhosphorType] ~ {
calibration: Calibration ← InitCIE[xr,yr,xg,yg,xb,yb];
Atom.PutProp[type, phosphorRegistrationKey, calibration];
};
CIEToRGB: PUBLIC PROC[x,y, Y: REAL, calibration: Calibration] RETURNS[r, g, b: REAL] = {
may return values outside the range [0..1]
rgb,cie: Column3;
s: REAL;
s ← Y/(calibration.yScale*y); --now a real Y
cie[0] ← x*s; cie[1] ← y*s; cie[2] ← s-(x+y)*s;
rgb ← MultiplyVec[calibration.toRGB,cie];
r ← rgb[0]; g ←rgb[1]; b ← rgb[2];
};
RGBToCIE: PUBLIC PROC[r, g, b: REAL, calibration: Calibration] RETURNS [x,y, Y: REAL] = {
cie,rgb: Column3;
s: REAL;
rgb ← [r,g,b];
cie ← MultiplyVec[calibration.toCIE,rgb];
s ← cie[0]+cie[1]+cie[2]; --cie must be positive
IF s=0 THEN RETURN[0.3101,0.3163,0]; --r, g, b =0 means color is black
x ← cie[0]/s; y ← cie[1]/s; Y ← cie[1]*calibration.yScale;
};
GetMaxY: PUBLIC PROC[x,y: REAL, calibration: Calibration] RETURNS[Y: REAL] = {
work this out by algebra, find we can use ToRGB to solve it
cie,ymax: Column3;
cie ← [x/y,1,(1-(x+y))/y];
ymax ← MultiplyVec[calibration.toRGB,cie]; --actually the inverses
YMAX[MAX[ymax[0],ymax[1]],ymax[2]];
Y ← calibration.yScale/Y;
};
Matrix routines. Specialized versions here to avoid imports
Matrix2: TYPE = ARRAY [0..2) OF Row2;
Row2: TYPE = ARRAY [0..2) OF Real0;
Column2: TYPE = ARRAY [0..2) OF Real0;
MultiplyVec: PROC[a: Matrix3, v: Column3] RETURNS [c: Column3] = {
FOR i: INTEGER IN [0..3) DO
c[i] ← 0;
FOR j: INTEGER IN [0..3) DO
c[i] ← c[i]+a[i][j]*v[j];
ENDLOOP;
ENDLOOP;
};
Invert3: PROCEDURE [a: Matrix3] RETURNS [ai: Matrix3] = {
nrows,ncols: INTEGER ← 3;
det: REAL;
Aij: Matrix2;
det ← Determinant3[a];
FOR i: INTEGER IN [0..nrows) DO
FOR j: INTEGER IN [0..ncols) DO
Aij ← MakeAij[a,i,j];
ai[j][i] ← Sign[i,j]*Determinant2[Aij]/det;
ENDLOOP;
ENDLOOP;
RETURN[ai];
};
Determinant3: PROC[a: Matrix3] RETURNS [det: REAL] = {
nrows,ncols: INTEGER ← 3;
i,j: INTEGER;
Aij: Matrix2;
det ← 0;
j ← 0; --always use column 0 for now
FOR i IN [0..nrows) DO
Aij ← MakeAij[a,i,j];
det ← det + a[i][j]*Sign[i,j]*Determinant2[Aij];
ENDLOOP;
RETURN[det];
};
Determinant2: PROC[a: Matrix2] RETURNS [det: REAL] = {
det ← a[0][0]*a[1][1]-a[0][1]*a[1][0];
};
Sign: PROC[i,j: INTEGER] RETURNS[REAL] = {RETURN[IF (i+j) MOD 2 = 0 THEN 1 ELSE -1]};
MakeAij: PROC[a: Matrix3, i,j: INTEGER] RETURNS[Aij: Matrix2]= {
n,m: INTEGER ← 0; --row index, column index for new matrix
FOR row: INTEGER IN [0..3) DO
IF row=i THEN LOOP; --row index for original matrix
m ← 0; --column index for new matrix
FOR col: INTEGER IN [0..3) DO
IF col=j THEN LOOP;
Aij[n][m] ← a[row][col];
m ← m+1;
ENDLOOP;
n ← n+1;
ENDLOOP;
};
register the known calibrations
RegisterPhosphorCalibration[longXR, longYR, longXG, longYG, longXB, longYB, $DefaultLP];
RegisterPhosphorCalibration[normalXR, normalYR, normalXG, normalYG, normalXB, normalYB, $DefaultNP];
RegisterPhosphorCalibration[HitachiLPxR, HitachiLPyR, HitachiLPxG, HitachiLPyG, HitachiLPxB, HitachiLPyB, $HitachiLP];
RegisterPhosphorCalibration[HitachiNPxR, HitachiNPyR, HitachiNPxG, HitachiNPyG, HitachiNPxB, HitachiNPyB, $HitachiNP];
END.