ImagerBitmapContextImpl.mesa
Copyright Ó 1986, 1987, 1988, 1989, 1991, 1992 by Xerox Corporation. All rights reserved.
Michael Plass, October 26, 1993 5:05 pm PDT
Doug Wyatt, January 19, 1987 9:01:22 pm PST
Bier, March 21, 1989 6:52:32 pm PST
DIRECTORY
FunctionCache USING [Cache, CompareProc, GlobalCache, Insert, Lookup],
Imager USING [black, ClipRectangle, ConcatT, Context, DoSave, DoSaveAll, Error, MaskBitmap, MaskPixel, Object, SetColor, SetXY, TranslateT, white],
ImagerBackdoor USING [GetBounds, GetColor, GetTransformation, SetT],
ImagerBrick USING [Brick, HalftoneProperties],
ImagerBitmapContext USING [classCode],
ImagerBox USING [Box, BoxFromRectangle, IntersectBox, Rectangle],
ImagerColor,
ImagerColorPrivate,
ImagerDeviceColor,
ImagerDevice USING [Device, DeviceClass, DeviceClassRep, DeviceParm, MakeDeviceParm],
ImagerMaskCache USING [GetNamedCache, SetNamedCacheParameters],
ImagerMaskCapture USING [CaptureBitmap],
ImagerPixel USING [MakePixelMap, PixelBuffer, PixelMap, PixelMapRep, Resample],
ImagerPixelArray USING [maxVec, PixelArray, Transfer],
ImagerPrivate USING [Class, DefaultDrawObject],
ImagerRaster USING [Create, CreateClass, GetDevice, SetDeviceClipBox],
ImagerSample USING [Clear, Clip, EdgeAction, FillBoxes, Function, GetBitsPerSample, GetBox, GetSize, GetTileSamples, Halftone, maxSample, maxVec, Move, NewSampleMap, ObtainScratchMap, ObtainScratchSamples, Put, PutSamples, RasterSampleMap, RawDescriptor, RawListTransfer, RegionFill, ReleaseDescriptor, ReleaseScratchMap, ReleaseScratchSamples, SampleBuffer, SampleMap, Shift, TileBoxes, TileFromStipple, Transfer, TransferBoxes, WordsForMap],
ImagerTransformation USING [ApplyCat, CloseToTranslation, Copy, Destroy, Invert, Scale, ScanMode, SingularValues, Transformation, TransformRectangle, TransformVec],
Real USING [Floor, Round],
RefTab USING [Create, Delete, EachPairAction, Fetch, GetSize, Key, Pairs, Ref, Store, Val],
SF USING [Box, BoxGenerator, Displace, maxVec, SizeF, Vec],
Vector2 USING [Add, Sub, VEC];
ImagerBitmapContextImpl: CEDAR MONITOR
IMPORTS RefTab, FunctionCache, ImagerDevice, Imager, ImagerBackdoor, ImagerBitmapContext, ImagerBox, ImagerColor, ImagerColorPrivate, ImagerDeviceColor, ImagerMaskCache, ImagerMaskCapture, ImagerPixel, ImagerPixelArray, ImagerPrivate, ImagerRaster, ImagerSample, ImagerTransformation, Real, SF, Vector2
EXPORTS ImagerBitmapContext
~ BEGIN
Types
Color: TYPE ~ ImagerColor.Color;
ColorOperator: TYPE ~ ImagerColor.ColorOperator;
Context: TYPE ~ Imager.Context;
Device: TYPE ~ ImagerDevice.Device;
Object: TYPE ~ Imager.Object;
PixelMap: TYPE ~ ImagerPixel.PixelMap;
PixelArray: TYPE ~ ImagerPixelArray.PixelArray;
RasterSampleMap: TYPE ~ ImagerSample.RasterSampleMap;
Rectangle: TYPE ~ ImagerBox.Rectangle;
SampleMap: TYPE ~ ImagerSample.SampleMap;
ScanMode: TYPE ~ ImagerTransformation.ScanMode;
Transformation: TYPE ~ ImagerTransformation.Transformation;
VEC: TYPE ~ Vector2.VEC;
classCode: PUBLIC ATOM ¬ $Bitmap;
fontCacheMaxSize: NAT ¬ 4000;
Case: TYPE ~ {constant, tile, sampled, sampledBlack};
Data: TYPE ~ REF DataRep;
DataRep: TYPE ~ RECORD [
color: ImagerColor.Color ¬ NIL, -- used to re-validate color when the halftone changes.
bitmap: SampleMap ¬ NIL,
savedBuffer: SampleMap ¬ NIL,
case: Case ¬ constant,
constant: [0..1] ¬ 1,
function: ImagerSample.Function ¬ [null, null],
maskBitmapFunction: ImagerSample.Function ¬ [null, null],
tile: ImagerBrick.Brick ¬ [0, NIL, 0], -- bitmap for case=tile
separation: ATOM,
brick: ImagerBrick.Brick ¬ [0, NIL, 0], -- thresholds
control: ImagerDeviceColor.DeviceColorControl ¬ NIL,
controlStamp: CARD ¬ 0,
colorTransform: ImagerColorPrivate.ColorTransform ¬ NIL,
paToDevice: Transformation,
source: ImagerPixel.PixelMap ¬ NIL,
scratchSampleMapThatIsInUse: SampleMap ¬ NIL, -- SampleMap that should go back to the scratch pool at the next SetColor
scratchStippleMap: SampleMap ¬ NIL, -- scratch storage for stipple case
scratchPixelMap: PixelMap ¬ NIL, -- scratch descriptor for AccessBuffer
scratchBitmapContext: Imager.Context ¬ NIL -- for making fancy tiles
];
Public Procedures
Create: PUBLIC PROC [deviceSpaceSize: SF.Vec, scanMode: ScanMode, surfaceUnitsPerInch: VEC, surfaceUnitsPerPixel: NAT ¬ 1, pixelUnits: BOOL, fontCacheName: ATOM] 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: surfaceUnitsPerPixel, fontCache: IF fontCacheName = NIL THEN NIL ELSE ImagerMaskCache.GetNamedCache[fontCacheName]];
context: Context ~ ImagerRaster.Create[class: contextClass, deviceClass: deviceClass, deviceParm: deviceParm, data: data, pixelUnits: pixelUnits];
data.brick ¬ defaultBrick;
data.control ¬ ImagerDeviceColor.GetDeviceColorControl[ImagerRaster.GetDevice[context]];
ImagerRaster.SetDeviceClipBox[context, [[0,0], [0,0]]];
RETURN [context];
};
SetBitmap: PUBLIC PROC [context: Context, bitmap: SampleMap] ~ {
device: Device ~ ImagerRaster.GetDevice[context];
data: Data ~ NARROW[device.data];
data.bitmap ¬ bitmap;
ImagerRaster.SetDeviceClipBox[context, IF bitmap = NIL THEN [] ELSE ImagerSample.GetBox[bitmap]];
};
GetBitmap: PUBLIC PROC [context: Context] RETURNS [SampleMap] ~ {
device: Device ~ ImagerRaster.GetDevice[context];
data: Data ~ NARROW[device.data];
RETURN [data.bitmap];
};
Color Setup
MakeSimpleBrick: PROC [t: ARRAY [0..4) OF PACKED ARRAY [0..4) OF [0..16)] RETURNS [ImagerBrick.Brick] ~ {
b: SampleMap ~ ImagerSample.NewSampleMap[box: [max: [4, 4]], bitsPerSample: 8];
FOR s: NAT IN [0..4) DO
FOR f: NAT IN [0..4) DO
ImagerSample.Put[b, [s, f], t[s][f]];
ENDLOOP;
ENDLOOP;
RETURN [[maxSample: 15, sampleMap: b, phase: 0]]
};
coarseBrick: ImagerBrick.Brick ¬ MakeSimpleBrick[[
[00, 01, 13, 14],
[08, 02, 03, 15],
[09, 10, 04, 05],
[07, 11, 12, 06]
]];
fineBrick: ImagerBrick.Brick ¬ MakeSimpleBrick[[
[00, 08, 02, 10],
[14, 04, 12, 06],
[03, 11, 01, 09],
[13, 07, 15, 05]
]];
defaultBrick: ImagerBrick.Brick ¬ fineBrick;
SetBrick: PUBLIC PROC [context: Context, brick: ImagerBrick.Brick] ~ {
device: Device ~ ImagerRaster.GetDevice[context];
WITH device.data SELECT FROM
data: Data => {
IF data.brick.maxSample # brick.maxSample THEN {
data.colorTransform ¬ NIL;
};
data.brick ¬ brick;
IF data.color # NIL THEN BitmapSetColor[device, data.color, NIL];
};
ENDCASE => NULL;
};
BitmapSetHalftoneProperties: PROC [device: Device, halftoneProperties: ImagerBrick.HalftoneProperties] ~ {
black: ImagerBrick.HalftoneProperties ¬ halftoneProperties;
UNTIL black = NIL OR black.first.toner=$black DO black ¬ black.rest ENDLOOP;
IF black # NIL THEN WITH device.data SELECT FROM
data: Data => {
brick: ImagerBrick.Brick ~ black.first.brick;
IF data.brick.maxSample # brick.maxSample THEN {
data.colorTransform ¬ NIL;
};
data.brick ¬ brick;
IF data.color # NIL THEN BitmapSetColor[device, data.color, NIL];
};
ENDCASE => NULL;
};
GetBrick: PUBLIC PROC [context: Context] RETURNS [ImagerBrick.Brick] ~ {
device: Device ~ ImagerRaster.GetDevice[context];
WITH device.data SELECT FROM
data: Data => {
RETURN [data.brick]
};
ENDCASE => NULL;
RETURN [[0, NIL, 0]]
};
SetSeparation: PUBLIC PROC [context: Context, separation: ATOM] ~ {
device: Device ~ ImagerRaster.GetDevice[context];
WITH device.data SELECT FROM
data: Data => {
data.separation ¬ separation;
Imager.SetColor[context, ImagerBackdoor.GetColor[context]]; -- to re-validate the color
};
ENDCASE => NULL;
};
GetSeparation: PUBLIC PROC [context: Context] RETURNS [ATOM] ~ {
device: Device ~ ImagerRaster.GetDevice[context];
WITH device.data SELECT FROM
data: Data => { RETURN [data.separation] };
ENDCASE => NULL;
RETURN [NIL]
};
GetExtendedBox: PROC [brick: ImagerBrick.Brick] RETURNS [SF.Box] ~ {
This computes a box for a bitmap tile that will hit a fast case of BITBLT, if this is possible.
bpw: NAT = BITS[WORD];
box: SF.Box ¬ ImagerSample.GetBox[brick.sampleMap];
size: SF.Vec ~ ImagerSample.GetSize[brick.sampleMap];
fSizeBox: NAT ~ size.f;
IF brick.phase = 0 AND size.f IN [1..bpw) AND size.s IN [1..16) AND bpw MOD NAT[size.f] = 0 THEN {
box.max.f ¬ box.min.f+bpw;
};
RETURN [box]
};
allowedColorSpaces: LIST OF ImagerColorPrivate.ColorSpace ~ LIST[$Y];
GetColorTransform: PROC [data: Data, colorOperator: ColorOperator] RETURNS [ImagerColorPrivate.ColorTransform] ~ {
colorSpace: ImagerColorPrivate.ColorSpace ~ ImagerColorPrivate.ChooseColorSpace[colorOperator, allowedColorSpaces];
IF data.colorTransform = NIL OR colorSpace # data.colorTransform.domain OR data.controlStamp # data.control.stamp THEN {
rangeMax: ImagerColorPrivate.ColorPoint ~ ImagerColorPrivate.MakeColorPoint[1, data.brick.maxSample+1];
ct: ImagerColorPrivate.ColorTransform ~ NEW[ImagerColorPrivate.ColorTransformRep ¬ [domain: $Y, rangeMax: rangeMax, proc: Transform, data: data.control]];
data.colorTransform ¬ ct;
data.controlStamp ¬ data.control.stamp;
};
RETURN [data.colorTransform]
};
Transform: PROC [self: ImagerColorPrivate.ColorTransform, in: ImagerColorPrivate.ColorPoint, out: ImagerColorPrivate.ColorPoint] ~ {
control: ImagerDeviceColor.DeviceColorControl ~ NARROW[self.data];
ImagerDeviceColor.YFromY[control, in[0], self.rangeMax[0], out];
};
MakeTile: PROC [sample: CARDINAL, brick: ImagerBrick.Brick] RETURNS [ImagerBrick.Brick] ~ {
box: SF.Box ~ GetExtendedBox[brick];
b: ImagerSample.SampleBuffer ~ ImagerSample.ObtainScratchSamples[SF.SizeF[box]];
sampleMap: SampleMap ~ ImagerSample.ObtainScratchMap[box];
FOR s: INTEGER IN [box.min.s..box.max.s) DO
ImagerSample.GetTileSamples[tile: brick.sampleMap, phase: brick.phase, initIndex: [s, 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: sampleMap, initIndex: [s, box.min.f], buffer: b];
ENDLOOP;
ImagerSample.ReleaseScratchSamples[b];
RETURN [[maxSample: 1, sampleMap: sampleMap, phase: brick.phase]];
};
SampledColorData: TYPE ~ REF SampledColorDataRep;
SampledColorDataRep: TYPE ~ RECORD [
pa: PixelArray,
colorOperator: ColorOperator,
separation: ATOM,
maxSample: CARDINAL,
filterDiameter: NAT,
source: ImagerPixel.PixelMap
];
me: REF TEXT ~ "Bitmap"; -- a globally unique REF for use as a clientID in the global cache.
BitmapSetColor: PROC [device: Device, color: Color, viewToDevice: Transformation] ~ {
data: Data ~ NARROW[device.data];
realRaster: BOOL ~ WITH data.bitmap SELECT FROM r: RasterSampleMap => TRUE ENDCASE => FALSE;
allowBitmaps, hardSampledCase: BOOL;
{
First clean up any scratch storage from earlier calls.
data.color ¬ NIL;
data.source ¬ NIL;
data.tile.sampleMap ¬ NIL;
IF data.scratchSampleMapThatIsInUse # NIL THEN {
ImagerSample.ReleaseScratchMap[data.scratchSampleMapThatIsInUse];
data.scratchSampleMapThatIsInUse ¬ NIL;
};
};
WITH color SELECT FROM
c: ImagerColor.OpConstantColor => {
transform: ImagerColorPrivate.ColorTransform ~ GetColorTransform[data, c.colorOperator];
point: ImagerColorPrivate.ColorPoint ¬ ImagerColorPrivate.ColorPointFromColor[c, transform];
max: REAL = transform.rangeMax[0];
f: REAL ¬ point[0];
point ¬ ImagerColorPrivate.DestroyColorPoint[point];
data.case ¬ constant;
data.function ¬ [null, null];
data.color ¬ c; -- Need to revalidate this case if the halftone screen changes.
SELECT f FROM
< 0.5 => { data.case ¬ constant; data.constant ¬ 1 };
> max-0.5 => { data.case ¬ constant; data.constant ¬ 0 };
ENDCASE => {
data.tile ¬ MakeTile[Real.Round[max-f], data.brick];
data.scratchSampleMapThatIsInUse ¬ data.tile.sampleMap;
data.case ¬ tile;
};
};
sampledColor: ImagerColor.SampledColor => {
data.color ¬ sampledColor; -- Need to revalidate this case if the halftone screen changes.
BitmapSetSampledColor[data: data, sampledColor: sampledColor, viewToDevice: viewToDevice];
};
s: ImagerColor.SampledBlack => {
pa: PixelArray ~ s.pa;
ImagerTransformation.ApplyCat[data.paToDevice, pa.m, s.um, viewToDevice];
data.function ¬ IF s.clear THEN [or, null] ELSE [null, null];
SELECT TRUE FROM
data.paToDevice.form = 3 AND data.paToDevice.integerTrans => {
This bitmap is already in the right orientation for the output raster; just use it!
min: SF.Vec ~ [s: data.paToDevice.tx, f: data.paToDevice.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];
data.case ¬ tile;
data.tile.maxSample ¬ 1;
data.tile.sampleMap ¬ sampleMap;
data.scratchSampleMapThatIsInUse ¬ sampleMap;
data.tile.phase ¬ 0;
};
newStuff AND TryTransformingBitmapIntoTile[data, pa] => {
TryTransformingBitmapIntoTile actually did the setup.
};
ENDCASE => {
This bitmap is not easy to turn into a simple tile, so we resort to the slow way.
sampleMap: SampleMap ~ ImagerSample.ObtainScratchMap[box: [max: [pa.sSize, pa.fSize]]];
ImagerPixelArray.Transfer[pa: pa, dst: sampleMap];
data.case ¬ sampledBlack;
data.source ¬ ImagerPixel.MakePixelMap[sampleMap];
data.scratchSampleMapThatIsInUse ¬ sampleMap;
};
};
s: ImagerColor.SpecialColor => {
SELECT s.type FROM
$Stipple => {
stippleData: ImagerColorPrivate.StippleData ~ NARROW[s.data];
data.function ¬ stippleData.function;
SELECT stippleData.word FROM
00000H => { data.case ¬ constant; data.constant ¬ 0 };
0FFFFH => { data.case ¬ constant; data.constant ¬ 1 };
ENDCASE => {
data.case ¬ tile;
data.tile.sampleMap ¬ data.scratchStippleMap ¬ ImagerSample.TileFromStipple[stipple: stippleData.word, scratch: data.scratchStippleMap];
data.tile.phase ¬ 0;
};
};
$BitmapTile => {
This is for specifying fancier stipples. It would be cleaner if there were a data type defined in an interface somewhere for this, but for now we use a LIST.
list: LIST OF REF ~ NARROW[s.data];
brick: REF ImagerBrick.Brick ~ NARROW[list.first];
rf: REF ImagerSample.Function ~ NARROW[list.rest.first];
data.function ¬ rf­;
data.case ¬ tile;
data.tile.sampleMap ¬ brick.sampleMap;
data.tile.phase ¬ brick.phase;
};
ENDCASE => {
IF s.substitute # NIL
THEN { [] ¬ BitmapSetColor[device, s.substitute, viewToDevice] }
ELSE { ERROR Imager.Error[[code: $unknownSpecialColor, explanation: "Unknown special color has no substitute value"]] };
};
};
ENDCASE => ERROR;
data.maskBitmapFunction ¬ [null, null];
IF data.case = constant THEN SELECT data.function FROM
[null, null] => {
SELECT data.constant FROM
0 => data.maskBitmapFunction ¬ [and, complement];
1 => data.maskBitmapFunction ¬ [or, null];
ENDCASE => NULL;
};
[xor, null] => {
IF data.constant = 1 THEN data.maskBitmapFunction ¬ [xor, null];
};
ENDCASE => NULL;
allowBitmaps ¬ (data.maskBitmapFunction # [null, null]);
hardSampledCase ¬ data.case = sampledBlack OR data.case = sampled;
device.state.allow ¬ [
unorderedBoxes: NOT hardSampledCase,
multipleCoverage: (data.function.dstFunc # xor AND NOT hardSampledCase),
regionFill: data.case = constant AND realRaster,
bitmap: allowBitmaps,
rawBitmaps: allowBitmaps AND realRaster
];
};
newStuff: BOOL ¬ TRUE; -- for testing
TryTransformingBitmapIntoTile: PROC [data: Data, pa: PixelArray] RETURNS [BOOL] ~ {
m: Transformation ~ data.paToDevice;
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+4.0 = 4.0 AND dRect.h-fSize+4.0 = 4.0 THEN {
This bitmap is not quite right for the output raster, but we can make it so.
action: PROC ~ {
pamInverse: Transformation ~ ImagerTransformation.Invert[pa.m];
Imager.TranslateT[data.scratchBitmapContext, [-dRect.x, -dRect.y]];
Imager.ConcatT[data.scratchBitmapContext, m];
Imager.ConcatT[data.scratchBitmapContext, pamInverse];
Imager.MaskPixel[data.scratchBitmapContext, 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];
IF data.scratchBitmapContext = NIL THEN data.scratchBitmapContext ¬ Create[deviceSpaceSize: SF.maxVec, scanMode: [slow: right, fast: up], surfaceUnitsPerInch: [1,1], pixelUnits: TRUE, fontCacheName: $Bitmap];
SetBitmap[data.scratchBitmapContext, sampleMap];
Imager.DoSave[data.scratchBitmapContext, action];
SetBitmap[data.scratchBitmapContext, NIL];
TRUSTED {ImagerSample.ReleaseDescriptor[sampleMap]; sampleMap ¬ NIL};
data.case ¬ tile;
data.tile.maxSample ¬ 1;
data.tile.sampleMap ¬ tileMap;
data.scratchSampleMapThatIsInUse ¬ tileMap;
data.tile.phase ¬ 0;
RETURN [TRUE];
};
};
};
RETURN [FALSE];
};
ComputeSource: PROC [sampledColor: ImagerColor.SampledColor, filterDiameter: NAT, data: Data] RETURNS [ImagerPixel.PixelMap] ~ {
colorOperator: ColorOperator ~ sampledColor.colorOperator;
pixelMap: ImagerPixel.PixelMap ~ ImagerColorPrivate.Translate[colorOperator: sampledColor.colorOperator, transform: GetColorTransform[data, sampledColor.colorOperator], pa: sampledColor.pa];
Do any pre-filtering here. Need a box-filter implementation somewhere.
RETURN [pixelMap]
};
BitmapSetSampledColor: PROC [data: Data, sampledColor: ImagerColor.SampledColor, viewToDevice: Transformation] ~ {
pa: PixelArray ~ sampledColor.pa;
largerPaSize: NAT ~ MIN[pa.sSize, pa.fSize] + 1;
cache: FunctionCache.Cache ~ FunctionCache.GlobalCache[];
filterDiameter: NAT ¬ 1; -- for low-pass filtering
scd: SampledColorData ¬ NIL;
compare: FunctionCache.CompareProc ~ {
WITH argument SELECT FROM
scd: SampledColorData => RETURN [scd.pa = pa AND scd.colorOperator = sampledColor.colorOperator AND scd.separation = data.separation AND scd.maxSample = data.brick.maxSample+1 AND scd.filterDiameter = filterDiameter];
ENDCASE => RETURN [FALSE]
};
ImagerTransformation.ApplyCat[data.paToDevice, pa.m, sampledColor.um, viewToDevice];
IF interpolate THEN filterDiameter ¬ MIN[MAX[Real.Round[1.0/MAX[ImagerTransformation.SingularValues[data.paToDevice].y, 1.0/largerPaSize]], 1], 255];
data.case ¬ sampled;
data.function ¬ [null, null];
IF pa.immutable AND data.control.table[$grayTransfer]=NIL
THEN {
scd ¬ NARROW[FunctionCache.Lookup[cache, compare, me].value];
IF scd = NIL THEN {
pixelMap: ImagerPixel.PixelMap ~ ComputeSource[sampledColor, filterDiameter, data];
scd ¬ NEW [SampledColorDataRep ¬ [pa: pa, colorOperator: sampledColor.colorOperator, separation: data.separation, maxSample: data.brick.maxSample+1, filterDiameter: filterDiameter, source: pixelMap]];
FunctionCache.Insert[x: cache, argument: scd, value: scd, size: ImagerSample.WordsForMap[size: ImagerSample.GetSize[pixelMap[0]], bitsPerSample: ImagerSample.GetBitsPerSample[pixelMap[0]]], clientID: me];
};
data.source ¬ scd.source;
}
ELSE {
data.source ¬ ComputeSource[sampledColor, filterDiameter, data];
If ComputeSource got a pixel map that had sample maps out of the scratch pool, we could do data.scratchSampleMapThatIsInUse ← data.source[0]; here.
};
};
Masking Procs
interpolate: BOOL ¬ FALSE;
BitmapMaskBoxes: PROC [device: Device, bounds: SF.Box, boxes: SF.BoxGenerator] ~ {
data: Data ~ NARROW[device.data];
SELECT data.case FROM
constant => {
ImagerSample.FillBoxes[map: data.bitmap, boxes: boxes, value: data.constant, function: data.function];
};
tile => {
ImagerSample.TileBoxes[map: data.bitmap, boxes: boxes, tile: data.tile.sampleMap, phase: data.tile.phase, function: data.function];
};
sampled => 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: data.brick.sampleMap, initIndex: min, buffer: buffer, phase: data.brick.phase];
ImagerSample.Halftone[map: data.bitmap, min: min, sampleBuffer: pixels[0], thresholdBuffer: buffer, function: data.function];
};
ImagerPixel.Resample[self: data.source, m: data.paToDevice, interpolate: interpolate, boxes: boxes, bounds: bounds, action: Action];
ImagerSample.ReleaseScratchSamples[buffer];
};
sampledBlack => {
Action: PROC [pixels: ImagerPixel.PixelBuffer, min: SF.Vec] ~ {
ImagerSample.PutSamples[map: data.bitmap, initIndex: min, buffer: pixels[0], start: 0, count: pixels.length, function: data.function];
};
ImagerPixel.Resample[self: data.source, m: data.paToDevice, interpolate: FALSE, boxes: boxes, bounds: bounds, action: Action];
};
ENDCASE => ERROR;
};
BitmapMaskRegion: PROC [device: Device, bounds: SF.Box, edgeGenerator: PROC [ImagerSample.EdgeAction]] ~ {
data: Data ~ NARROW[device.data];
IF data.case # constant THEN ERROR;
ImagerSample.RegionFill[dst: NARROW[data.bitmap], edgeGenerator: edgeGenerator, value: data.constant, function: data.function];
};
BitmapMaskBitmap: PROC [device: Device, bitmap: SampleMap, delta: SF.Vec, bounds: SF.Box, boxes: SF.BoxGenerator] ~ {
data: Data ~ NARROW[device.data];
ImagerSample.TransferBoxes[dst: data.bitmap, src: bitmap, delta: delta, boxes: boxes, function: data.maskBitmapFunction];
};
BitmapMaskRawBitmaps: PROC [device: Device, list: LIST OF ImagerSample.RawDescriptor] ~ {
data: Data ~ NARROW[device.data];
ImagerSample.RawListTransfer[dst: NARROW[data.bitmap], src: list, function: data.maskBitmapFunction];
};
BitmapDrawBitmap: PROC [device: Device, bitmap: SampleMap, delta: SF.Vec, bounds: SF.Box, boxes: SF.BoxGenerator] ~ {
data: Data ~ NARROW[device.data];
ImagerSample.TransferBoxes[dst: data.bitmap, src: bitmap, delta: delta, boxes: boxes];
};
BitmapMoveBox: PROC [device: Device, dstMin, srcMin, size: SF.Vec] ~ {
data: Data ~ NARROW[device.data];
ImagerSample.Move[map: data.bitmap, dstMin: dstMin, srcMin: srcMin, size: size];
};
BitmapSwitchBuffer: PROC [device: Device, bounds: SF.Box, copy: BOOL, alt: BOOL] ~ {
data: Data ~ NARROW[device.data];
oldBuffer: SampleMap ~ data.bitmap;
newBuffer: SampleMap ~ IF alt THEN ImagerSample.ObtainScratchMap[box: bounds, bitsPerSample: 1] ELSE data.savedBuffer;
IF copy THEN ImagerSample.Transfer[dst: newBuffer, src: oldBuffer];
data.bitmap ¬ newBuffer;
IF alt
THEN { data.savedBuffer ¬ oldBuffer }
ELSE { data.savedBuffer ¬ NIL; ImagerSample.ReleaseScratchMap[oldBuffer] };
};
Objects
ObjectTable: TYPE = REF ObjectTableObj;
ObjectTableObj: TYPE = RECORD [
table: RefTab.Ref,
entries: NAT,
totalWords: CARD32,
garbageIndex: NAT ¬ 0
];
objectTable: ObjectTable ¬ NEW[ObjectTableObj ¬ [
table: RefTab.Create[mod: 101],
entries: 0,
totalWords: 0
]];
AllBits: TYPE = REF AllBitsObj;
AllBitsObj: TYPE = RECORD [
list: LIST OF ObjectCacheData
];
<<objectCache: FunctionCache.Cache ¬ FunctionCache.Create[maxEntries: 300, maxTotalSize: 250000]; -- another index on the same cached data. This data structure keeps track of total storage use, and suggests which Imager.Objects to toss out.>>
ObjectCacheData: TYPE ~ REF ObjectCacheDataRep;
ObjectCacheDataRep: TYPE ~ RECORD [
object: Object,
clientToDevice: Transformation,
size: CARD32 ¬ 10000,
v: SELECT tag: * FROM
notCached => [],
bitmap => [mask: SampleMap, shadingBits: SampleMap],
boxes => [boxes: ManhattanPolygon],
ENDCASE
];
maxBitsForCachedObject: REAL ~ 262144; -- reduced from 2000000 because allocations took too long, Bier
maxObjects: NAT ¬ 500;
maxWordsOfStorage: CARD32 ¬ 250000;
FindBitsAtTransformation: ENTRY PROC [objectTable: ObjectTable, object: Object, clientToDevice: Transformation] RETURNS [objectCacheData: ObjectCacheData] = {
OPEN ImagerTransformation; -- see CloseToTranslation
ENABLE UNWIND => NULL;
val: RefTab.Val;
allBits: AllBits;
found: BOOL ¬ FALSE;
[found, val] ¬ RefTab.Fetch[objectTable.table, object];
IF NOT found THEN RETURN[NIL];
allBits ¬ NARROW[val];
FOR list: LIST OF ObjectCacheData ¬ allBits.list, list.rest UNTIL list = NIL DO
IF CloseToTranslation[list.first.clientToDevice, clientToDevice] THEN RETURN[list.first];
ENDLOOP;
objectCacheData ¬ NIL;
};
InsertBitsAtTransformation: ENTRY PROC [objectTable: ObjectTable, object: Object, objectCacheData: ObjectCacheData] = {
OPEN ImagerTransformation; -- see CloseToTranslation
ENABLE UNWIND => NULL;
val: RefTab.Val;
allBits: AllBits;
found: BOOL ¬ FALSE;
[found, val] ¬ RefTab.Fetch[objectTable.table, object];
IF NOT found THEN {
allBits ¬ NEW[AllBitsObj ¬ [list: LIST[objectCacheData]]];
}
ELSE {
allBits ¬ NARROW[val];
allBits.list ¬ CONS[objectCacheData, allBits.list];
};
[] ¬ RefTab.Store[objectTable.table, object, allBits];
objectTable.entries ¬ objectTable.entries + 1;
objectTable.totalWords ¬ objectTable.totalWords + objectCacheData.size;
IF objectTable.entries > maxObjects OR objectTable.totalWords > maxWordsOfStorage
THEN EnforceLimits[objectTable];
};
EnforceLimits: INTERNAL PROC [objectTable: ObjectTable] = {
TrimBitsAfterIndex: RefTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOLFALSE];
allBits: AllBits ¬ NARROW[val];
limitsDone: BOOL ¬ FALSE;
IF index < objectTable.garbageIndex THEN {
index ¬ index + 1;
RETURN[FALSE];
};
objectTable.garbageIndex ¬ objectTable.garbageIndex + 1;
UNTIL allBits.list = NIL DO
objectTable.entries ¬ objectTable.entries - 1;
objectTable.totalWords ¬ objectTable.totalWords - allBits.list.first.size;
allBits.list ¬ allBits.list.rest;
limitsDone ¬ objectTable.entries <= maxObjects AND objectTable.totalWords <= maxWordsOfStorage;
IF allBits.list = NIL THEN cleanupNILs ¬ CONS[key, cleanupNILs];
IF limitsDone THEN RETURN[TRUE];
ENDLOOP;
Otherwise continue on to the next Imager.Object.
};
TrimBits: RefTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOLFALSE];
allBits: AllBits ¬ NARROW[val];
limitsDone: BOOL ¬ FALSE;
UNTIL allBits.list = NIL DO
objectTable.entries ¬ objectTable.entries - 1;
objectTable.totalWords ¬ objectTable.totalWords - allBits.list.first.size;
allBits.list ¬ allBits.list.rest;
limitsDone ¬ objectTable.entries <= maxObjects AND objectTable.totalWords <= maxWordsOfStorage;
IF allBits.list = NIL THEN cleanupNILs ¬ CONS[key, cleanupNILs];
IF limitsDone THEN RETURN[TRUE];
ENDLOOP;
Otherwise continue on to the next Imager.Object.
};
index: NAT ¬ 0;
done: BOOL ¬ FALSE;
cleanupNILs: LIST OF RefTab.Key ¬ NIL;
done ¬ RefTab.Pairs[objectTable.table, TrimBitsAfterIndex];
IF NOT done THEN [] ¬ RefTab.Pairs[objectTable.table, TrimBits];
FOR list: LIST OF RefTab.Key ¬ cleanupNILs, list.rest UNTIL list = NIL DO
deleted: BOOL ¬ RefTab.Delete[objectTable.table, list.first];
IF NOT deleted THEN ERROR;
ENDLOOP;
IF objectTable.garbageIndex > RefTab.GetSize[objectTable.table] THEN objectTable.garbageIndex ¬ 0;
};
RemoveBitsAtTransformation: INTERNAL PROC [objectTable: RefTab.Ref, objectCacheData: ObjectCacheData] = {
OPEN ImagerTransformation; -- see CloseToTranslation
val: RefTab.Val;
allBits: LIST OF ObjectCacheData;
found: BOOL ¬ FALSE;
[found, val] ¬ RefTab.Fetch[objectTable, objectCacheData.object];
IF found THEN {
allBits ¬ NARROW[val];
allBits ¬ DeleteBitsFromList[objectCacheData, allBits];
[] ¬ RefTab.Store[objectTable, objectCacheData.object, allBits];
};
};
DeleteBitsFromList: INTERNAL PROC [entity: ObjectCacheData, entityList: LIST OF ObjectCacheData] RETURNS [smallerList: LIST OF ObjectCacheData] = {
beforeEnt, ent, afterEnt: LIST OF ObjectCacheData;
notFound: BOOL ¬ FALSE;
[beforeEnt, ent, afterEnt, notFound] ¬ FindBitsAndNeighbors[entity, entityList];
IF notFound THEN RETURN[entityList];
IF beforeEnt = NIL THEN smallerList ¬ afterEnt
ELSE {
beforeEnt.rest ¬ afterEnt;
smallerList ¬ entityList;
};
}; -- end of DeleteBitsFromList
FindBitsAndNeighbors: INTERNAL PROC [entity: ObjectCacheData, entityList: LIST OF ObjectCacheData] RETURNS [beforeEnt, ent, afterEnt: LIST OF ObjectCacheData, notFound: BOOL ¬ FALSE] = {
lastE: LIST OF ObjectCacheData ¬ NIL;
eList: LIST OF ObjectCacheData ¬ entityList;
IF eList = NIL THEN RETURN[NIL, NIL, NIL, TRUE];
UNTIL eList = NIL DO
IF eList.first = entity THEN {
beforeEnt ¬ lastE; ent ¬ eList; afterEnt ¬ eList.rest; RETURN};
lastE ¬ eList;
eList ¬ eList.rest;
ENDLOOP;
RETURN[NIL, NIL, NIL, TRUE];
};
BitmapDrawObjectMultiColor: PROC [context: Context, object: Object, position: VEC, interactive: BOOL] ~ {
This caches an Imager Object as two bitmaps. The first bitmap, a mask, describes those bits that should be cleared to white so the Imager Object will hide those objects that are under it. The second bitmap will only have black bits inside the mask region described by the first bitmap. It describes the stipple patterns needed to color in the regions of the Imager Object.
device: Device = ImagerRaster.GetDevice[context];
data: Data = NARROW[device.data];
contextBox, intersection: ImagerBox.Box;
objectRect: Rectangle;
contextBox ¬ ImagerBox.BoxFromRectangle[ImagerBackdoor.GetBounds[context]];
objectRect ¬ [x: position.x+object.clip.x, y: position.y+object.clip.y, w: object.clip.w, h: object.clip.h]; -- in client coordinates
intersection ¬ ImagerBox.IntersectBox[contextBox, ImagerBox.BoxFromRectangle[objectRect]];
IF intersection.xmax=intersection.xmin THEN RETURN; -- not visible
IF interactive AND data.control.table[$grayTransfer] = NIL THEN {
We are not printing, so try to use cached bitmaps if one is available that is not too far off.
We use these steps to find out if the cache already contains a suitable bitmap:
(1) If the object is too large (too many bits would be needed to cache it), it is not present
(2) If the Imager.Object is already in the cache, at approximately the current rotation and size, then we can use its cached bitmap without any further work.
clientToDevice: Transformation ~
ImagerBackdoor.GetTransformation[context: context, from: client, to: device];
bounds: Rectangle ~ ImagerTransformation.TransformRectangle[clientToDevice, objectRect];
bounds is in device coordinates.
IF ABS[bounds.w*bounds.h] <= maxBitsForCachedObject THEN {
objectCacheData: ObjectCacheData ¬
FindBitsAtTransformation[objectTable, object, clientToDevice];
IF objectCacheData = NIL THEN { -- no such Imager.Object in cache. Try to add one
ClipAndDraw: PROC [c: Context] ~ {
Imager.ClipRectangle[c, object.clip];
object.draw[object, c];
};
bitmap: SampleMap ¬ NIL;
size: INT ¬ 0;
bitmap ¬ ImagerMaskCapture.CaptureBitmap[ClipAndDraw, clientToDevice, FALSE]; -- build a mask
BEGIN -- store the mask in the representation
o: REF ObjectCacheDataRep.bitmap ¬ NEW[ObjectCacheDataRep.bitmap];
movedBox: SF.Box ¬ SF.Displace[ImagerSample.GetBox[bitmap], [viewOrigin, viewOrigin]];
movedBox: SF.Box ← bitmap.box;
objectCacheData ¬ o;
o.mask ¬ bitmap;
o.shadingBits ¬ RenderOntoWhite[ClipAndDraw, clientToDevice, movedBox];
size ¬ ImagerSample.WordsForMap[ImagerSample.GetSize[bitmap]] + 10;
END;
objectCacheData.object ¬ object;
objectCacheData.clientToDevice ¬ ImagerTransformation.Copy[clientToDevice];
objectCacheData.size ¬ size;
InsertBitsAtTransformation[objectTable, object, objectCacheData];
};
At this point, there is an object in the cache.
WITH objectCacheData SELECT FROM
ocd: REF ObjectCacheDataRep.bitmap => { -- the fast case
t1: VEC ~ ImagerTransformation.TransformVec[clientToDevice, position];
t2: VEC ~ [clientToDevice.c, clientToDevice.f];
t3: VEC ~ [ocd.clientToDevice.c, ocd.clientToDevice.f];
t: VEC ¬ Vector2.Add[t1, Vector2.Sub[t2, t3]];
save: PROC ~ {
m: Transformation ¬ ImagerBackdoor.GetTransformation[context: context, from: device, to: view];
ImagerBackdoor.SetT[context, m]; -- work in device coordinates here
ImagerTransformation.Destroy[m]; m ¬ NIL;
Imager.SetColor[context, Imager.white];
Imager.MaskBitmap[context: context, bitmap: ocd.mask, scanMode: [slow: right, fast: up], position: t];
Imager.SetColor[context, Imager.black];
Imager.MaskBitmap[context: context, bitmap: ocd.shadingBits, scanMode: [slow: right, fast: up], position: Vector2.Sub[t, [viewOrigin, viewOrigin]]];
};
Imager.DoSave[context, save];
ImagerTransformation.Destroy[clientToDevice];
RETURN;
};
ENDCASE => NULL;
}; -- use the slow case
ImagerTransformation.Destroy[clientToDevice];
}; -- end of interactive case
ImagerPrivate.DefaultDrawObject[context, object, position, interactive];
};
viewOrigin: NAT ~ 10000;
RenderOntoWhite: PROC [operator: PROC [Context], clientToDevice: Transformation, box: SF.Box] RETURNS [shadingBits: SampleMap] = {
context: Imager.Context;
proc: PROC ~ {
Imager.TranslateT[context, [viewOrigin, viewOrigin]];
Imager.ConcatT[context, clientToDevice];
operator[context];
};
shadingBits ¬ ImagerSample.NewSampleMap[box];
ImagerSample.Clear[shadingBits];
context ¬ Create[deviceSpaceSize: [INTEGER.LAST, INTEGER.LAST], scanMode: [right, up],
surfaceUnitsPerInch: [1,1], pixelUnits: TRUE, fontCacheName: ImagerBitmapContext.classCode];
The view is translated relative to the device, so that shapes don't get clipped in the regions of negative x and y in device coordinates.
Imager.SetXY[context, [0, 0]];
SetBitmap[context, shadingBits];
Imager.DoSaveAll[context, proc];
};
<<BitmapDrawObject: PROC [context: Context, object: Object, position: VEC, interactive: BOOL] ~ {
contextBox: ImagerBox.Box ~ ImagerBox.BoxFromRectangle[ImagerBackdoor.GetBounds[context]];
objectRect: Rectangle ~ [x: position.x+object.clip.x, y: position.y+object.clip.y, w: object.clip.w, h: object.clip.h];
intersection: ImagerBox.Box ~ ImagerBox.IntersectBox[contextBox, ImagerBox.BoxFromRectangle[objectRect]];
IF intersection.xmax=intersection.xmin THEN RETURN; -- not visible
IF interactive THEN {
clientToDevice: Transformation ~ ImagerBackdoor.GetTransformation[context: context, from: client, to: device];
Compare: PROC [argument: REF] RETURNS [BOOL] ~ {
key: ObjectCacheData ~ NARROW[argument];
IF object # key.object THEN RETURN [FALSE];
RETURN [ImagerTransformation.CloseToTranslation[key.clientToDevice, clientToDevice]];
};
bounds: Rectangle ~ ImagerTransformation.TransformRectangle[clientToDevice, objectRect];
IF ABS[bounds.w*bounds.h] <= maxBitsForCachedObject THEN {
objectCacheData: ObjectCacheData ¬ NARROW [FunctionCache.Lookup[x: objectCache, compare: Compare, clientID: NIL].value];
IF objectCacheData = NIL THEN {
bitmap: SampleMap ¬ NIL;
size: INT ¬ 0;
ok: BOOL ¬ TRUE;
Op: PROC [c: Context] ~ { Imager.ClipRectangle[c, object.clip]; object.draw[object, c] };
bitmap ¬ ImagerMaskCapture.CaptureBitmap[Op, clientToDevice, TRUE !
ImagerMaskCapture.Cant => {ok ¬ FALSE; CONTINUE}];
IF ok
THEN {
o: REF ObjectCacheDataRep.bitmap ¬ NEW[ObjectCacheDataRep.bitmap];
objectCacheData ¬ o;
o.bitmap ¬ bitmap;
size ¬ ImagerSample.WordsForMap[ImagerSample.GetSize[bitmap]] + 10;
}
ELSE {
objectCacheData ¬ NEW[ObjectCacheDataRep.notCached];
size ¬ SIZE[ObjectCacheDataRep.notCached];
};
objectCacheData.object ¬ object;
objectCacheData.clientToDevice ¬ ImagerTransformation.Copy[clientToDevice];
FunctionCache.Insert[x: objectCache, argument: objectCacheData, value: objectCacheData, size: size, clientID: NIL];
};
WITH objectCacheData SELECT FROM
ocd: REF ObjectCacheDataRep.bitmap => {
t1: VEC ~ ImagerTransformation.TransformVec[clientToDevice, position];
t2: VEC ~ [clientToDevice.c, clientToDevice.f];
t3: VEC ~ [ocd.clientToDevice.c, ocd.clientToDevice.f];
t: VEC ¬ Vector2.Add[t1, Vector2.Sub[t2, t3]];
save: PROC ~ {
m: Transformation ~ ImagerBackdoor.GetTransformation[context: context, from: device, to: view];
ImagerBackdoor.SetT[context, m];
Imager.MaskBitmap[context: context, bitmap: ocd.bitmap, scanMode: [slow: right, fast: up], position: t];
ImagerTransformation.Destroy[m];
};
Imager.DoSave[context, save];
ImagerTransformation.Destroy[clientToDevice];
RETURN;
};
ENDCASE => NULL;
};
ImagerTransformation.Destroy[clientToDevice];
};
ImagerPrivate.DefaultDrawObject[context, object, position, interactive];
};
>>
BitmapGetBufferColorOperator: PROC [device: Device] RETURNS [ColorOperator] ~ {
RETURN [ImagerColor.NewColorOperatorGrayLinear[sWhite: 0, sBlack: 1]]
};
BitmapAccessBuffer: PROC [device: Device, box: SF.Box, action: PROC [pixelMap: PixelMap]] ~ {
data: Data ~ NARROW[device.data];
bitmap: SampleMap ~ ImagerSample.Clip[data.bitmap, box];
pixelMap: PixelMap ~ IF data.scratchPixelMap = NIL THEN NEW[ImagerPixel.PixelMapRep[1]] ELSE data.scratchPixelMap;
data.scratchPixelMap ¬ NIL;
pixelMap.box ¬ ImagerSample.GetBox[bitmap];
pixelMap[0] ¬ bitmap;
action[pixelMap];
pixelMap[0] ¬ NIL;
TRUSTED {ImagerSample.ReleaseDescriptor[bitmap]};
data.scratchPixelMap ¬ pixelMap;
};
Classes
deviceClass: ImagerDevice.DeviceClass ~ NEW[ImagerDevice.DeviceClassRep ¬ [
SetColor: BitmapSetColor,
SetPriority: NIL,
SetHalftoneProperties: BitmapSetHalftoneProperties,
MaskBoxes: BitmapMaskBoxes,
MaskRegion: BitmapMaskRegion,
MaskBitmap: BitmapMaskBitmap,
MaskRawBitmaps: BitmapMaskRawBitmaps,
DrawBitmap: BitmapDrawBitmap,
MaskChar: NIL,
MoveBox: BitmapMoveBox,
SwitchBuffer: BitmapSwitchBuffer,
GetBufferColorOperator: BitmapGetBufferColorOperator,
AccessBuffer: BitmapAccessBuffer
]];
contextClass: ImagerPrivate.Class ~ CreateClass[];
CreateClass: PROC RETURNS [class: ImagerPrivate.Class] ~ INLINE {
class ¬ ImagerRaster.CreateClass[type: classCode];
class.DrawObject ¬ BitmapDrawObjectMultiColor;
};
ImagerMaskCache.SetNamedCacheParameters[$Bitmap, [runsCost: [slope: 2, offset: 3]]];
END.
Bier, March 3, 1989 2:48:25 pm PST
Commented out BitmapDrawObject and replaced it with BitmapDrawObjectMultiColor, which can handle Imager.Objects that have stipple patterns and Imager.Objects that contain color changes.
changes to: BitmapDrawObjectMultiColor, CreateClass, ObjectCacheDataRep (renamed "bitmap" field to "mask", and added "shadingBits" field