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