<> <> <> <> 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; <> <> <> <> <> <> <> <> <> <<];>> <<>> 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]; <> 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]]; }; <> <> 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]]; }; <> 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] = { <> 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] = { <> <> 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]; }; <> 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; <> <> 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]; }; <> <> <<>> GrayAxis: PUBLIC SampleProc = { <> 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 = { <> 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 = { <> 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 = { <> 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 = { <> 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.