~
BEGIN
kU: PrintColor.TonerUniverse ~ [black: TRUE];
cmyU: PrintColor.TonerUniverse ~ [cyan: TRUE, magenta: TRUE, yellow: TRUE];
cmykU: PrintColor.TonerUniverse ~ [black:
TRUE, cyan:
TRUE, magenta:
TRUE, yellow:
TRUE];
TileShape: TYPE ~ RECORD [box: SF.Box, phase: INTEGER];
GetGoodShape:
PROC [box:
SF.Box, phase:
INTEGER]
RETURNS [TileShape] ~ {
size: SF.Vec ~ SF.Size[box];
IF size.s <= 0 OR size.f <= 0 THEN ERROR;
WHILE phase < 0 DO phase ← phase + size.f ENDLOOP;
IF (16
MOD
NAT[size.s] = 0)
AND (16
MOD
NAT[size.f] = 0)
AND ((
NAT[phase]*(16/
NAT[size.s]))
MOD 16 = 0)
THEN {
Can turn this to a 16 by 16 brick.
RETURN [[box: [max: [16, 16]], phase: 0]]
};
RETURN [[box: [max: size], phase: phase]];
};
PixelMapWords:
PROC [pixelMap: ImagerPixel.PixelMap]
RETURNS [
INT] ~ {
words: INT ← 0;
FOR i:
NAT
IN [0..pixelMap.samplesPerPixel)
DO
words ← words + ImagerSample.WordsForMap[size: ImagerSample.GetSize[pixelMap[i]], bitsPerSample: ImagerSample.GetBitsPerSample[pixelMap[i]]]
ENDLOOP;
RETURN [words];
};
NewDeviceColorData:
PUBLIC
PROC [logicalDevice: PrintColor.LogicalDevice, halftoneProperties: PrintColor.HalftoneProperties, correction: PrintColor.ColorCorrection ←
NIL, interpolate:
BOOL ←
FALSE]
RETURNS [ImagerPrintColor.DeviceColorData] ~ {
deviceColorData: ImagerPrintColor.DeviceColorData ← NEW[ImagerPrintColor.DeviceColorDataRep];
deviceColorData.halftoneProperties ← halftoneProperties;
deviceColorData.correction ← correction;
deviceColorData.interpolate ← interpolate;
deviceColorData.case ← nil;
deviceColorData.pixelToDevice ← ImagerTransformation.Scale[1];
deviceColorData.bitmap ← NEW[ImagerPixel.PixelMapRep[1]];
deviceColorData.tonerUniverse ← ALL[FALSE];
FOR each:
LIST
OF PrintColor.HalftonePropertiesForSeparation ← halftoneProperties, each.rest
UNTIL each =
NIL
DO
deviceColorData.tonerUniverse[each.first.toner] ← TRUE;
ENDLOOP;
RETURN [deviceColorData]
};
SetSeparation:
PUBLIC
PROC [deviceColorData: ImagerPrintColor.DeviceColorData, toner: PrintColor.Toner] ~ {
IF NOT deviceColorData.tonerUniverse[toner] THEN ERROR Imager.Error[[code: $invalidToner, explanation: "Illegal toner (no halftone properties supplied)"]];
deviceColorData.brick ← [0, NIL, 0];
FOR each:
LIST
OF PrintColor.HalftonePropertiesForSeparation ← deviceColorData.halftoneProperties, each.rest
UNTIL each =
NIL
DO
IF each.first.toner = toner THEN deviceColorData.brick ← each.first.brick;
ENDLOOP;
deviceColorData.toner ← toner;
};
SetDeviceColorData:
PUBLIC
PROC [deviceColorData: ImagerPrintColor.DeviceColorData, color: ImagerColor.Color, viewToDevice: ImagerTransformation.Transformation] ~ {
deviceColorData.case ← nil;
deviceColorData.ink ← set;
deviceColorData.tileBits ← NIL;
deviceColorData.alpha ← 255;
deviceColorData.separation ← NIL;
deviceColorData.bitmap[0] ← NIL;
IF deviceColorData.sampleMapInUse #
NIL
THEN {
ImagerSample.ReleaseScratchMap[deviceColorData.sampleMapInUse];
deviceColorData.sampleMapInUse ← NIL;
};
deviceColorData.function ← [null, null];
DO
WITH color
SELECT
FROM
specialColor: ImagerColor.SpecialColor => {
SELECT specialColor.type
FROM
$PrintColor => {
data: PrintColor.PrintSpecialColorData ~
NARROW[specialColor.data];
IF deviceColorData.logicalDevice < data.size
THEN {
IF data[deviceColorData.logicalDevice] =
NIL
THEN {
ColorRegistry.??
};
IF data[deviceColorData.logicalDevice] #
NIL
THEN {
SetLogicalDeviceColor[deviceColorData, data[deviceColorData.logicalDevice]];
RETURN;
};
};
color ← specialColor.substitute; LOOP;
};
ENDCASE => {
IF specialColor.substitute =
NIL
THEN ERROR Imager.Error[[code: $unknownSpecialColor, explanation: "Unknown special color has no substitute value"]]
ELSE { color ← specialColor.substitute; LOOP }
};
};
opColor: ImagerColor.OpConstantColor => {
SetOpConstantColor[deviceColorData, opColor];
};
sampledColor: ImagerColor.SampledColor => {
ImagerTransformation.ApplyCat[deviceColorData.pixelToDevice, sampledColor.pa.m, sampledColor.um, viewToDevice];
SetSampledColor[deviceColorData, sampledColor];
};
sampledBlack: ImagerColor.SampledBlack => {
ImagerTransformation.ApplyCat[deviceColorData.pixelToDevice, sampledBlack.pa.m, sampledBlack.um, viewToDevice];
SetSampledBlack[deviceColorData, sampledBlack];
};
ENDCASE => ERROR;
EXIT;
ENDLOOP;
};
SetInk:
PROC [deviceColorData: ImagerPrintColor.DeviceColorData, ink: PrintColor.Ink] ~ {
deviceColorData.case ← constant;
deviceColorData.ink ← ink;
};
SetFraction:
PROC [deviceColorData: ImagerPrintColor.DeviceColorData, f:
REAL] ~ {
SELECT f
FROM
>= 0.9999 => {
-- all toner
SetInk[deviceColorData, set]
};
<= 0.0001 => {
-- no toner
SetInk[deviceColorData, remove]
};
ENDCASE => {
brick: ImagerBitmapContext.Brick ~ deviceColorData.brick;
shape: TileShape ~ GetGoodShape[ImagerSample.GetBox[brick.sampleMap], brick.phase];
bitmap: ImagerSample.RasterSampleMap ~ ImagerSample.ObtainScratchMap[shape.box];
b: ImagerSample.SampleBuffer ~ ImagerSample.ObtainScratchSamples[SF.SizeF[shape.box]];
sample: CARDINAL ~ Real.InlineRoundC[f*brick.maxSample];
ImagerSample.Clear[bitmap];
FOR s:
INTEGER
IN [shape.box.min.s..shape.box.max.s)
DO
ImagerSample.GetTileSamples[tile: brick.sampleMap, phase: brick.phase, initIndex: [s, shape.box.min.f], buffer: b];
FOR j:
INTEGER
IN [0..b.length)
DO
b[j] ← IF sample > b[j] THEN 1 ELSE 0;
ENDLOOP;
ImagerSample.PutSamples[map: bitmap, initIndex: [s, shape.box.min.f], buffer: b];
ENDLOOP;
SetTile[deviceColorData: deviceColorData, tileBits: bitmap, tilePhase: shape.phase, function: [null, null]];
deviceColorData.sampleMapInUse ← bitmap; -- for eventual recycling
};
};
SetTile:
PROC [deviceColorData: ImagerPrintColor.DeviceColorData, tileBits: ImagerSample.SampleMap, tilePhase:
NAT, function: ImagerSample.Function] ~ {
deviceColorData.case ← tile;
deviceColorData.tileBits ← tileBits;
deviceColorData.tilePhase ← tilePhase;
deviceColorData.function ← function;
};
SetLogicalDeviceColor:
PROC [deviceColorData: ImagerPrintColor.DeviceColorData, lData: PrintColor.LogicalDeviceColorData] ~ {
IF deviceColorData.logicalDevice # lData.logicalDevice THEN ERROR; -- bad color def
FOR e:
LIST
OF PrintColor.BehaviorForSeparation ← lData.behaviors, e.rest
UNTIL e =
NIL
DO
b: PrintColor.BehaviorForSeparation ~ e.first;
IF b.toner = deviceColorData.toner
THEN {
WITH b
SELECT
FROM
b: REF PrintColor.BehaviorForSeparationRep.constant => SetInk[deviceColorData, b.ink];
b: REF PrintColor.BehaviorForSeparationRep.fraction => SetFraction[deviceColorData, b.f];
b: REF PrintColor.BehaviorForSeparationRep.tile => SetTile[deviceColorData, b.tileBits, b.tilePhase, b.function];
ENDCASE => ERROR;
RETURN;
};
ENDLOOP;
SetInk[deviceColorData, remove]; -- no behavior was specified.
};
SetSubtractivePrimaries:
PROC [deviceColorData: ImagerPrintColor.DeviceColorData, color: ImagerColor.OpConstantColor, output: ImagerColorPrivate.ColorOutput] ~ {
set primaries directly, no color correction
indexFromToner: ARRAY PrintColor.Toner[black..yellow] OF NAT ~ [cyan: 0, magenta: 1, yellow: 2, black: 3];
min: PrintColor.Toner ~ IF output.type = $CMYK THEN black ELSE cyan;
PixelIn: ImagerColorPrivate.PixelProc ~ { RETURN[color.pixel[i]] };
DoTuple:
PROC [tupleOut: ImagerColorPrivate.TupleProc] ~ {
IF deviceColorData.toner
IN [min..yellow]
THEN {SetFraction[deviceColorData, tupleOut[indexFromToner[deviceColorData.toner]]]}
ELSE {SetInk[deviceColorData, remove]};
};
ImagerColorPrivate.TupleFromPixel[color.colorOperator, output, PixelIn, DoTuple];
};
SetRGB:
PROC [deviceColorData: ImagerPrintColor.DeviceColorData, rgb: ImagerColor.
RGB] ~ {
SetRGB may do color correction
IF deviceColorData.correction #
NIL
THEN {
pixelsRGB: ImagerPixel.PixelBuffer ~ ImagerPixel.ObtainScratchPixels[samplesPerPixel: 3, length: 1];
pixelsOut: ImagerPixel.PixelBuffer ~ ImagerPixel.ObtainScratchPixels[samplesPerPixel: IF deviceColorData.tonerUniverse[black] THEN 4 ELSE 3, length: 1];
pixelsRGB[0][0] ← Real.InlineRoundC[rgb.R*255];
pixelsRGB[1][0] ← Real.InlineRoundC[rgb.G*255];
pixelsRGB[2][0] ← Real.InlineRoundC[rgb.B*255];
deviceColorData.correction.correctionProc[self: deviceColorData.correction, pixelsOut: pixelsOut, maxSampleOut: ALL[255], rgbIn: pixelsRGB];
SetFraction[deviceColorData,
SELECT deviceColorData.toner
FROM
cyan => pixelsOut[0][0]/255.0,
magenta => pixelsOut[1][0]/255.0,
yellow => pixelsOut[2][0]/255.0,
black => pixelsOut[3][0]/255.0,
ENDCASE => 0.0
];
ImagerPixel.ReleaseScratchPixels[pixelsRGB];
ImagerPixel.ReleaseScratchPixels[pixelsOut];
}
ELSE {
SetFraction[deviceColorData,
SELECT deviceColorData.toner
FROM
cyan => 1.0 - rgb.R,
magenta => 1.0 - rgb.G,
yellow => 1.0 - rgb.B,
ENDCASE => 0.0
];
};
};
SetGray:
PROC [deviceColorData: ImagerPrintColor.DeviceColorData, f:
REAL] ~ {
This requires a black toner to be present.
IF deviceColorData.tonerUniverse[black]
THEN {
IF deviceColorData.toner = black
THEN SetFraction[deviceColorData, f]
ELSE SetInk[deviceColorData, remove];
}
ELSE {
SetInk[deviceColorData, set];
SIGNAL Imager.Warning[[$illegalColor, "Unable to render gray on this device"]];
};
};
SetOpConstantColor:
PROC [deviceColorData: ImagerPrintColor.DeviceColorData, color: ImagerColor.OpConstantColor] ~ {
tu: PrintColor.TonerUniverse ~ deviceColorData.tonerUniverse;
SELECT tu
FROM
cmyU => {
IF ImagerColorPrivate.SupportsOutput[color.colorOperator, $CMY]
THEN {
SetSubtractivePrimaries[deviceColorData, color, [type: $CMY, samplesPerPixelOut: 3]];
RETURN;
};
};
cmykU => {
IF ImagerColorPrivate.SupportsOutput[color.colorOperator, $CMYK]
THEN {
SetSubtractivePrimaries[deviceColorData, color, [type: $CMYK, samplesPerPixelOut: 4]];
RETURN;
};
};
ENDCASE => NULL;
IF tu[cyan]
AND tu[magenta]
AND tu[yellow]
SetRGB may do color correction
THEN { SetRGB[deviceColorData, ImagerColorPrivate.RGBFromColor[color]] }
ELSE { SetGray[deviceColorData, 1.0 - ImagerColorPrivate.IntensityFromColor[color]] };
};
SampledColorData: TYPE ~ REF SampledColorDataRep;
SampledColorDataRep:
TYPE ~
RECORD [
pa: ImagerPixelArray.PixelArray,
colorOperator: ImagerColor.ColorOperator,
fullColor: BOOL,
colorCorrection: PrintColor.ColorCorrection,
source: ImagerPixel.PixelMap
];
SetSampledColor:
PROC [deviceColorData: ImagerPrintColor.DeviceColorData, color: ImagerColor.SampledColor] ~ {
pa: ImagerPixelArray.PixelArray ~ color.pa;
cache: FunctionCache.Cache ~ FunctionCache.GlobalCache[];
colorOperator: ImagerColor.ColorOperator ~ color.colorOperator;
tu: PrintColor.TonerUniverse ~ deviceColorData.tonerUniverse;
fullColor: BOOL ~ colorOperator.chromatic AND tu[cyan] AND tu[magenta] AND tu[yellow];
Compare: FunctionCache.CompareProc ~ {
WITH argument
SELECT
FROM
scd: SampledColorData => RETURN [scd.pa = pa AND scd.colorOperator = colorOperator AND scd.fullColor = fullColor AND scd.colorCorrection = deviceColorData.correction];
ENDCASE => RETURN [FALSE]
};
toner: PrintColor.Toner ~ deviceColorData.toner;
index: NAT ~ IF fullColor THEN (SELECT toner FROM black => 3, cyan => 0, magenta => 1, yellow => 2, ENDCASE => 0) ELSE 0;
scd: SampledColorData ← NARROW[FunctionCache.Lookup[x: cache, compare: Compare, clientID: $PrintColor].value];
IF scd =
NIL
THEN {
pixelMap: ImagerPixel.PixelMap ~ ComputeSource[deviceColorData, color];
scd ← NEW [SampledColorDataRep ← [pa: pa, colorOperator: colorOperator, fullColor: fullColor, colorCorrection: deviceColorData.correction, source: pixelMap]];
IF pa.immutable THEN FunctionCache.Insert[x: cache, argument: scd, value: scd, size: PixelMapWords[pixelMap], clientID: $PrintColor];
};
IF index >= scd.source.samplesPerPixel
THEN { SetInk[deviceColorData, remove] }
ELSE {
deviceColorData.case ← sampledColor;
deviceColorData.separation ← ImagerPixel.MakePixelMap[scd.source[index]];
};
};
ComputeSource:
PROC [deviceColorData: ImagerPrintColor.DeviceColorData, color: ImagerColor.SampledColor]
RETURNS [ImagerPixel.PixelMap] ~ {
pa: ImagerPixelArray.PixelArray ~ color.pa;
colorOperator: ImagerColor.ColorOperator ~ color.colorOperator;
tu: PrintColor.TonerUniverse ~ deviceColorData.tonerUniverse;
fullColor: BOOL ~ colorOperator.chromatic AND tu[cyan] AND tu[magenta] AND tu[yellow];
MaxOut: ImagerPixel.PixelProc ~ { RETURN[BYTE.LAST] }; -- for now
SELECT tu
FROM
cmyU => {
IF ImagerColorPrivate.SupportsOutput[color.colorOperator, $CMY]
THEN {
RETURN [ImagerColorPrivate.Translate[colorOperator: colorOperator, output: [type: $CMY, samplesPerPixelOut: 3], pa: pa, maxOut: MaxOut]]
};
};
cmykU => {
IF ImagerColorPrivate.SupportsOutput[color.colorOperator, $CMYK]
THEN {
RETURN [ImagerColorPrivate.Translate[colorOperator: colorOperator, output: [type: $CMYK, samplesPerPixelOut: 4], pa: pa, maxOut: MaxOut]]
}
};
ENDCASE => NULL;
CHECKED {
size: SF.Vec ~ [s: NAT[pa.sSize], f: NAT[pa.fSize]];
MaxIn: ImagerPixel.PixelProc ~ { RETURN[ImagerPixelArray.MaxSampleValue[pa, i]] };
samplesPerPixelOut:
NAT ~
SELECT
TRUE
FROM
fullColor AND tu[black] AND deviceColorData.correction#NIL => 4, -- CMYK
fullColor => 3, -- CMY
ENDCASE => 1; -- Intensity
pixelMap: ImagerPixel.PixelMap ~ ImagerPixel.NewPixelMap[samplesPerPixel: samplesPerPixelOut, box: [max: size], maxSample: MaxOut];
TranslateAction:
PROC [translate: ImagerColorPrivate.TranslateProc] ~ {
pixelsIn: ImagerPixel.PixelBuffer ~ ImagerPixel.ObtainScratchPixels[pa.samplesPerPixel, size.f];
pixelsRGB: ImagerPixel.PixelBuffer ~ ImagerPixel.ObtainScratchPixels[3, size.f];
pixelsOut: ImagerPixel.PixelBuffer ~ ImagerPixel.ObtainScratchPixels[samplesPerPixelOut, size.f];
FOR i:
NAT
IN [0..samplesPerPixelOut)
DO
ImagerSample.ClearSamples[pixelsOut[i]]
ENDLOOP;
FOR s:
NAT
IN [0..size.s)
DO
ImagerPixelArray.GetPixels[pa: pa, s: s, f: 0, pixels: pixelsIn];
IF fullColor
THEN {
correction: PrintColor.ColorCorrection ~ deviceColorData.correction;
translate[pixelsIn: pixelsIn, pixelsOut: pixelsRGB];
IF correction #
NIL
THEN {
correction.correctionProc[self: correction, pixelsOut: pixelsOut, maxSampleOut: ALL[255], rgbIn: pixelsRGB];
}
ELSE
TRUSTED {
FOR i:
NAT
IN [0..3)
DO
a: LONG POINTER TO Basics.RawWords ~ ImagerSample.PointerToSamples[buffer: pixelsRGB[i], start: 0, count: size.f];
b: LONG POINTER TO Basics.RawWords ~ ImagerSample.PointerToSamples[buffer: pixelsOut[i], start: 0, count: size.f];
FOR j: NAT IN [0..size.f) DO b[j] ← BYTE.LAST-a[j] ENDLOOP; -- convert to fraction of ink
ENDLOOP;
};
}
ELSE {
buf: LONG POINTER TO Basics.RawWords ~ ImagerSample.PointerToSamples[buffer: pixelsOut[0], start: 0, count: size.f];
translate[pixelsIn: pixelsIn, pixelsOut: pixelsOut];
TRUSTED {
FOR i: NAT IN [0..size.f) DO buf[i] ← BYTE.LAST-buf[i] ENDLOOP; -- convert to fraction of ink
};
};
pixelMap.PutPixels[initIndex: [s, 0], pixels: pixelsOut, count: size.f];
ENDLOOP;
ImagerPixel.ReleaseScratchPixels[pixelsIn];
ImagerPixel.ReleaseScratchPixels[pixelsRGB];
ImagerPixel.ReleaseScratchPixels[pixelsOut];
};
ImagerColorPrivate.TranslatePixels[colorOperator: colorOperator, output: IF fullColor THEN [type: $RGB, samplesPerPixelOut: 3] ELSE [type: $Y, samplesPerPixelOut: 1], maxIn: MaxIn, maxOut: MaxOut, translateAction: TranslateAction];
RETURN [pixelMap];
};
};
SetSampledBlack:
PROC [deviceColorData: ImagerPrintColor.DeviceColorData, color: ImagerColor.SampledBlack] ~ {
pa: ImagerPixelArray.PixelArray ~ color.pa;
IF deviceColorData.tonerUniverse[black]
AND deviceColorData.toner # black
THEN {
deviceColorData.case ← constant;
deviceColorData.ink ← IF color.clear THEN nop ELSE remove;
RETURN
};
deviceColorData.function ← IF color.clear THEN [or, null] ELSE [null, null];
SELECT
TRUE
FROM
deviceColorData.pixelToDevice.form = 3
AND deviceColorData.pixelToDevice.integerTrans => {
This bitmap is already in the right orientation for the output raster; just use it!
min: SF.Vec ~ [s: deviceColorData.pixelToDevice.tx, f: deviceColorData.pixelToDevice.ty];
max: SF.Vec ~ [s: min.s + pa.sSize, f: min.f + pa.fSize];
sampleMap: ImagerSample.SampleMap ~ ImagerSample.ObtainScratchMap[box: [min: min, max: max]];
ImagerPixelArray.Transfer[pa: pa, dst: sampleMap, dstMin: min];
deviceColorData.case ← tile;
deviceColorData.tilePhase ← 0;
deviceColorData.tileBits ← sampleMap;
deviceColorData.sampleMapInUse ← sampleMap;
};
FALSE => {
Want a case here to attempt to build a tile. See ImagerBitmapContextImpl
};
ENDCASE => {
The hard case - use the sampler
sampleMap: ImagerSample.SampleMap ~ ImagerSample.ObtainScratchMap[box: [max: [pa.sSize, pa.fSize]]];
ImagerPixelArray.Transfer[pa: pa, dst: sampleMap];
deviceColorData.case ← sampledBlack;
deviceColorData.bitmap[0] ← sampleMap;
deviceColorData.bitmap.box ← ImagerSample.GetBox[sampleMap];
deviceColorData.sampleMapInUse ← sampleMap;
};
};
MaskBoxes:
PUBLIC
PROC [bitmap: ImagerSample.SampleMap, deviceColorData: ImagerPrintColor.DeviceColorData, bounds:
SF.Box, boxes:
SF.BoxGenerator] ~ {
SELECT deviceColorData.case
FROM
constant => {
IF deviceColorData.ink # nop
THEN {
function: ImagerSample.Function ~ IF deviceColorData.ink = set THEN [or, null] ELSE [and, complement];
ImagerSample.FillBoxes[map: bitmap, boxes: boxes, value: 1, function: function];
};
};
tile => {
ImagerSample.TileBoxes[map: bitmap, boxes: boxes, tile: deviceColorData.tileBits, phase: deviceColorData.tilePhase, function: deviceColorData.function];
};
sampledColor =>
TRUSTED {
buffer: ImagerSample.SampleBuffer ~ ImagerSample.ObtainScratchSamples[SF.SizeF[bounds]];
Action:
SAFE
PROC [pixels: ImagerPixel.PixelBuffer, min:
SF.Vec] ~
TRUSTED {
buffer.length ← pixels.length;
ImagerSample.GetTileSamples[tile: deviceColorData.brick.sampleMap, initIndex: min, buffer: buffer, phase: deviceColorData.brick.phase];
ImagerSample.Halftone[map: bitmap, min: min, sampleBuffer: pixels[0], thresholdBuffer: buffer, function: [deviceColorData.function.dstFunc, IF deviceColorData.function.srcFunc = null THEN complement ELSE null]];
};
ImagerPixel.Resample[self: deviceColorData.separation, m: deviceColorData.pixelToDevice, interpolate: deviceColorData.interpolate, boxes: boxes, bounds: bounds, action: Action];
ImagerSample.ReleaseScratchSamples[buffer];
};
sampledBlack => {
Action:
PROC [pixels: ImagerPixel.PixelBuffer, min:
SF.Vec] ~ {
ImagerSample.PutSamples[map: bitmap, initIndex: min, buffer: pixels[0], start: 0, count: pixels.length, function: deviceColorData.function];
};
ImagerPixel.Resample[self: deviceColorData.bitmap, m: deviceColorData.pixelToDevice, interpolate: FALSE, boxes: boxes, bounds: bounds, action: Action];
};
ENDCASE => ERROR;
};