~
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: REF ← NIL,
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: REF ← NIL] 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: REAL ← IF note#NIL THEN height[noteBox]+gap ELSE 0;
xOffset: REAL ← IF centeredY[state] THEN 0 ELSE width[yLabelBox]/2.0;
yOffset: REAL ← IF 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:
BOOLEAN ←
TRUE, note:
ROPE ← NIL, labelColor: ImagerColor.Color ←
NIL, bleedBackground:
BOOLEAN ←
FALSE] = {
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: REAL ← MAX[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:
BOOLEAN ←
TRUE] = {
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: BOOLEAN ← FALSE;
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;
};