ImagerPrintColorImpl.mesa
Copyright Ó 1987, 1990, 1991, 1992, 1993 by Xerox Corporation. All rights reserved.
Michael Plass, October 26, 1993 2:18 pm PDT
Dave Rumph, July 10, 1991 11:58 am PDT
Willie-s, July 23, 1991 10:49 am PDT
Russ Atkinson (RRA) November 11, 1992 6:03 pm PST
DIRECTORY Basics, FunctionCache, Imager, ImagerBox, ImagerBitmapContext, ImagerBrick, ImagerColor, ImagerColorPrivate, ImagerDeviceColor, ImagerPixel, ImagerPixelArray, ImagerPrintColor, ImagerPrintColorPrivate, ImagerSample, ImagerTransformation, PrintColor, SF, Real;
ImagerPrintColorImpl: CEDAR PROGRAM
IMPORTS FunctionCache, Imager, ImagerBitmapContext, ImagerColor, ImagerColorPrivate, ImagerDeviceColor, ImagerPixelArray, ImagerSample, ImagerTransformation, SF, Real, ImagerPixel
EXPORTS ImagerPrintColor, ImagerPrintColorPrivate
~ BEGIN
PixelArray: TYPE ~ ImagerPixelArray.PixelArray;
Rectangle: TYPE ~ ImagerBox.Rectangle;
SampleMap: TYPE ~ ImagerSample.SampleMap;
Transformation: TYPE ~ ImagerTransformation.Transformation;
ColorOperator: TYPE ~ ImagerColor.ColorOperator;
ColorTransform: TYPE ~ ImagerColorPrivate.ColorTransform;
ColorPoint: TYPE ~ ImagerColorPrivate.ColorPoint;
PixelMap: TYPE ~ ImagerPixel.PixelMap;
PrivateRep: TYPE ~ RECORD [
scratchBitmapContext: Imager.Context ¬ NIL,
colorTransform: ImagerColorPrivate.ColorTransform ¬ NIL,
fractionCache: ARRAY [0..fractionMod] OF FractionEntry ¬ ALL[[]],
control: ImagerDeviceColor.DeviceColorControl ¬ NIL,
controlStamp: CARD ¬ 0
];
fractionMod: NAT = 256;
FractionEntry: TYPE = RECORD [
brick: ImagerBrick.Brick ¬ [0, NIL, 0],
bitmap: ImagerSample.RasterSampleMap ¬ NIL,
phase: INTEGER ¬ 0,
sample: CARDINAL ¬ 0];
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];
fractionProbes: CARD ¬ 0;
fractionMisses: CARD ¬ 0;
GetFractionProbes: PROC RETURNS [CARD] = {
interp [ GetFractionProbes←P60 ]
RETURN [fractionProbes];
};
GetFractionMisses: PROC RETURNS [CARD] = {
interp [ GetFractionMisses←P120 ]
RETURN [fractionMisses];
};
TileShape: TYPE ~ RECORD [box: SF.Box, phase: INTEGER];
bpw: NAT ~ BITS[WORD];
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 (bpw MOD NAT[size.s] = 0) AND (bpw MOD NAT[size.f] = 0) AND ((NAT[phase]*(bpw/NAT[size.s])) MOD bpw = 0) THEN {
Can turn this to a bpw by bpw brick.
RETURN [[box: [max: [bpw, bpw]], 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;
deviceColorData.private ¬ NEW[PrivateRep ¬ []];
RETURN [deviceColorData]
};
SetSeparation: PUBLIC PROC
[deviceColorData: ImagerPrintColor.DeviceColorData, toner: PrintColor.Toner] ~ {
old: ImagerBrick.Brick = deviceColorData.brick;
new: ImagerBrick.Brick ¬ [0, NIL, 0];
IF NOT deviceColorData.tonerUniverse[toner] THEN
ERROR Imager.Error[[code: $invalidToner, explanation: "Illegal toner (no halftone properties supplied)"]];
FOR each: LIST OF PrintColor.HalftonePropertiesForSeparation ¬ deviceColorData.halftoneProperties, each.rest UNTIL each = NIL DO
IF each.first.toner = toner THEN {new ¬ each.first.brick; EXIT};
ENDLOOP;
IF new.sampleMap = NIL AND deviceColorData.halftoneProperties # NIL THEN {
This case might come up when trying to set a gray screen on a color device - we need to use something!
new ¬ deviceColorData.halftoneProperties.first.brick;
};
IF new.maxSample # old.maxSample OR new.phase # old.phase
OR new.sampleMap = NIL OR old.sampleMap = NIL
OR NOT ImagerSample.Equal[new.sampleMap, old.sampleMap] THEN
We ONLY change the brick if the new and old bricks differ in contents. This allows the fraction cache to work better. This assumes that the brick sample maps are readonly and persistent.
deviceColorData.brick ¬ new;
deviceColorData.toner ¬ toner;
};
SetDeviceColorControl: PUBLIC PROC [deviceColorData: ImagerPrintColor.DeviceColorData, deviceColorControl: ImagerDeviceColor.DeviceColorControl] = {
private: REF PrivateRep = NARROW[deviceColorData.private];
private.control ¬ deviceColorControl;
private.controlStamp ¬ deviceColorControl.stamp - 1;
};
SetDeviceColorData: PUBLIC PROC [deviceColorData: ImagerPrintColor.DeviceColorData, color: ImagerColor.Color, viewToDevice: 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 => SetInk[deviceColorData, set];
<= 0.0001 => SetInk[deviceColorData, remove];
ENDCASE => {
brick: ImagerBrick.Brick ~ deviceColorData.brick;
sample: CARDINAL ~ Real.Round[f*brick.maxSample];
SELECT sample FROM
brick.maxSample => SetInk[deviceColorData, set];
0 => SetInk[deviceColorData, remove];
ENDCASE => {
private: REF PrivateRep = NARROW[deviceColorData.private];
mod: [0..fractionMod) = sample MOD fractionMod;
fractionProbes ¬ fractionProbes + 1;
IF private # NIL THEN
IF private.fractionCache[mod].brick = brick
AND private.fractionCache[mod].sample = sample THEN {
We have a match!
SetTile[deviceColorData: deviceColorData,
tileBits: private.fractionCache[mod].bitmap,
tilePhase: private.fractionCache[mod].phase,
function: [null, null]];
RETURN;
};
{
shape: TileShape ~ GetGoodShape[ImagerSample.GetBox[brick.sampleMap], brick.phase];
b: ImagerSample.SampleBuffer = ImagerSample.ObtainScratchSamples[SF.SizeF[shape.box]];
bitmap: ImagerSample.RasterSampleMap = ImagerSample.NewSampleMap[shape.box];
ImagerSample.Clear[bitmap];
fractionMisses ¬ fractionMisses + 1;
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: NAT 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]];
IF private # NIL THEN
private.fractionCache[mod] ¬
[brick: brick, bitmap: bitmap, phase: shape.phase, sample: sample];
};
};
};
};
SetTile: PROC [deviceColorData: ImagerPrintColor.DeviceColorData, tileBits: 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.TupleProc ~ { 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.Round[rgb.R*255];
pixelsRGB[1][0] ¬ Real.Round[rgb.G*255];
pixelsRGB[2][0] ¬ Real.Round[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] ~ {
transform: ColorTransform ~ GetColorTransform[deviceColorData, color.colorOperator];
p: ColorPoint ¬ ImagerColorPrivate.ColorPointFromColor[color, transform];
SELECT p.dim FROM
1 => { SetGray[deviceColorData, p[0]/255.0] };
4 => {
i: NAT ~ SELECT deviceColorData.toner FROM
cyan => 0,
magenta => 1,
yellow => 2,
black => 3,
ENDCASE => 3;
SetFraction[deviceColorData, p[i]/255.0]
};
ENDCASE => ERROR;
p ¬ ImagerColorPrivate.DestroyColorPoint[p];
};
SampledColorData: TYPE ~ REF SampledColorDataRep;
SampledColorDataRep: TYPE ~ RECORD [
pa: PixelArray,
colorOperator: ImagerColor.ColorOperator,
fullColor: BOOL,
source: ImagerPixel.PixelMap
];
SetSampledColor: PROC [deviceColorData: ImagerPrintColor.DeviceColorData, color: ImagerColor.SampledColor] ~ {
private: REF PrivateRep = NARROW[deviceColorData.private];
pa: 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];
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, source: pixelMap]];
IF pa.immutable AND private.control.allIdentity THEN FunctionCache.Insert[x: cache, argument: scd, value: scd, size: PixelMapWords[pixelMap], clientID: $PrintColor];
FIXTHIS - if not pa.immutable (as for PS), this caching strategy gives pretty mediocre results for ImagerFourColorContext - the ComputeSource happens once per separaion, even though they are all done at (roughly) the same time!
};
IF index >= scd.source.samplesPerPixel OR (NOT fullColor AND toner#black)
THEN {
FIXTHIS - overprint
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: PixelArray ~ color.pa;
colorOperator: ImagerColor.ColorOperator ~ color.colorOperator;
transform: ImagerColorPrivate.ColorTransform ~ GetColorTransform[deviceColorData, colorOperator];
pixelMap: PixelMap ~ ImagerColorPrivate.Translate[colorOperator: colorOperator, transform: transform, pa: pa];
RETURN [pixelMap];
};
colorSpaceCMYK: ImagerColorPrivate.ColorSpace ~ ImagerColorPrivate.DefineProcessSpace[ImagerColor.NewColorOperatorCMYK[255]];
allowY: LIST OF ImagerColorPrivate.ColorSpace ~ LIST[$Y];
allowColorOrY: LIST OF ImagerColorPrivate.ColorSpace ~ LIST[colorSpaceCMYK, $RGB, $Y];
GetColorTransform: PROC [deviceColorData: ImagerPrintColor.DeviceColorData, colorOperator: ColorOperator] RETURNS [ImagerColorPrivate.ColorTransform] ~ {
private: REF PrivateRep ~ NARROW[deviceColorData.private];
tu: PrintColor.TonerUniverse ~ deviceColorData.tonerUniverse;
fullColor: BOOL ~ tu[cyan] AND tu[magenta] AND tu[yellow] AND tu[black];
IF fullColor
THEN {
colorSpace: ImagerColorPrivate.ColorSpace ~ ImagerColorPrivate.ChooseColorSpace[colorOperator, allowColorOrY];
IF private.colorTransform = NIL OR colorSpace # private.colorTransform.domain OR private.control.stamp # private.controlStamp THEN {
rangeMax: ImagerColorPrivate.ColorPoint ~ ImagerColorPrivate.MakeColorPoint[4, 255];
ct: ImagerColorPrivate.ColorTransform ~ NEW[ImagerColorPrivate.ColorTransformRep ¬ [domain: colorSpace, rangeMax: rangeMax, proc: CMYKTransform, data: private.control]];
private.colorTransform ¬ ct;
private.controlStamp ¬ private.control.stamp;
};
}
ELSE {
colorSpace: ImagerColorPrivate.ColorSpace ~ ImagerColorPrivate.ChooseColorSpace[colorOperator, allowY];
IF private.colorTransform = NIL OR colorSpace # private.colorTransform.domain OR private.control.stamp # private.controlStamp THEN {
rangeMax: ImagerColorPrivate.ColorPoint ~ ImagerColorPrivate.MakeColorPoint[1, 255];
ct: ImagerColorPrivate.ColorTransform ~ NEW[ImagerColorPrivate.ColorTransformRep ¬ [domain: colorSpace, rangeMax: rangeMax, proc: KTransform, data: private.control]];
private.colorTransform ¬ ct;
private.controlStamp ¬ private.control.stamp;
};
};
RETURN [private.colorTransform]
};
CMYKTransform: PROC [self: ColorTransform, in: ColorPoint, out: ColorPoint] ~ {
control: ImagerDeviceColor.DeviceColorControl = NARROW[self.data];
SELECT self.domain FROM
$Y => {
ImagerDeviceColor.CMYKFromY[control, in[0], 255.0, out];
};
$RGB => {
ImagerDeviceColor.CMYKFromRGB[control, in[0], in[1], in[2], 255.0, out];
};
ENDCASE => {
ImagerDeviceColor.CMYKFromCMYK[control, in[0]/255.0, in[1]/255.0, in[2]/255.0, in[3]/255.0, 255.0, out];
};
};
KTransform: PROC [self: ColorTransform, in: ColorPoint, out: ColorPoint] ~ {
control: ImagerDeviceColor.DeviceColorControl = NARROW[self.data];
SELECT self.domain FROM
$Y => {
ImagerDeviceColor.YFromY[control, in[0], 255.0, out];
out[0] ¬ 255.0-out[0]; -- convert to fraction of ink
};
ENDCASE => ERROR;
};
SetSampledBlack: PROC [deviceColorData: ImagerPrintColor.DeviceColorData, color: ImagerColor.SampledBlack] ~ {
pa: 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: 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;
};
newStuff AND TryTransformingBitmapIntoTile[deviceColorData, pa] => {
TryTransformingBitmapIntoTile already did the setup.
};
ENDCASE => {
The hard case - use the sampler
sampleMap: 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;
};
};
ImagerPrintColorNewStuff: PROC [new: CARD] RETURNS [old: BOOL] ~ {
old ¬ newStuff;
newStuff ¬ new#0;
};
newStuff: BOOL ¬ TRUE; -- for testing
ImagerPrintColorTileTolerance: PROC [int: INT] RETURNS [old: INT] ~ {
old ¬ Real.Round[int];
tileTolerance ¬ int;
};
tileTolerance: REAL ¬ 2* 262144.0; -- tileTolerance is added to a small number (epsilon) and compared to tileTolerance to decide whether epsilon is close enough to zero. The default value allows a little more that 2% of a pixel of tolerance.
TryTransformingBitmapIntoTile: PROC [deviceColorData: ImagerPrintColor.DeviceColorData, pa: PixelArray] RETURNS [BOOL] ~ {
m: Transformation ~ deviceColorData.pixelToDevice;
IF m.form#0 THEN {
sRect: Rectangle ~ [0, 0, pa.sSize, pa.fSize];
dRect: Rectangle ~ ImagerTransformation.TransformRectangle[m, sRect];
IF MAX[ABS[dRect.x], ABS[dRect.y], ABS[dRect.w], ABS[dRect.h]] <= 16383 AND ABS[dRect.w*dRect.h] <= 1048576.0 THEN {
sSize: NAT ~ Real.Round[dRect.w];
fSize: NAT ~ Real.Round[dRect.h];
IF (dRect.w-sSize)+tileTolerance = tileTolerance AND (dRect.h-fSize)+tileTolerance = tileTolerance THEN {
This bitmap is not quite right for the output raster, but we can make it so.
private: REF PrivateRep ~ NARROW[deviceColorData.private];
context: Imager.Context ¬ IF private.scratchBitmapContext = NIL THEN (private.scratchBitmapContext ¬ ImagerBitmapContext.Create[deviceSpaceSize: SF.maxVec, scanMode: [slow: right, fast: up], surfaceUnitsPerInch: [1,1], pixelUnits: TRUE, fontCacheName: $Bitmap]) ELSE private.scratchBitmapContext;
action: PROC ~ {
pamInverse: Transformation ~ ImagerTransformation.Invert[pa.m];
Imager.TranslateT[context, [-dRect.x, -dRect.y]];
Imager.ConcatT[context, m];
Imager.ConcatT[context, pamInverse];
Imager.MaskPixel[context, pa];
ImagerTransformation.Destroy[pamInverse];
};
sampleMap: SampleMap ¬ ImagerSample.ObtainScratchMap[box: [max: [sSize, fSize]]];
tileMap: SampleMap ~ ImagerSample.Shift[sampleMap, [s: Real.Floor[dRect.x+0.5], f: Real.Floor[dRect.y+0.5]]];
ImagerSample.Clear[sampleMap];
ImagerBitmapContext.SetBitmap[context, sampleMap];
Imager.DoSave[context, action];
ImagerBitmapContext.SetBitmap[context, NIL];
TRUSTED {ImagerSample.ReleaseDescriptor[sampleMap]; sampleMap ¬ NIL};
SetTile[deviceColorData: deviceColorData, tileBits: tileMap, tilePhase: 0, function: deviceColorData.function];
deviceColorData.sampleMapInUse ¬ tileMap;
RETURN [TRUE];
};
};
};
RETURN [FALSE];
};
MaskBoxes: PUBLIC PROC [bitmap: SampleMap, deviceColorData: ImagerPrintColor.DeviceColorData, bounds: SF.Box, boxes: SF.BoxGenerator] ~ {
SELECT deviceColorData.case FROM
constant => {
IF deviceColorData.ink # nop THEN
ImagerSample.FillBoxes[map: bitmap, boxes: boxes, value: IF deviceColorData.ink = set THEN 1 ELSE 0];
};
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;
};
END.
ComputeSource: PROC [deviceColorData: ImagerPrintColor.DeviceColorData, color: ImagerColor.SampledColor] RETURNS [ImagerPixel.PixelMap] ~ {
pa: 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];
};
CHECKED {
transform: ImagerColorPrivate.ColorTransform ~ GetColorTransform[deviceColorData, colorOperator];
pixelMap: PixelMap ~ ImagerColorPrivate.Translate[colorOperator: colorOperator, transform: transform, pa: pa];
RETURN [pixelMap];
};
};