Plot Image Gamuts
PlotInputData:
PUBLIC
PROC [state: State, inputData: RefCache, mapData:
REF, mapProc: MapProc, imageColorProc: ColorProc ←
NIL] ~ {
Enumerates the input data and makes a plot
plot:
PROC[key: Sample, data:
REF, hits:
INT] = {
inputData: InputData ← NARROW[data];
xyz: XYZ ← mapProc[key, inputData.rgb, mapData];
x,y: REAL;
rgb: RGB ← imageColorProc[key];
[x,y] ← ColorPlotGamuts.MapValues[state, xyz];
ColorPlotGamuts.MarkPoint[state, Real.Round[x], Real.Round[y], rgb];
};
Process.SetPriority[Process.priorityBackground];
SampleCache.EnumerateRefCache[inputData, plot];
};
GetCalibrations:
PUBLIC PROC[rgbAtom, sampledAtom:
ATOM]
RETURNS[rgbCal: RGBCalibration, sampledCal: SampledCalibration] = {
rgbCal ← CreateCalibration.CreateRGB[CreateCalibration.GetRGBDataValues[rgbAtom]];
sampledCal ← CreateCalibration.CreateSampled[CreateCalibration.GetSampledFiles[sampledAtom], sampledAtom];
};
MakeChromaPlot:
PUBLIC PROC [pi: PlotInfo, xAxis, yAxis: Projection, mapInfo: MapInfo] ~ {
rgbState: State ← ColorPlotGamuts.StateFromRGBCalibration[pi.rgbCal, xAxis, yAxis, pi.res, ColorPlotGamuts.RGBRainbow, pi.rgbCal];
sampledState: State ← ColorPlotGamuts.StateFromCalibration[pi.sampledCal, xAxis, yAxis, pi.res, ColorPlotGamuts.SampledRainbow, pi.sampledCal];
inputData: RefCache ← pi.inputData;
IF inputData =
NIL
THEN {
inputData ← CreateInputData[pi.aisRoot, pi.rgbCal];
pi.inputData ← inputData};
ColorPlotGamuts.RGBSampleAndPlot[rgbState, pi.rgbCal, 256, ColorPlotGamuts.HueCircle];
ColorPlotGamuts.SampleAndPlot[sampledState, pi.sampledCal, 256, ColorPlotGamuts.HueCircle];
IF mapInfo.rgbState THEN PlotInputData[rgbState, inputData, mapInfo.mapData, mapInfo.mapProc, Rainbow]
ELSE PlotInputData[sampledState, inputData, mapInfo.mapData, mapInfo.mapProc, Rainbow];
ColorPlotGamuts.StatesToInterpress[states: LIST[rgbState, sampledState], ipName: Rope.Cat[pi.ipRoot, ".ip"], label: TRUE, note: pi.note, labelColor: pi.labelColor, bleedBackground: pi.bleedBackground];
};
MakeLStarPlot:
PUBLIC PROC [pi: PlotInfo, xAxis: Projection, mapInfo: MapInfo] ~ {
rgbState: State ← ColorPlotGamuts.StateFromRGBCalibration[pi.rgbCal, xAxis, lStar, pi.res, ColorPlotGamuts.White, pi.rgbCal];
sampledState: State ← ColorPlotGamuts.StateFromCalibration[pi.sampledCal, xAxis, lStar, pi.res, ColorPlotGamuts.Black, pi.sampledCal];
inputData: RefCache ← pi.inputData;
states: LIST OF State ← LIST[sampledState, rgbState];
IF inputData =
NIL
THEN {
inputData ← CreateInputData[pi.aisRoot, pi.rgbCal];
pi.inputData ← inputData};
ColorPlotGamuts.RGBSampleAndPlot[rgbState, pi.rgbCal, 8, ColorPlotGamuts.WholeGamut];
ColorPlotGamuts.SampleAndPlot[sampledState, pi.sampledCal, 16, ColorPlotGamuts.WholeGamut, FALSE];
IF mapInfo.rgbState THEN PlotInputData[rgbState, inputData, mapInfo.mapData, mapInfo.mapProc, SatRainbow]
ELSE {
PlotInputData[sampledState, inputData, mapInfo.mapData, mapInfo.mapProc, SatRainbow];
states ← LIST[rgbState, sampledState];
};
ColorPlotGamuts.StatesToInterpress[states: states, ipName: Rope.Cat[pi.ipRoot, ".ip"], label: TRUE, note: pi.note, labelColor: pi.labelColor, bleedBackground: pi.bleedBackground];
};
Make3LABViews:
PUBLIC PROC [pi: PlotInfo, mapInfo: MapInfo] ~ {
ipRoot: ROPE ← pi.ipRoot;
pi.ipRoot ← Rope.Cat[ipRoot, "AB"];
MakeChromaPlot[pi: pi, xAxis: aStar, yAxis: bStar, mapInfo: mapInfo];
pi.ipRoot ← Rope.Cat[ipRoot, "AL"];
MakeLStarPlot[pi: pi, xAxis: aStar, mapInfo: mapInfo];
pi.ipRoot ← Rope.Cat[ipRoot, "BL"];
MakeLStarPlot[pi: pi, xAxis: bStar, mapInfo: mapInfo];
};
Rainbow: ColorProc = {
RETURN[[R: sample.s0/255.0, G: sample.s1/255.0, B: sample.s2/255.0]];
};
DoSat:
PROC[in:
RGB]
RETURNS[out:
RGB] = {
hsl: ImagerColorFns.HSL← ImagerColorFns.HSLFromRGB[boundRGB[in]];
boundRGB:
PROC[val:
RGB]
RETURNS[
RGB] = {
RETURN[[R: MAX[MIN[val.R, 1],0], G: MAX[MIN[val.G, 1],0], B: MAX[MIN[val.B, 1],0]]]};
out ← ImagerColorFns.RGBFromHSL[[H: hsl.H, S: hsl.S, L: 0.5]];
};
SatRainbow: ColorProc = {
rgb: RGB ← [R: sample.s0/255.0, G: sample.s1/255.0, B: sample.s2/255.0];
rgb ← DoSat[rgb];
RETURN[rgb];
};
SatRGB: ColorPlotGamuts.GetColorProc = {
--assumes data is RGBCalibration
cal: RGBCalibration ← NARROW[data];
rgb: RGB ← CalibratedColor.RGBFromXYZ[xyz, cal];
rgb ← DoSat[rgb];
RETURN[rgb];
};
SatSampled: ColorPlotGamuts.GetColorProc = {
--assumes data is RGBCalibration
cal: SampledCalibration ← NARROW[data];
triple: CalibratedColor.Triple ← CalibratedColor.TripleFromXYZ[xyz, cal].triple;
rgb: RGB ← [triple.v0, triple.v1, triple.v2];
rgb ← DoSat[rgb];
RETURN[rgb];
};
Map Image Gamuts
SampleProc: TYPE = PROC[in: Sample] RETURNS[out: Sample];
AdjustFiles:
PUBLIC
PROC [fileStem, adjusted:
ROPE, proc: SampleProc] ~ {
Opens the files indicated by the fileStem and creates files adjusted by lightness
inFiles: AISFiles ← OpenFiles[fileStem];
outFiles: AISFiles ← CreateFiles[adjusted, inFiles[r].raster];
nLines: NAT ← inFiles[r].raster.scanCount;
nPixels: NAT ← inFiles[r].raster.scanLength;
key, sample: Sample;
valid: BOOLEAN ← FALSE;
cache: Cache ← SampleCache.Create[];
Process.SetPriority[Process.priorityBackground];
FOR line:
NAT
IN [0..nLines)
DO
FOR pixel:
NAT
IN [0..nPixels)
DO
key.s0 ← AIS.ReadSample[w: inFiles[r].wRef,line: line, pixel: pixel];
key.s1 ← AIS.ReadSample[w: inFiles[g].wRef,line: line, pixel: pixel];
key.s2 ← AIS.ReadSample[w: inFiles[b].wRef,line: line, pixel: pixel];
[valid, sample] ← SampleCache.Fetch[cache, key];
IF
NOT valid
THEN {
sample ← proc[key];
SampleCache.Store[cache, key, sample]
};
AIS.WriteSample[w: outFiles[r].wRef,value: sample.s0, line: line, pixel: pixel];
AIS.WriteSample[w: outFiles[g].wRef,value: sample.s1, line: line, pixel: pixel];
AIS.WriteSample[w: outFiles[b].wRef,value: sample.s2, line: line, pixel: pixel];
AIS.WriteSample[w: outFiles[clipped].wRef,value: sample.s3, line: line, pixel: pixel];
ENDLOOP;
ENDLOOP;
CloseFiles[inFiles];
CloseFiles[outFiles];
};
SaturationMatrix:
PUBLIC
PROC [rgbToLStar: PrintColorXForms.TRCTable, saturation:
REAL, printerDMax:
REAL]
RETURNS [PrintColorXForms.MatrixTables]~ {
matrix: PrintColorXForms.MatrixN ← PrintColorXForms.IdentityMatrix[];
RETURN[PrintColorXForms.InitMatrixTables[
rgbToLStar: rgbToLStar,
densityToDotArea: PrintColorXForms.DensityToLStar[printerDMax],
printerDMax: printerDMax,
saturation: saturation,
contrast: -0.0,
matrix: matrix]];
};
MatrixMapProc:
PROC[in: Sample, matrixTables: PrintColorXForms.MatrixTables] RETURNS [out: Sample] = {
rgbIn: ImagerPixel.PixelBuffer ← ImagerPixel.NewPixels[3,1];
cmyOut: ImagerPixel.PixelBuffer ← ImagerPixel.NewPixels[3,1];
maxSampleIn: ARRAY [0..3) OF CARDINAL ← [255, 255, 255];
rgbIn[0][0] ← in.s0;
rgbIn[1][0] ← in.s1;
rgbIn[2][0] ← in.s2;
PrintColorXForms.MatrixTransform[maxSampleIn, rgbIn, cmyOut, matrixTables];
out.s0 ← 255-cmyOut[0][0];
out.s1 ← 255-cmyOut[1][0];
out.s2 ← 255-cmyOut[2][0];
out.s3 ← 0;
};
AdjustWithMatrix:
PUBLIC
PROC [fileStem, adjusted:
ROPE, matrixTables: PrintColorXForms.MatrixTables] ~ {
Maps R, G, B through LStar map independently. Can use either ConracToLStar or RGBToLStar for the array. This works like a gamma correction
rgbIn: ImagerPixel.PixelBuffer ← ImagerPixel.NewPixels[3,1];
cmyOut: ImagerPixel.PixelBuffer ← ImagerPixel.NewPixels[3,1];
maxSampleIn: ARRAY [0..3) OF CARDINAL ← [255, 255, 255];
map: SampleProc = {
rgbIn[0][0] ← in.s0;
rgbIn[1][0] ← in.s1;
rgbIn[2][0] ← in.s2;
PrintColorXForms.MatrixTransform[maxSampleIn, rgbIn, cmyOut, matrixTables];
out.s0 ← 255-cmyOut[0][0];
out.s1 ← 255-cmyOut[1][0];
out.s2 ← 255-cmyOut[2][0];
out.s3 ← 0;
};
AdjustFiles[fileStem, adjusted, map];
};
AdjustRGBToLStar:
PUBLIC
PROC [fileStem, adjusted:
ROPE, array: ARRAY[0..255]
OF
BYTE] ~ {
Maps R, G, B through LStar map independently. Can use either ConracToLStar or RGBToLStar for the array. This works like a gamma correction
map: SampleProc = {
out.s0 ← array[in.s0];
out.s1 ← array[in.s1];
out.s2 ← array[in.s2];
out.s3 ← 0;
};
AdjustFiles[fileStem, adjusted, map];
};
AdjustOutOfGamut:
PUBLIC
PROC [fileStem, adjusted:
ROPE, useCIELAB:
BOOLEAN ←
FALSE] ~ {
Makes an image that is black wherever the rgb to xyz (or cielab) to cmy conversion
is out-of-gamut, otherwise just copy the color over.
rgbCal: RGBCalibration;
sampledCal: SampledCalibration;
sampleWhite, rgbWhite: XYZ;
map: SampleProc = {
clipped: BOOLEAN;
xyz: XYZ ← CalibratedColor.XYZFromRGB[[in.s0/255.0, in.s1/255.0, in.s2/255.0],rgbCal];
IF useCIELAB
THEN {
cielab: CIELAB ← CalibratedColorFns.CIELABFromXYZ[xyz,rgbWhite];
xyz ← [cielab.lStar, cielab.aStar, cielab.bStar];
};
[,,clipped] ← CalibratedColor.TripleFromXYZ[xyz,sampledCal, 0.1];
IF clipped THEN out ← [0,0,0,1]
ELSE {
out.s0 ← in.s0;
out.s1 ← in.s1;
out.s2 ← in.s2;
out.s3 ← 0;
};
};
[rgbCal, sampledCal] ← GetCalibrations[$Conrac, $Cromalin];
sampleWhite ← CalibratedColor.XYZFromTriple[[1,1,1], sampledCal];
rgbWhite ← CalibratedColor.XYZFromRGB[[1,1,1], rgbCal];
convert the sampled calibration to CIELAB.
IF useCIELAB
THEN
FOR v1:
NAT
IN [0..8)
DO
FOR v2:
NAT
IN [0..8)
DO
FOR v3:
NAT
IN [0..8)
DO
xyz: XYZ;
cielab: CIELAB;
xyz.X ← sampledCal.cieX[v1][v2][v3];
xyz.Y ← sampledCal.cieY[v1][v2][v3];
xyz.Z ← sampledCal.cieX[v1][v2][v3];
cielab ← CalibratedColorFns.CIELABFromXYZ[xyz,sampleWhite];
sampledCal.cieX[v1][v2][v3] ← cielab.lStar;
sampledCal.cieY[v1][v2][v3] ← cielab.aStar;
sampledCal.cieZ[v1][v2][v3] ← cielab.bStar;
ENDLOOP;
ENDLOOP;
ENDLOOP;
AdjustFiles[fileStem, adjusted, map];
};
RGBToLStar:
ARRAY[0..255]
OF
BYTE ←
[0, 9, 18, 26, 33, 39, 44, 48, 52, 56, 60, 63, 66, 69, 72, 74, 77, 79, 81, 84, 86, 88, 90, 92, 94, 96, 97, 99, 101, 103, 104, 106, 107, 109, 110, 112, 113, 115, 116, 117, 119, 120, 121, 123, 124, 125, 126, 128, 129, 130, 131, 132, 133, 134, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 151, 152, 153, 154, 155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 163, 164, 165, 166, 167, 167, 168, 169, 170, 171, 171, 172, 173, 174, 174, 175, 176, 176, 177, 178, 179, 179, 180, 181, 181, 182, 183, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189, 190, 191, 191, 192, 192, 193, 194, 194, 195, 196, 196, 197, 197, 198, 198, 199, 200, 200, 201, 201, 202, 203, 203, 204, 204, 205, 205, 206, 206, 207, 208, 208, 209, 209, 210, 210, 211, 211, 212, 212, 213, 213, 214, 215, 215, 216, 216, 217, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224, 225, 225, 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236, 236, 237, 237, 238, 238, 239, 239, 240, 240, 240, 241, 241, 242, 242, 242, 243, 243, 244, 244, 245, 245, 245, 246, 246, 247, 247, 247, 248, 248, 249, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 253, 254, 254, 255, 255];
AdjustRGBToLStarGC:
PUBLIC
PROC [fileStem, adjusted:
ROPE] ~ {
Maps MIN[R,G,B] through the RGBToLStar trc and adds it back into the image
map: SampleProc = {
gc: BYTE ← MIN[in.s0, in.s1, in.s2];
newgc: BYTE ← RGBToLStar[gc];
r: NAT ← in.s0-gc+newgc;
g: NAT ← in.s1-gc+newgc;
b: NAT ← in.s2-gc+newgc;
IF r >255 OR g>255 OR b>255 THEN out.s3 ← 1 ELSE out.s3 ← 0;
out.s0 ← MIN[255, r];
out.s1 ← MIN[255, g];
out.s2 ← MIN[255, b];
};
AdjustFiles[fileStem, adjusted, map];
};
CMYToLStar:
ARRAY[0..255]
OF
BYTE ←
[0, 0, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 8, 9, 9, 10, 10, 10, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, 30, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, 39, 39, 40, 40, 41, 42, 42, 43, 43, 44, 44, 45, 45, 46, 46, 47, 47, 48, 49, 49, 50, 50, 51, 51, 52, 52, 53, 54, 54, 55, 55, 56, 57, 57, 58, 58, 59, 59, 60, 61, 61, 62, 63, 63, 64, 64, 65, 66, 66, 67, 68, 68, 69, 70, 70, 71, 72, 72, 73, 74, 74, 75, 76, 76, 77, 78, 79, 79, 80, 81, 81, 82, 83, 84, 84, 85, 86, 87, 88, 88, 89, 90, 91, 92, 92, 93, 94, 95, 96, 96, 97, 98, 99, 100, 101, 102, 103, 104, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 121, 122, 123, 124, 125, 126, 127, 129, 130, 131, 132, 134, 135, 136, 138, 139, 140, 142, 143, 145, 146, 148, 149, 151, 152, 154, 156, 158, 159, 161, 163, 165, 167, 169, 171, 174, 176, 178, 181, 183, 186, 189, 192, 195, 199, 203, 207, 211, 216, 222, 229, 237, 246, 255];
AdjustCMYToLStar:
PUBLIC
PROC [fileStem, adjusted:
ROPE] ~ {
Maps MIN[cmy] through the CMYToLStar trc and adds it back into the image
CMYToLStar is derived from RGBToLStar, so this whole thing is highly dubious
map: SampleProc = {
c: NAT ← 255-in.s0;
m: NAT ← 255-in.s1;
y: NAT ← 255-in.s2;
gc: BYTE ← MIN[c,m,y];
newgc: BYTE ← CMYToLStar[gc];
r: NAT ← 255-(c-gc+newgc);
g: NAT ← 255-(m-gc+newgc);
b: NAT ← 255-(y-gc+newgc);
IF r >255 OR g>255 OR b>255 THEN out.s3 ← 1 ELSE out.s3 ← 0;
out.s0 ← MIN[255, r];
out.s1 ← MIN[255, g];
out.s2 ← MIN[255, b];
};
AdjustFiles[fileStem, adjusted, map];
};
ConracToLStarTRC:
PROC
RETURNS[trc: PrintColorXForms.TRCTable] = {
trc ← NEW[PrintColorXForms.TRCTableRec];
FOR i:
NAT
IN [0..256)
DO
trc[i] ← ConracToLStar[i];
ENDLOOP;
};
ConracToLStar:
ARRAY[0..255]
OF
BYTE ←
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 9, 11, 14, 16, 19, 22, 25, 29, 32, 35, 37, 39, 42, 44, 46, 48, 51, 53, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 83, 85, 87, 88, 90, 91, 93, 94, 95, 97, 98, 100, 101, 103, 104, 106, 107, 109, 110, 112, 113, 114, 116, 117, 118, 119, 121, 122, 123, 125, 126, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 149, 150, 151, 152, 153, 153, 154, 155, 156, 157, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 168, 169, 170, 171, 172, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 181, 182, 183, 184, 185, 185, 186, 187, 188, 189, 189, 190, 191, 192, 193, 193, 194, 195, 196, 197, 197, 198, 199, 200, 201, 201, 202, 203, 204, 205, 205, 206, 207, 208, 208, 209, 209, 210, 211, 212, 212, 213, 214, 215, 216, 216, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 224, 224, 225, 225, 226, 227, 227, 228, 228, 229, 230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236, 237, 238, 238, 239, 239, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 245, 246, 246, 247, 247, 247, 248, 248, 249, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 253, 254, 254, 255, 255];