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];
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;
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
];
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: BOOL ← FALSE];
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: BOOL ← FALSE];
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;
};