CGColorWithCIEImpl.mesa
Last edited by Maureen Stone, June 21, 1985 1:52:14 pm PDT
Last edited by Doug Wyatt, November 12, 1982 1:30 pm
Last Edited by: Beach, February 13, 1984 2:23:14 pm PST
Last Edited by: Naiman, July 8, 1985 6:32:29 pm PDT
DIRECTORY
CGColorWithCIE USING [Color, GetStipple, undefined, PhosphorType],
Basics USING[BITSHIFT, BITAND],
Real USING [FixI, RoundC],
ImagerColor USING [RGB, HSV, HSL, YIQ, HSVFromRGB, RGBFromHSV, HSLFromRGB, RGBFromHSL, YIQFromRGB, RGBFromYIQ],
Matrix,
RuntimeError USING [BoundsFault];
CGColorWithCIEImpl: CEDAR PROGRAM
IMPORTS CGColorWithCIE, Basics, Real, RuntimeError, Matrix, ImagerColor
EXPORTS CGColorWithCIE = {
Color: TYPE = CGColorWithCIE.Color;
Byte: TYPE = [0..256);
undefined: REAL= CGColorWithCIE.undefined;
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
ToByte: PROC[v: REAL] RETURNS[Byte] = INLINE { RETURN[Real.RoundC[ToRange[v]*255]] };
-- assumes v IN[0..1]
MakeRGB: PROC[r, g, b: Byte] RETURNS[Color] = INLINE { RETURN[[tag: rgb, r: r, g: g, b: b]] };
IntensityToColor: PUBLIC PROC[intensity: REAL] RETURNS[Color] = {
i: Byte ← ToByte[ToRange[intensity]];
RETURN[MakeRGB[i, i, i]] };
RGBToColor: PUBLIC PROC[r,g,b: REAL] RETURNS[Color] = {
red: Byte ← ToByte[ToRange[r]];
grn: Byte ← ToByte[ToRange[g]];
blu: Byte ← ToByte[ToRange[b]];
RETURN[MakeRGB[red, grn, blu]] };

HSVToColor: PUBLIC PROC[h,s,v: REAL] RETURNS[Color] = {
r,g,b: REAL; [r,g,b] ← HSVToRGB[h, s, v];
RETURN[MakeRGB[ToByte[r],ToByte[g],ToByte[b]]] };

ColorToIntensity: PUBLIC PROC[color: Color] RETURNS[intensity: REAL] = {
SELECT color.tag FROM
rgb => { i: REAL;
IF color.b=color.r AND color.g=color.r THEN i ← color.r
ELSE i ← 0.30*color.r+0.11*color.b+0.59*color.g;
intensity ← i/255.0 };
stipple => intensity ← StippleToIntensity[color];
ENDCASE => intensity ← 0;
RETURN[intensity];
};
ColorToHSV: PUBLIC PROC[color: Color] RETURNS[h, s, v: REAL] = {
SELECT color.tag FROM
rgb => [h,s,v] ← RGBToHSV[color.r/255.0, color.g/255.0, color.b/255.0];
stipple => { h ← s ← 0; v ← StippleToIntensity[color] };
ENDCASE => h ← s ← v ← 0;
RETURN[h,s,v];
};

ColorToRGB: PUBLIC PROC[color: Color] RETURNS[r, g, b: REAL] = {
SELECT color.tag FROM
rgb => RETURN[r: color.r/255.0, g: color.g/255.0, b: color.b/255.0];
stipple => { i: REAL ← StippleToIntensity[color]; RETURN[i, i, i] };
ENDCASE => RETURN[0, 0, 0];
};

StippleToIntensity: PROC[color: Color] RETURNS[intensity: REAL] = { -- assumes color.tag = stipple
bits: CARDINAL ← CGColorWithCIE.GetStipple[color];
count: NAT ← 0;
FOR i: CARDINAL IN[0..16) DO TRUSTED {
IF Basics.BITAND[bits,1]=0 THEN count ← count+1; -- count "white" bits
bits ← Basics.BITSHIFT[bits,-1] };
ENDLOOP;
intensity ← count/16.0;
};
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 THEN {r ← g ← b ← lightness}
ELSE SIGNAL InvalidColor
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;
};
rgbToYiqMatrix: Matrix.MatrixN ← Matrix.Create[3,3];
yiqToRgbMatrix: Matrix.MatrixN ← Matrix.Create[3,3];
YIQToRGB: PUBLIC PROC[y, i, q: REAL] RETURNS[r, g, b: REAL] = {
rgbMatrix, yiqMatrix: Matrix.MatrixN;
rgbMatrix ← Matrix.Create[3,1];
yiqMatrix ← Matrix.Create[3,1];
yiqMatrix[0][0] ← y; yiqMatrix[1][0] ← i * 1.2 - 0.6; yiqMatrix[2][0] ← q * 1.04 - 0.52;
rgbMatrix ← Matrix.Multiply[yiqToRgbMatrix, yiqMatrix];
r ← rgbMatrix[0][0]; g ← rgbMatrix[1][0]; b ← rgbMatrix[2][0];
r ← MIN[r,1]; g ← MIN[g,1]; b ← MIN[b,1]; --control precision problems
r ← MAX[r,0]; g ← MAX[g,0]; b ← MAX[b,0];
};
RGBToYIQ: PUBLIC PROC[r, g, b: REAL] RETURNS[y, i, q: REAL] = {
rgbMatrix, yiqMatrix: Matrix.MatrixN;
rgbMatrix ← Matrix.Create[3,1];
yiqMatrix ← Matrix.Create[3,1];
rgbMatrix[0][0] ← r; rgbMatrix[1][0] ← g; rgbMatrix[2][0] ← b;
yiqMatrix ← Matrix.Multiply[rgbToYiqMatrix, rgbMatrix];
y ← yiqMatrix[0][0]; i ← (yiqMatrix[1][0] + 0.6) / 1.2; q ← (yiqMatrix[2][0] + 0.52) / 1.04;
y ← MIN[y,1]; i ← MIN[i,1]; q ← MIN[q,1]; --control precision problems
y ← MAX[y,0]; i ← MAX[i,0]; q ← MAX[q,0];
};
rgbToArgybMatrix: Matrix.MatrixN ← Matrix.Create[3,3];
argybToRgbMatrix: Matrix.MatrixN ← Matrix.Create[3,3];
ARgYbToRGB: PUBLIC PROC[a, rg, yb: REAL] RETURNS[r, g, b: REAL] = {
rgbMatrix, argybMatrix: Matrix.MatrixN;
rgbMatrix ← Matrix.Create[3,1];
argybMatrix ← Matrix.Create[3,1];
argybMatrix[0][0] ← a; argybMatrix[1][0] ← rg - 0.5; argybMatrix[2][0] ← yb - 0.5;
rgbMatrix ← Matrix.Multiply[argybToRgbMatrix, argybMatrix];
r ← rgbMatrix[0][0]; g ← rgbMatrix[1][0]; b ← rgbMatrix[2][0];
r ← MIN[r,1]; g ← MIN[g,1]; b ← MIN[b,1]; --control precision problems
r ← MAX[r,0]; g ← MAX[g,0]; b ← MAX[b,0];
};
RGBToARgYb: PUBLIC PROC[r, g, b: REAL] RETURNS[a, rg, yb: REAL] = {
rgbMatrix, argybMatrix: Matrix.MatrixN;
rgbMatrix ← Matrix.Create[3,1];
argybMatrix ← Matrix.Create[3,1];
rgbMatrix[0][0] ← r; rgbMatrix[1][0] ← g; rgbMatrix[2][0] ← b;
argybMatrix ← Matrix.Multiply[rgbToArgybMatrix, rgbMatrix];
a ← argybMatrix[0][0]; rg ← argybMatrix[1][0] + 0.5; yb ← argybMatrix[2][0] + 0.5;
a ← MIN[a,1]; rg ← MIN[rg,1]; yb ← MIN[yb,1]; --control precision problems
a ← MAX[a,0]; rg ← MAX[rg,0]; yb ← MAX[yb,0];
};
Real0: TYPE = REAL ← 0;
Matrix3: TYPE = ARRAY [0..3) OF Row3;
Row3: TYPE = ARRAY [0..3) OF Real0;
Column3: TYPE = ARRAY [0..3) OF Real0;
ToCIE: Matrix3;
ToRGB: Matrix3;
yScale: REAL ← 1;
Initialized: BOOLEANFALSE;
InitCIE: PUBLIC PROC[xr,yr,xg,yg,xb,yb: REAL, whiteY: REAL ← 1] = {
matrix: Matrix.MatrixN;
ToCIE[0][0] ←xr; ToCIE[1][0] ←yr; ToCIE[2][0] 𡤁-(xr+yr);
ToCIE[0][1] ←xg; ToCIE[1][1] ←yg; ToCIE[2][1] 𡤁-(xg+yg);
ToCIE[0][2] ←xb; ToCIE[1][2] ←yb; ToCIE[2][2] 𡤁-(xb+yb);
Initialized ← TRUE;
yScale ← whiteY/(yr+yg+yb);
matrix ← Matrix.Create[3,3];
FOR i: INTEGER IN [0..3) DO
FOR j: INTEGER IN [0..3) DO
matrix[i][j] ← ToCIE[i][j];
ENDLOOP;
ENDLOOP;
matrix ← Matrix.Invert[matrix];
FOR i: INTEGER IN [0..3) DO
FOR j: INTEGER IN [0..3) DO
ToRGB[i][j] ← matrix[i][j];
ENDLOOP;
ENDLOOP;
};
longXR: REAL ← 0.6;
longYR: REAL ← 0.325;
longXG: REAL ← 0.22;
longYG: REAL ← 0.62;
longXB: REAL ← 0.23;
longYB: REAL ← 0.2;
normalXR: REAL ← 0.615;
normalYR: REAL ← 0.34;
normalXG: REAL ← 0.3;
normalYG: REAL ← 0.59;
normalXB: REAL ← 0.15;
normalYB: REAL ← 0.065;
InitDefaultCIE: PUBLIC PROC[type: CGColorWithCIE.PhosphorType ← long] = {
SELECT type FROM
long => InitCIE[longXR,longYR,longXG,longYG,longXB,longYB];
normal => InitCIE[normalXR,normalYR,normalXG,normalYG,normalXB,normalYB];
ENDCASE => ERROR;
};
GetDefaultValues: PUBLIC PROC[type: CGColorWithCIE.PhosphorType] RETURNS[xr,yr,xg,yg,xb,yb: REAL] = {
SELECT type FROM
long => RETURN[xr: longXR, yr: longYR, xg: longXG, yg: longYG, xb: longXB, yb: longYB];
normal => RETURN[xr: normalXR, yr: normalYR, xg: normalXG, yg: normalYG, xb: normalXB, yb: normalYB];
ENDCASE => ERROR;
};
SetDefaultValues: PUBLIC PROC[xr,yr,xg,yg,xb,yb: REAL, type: CGColorWithCIE.PhosphorType] = {
SELECT type FROM
long => {
longXR ← xr; longYR ← yr;
longXG ← xg; longYG ← yg;
longXB ← xb; longYB ← yb;
};
normal => {
normalXR ← xr; normalYR ← yr;
normalXG ← xg; normalYG ← yg;
normalXB ← xb; normalYB ← yb;
};
ENDCASE => ERROR;
};
CIEToRGB: PUBLIC PROC[x,y, Y: REAL] RETURNS[r, g, b: REAL] = {
may return values outside the range [0..1]
rgb,cie: Column3;
s: REAL;
IF ~Initialized THEN SIGNAL Uninitialized;
s ← Y/(yScale*y); --now a real Y
cie[0] ← x*s; cie[1] ← y*s; cie[2] ← s-(x+y)*s;
rgb ← MultiplyVec[ToRGB,cie];
r ← rgb[0]; g ←rgb[1]; b ← rgb[2];
};
RGBToCIE: PUBLIC PROC[r, g, b: REAL] RETURNS [x,y, Y: REAL] = {
cie,rgb: Column3;
s: REAL;
IF ~Initialized THEN SIGNAL Uninitialized;
rgb ← [r,g,b];
cie ← MultiplyVec[ToCIE,rgb];
s ← cie[0]+cie[1]+cie[2]; --cie must be positive
IF s=0 THEN RETURN[0,0,0]; --r, g, b =0 means color is black
x ← cie[0]/s; y ← cie[1]/s; Y ← cie[1]*yScale;
};
GetMaxY: PUBLIC PROC[x,y: REAL] 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[ToRGB,cie]; --actually the inverses
YMAX[MAX[ymax[0],ymax[1]],ymax[2]];
Y ← yScale/Y;
};
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;
};
ContrastInRGB: PUBLIC PROC[rgb: ImagerColor.RGB] RETURNS[rgbContrast: ImagerColor.RGB] = {
IF rgb.R < 0.5 THEN rgbContrast.R ← 1.0 ELSE rgbContrast.R ← 0.0;
IF rgb.G < 0.5 THEN rgbContrast.G ← 1.0 ELSE rgbContrast.G ← 0.0;
IF rgb.B < 0.5 THEN rgbContrast.B ← 1.0 ELSE rgbContrast.B ← 0.0;
};
ContrastInHSV: PUBLIC PROC[rgb: ImagerColor.RGB] RETURNS[hsvContrast: ImagerColor.RGB] = {
hsvContrastInHSV: ImagerColor.HSV;
hsv: ImagerColor.HSV ← ImagerColor.HSVFromRGB[rgb];
hsvContrastInHSV.H ← hsv.H + 0.5;
IF hsvContrastInHSV.H > 1.0 THEN hsvContrastInHSV.H ← hsvContrastInHSV.H - 1.0;
IF hsv.S < 0.5 THEN hsvContrastInHSV.S ← 1.0 ELSE hsvContrastInHSV.S ← 0.0;
IF hsv.V < 0.5 THEN hsvContrastInHSV.V ← 1.0 ELSE hsvContrastInHSV.V ← 0.0;
hsvContrast ← ImagerColor.RGBFromHSV[hsvContrastInHSV];
};
ContrastInHSL: PUBLIC PROC[rgb: ImagerColor.RGB] RETURNS[hslContrast: ImagerColor.RGB] = {
hslContrastInHSL: ImagerColor.HSL;
hsl: ImagerColor.HSL ← ImagerColor.HSLFromRGB[rgb];
hslContrastInHSL.H ← hsl.H + 0.5;
IF hslContrastInHSL.H > 1.0 THEN hslContrastInHSL.H ← hslContrastInHSL.H - 1.0;
IF hsl.S < 0.5 THEN hslContrastInHSL.S ← 1.0 ELSE hslContrastInHSL.S ← 0.0;
IF hsl.L < 0.5 THEN hslContrastInHSL.L ← 1.0 ELSE hslContrastInHSL.L ← 0.0;
hslContrast ← ImagerColor.RGBFromHSL[hslContrastInHSL];
};
ContrastInYIQ: PUBLIC PROC[rgb: ImagerColor.RGB] RETURNS[yiqContrast: ImagerColor.RGB] = {
yiqContrastInYIQ: ImagerColor.YIQ;
yiq: ImagerColor.YIQ ← ImagerColor.YIQFromRGB[rgb];
yiq.I ← (yiq.I + 0.6) / 1.2; yiq.Q ← (yiq.Q + 0.52) / 1.04;
IF yiq.Y < 0.5 THEN yiqContrastInYIQ.Y ← 1.0 ELSE yiqContrastInYIQ.Y ← 0.0;
IF yiq.I < 0.5 THEN yiqContrastInYIQ.I ← 1.0 ELSE yiqContrastInYIQ.I ← 0.0;
IF yiq.Q < 0.5 THEN yiqContrastInYIQ.Q ← 1.0 ELSE yiqContrastInYIQ.Q ← 0.0;
yiqContrastInYIQ.I ← yiqContrastInYIQ.I * 1.2 - 0.6; yiqContrastInYIQ.Q ← yiqContrastInYIQ.Q * 1.04 - 0.52;
yiqContrast ← ImagerColor.RGBFromYIQ[yiqContrastInYIQ];
yiqContrast.R ← MIN[yiqContrast.R,1]; yiqContrast.G ← MIN[yiqContrast.G,1]; yiqContrast.B ← MIN[yiqContrast.B,1];
yiqContrast.R ← MAX[yiqContrast.R,0]; yiqContrast.G ← MAX[yiqContrast.G,0]; yiqContrast.B ← MAX[yiqContrast.B,0];
};
ContrastInARgYb: PUBLIC PROC[rgb: ImagerColor.RGB] RETURNS[argybContrast: ImagerColor.RGB] = {
a, rg, yb: REAL;
aContrastInARgYb, rgContrastInARgYb, ybContrastInARgYb: REAL;
[a, rg, yb] ← RGBToARgYb[rgb.R, rgb.G, rgb.B];
IF a < 0.5 THEN aContrastInARgYb ← 1.0 ELSE aContrastInARgYb ← 0.0;
IF rg < 0.5 THEN rgContrastInARgYb ← 1.0 ELSE rgContrastInARgYb ← 0.0;
IF yb < 0.5 THEN ybContrastInARgYb ← 1.0 ELSE ybContrastInARgYb ← 0.0;
[argybContrast.R, argybContrast.G, argybContrast.B] ← ARgYbToRGB[aContrastInARgYb, rgContrastInARgYb, ybContrastInARgYb];
argybContrast.R ← MIN[argybContrast.R,1]; argybContrast.G ← MIN[argybContrast.G,1]; argybContrast.B ← MIN[argybContrast.B,1];
argybContrast.R ← MAX[argybContrast.R,0]; argybContrast.G ← MAX[argybContrast.G,0]; argybContrast.B ← MAX[argybContrast.B,0];
};
Initialization
rgbToYiqMatrix[0][0] ← 0.3; rgbToYiqMatrix[0][1] ← 0.59; rgbToYiqMatrix[0][2] ← 0.11;
rgbToYiqMatrix[1][0] ← 0.6; rgbToYiqMatrix[1][1] ← -0.28; rgbToYiqMatrix[1][2] ← -0.32;
rgbToYiqMatrix[2][0] ← 0.21; rgbToYiqMatrix[2][1] ← -0.52; rgbToYiqMatrix[2][2] ← 0.31;
yiqToRgbMatrix ← Matrix.Invert[rgbToYiqMatrix];
rgbToArgybMatrix[0][0] ← 0.3; rgbToArgybMatrix[0][1] ← 0.59; rgbToArgybMatrix[0][2] ← 0.11;
rgbToArgybMatrix[1][0] ← 0.5; rgbToArgybMatrix[1][1] ← -0.5; rgbToArgybMatrix[1][2] ← 0.0;
rgbToArgybMatrix[2][0] ← 0.25; rgbToArgybMatrix[2][1] ← 0.25; rgbToArgybMatrix[2][2] ← -0.5;
argybToRgbMatrix ← Matrix.Invert[rgbToArgybMatrix];
}.