ImagerHighlightContextImpl.mesa
Copyright Ó 1991, 1992, 1993 by Xerox Corporation. All rights reserved.
Michael Plass, October 28, 1993 12:58 pm PDT
Russ Atkinson (RRA) August 29, 1991 12:31 pm PDT
DIRECTORY
IO, Commander,
Basics, FunctionCache, Imager, ImagerBrick, ImagerColor, ImagerColorPrivate, ImagerDevice, ImagerDeviceColor, ImagerDeviceProcs, ImagerError, ImagerHighlightContext, ImagerHighlightContextBackdoor, ImagerHighlightContextBackdoorExtras, ImagerManhattan, ImagerMaskCache, ImagerPixel, ImagerPixelArray, ImagerPrivate, ImagerRaster, ImagerSample, ImagerSwitches, ImagerTransformation, MaskWithColor, RasterBasics, Real, RealInline, Rope, SF;
ImagerHighlightContextImpl: CEDAR PROGRAM
IMPORTS
IO, Commander,
Basics, FunctionCache, Imager, ImagerBrick, ImagerColor, ImagerColorPrivate, ImagerDevice, ImagerDeviceColor, ImagerDeviceProcs, ImagerError, ImagerManhattan, ImagerMaskCache, ImagerPixel, ImagerPixelArray, ImagerRaster, ImagerSample, ImagerSwitches, ImagerTransformation, MaskWithColor, Real, RealInline, SF
EXPORTS ImagerHighlightContext, ImagerHighlightContextBackdoor, ImagerHighlightContextBackdoorExtras
~ BEGIN
bpw: NAT = BITS[WORD];
BitOffset: TYPE = [0..bpw);
Box: TYPE ~ SF.Box;
Context: TYPE ~ Imager.Context;
Device: TYPE ~ ImagerDevice.Device;
classCode: PUBLIC ATOM ¬ $Highlight;
SampledColor: TYPE ~ ImagerColor.SampledColor;
OpConstantColor: TYPE ~ ImagerColor.OpConstantColor;
SpecialColor: TYPE ~ ImagerColor.SpecialColor;
SampledBlack: TYPE ~ ImagerColor.SampledBlack;
Transformation: TYPE ~ ImagerTransformation.Transformation;
PixelArray: TYPE ~ ImagerPixelArray.PixelArray;
ColorOperator: TYPE ~ ImagerColor.ColorOperator;
PixelProc: TYPE ~ ImagerPixelArray.PixelProc;
PixelBuffer: TYPE ~ ImagerPixelArray.PixelBuffer;
PixelMap: TYPE ~ ImagerPixel.PixelMap;
RawBytesPtr: TYPE = POINTER TO Basics.RawBytes;
RawWordsPtr: TYPE = POINTER TO Basics.RawWords;
SampleMap: TYPE = ImagerSample.SampleMap;
SampleBuffer: TYPE = ImagerSample.SampleBuffer;
RasterSampleMap: TYPE = ImagerSample.RasterSampleMap;
ColorLookupTable: TYPE ~ ImagerHighlightContextBackdoor.ColorLookupTable;
Case: TYPE ~ { nil, constant, tile, pixelarray, sampled, sampledBlack, clearSampledBlack };
Data: TYPE ~ REF DataRep;
DataRep: TYPE ~ RECORD [
sampleMap: ImagerSample.SampleMap,
highlight: ImagerColor.ConstantColor ¬ NIL, -- note: we assume base color is white
colorLookupTable: ColorLookupTable ¬ NIL, -- takes precedence over highlight mapping
control: ImagerDeviceColor.DeviceColorControl ¬ NIL,
controlStamp: CARD ¬ 0,
scratchColorPoint: ImagerColorPrivate.ColorPoint,
hY, hE, hS: REAL ¬ 0.0,
hE2hS2Sqr: REAL ¬ 0.0, -- precomputed (data.hE**2 + data.hS**2)**2
fastColorOperator: ImagerColor.ColorOperator,
colorTransform: ImagerColorPrivate.ColorTransform ¬ NIL,
brick: ImagerBrick.Brick ¬ [0, NIL, 0],
interpolate: BOOL ¬ FALSE, -- controls sampling; it would be nice to enable this, but we have some trouble with images that come in bands that way.
case: Case ¬ nil, -- what type of color
constant =>
constant: WORD ¬ 0,
colorWord: WORD ¬ 0, -- the constant, replicated to fill a word.
tile =>
tile: ImagerSample.SampleMap ¬ NIL, -- sampleMap for case=tile
pixelarray =>
pixelarray: ImagerPixelArray.PixelArray ¬ NIL,
sampled, sampledBlack, clearSampledBlack =>
sharedSource: BOOL ¬ FALSE, -- If TRUE, don't recycle source.
paToDevice: ImagerTransformation.Transformation ¬ NIL,
transformation from pa coords to display
source: PixelMap ¬ NIL
select case from
sampledBlack, clearSampledBlack =>
normal sampledBlack interpretation
sampled =>
select samples/pixel from
1 => intensity,
2 => (black, highlight)
];
SampledColorData: TYPE ~ REF SampledColorDataRep;
SampledColorDataRep: TYPE ~ RECORD [
pa: ImagerPixelArray.PixelArray,
colorOperator: ImagerColor.ColorOperator,
highlight: ImagerColor.ConstantColor ¬ NIL,
colorLookupTable: ColorLookupTable ¬ NIL,
source: PixelMap
];
Create: PUBLIC PROC [deviceSpaceSize: SF.Vec, scanMode: Imager.ScanMode, surfaceUnitsPerInch: Imager.VEC, pixelUnits: BOOL ¬ FALSE, fontCacheName: ATOM ¬ NIL, highlight: Imager.ConstantColor ¬ NIL] RETURNS [Context] ~ {
data: Data ~ NEW[DataRep ¬ [
paToDevice: ImagerTransformation.Scale[1]
]];
deviceParm: ImagerDevice.DeviceParm ¬ ImagerDevice.MakeDeviceParm[
class: deviceClass,
sSize: deviceSpaceSize.s,
fSize: deviceSpaceSize.f,
scanMode: scanMode,
surfaceUnitsPerInch: surfaceUnitsPerInch,
surfaceUnitsPerPixel: 1,
fontCache: IF fontCacheName = NIL THEN NIL ELSE ImagerMaskCache.GetNamedCache[fontCacheName]
];
context: Context ~ ImagerRaster.Create[class: contextClass, deviceClass: deviceClass, deviceParm: deviceParm, data: data, pixelUnits: pixelUnits];
device: ImagerDevice.Device = ImagerRaster.GetDevice[context];
data.control ¬ ImagerDeviceColor.GetDeviceColorControl[device];
data.scratchColorPoint ¬ ImagerColorPrivate.MakeColorPoint[3];
SetDataHighlight[data, highlight];
SetDataBrick[data, ImagerBrick.BrickFromDotScreen[pixelsPerDot: 5.656, degrees: 45, shape: 0.48]];
ImagerRaster.SetDeviceClipBox[context, [[0,0], [0,0]]];
RETURN [context];
};
SetSampleMap: PUBLIC PROC [context: Context, sampleMap: Imager.SampleMap] ~ {
device: Device = ImagerRaster.GetDevice[context];
IF DeviceSetSampleMap[device, sampleMap].clipperNeedsFixing THEN {
ImagerRaster.SetDeviceClipBox[context, ImagerSample.GetBox[sampleMap]];
};
};
DeviceSetSampleMap: PUBLIC<<BackdoorExtras>> PROC [device: Device, sampleMap: SampleMap] RETURNS [clipperNeedsFixing: BOOL] ~ {
data: Data ~ NARROW[device.data];
clipper: ImagerDevice.DeviceClipper = device.worksState.clipper;
box: SF.Box = ImagerSample.GetBox[sampleMap];
IF sampleMap.GetBitsPerSample # 2 THEN ImagerError.Error[[$bounds, "sampleMap must have 2 bits per sample for a Highlight context"]];
clipperNeedsFixing ¬ NOT SF.Inside[box, device.state.bounds];
device.state.bounds ¬ box;
clipper.clipMask ¬ ImagerManhattan.DestructiveClip[clipper.clipMask, box];
clipper.clipBox ¬ ImagerManhattan.BoundingBox[clipper.clipMask];
data.sampleMap ¬ sampleMap;
};
GetSampleMap: PUBLIC PROC [context: Context] RETURNS [Imager.SampleMap] ~ {
data: Data ~ NARROW[ImagerRaster.GetDevice[context].data];
RETURN [data.sampleMap];
};
SetColorLookupTable: PUBLIC<<Backdoor>> PROC [context: Context, colorLookupTable: ColorLookupTable] ~ {
DeviceSetColorLookupTable[ImagerRaster.GetDevice[context], colorLookupTable];
};
DeviceSetColorLookupTable: PUBLIC<<BackdoorExtras>> PROC [device: ImagerDevice.Device, colorLookupTable: ColorLookupTable] ~ {
WITH device.data SELECT FROM
data: Data => {
data.colorLookupTable ¬ colorLookupTable;
data.colorTransform ¬ NIL
};
ENDCASE;
};
DeviceSetHighlight: PUBLIC<<BackdoorExtras>> PROC [device: ImagerDevice.Device, highlight: ImagerColor.ConstantColor] ~ {
WITH device.data SELECT FROM
data: Data => { SetDataHighlight[data, highlight] };
ENDCASE;
};
SetDataHighlight: PROC [data: Data, highlight: Imager.ConstantColor] ~ {
data.highlight ¬ highlight;
[[data.hY, data.hE, data.hS]] ¬ ImagerColor.YESFromColor[ImagerColor.NarrowToOpConstantColor[highlight]];
data.hE2hS2Sqr ¬ (data.hE**2 + data.hS**2)**2;
data.fastColorOperator ¬ NewColorOperatorHighlight[highlight];
};
SetDataBrick: PROC [data: Data, brick: ImagerBrick.Brick] ~ {
IF brick.maxSample # 255 THEN ERROR; -- temporary(?) restriction.
data.brick ¬ brick;
data.tile ¬ NIL;
};
NewColorOperatorHighlight: PROC [highlight: Imager.ConstantColor] RETURNS [co: ImagerColor.ColorOperator] ~ {
mapper: PROC [s: ImagerSample.Sample] RETURNS [ImagerColor.ConstantColor] = {
RETURN [SELECT s FROM 0 => Imager.white, 1 => highlight, ENDCASE => Imager.black]
};
co ¬ ImagerColor.NewColorOperatorMap[3, mapper];
};
HiliteGetBufferColorOperator: PROC [device: Device] RETURNS [ColorOperator] ~ {
WITH device.data SELECT FROM
data: Data => { RETURN [data.fastColorOperator] };
ENDCASE => RETURN [NIL];
};
me: REF TEXT ~ "HIGHLITE";
a globally unique REF for use as a clientID in the global cache.
PixelMapWords: PROC [pm: PixelMap] RETURNS [words: INT ¬ 0] ~ {
FOR i: NAT IN [0..pm.samplesPerPixel) DO
words ¬ words + ImagerSample.WordsForMap[size: ImagerSample.GetSize[pm[0]], bitsPerSample: ImagerSample.GetBitsPerSample[pm[0]]];
ENDLOOP;
};
ignoreHighlightSetScreen: CHAR['h..'h] ~ ImagerSwitches.Define['h, $ignorehighlightsetscreen, "Ignore setscreen on highlight color device.", NIL];
HiliteSetHalftoneProperties: PROC [device: ImagerDevice.Device, halftoneProperties: ImagerBrick.HalftoneProperties] ~ {
data: Data ~ NARROW[device.data];
brick: ImagerBrick.Brick ¬ data.brick;
IF ImagerSwitches.BoolValue[ignoreHighlightSetScreen] THEN RETURN;
IF halftoneProperties # NIL THEN brick ¬ halftoneProperties.first.brick;
FOR tail: ImagerBrick.HalftoneProperties ¬ halftoneProperties, tail.rest UNTIL tail = NIL DO
IF tail.first.toner = black THEN { brick ¬ tail.first.brick; EXIT };
ENDLOOP;
SetDataBrick[data, brick];
};
HiliteSetColor: PROC [device: Device, color: Imager.Color, viewToDevice: Imager.Transformation] ~ {
data: Data ~ NARROW [device.data];
sampled: BOOL ¬ FALSE;
allowed: ImagerDevice.AllowedMasks ¬ [
unorderedBoxes: TRUE,
multipleCoverage: TRUE,
regionFill: FALSE,
bitmap: FALSE,
rawBitmaps: FALSE,
runGroupChar: FALSE,
rasterChar: FALSE];
data.case ¬ nil;
IF data.source # NIL AND NOT data.sharedSource THEN
ImagerSample.ReleaseScratchMap[data.source[0]];
data.source ¬ NIL;
WITH color SELECT FROM
color: ImagerColor.OpConstantColor => {
transform: ImagerColorPrivate.ColorTransform ~ GetColorTransform[data, color.colorOperator];
point: ImagerColorPrivate.ColorPoint ¬ ImagerColorPrivate.ColorPointFromColor[color, transform];
black: REAL ~ point[0];
hilite: REAL ~ IF point.dim < 2 THEN 0.0 ELSE point[1];
threshold: REAL ~ 0.5;
blackBit: BOOL ~ black > maxSample-threshold;
blackSolid: BOOL ~ blackBit OR (black < threshold);
hiliteBit: BOOL ~ hilite > maxSample-threshold;
hiliteSolid: BOOL ~ hiliteBit OR (hilite < threshold);
point ¬ ImagerColorPrivate.DestroyColorPoint[point];
IF blackSolid AND hiliteSolid
THEN TRUSTED {
data.case ¬ constant;
data.constant ← ORD[blackBit] * 2 + ORD[hiliteBit];
data.colorWord ¬ MaskWithColor.MakeColorWord[data.constant, lgTable[ImagerSample.GetBitsPerSample[data.sampleMap]]];
}
ELSE {
box: SF.Box ~ ImagerSample.GetBox[data.brick.sampleMap];
b: ImagerPixel.PixelBuffer ~ ImagerPixel.ObtainScratchPixels[samplesPerPixel: 2, length: box.max.f-box.min.f];
Gen: PROC [action: PROC[pixels: ImagerPixel.PixelBuffer, min: SF.Vec]] ~ {
FOR s: INTEGER IN [box.min.s..box.max.s) DO
action[b, [s, box.min.f]];
ENDLOOP;
};
ImagerSample.FillSamples[b[0], Real.Round[black]];
ImagerSample.FillSamples[b[1], Real.Round[hilite]];
data.case ¬ tile;
IF data.tile = NIL THEN {
data.tile ¬ ImagerSample.NewSampleMap[box: ImagerSample.GetBox[data.brick.sampleMap], bitsPerSample: 2];
};
HiliteHalftone[dst: data.tile, src: Gen, bounds: box, brick: data.brick];
ImagerPixel.ReleaseScratchPixels[b];
};
};
color: SampledColor => {
cache: FunctionCache.Cache ~ FunctionCache.GlobalCache[];
pa: PixelArray ~ color.pa;
um: Transformation ~ color.um;
colorOperator: ColorOperator ~ color.colorOperator;
data.paToDevice ¬ ImagerTransformation.Cat[pa.m, color.um, viewToDevice];
IF colorOperator = data.fastColorOperator AND data.paToDevice.form = 3 AND data.paToDevice.integerTrans THEN {
This is a fast case - the pixels can be block-transferred out.
data.case ¬ pixelarray;
data.pixelarray ¬ pa;
allowed.multipleCoverage ¬ FALSE;
device.state.allow ¬ allowed;
RETURN;
};
data.case ¬ $sampled;
sampled ¬ TRUE;
IF pa.immutable AND data.control.allIdentity
THEN {
Compare: FunctionCache.CompareProc ~ {
WITH argument SELECT FROM
scd: SampledColorData =>
RETURN [scd.pa = pa AND scd.colorOperator = color.colorOperator AND scd.highlight = data.highlight AND scd.colorLookupTable = data.colorLookupTable];
ENDCASE => RETURN [FALSE]
};
scd: SampledColorData ¬ NARROW[FunctionCache.Lookup[cache, Compare, me].value];
IF scd = NIL THEN {
pixelMap: PixelMap ~ ComputeSource[data, color, color.colorOperator];
words: INT ~ PixelMapWords[pixelMap];
scd ¬ NEW [SampledColorDataRep ¬ [pa: pa, colorOperator: color.colorOperator, highlight: data.highlight, source: pixelMap]];
FunctionCache.Insert[x: cache, argument: scd, value: scd, size: words, clientID: me];
};
data.source ¬ scd.source;
data.sharedSource ¬ TRUE;
}
ELSE {
data.source ¬ ComputeSource[data, color, color.colorOperator];
data.sharedSource ¬ FALSE;
};
};
color: SampledBlack => {
pa: PixelArray ~ color.pa;
data.case ¬ IF color.clear THEN clearSampledBlack ELSE sampledBlack;
sampled ¬ TRUE;
data.paToDevice ¬ ImagerTransformation.Cat[pa.m, color.um, viewToDevice];
data.source ¬ ImagerPixel.MakePixelMap[ImagerSample.ObtainScratchMap[box: [max: [pa.sSize, pa.fSize]], bitsPerSample: 1]];
ImagerPixelArray.Transfer[pa: pa, dst: data.source[0]];
data.sharedSource ¬ FALSE;
};
color: ImagerColor.SpecialColor => {
SELECT color.type FROM
ENDCASE => {
IF color.substitute = NIL THEN
ERROR ImagerError.Error[[code: $unknownSpecialColor, explanation: "Unknown special color has no substitute value"]];
HiliteSetColor[device, color.substitute, viewToDevice];
};
};
ENDCASE => ERROR; -- unknown color variant
IF data.case = constant THEN {
allowed.rawBitmaps ¬ allowed.bitmap ¬ TRUE;
};
IF sampled THEN allowed.multipleCoverage ¬ allowed.unorderedBoxes ¬ FALSE;
device.state.allow ¬ allowed;
};
colorSpaceCMYK: ImagerColorPrivate.ColorSpace ~ ImagerColorPrivate.DefineProcessSpace[ImagerColor.NewColorOperatorCMYK[255]];
allowedColorSpaces: LIST OF ImagerColorPrivate.ColorSpace ~ LIST[$Highlight, $YES, $RGB, $Y];
allowedColorSpaces2: LIST OF ImagerColorPrivate.ColorSpace ~ LIST[$Highlight, colorSpaceCMYK, $RGB, $Y];
GetColorTransform: PROC [data: Data, colorOperator: ColorOperator] RETURNS [ImagerColorPrivate.ColorTransform] ~ {
allow: LIST OF ImagerColorPrivate.ColorSpace ~ IF data.colorLookupTable # NIL THEN allowedColorSpaces2 ELSE allowedColorSpaces;
colorSpace: ImagerColorPrivate.ColorSpace ~ ImagerColorPrivate.ChooseColorSpace[colorOperator, allow];
IF data.colorTransform = NIL OR colorSpace # data.colorTransform.domain OR data.control.stamp # data.controlStamp THEN {
domain: ImagerColorPrivate.ColorSpace ~ colorSpace;
dim: NAT ~ IF domain = $Y THEN 1 ELSE 2;
rangeMax: ImagerColorPrivate.ColorPoint ~ ImagerColorPrivate.MakeColorPoint[dim, data.brick.maxSample];
ct: ImagerColorPrivate.ColorTransform ~ NEW[ImagerColorPrivate.ColorTransformRep ¬ [
domain: domain,
rangeMax: rangeMax,
proc: SELECT domain FROM
$Highlight => HighlightTransform,
$RGB => IF data.colorLookupTable # NIL THEN TableTransform ELSE PictorialTransform,
$YES => PictorialTransform,
$Y => IntensityTransform,
colorSpaceCMYK => TableTransform,
ENDCASE => ERROR,
data: data]];
data.colorTransform ¬ ct;
data.controlStamp ¬ data.control.stamp;
};
RETURN [data.colorTransform]
};
debug: IO.STREAM ¬ RegisterDebugHighlightTransform[];
DebugHighlightTransform: Commander.CommandProc ~ { debug ¬ cmd.out };
RegisterDebugHighlightTransform: PROC RETURNS [IO.STREAM] ~ {
Commander.Register["DebugHighlightTransform", DebugHighlightTransform];
RETURN [IO.noWhereStream]
};
HighlightTransform: PROC [self: ImagerColorPrivate.ColorTransform, in: ImagerColorPrivate.ColorPoint, out: ImagerColorPrivate.ColorPoint] ~ {
data: Data ~ NARROW[self.data];
control: ImagerDeviceColor.DeviceColorControl ~ data.control;
gray: REAL = ImagerDeviceColor.LookupReal[control.table[$grayTransfer], (in[0]+in[1])];
b: REAL ¬ (1.0 - gray)*maxSample;
h: REAL ¬ (in[1])*maxSample; -- Should this go through a lookup? - probably, but which?
outOfGamut: BOOL ¬ FALSE;
IF b < 0.0 THEN {outOfGamut ¬ TRUE; b ¬ 0.0};
IF b > maxSample THEN {outOfGamut ¬ TRUE; b ¬ maxSample};
IF h < 0.0 THEN {outOfGamut ¬ TRUE; h ¬ 0.0};
IF h > maxSample-b THEN {outOfGamut ¬ TRUE; h ¬ maxSample-b};
out[0] ¬ b;
out[1] ¬ h;
out.outOfGamut ¬ outOfGamut;
debug.PutFL["HighlightTransform: [%g, %g] => [%g, %g] outOfGamut: %g\n", LIST[
[real[in[0]]],
[real[in[1]]],
[real[out[0]]],
[real[out[1]]],
[boolean[out.outOfGamut]]
]];
};
maxSample: INT ~ 255;
IntensityTransform: PROC [self: ImagerColorPrivate.ColorTransform, in: ImagerColorPrivate.ColorPoint, out: ImagerColorPrivate.ColorPoint] ~ {
data: Data ~ NARROW[self.data];
control: ImagerDeviceColor.DeviceColorControl ~ data.control;
ImagerDeviceColor.KFromY[control, in[0], maxSample, out];
debug.PutFL["IntensityTransform: [%g] => [%g] outOfGamut: %g\n", LIST[
[real[in[0]]],
[real[out[0]]],
[boolean[out.outOfGamut]]
]];
};
YESFromRGB: PROC [r, g, b: REAL] RETURNS [y, e, s: REAL] ~ INLINE {
y ¬ 0.253*r + 0.684*g + 0.063*b;
e ¬ (r - g) / 2;
s ¬ ((r + g) / 4) - b/2;
};
Store: PROC [out: ImagerColorPrivate.ColorPoint, i: NAT, x: REAL, limit: INTEGER] RETURNS [INTEGER] ~ {
This stores a component of a ColorPoint, scaling it, and clipping it if needed.
Returns the scaled & clipped value.
scaled: INT ~ RealInline.MCRound[x*maxSample];
clipped: INT ~ IF scaled < 0 THEN 0 ELSE IF scaled > limit THEN limit ELSE scaled;
out[i] ¬ clipped;
IF clipped # scaled THEN out.outOfGamut ¬ TRUE;
RETURN [clipped]
};
PictorialTransform: PROC [self: ImagerColorPrivate.ColorTransform, in: ImagerColorPrivate.ColorPoint, out: ImagerColorPrivate.ColorPoint] ~ {
The following procedure is based off of Steven Harrington's Color Mappings Report of January 1990. It applies the pictorial mapping algorithm for an arbitrary highlight color. The procedure uses a YES color model operator to determine the amount of highlight (h) and white (w) for each pixel of each of the two resultant bit planes.
The procedure assumes that the provided colors are in YES or RGB format.
for each sample color (Ys, Es, Ss) and highlight toner color (Yh, Eh, Sh);
Y = 0.253r + 0.684g + 0.063b
E = (r - g) / 2
S = ((r + g) / 4) - b/2
h = MAX( 0, (EhEs + ShSs)**3 / ((Es**2 + Ss**2)(Eh**2 + Sh**2)**2) )
w = MIN( 1-h, Ys - h*Yh )
k = 1 - h - w
data: Data ~ NARROW[self.data];
y, e, s: REAL;
IF self.domain = $RGB
THEN {
tmp: ImagerColorPrivate.ColorPoint = data.scratchColorPoint;
control: ImagerDeviceColor.DeviceColorControl ~ data.control;
ImagerDeviceColor.RGBFromRGB[control, in[0], in[1], in[2], 1.0, tmp];
[y, e, s] ¬ YESFromRGB[r: tmp[0], g: tmp[1], b: tmp[2]];
out.outOfGamut ¬ tmp.outOfGamut;
}
ELSE {
No transfer function(?)
y ¬ in[0]; e ¬ in[1]; s ¬ in[2];
out.outOfGamut ¬ FALSE;
};
{
denom: REAL ~ ((e**2 + s**2) * data.hE2hS2Sqr);
h: REAL ~
IF denom = 0.0
THEN 0.0
ELSE MAX[0, (((data.hE * e) + (data.hS * s))**3) / denom];
w: REAL ~ MIN[1.0-h, y - (h * data.hY)];
[] ¬ Store[out, 1, h, maxSample - Store[out, 0, 1.0-h-w, maxSample]];
};
IF self.domain = $RGB THEN debug.PutFL["PictorialTransform: [%g, %g, %g] => [%g, %g]\n", LIST[
[real[in[0]]],
[real[in[1]]],
[real[in[2]]],
[real[out[0]]],
[real[out[1]]]
]];
};
TableTransform: PROC [self: ImagerColorPrivate.ColorTransform, in: ImagerColorPrivate.ColorPoint, out: ImagerColorPrivate.ColorPoint] ~ TRUSTED {
This applies a brute-force table lookup.
The procedure assumes that the provided colors are in CMYK or RGB format.
data: Data ~ NARROW[self.data];
control: ImagerDeviceColor.DeviceColorControl ~ data.control;
p: POINTER TO ImagerHighlightContextBackdoor.ColorLookupArray ~ data.colorLookupTable.tablePointer;
tmp: ImagerColorPrivate.ColorPoint = data.scratchColorPoint;
max: ImagerHighlightContextBackdoor.RedIndex ~ ImagerHighlightContextBackdoor.RedIndex.LAST;
IF self.domain = $RGB
THEN ImagerDeviceColor.RGBFromRGB[control, in[0], in[1], in[2], max, tmp]
ELSE ImagerDeviceColor.RGBFromCMYK[control, in[0]/255.0, in[1]/255.0, in[2]/255.0, in[3]/255.0, max, tmp];
{
red: [0..max] ~ MIN[MAX[RealInline.MCRound[tmp[0]], 0], max];
grn: [0..max] ~ MIN[MAX[RealInline.MCRound[tmp[1]], 0], max];
blu: [0..max] ~ MIN[MAX[RealInline.MCRound[tmp[2]], 0], max];
black, highlight: BYTE;
[highlight: highlight, black: black] ¬ p[red][grn][blu];
{
b: REAL ¬ black;
h: REAL ¬ highlight;
outOfGamut: BOOL ¬ tmp.outOfGamut;
IF b < 0.0 THEN {outOfGamut ¬ TRUE; b ¬ 0.0};
IF b > maxSample THEN {outOfGamut ¬ TRUE; b ¬ maxSample};
IF h < 0.0 THEN {outOfGamut ¬ TRUE; h ¬ 0.0};
IF h > maxSample-b THEN {outOfGamut ¬ TRUE; h ¬ maxSample-b};
out[0] ¬ b;
out[1] ¬ h;
out.outOfGamut ¬ outOfGamut;
};
};
IF self.domain = $RGB
THEN {
debug.PutFL["TableRGBTransform: [%g, %g, %g] => [%g, %g] => [%g, %g] outOfGamut: %g\n", LIST[
[real[in[0]]],
[real[in[1]]],
[real[in[2]]],
[real[black]],
[real[highlight]],
[real[out[0]]],
[real[out[1]]],
[boolean[out.outOfGamut]]
]];
}
ELSE {
debug.PutFL["TableCMYKTransform: [%g, %g, %g, %g] => [%g, %g]\n", LIST[
[real[in[0]]],
[real[in[1]]],
[real[in[2]]],
[real[in[3]]],
[real[out[0]]],
[real[out[1]]]
]];
};
};
ComputeSource: PROC [data: Data, sampledColor: ImagerColor.SampledColor, colorOperator: ImagerColor.ColorOperator] RETURNS [PixelMap] ~ {
pa: PixelArray ~ sampledColor.pa;
transform: ImagerColorPrivate.ColorTransform ~ GetColorTransform[data, colorOperator];
pixelMap: PixelMap ~ ImagerColorPrivate.Translate[colorOperator: colorOperator, transform: transform, pa: pa];
RETURN [pixelMap];
};
HiliteHalftone: PROC [dst: SampleMap, src: PROC[PROC[pixels: ImagerPixel.PixelBuffer, min: SF.Vec]], bounds: Box, brick: ImagerBrick.Brick] ~ TRUSTED {
thresholdBuffer: SampleBuffer ~ ImagerSample.ObtainScratchSamples[SF.SizeF[bounds]];
zeroBuffer: SampleBuffer ¬ NIL;
GetHBuffer: SAFE PROC [pixels: ImagerPixel.PixelBuffer] RETURNS [SampleBuffer] ~ TRUSTED {
Provides a buffer full of zeros in case there is no highlight plane in pixels
IF pixels.samplesPerPixel < 2
THEN {
IF zeroBuffer = NIL THEN {
zeroBuffer ¬ ImagerSample.ObtainScratchSamples[SF.SizeF[bounds]];
ImagerSample.ClearSamples[zeroBuffer];
};
RETURN[zeroBuffer];
}
ELSE { RETURN [pixels[1]] };
};
BitBuffer: TYPE ~ PACKED ARRAY [0..8*16) OF [0..2**16);
bitsPerBuffer: NAT ~ BITS[BitBuffer];
pixelsPerBuffer: NAT ~ bitsPerBuffer/2;
bitBuffer: BitBuffer;
bitBufferMap: SampleMap ~ ImagerSample.ObtainUnsafeDescriptor[size: [s: 1, f: bitsPerBuffer/2], bitsPerSample: 2, bitsPerLine: bitsPerBuffer, base: [word: LOOPHOLE[@bitBuffer], bit: 0], ref: NIL, words: WORDS[BitBuffer]];
Action: SAFE PROC [pixels: ImagerPixel.PixelBuffer, min: SF.Vec] ~ TRUSTED {
blackBuffer: SampleBuffer ~ pixels[0];
hiliteBuffer: SampleBuffer ~ GetHBuffer[pixels];
count: NAT ~ pixels.length;
index: SF.Vec ¬ min;
residual: NAT ¬ thresholdBuffer.length ¬ count;
SrcChunk: TYPE ~ ARRAY [0..8) OF WORD;
t: POINTER TO SrcChunk ¬ LOOPHOLE[ImagerSample.PointerToSamples[buffer: thresholdBuffer, start: 0, count: count]];
b: POINTER TO SrcChunk ¬ LOOPHOLE[ImagerSample.PointerToSamples[buffer: blackBuffer, start: 0, count: count]];
h: POINTER TO SrcChunk ¬ LOOPHOLE[ImagerSample.PointerToSamples[buffer: hiliteBuffer, start: 0, count: count]];
unit: CARDINAL ¬ maxSample + 1; -- try to get this in a register
BH: UNSAFE PROC [j: [0..8)] RETURNS [WORD] ~ UNCHECKED INLINE {
thresh: CARDINAL ~ t[j];
-- Next two lines changed to fix IPD AR 3093
-- Changed again (May 27, 1993) - too light at the white end.
-- black: CARDINAL ~ Basics.BITRSHIFT[(thresh+CARDINAL[1])-b[j], bpw-1];
black: CARDINAL ~ Basics.BITRSHIFT[(thresh)-b[j], bpw-1];
hilite: CARDINAL ~ Basics.BITRSHIFT[(unit-thresh)-h[j], bpw-1];
RETURN [black + Basics.BITOR[black, hilite]] -- just a little tricky
};
ImagerSample.GetTileSamples[tile: brick.sampleMap, initIndex: min, buffer: thresholdBuffer, phase: brick.phase];
WHILE residual # 0 DO
chunkSize: NAT ~ MIN[pixelsPerBuffer, residual];
bitBufferIndex: NAT ¬ 0;
chunkResidual: CARDINAL ¬ chunkSize;
WHILE chunkResidual >= 8 DO
bitBuffer[bitBufferIndex] ¬ (((((((BH[0]*4)+BH[1])*4+BH[2])*4+BH[3])*4+BH[4])*4+BH[5])*4+BH[6])*4+BH[7];
bitBufferIndex ¬ bitBufferIndex + 1;
chunkResidual ¬ chunkResidual - 8;
t ¬ t + SIZE[SrcChunk];
b ¬ b + SIZE[SrcChunk];
h ¬ h + SIZE[SrcChunk];
ENDLOOP;
IF chunkResidual # 0 THEN {
w: CARDINAL ¬ 0;
checkedResidual: [0..8) ~ chunkResidual;
IF chunkSize # residual THEN ERROR; -- must be last time through
FOR i: [0..8) IN [0..checkedResidual) DO
w ¬ w*4 + BH[i];
ENDLOOP;
bitBuffer[bitBufferIndex] ¬ Basics.BITLSHIFT[w, 16-(checkedResidual*2)];
};
ImagerSample.BasicTransfer[dst: dst, src: bitBufferMap, dstMin: index, size: [s: 1, f: chunkSize]];
index.f ¬ index.f + chunkSize;
residual ¬ residual - chunkSize;
ENDLOOP;
};
src[Action];
ImagerSample.ReleaseScratchSamples[thresholdBuffer];
IF zeroBuffer # NIL THEN ImagerSample.ReleaseScratchSamples[zeroBuffer];
ImagerSample.ReleaseDescriptor[bitBufferMap];
};
HiliteMaskBoxes: PROC [device: Device, bounds: Box, boxes: SF.BoxGenerator] ~ {
data: Data ~ NARROW[device.data];
SELECT data.case FROM
nil => ERROR; -- color not initialized
constant => {
ImagerSample.FillBoxes[map: data.sampleMap, boxes: boxes, value: data.constant];
};
tile => {
ImagerSample.TileBoxes[map: data.sampleMap, boxes: boxes, tile: data.tile, phase: data.brick.phase];
};
sampled => {
Src: PROC [action: PROC[pixels: ImagerPixel.PixelBuffer, min: SF.Vec]] ~ {
ImagerPixel.Resample[self: data.source, m: data.paToDevice, interpolate: data.interpolate, boxes: boxes, bounds: bounds, action: action];
};
HiliteHalftone[dst: data.sampleMap, src: Src, bounds: bounds, brick: data.brick];
};
pixelarray => {
paBox: Box ~ ImagerTransformation.EasyTransformBox[m: data.paToDevice, x: 0, y: 0, w: data.pixelarray.sSize, h: data.pixelarray.fSize, clip: SF.maxBox];
BoxAction: PROC [box: Box] ~ {
ImagerPixelArray.Transfer[pa: data.pixelarray, i: 0, s: box.min.s-paBox.min.s, f: box.min.f-paBox.min.f, dst: data.sampleMap, dstMin: box.min, size: SF.Size[box]];
};
IF SF.Inside[bounds, paBox]
THEN boxes[BoxAction]
ELSE {
bpp: NAT ~ lgTable[ImagerPixelArray.MaxSampleValue[data.pixelarray, 0]+1];
tile: SampleMap ~ ImagerSample.ObtainScratchMap[box: paBox, bitsPerSample: bpp];
ImagerPixelArray.Transfer[pa: data.pixelarray, dst: tile, dstMin: paBox.min, size: SF.Size[paBox]];
ImagerSample.TileBoxes[map: data.sampleMap, boxes: boxes, tile: tile, phase: 0];
ImagerSample.ReleaseScratchMap[tile];
};
};
sampledBlack, clearSampledBlack => {
BlackAction: PROC [pixels: ImagerPixel.PixelBuffer, min: SF.Vec] ~ TRUSTED {
count: NAT ~ pixels.length;
buffer: SampleBuffer ~ pixels[0];
bufferPointer: LONG POINTER TO Basics.RawWords ~ ImagerSample.PointerToSamples[buffer: pixels[0], start: 0, count: count];
SELECT data.case FROM
sampledBlack => {
white: WORD ~ 0;
black: WORD ~ 2;
FOR i: NAT IN [0..count) DO
Need to translate 0 to white, 1 to black.
bufferPointer[i] ¬ IF bufferPointer[i] = 0 THEN white ELSE black;
ENDLOOP;
ImagerSample.PutSamples[map: data.sampleMap, initIndex: min, buffer: buffer, start: 0, count: count];
};
clearSampledBlack => {
black: WORD ~ 2;
FOR i: NAT IN [0..count) DO
Need to translate 0 to ALL[0], 1 to ALL[1].
bufferPointer[i] ¬ CARDINAL[0] - bufferPointer[i];
ENDLOOP;
ImagerSample.PutSamples[map: data.sampleMap, initIndex: min, buffer: buffer, start: 0, count: count, function: [and, complement]];
FOR i: NAT IN [0..count) DO
Need to translate 0 to 0, 1 to black.
bufferPointer[i] ¬ Basics.BITAND[bufferPointer[i], black];
ENDLOOP;
ImagerSample.PutSamples[map: data.sampleMap, initIndex: min, buffer: buffer, start: 0, count: count, function: [or, null]];
};
ENDCASE => ERROR;
};
ImagerPixel.Resample[self: data.source, m: data.paToDevice, interpolate: FALSE, boxes: boxes, bounds: bounds, action: BlackAction];
};
ENDCASE => ERROR;
};
HiliteMaskBitmap: PROC [device: Device, bitmap: SampleMap, delta: SF.Vec, bounds: Box, boxes: SF.BoxGenerator] ~ TRUSTED {
data: Data ~ NARROW[device.data];
WITH bitmap SELECT FROM
srcRast: RasterSampleMap => {
srcBitsPerLine: NAT = srcRast.GetBitsPerLine;
IF srcRast.GetBitsPerSample = 1 AND srcBitsPerLine MOD BITS[WORD] = 0 THEN {
WITH data.sampleMap SELECT FROM
dstRast: RasterSampleMap => {
dstBitsPerLine: NAT = dstRast.GetBitsPerLine;
dstBitsPerSample: [2..2] = dstRast.GetBitsPerSample;
logBitsPerSample: NAT = 1;
IF dstBitsPerLine MOD BITS[WORD] = 0 THEN {
This is potentially a fast case using MaskWithColor.MakeColorWord
dstBox: Box ~ dstRast.GetBox;
srcBox: Box ~ SF.Displace[srcRast.GetBox, delta];
dstWpl: CARDINAL ~ dstBitsPerLine / BITS[WORD];
maskWpl: CARDINAL ~ srcBitsPerLine / BITS[WORD];
FastTransfer: SF.BoxAction ~ TRUSTED {
sSize: INTEGER = box.max.s-box.min.s;
fSize: INTEGER = box.max.f-box.min.f;
IF sSize > 0 AND fSize > 0 THEN {
dstBitIndex: CARD ~ dstRast.GetBase.bit +
CARD[box.min.s-dstBox.min.s]*dstBitsPerLine +
Basics.BITLSHIFT[CARD[box.min.f-dstBox.min.f], logBitsPerSample];
srcBitIndex: CARD ~ srcRast.GetBase.bit +
CARD[box.min.s-srcBox.min.s]*srcBitsPerLine +
CARD[box.min.f-srcBox.min.f]*1;
StoreWithColorMask2[
maskBase: LOOPHOLE[srcRast.GetBase.word],
maskOffset: srcBitIndex,
maskWpl: maskWpl,
dstBase: LOOPHOLE[dstRast.GetBase.word],
dstOffset: Basics.BITRSHIFT[dstBitIndex, logBitsPerSample],
dstWpl: dstWpl,
height: sSize,
width: fSize,
colorWord: data.colorWord]
};
};
IF dstRast.GetBitsPerSample # 2 THEN ERROR; -- should not have happened.
boxes[FastTransfer];
RETURN;
};
};
ENDCASE;
};
};
ENDCASE;
ImagerDeviceProcs.DMaskBitmap[device, bitmap, delta, bounds, boxes];
};
HiliteMaskRawBitmaps: <<UNSAFE>> PROC
[device: Device, list: LIST OF ImagerSample.RawDescriptor] ~ TRUSTED {
data: Data ~ NARROW[device.data];
rast: RasterSampleMap = NARROW[data.sampleMap];
dstPtr: LONG POINTER TO WORD = LOOPHOLE[rast.GetBase.word];
dstOff: NAT = rast.GetBase.bit;
dstBitsPerLine: NAT = rast.GetBitsPerLine;
dstWpl: CARDINAL = dstBitsPerLine / BITS[WORD];
dstBitsPerSample: [2..2] = rast.GetBitsPerSample;
logBitsPerSample: NAT = 1;
colorWord: WORD = data.colorWord;
WHILE list # NIL DO
r: POINTER TO ImagerSample.RawDescriptor = @(list.first);
sSize: INTEGER = list.first.box.max.s-list.first.box.min.s;
fSize: INTEGER = list.first.box.max.f-list.first.box.min.f;
IF sSize > 0 AND fSize > 0 THEN {
dstBitIndex: CARD ~ dstOff
+ CARD[r.box.min.s-rast.GetBox.min.s]*dstBitsPerLine
+ Basics.BITLSHIFT[CARD[r.box.min.f-rast.GetBox.min.f], logBitsPerSample];
StoreWithColorMask2[
maskBase: LOOPHOLE[r.basePointer],
maskOffset: 0,
maskWpl: r.bitsPerLine / BITS[WORD],
dstBase: LOOPHOLE[dstPtr],
dstOffset: Basics.BITRSHIFT[dstBitIndex, logBitsPerSample],
dstWpl: dstWpl,
height: sSize,
width: fSize,
colorWord: colorWord]
};
list ¬ list.rest;
ENDLOOP;
};
ExpandArray2: TYPE = PACKED ARRAY BYTE OF CARD16;
expandArray2: REF ExpandArray2 ¬ MakeExpandArray2[];
MakeExpandArray2: PROC RETURNS [REF ExpandArray2] = {
new: REF ExpandArray2 ¬ NEW[ExpandArray2 ¬ ALL[0]];
FOR b: BYTE IN BYTE DO
elem: WORD ¬ 3;
word: WORD ¬ 0;
x: BYTE ¬ b;
WHILE x # 0 DO
IF x MOD 2 = 1 THEN word ¬ word + elem;
elem ¬ elem * 4;
x ¬ x / 2;
ENDLOOP;
new[b] ¬ word;
ENDLOOP;
RETURN [new];
};
StoreWithColorMask2: UNSAFE PROC [
maskBase: RawWordsPtr, maskOffset: CARDINAL, maskWpl: CARDINAL,
dstBase: RawWordsPtr, dstOffset: CARDINAL, dstWpl: CARDINAL,
height: CARDINAL, width: CARDINAL, colorWord: WORD
] = UNCHECKED {
This implements the special case with logDepth = 1
IF bpw # 32 -- a compile-time decision, presumably!
THEN {
MaskWithColor.StoreWithColorMask[
maskBase: LOOPHOLE[maskBase],
maskOffset: maskOffset,
maskWpl: maskWpl,
dstBase: LOOPHOLE[dstBase],
dstOffset: dstOffset,
dstWpl: dstWpl,
height: height,
width: width,
logDepth: 1,
colorWord: colorWord];
}
ELSE {
Special case (logDepth = 1 & bpw = 32)
expand: REF ExpandArray2 ¬ expandArray2;
dstMod: BitOffset ¬ (dstOffset ¬ dstOffset*2) MOD bpw;
maskMod: BitOffset ¬ maskOffset MOD bpw;
tailMask: WORD ¬ Basics.BITNOT[Basics.BITRSHIFT[WORD.LAST, width MOD bpw]];
dstBase ¬ dstBase + (dstOffset / bpw)*SIZE[WORD];
maskBase ¬ maskBase + (maskOffset / bpw)*SIZE[WORD];
IF tailMask = 0 THEN tailMask ¬ WORD.LAST;
IF width <= bpw AND maskMod = 0
THEN {
Special fast case for smallish characters (single-word aligned mask lines)
DO
Iterate through the lines
maskW: WORD ¬ Basics.BITAND[tailMask, maskBase[0]];
IF maskW # 0 THEN {
b0: BYTE = Basics.BITRSHIFT[maskW, 24] MOD 256;
b1: BYTE = Basics.BITRSHIFT[maskW, 16] MOD 256;
b2: BYTE = Basics.BITRSHIFT[maskW, 8] MOD 256;
b3: BYTE = maskW MOD 256;
dstMask0: WORD ¬ Basics.BITLSHIFT[expand[b0], 16] + expand[b1];
dstMask1: WORD ¬ Basics.BITLSHIFT[expand[b2], 16] + expand[b3];
dstMask2: WORD ¬ 0;
IF dstMod # 0 THEN {
dmc: BitOffset ¬ WORD[bpw - dstMod] MOD bpw;
dstMask2 ¬ Basics.BITLSHIFT[dstMask1, dmc];
dstMask1 ¬ Basics.BITLSHIFT[dstMask0, dmc] + Basics.BITRSHIFT[dstMask1, dstMod];
dstMask0 ¬ Basics.BITRSHIFT[dstMask0, dstMod];
};
IF dstMask0 # 0 THEN
dstBase[0] ¬ Basics.BITOR[
Basics.BITAND[Basics.BITNOT[dstMask0], dstBase[0]],
Basics.BITAND[dstMask0, colorWord]];
IF dstMask1 # 0 THEN
dstBase[1] ¬ Basics.BITOR[
Basics.BITAND[Basics.BITNOT[dstMask1], dstBase[1]],
Basics.BITAND[dstMask1, colorWord]];
IF dstMask2 # 0 THEN
dstBase[2] ¬ Basics.BITOR[
Basics.BITAND[Basics.BITNOT[dstMask2], dstBase[2]],
Basics.BITAND[dstMask2, colorWord]];
};
IF height = 1 THEN EXIT;
maskBase ¬ maskBase + maskWpl*SIZE[WORD];
dstBase ¬ dstBase + dstWpl*SIZE[WORD];
height ¬ height - 1;
ENDLOOP;
}
ELSE {
Multi-word or unaligned mask lines
maskMask: WORD ¬ Basics.BITNOT[Basics.BITRSHIFT[WORD.LAST, width MOD bpw]];
maskModC: BitOffset = WORD[bpw-maskMod] MOD bpw;
DO
Iterate through the lines
dstPtr: RawWordsPtr ¬ dstBase;
maskPtr: RawWordsPtr ¬ maskBase;
rem: CARDINAL ¬ width;
DO
maskW: WORD ¬ maskPtr[0]; -- AR3036 fixed - this line.
IF maskMod # 0 THEN {
The mask is not word-aligned, so get the overlapping part
maskW ¬ Basics.BITLSHIFT[maskW, maskMod];
IF rem > maskModC THEN
Need the next word, too, for the mask
maskW ¬ maskW + Basics.BITRSHIFT[maskPtr[1], maskModC];
};
IF rem < bpw THEN maskW ¬ Basics.BITAND[maskW, tailMask];
We can't use all of the mask
IF maskW # 0 THEN {
b0: BYTE = Basics.BITRSHIFT[maskW, 24] MOD 256;
b1: BYTE = Basics.BITRSHIFT[maskW, 16] MOD 256;
b2: BYTE = Basics.BITRSHIFT[maskW, 8] MOD 256;
b3: BYTE = maskW MOD 256;
dstMask0: WORD ¬ Basics.BITLSHIFT[expand[b0], 16] + expand[b1];
dstMask1: WORD ¬ Basics.BITLSHIFT[expand[b2], 16] + expand[b3];
dstMask2: WORD ¬ 0;
IF dstMod # 0 THEN {
dmc: BitOffset ¬ WORD[bpw - dstMod] MOD bpw;
dstMask2 ¬ Basics.BITLSHIFT[dstMask1, dmc];
dstMask1 ¬ Basics.BITLSHIFT[dstMask0, dmc] + Basics.BITRSHIFT[dstMask1, dstMod];
dstMask0 ¬ Basics.BITRSHIFT[dstMask0, dstMod];
};
IF dstMask0 # 0 THEN
dstPtr[0] ¬ Basics.BITOR[
Basics.BITAND[Basics.BITNOT[dstMask0], dstPtr[0]],
Basics.BITAND[dstMask0, colorWord]];
IF dstMask1 # 0 THEN
dstPtr[1] ¬ Basics.BITOR[
Basics.BITAND[Basics.BITNOT[dstMask1], dstPtr[1]],
Basics.BITAND[dstMask1, colorWord]];
IF dstMask2 # 0 THEN
dstPtr[2] ¬ Basics.BITOR[
Basics.BITAND[Basics.BITNOT[dstMask2], dstPtr[2]],
Basics.BITAND[dstMask2, colorWord]];
};
IF rem <= bpw THEN EXIT;
rem ¬ rem - bpw;
dstPtr ¬ dstPtr + SIZE[WORD]*2;
maskPtr ¬ maskPtr + SIZE[WORD];
ENDLOOP;
IF height = 1 THEN EXIT;
maskBase ¬ maskBase + maskWpl*SIZE[WORD];
dstBase ¬ dstBase + dstWpl*SIZE[WORD];
height ¬ height - 1;
ENDLOOP;
};
};
};
lgTable: PACKED ARRAY [0..32] OF BYTE = [
0, 0, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4,
5];
deviceClass: ImagerDevice.DeviceClass ~ NEW[ImagerDevice.DeviceClassRep ¬ [
SetColor: HiliteSetColor,
SetPriority: NIL,
SetHalftoneProperties: HiliteSetHalftoneProperties,
MaskBoxes: HiliteMaskBoxes,
MaskBitmap: HiliteMaskBitmap,
MaskRawBitmaps: HiliteMaskRawBitmaps,
GetBufferColorOperator: HiliteGetBufferColorOperator
]];
contextClass: ImagerPrivate.Class ~ CreateClass[];
CreateClass: PROC RETURNS [class: ImagerPrivate.Class] ~ {
class ¬ ImagerRaster.CreateClass[type: classCode];
};
END.