ImagerPDImpl.mesa
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Michael Plass, March 6, 1987 7:02:52 pm PST
Doug Wyatt, May 30, 1985 10:32:41 pm PDT
Dave Rumph, September 20, 1985 9:41:20 pm PDT
Tim Diebert: April 25, 1986 11:45:19 am PST
Eric Nickell February 18, 1986 6:49:26 pm PST
Stone, February 11, 1987 12:04:24 pm PST
Last edited by: Mik Lamming - May 1, 1987 11:13:22 am PDT
Maureen Stone, June 4, 1987 1:23:53 pm PDT
~
BEGIN
ROPE: TYPE ~ Rope.ROPE;
VEC: TYPE ~ Imager.VEC;
PathProc: TYPE ~ ImagerPath.PathProc;
Context: TYPE ~ Imager.Context;
CharMask: TYPE ~ ImagerMaskCache.CharMask;
Toner: TYPE ~ ImagerPDPublic.Toner;
Toners: TYPE ~ LIST OF ImagerPDPublic.Toner;
UCR: TYPE ~ ImagerPDPublic.UCR;
PrinterType: TYPE ~ ImagerPD.PrinterType;
Transformation: TYPE ~ ImagerTransformation.Transformation;
DeviceCode: TYPE ~ PDFileFormat.DeviceCode;
PD: TYPE ~ REF PDRep;
PDRep: PUBLIC TYPE ~ ImagerPDPrivate.PDRep;
ClassRep: PUBLIC TYPE ~ ImagerPrivate.ClassRep;
StateRep:
PUBLIC
TYPE ~ ImagerState.StateRep;
DeviceData: TYPE ~ ImagerPDPrivate.DeviceData;
DeviceDataRep: TYPE ~ ImagerPDPrivate.DeviceDataRep;
deviceClass: ImagerDevice.DeviceClass ~
NEW[ImagerDevice.DeviceClassRep ← [
SetColor: ImagerPDPrivate.SetColor,
SetPriority: SetPriority,
MaskBoxes: MaskBoxes,
MaskBitmap: MaskBitmap,
MaskChar: MaskChar
]];
SampleMap: TYPE ~ ImagerSample.SampleMap;
Tile: TYPE ~ ImagerPD.Tile;
bitsPerWord: NAT ~ Basics.bitsPerWord;
TonerSetFromToners:
PROC [toners: Toners]
RETURNS [tonerSet:
PACKED
ARRAY PDFileFormat.Toner
OF
BOOL] ~ {
tonerSet ← ALL[FALSE];
FOR t: Toners ← toners, t.rest
UNTIL t=
NIL
DO
pdt: PDFileFormat.Toner ~ VAL[ORD[t.first]];
IF tonerSet[pdt] THEN Imager.Error[[$specification, "Multiple use of same toner in PD Create"]];
tonerSet[pdt] ← TRUE;
ENDLOOP;
};
rastWeight: REAL ← 1.7;
fontCacheSize:
NAT ← 8000;
defaultDotShape:
REAL ← 0.45;
defaultScreenAngle:
ARRAY PDFileFormat.Toner[black..yellow]
OF
REAL ← [
black: 90,
cyan: 75,
magenta: 105,
yellow: 45
];
GetDefaultBricks:
PROC [deviceData: DeviceData, pixelsPerHalftoneDot:
REAL] ~ {
FOR t: PDFileFormat.Toner
IN PDFileFormat.Toner
DO
IF deviceData.tonerSet[t]
THEN {
angle: REAL ~ IF t IN[black..yellow] THEN defaultScreenAngle[t] ELSE 0;
brick: ImagerBrick.Brick ~ ImagerBrick.BrickFromDotScreen[pixelsPerDot: pixelsPerHalftoneDot, degrees: angle, shape: defaultDotShape];
deviceData.halftoneBrick[t] ← NEW[Tile ← brick];
};
ENDLOOP;
};
CreateFromParameters:
PUBLIC
PROC [name:
ROPE, deviceCode:
CARDINAL,
deviceType:
ATOM,
sResolution, fResolution:
CARDINAL,
-- pixels per inch
imageSSize, imageFSize:
CARDINAL,
-- pixels
toners: Toners, leftovers:
BOOL, bandSSize:
NAT, maxLoadWords:
INT, fontTuning:
ROPE, tonerUniverse: Toners ←
NIL, pixelsPerHalftoneDot:
REAL, ucr:
UCR ← []]
RETURNS [
PD] ~ {
writer: PDFileWriter.PDState ~ PDFileWriter.Create[
fileName: name,
deviceCode: VAL[deviceCode],
sResolution: sResolution,
fResolution: fResolution,
imageSSize: imageSSize,
imageFSize: imageFSize,
bandSSize: bandSSize,
leftOverMode: leftovers,
maxLoadWords: maxLoadWords
];
deviceData: DeviceData ~
NEW [DeviceDataRep ← [
deviceType: deviceType,
writer: writer,
sSize: imageSSize,
fSize: imageFSize,
feed: FALSE,
strip: FALSE,
imageStarted: FALSE,
priorityImportant: FALSE,
tonerSet: TonerSetFromToners[toners],
tonerUniverseSet: TonerSetFromToners[IF tonerUniverse # NIL THEN tonerUniverse ELSE toners],
toner: black,
ucr: ucr,
maskTab: RefTab.Create[997],
colorKind: nil,
tileTable: NEW[ImagerPDPrivate.TileTableRep[17]],
colorTile: [0, NIL, 0],
colorClear: FALSE,
sampledSource: NIL,
paToDevice: ImagerTransformation.Scale[0],
halftoneBrick: ALL[NIL],
pixelsRGB: ImagerPixel.ObtainScratchPixels[3, 1],
pixelsCMYK: ImagerPixel.ObtainScratchPixels[4, 1],
interpolate: FALSE
]];
sInches: REAL ~ REAL[imageSSize]/REAL[sResolution];
fInches: REAL ~ REAL[imageFSize]/REAL[fResolution];
fontCacheID: ATOM ~ IF Rope.Size[fontTuning] = 0 THEN $PD ELSE Atom.MakeAtom[fontTuning];
fontTuner: ImagerDevice.FontTuner ← -- IF Rope.Size[fontTuning] # 0 THEN FontTune.CreateFontTuner[fontTuning] ELSE -- NIL;
deviceParm: ImagerDevice.DeviceParm ←
NEW[ImagerDevice.DeviceParmRep ← [
class: deviceClass,
sSize: imageSSize,
fSize: imageFSize,
scanMode:
IF sInches>fInches
THEN [slow: down, fast: right]
ELSE [slow: right, fast: up],
Call the larger dimension y; see the Interpress standard, section 4.3.1.
surfaceUnitsPerInch: [sResolution, fResolution],
surfaceUnitsPerPixel: 1,
fontTuner: fontTuner,
fontCache: ImagerMaskCache.GetNamedCache[fontCacheID, fontCacheSize],
rastWeight: rastWeight
]];
context: Context ~ ImagerRaster.Create[class: contextClass, deviceParm: deviceParm, data: deviceData, pixelUnits: FALSE];
pd:
PD ~
NEW [PDRep ← [
context: context,
toners: toners,
deviceData: deviceData
]];
IF deviceType=$raven384
OR deviceType=$versatec
OR deviceType=$bw400
THEN {
Stash the MaskStroke and the MaskVector Procs.
black: ImagerColor.Color ← ImagerColor.Find["Xerox/Research/Distinct/Black"];
white: ImagerColor.Color ← ImagerColor.Find["Xerox/Research/Distinct/White"];
oldClass: REF ClassRep ← context.class; --crack the opaque type
newClass: REF ClassRep ← NEW[ClassRep ← oldClass^];
ImagerRasterMaskVector ← oldClass.MaskVector;
ImagerRasterMaskStroke ← oldClass.MaskStroke;
newClass.MaskStroke ← MyMaskStroke;
newClass.MaskVector ← MyMaskVector;
Imager.PutProp[context, $DistinctBlack, black];
Imager.PutProp[context, $DistinctWhite, white];
context.class ← newClass; --replace the class with our local subclass
};
IF pixelsPerHalftoneDot <= 0 THEN pixelsPerHalftoneDot ← 2;
GetDefaultBricks[deviceData, pixelsPerHalftoneDot];
RETURN[pd];
};
The great, dashed line hack. In each of these procedures, the call to SetColor will be noticed by the call to Validate in ImagerRaster. The color has to be set at the conclusion of the call in case the next masks are areas.
ImagerRasterMaskVector: PROC [context: Context, p1, p2: VEC] ← NIL;
ImagerRasterMaskStroke: PROC [context: Context, path: PathProc, closed: BOOL] ← NIL;
DoDash:
PROC[context: Context]
RETURNS[
BOOLEAN] = {
state: REF StateRep ← context.state;
WITH state.color
SELECT
FROM
color: ImagerColor.SpecialColor =>
IF color.type=$Distinct
THEN {
nameData: ColorRegistry.Data ← NARROW[color.data];
IF nameData.id#$Black AND nameData.id#$White AND nameData.id#$Gray THEN {
widthInPixels: REAL ← ImagerTransformation.TransformVec[state.T, [state.np.strokeWidth, 0]].x;
IF widthInPixels < 10 THEN RETURN[TRUE]; --define 10 device pixels as ok for texture
}};
ENDCASE;
RETURN[FALSE];
};
MyMaskVector:
PROC [context: Context, p1, p2: Imager.
VEC] = {
IF DoDash[context]
THEN {
black: ImagerColor.Color ← NARROW[Imager.GetProp[context, $DistinctBlack]];
white: ImagerColor.Color ← NARROW[Imager.GetProp[context, $DistinctWhite]];
path: ImagerPath.PathProc = {moveTo[p1]; lineTo[p2]};
color: ImagerColor.SpecialColor ← NARROW[ImagerState.StateGetColor[context]];
ImagerState.StateSetColor[context, white];
ImagerRasterMaskVector[context, p1, p2];
ImagerState.StateSetColor[context, black];
MaskDashedStroke[context, color, path, FALSE];
ImagerState.StateSetColor[context, color];
}
ELSE ImagerRasterMaskVector[context, p1, p2];
};
MyMaskStroke:
PROC [context: Context, path: PathProc, closed:
BOOL] = {
IF DoDash[context]
THEN {
black: ImagerColor.Color ← NARROW[Imager.GetProp[context, $DistinctBlack]];
white: ImagerColor.Color ← NARROW[Imager.GetProp[context, $DistinctWhite]];
color: ImagerColor.SpecialColor ← NARROW[ImagerState.StateGetColor[context]];
ImagerState.StateSetColor[context, white];
ImagerRasterMaskStroke[context, path, closed];
ImagerState.StateSetColor[context, black];
MaskDashedStroke[context, color, path, closed];
ImagerState.StateSetColor[context, color];
}
ELSE ImagerRasterMaskStroke[context, path, closed];
};
yellowDash: ARRAY [0..3] OF REAL ← [0,2.5,0,2.5];
redDash: ARRAY [0..3] OF REAL ← [3,2,3,2];
greenDash: ARRAY [0..3] OF REAL ← [1,2,1,2];
blueDash: ARRAY [0..3] OF REAL ← [4,2,1,2];
MaskDashedStroke:
PROC [context: Context, color: ImagerColor.SpecialColor, path: PathProc, closed:
BOOL] = {
deviceData: DeviceData ~ NARROW[context.data];
unit: REAL ← SELECT deviceData.deviceType FROM
$raven384, $bw400 => 10, $versatec => 5, ENDCASE => 1;
array: ARRAY [0..3] OF REAL;
pattern:
PROC[i:
NAT]
RETURNS[
REAL] = {
v: VEC ← ImagerTransformation.InverseTransformVec[state.T, [array[i]*unit, 0]];
RETURN[v.x]};
state: REF StateRep ← context.state;
end: INT ← state.np.strokeEnd;
joint: INT ← state.np.strokeJoint;
nameData: ColorRegistry.Data ← NARROW[color.data];
Imager.SetStrokeEnd[context, round];
Imager.SetStrokeJoint[context, round];
array ←
SELECT nameData.id
FROM
$Red => redDash, $Green => greenDash,
$Blue => blueDash, $Yellow => yellowDash,
ENDCASE => ERROR;
Imager.MaskDashedStroke[context, path, 4, pattern, 0,0];
state.np.strokeEnd ← end;
state.np.strokeJoint ← joint;
};
Close:
PUBLIC
PROC [pd:
PD] ~ {
PDFileWriter.Close[pd.deviceData.writer];
[] ← FunctionCache.Obtain[x: FunctionCache.GlobalCache[], compare: FunctionCache.Any, limit: INT.LAST, clientID: pd.deviceData]; -- Flush our stuff from the cache.
};
StartImage:
PROC [deviceData: DeviceData] ~ {
tonerSet: PACKED ARRAY PDFileFormat.Toner OF BOOL ← ALL[FALSE];
tonerSet[deviceData.toner] ← TRUE;
PDFileWriter.StartImage[pdState: deviceData.writer, toners: tonerSet, feed: deviceData.feed, strip: deviceData.strip];
deviceData.imageStarted ← TRUE;
};
identity: Transformation ~ ImagerTransformation.Scale[1];
DoPage:
PUBLIC
PROC [pd:
PD, action:
PROC [context: Context], pixelUnits:
BOOL ←
FALSE] ~ {
proc: PROC ~ {action[pd.context]};
littleRectangle:
PROC ~ {
PD files don't work very well with empty separations.
Imager.SetPriorityImportant[pd.context, TRUE];
Imager.SetGray[pd.context, 0];
ImagerBackdoor.SetT[pd.context, identity];
Imager.MaskRectangleI[pd.context, 0, 0, 1, 1];
};
pd.deviceData.feed ← TRUE;
FOR t: Toners ← pd.toners, t.rest
UNTIL t=
NIL
DO
tonerCard: CARDINAL ~ ORD[t.first];
toner: PDFileFormat.Toner ~ VAL[tonerCard];
pd.deviceData.toner ← toner;
pd.deviceData.imageStarted ← FALSE;
pd.deviceData.strip ← t.rest=NIL;
StartImage[pd.deviceData];
Imager.SetGray[pd.context, 1];
Imager.DoSaveAll[pd.context, littleRectangle];
Imager.DoSaveAll[pd.context, proc];
IF pd.deviceData.strip
AND
NOT pd.deviceData.imageStarted
THEN {
Were we more clever, we would not generate StartImage for empty separations.
};
IF pd.deviceData.imageStarted
THEN {
PDFileWriter.EndPage[pd.deviceData.writer];
pd.deviceData.feed ← FALSE;
};
ENDLOOP;
};
SetPriority:
PROC [context: Context, priorityImportant:
BOOL] ~ {
deviceData: DeviceData ~ NARROW[context.data];
deviceData.priorityImportant ← priorityImportant;
[] ← PDFileWriter.SetPriorityImportant[deviceData.writer, priorityImportant];
};
MaskBox:
PROC [context: Context, box:
SF.Box] ~ {
deviceData: DeviceData ~ NARROW[context.data];
SELECT deviceData.colorKind
FROM
nil => ERROR; -- color not initialized
constant => {
PDFileWriter.MaskRectangle[deviceData.writer, box.min.s, box.min.f, box.max.s-box.min.s, box.max.f-box.min.f];
};
sampled => {
size: SF.Vec ~ SF.Size[box];
DeliverLines:
PROC[p:
PROC [
LONG
POINTER]] ~ {
buffer: ImagerSample.SampleBuffer ~ ImagerSample.ObtainScratchSamples[size.f];
bits: ImagerSample.RasterSampleMap ~ ImagerSample.ObtainScratchMap[[max: [1, size.f]]];
brick: ImagerPD.Tile ~ deviceData.halftoneBrick[deviceData.toner]^;
Action:
SAFE
PROC [pixels: ImagerPixel.PixelBuffer, min:
SF.Vec] ~ {
buffer.length ← pixels.length;
ImagerSample.GetTileSamples[tile: brick.sampleMap, initIndex: min, buffer: buffer, phase: brick.phase];
ImagerSample.Halftone[map: bits, min: [0, 0], sampleBuffer: pixels[0], thresholdBuffer: buffer, function: [null, complement]];
TRUSTED { p[ImagerSample.GetBase[bits].word] };
};
Boxes: SF.BoxGenerator ~ {boxAction[box]};
ImagerPixel.Resample[self: deviceData.sampledSource, m: deviceData.paToDevice, interpolate: deviceData.interpolate, boxes: Boxes, bounds: box, action: Action];
ImagerSample.ReleaseScratchSamples[buffer];
ImagerSample.ReleaseScratchMap[bits];
};
PDFileWriter.ColorSamples[pdState: deviceData.writer, toner: deviceData.toner,
sMin: box.min.s, fMin: box.min.f, sSize: size.s, fSize: size.f, deliverProc: DeliverLines,
tFlag: opaque];
};
sampledBlack => {
size: SF.Vec ~ SF.Size[box];
DeliverLines:
PROC[p:
PROC [
LONG
POINTER]] ~ {
bits: ImagerSample.RasterSampleMap ~ ImagerSample.ObtainScratchMap[[max: [1, size.f]]];
Action:
PROC [pixels: ImagerPixel.PixelBuffer, min:
SF.Vec] ~ {
ImagerSample.PutSamples[map: bits, buffer: pixels[0]];
TRUSTED { p[ImagerSample.GetBase[bits].word] };
};
Boxes: SF.BoxGenerator ~ {boxAction[box]};
ImagerPixel.Resample[self: deviceData.sampledSource, m: deviceData.paToDevice, interpolate: deviceData.interpolate, boxes: Boxes, bounds: box, action: Action];
ImagerSample.ReleaseScratchMap[bits];
};
PDFileWriter.ColorSamples[pdState: deviceData.writer, toner: deviceData.toner,
sMin: box.min.s, fMin: box.min.f, sSize: size.s, fSize: size.f, deliverProc: DeliverLines,
tFlag: IF deviceData.colorClear THEN transparent ELSE opaque];
};
tile => {
tFlag: PDFileWriter.TFlag ~ IF deviceData.colorClear THEN transparent ELSE opaque;
deliverLines:
PROC[p:
PROC [
LONG
POINTER]] ~
TRUSTED {
FOR s:
INTEGER
IN [box.min.s..box.max.s)
DO
line: ImagerSample.RasterSampleMap ~ ImagerSample.ObtainScratchMap[box: [min: [s, box.min.f], max: [s+1, box.max.f]]];
ImagerSample.Tile[map: line, tile: deviceData.colorTile.sampleMap, phase: deviceData.colorTile.phase];
TRUSTED {p[ImagerSample.GetBase[line].word]};
ImagerSample.ReleaseScratchMap[line];
ENDLOOP;
};
PDFileWriter.ColorSamples[pdState: deviceData.writer, toner: deviceData.toner, sMin: box.min.s, fMin: box.min.f, sSize: box.max.s-box.min.s, fSize: box.max.f-box.min.f, deliverProc: deliverLines, tFlag: tFlag];
};
ENDCASE => ERROR; -- illegal case
};
useLoadForChars:
BOOL ←
TRUE;
GetLoadEntry:
PROC [deviceData: DeviceData, mask: CharMask]
RETURNS [
REF
INT] ~ {
refInt: REF INT ← NARROW[RefTab.Fetch[deviceData.maskTab, mask].val];
load: INT ← 0;
IF refInt#NIL THEN RETURN [refInt];
IF NOT useLoadForChars THEN RETURN [NIL];
WITH mask
SELECT
FROM
raster:
REF ImagerMaskCache.CharMaskRep.raster => {
size: SF.Vec ~ SF.Size[raster.box];
loadWords: INT ~ SIZE[PDFileFormat.SampleArray] + Basics.LongMult[size.s, (size.f+bitsPerWord-1)/bitsPerWord];
IF loadWords > PDFileWriter.RemainingLoadSize[deviceData.writer] THEN RETURN[NIL]; -- load full
TRUSTED {load ← PDFileWriter.LoadContiguousSampleArray[pdState: deviceData.writer, sSize: size.s, fSize: size.f, bitsPtr: @(raster[0])]};
};
runs:
REF ImagerMaskCache.CharMaskRep.runs => {
Runs:
PROC [run:
PROC [sMin, fMin:
INTEGER, fSize:
NAT]] ~ {
s: INTEGER ← 0;
sMax: INTEGER ~ SF.SizeS[runs.box];
FOR i:
NAT
IN [0..runs.nRuns)
WHILE s < sMax
DO
r: ImagerMaskCache.Run ~ runs[i];
IF r.fSize # 0 THEN run[s, r.fMin, r.fSize];
IF r.lastRun THEN s ← s + 1;
ENDLOOP;
};
loadWords: INT ~ SIZE[PDFileFormat.RunGroup] + runs.nRuns*SIZE[PDFileFormat.Run];
IF loadWords > PDFileWriter.RemainingLoadSize[deviceData.writer] THEN RETURN[NIL];
load ← PDFileWriter.LoadRunGroup[deviceData.writer, Runs];
};
ENDCASE => ERROR;
refInt ← NEW[INT ← load];
[] ← RefTab.Store[deviceData.maskTab, mask, refInt];
RETURN [refInt];
};
MaskChar:
PROC [context: Context, delta:
SF.Vec, mask: CharMask] ~ {
deviceData: DeviceData ~ NARROW[context.data];
loadEntry: REF INT ← GetLoadEntry[deviceData, mask];
sMin: NAT ~ delta.s+mask.box.min.s;
fMin: NAT ~ delta.f+mask.box.min.f;
IF deviceData.colorKind#constant THEN ERROR;
IF loadEntry#
NIL
THEN {
load: LONG CARDINAL ~ loadEntry^;
SELECT mask.rep
FROM
raster => PDFileWriter.MaskSamplesRef[deviceData.writer, load, sMin, fMin];
runs => PDFileWriter.MaskRunGroupRef[deviceData.writer, load, sMin, fMin];
ENDCASE => ERROR;
}
ELSE
WITH mask
SELECT
FROM
raster:
REF ImagerMaskCache.CharMaskRep.raster => {
bitmap: ImagerSample.SampleMap ~ ImagerMaskCache.BitmapFromCharMask[raster];
MaskBitmap[context, bitmap, delta, SF.Displace[box: mask.box, t: delta], NIL];
};
runs:
REF ImagerMaskCache.CharMaskRep.runs => {
Runs:
PROC [run: PDFileWriter.CaptureRunProc] ~ {
s: INTEGER ← sMin;
sMax: INTEGER ~ delta.s+mask.box.max.s;
FOR i:
NAT
IN [0..runs.nRuns)
WHILE s < sMax
DO
r: ImagerMaskCache.Run ~ runs[i];
IF r.fSize # 0 THEN run[s, r.fMin+fMin, r.fSize];
IF r.lastRun THEN s ← s + 1;
ENDLOOP;
};
PDFileWriter.MaskRunGroup[deviceData.writer, Runs];
};
ENDCASE => ERROR;
};
MaskBoxes:
PROC [context: Context, bounds:
SF.Box, boxes:
SF.BoxGenerator] ~ {
deviceData: DeviceData ~ NARROW[context.data];
SELECT deviceData.colorKind
FROM
nil => ERROR; -- color not initialized
constant => {
constantBox:
PROC [box:
SF.Box] ~ {
PDFileWriter.MaskRectangle[deviceData.writer, box.min.s, box.min.f, box.max.s-box.min.s, box.max.f-box.min.f];
};
boxes[constantBox];
};
ENDCASE => {
hardBox: PROC[box: SF.Box] ~ { MaskBox[context, box] };
boxes[hardBox];
};
};
MaskBitmap:
PROC [context: Context, bitmap: SampleMap, delta:
SF.Vec, bounds:
SF.Box, boxes:
SF.BoxGenerator] ~ {
deviceData: DeviceData ~ NARROW[context.data];
SELECT deviceData.colorKind
FROM
nil => ERROR; -- color not initialized
constant => {
constantBox:
PROC [box:
SF.Box] ~ {
deliverLines:
PROC [p:
PROC [
LONG
POINTER]] ~
TRUSTED {
FOR s:
INTEGER
IN [box.min.s..box.max.s)
DO
line: ImagerSample.RasterSampleMap ~ ImagerSample.ObtainScratchMap[box: [min: [s, box.min.f], max: [s+1, box.max.f]]];
ImagerSample.Transfer[dst: line, src: bitmap, delta: delta];
TRUSTED {p[ImagerSample.GetBase[line].word]};
ImagerSample.ReleaseScratchMap[line];
ENDLOOP;
};
PDFileWriter.MaskSamples[pdState: deviceData.writer, sMin: box.min.s, fMin: box.min.f, sSize: box.max.s-box.min.s, fSize: box.max.f-box.min.f, deliverProc: deliverLines];
};
IF boxes = NIL THEN constantBox[bounds] ELSE boxes[constantBox];
};
ENDCASE => ERROR; -- SetColor should not have allowed bitmaps
};
contextClass: ImagerPrivate.Class ~ ImagerRaster.CreateClass[type: $PD, deviceClass: deviceClass];