ColorPlotGamutsImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Maureen Stone July 23, 1986 12:07:04 sm PDT
Maureen Stone, June 29, 1988 2:27:15 am PDT
DIRECTORY
CalibratedColor, CalibratedColorFns, Imager, ImagerColor, ColorPlotGamuts, ImagerPixel, ImagerPixelArray, ImagerInterpress, ImagerSample, ImagerFont, ImagerTransformation, Real, Rope, SF, IO, CedarProcess;
ColorPlotGamutsImpl: CEDAR PROGRAM
IMPORTS Imager, CalibratedColorFns, ImagerPixel, ImagerPixelArray, ImagerInterpress, ImagerSample, ImagerFont, Real, ImagerTransformation, CalibratedColor, ImagerColor, CedarProcess
EXPORTS ColorPlotGamuts
~ BEGIN OPEN ColorPlotGamuts;
SampleMap: TYPE = ImagerSample.SampleMap;
ROPE: TYPE = Rope.ROPE;
VEC: TYPE = Imager.VEC;
XYZ: TYPE = CalibratedColor.XYZ;
RGB: TYPE = ImagerColor.RGB;
State: TYPE = REF StateRec;
StateRec: TYPE = RECORD [
redMap,greenMap,blueMap: SampleMap,
xAxis, yAxis: Projection,
res: NAT,  -- number of samples along longest dimension.
white: XYZ,
getColor: GetColorProc,
getColorData: REFNIL,
transformation: ImagerTransformation.Transformation --based on projections
];
CreateMapState: PUBLIC PROC [xAxis, yAxis: Projection, res: NAT, white: XYZ, getColor: GetColorProc, getColorData: REF, backgroundColor: RGB ← [0.5, 0.5, 0.5]] RETURNS [State] = {
state: State ← NEW[StateRec ← [
redMap: NIL,
greenMap: NIL,
blueMap: NIL,
xAxis: xAxis,
yAxis: yAxis,
res: res,
white: white,
getColor: getColor,
getColorData: getColorData,
backgroundColor: backgroundColor,
transformation: NIL --based on projections
]];
valsFromProjection: PROC[proj: Projection] RETURNS[min, max: REAL] = {
SELECT proj FROM
x => {min ← 0; max ← 0.8};
y => {min ← 0; max ← 0.9};
aStar => {min ← -140; max ← 135};
bStar => {min ← -125; max ← 125};
uStar => {min ← -180; max ← 195};
vStar => {min ← -175; max ← 175};
lStar, reflectance => {min ← 0; max ← 100};
hueAngle => {min ← 0; max ← 360};
ENDCASE => ERROR;
};
xMin,xMax, yMin, yMax, dx, dy: REAL;
[xMin,xMax] ← valsFromProjection[xAxis];
[yMin, yMax] ← valsFromProjection[yAxis];
dx ← xMax-xMin;
dy ← yMax-yMin;
IF dx > dy THEN [state.redMap,state.greenMap, state.blueMap] ←
CreateSampleMaps[res, Real.Round[1+res*dy/dx], backgroundColor]
ELSE [state.redMap,state.greenMap, state.blueMap] ← CreateSampleMaps[Real.Round[1+res*dx/dy], res, backgroundColor];
Think in SampleMap pixels.
state.transformation ← ImagerTransformation.Translate[[-xMin, -yMin]];
state.transformation ← ImagerTransformation.Concat[state.transformation, ImagerTransformation.Scale[res/MAX[dx,dy]]];
RETURN[state];
};
CreateSampleMaps: PROC[xVal,yVal: NAT, backgroundColor: RGB] RETURNS[r,g,b: SampleMap] = {
box: SF.Box ← [[f:0,s:0], [f: xVal+2, s: yVal+2]];
backgroundR, backgroundG, backgroundB: BYTE;
[backgroundR, backgroundG, backgroundB] ← BytesFromRGB[backgroundColor];
r ← ImagerSample.NewSampleMap[box: box, bitsPerSample: 8];
g ← ImagerSample.NewSampleMap[box: box, bitsPerSample: 8];
b ← ImagerSample.NewSampleMap[box: box, bitsPerSample: 8];
ImagerSample.Fill[r,box, backgroundR];
ImagerSample.Fill[g,box, backgroundG];
ImagerSample.Fill[b,box, backgroundB];
};
BytesFromRGB: PROC [rgb: RGB] RETURNS [rByte, gByte, bByte: BYTE] ~ {
rByte ← Real.Round[rgb.R*255];
gByte ← Real.Round[rgb.G*255];
bByte ← Real.Round[rgb.B*255];
};
MapColor: PUBLIC PROC [state: State, color: XYZ] RETURNS [x,y: REAL, rgb: RGB] = {
[x,y] ← MapValues[state,color];
rgb ← state.getColor[color, state.getColorData];
};
MapValues: PUBLIC PROC [state: State, color: XYZ] RETURNS [x,y: REAL] = {
valFromProjection: PROC[proj: Projection] RETURNS[val: REAL] = {
SELECT proj FROM
x => val ← CalibratedColorFns.ChromaticityFromXYZ[color].x;
y => val ← CalibratedColorFns.ChromaticityFromXYZ[color].y;
aStar => val ← CalibratedColorFns.CIELABFromXYZ[color, state.white].aStar;
bStar => val ← CalibratedColorFns.CIELABFromXYZ[color, state.white].bStar;
uStar => val ← CalibratedColorFns.CIELUVFromXYZ[color, state.white].uStar;
vStar => val ← CalibratedColorFns.CIELUVFromXYZ[color, state.white].vStar;
lStar => val ← CalibratedColorFns.LStarFromLuminance[color.Y, state.white.Y];
hueAngle => val ← CalibratedColorFns.HueAngleAB[ CalibratedColorFns.CIELABFromXYZ[color, state.white]];
reflectance => val ← color.Y/state.white.Y;
ENDCASE => ERROR;
};
x ← valFromProjection[state.xAxis];
y ← valFromProjection[state.yAxis];
[[x,y]] ← ImagerTransformation.Transform[state.transformation, [x,y]];
};
MarkColor: PUBLIC PROC[state: State, color: XYZ] = {
x,y: REAL;
rgb: RGB;
[x,y, rgb] ← MapColor[state, color ! Real.RealException => GOTO Skip];
MarkPoint[state, Real.Round[x], Real.Round[y], rgb];
EXITS Skip => NULL;
};
MarkColorVals: PUBLIC PROC[state: State, x, y: REAL, rgb: RGB] = {
[[x,y]] ← ImagerTransformation.Transform[state.transformation, [x,y]];
MarkPoint[state, Real.Round[x], Real.Round[y], rgb];
};
MarkPoint: PUBLIC PROC[state: State, ix, iy: INTEGER, rgb: RGB] = {
putInMap: PROC [sm: SampleMap, v: WORD] = {
ImagerSample.Put[map: sm, index: [f: ix, s: iy], value: v];
ImagerSample.Put[map: sm, index: [f: ix+1, s: iy], value: v];
ImagerSample.Put[map: sm, index: [f: ix+1, s: iy+1], value: v];
ImagerSample.Put[map: sm, index: [f: ix, s: iy+1], value: v];
};
putInMap[state.redMap, Real.Round[255*rgb.R]];
putInMap[state.greenMap, Real.Round[255*rgb.G]];
putInMap[state.blueMap, Real.Round[255*rgb.B]];
};
GetColor Procs
GetColorProc: TYPE = PROC[xyz: XYZ, data: REFNIL] RETURNS[RGB];
Black: PUBLIC GetColorProc = {RETURN[[0,0,0]]}; --constant black
Gray: PUBLIC GetColorProc = {RETURN[[0.5,0.5,0.5]]}; --constant gray
White: PUBLIC GetColorProc = {RETURN[[1,1,1]]}; --constant white
SampledRainbow: PUBLIC GetColorProc = { --assumes data is SampledCalibration
cal: SampledCalibration ← NARROW[data];
rgb: RGB;
[[rgb.R, rgb.G, rgb.B]] ← CalibratedColor.TripleFromXYZ[xyz, cal];
RETURN[rgb];
};
SampledGrayscale: PUBLIC GetColorProc = { --assumes data is SampledCalibration
cal: SampledCalibration ← NARROW[data];
white: XYZ ← CalibratedColor.XYZFromTriple[CalibratedColor.TripleFromRGB[[1,1,1]], cal];
v: REAL ← xyz.Y/white.Y;
RETURN[[v,v,v]];
};
RGBRainbow: PUBLIC GetColorProc = { --assumes data is RGBCalibration
cal: RGBCalibration ← NARROW[data];
rgb: RGB ← CalibratedColor.RGBFromXYZ[xyz, cal];
RETURN[rgb];
};
RGBGrayscale: PUBLIC GetColorProc = { --assumes data is RGBCalibration
cal: RGBCalibration ← NARROW[data];
white: XYZ ← CalibratedColor.XYZFromRGB[[1,1,1], cal];
v: REAL ← xyz.Y/white.Y;
RETURN[[v,v,v]];
};
Imaging
rgbOperator: ImagerColor.ColorOperator ← ImagerColor.NewColorOperatorRGB[255];
RopeFromProjection: PUBLIC PROC[proj: Projection] RETURNS[r: ROPE] = {
SELECT proj FROM
x => r ← "x";
y => r ← "y";
aStar => r ← "a*";
bStar => r ← "b*";
uStar => r ← "u*";
vStar => r ← "v*";
lStar => r ← "L*";
reflectance => r ← "R";
hueAngle => r ← "Hue";
ENDCASE => ERROR;
};
ImageSampleMap: PUBLIC PROC[dc: Imager.Context, state: State] = {
box: SF.Box ← ImagerSample.GetBox[state.redMap];
pa: ImagerPixelArray.PixelArray;
pa ← ImagerPixelArray.FromPixelMap[pixelMap: ImagerPixel.MakePixelMap[state.redMap, state.greenMap, state.blueMap], box: box, scanMode: [slow: up, fast: right]];
Imager.SetSampledColor[dc, pa, NIL, rgbOperator];
Imager.MaskRectangleI[dc, box.min.f, box.min.s, (box.max.f-box.min.f), (box.max.s-box.min.s)];
};
fontSize: REAL ← 8;
ImageAndLabel: PUBLIC PROC [dc: Imager.Context, state: State, note: ROPE ← NIL, labelColor: ImagerColor.Color ← NIL] = {
centeredX: PROC[state: State] RETURNS[BOOLEAN] = {
x axis is centered if value on y axis requires it
SELECT state.yAxis FROM
x,y,lStar,reflectance => RETURN[FALSE];
ENDCASE => RETURN[TRUE];
};
centeredY: PROC[state: State] RETURNS[BOOLEAN] = {
SELECT state.xAxis FROM
x,y,lStar,reflectance => RETURN[FALSE];
ENDCASE => RETURN[TRUE];
};
font: ImagerFont.Font ← ImagerFont.Scale[ImagerFont.Find["Xerox/PressFonts/Helvetica-mrr"],fontSize];
xLabel: ROPE ← RopeFromProjection[state.xAxis];
yLabel: ROPE ← RopeFromProjection[state.yAxis];
do: PROC = {
smBox: SF.Box ← ImagerSample.GetBox[state.redMap];
width: PROC[extent: ImagerFont.Extents] RETURNS[REAL] = {
RETURN[extent.leftExtent+extent.rightExtent]};
height: PROC[extent: ImagerFont.Extents] RETURNS[REAL] = {
RETURN[extent.ascent+extent.descent]};
yLabelBox: ImagerFont.Extents ← ImagerFont.RopeBoundingBox[font, yLabel];
xLabelBox: ImagerFont.Extents ← ImagerFont.RopeBoundingBox[font, xLabel];
noteBox: ImagerFont.Extents ← IF note#NIL THEN ImagerFont.RopeBoundingBox[font, note] ELSE [0,0,0,0];
gap: REAL ← 2.0; --standard small space
noteOffset: REALIF note#NIL THEN height[noteBox]+gap ELSE 0;
xOffset: REALIF centeredY[state] THEN 0 ELSE width[yLabelBox]/2.0;
yOffset: REALIF centeredX[state] THEN noteOffset ELSE noteOffset+height[xLabelBox]/2.0;
doMap: PROC = {
Imager.TranslateT[dc,[xOffset,yOffset]];
ImageSampleMap[dc, state];
};
doYAxis: PROC = { --we've been translated to the lower left corner of the map already
Imager.TranslateT[dc,[(IF centeredY[state] THEN smBox.max.f/2.0 ELSE 0), smBox.max.s]];
Imager.MaskVector[dc, [0,0], [0,-smBox.max.s]];
Imager.SetXY[dc,[-width[yLabelBox]/2.0, yLabelBox.descent+gap]];
Imager.ShowRope[dc, yLabel];
};
doXAxis: PROC = { --we've been translated to the lower left corner of the map already
Imager.TranslateT[dc,[smBox.max.f, (IF centeredX[state] THEN smBox.max.s/2.0 ELSE 0)]];
Imager.MaskVector[dc, [0,0], [-smBox.max.f,0]];
Imager.SetXY[dc, [gap, -height[xLabelBox]/2.0]];
Imager.ShowRope[dc, xLabel];
};
Imager.DoSave[dc, doMap];
IF labelColor#NIL THEN Imager.SetColor[dc, labelColor];
Imager.SetFont[dc, font];
Imager.SetStrokeWidth[dc,1];
IF note # NIL THEN {
fullChartW: REAL ← smBox.max.f + xOffset + width[xLabelBox] +gap;
Imager.SetXY[dc, [x: (fullChartW-width[noteBox])/2.0, y: noteBox.descent]];
Imager.ShowRope[dc,note];
};
Imager.TranslateT[dc, [xOffset,yOffset]];
Imager.DoSave[dc, doYAxis];
Imager.DoSave[dc, doXAxis];
};
Imager.DoSave[dc, do];
};
StatesToInterpress: PUBLIC PROC[states: LIST OF State, ipName: ROPE, label: BOOLEANTRUE, note: ROPE ← NIL, labelColor: ImagerColor.Color ← NIL, bleedBackground: BOOLEANFALSE] = {
Makes an InterpressMaster on ipName by calling ImageSampleMap or ImageAndLabel
The states had better all be the same type. last state defines the label
ref: ImagerInterpress.Ref ← ImagerInterpress.Create[ipName];
first: State ← states.first;
box: SF.Box ← ImagerSample.GetBox[first.redMap];
combineMaps: PROC RETURNS[redMap, greenMap, blueMap: ImagerSample.SampleMap] = {
backgroundR, backgroundG, backgroundB: BYTE;
[backgroundR, backgroundG, backgroundB] ← BytesFromRGB[first.backgroundColor];
redMap ← ImagerSample.Copy[first.redMap, [0,0], box];
greenMap ← ImagerSample.Copy[first.greenMap, [0,0], box];
blueMap ← ImagerSample.Copy[first.blueMap, [0,0], box];
FOR list: LIST OF State ← states.rest, list.rest UNTIL list=NIL DO
state: State ← list.first;
FOR s: NAT IN [box.min.s..box.max.s) DO
FOR f: NAT IN [box.min.f..box.max.f) DO
r: BYTE ← ImagerSample.Get[state.redMap, [s: s, f: f]];
g: BYTE ← ImagerSample.Get[state.greenMap, [s: s, f: f]];
b: BYTE ← ImagerSample.Get[state.blueMap, [s: s, f: f]];
IF r # backgroundR OR g#backgroundG OR b#backgroundB THEN {
ImagerSample.Put[redMap, [s: s, f: f], r];
ImagerSample.Put[greenMap, [s: s, f: f], g];
ImagerSample.Put[blueMap, [s: s, f: f], b];
};
ENDLOOP;
ENDLOOP;
ENDLOOP;
};
action: PROC[dc: Imager.Context] = {
IF bleedBackground THEN {
size: REALMAX[box.max.f, box.max.s];
Imager.SetColor[dc, ImagerColor.ColorFromRGB[first.backgroundColor]];
Imager.MaskRectangle[dc, [-size/2.0, -size/2.0, 2*size, 2*size]];
};
IF states.rest=NIL THEN ImageAndLabel[dc, states.first, note, labelColor]
ELSE { --do the combining hack
r,g,b: ImagerSample.SampleMap;
temp: State;
[r,g,b] ← combineMaps[];
temp ← NEW[StateRec ← [
redMap: r,
greenMap: g,
blueMap: b,
xAxis: first.xAxis,
yAxis: first.yAxis,
res: first.res,
white: first.white,  --all of these last 5 fields are bogus, but don't matter
getColor: first.getColor,
getColorData: first.getColorData,
backgroundColor: first.backgroundColor,
transformation: first.transformation
]];
ImageAndLabel[dc, temp, note, labelColor];
};
};
ImagerInterpress.DoPage[ref, action, Imager.metersPerPoint];
ImagerInterpress.Close[ref];
};
Device Gamuts
StateFromCalibration: PUBLIC PROC [cal: SampledCalibration, xAxis, yAxis: Projection, res: NAT, getColor: GetColorProc, getColorData: REF ← NIL] RETURNS [State] = {
white: XYZ ← CalibratedColor.XYZFromTriple[CalibratedColor.TripleFromRGB[[1,1,1]], cal];
RETURN[CreateMapState[xAxis, yAxis, res, white, getColor, getColorData]];
};
StateFromRGBCalibration: PUBLIC PROC [cal: RGBCalibration, xAxis, yAxis: Projection, res: NAT, getColor: GetColorProc, getColorData: REF ← NIL] RETURNS [State] = {
white: XYZ ← CalibratedColor.XYZFromRGB[[1,1,1], cal];
RETURN[CreateMapState[xAxis, yAxis, res, white, getColor, getColorData]];
};
SampleAndPlot: PUBLIC PROC [state: State, cal: SampledCalibration, sample: NAT ← 8, sampleProc: SampleProc, substituteRainbow: BOOLEANTRUE] = {
newRGB: RGBProc = {
x,y: REAL ← 0;
mappedRGB: RGB;
xyz: XYZ ← CalibratedColor.XYZFromTriple[CalibratedColor.TripleFromRGB[rgb], cal];
[x,y, mappedRGB] ← MapColor[state, xyz ! Real.RealException => CONTINUE];
IF substitute THEN mappedRGB ← rgb;
MarkPoint[state, Real.Round[x], Real.Round[y], mappedRGB];
};
oldColor: GetColorProc;
substitute: BOOLEANFALSE;
Sampled XYZ to RGB and back is expensive. Since we are sampling the gamut as a function of RGB, we can do the rainbow colorproc without the conversion. substituteRainbow=TRUE means do the conversion
IF substituteRainbow AND state.getColor=SampledRainbow THEN {
IF substituteRainbow THEN {
oldColor ← state.getColor;
state.getColor ← Black;
substitute ← TRUE;
};
CedarProcess.SetPriority[background];
sampleProc[sample, newRGB];
IF substitute THEN state.getColor ← oldColor;
};
RGBSampleAndPlot: PUBLIC PROC [state: State, cal: RGBCalibration, sample: NAT ← 8, sampleProc: SampleProc] = {
newRGB: RGBProc = {
x,y: REAL ← 0;
xyz: XYZ ← CalibratedColor.XYZFromRGB[rgb, cal];
mappedRGB: RGB;
[x,y, mappedRGB] ← MapColor[state, xyz ! Real.RealException => CONTINUE];
MarkPoint[state, Real.Round[x], Real.Round[y], mappedRGB];
};
CedarProcess.SetPriority[background];
sampleProc[sample, newRGB];
};
RGBProc: TYPE = PROC[rgb: RGB];
SampleProc: TYPE = PROC[sample: NAT ← 8, returnRGB: RGBProc];
GrayAxis: PUBLIC SampleProc = {
interpolate from black to white
step: REAL ← 1.0/(sample-1);
FOR i: NAT IN [0..sample) DO
v: REAL ← i*step;
returnRGB[[v,v,v]];
ENDLOOP;
};
InterpRGB: PROC[from, to: RGB, sample: NAT, returnRGB: RGBProc] = {
step: REAL ← 1.0/(sample-1);
interp: PROC[alpha, f, t: REAL] RETURNS[REAL] = {RETURN[alpha*t+(1-alpha)*f]};
FOR i: NAT IN [0..sample) DO
v: REAL ← i*step;
returnRGB[[R: interp[v,from.R,to.R], G: interp[v,from.G,to.G], B: interp[v,from.B,to.B]]];
ENDLOOP;
};
HueCircle: PUBLIC SampleProc = {
interpolates around the surface of the gamut (R,Y,G,C,B,M) in sample steps between vertices. ie, 8 steps between red and yellow, yellow and green, etc.
InterpRGB[[1,0,0], [1,1,0], sample, returnRGB];
InterpRGB[[1,1,0], [0,1,0], sample, returnRGB];
InterpRGB[[0,1,0], [0,1,1], sample, returnRGB];
InterpRGB[[0,1,1], [0,0,1], sample, returnRGB];
InterpRGB[[0,0,1], [1,0,1], sample, returnRGB];
InterpRGB[[1,0,1], [1,0,0], sample, returnRGB];
};
ColorsToWhite: PUBLIC SampleProc = {
Interpolates from the vertices to the white point.
InterpRGB[[1,0,0], [1,1,1], sample, returnRGB];
InterpRGB[[1,0,1], [1,1,1], sample, returnRGB];
InterpRGB[[0,1,0], [1,1,1], sample, returnRGB];
InterpRGB[[0,1,1], [1,1,1], sample, returnRGB];
InterpRGB[[0,0,1], [1,1,1], sample, returnRGB];
InterpRGB[[1,1,0], [1,1,1], sample, returnRGB];
};
ColorsToBlack: PUBLIC SampleProc = {
Interpolates from the vertices to the black point.
black: RGB ← [0.00001, 0.00001, 0.00001]; --avoid RealException for monitor black
InterpRGB[[1,0,0], black, sample, returnRGB];
InterpRGB[[1,0,1], black, sample, returnRGB];
InterpRGB[[0,1,0], black, sample, returnRGB];
InterpRGB[[0,1,1], black, sample, returnRGB];
InterpRGB[[0,0,1], black, sample, returnRGB];
InterpRGB[[1,1,0], black, sample, returnRGB];
};
GamutSurface: PUBLIC SampleProc = {
ColorsToBlack[sample, returnRGB];
ColorsToWhite[sample, returnRGB];
HueCircle[sample, returnRGB];
};
WholeGamut: PUBLIC SampleProc = {
interpolates uniformly along RGB in sample steps. Calls toXYZ to get the tristimulus value of the sample. Calls returns to get the value plotted (or whatever). The procedure toXYZ would be less necessary if CalibratedColor had a single calibration type.
step: REAL ← 1.0/(sample-1);
FOR r: NAT IN [0..sample) DO
rv: REAL ← step*r;
FOR g: NAT IN [0..sample) DO
gv: REAL ← step*g;
FOR b: NAT IN [0..sample) DO
bv: REAL ← step*b;
returnRGB[[rv,gv,bv]];
ENDLOOP;
ENDLOOP;
ENDLOOP;
};
END.