ImagerLFImpl.mesa
Michael Plass, October 31, 1983 11:18 am
Doug Wyatt, October 11, 1983 3:53 pm
DIRECTORY
Environment USING [bitsPerWord],
Font USING [FONT],
Imager,
ImagerBasic USING [Pair, IntPair, Rectangle, IntRectangle, DeviceRectangle, TransformType, Transformation, Visibility, PathMapType, StrokeEnd, Color, ColorRep, ConstantColor, SampledColor, PixelArray, PixelArrayRep, PixelBuffer, PixelBufferRep],
ImagerConic,
ImagerDefault,
ImagerFontCache,
ImagerFrameBuffer USING [LFDisplay],
ImagerHalftone,
ImagerMasks,
ImagerPixelMaps,
ImagerPrivate,
ImagerScanConverter,
ImagerStroke,
ImagerTransform,
Real USING [RoundLI],
Scaled,
TerminalMultiplex USING [SelectTerminal],
UserTerminal USING [GetState, SetState]
;
ImagerLFImpl: CEDAR MONITOR
IMPORTS Imager, ImagerConic, ImagerDefault, ImagerFontCache, ImagerFrameBuffer, ImagerHalftone, ImagerMasks, ImagerPixelMaps, ImagerPrivate, ImagerScanConverter, ImagerStroke, ImagerTransform, Real, Scaled, TerminalMultiplex, UserTerminal
~ BEGIN OPEN ImagerBasic;
StatsRecord: TYPE ~ RECORD[
loadRunGroups, loadRasters, runGroupChars, rasterChars, clippedChars, culledChars: INT ← 0];
stats: StatsRecord ← [];
Stats: PROC RETURNS [StatsRecord] ~ { x: StatsRecord ~ stats; stats ← []; RETURN[x] };
FONT: TYPE ~ Font.FONT;
Mask: TYPE ~ ImagerMasks.Mask;
PixelMap: TYPE ~ ImagerPixelMaps.PixelMap;
Tile: TYPE ~ ImagerPixelMaps.Tile;
SpecialColor: TYPE ~ REF ColorRep[special];
Name: TYPE ~ ImagerPrivate.Name;
Context: TYPE ~ Imager.Context;
State: TYPE ~ ImagerDefault.State;
Data: TYPE ~ REF DataRep;
DataRep: TYPE ~ RECORD [
Now in context.state:
currentColor: Color,
currentPosition: Pair, -- in device space
transformation: Transformation,
surfaceToDevice: Transformation,
doSaveCount: [0..1000) ← 0,
Clipper info:
clientClipper: Mask, -- in device coords
clientClipperPresent: BOOLEAN,
clientClipperExclude: BOOLEAN,
viewClipper: Mask, -- in device coords
compositeClipperValid: BOOLEAN,
compositeClipper: Mask, -- in device coords
View-to-device transformation info
sOriginView, fOriginView: INTEGER,
sOriginBrick, fOriginBrick: INTEGER,
Scratch storage
devicePath: ImagerScanConverter.DevicePath,
lineBuffer: Mask,
tileSamples: SampledColor,
Cached color info
tile: Tile,
The bitmap
canvas: PixelMap
];
transformOfType: ARRAY TransformType OF Transformation ~ [
none   : [ 0, 0, 0, 0, 0, 0, none],
identity  : [ 1, 0, 0, 0, 1, 0, identity],
rot90   : [ 0, -1, 0, 1, 0, 0, rot90],
rot180   : [-1, 0, 0, 0, -1, 0, rot180],
rot270   : [ 0, 1, 0, -1, 0, 0, rot270],
mirrorX  : [-1, 0, 0, 0, 1, 0, mirrorX],
mirrorY  : [ 1, 0, 0, 0, -1, 0, mirrorY],
mirror45Deg : [ 0, 1, 0, 1, 0, 0, mirror45Deg],
mirror135Deg : [ 0, -1, 0, -1, 0, 0, mirror135Deg],
hard   : [3, 1, 4, 1, 5, 9, hard]
];
rot90Type: ARRAY TransformType OF TransformType ~ PrecomputeRot90Type[];
PrecomputeRot90Type: PROC RETURNS [t: ARRAY TransformType OF TransformType] ~ {
rot90: Transformation ~ transformOfType[rot90];
FOR transformType: TransformType IN TransformType DO
t[transformType] ←
IF transformType = none THEN none
ELSE ImagerTransform.Concat[transformOfType[transformType], rot90].type
ENDLOOP;
};
CompositeT: PROC [data: Data, state: State] RETURNS [Transformation] ~ {
concatenates T with the current view-to-device transformation
RETURN [[
a: -state.nps.T.d, d: state.nps.T.a,
b: -state.nps.T.e, e: state.nps.T.b,
c: -state.nps.T.f+data.sOriginView,
f: state.nps.T.c+data.fOriginView,
type: rot90Type[state.nps.T.type]
]]
};
SurfaceToDevice: PROC [data: Data] RETURNS [Transformation] ~ {
RETURN [[
a: 0, d: 1,
b: -1, e: 0,
c: data.canvas.sSize,
f: 0,
type: rot90
]]
};
DeviceCP: TYPE ~ RECORD [s, f: Scaled.Value];
GetDeviceCP: PROC [data: Data, state: State] RETURNS [DeviceCP] ~ {
RETURN [[
s: ScaledFromReal[-state.cpy].PLUS[Scaled.FromInt[data.sOriginView]],
f: ScaledFromReal[state.cpx].PLUS[Scaled.FromInt[data.fOriginView]]
]]
};
SetDeviceCP: PROC [data: Data, state: State, cp: DeviceCP] ~ {
state.cpy ← -Scaled.Float[cp.s.MINUS[Scaled.FromInt[data.sOriginView]]];
state.cpx ← Scaled.Float[cp.f.MINUS[Scaled.FromInt[data.fOriginView]]];
};
LFBrick: ImagerHalftone.DeviceBrick ← ImagerHalftone.MakeSquareBrick[4, 3, 2, 1, 1, 255];
Init: PROC [context: Context, info: REF] ~ {
data: Data ~ NEW[DataRep];
context.data ← data;
data.tile ← ImagerPixelMaps.CreateTile[
pixelMap: ImagerPixelMaps.Create[lgBitsPerPixel: 0, bounds: [0, 0, 16, 16]]];
data.tileSamples ← MakeTileSamples[16, 16];
data.devicePath ← NIL;
WITH info SELECT FROM
pm: REF PixelMap => data.canvas ← pm^;
ENDCASE => TRUSTED {data.canvas ← ImagerFrameBuffer.LFDisplay[]};
ImagerDefault.InitState[context];
Reset[context];
};
pixelsPerInch: REAL ~ 72.0;
mmPerInch: REAL ~ 25.4;
inchesPerMeter: REAL ~ 1000.0/mmPerInch;
pixelsPerMeter: REAL ← pixelsPerInch*inchesPerMeter;
Reset: PROC [context: Context] ~ {
data: Data ~ NARROW[context.data];
state: State ~ NARROW[context.state];
ImagerDefault.Reset[context];
ImagerDefault.ScaleT[context, pixelsPerMeter];
state.nps.fieldXMax ← state.nps.mediumXSize ← data.canvas.fSize/pixelsPerMeter;
state.nps.fieldYMax ← state.nps.mediumYSize ← data.canvas.sSize/pixelsPerMeter;
data.clientClipperPresent ← data.compositeClipperValid ← FALSE;
data.viewClipper ← ImagerMasks.Box[
[sMin: 0, fMin: 0, sSize: data.canvas.sSize, fSize: data.canvas.fSize]];
data.sOriginView ← data.sOriginBrick ← data.canvas.sSize;
data.fOriginView ← data.fOriginBrick ← 0;
SetColor[context, Imager.black];
};
MakeTileSamples: PROC [xSize, ySize: NAT] RETURNS [tileSamples: SampledColor] ~ {
tileSamples ← NEW[ColorRep[sampled]];
tileSamples.transparent ← FALSE;
tileSamples.pa ← NEW[PixelArrayRep];
tileSamples.m ← ImagerTransform.Rotate[0];
tileSamples.colorOperator ← $Intensity8bpp;
tileSamples.pa.xPixels ← xSize;
tileSamples.pa.yPixels ← ySize;
tileSamples.pa.maxSampleValue ← 255;
tileSamples.pa.samplesPerPixel ← 1;
tileSamples.pa.get ← ConstantGet;
tileSamples.pa.data ← NEW[NAT ← 0];
};
ConstantGet: PROC [self: PixelArray, buffer: PixelBuffer, nSamples: NAT, layer: INT, xStart, yStart: Scaled.Value, xDelta, yDelta: Scaled.Value] ~ {
WITH self.data SELECT FROM
n: REF NAT => {
value: NAT ← n^;
FOR i: NAT IN [0..nSamples) DO buffer[i] ← value ENDLOOP;
};
ENDCASE => ERROR;
};
SetTileSamples: PROC [tileSamples: SampledColor, intensity: [0..255]] ~ {
WITH tileSamples.pa.data SELECT FROM
n: REF NAT => n^ ← intensity;
ENDCASE => ERROR;
};
DoSaveAll: PROC [context: Context, body: PROC] ~ {
data: Data ← NARROW[context.data];
state: State ← NARROW[context.state];
stateRep: ImagerDefault.StateRep ← state^;
clipper: Clipper ← GetClipper[context];
sOriginView: INTEGER ← data.sOriginView;
fOriginView: INTEGER ← data.fOriginView;
sOriginBrick: INTEGER ← data.sOriginBrick;
fOriginBrick: INTEGER ← data.fOriginBrick;
Restore: PROC ~ {
data.doSaveCount ← data.doSaveCount - 1;
data.sOriginView ← sOriginView;
data.fOriginView ← fOriginView;
data.sOriginBrick ← sOriginBrick;
data.fOriginBrick ← fOriginBrick;
IF stateRep.nps.color # state.nps.color THEN SetColor[context, stateRep.nps.color];
SetClipper[context, clipper];
state^ ← stateRep;
};
data.doSaveCount ← data.doSaveCount + 1;
body[! UNWIND => Restore[]];
Restore[];
};
DoSave: PROC [context: Context, body: PROC] ~ {
data: Data ← NARROW[context.data];
state: State ← NARROW[context.state];
stateNPS: ImagerDefault.NonPersistentState ← state.nps;
clipper: Clipper ← GetClipper[context];
sOriginView: INTEGER ← data.sOriginView;
fOriginView: INTEGER ← data.fOriginView;
sOriginBrick: INTEGER ← data.sOriginBrick;
fOriginBrick: INTEGER ← data.fOriginBrick;
Restore: PROC ~ {
data.doSaveCount ← data.doSaveCount - 1;
data.sOriginView ← sOriginView;
data.fOriginView ← fOriginView;
data.sOriginBrick ← sOriginBrick;
data.fOriginBrick ← fOriginBrick;
IF stateNPS.color # state.nps.color THEN SetColor[context, stateNPS.color];
SetClipper[context, clipper];
state.nps ← stateNPS;
};
data.doSaveCount ← data.doSaveCount + 1;
body[! UNWIND => Restore[]];
Restore[];
};
Clipper: TYPE ~ REF ClipperRep;
ClipperRep: TYPE ~ RECORD [
exclude: BOOLEAN,
mask: Mask
];
ccCount: INT ← 0;
GetClipper: PROC [context: Context] RETURNS [Clipper] ~ {
data: Data ~ NARROW[context.data];
IF data.clientClipperPresent THEN {
clipper: Clipper ~ NEW[ClipperRep];
clipper.exclude ← data.clientClipperExclude;
clipper.mask ← data.clientClipper.InlineShift[-data.sOriginView, -data.fOriginView];
ccCount ← ccCount + 1;
RETURN [clipper]
}
ELSE RETURN [NIL]
};
SetClipper: PROC [context: Context, clipper: Clipper] ~ {
data: Data ~ NARROW[context.data];
IF clipper = NIL THEN {
data.clientClipperPresent ← FALSE;
data.clientClipper.refRep ← NIL;
ccCount ← ccCount + 1;
}
ELSE {
data.clientClipperPresent ← TRUE;
data.clientClipperExclude ← clipper.exclude;
data.clientClipper ← clipper.mask.Shift[data.sOriginView, data.fOriginView];
ccCount ← ccCount + 1;
};
data.compositeClipperValid ← FALSE;
};
IGet: PROC [context: Context, n: Name] RETURNS [REF] ~ {
IF n = $clipOutline THEN RETURN [GetClipper[context]]
ELSE RETURN [ImagerDefault.IGet[context, n]]
};
ISet: PROC [context: Context, n: Name, x: REF] ~ {
SELECT n FROM
$color => {
SetColor[context, NARROW[x]];
};
$clipOutline => {
SetClipper[context, NARROW[x]];
RETURN;
};
ENDCASE => NULL;
ImagerDefault.ISet[context, n, x];
};
MaskFromPath: PROC [context: Context, pathMap: PathMapType, pathData: REF]
RETURNS [mask: Mask] ~ {
data: Data ~ NARROW[context.data];
state: State ~ NARROW[context.state];
GenPath: ImagerScanConverter.PathProc
move: PROC [s, f: REAL],
line: PROC [s, f: REAL],
curve: PROC [s1, f1, s2, f2, s3, f3: REAL]
~ {
m: Transformation ~ CompositeT[data, state];
Xform: PROC [p: Pair] RETURNS [Pair] ~ {RETURN[[
Transforms (x, y) to (s, f) — note s comes first for ScanConvert!
m.a * p.x + m.b * p.y + m.c,
m.d * p.x + m.e * p.y + m.f
]]};
Xmove: PROC [p: Pair] ~ { q: Pair ~ Xform[p]; move[q.x, q.y]; lp ← p };
Xline: PROC [p: Pair] ~ { q: Pair ~ Xform[p]; line[q.x, q.y]; lp ← p };
Xcurve: PROC [p1, p2, p3: Pair] ~ {
q1: Pair ~ Xform[p1]; q2: Pair ~ Xform[p2]; q3: Pair ~ Xform[p3];
curve[q1.x, q1.y, q2.x, q2.y, q3.x, q3.y]; lp ← p3 };
Curve: PROC [p1, p2, p3: Pair] ~ {curve[p1.x, p1.y, p1.x, p1.y, p1.x, p1.y]};
Xconic: PROC [p1, p2: Pair, r: REAL] ~ {
q1: Pair ~ Xform[p1]; q2: Pair ~ Xform[p2];
ImagerConic.ToCurves[lp, q1, q2, r, Curve];
lp ← p2;
};
lp: Pair;
pathMap[pathData, Xmove, Xline, Xcurve, Xconic];
};
Runs: PROC [
run: PROC [sMin, fMin: INTEGER, fSize: NAT],
repeat: PROC [timesToRepeatScanline: NAT]] ~ {
data.devicePath.ConvertToRuns[runProc: run];
};
data.devicePath ← ImagerScanConverter.CreatePath[pathProc: GenPath, scratch: data.devicePath];
mask ← ImagerMasks.Create[Runs];
};
MaskFromStroke: PROC [context: Context, pathMap: PathMapType, pathData: REF,
strokeWidth: REAL, strokeEnd: StrokeEnd, closed: BOOL]
RETURNS [mask: Mask] ~ {
data: Data ~ NARROW[context.data];
state: State ~ NARROW[context.state];
Runs: PROC [
run: PROC [sMin, fMin: INTEGER, fSize: NAT],
repeat: PROC [timesToRepeatScanline: NAT]] ~ {
data.devicePath.ConvertToRuns[runProc: run];
};
data.devicePath ← ImagerStroke.DevicePathFromStroke[
pathMap: pathMap,
pathData: pathData,
clientToDevice: CompositeT[data, state],
width: strokeWidth,
strokeEnd: strokeEnd,
closed: FALSE,
scratch: data.devicePath
];
mask ← ImagerMasks.Create[Runs];
};
MaskFromRectangle: PROC [context: Context, area: Rectangle] RETURNS [mask: Mask] ~ {
data: Data ~ NARROW[context.data];
state: State ~ NARROW[context.state];
m: Transformation ~ CompositeT[data, state];
GenPath: ImagerScanConverter.PathProc
move: PROC [s, f: REAL],
line: PROC [s, f: REAL],
curve: PROC [s1, f1, s2, f2, s3, f3: REAL]
~ {
Xform: PROC [p: Pair] RETURNS [Pair] ~ {RETURN[[
Transforms (x, y) to (s, f) — note s comes first for ScanConvert!
m.a * p.x + m.b * p.y + m.c,
m.d * p.x + m.e * p.y + m.f
]]};
{ q: Pair ~ Xform[[area.x, area.y]]; move[q.x, q.y] };
{ q: Pair ~ Xform[[area.x + area.w, area.y]]; line[q.x, q.y] };
{ q: Pair ~ Xform[[area.x + area.w, area.y + area.h]]; line[q.x, q.y] };
{ q: Pair ~ Xform[[area.x, area.y + area.h]]; line[q.x, q.y] };
};
Runs: PROC [
run: PROC [sMin, fMin: INTEGER, fSize: NAT],
repeat: PROC [timesToRepeatScanline: NAT]] ~ {
data.devicePath.ConvertToRuns[runProc: run];
};
IF m.type # hard THEN {
p1: Pair ← ImagerTransform.Transform[[area.x, area.y], m];
p2: Pair ← ImagerTransform.Transform[[area.x+area.w, area.y+area.h], m];
sMin: INTEGER ← Real.RoundLI[MAX[MIN[p1.x, p2.x], -LAST[INTEGER]/2]];
sMax: INTEGER ← Real.RoundLI[MIN[MAX[p1.x, p2.x], LAST[INTEGER]/2]];
fMin: INTEGER ← Real.RoundLI[MAX[MIN[p1.y, p2.y], -LAST[INTEGER]/2]];
fMax: INTEGER ← Real.RoundLI[MIN[MAX[p1.y, p2.y], LAST[INTEGER]/2]];
RETURN [ImagerMasks.InlineBox[
[sMin: sMin, fMin: fMin, sSize: sMax-sMin, fSize: fMax-fMin]]];
};
data.devicePath ← ImagerScanConverter.CreatePath[pathProc: GenPath, scratch: data.devicePath];
mask ← ImagerMasks.Create[Runs];
};
ScaledFromReal: PROC [real: REAL] RETURNS [Scaled.Value] ~ TRUSTED {
Because of an apparent bug in Real.FScale.
i: INT ← Real.RoundLI[real * 65536.0];
RETURN[LOOPHOLE[i]]
};
MaskFromPixelArray: PROC [context: Context, pixelArray: PixelArray] RETURNS [Mask] ~ {
data: Data ~ NARROW[context.data];
state: State ~ NARROW[context.state];
m: Transformation ~ CompositeT[data, state];
destArea: Mask ← MaskFromRectangle[context, [0, 0, pixelArray.xPixels, pixelArray.yPixels]];
Runs: PROC [
run: PROC [sMin, fMin: INTEGER, fSize: NAT],
repeat: PROC [timesToRepeatScanline: NAT]] ~ {
buffer: PixelBuffer ← NEW[PixelBufferRep[destArea.fSize+1]];
DoRun: PROC [sMin, fMin: INTEGER, fSize: CARDINAL] ~ {
nextPixel: Pair ← ImagerTransform.InverseTransformVec[[0, 1], m];
start: Pair ← ImagerTransform.InverseTransform[[0.5+sMin, 0.5+fMin], m];
xStart: Scaled.Value ← ScaledFromReal[start.x];
yStart: Scaled.Value ← ScaledFromReal[start.y];
xDeltaPixel: Scaled.Value ← ScaledFromReal[nextPixel.x];
yDeltaPixel: Scaled.Value ← ScaledFromReal[nextPixel.y];
runStart: NAT ← 0;
fRel: NAT ← 0;
pixelArray.get[pixelArray, buffer, fSize, 0, xStart, yStart, xDeltaPixel, yDeltaPixel];
WHILE fRel < fSize DO
buffer[fSize] ← 0;
WHILE buffer[fRel] # 0 DO fRel ← fRel + 1 ENDLOOP;
IF fRel > runStart THEN {run[sMin, fMin + fRel, fRel - runStart]};
buffer[fSize] ← 1;
WHILE buffer[fRel] = 0 DO fRel ← fRel + 1 ENDLOOP;
runStart ← fRel;
ENDLOOP;
};
destArea.MapRuns[DoRun];
};
RETURN [ImagerMasks.Create[Runs]];
};
CompositeClipper: PROC [data: Data] RETURNS [Mask] ~ {
IF NOT data.compositeClipperValid THEN {
IF data.clientClipperPresent THEN {
IF data.clientClipperExclude THEN data.compositeClipper ← data.viewClipper.Difference[data.clientClipper]
ELSE data.compositeClipper ← data.viewClipper.And[data.clientClipper];
}
ELSE data.compositeClipper ← data.viewClipper;
data.compositeClipperValid ← TRUE;
};
RETURN [data.compositeClipper]
};
ClipMask: PROC [context: Context, mask: Mask, exclude: BOOLEANFALSE] ~ {
data: Data ~ NARROW[context.data];
data.compositeClipperValid ← FALSE;
IF NOT data.clientClipperPresent THEN {
data.clientClipperPresent ← TRUE;
data.clientClipperExclude ← exclude;
data.clientClipper ← mask;
ccCount ← ccCount + 1;
}
ELSE IF exclude THEN {
IF data.clientClipperExclude THEN {
data.clientClipper ← data.clientClipper.Or[mask];
ccCount ← ccCount + 1;
}
ELSE {
data.clientClipper ← data.clientClipper.Difference[mask];
ccCount ← ccCount + 1;
};
}
ELSE {
IF data.clientClipperExclude THEN {
data.clientClipper ← mask.Difference[data.clientClipper];
ccCount ← ccCount + 1;
data.clientClipperExclude ← FALSE;
}
ELSE {
data.clientClipper ← data.clientClipper.And[mask];
ccCount ← ccCount + 1;
};
};
};
ClipOutline: PROC [context: Context, pathMap: PathMapType, pathData: REF] ~ {
ClipMask[context, MaskFromPath[context, pathMap, pathData]];
};
ExcludeOutline: PROC [context: Context, pathMap: PathMapType, pathData: REF] ~ {
ClipMask[context, MaskFromPath[context, pathMap, pathData], TRUE];
};
ClipRectangle: PROC [context: Context, x, y, w, h: REAL] ~ {
ClipMask[context, MaskFromRectangle[context, [x, y, w, h]]];
};
ExcludeRectangle: PROC [context: Context, x, y, w, h: REAL] ~ {
ClipMask[context, MaskFromRectangle[context, [x, y, w, h]], TRUE];
};
IntegerClipRectangle: PROC [context: Context, x, y, w, h: INTEGER] ~ {
ClipMask[context, MaskFromRectangle[context, [x, y, w, h]], TRUE];
};
IntegerExcludeRectangle: PROC [context: Context, x, y, w, h: INTEGER] ~ {
ClipMask[context, MaskFromRectangle[context, [x, y, w, h]], TRUE];
};
SetColor: PROC [context: Context, color: Color] ~ {
data: Data ~ NARROW[context.data];
state: State ~ NARROW[context.state];
IF color = state.nps.color THEN RETURN;
state.nps.color ← color;
IF color = Imager.black OR color = Imager.white THEN RETURN;
WITH color SELECT FROM
constantColor: ConstantColor => {
intensity: INT ← constantColor.Y;
intensity ← (intensity + 127)/(LAST[CARDINAL]/255);
SetTileSamples[data.tileSamples, intensity];
ImagerHalftone.Halftone[
dest: [sOrigin: data.tile.sOrigin, fOrigin: data.tile.fOrigin, sMin: 0, fMin: 0, sSize: data.tile.sSize, fSize: data.tile.fSize, refRep: data.tile.refRep],
mask: ImagerMasks.Box[[0, 0, 16, 16]],
source: data.tileSamples.pa,
transformation: ImagerTransform.Rotate[0],
deviceBrick: LFBrick
];
};
specialColor: SpecialColor => {
WITH specialColor.ref SELECT FROM
stipple: REF CARDINAL =>
data.tile ← ImagerPixelMaps.TileFromStipple[stipple: stipple^, scratch: data.tile.refRep];
atom: ATOM => {
SELECT atom FROM
$XOR => NULL;
ENDCASE => ERROR Imager.Error[$UnknownSpecialColor];
};
ENDCASE => ERROR Imager.Error[$UnknownSpecialColor];
};
sampledColor: SampledColor => NULL;
ENDCASE => state.nps.color ← Imager.black;
};
MaskStroke: PROC [context: Context, pathMap: PathMapType, pathData: REF,
strokeWidth: REAL, strokeEnd: StrokeEnd] ~ {
data: Data ~ NARROW[context.data];
mask: ImagerMasks.Mask ← MaskFromStroke[context, pathMap, pathData, strokeWidth, strokeEnd, FALSE];
ApplyMask[context, mask];
};
MaskStrokeClosed: PROC [context: Context, pathMap: PathMapType, pathData: REF,
strokeWidth: REAL] ~ {
data: Data ~ NARROW[context.data];
mask: ImagerMasks.Mask ← MaskFromStroke[context, pathMap, pathData, strokeWidth, nil, TRUE];
ApplyMask[context, mask];
};
blackTile: Tile ← ImagerPixelMaps.TileFromConstant[pixelValue: 1, lgBitsPerPixel: 0];
whiteTile: Tile ← ImagerPixelMaps.TileFromConstant[pixelValue: 0, lgBitsPerPixel: 0];
ApplyMask: PROC [context: Context, mask: Mask] ~ {
state: State ~ NARROW[context.state];
IF state.nps.noImage = 0 THEN {
data: Data ~ NARROW[context.data];
color: Color ~ NARROW[state.nps.color];
clippedMask: Mask ← mask.And[CompositeClipper[data]];
IF clippedMask.sSize = 0 THEN RETURN;
SELECT color FROM
Imager.black => ImagerMasks.MaskTile[data.canvas, clippedMask, blackTile];
Imager.white => ImagerMasks.MaskTile[data.canvas, clippedMask, whiteTile];
Imager.XOR => ImagerMasks.MaskTile[data.canvas, clippedMask, blackTile, [xor, null]];
ENDCASE =>
WITH color SELECT FROM
sampledColor: SampledColor => {
transform: Transformation ← ImagerTransform.Concat[sampledColor.m, SurfaceToDevice[data]];
transform.c ← transform.c + data.sOriginBrick;
transform.f ← transform.f + data.fOriginBrick;
ImagerHalftone.Halftone[data.canvas, clippedMask, sampledColor.pa, transform, LFBrick];
};
ENDCASE => { -- constant (other than black or white) or stipple
tile: ImagerPixelMaps.Tile ← data.tile;
tile.sOrigin ← tile.sOrigin + data.sOriginBrick;
tile.fOrigin ← tile.fOrigin + data.fOriginBrick;
ImagerMasks.MaskTile[data.canvas, clippedMask, tile];
};
};
};
MaskFill: PROC [context: Context, pathMap: PathMapType, pathData: REF] ~ {
data: Data ← NARROW[context.data];
mask: Mask ← MaskFromPath[context, pathMap, pathData];
ApplyMask[context, mask];
};
MaskPixel: PROC [context: Context, pa: PixelArray] ~ {
data: Data ← NARROW[context.data];
mask: Mask ← MaskFromPixelArray[context, pa];
ApplyMask[context, mask];
};
scratchPixelArray: PixelArray ← NEW[PixelArrayRep];
scratchBitmapDesc: REF ImagerPixelMaps.PixelMapRep ← NEW[ImagerPixelMaps.PixelMapRep];
MaskBits: ENTRY PROC [context: Context, base: LONG POINTER, raster: CARDINAL, tile: IntRectangle, area: IntRectangle] ~ {
ENABLE UNWIND => NULL;
data: Data ← NARROW[context.data];
state: State ~ NARROW[context.state];
m: Transformation ~ CompositeT[data, state];
color: Color ~ NARROW[state.nps.color];
IF m.type = rot90 AND tile.w = 16 AND raster = 1 AND tile.h <= 16 AND (color = Imager.black OR color = Imager.XOR) THEN {
sMin, fMin, sSize, fSize: INTEGER;
sMinTile, fMinTile, sSizeTile, fSizeTile: INTEGER;
[[sMin, fMin, sSize, fSize]] ← ImagerTransform.TransformIntRectangle[area, m];
[[sMinTile, fMinTile, sSizeTile, fSizeTile]] ← ImagerTransform.TransformIntRectangle[tile, m];
scratchBitmapDesc.pointer ← base;
scratchBitmapDesc.rast ← raster;
scratchBitmapDesc.lines ← sSizeTile;
ImagerMasks.MaskTile[data.canvas,
CompositeClipper[data].And[[sMin, fMin, sSize, fSize, NIL]],
[sMinTile, fMinTile, sSizeTile, fSizeTile, 0, scratchBitmapDesc],
IF color = Imager.black THEN [or, null] ELSE [xor, null]];
}
ELSE {
IF firstBadMaskBits THEN {firstBadMaskBits ← FALSE; ERROR Imager.Error[$NotImplemented]}
-- sorry, not yet.
};
};
firstBadMaskBits: BOOLEANTRUE;
MaskRectangle: PROC [context: Context, x, y, w, h: REAL] ~ {
data: Data ← NARROW[context.data];
mask: Mask ← MaskFromRectangle[context, [x, y, w, h]];
ApplyMask[context, mask];
};
IntegerMaskRectangle: PROC [context: Context, x, y, w, h: INTEGER] ~ {
data: Data ← NARROW[context.data];
mask: Mask ← MaskFromRectangle[context, [x, y, w, h]];
ApplyMask[context, mask];
};
rasterToRunGroupStorageRatio: INT ← 1;
CharLoadInfo: TYPE ~ RECORD[sWidth, fWidth: Scaled.Value, mask: Mask, loadRepSize: INT];
MaskAndRunCount: TYPE ~ RECORD [mask: Mask, nRuns: INT];
GetCharMask: PROC [font: FONT, transformation: Transformation, char: CHAR]
RETURNS [ans: MaskAndRunCount] ~ {
nRuns: INT ← 0;
Runs: PROC [
run: PROC [sMin, fMin: INTEGER, fSize: NAT],
repeat: PROC [timesToRepeatScanline: NAT]] ~ {
Run: PROC [sMin, fMin: INTEGER, fSize: NAT] ~ {
run[sMin: sMin, fMin: fMin, fSize: fSize];
nRuns ← nRuns + 1;
};
font.fontGraphicsClass.maskProc[font, transformation, char, Run];
};
nRuns ← 0;
ans.mask ← ImagerMasks.Create[Runs];
ans.nRuns ← nRuns;
};
FontCompositeTransform: PROC [context: Context, font: FONT] RETURNS [t: Transformation] ~ {
data: Data ~ NARROW[context.data];
state: State ~ NARROW[context.state];
t ← CompositeT[data, state];
t.c ← t.f ← 0;
t ← ImagerTransform.Concat[font.actualTransformation, t];
};
fontCache: ImagerFontCache.FontCache ← ImagerFontCache.Create[];
LoadCharData: PROC [self: ImagerFontCache.FontObject, charCode: CARDINAL]
RETURNS [charData: REF, memoryCost: INT] ~ {
font: FONT ~ NARROW[self.fontAnyData];
char: CHAR ~ 0C+charCode; -- does this bounds check properly?
transform: Transformation ~ ImagerTransform.Create[self.r0, self.r1, self.r2, self.r3, self.r4, self.r5]; -- character (to client) to device
clientTransform: Transformation ~ ImagerTransform.Concat[
ImagerTransform.Invert[font.actualTransformation], transform];
loadInfo: REF CharLoadInfo ← NEW[CharLoadInfo];
maskN: MaskAndRunCount ← GetCharMask[font, transform, char];
runGroupSize: INT ← maskN.nRuns * 2 + 2;
rasterSize: INT ← maskN.mask.sSize * INT[(maskN.mask.fSize+15)/16] + 2;
width: Pair ← ImagerTransform.TransformVec[font.fontGraphicsClass.widthVectorProc[font, char], clientTransform];
loadInfo.mask ← maskN.mask;
SELECT TRUE FROM
maskN.mask.sSize = 0 OR maskN.mask.fSize = 0 => {loadInfo.loadRepSize ← 0};
maskN.mask.fSize > 32*Environment.bitsPerWord OR
runGroupSize*rasterToRunGroupStorageRatio < rasterSize => {loadInfo.loadRepSize ← runGroupSize};
ENDCASE => {loadInfo.loadRepSize ← rasterSize; loadInfo.mask ← loadInfo.mask.Bitmap};
loadInfo.sWidth ← Scaled.FromReal[width.x];
loadInfo.fWidth ← Scaled.FromReal[width.y];
RETURN[loadInfo, 0];
};
ShowChar: PROC [context: Context, char: CHAR, font: FONT] ~ {
data: Data ~ NARROW[context.data];
state: State ~ NARROW[context.state];
transform: Transformation ← FontCompositeTransform[context, font];
fontCode: ImagerFontCache.FontCode ← ImagerFontCache.GetFontCode[[
CharDataProc: LoadCharData,
r0: transform.a, r1: transform.b, r2: transform.c, r3: transform.d, r4: transform.e, r5: transform.f,
fontScalarData: 0, -- unused
fontAnyData: font]];
cp: DeviceCP ← GetDeviceCP[data, state];
loadInfo: REF CharLoadInfo ~ NARROW[
ImagerFontCache.GetCharData[fontCache, fontCode, char-0C]];
ApplyMask[context, loadInfo.mask.Shift[Scaled.Round[cp.s], Scaled.Round[cp.f]]];
cp.s ← cp.s.PLUS[loadInfo.sWidth];
cp.f ← cp.f.PLUS[loadInfo.fWidth];
SetDeviceCP[data, state, cp];
};
ShowCharacters: PROC [
context: Context,
characters: REF, -- may be a ROPE or a REF TEXT
font: FONT,
start: INT ← 0,
length: INTLAST[INT]
] ~ {
data: Data ~ NARROW[context.data];
state: State ~ NARROW[context.state];
transform: Transformation ← FontCompositeTransform[context, font];
fontCode: ImagerFontCache.FontCode ← ImagerFontCache.GetFontCode[[
CharDataProc: LoadCharData,
r0: transform.a, r1: transform.b, r2: transform.c, r3: transform.d, r4: transform.e, r5: transform.f,
fontScalarData: 0, -- unused
fontAnyData: font]];
cp: DeviceCP ← GetDeviceCP[data, state];
DoChar: PROC [charCode: CARDINAL, charData: REF] ~ {
loadInfo: REF CharLoadInfo ~ NARROW[charData];
ApplyMask[context, loadInfo.mask.Shift[Scaled.Round[cp.s], Scaled.Round[cp.f]]];
cp.s ← cp.s.PLUS[loadInfo.sWidth];
cp.f ← cp.f.PLUS[loadInfo.fWidth];
};
ImagerFontCache.GetStringData[DoChar, fontCache, fontCode, characters, start, length];
SetDeviceCP[data, state, cp];
};
firstBadDrawBitmap: BOOLEANTRUE;
DrawBitmap: ENTRY PROC [context: Context, base: LONG POINTER, raster: CARDINAL, area: IntRectangle] ~ {
ENABLE UNWIND => NULL;
data: Data ~ NARROW[context.data];
state: State ~ NARROW[context.state];
m: Transformation ~ CompositeT[data, state];
IF m.type = rot90 THEN {
sMin, fMin, sSize, fSize: INTEGER;
[[sMin, fMin, sSize, fSize]] ← ImagerTransform.TransformIntRectangle[area, m];
scratchBitmapDesc.pointer ← base;
scratchBitmapDesc.rast ← raster;
scratchBitmapDesc.lines ← sSize;
ImagerMasks.MaskTile[data.canvas,
ImagerMasks.Box[[sMin, fMin, sSize, fSize]].And[CompositeClipper[data]],
[sMin, fMin, sSize, fSize, 0, scratchBitmapDesc]];
}
ELSE {
IF firstBadDrawBitmap THEN {firstBadDrawBitmap ← FALSE; ERROR Imager.Error[$NotImplemented]}
Sorry, not yet.
};
};
MaskFromSurfaceRectangle: PROC [context: Context, box: IntRectangle] RETURNS [Mask] ~ {
data: Data ~ NARROW[context.data];
deviceBox: IntRectangle ← ImagerTransform.TransformIntRectangle[box, SurfaceToDevice[data]];
RETURN [ImagerMasks.InlineBox[[deviceBox.x, deviceBox.y, deviceBox.w, deviceBox.h]]];
};
SetView: PROC [context: Context, box: IntRectangle, halftoneOrigin: IntPair ← [0, 0]] ~ {
data: Data ← NARROW[context.data];
mask: Mask ← MaskFromSurfaceRectangle[context, box];
sOriginView: INTEGER ← data.canvas.sSize-box.y;
fOriginView: INTEGER ← box.x;
sShift: INTEGER ← sOriginView-data.sOriginView;
fShift: INTEGER ← fOriginView-data.fOriginView;
newHalftoneOrigin: IntPair ← ImagerTransform.IntTransform[halftoneOrigin, SurfaceToDevice[data]];
IF data.doSaveCount # 0 THEN ERROR; -- Can't change a view with DoSave in progress.
IF data.viewClipper = mask AND sShift = 0 AND fShift = 0 AND newHalftoneOrigin = [data.sOriginBrick, data.fOriginBrick] THEN RETURN;
data.compositeClipperValid ← FALSE;
data.compositeClipper.refRep ← NIL;
IF data.clientClipperPresent THEN {
data.clientClipper ← data.clientClipper.Shift[sShift, fShift];
ccCount ← ccCount + 1;
};
data.viewClipper ← mask;
data.sOriginView ← sOriginView;
data.fOriginView ← fOriginView;
[data.sOriginBrick, data.fOriginBrick] ← newHalftoneOrigin;
};
ClipView: PROC [context: Context, box: IntRectangle, exclude: BOOLEAN] ~ {
data: Data ← NARROW[context.data];
mask: Mask ← MaskFromSurfaceRectangle[context, box];
data.compositeClipperValid ← FALSE;
data.compositeClipper.refRep ← NIL;
data.viewClipper ←
IF exclude THEN data.viewClipper.Difference[mask]
ELSE data.viewClipper.And[mask];
};
MoveSurfaceRectangle: PROC [context: Context, source: IntRectangle, dest: IntPair] ~ {
data: Data ~ NARROW[context.data];
m: Transformation ~ SurfaceToDevice[data];
sMinDest, fMinDest, sSize, fSize: INTEGER;
sShift, fShift: INTEGER;
[[sMinDest, fMinDest, sSize, fSize]] ← ImagerTransform.TransformIntRectangle[[dest.x, dest.y, source.x, source.h], m];
[[sShift, fShift]] ← ImagerTransform.TransformIntVec[[dest.x-source.x, dest.y-source.y], m];
data.canvas.Clip[[sMinDest, fMinDest, sSize, fSize]].Transfer[data.canvas.ShiftMap[sShift, fShift]];
};
TestRectangle: PROC [context: Context, x, y, w, h: REAL] RETURNS [Visibility] ~ {
data: Data ~ NARROW[context.data];
mask: Mask ~ MaskFromRectangle[context, [x, y, w, h]];
RETURN[mask.IsVisible[CompositeClipper[data]]];
};
GetSurfaceBounds: PROC [context: Context] RETURNS [IntRectangle] ~ {
data: Data ~ NARROW[context.data];
deviceToSurface: Transformation ~ ImagerTransform.Invert[SurfaceToDevice[data]];
RETURN [ImagerTransform.TransformIntRectangle[[data.canvas.sMin, data.canvas.fMin, data.canvas.sSize, data.canvas.fSize], deviceToSurface]]
};
GetViewBounds: PROC [context: Context] RETURNS [IntRectangle] ~ {
data: Data ~ NARROW[context.data];
deviceToSurface: Transformation ~ ImagerTransform.Invert[SurfaceToDevice[data]];
RETURN[ImagerTransform.TransformIntRectangle[[data.viewClipper.sMin, data.viewClipper.fMin, data.viewClipper.sSize, data.viewClipper.fSize], deviceToSurface]]
};
SpecialOp: PROC [context: Context, op: ATOM, data: REF] RETURNS [REF] ~ {
clipperData: Data ~ NARROW[context.data];
SELECT op FROM
$OtherScreen => {
swapped: BOOLEAN;
TRUSTED {swapped ← TerminalMultiplex.SelectTerminal[swap]};
TRUSTED {IF UserTerminal.GetState[] = disconnected THEN [] ← UserTerminal.SetState[on]};
TRUSTED {clipperData.canvas ← ImagerFrameBuffer.LFDisplay[]};
TRUSTED {swapped ← TerminalMultiplex.SelectTerminal[swap]};
RETURN [IF swapped THEN $OK ELSE NIL]
};
ENDCASE => ERROR Imager.Error[$UnimplementedSpecialOp];
};
LFDisplayClass: ImagerPrivate.Class ← NEW [ImagerPrivate.ClassRep ← [
deviceType: $LFDisplay,
Init: Init,
IGet: IGet,
ISet: ISet,
IGetReal: ImagerDefault.IGetReal,
ISetReal: ImagerDefault.ISetReal,
IGetInt: ImagerDefault.IGetInt,
ISetInt: ImagerDefault.ISetInt,
DoSave: DoSave,
DoSaveAll: DoSaveAll,
ConcatT: ImagerDefault.ConcatT,
TranslateT: ImagerDefault.TranslateT,
RotateT: ImagerDefault.RotateT,
ScaleT: ImagerDefault.ScaleT,
Scale2T: ImagerDefault.Scale2T,
Move: ImagerDefault.Move,
Trans: ImagerDefault.Trans,
SetXY: ImagerDefault.SetXY,
IntegerSetXY: ImagerDefault.IntegerSetXY,
SetXYRel: ImagerDefault.SetXYRel,
IntegerSetXYRel: ImagerDefault.IntegerSetXYRel,
GetCP: ImagerDefault.GetCP,
GetCPRounded: ImagerDefault.GetCPRounded,
MaskFill: MaskFill,
MaskStroke: MaskStroke,
MaskStrokeClosed: MaskStrokeClosed,
MaskVector: ImagerDefault.MaskVector,
IntegerMaskVector: ImagerDefault.IntegerMaskVector,
MaskRectangle: MaskRectangle,
IntegerMaskRectangle: IntegerMaskRectangle,
StartUnderline: ImagerDefault.StartUnderline,
MaskUnderline: ImagerDefault.MaskUnderline,
IntegerMaskUnderline: ImagerDefault.IntegerMaskUnderline,
MaskPixel: MaskPixel,
ClipOutline: ClipOutline,
ExcludeOutline: ExcludeOutline,
ClipRectangle: ClipRectangle,
ExcludeRectangle: ExcludeRectangle,
IntegerClipRectangle: IntegerClipRectangle,
IntegerExcludeRectangle: IntegerExcludeRectangle,
ShowChar: ShowChar,
ShowCharacters: ShowCharacters,
CorrectMask: ImagerDefault.CorrectMask,
CorrectSpace: ImagerDefault.CorrectSpace,
SetCorrectMeasure: ImagerDefault.SetCorrectMeasure,
SetCorrectTolerance: ImagerDefault.SetCorrectTolerance,
Space: ImagerDefault.Space,
IntegerSpace: ImagerDefault.IntegerSpace,
Correct: ImagerDefault.Correct,
Reset: Reset,
SetView: SetView,
ClipView: ClipView,
DrawBitmap: DrawBitmap,
MaskBits: MaskBits,
MoveSurfaceRectangle: MoveSurfaceRectangle,
TestRectangle: TestRectangle,
GetSurfaceBounds: GetSurfaceBounds,
GetViewBounds: GetViewBounds,
SpecialOp: SpecialOp
]];
ImagerPrivate.RegisterDevice[LFDisplayClass];
END.