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
Y ← MAX[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.