ImagerDeviceWorksImpl.mesa
Copyright Ó 1989, 1990, 1991, 1992, 1993, 1994 by Xerox Corporation. All rights reserved.
Michael Plass, February 3, 1994 5:24 pm PST
Russ Atkinson (RRA) August 18, 1993 8:10 pm PDT
DIRECTORY
Basics,
BlockRotate,
Char,
ImagerBox USING [Box, BoxFromRectangle, Extents, Rectangle],
ImagerClipper,
ImagerDevice,
ImagerDeviceInterchange USING [InterchangeStateRep],
ImagerDeviceProcs USING [],
ImagerDeviceVector USING [DVec, DVecRep],
ImagerDeviceWorks USING [],
ImagerError USING [Error],
ImagerFont USING [CorrectionType, Font, Modify, nullXChar, XChar, XCharProc, XStringProc],
ImagerFontWorks,
ImagerManhattan,
ImagerMaskCache USING [BitmapFromCharMask, BoxesFromCharMask, CharFlags, CharMask, CharMaskRep, Fetch, GetParameters, MaskCache, ObtainSmallCache, Parameters, ParametersRep, ReleaseSmallCache, SmallCache, Store],
ImagerPath,
ImagerPen,
ImagerPixel,
ImagerPixelArray,
ImagerSample,
ImagerScanConverter,
ImagerScanConverterExtras,
ImagerStroke USING [buttEnd, ConvolvePenWithPath, Dashes, EndCode, JointCode, MapBitPath, PathFromStroke, PathFromVector, roundEnd, SignalSquareEndWithNoDirection, warningSquareEnd],
ImagerSwitches,
ImagerSys USING [SMul],
ImagerTransformation,
ImagerTypeface USING [MakeFont, Typeface, TypefaceClass, TypefaceClassRep, TypefaceRep],
Prop USING [PropList],
Real USING [Ceiling, Floor, Round],
RealInline,
RealFns USING [AlmostEqual],
RefText USING [ObtainScratch, ReleaseScratch],
ImagerScaled USING [Float, Floor, FromReal, IntRep, PLUS, Round, Value],
SF,
SFInline USING [Intersect, Nonempty],
Vector2 USING [InlineSub, Length, VEC];
ImagerDeviceWorksImpl: CEDAR MONITOR
IMPORTS Basics, BlockRotate, Char, ImagerBox, ImagerError, ImagerFont, ImagerFontWorks, ImagerManhattan, ImagerMaskCache, ImagerPath, ImagerPixel, ImagerPixelArray, ImagerSample, ImagerScanConverter, ImagerScanConverterExtras, ImagerStroke, ImagerSwitches, ImagerSys, ImagerTransformation, ImagerTypeface, Real, RealFns, RealInline, RefText, ImagerScaled, SF, SFInline, Vector2
EXPORTS ImagerDevice, ImagerDeviceProcs, ImagerDeviceWorks, ImagerDeviceInterchange
~ BEGIN
Types and Constants
Device: TYPE = ImagerDevice.Device;
DeviceClipper: TYPE = ImagerDevice.DeviceClipper;
DeviceParm: TYPE = ImagerDevice.DeviceParm;
DevicePath: TYPE = ImagerScanConverter.DevicePath;
EasyMetrics: TYPE = ImagerDevice.EasyMetrics;
Extents: TYPE = ImagerBox.Extents;
Font: TYPE = ImagerFont.Font;
ManhattanPolygon: TYPE = ImagerManhattan.Polygon;
RasterMask: TYPE = REF ImagerMaskCache.CharMaskRep.raster;
PathProc: TYPE = ImagerPath.PathProc;
PixelArray: TYPE = ImagerPixelArray.PixelArray;
SampleMap: TYPE = ImagerSample.SampleMap;
Transformation: TYPE = ImagerTransformation.Transformation;
Typeface: TYPE = ImagerTypeface.Typeface;
VEC: TYPE = Vector2.VEC;
XChar: TYPE = ImagerFont.XChar;
XStringProc: TYPE = ImagerFont.XStringProc;
bitsPerWord: NAT = BITS[WORD];
nullXChar: XChar = ImagerFont.nullXChar;
ordinaryMetrics: ImagerMaskCache.CharFlags ~ ImagerDevice.ordinaryMetrics;
worryNat: NAT = LAST[NAT15] / 2 - 1;
worryReal: REAL ¬ worryNat;
Setup
scanFatSwitch: CHAR['s..'s] ~ ImagerSwitches.Define['s, $scanfat, "Use fat-mode scan conversion", NIL];
CreatePath: PROC [path: PathProc, transformation: Transformation, clipBox: SF.Box] RETURNS [DevicePath] ~ {
The difference between this and ImagerScanConverter.CreatePath is that here we use the scanFatSwitch to set the scan conversion mode. Note that we intentionally do not use this switch for strokes!
devicePath: DevicePath ~ ImagerScanConverter.Create[];
ImagerScanConverterExtras.SetScanConversionMode[devicePath, ORD[ImagerSwitches.BoolValue[scanFatSwitch]]];
ImagerScanConverter.SetPath[devicePath, path, transformation, clipBox];
RETURN [devicePath]
};
WhatClassHas: PROC [class: ImagerDevice.DeviceClass] RETURNS [ImagerDevice.ClassHas] = INLINE {
RETURN [[
SetPriority: class.SetPriority # NIL,
DrawBitmap: class.DrawBitmap # NIL,
MoveBox: class.MoveBox # NIL,
SwitchBuffer: class.SwitchBuffer # NIL,
AccessBuffer: class.AccessBuffer # NIL
]]
};
MakeDeviceParm: PUBLIC PROC [class: ImagerDevice.DeviceClass, sSize, fSize: NAT, scanMode: ImagerTransformation.ScanMode, surfaceUnitsPerInch: Vector2.VEC, surfaceUnitsPerPixel: NAT ¬ 1, fontCache: ImagerMaskCache.MaskCache ¬ NIL, parameters: ImagerMaskCache.Parameters ¬ NIL, propList: Prop.PropList ¬ NIL] RETURNS [DeviceParm] = {
deviceParm: DeviceParm ~ NEW [ImagerDevice.DeviceParmRepr ¬ [
classHas: WhatClassHas[class],
sSize: sSize,
fSize: fSize,
scanMode: scanMode,
surfaceUnitsPerInch: surfaceUnitsPerInch,
surfaceUnitsPerPixel: surfaceUnitsPerPixel,
fontCache: fontCache,
parameters: IF parameters # NIL THEN parameters ELSE IF fontCache # NIL THEN ImagerMaskCache.GetParameters[fontCache] ELSE NEW[ImagerMaskCache.ParametersRep ¬ []],
propList: propList
]];
RETURN [deviceParm]
};
Clipping
BoxesFromPath: PUBLIC PROC [action: PROC [bounds: SF.Box, boxGenerator: SF.BoxGenerator], path: PathProc, oddWrap: BOOL, pathToDevice: Transformation, clipper: DeviceClipper] = {
devicePath: DevicePath = CreatePath[path: path, transformation: pathToDevice, clipBox: clipper.clipBox];
pathBox: SF.Box = ImagerScanConverter.BoundingBox[devicePath];
bounds: SF.Box = SF.Intersect[pathBox, clipper.clipBox];
Runs: --SF.BoxGenerator-- PROC [boxAction: SF.BoxAction] = {
rem: ManhattanPolygon ¬ clipper.clipMask;
IF rem#NIL AND rem.rest=NIL
THEN {
ImagerScanConverter.ConvertToBoxes[devicePath: devicePath, oddWrap: oddWrap, clipBox: clipper.clipMask.first, boxAction: boxAction];
}
ELSE {
ClipBoxAction: --SF.BoxAction-- PROC [box: SF.Box] = {
WHILE rem#NIL AND rem.first.max.s<=box.min.s DO rem ¬ rem.rest ENDLOOP;
FOR l: LIST OF SF.Box ¬ rem, l.rest UNTIL l=NIL OR l.first.min.s>=box.max.s DO
clipped: SF.Box = SF.Intersect[box, l.first];
IF SF.Nonempty[clipped] THEN boxAction[clipped];
ENDLOOP;
};
ImagerScanConverter.ConvertToBoxes[devicePath: devicePath, oddWrap: oddWrap, clipBox: bounds, boxAction: ClipBoxAction];
};
};
IF SF.Nonempty[bounds] THEN action[bounds, Runs];
ImagerScanConverter.Destroy[devicePath];
};
StandardClip: PUBLIC PROC [device: Device, viewClipper: DeviceClipper, clipperToDevice: Transformation, clientClipper: ImagerClipper.Clipper] = {
cc: ImagerManhattan.Polygon ¬ NIL;
clipper: DeviceClipper ¬ device.worksState.clipper;
IF clipper = NIL THEN device.worksState.clipper ¬ clipper ¬ NEW[ImagerDevice.DeviceClipperRep];
ImagerManhattan.Destroy[clipper.clipMask];
cc ¬ ImagerManhattan.Copy[viewClipper.clipMask];
cc ¬ ImagerManhattan.DestructiveClip[cc, device.state.bounds];
FOR each: ImagerClipper.Clipper ¬ clientClipper, each.rest UNTIL each=NIL DO
Combine: PROC [a, b: ManhattanPolygon] RETURNS [ManhattanPolygon] ¬ (
IF each.first.exclude
THEN ImagerManhattan.DestructiveDifference
ELSE ImagerManhattan.DestructiveIntersection
);
path: PathProc ~ { ImagerPath.MapOutline[outline: each.first.outline, moveTo: moveTo, lineTo: lineTo, curveTo: curveTo, conicTo: conicTo, arcTo: arcTo] };
devicePath: DevicePath ¬ CreatePath[path: path, transformation: clipperToDevice, clipBox: viewClipper.clipBox];
this: ManhattanPolygon ¬ ImagerScanConverter.ConvertToManhattanPolygon[devicePath: devicePath, clipBox: viewClipper.clipBox, oddWrap: each.first.oddWrap];
cc ¬ Combine[cc, this];
ImagerManhattan.Destroy[this];
ImagerScanConverter.Destroy[devicePath];
ENDLOOP;
clipper.clipMask ¬ cc;
clipper.clipBox ¬ ImagerManhattan.BoundingBox[cc];
device.worksState.clipperToDevice ¬ clipperToDevice;
device.worksState.clientClipper ¬ clientClipper;
};
Fills
StandardMaskFill: PUBLIC PROC [device: Device, path: PathProc, oddWrap: BOOL, pathToDevice: Transformation] = {
clipper: DeviceClipper = device.worksState.clipper;
devicePath: DevicePath = CreatePath[path: path, transformation: pathToDevice, clipBox: clipper.clipBox];
FillDevicePath[device, devicePath, oddWrap];
ImagerScanConverter.Destroy[devicePath];
};
FillDevicePath: PROC [device: Device, devicePath: DevicePath, oddWrap: BOOL ¬ FALSE] ~ {
clipper: DeviceClipper ~ device.worksState.clipper;
pathBox: SF.Box = ImagerScanConverter.BoundingBox[devicePath];
bounds: SF.Box = SFInline.Intersect[pathBox, clipper.clipBox];
IF SFInline.Nonempty[bounds] THEN {
IF device.state.allow.regionFill AND ImagerScanConverter.Monotone[devicePath] AND SF.Inside[inner: bounds, outer: clipper.clipMask.first]
THEN {
GenerateEdges: PROC [edgeAction: ImagerSample.EdgeAction] = {
ImagerScanConverter.GenerateEdges[devicePath: devicePath, edgeAction: edgeAction];
};
device.class.MaskRegion[device: device, bounds: bounds, edgeGenerator: GenerateEdges];
}
ELSE {
GenerateBoxes: --SF.BoxGenerator-- PROC [boxAction: SF.BoxAction] = {
rem: ManhattanPolygon ¬ clipper.clipMask;
IF rem#NIL AND rem.rest=NIL
THEN {
ImagerScanConverter.ConvertToBoxes[devicePath: devicePath, oddWrap: oddWrap, clipBox: clipper.clipMask.first, boxAction: boxAction];
}
ELSE {
ClipBoxAction: --SF.BoxAction-- PROC [box: SF.Box] = {
WHILE rem#NIL AND rem.first.max.s<=box.min.s DO rem ¬ rem.rest ENDLOOP;
FOR l: LIST OF SF.Box ¬ rem, l.rest UNTIL l=NIL OR l.first.min.s>=box.max.s DO
clipped: SF.Box = SFInline.Intersect[box, l.first];
IF SFInline.Nonempty[clipped] THEN boxAction[clipped];
ENDLOOP;
};
ImagerScanConverter.ConvertToBoxes[devicePath: devicePath, oddWrap: oddWrap, clipBox: bounds, boxAction: ClipBoxAction];
};
};
device.class.MaskBoxes[device: device, bounds: bounds, boxes: GenerateBoxes];
};
};
};
ClippedRound: PROC [r: REAL, min, max: INTEGER] RETURNS [i: INTEGER] = {
r ¬ r + 0.5;
IF r < min THEN r ¬ min;
IF r > max THEN r ¬ max;
RETURN [Real.Floor[r]];
};
StandardMaskRectangle: PUBLIC PROC [device: Device, rectangle: ImagerBox.Rectangle, rectangleToDevice: Transformation] = {
clipper: DeviceClipper ~ device.worksState.clipper;
IF rectangleToDevice.form # 0 AND NOT ImagerSwitches.BoolValue[scanFatSwitch]
THEN {
p0: VEC = ImagerTransformation.Transform[rectangleToDevice, [rectangle.x, rectangle.y]];
p1: VEC = ImagerTransformation.Transform[rectangleToDevice, [rectangle.x + rectangle.w, rectangle.y + rectangle.h]];
s0: INTEGER ¬ ClippedRound[p0.x, clipper.clipBox.min.s, clipper.clipBox.max.s];
s1: INTEGER ¬ ClippedRound[p1.x, clipper.clipBox.min.s, clipper.clipBox.max.s];
f0: INTEGER ¬ ClippedRound[p0.y, clipper.clipBox.min.f, clipper.clipBox.max.f];
f1: INTEGER ¬ ClippedRound[p1.y, clipper.clipBox.min.f, clipper.clipBox.max.f];
IF s1 < s0 THEN {temp: INTEGER = s0; s0 ¬ s1; s1 ¬ temp};
IF f1 < f0 THEN {temp: INTEGER = f0; f0 ¬ f1; f1 ¬ temp};
IF s0 < s1 AND f0 < f1 THEN {
box: SF.Box = [min: [s0, f0], max: [s1, f1]];
Boxes: PROC [action: PROC [SF.Box]] = {
ImagerManhattan.ClipBoxToMask[box: box, mask: clipper.clipMask, action: action];
};
device.class.MaskBoxes[device: device, bounds: box, boxes: Boxes];
};
}
ELSE {
RectanglePath: PathProc = {
moveTo[[rectangle.x, rectangle.y]];
lineTo[[rectangle.x + rectangle.w, rectangle.y]];
lineTo[[rectangle.x + rectangle.w, rectangle.y + rectangle.h]];
lineTo[[rectangle.x, rectangle.y + rectangle.h]];
};
device.works.MaskFill[device: device, path: RectanglePath, oddWrap: FALSE, pathToDevice: rectangleToDevice];
};
};
Strokes
StandardMaskStroke: PUBLIC PROC [device: Device, path: PathProc, closed: BOOL, pathToDevice: Transformation, end: ImagerStroke.EndCode, joint: ImagerStroke.JointCode, miterLimit: REAL, pen: ImagerPen.Pen] = {
clipper: DeviceClipper ~ device.worksState.clipper;
devicePath: DevicePath ¬ NIL;
T: PROC [p: VEC] RETURNS [ImagerScanConverter.Pair] ~ INLINE { RETURN [[s: p.x, f: p.y]] };
MoveTo: ImagerPath.MoveToProc ~ { -- [p: VEC]
ImagerScanConverter.MoveTo[devicePath, T[p]];
};
LineTo: ImagerPath.LineToProc ~ { -- PROC [p1: VEC]
ImagerScanConverter.LineTo[devicePath, T[p1]];
};
ConicTo: ImagerPath.ConicToProc ~ { -- PROC [p1, p2: VEC, r: REAL]
ASSERT[r=0.5]; -- see comment for ImagerStroke.PathFromStroke
ImagerScanConverter.ParTo[devicePath, T[p1], T[p2]];
};
StrokePath: ImagerPath.PathProc ~ { -- PROC [moveTo: MoveToProc, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc]
StrokeMoveTo: ImagerPath.MoveToProc ~ { -- [p: VEC]
moveTo[p]; -- This comes first to flush out any previous trajectory. We rely on the the fact that all ImagerStroke.PathFromStroke does with this point is save it away - it can't do more until it has a direction.
IF devicePath = NIL
THEN { devicePath ¬ ImagerScanConverter.Create[] }
ELSE { FillDevicePath[device, devicePath] };
N. B. The node on this device path should not be changed from its default value - to work properly, the strokes must be rendered in center-sampling mode.
<< ImagerScanConverter.SetScanConversionMode[devicePath, 0]; >>
ImagerScanConverter.SetBounds[devicePath, clipper.clipBox];
};
path[moveTo: StrokeMoveTo, lineTo: lineTo, curveTo: curveTo, conicTo: conicTo, arcTo: arcTo];
};
ImagerStroke.PathFromStroke[path: StrokePath, closed: closed, m: pathToDevice, pen: pen, end: end, joint: joint, moveTo: MoveTo, lineTo: LineTo, conicTo: ConicTo, box: [xmin: clipper.clipBox.min.s, ymin: clipper.clipBox.min.f, xmax: clipper.clipBox.max.s, ymax: clipper.clipBox.max.f], miterLimit: miterLimit];
IF devicePath # NIL THEN {
FillDevicePath[device, devicePath];
ImagerScanConverter.Destroy[devicePath];
};
};
BailOut: ERROR = CODE;
HardCurve: ImagerPath.CurveToProc = { ERROR BailOut };
HardConic: ImagerPath.ConicToProc = { ERROR BailOut };
HardArc: ImagerPath.ArcToProc = { ERROR BailOut };
SimplePath: TYPE = RECORD [
valid: BOOL ¬ FALSE,
p0: VEC ¬ [0,0],
p1: VEC ¬ [0,0]
];
CheckSimplePath: PROC [path: PathProc] RETURNS [SimplePath] = {
result: SimplePath ¬ [];
state: NAT ¬ 0;
EasyMove: ImagerPath.MoveToProc = {
IF state = 0 THEN { result.p0 ¬ p; state ¬ 1 } ELSE ERROR BailOut
};
EasyLine: ImagerPath.LineToProc = {
IF state = 1 THEN { result.p1 ¬ p1; state ¬ 2 } ELSE ERROR BailOut
};
path[moveTo: EasyMove, lineTo: EasyLine, curveTo: HardCurve, conicTo: HardConic, arcTo: HardArc ! BailOut => { state ¬ 3; CONTINUE }];
result.valid ¬ state=2;
RETURN [result]
};
newDash: BOOL ¬ TRUE;
NewDash: PROC [c: CARD] RETURNS [old: BOOL] = { old ¬ newDash; newDash ¬ c#0 };
StandardMaskDashedStroke: PUBLIC PROC [device: Device, path: PathProc, patternLen: NAT, pattern: PROC [NAT] RETURNS [REAL], offset: REAL, length: REAL, closed: BOOL, pathToDevice: Transformation, end: INT, joint: INT, miterLimit: REAL, pen: ImagerPen.Pen] ~ {
clipper: DeviceClipper ~ device.worksState.clipper;
DashedPath: ImagerPath.PathProc ~ { ImagerStroke.Dashes[path: path, patternLen: patternLen, pattern: pattern, offset: offset, length: length, moveTo: moveTo, lineTo: lineTo, conicTo: conicTo, curveTo: curveTo] };
IF clipper.clipMask = NIL THEN RETURN;
IF newDash AND NOT closed AND patternLen > 0 AND pattern[0] > 1.0E-3 AND offset >= 0.0 THEN {
Try for the interesting case of just a straight line.
v: SimplePath = CheckSimplePath[path];
IF v.valid THEN {
Easy case - it is just a single, straight line.
IF length < 1.0E-10 THEN length ¬ Vector2.Length[Vector2.InlineSub[v.p0, v.p1]];
IF length >= 1.0E-10 THEN SELECT TRUE FROM
device.state.allow.unorderedBoxes AND end # ImagerStroke.roundEnd AND pathToDevice.form # 0 AND (v.p0.x = v.p1.x OR v.p0.y = v.p1.y) => {
This is a square or butt end, and nice in device space; go for speed
vd0: VEC = ImagerTransformation.Transform[m: pathToDevice, v: v.p0];
vd1: VEC = ImagerTransformation.Transform[m: pathToDevice, v: v.p1];
sConstant: BOOL ~ vd0.x = vd1.x;
fConstant: BOOL ~ vd0.y = vd1.y;
d1: VEC ¬ vd0;
d2: VEC ¬ vd1;
IF d1.x > d2.x THEN {t: REAL ¬ d1.x; d1.x ¬ d2.x; d2.x ¬ t};
IF d1.y > d2.y THEN {t: REAL ¬ d1.y; d1.y ¬ d2.y; d2.y ¬ t};
{
square: BOOL ~ (end # ImagerStroke.buttEnd);
ds: REAL ~ IF square OR sConstant THEN pen.bounds.x ELSE 0.0;
df: REAL ~ IF square OR fConstant THEN pen.bounds.y ELSE 0.0;
s0: INTEGER ~ ClippedRound[d1.x-ds, clipper.clipBox.min.s, clipper.clipBox.max.s];
s1: INTEGER ~ ClippedRound[d2.x+ds, clipper.clipBox.min.s, clipper.clipBox.max.s];
f0: INTEGER ~ ClippedRound[d1.y-df, clipper.clipBox.min.f, clipper.clipBox.max.f];
f1: INTEGER ~ ClippedRound[d2.y+df, clipper.clipBox.min.f, clipper.clipBox.max.f];
IF s0 < s1 AND f0 < f1 THEN {
Blend: PROC [u0, u1, d, start, stop: REAL, min, max: INTEGER] RETURNS [ARRAY [0..1] OF INTEGER] = {
v0: REAL ¬ (u0*(length-start) + u1*(start))/length;
v1: REAL ¬ (u0*(length-stop) + u1*(stop))/length;
IF v0 > v1 THEN {t: REAL ¬ v0; v0 ¬ v1; v1 ¬ t};
RETURN [[
ClippedRound[v0-d, min, max],
ClippedRound[v1+d, min, max]
]]
};
Boxes: PROC [action: PROC[SF.Box]] ~ {
SConstantDash: PROC [start, stop: REAL] = {
f: ARRAY [0..1] OF INTEGER = Blend[vd0.y, vd1.y, df, start, stop, clipper.clipBox.min.f, clipper.clipBox.max.f];
ImagerManhattan.ClipBoxToMask[box: [min: [s0, f[0]], max: [s1, f[1]]], mask: clipper.clipMask, action: action];
};
FConstantDash: PROC [start, stop: REAL] = {
s: ARRAY [0..1] OF INTEGER = Blend[vd0.x, vd1.x, ds, start, stop, clipper.clipBox.min.s, clipper.clipBox.max.s];
ImagerManhattan.ClipBoxToMask[box: [min: [s[0], f0], max: [s[1], f1]], mask: clipper.clipMask, action: action];
};
MapDashSegments[patternLen, pattern, offset, length, IF sConstant THEN SConstantDash ELSE FConstantDash];
};
device.class.MaskBoxes[device: device, bounds: [min: [s0, f0], max: [s1, f1]], boxes: Boxes];
RETURN;
};
};
};
ENDCASE => {
EachDash: PROC [start, stop: REAL] = {
Blend: PROC [r: REAL] RETURNS [VEC] = {
s: REAL = length-r;
RETURN [[(v.p0.x*s+v.p1.x*r)/length, (v.p0.y*s+v.p1.y*r)/length]]
};
StandardMaskVector[
device: device,
p1: Blend[start],
p2: Blend[stop],
pointsToDevice: pathToDevice,
end: end,
pen: pen
];
};
MapDashSegments[patternLen, pattern, offset, length, EachDash];
RETURN;
};
};
};
device.works.MaskStroke[device: device, path: DashedPath, closed: FALSE, pathToDevice: pathToDevice, end: end, joint: joint, miterLimit: miterLimit, pen: pen];
};
MapDashSegments: PROC [patternLen: NAT, pattern: PROC [NAT] RETURNS [REAL], offset: REAL, length: REAL, action: PROC [start, stop: REAL]] = {
prev: REAL ¬ -1.0; -- To make sure we make progress
p: REAL ¬ 0.0; -- End of current dash or space
q: REAL ¬ 0.0; -- Start of current dash or space
k: NAT ¬ 0; -- Index into pattern
state: [0..1] ¬ 0;
UNTIL p > offset DO
pk: REAL = pattern[k];
IF pk < 0.0 THEN GOTO BadPattern;
p ¬ p + pk;
k ¬ k + 1;
IF k = patternLen THEN { k ¬ 0; IF p <= prev THEN GOTO NoProgress; prev ¬ p };
state ¬ 1-state;
ENDLOOP;
p ¬ p - offset;
prev ¬ -1.0;
UNTIL q >= length DO
pk: REAL = pattern[k];
IF pk < 0.0 THEN GOTO BadPattern;
IF state = 1 THEN { action[q, MIN[p, length]] };
q ¬ p;
p ¬ p + pk;
k ¬ k + 1;
IF k = patternLen THEN { k ¬ 0; IF p <= prev THEN GOTO NoProgress; prev ¬ p };
state ¬ 1-state;
ENDLOOP;
EXITS
BadPattern => {
ERROR ImagerError.Error[[$illegalArguments, "Negative element in dash pattern"]];
};
NoProgress => {
We get here if we would have ended up in an infinite loop (due to roundoff).
};
};
StandardMaskVector: PUBLIC PROC [device: Device, p1, p2: VEC, pointsToDevice: Transformation, end: ImagerStroke.EndCode, pen: ImagerPen.Pen] = {
clipper: DeviceClipper ~ device.worksState.clipper;
IF clipper.clipMask # NIL THEN {
IF end # ImagerStroke.roundEnd AND pointsToDevice.form # 0 AND (p1.x = p2.x OR p1.y = p2.y)
THEN {
This will just be a rectangle in device coordinates.
d1: VEC ¬ ImagerTransformation.Transform[m: pointsToDevice, v: p1];
d2: VEC ¬ ImagerTransformation.Transform[m: pointsToDevice, v: p2];
sConstant: BOOL ~ d1.x = d2.x;
fConstant: BOOL ~ d1.y = d2.y;
IF d1.x > d2.x THEN {t: REAL ¬ d1.x; d1.x ¬ d2.x; d2.x ¬ t};
IF d1.y > d2.y THEN {t: REAL ¬ d1.y; d1.y ¬ d2.y; d2.y ¬ t};
IF sConstant AND fConstant THEN {
zero-length vector; either warn or no-op, depending on stroke end
IF end = ImagerStroke.warningSquareEnd THEN {
ImagerStroke.SignalSquareEndWithNoDirection[];
};
RETURN;
};
BEGIN
square: BOOL ~ (end # ImagerStroke.buttEnd);
ds: REAL ~ IF square OR sConstant THEN pen.bounds.x ELSE 0.0;
df: REAL ~ IF square OR fConstant THEN pen.bounds.y ELSE 0.0;
s0: INTEGER ~ ClippedRound[d1.x-ds, clipper.clipBox.min.s, clipper.clipBox.max.s];
s1: INTEGER ~ ClippedRound[d2.x+ds, clipper.clipBox.min.s, clipper.clipBox.max.s];
f0: INTEGER ~ ClippedRound[d1.y-df, clipper.clipBox.min.f, clipper.clipBox.max.f];
f1: INTEGER ~ ClippedRound[d2.y+df, clipper.clipBox.min.f, clipper.clipBox.max.f];
IF s0 < s1 AND f0 < f1 THEN {
box: SF.Box ~ [min: [s0, f0], max: [s1, f1]];
Boxes: PROC [action: PROC[SF.Box]] ~ {
ImagerManhattan.ClipBoxToMask[box: box, mask: clipper.clipMask, action: action];
};
device.class.MaskBoxes[device: device, bounds: box, boxes: Boxes];
};
END;
}
ELSE {
StrokePath: PathProc ~ {
ImagerStroke.PathFromVector[p0: p1, p1: p2, m: pointsToDevice, pen: pen, end: end, moveTo: moveTo, lineTo: lineTo];
};
device.works.MaskFill[device: device, path: StrokePath, oddWrap: FALSE, pathToDevice: NIL];
};
};
};
MaskBitmap support
bigTranslate: REAL ¬ REAL[LAST[INT]/2];
IsAllInteger: PUBLIC PROC [m: Transformation] RETURNS [BOOL] = {
Is: PROC [r: REAL] RETURNS [BOOL] = {
IF r IN [-worryReal..worryReal] THEN {
ir: INT = Real.Round[r];
IF r = ir THEN RETURN[TRUE];
IF RealFns.AlmostEqual[r, ir, -18] THEN RETURN[TRUE];
};
RETURN[FALSE];
};
RETURN[Is[m.a] AND Is[m.b] AND ABS[m.c] <= bigTranslate AND Is[m.d] AND Is[m.e]
AND ABS[m.f] <= bigTranslate];
};
GetSampleMapClippedTransformedBox: PROC [map: SampleMap, clip: SF.Box, m: Transformation] RETURNS [SF.Box] = {
box: SF.Box = ImagerSample.GetBox[map];
IF m.form = 3
THEN {
ts: INT = Real.Round[m.c];
tf: INT = Real.Round[m.f];
clipped: SF.Box = ClippedBounds[clipBox: clip, p0: [box.min.s + ts, box.min.f + tf], p1: [box.max.s + ts, box.max.f + tf]];
RETURN[clipped]
}
ELSE {
p0: VEC ~ ImagerTransformation.Transform[m, [box.min.s, box.min.f]];
p1: VEC ~ ImagerTransformation.Transform[m, [box.max.s, box.max.f]];
clipped: SF.Box = ClippedBounds[clipBox: clip, p0: [Real.Round[p0.x], Real.Round[p0.y]], p1: [Real.Round[p1.x], Real.Round[p1.y]]];
RETURN[clipped]
};
};
IntVec: TYPE = RECORD [s, f: INT];
ClippedBounds: PROC [clipBox: SF.Box, p0, p1: IntVec] RETURNS [SF.Box] = {
Computes bounding box of p0 and p1, clipped against clipBox.
IF p0.s > p1.s THEN {t: INT ¬ p0.s; p0.s ¬ p1.s; p1.s ¬ t};
IF p0.f > p1.f THEN {t: INT ¬ p0.f; p0.f ¬ p1.f; p1.f ¬ t};
p0.s ¬ MAX[p0.s, clipBox.min.s];
p0.f ¬ MAX[p0.f, clipBox.min.f];
p1.s ¬ MIN[p1.s, clipBox.max.s];
p1.f ¬ MIN[p1.f, clipBox.max.f];
IF p0.s < p1.s AND p0.f < p1.f
THEN RETURN[[min: [INTEGER[p0.s], INTEGER[p0.f]], max: [INTEGER[p1.s], INTEGER[p1.f]]]]
ELSE RETURN[[min: clipBox.min, max: clipBox.min]];
};
DMaskBitmap: PUBLIC -- ImagerDeviceProcs -- PROC [device: Device, bitmap: ImagerSample.SampleMap, delta: SF.Vec, bounds: SF.Box, boxes: SF.BoxGenerator] = {
EachBox: PROC [box: SF.Box] = {
visibleBitmap: SampleMap ~ ImagerSample.ReIndex[map: bitmap, delta: delta, box: box];
visibleBounds: SF.Box ~ ImagerSample.GetBox[visibleBitmap];
LittleBoxes: SF.BoxGenerator = {
BoxGenerator: TYPE ~ PROC [boxAction: BoxAction];
ImagerSample.BoxesFromBitmap[map: visibleBitmap, boxAction: boxAction];
};
IF SF.Nonempty[visibleBounds] THEN device.class.MaskBoxes[device: device, bounds: visibleBounds, boxes: LittleBoxes];
TRUSTED { ImagerSample.ReleaseDescriptor[visibleBitmap] };
};
boxes[EachBox];
};
ResampleCellProc: TYPE ~ UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [nz: CARD];
MaskBitmapHelp: UNSAFE PROC [device: Device, bitmap: SampleMap, bitsToDevice: Transformation, sSizeSrc, fSizeSrc: NAT, resampleCell: ResampleCellProc] = UNCHECKED {
This does the bookkeeping needed to run a cell-resampling primitive over a source. The resampleCell primitive knows about the size of the source and destination. The values of sSizeSrc and fSizeSrc should be selected so that both fSizeSrc and fSizeDst work out to be multiples of BITS[WORD]. The resampleCell primitive should return a non-zero value whenever the cell contains any non-zero bits. When the resampleCell primitive returns zero, it need not have stored the zero bits.
vDst: VEC ~ ImagerTransformation.TransformVec[bitsToDevice, [sSizeSrc, fSizeSrc]];
sDeltaDst: INT ~ RealInline.MCRound[vDst.x];
fDeltaDst: INT ~ RealInline.MCRound[vDst.y];
sSizeDst: NAT ~ ABS[sDeltaDst];
fSizeDst: NAT ~ ABS[fDeltaDst];
check: [0..0] ~ Basics.BITOR[fSizeSrc, fSizeDst] MOD BITS[WORD];
clipper: DeviceClipper ~ device.worksState.clipper;
clip: ImagerManhattan.Polygon ¬ ImagerManhattan.DestructiveIntersection[
ImagerManhattan.CreateFromBox[
GetSampleMapClippedTransformedBox[bitmap, clipper.clipBox, bitsToDevice]
],
clipper.clipMask
];
IF clip # NIL THEN {
deltaSrcForSlowDst: VEC ~ ImagerTransformation.InverseTransformVec[bitsToDevice, [sSizeDst, 0]];
deltaSrcForFastDst: VEC ~ ImagerTransformation.InverseTransformVec[bitsToDevice, [0, fSizeDst]];
sDeltaSrcForSlowDst: INT ~ RealInline.MCRound[deltaSrcForSlowDst.x];
fDeltaSrcForSlowDst: INT ~ RealInline.MCRound[deltaSrcForSlowDst.y];
sDeltaSrcForFastDst: INT ~ RealInline.MCRound[deltaSrcForFastDst.x];
fDeltaSrcForFastDst: INT ~ RealInline.MCRound[deltaSrcForFastDst.y];
Boxes: SAFE PROC [action: PROC [SF.Box]] = CHECKED {
FOR tail: LIST OF SF.Box ¬ clip, tail.rest UNTIL tail = NIL DO
action[tail.first];
ENDLOOP;
};
box: SF.Box ~ ImagerManhattan.BoundingBox[clip];
sSize: NAT ~ box.max.s-box.min.s;
fSize: NAT ~ box.max.f-box.min.f;
sSizePadded: NAT ~ ((sSize+(sSizeDst-1))/sSizeDst)*sSizeDst;
fSizePadded: NAT ~ ((fSize+(fSizeDst-1))/fSizeDst)*fSizeDst;
buffer: ImagerSample.RasterSampleMap ~ ImagerSample.ObtainScratchMap[
[min: box.min, max: [box.min.s+sSizePadded, box.max.f+fSizePadded]]
];
sMinSrcOriented: INT ~ MIN[sDeltaSrcForSlowDst, sDeltaSrcForFastDst];
fMinSrcOriented: INT ~ MIN[fDeltaSrcForSlowDst, fDeltaSrcForFastDst];
srcOriented: ImagerSample.RasterSampleMap ~ ImagerSample.ObtainScratchMap[[min: [sMinSrcOriented, fMinSrcOriented], max: [s: sMinSrcOriented+sSizeSrc, f: fMinSrcOriented+fSizeSrc]]];
srcOrientedPtr: POINTER TO WORD ~ ImagerSample.GetBase[srcOriented].word;
dstPtr0: POINTER TO WORD ¬ ImagerSample.GetBase[buffer].word;
fUnitsDst: CARD ~ fSizeDst/BITS[UNIT];
dstupl: CARD ~ ImagerSample.GetBitsPerLine[buffer]/BITS[UNIT];
srcOrientedUpl: CARD ¬ fSizeSrc/BITS[UNIT];
p0: VEC ~ ImagerTransformation.InverseTransform[bitsToDevice, [box.min.s, box.min.f]];
sSrc0: INTEGER ¬ Real.Round[p0.x];
fSrc0: INTEGER ¬ Real.Round[p0.y];
this: ImagerManhattan.Polygon ¬ NIL;
live, lastLive: ImagerManhattan.Polygon ¬ NIL;
goodBase: POINTER ¬ NIL; -- Try to pull directly from buffer when possible
goodSrcBox: SF.Box ¬ []; -- denotes legal values for [sSrc, fSrc] for direct access
goodUpl: CARDINAL ¬ 0; -- units per source line for direct access
WITH bitmap SELECT FROM
raster: ImagerSample.RasterSampleMap => {
bpl: CARDINAL ¬ raster.GetBitsPerLine;
IF bpl MOD BITS[WORD] = 0 THEN {
goodUpl ¬ bpl / BITS[UNIT];
goodBase ¬ raster.GetBase.word;
goodSrcBox ¬ raster.GetBox;
goodSrcBox.min.s ¬ goodSrcBox.min.s-sMinSrcOriented;
goodSrcBox.min.f ¬ goodSrcBox.min.f-fMinSrcOriented-raster.GetBase.bit;
goodSrcBox.max.s ¬ goodSrcBox.max.s-(sMinSrcOriented+sSizeSrc);
goodSrcBox.max.f ¬ goodSrcBox.max.f-(fMinSrcOriented+fSizeSrc);
};
};
ENDCASE;
FOR s: INTEGER ¬ box.min.s, s + sSizeDst UNTIL s >= box.max.s DO
dstPtr: POINTER TO WORD ¬ dstPtr0 + (s-box.min.s)*dstupl;
fLiveMin: INTEGER ¬ INTEGER.LAST;
fLiveMax: INTEGER ¬ INTEGER.FIRST;
sSrc: INTEGER ¬ sSrc0;
fSrc: INTEGER ¬ fSrc0;
FOR f: INTEGER ¬ box.min.f, f + fSizeDst UNTIL f >= box.max.f DO
nz: WORD ¬ 0;
fSrcOffset: CARDINAL;
IF goodBase # NIL
AND sSrc IN [goodSrcBox.min.s..goodSrcBox.max.s]
AND fSrc IN [goodSrcBox.min.f..goodSrcBox.max.f]
AND (fSrcOffset ¬ LOOPHOLE[fSrc-goodSrcBox.min.f, CARDINAL]) MOD BITS[WORD] = 0
THEN {
Fast case - do the resampling directly out of the source.
src: POINTER ~ goodBase + LOOPHOLE[sSrc-goodSrcBox.min.s, CARDINAL]*goodUpl + fSrcOffset/BITS[UNIT];
nz ¬ resampleCell[src: src, srcupl: goodUpl, dst: dstPtr, dstupl: dstupl];
}
ELSE {
Otherwise need to make a copy of the cell to take care of alignment and/or edge effects
ImagerSample.Clear[srcOriented];
ImagerSample.Transfer[dst: srcOriented, src: bitmap, delta: [-sSrc, -fSrc]];
nz ¬ resampleCell[src: srcOrientedPtr, srcupl: srcOrientedUpl, dst: dstPtr, dstupl: dstupl];
};
IF nz # 0 THEN {
IF f < fLiveMin THEN fLiveMin ¬ f ELSE IF f > fLiveMax THEN {
new: ImagerManhattan.Polygon ~ ImagerManhattan.CreateFromBox[[[s, fLiveMin], [s+sSizeDst, fLiveMax]]];
IF lastLive = NIL THEN live ¬ lastLive ¬ new ELSE lastLive ¬ lastLive.rest ¬ new;
fLiveMin ¬ fLiveMax ¬ f;
};
IF f+fSizeDst > fLiveMax THEN fLiveMax ¬ f+fSizeDst;
};
dstPtr ¬ dstPtr + fUnitsDst;
sSrc ¬ sSrc + sDeltaSrcForFastDst;
fSrc ¬ fSrc + fDeltaSrcForFastDst;
ENDLOOP;
sSrc0 ¬ sSrc0 + sDeltaSrcForSlowDst;
fSrc0 ¬ fSrc0 + fDeltaSrcForSlowDst;
IF fLiveMin < fLiveMax THEN {
new: ImagerManhattan.Polygon ~ ImagerManhattan.CreateFromBox[[[s, fLiveMin], [s+sSizeDst, fLiveMax]]];
IF lastLive = NIL THEN live ¬ lastLive ¬ new ELSE lastLive ¬ lastLive.rest ¬ new;
};
ENDLOOP;
clip ¬ ImagerManhattan.DestructiveIntersection[clip, live];
ImagerSample.Tile[map: buffer, tile: ImagerSample.TileFromStipple[41H], function: [or, null]]; -- for debug.
(IF device.state.allow.bitmap THEN device.class.MaskBitmap ELSE DMaskBitmap)[
device: device,
bitmap: buffer,
delta: [0, 0],
bounds: box, boxes: Boxes
];
ImagerSample.ReleaseScratchMap[buffer];
ImagerSample.ReleaseScratchMap[srcOriented];
ImagerManhattan.Destroy[clip];
ImagerManhattan.Destroy[live];
ImagerManhattan.Destroy[this];
};
};
Special cases for MaskBitmap
SpecialMaskBitmapProc: TYPE ~ PROC [device: Device, bitmap: SampleMap, bitsToDevice: Transformation] RETURNS [BOOL ¬ FALSE];
SMB: TYPE ~ REF SpecialMaskBitmapRep;
SpecialMaskBitmapRep: TYPE ~ RECORD [
templateT: Transformation,
specialMaskBitmap: SpecialMaskBitmapProc
];
maxValidForm: NAT = 10; -- see ImagerTransformationImpl.CheckForm
smbRegistry: ARRAY [0..maxValidForm] OF LIST OF SMB ¬ ALL[NIL];
RegisterSpecialMaskBitmap: ENTRY PROC [scale: REAL, rotation: REAL, proc: SpecialMaskBitmapProc] RETURNS [SMB] ~ {
m: Transformation ~ ImagerTransformation.Scale[scale].PostRotate[rotation];
form: [0..maxValidForm] ~ m.form;
smb: SMB ~ NEW[SpecialMaskBitmapRep ¬ [m, proc]];
smbRegistry[form] ¬ CONS[smb, smbRegistry[form]];
RETURN [smb]
};
Easy case and rotations
BlockRotate180: UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [WORD] ~ UNCHECKED {
Rotates a 32 - bit square block by 180 degrees.
nz: WORD ¬ 0;
d: POINTER TO WORD32 ¬ dst + 32 * dstupl;
s: POINTER TO WORD32 ¬ src;
m8: WORD32 ¬ 00FF00FFH;
m4: WORD32 ¬ 0F0F0F0FH;
m2: WORD32 ¬ 33333333H;
m1: WORD32 ¬ 55555555H;
w: WORD32;
THROUGH [0..32) DO
d ¬ d - dstupl;
w ¬ s^;
nz ¬ Basics.BITOR[nz, w];
w ¬ w/2**16 + w*2**16;
w ¬ Basics.BITAND[m8, w/2**8] + Basics.BITAND[Basics.BITNOT[m8], w*2**8];
w ¬ Basics.BITAND[m4, w/2**4] + Basics.BITAND[Basics.BITNOT[m4], w*2**4];
w ¬ Basics.BITAND[m2, w/2**2] + Basics.BITAND[Basics.BITNOT[m2], w*2**2];
w ¬ Basics.BITAND[m1, w/2**1] + Basics.BITAND[Basics.BITNOT[m1], w*2**1];
d­ ¬ w;
s ¬ s + srcupl;
ENDLOOP;
RETURN [nz]
};
mb1to1: SMB ~ RegisterSpecialMaskBitmap[1.0, 0, MB1to1];
MB1to1: SpecialMaskBitmapProc ~ {
clipper: DeviceClipper ~ device.worksState.clipper;
box: SF.Box = GetSampleMapClippedTransformedBox[bitmap, clipper.clipBox, bitsToDevice];
Boxes: PROC [action: PROC [SF.Box]] = {
ImagerManhattan.ClipBoxToMask[box: box, mask: clipper.clipMask, action: action]
};
IF SF.Nonempty[box] THEN {
(IF device.state.allow.bitmap THEN device.class.MaskBitmap ELSE DMaskBitmap)[
device: device,
bitmap: bitmap,
delta: [s: Real.Round[bitsToDevice.c], f: Real.Round[bitsToDevice.f]],
bounds: box, boxes: Boxes];
};
RETURN [TRUE]
};
mb1to1L: SMB ~ RegisterSpecialMaskBitmap[1.0, 90, MB1to1L];
MB1to1L: SpecialMaskBitmapProc ~ TRUSTED {
RotateLeft: UNSAFE PROC [src: POINTER TO WORD, srcPitch: INT, dst: POINTER TO WORD, dstPitch: INT] RETURNS [WORD] ~ BlockRotate.RotateLeft;
MaskBitmapHelp[device, bitmap, bitsToDevice, 32, 32, LOOPHOLE[RotateLeft]];
RETURN [TRUE]
};
mb1to1R: SMB ~ RegisterSpecialMaskBitmap[1.0, -90, MB1to1R];
MB1to1R: SpecialMaskBitmapProc ~ TRUSTED {
RotateRight: UNSAFE PROC [src: POINTER TO WORD, srcPitch: INT, dst: POINTER TO WORD, dstPitch: INT] RETURNS [WORD] ~ BlockRotate.RotateRight;
MaskBitmapHelp[device, bitmap, bitsToDevice, 32, 32, LOOPHOLE[RotateRight]];
RETURN [TRUE]
};
mb1to1I: SMB ~ RegisterSpecialMaskBitmap[1.0, 180, MB1to1I];
MB1to1I: SpecialMaskBitmapProc ~ TRUSTED {
MaskBitmapHelp[device, bitmap, bitsToDevice, 32, 32, BlockRotate180];
RETURN [TRUE]
};
3/2 scale
resampleByte2to3: PACKED ARRAY BYTE OF [0..2**12) ¬ ALL[0];
initByte2to3: BOOL ~ InitByte2to3[];
InitByte2to3: PROC RETURNS [BOOL] ~ {
Precomputes a table for 2 -> 3 resampling.
b: ARRAY [0..4) OF [0..8) ~ [0, 3, 6, 7];
FOR i: [0..4) IN [0..4) DO
FOR j: [0..4) IN [0..4) DO
FOR k: [0..4) IN [0..4) DO
FOR l: [0..4) IN [0..4) DO
resampleByte2to3[(((i*4+j)*4+k)*4+l)] ¬ (((b[i]*8+b[j])*8+b[k])*8+b[l]);
ENDLOOP;
ENDLOOP;
ENDLOOP;
ENDLOOP;
RETURN [TRUE]
};
ResampleCell2to3: UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [WORD] ~ UNCHECKED {
x: POINTER TO PACKED ARRAY BYTE OF [0..2**12) ¬ @resampleByte2to3;
srcP: POINTER TO PACKED ARRAY [0..2) OF PACKED ARRAY[0..4) OF BYTE ¬ LOOPHOLE[src];
dstP: POINTER TO PACKED ARRAY [0..3) OF Basics.FWORD ¬ LOOPHOLE[dst];
src0: PACKED ARRAY[0..4) OF BYTE ¬ srcP[0];
src1: PACKED ARRAY[0..4) OF BYTE ¬ srcP[1];
dst00, dst01, dst02: CARD32;
dst20, dst21, dst22: CARD32;
nz: CARD32 ¬ Basics.BITOR[LOOPHOLE[src0], LOOPHOLE[src1]];
IF nz = 0
THEN { dst00 ¬ dst01 ¬ dst02 ¬ 0 }
ELSE {
dst00 ¬ x[src0[0]]*(2**20) + x[src0[1]]*(2**8) + (dst01 ¬ x[src0[2]])/(2**4);
dst01 ¬ dst01*(2**28) + x[src0[3]]*(2**16) + x[src1[0]]*(2**4) + (dst02 ¬ x[src1[1]])/(2**8);
dst02 ¬ dst02*(2**24) + x[src1[2]]*(2**12) + x[src1[3]];
};
dstP[0] ¬ Basics.FFromCard32[dst00];
dstP[1] ¬ Basics.FFromCard32[dst01];
dstP[2] ¬ Basics.FFromCard32[dst02];
srcP ¬ srcP + srcupl;
src0 ¬ srcP[0];
src1 ¬ srcP[1];
dst20 ¬ Basics.BITOR[LOOPHOLE[src0], LOOPHOLE[src1]];
nz ¬ Basics.BITOR[nz, dst20];
IF dst20 = 0
THEN { dst20 ¬ dst21 ¬ dst22 ¬ 0 }
ELSE {
dst20 ¬ x[src0[0]]*(2**20) + x[src0[1]]*(2**8) + (dst21 ¬ x[src0[2]])/(2**4);
dst21 ¬ dst21*(2**28) + x[src0[3]]*(2**16) + x[src1[0]]*(2**4) + (dst22 ¬ x[src1[1]])/(2**8);
dst22 ¬ dst22*(2**24) + x[src1[2]]*(2**12) + x[src1[3]];
};
dstP ¬ dstP + dstupl;
dstP[0] ¬ Basics.FFromCard32[Basics.BITOR[dst00, dst20]];
dstP[1] ¬ Basics.FFromCard32[Basics.BITOR[dst01, dst21]];
dstP[2] ¬ Basics.FFromCard32[Basics.BITOR[dst02, dst22]];
dstP ¬ dstP + dstupl;
dstP[0] ¬ Basics.FFromCard32[dst20];
dstP[1] ¬ Basics.FFromCard32[dst21];
dstP[2] ¬ Basics.FFromCard32[dst22];
RETURN [nz]
};
mb2to3: SMB ~ RegisterSpecialMaskBitmap[1.5, 0, MB2to3];
MB2to3: SpecialMaskBitmapProc ~ TRUSTED {
MaskBitmapHelp[device, bitmap, bitsToDevice, 2, 64, ResampleCell2to3];
RETURN [TRUE]
};
mb2to3: SMB ~ RegisterSpecialMaskBitmap[1.5, 0, FaxyMaskBitmap];
FaxyMaskBitmap: SpecialMaskBitmapProc ~ {
Especially interesting for a 200 -> 300 resolution conversion
It turns out that MaskBitmapHelp entails a little too much overhead for certain applications; hence we retain this special case.
srcPtr: POINTER ¬ NIL;
srcupl: NAT ¬ 0;
WITH bitmap SELECT FROM
bitmap: ImagerSample.RasterSampleMap => {
base: ImagerSample.BitAddress ~ ImagerSample.GetBase[bitmap];
bpl: NAT ~ ImagerSample.GetBitsPerLine[bitmap];
IF (base.bit = 0 AND bpl MOD BITS[WORD] = 0) THEN {
srcPtr ¬ LOOPHOLE[base.word];
srcupl ¬ bpl/BITS[UNIT];
};
};
ENDCASE;
TRUSTED {
clipper: DeviceClipper ~ device.worksState.clipper;
tmpBitsToDevice: Transformation ~ ImagerTransformation.Translate[[bitsToDevice.c, bitsToDevice.f]];
srcBox: SF.Box ¬ ImagerSample.GetBox[bitmap];
srcSize: SF.Vec ¬ SF.Size[srcBox];
dstMin: SF.Vec ¬ [srcBox.min.s*3/2, srcBox.min.f*3/2];
dstSize: SF.Vec ¬ [
s: MakeMultiple[(srcSize.s*3+1)/2, 3],
f: MakeMultiple[(srcSize.f*3+1)/2, 32*3]];
tmpBox: SF.Box ~ [min: dstMin, max: [dstMin.s+dstSize.s, dstMin.f+dstSize.f]];
tmp: ImagerSample.RasterSampleMap ~ ImagerSample.ObtainScratchMap[tmpBox];
dstPtr: POINTER ¬ ImagerSample.GetBase[tmp].word;
dstupl: CARD ~ ImagerSample.GetBitsPerLine[tmp]/BITS[UNIT];
buf: ImagerSample.RasterSampleMap ~ ImagerSample.ObtainScratchMap[[max: [2, 2*32]]];
nz: CARD ¬ 0;
FOR s: INTEGER ¬ 0, s + 2 UNTIL s >= srcSize.s DO
srcOffset: CARD ¬ 0;
dstOffset: CARD ¬ 0;
FOR f: INTEGER ¬ 0, f+64 UNTIL f >= srcSize.f DO
IF srcPtr = NIL OR (s+2) > srcSize.s OR (f+64) > srcSize.f
THEN {
ImagerSample.Clear[buf];
ImagerSample.Transfer[dst: buf, src: bitmap, delta: [-(srcBox.min.s+s), -(srcBox.min.f+f)]];
nz ¬ Basics.BITOR[nz, ResampleCell2to3[
src: LOOPHOLE[buf.GetBase.word], srcupl: SIZE[PACKED ARRAY [0..8) OF BYTE],
dst: dstPtr+dstOffset, dstupl: dstupl
]];
}
ELSE {
nz ¬ Basics.BITOR[nz, ResampleCell2to3[
src: srcPtr+srcOffset, srcupl: srcupl,
dst: dstPtr+dstOffset, dstupl: dstupl
]];
};
srcOffset ¬ srcOffset + SIZE[PACKED ARRAY [0..8) OF BYTE];
dstOffset ¬ dstOffset + SIZE[PACKED ARRAY [0..12) OF BYTE];
ENDLOOP;
IF srcPtr # NIL THEN srcPtr ¬ srcPtr + 2*srcupl;
dstPtr ¬ dstPtr + 3*dstupl;
ENDLOOP;
IF nz # 0 THEN {
IF NOT MB1to1[device, tmp, tmpBitsToDevice] THEN ERROR;
};
ImagerSample.ReleaseScratchMap[buf];
ImagerSample.ReleaseScratchMap[tmp];
ImagerTransformation.Destroy[tmpBitsToDevice];
RETURN[TRUE];
};
};
mb2to3L: SMB ~ RegisterSpecialMaskBitmap[1.5, 90, MB2to3L];
MB2to3L: SpecialMaskBitmapProc ~ TRUSTED {
Line: TYPE ~ PACKED ARRAY [0..2*BITS[WORD]) OF [0..1];
buf: ARRAY [0..BITS[WORD]) OF Line;
RRCell2to3: UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [WORD] ~ UNCHECKED {
bp: POINTER ¬ @buf;
bufupl: CARD ~ UNITS[Line];
dstupl3: CARD ~ 3*dstupl;
nz0: CARD ¬ BlockRotate.RotateLeft[src: src, srcPitch: srcupl, dst: bp, dstPitch: UNITS[Line]];
nz1: CARD ¬ BlockRotate.RotateLeft[src: src + srcupl*BITS[WORD], srcPitch: srcupl, dst: bp + UNITS[WORD], dstPitch: UNITS[Line]];
IF Basics.BITOR[nz0, nz1] = 0 THEN RETURN [0];
THROUGH [0..BITS[WORD]/2) DO
[] ¬ ResampleCell2to3[src: bp, srcupl: UNITS[Line], dst: dst, dstupl: dstupl];
bp ¬ bp + 2*UNITS[Line];
dst ¬ dst + dstupl3;
ENDLOOP;
RETURN [1]
};
MaskBitmapHelp[device, bitmap, bitsToDevice, 2*BITS[WORD], BITS[WORD], RRCell2to3];
RETURN [TRUE]
};
mb2to3R: SMB ~ RegisterSpecialMaskBitmap[1.5, -90, MB2to3R];
MB2to3R: SpecialMaskBitmapProc ~ TRUSTED {
Line: TYPE ~ PACKED ARRAY [0..2*BITS[WORD]) OF [0..1];
buf: ARRAY [0..BITS[WORD]) OF Line;
RRCell2to3: UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [WORD] ~ UNCHECKED {
bp: POINTER ¬ @buf;
bufupl: CARD ~ UNITS[Line];
dstupl3: CARD ~ 3*dstupl;
nz0: CARD ¬ BlockRotate.RotateRight[src: src + srcupl*BITS[WORD], srcPitch: srcupl, dst: bp, dstPitch: UNITS[Line]];
nz1: CARD ¬ BlockRotate.RotateRight[src: src, srcPitch: srcupl, dst: bp + UNITS[WORD], dstPitch: UNITS[Line]];
IF Basics.BITOR[nz0, nz1] = 0 THEN RETURN [0];
THROUGH [0..BITS[WORD]/2) DO
[] ¬ ResampleCell2to3[src: bp, srcupl: UNITS[Line], dst: dst, dstupl: dstupl];
bp ¬ bp + 2*UNITS[Line];
dst ¬ dst + dstupl3;
ENDLOOP;
RETURN [1]
};
MaskBitmapHelp[device, bitmap, bitsToDevice, 2*BITS[WORD], BITS[WORD], RRCell2to3];
RETURN [TRUE]
};
mb2to3I: SMB ~ RegisterSpecialMaskBitmap[1.5, 180, MB2to3I];
MB2to3I: SpecialMaskBitmapProc ~ TRUSTED {
Line: TYPE ~ PACKED ARRAY [0..2*BITS[WORD]) OF [0..1];
buf: ARRAY [0..BITS[WORD]) OF Line;
ICell2to3: UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [WORD] ~ UNCHECKED {
bp: POINTER ¬ @buf;
nz0: CARD ¬ BlockRotate180[src: src, srcupl: srcupl, dst: bp+UNITS[WORD], dstupl: UNITS[Line]];
nz1: CARD ¬ BlockRotate180[src: src+UNITS[WORD], srcupl: srcupl, dst: bp, dstupl: UNITS[Line]];
bufupl: CARD ~ UNITS[Line];
dstupl3: CARD ~ 3*dstupl;
IF Basics.BITOR[nz0, nz1] = 0 THEN RETURN [0];
THROUGH [0..BITS[WORD]/2) DO
[] ¬ ResampleCell2to3[src: bp, srcupl: UNITS[Line], dst: dst, dstupl: dstupl];
bp ¬ bp + 2*UNITS[Line];
dst ¬ dst + dstupl3;
ENDLOOP;
RETURN [1]
};
MaskBitmapHelp[device, bitmap, bitsToDevice, BITS[WORD], 2*BITS[WORD], ICell2to3];
RETURN [TRUE]
};
2/1 scale
ResampleCell2to1: UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [WORD] ~ UNCHECKED {
srcP0: POINTER TO WORD ¬ LOOPHOLE[src];
srcP1: POINTER TO WORD ¬ LOOPHOLE[LOOPHOLE[srcP0, CARD]+srcupl];
dstP: POINTER TO WORD ¬ LOOPHOLE[dst];
w0: WORD ¬ Basics.BITOR[srcP0­, srcP1­];
w1: WORD ¬ Basics.BITOR[(srcP0+SIZE[WORD])­, (srcP1+SIZE[WORD])­];
m: WORD ¬ 55555555h;
xyxyxyxy -> 0a0b0c0d [in each byte, each letter = 1 bit]
w0 ¬ Basics.BITOR[Basics.BITAND[w0, m], Basics.BITAND[w0 / 2, m]];
w1 ¬ Basics.BITOR[Basics.BITAND[w1, m], Basics.BITAND[w1 / 2, m]];
0a0b0c0d -> 00ab00cd [in each byte, each letter = 1 bit]
m ¬ 33333333h;
w0 ¬ Basics.BITOR[Basics.BITAND[w0, m], Basics.BITAND[w0 / 2, m]];
w1 ¬ Basics.BITOR[Basics.BITAND[w1, m], Basics.BITAND[w1 / 2, m]];
00ab00cd -> 0000abcd [in each byte, each letter = 1 bit]
m ¬ 0f0f0f0fh;
w0 ¬ Basics.BITOR[Basics.BITAND[w0, m], Basics.BITAND[w0 / 4, m]];
w1 ¬ Basics.BITOR[Basics.BITAND[w1, m], Basics.BITAND[w1 / 4, m]];
0A0B0C0D -> 00AB00CD [in each word, each letter = 4 bits]
m ¬ 00ff00ffh;
w0 ¬ Basics.BITOR[Basics.BITAND[w0, m], Basics.BITAND[w0 / 16, m]];
w1 ¬ Basics.BITOR[Basics.BITAND[w1, m], Basics.BITAND[w1 / 16, m]];
00AB00CD -> 0000ABCD [in each word, each letter = 4 bits]
m ¬ 0000ffffh;
w0 ¬ Basics.BITOR[Basics.BITAND[w0, m], Basics.BITAND[w0 / 256, m]];
w1 ¬ Basics.BITOR[Basics.BITAND[w1, m], Basics.BITAND[w1 / 256, m]];
w0 ¬ w0 * (256*256) + w1;
dstP­ ¬ w0;
RETURN [w0];
};
mb2to1: SMB ~ RegisterSpecialMaskBitmap[0.5, 0, MB2to1];
MB2to1: SpecialMaskBitmapProc ~ TRUSTED {
MaskBitmapHelp[device, bitmap, bitsToDevice, 2, 64, ResampleCell2to1];
RETURN [TRUE]
};
mb2to1L: SMB ~ RegisterSpecialMaskBitmap[0.5, 90, MB2to1L];
MB2to1L: SpecialMaskBitmapProc ~ TRUSTED {
Line: TYPE = ARRAY [0..1] OF WORD;
Buf: TYPE ~ ARRAY [0..BITS[WORD]) OF Line;
buf: Buf;
RRCell2to1: UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [WORD] ~ UNCHECKED {
bp: POINTER ¬ @buf;
bufupl: CARD ~ UNITS[Line];
nz0: CARD ¬ BlockRotate.RotateLeft[src: src, srcPitch: srcupl, dst: bp, dstPitch: UNITS[Line]];
nz1: CARD ¬ BlockRotate.RotateLeft[src: src + srcupl*BITS[WORD], srcPitch: srcupl, dst: bp + UNITS[WORD], dstPitch: UNITS[Line]];
IF Basics.BITOR[nz0, nz1] = 0 THEN RETURN [0];
THROUGH [0..BITS[WORD]/2) DO
[] ¬ ResampleCell2to1[src: bp, srcupl: UNITS[Line], dst: dst, dstupl: dstupl];
bp ¬ bp + 2*UNITS[Line];
dst ¬ dst + dstupl;
ENDLOOP;
RETURN [1];
};
MaskBitmapHelp[device, bitmap, bitsToDevice, BITS[Line], BITS[WORD], RRCell2to1];
RETURN [TRUE]
};
mb2to1R: SMB ~ RegisterSpecialMaskBitmap[0.5, -90, MB2to1R];
MB2to1R: SpecialMaskBitmapProc ~ TRUSTED {
Line: TYPE = ARRAY [0..1] OF WORD;
Buf: TYPE ~ ARRAY [0..BITS[WORD]) OF Line;
buf: Buf;
RRCell2to1: UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [WORD] ~ UNCHECKED {
bp: POINTER ¬ @buf;
bufupl: CARD ~ UNITS[Line];
nz0: CARD ¬ BlockRotate.RotateRight[src: src + srcupl*BITS[WORD], srcPitch: srcupl, dst: bp, dstPitch: UNITS[Line]];
nz1: CARD ¬ BlockRotate.RotateRight[src: src, srcPitch: srcupl, dst: bp + UNITS[WORD], dstPitch: UNITS[Line]];
IF Basics.BITOR[nz0, nz1] = 0 THEN RETURN [0];
THROUGH [0..BITS[WORD]/2) DO
[] ¬ ResampleCell2to1[src: bp, srcupl: UNITS[Line], dst: dst, dstupl: dstupl];
bp ¬ bp + 2*UNITS[Line];
dst ¬ dst + dstupl;
ENDLOOP;
RETURN [1]
};
MaskBitmapHelp[device, bitmap, bitsToDevice, BITS[Line], BITS[WORD], RRCell2to1];
RETURN [TRUE]
};
mb2to1I: SMB ~ RegisterSpecialMaskBitmap[0.5, 180, MB2to1I];
MB2to1I: SpecialMaskBitmapProc ~ TRUSTED {
Line: TYPE = ARRAY [0..1] OF WORD;
Buf: TYPE ~ ARRAY [0..BITS[WORD]) OF Line;
buf: Buf;
ICell2to1: UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [WORD] ~ UNCHECKED {
bp: POINTER ¬ @buf;
nz0: CARD ¬ BlockRotate180[src: src, srcupl: srcupl, dst: bp+UNITS[WORD], dstupl: UNITS[Line]];
nz1: CARD ¬ BlockRotate180[src: src+UNITS[WORD], srcupl: srcupl, dst: bp, dstupl: UNITS[Line]];
bufupl: CARD ~ UNITS[Line];
IF Basics.BITOR[nz0, nz1] = 0 THEN RETURN [0];
THROUGH [0..BITS[WORD]/2) DO
[] ¬ ResampleCell2to1[src: bp, srcupl: UNITS[Line], dst: dst, dstupl: dstupl];
bp ¬ bp + 2*UNITS[Line];
dst ¬ dst + dstupl;
ENDLOOP;
RETURN [1]
};
MaskBitmapHelp[device, bitmap, bitsToDevice, BITS[WORD], BITS[Line], ICell2to1];
RETURN [TRUE]
};
4/5 scale
ResampleCell4to5: UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [WORD] ~ UNCHECKED {
bpw: NAT = BITS[WORD];
sSizeSrc: NAT = 4;
fSizeSrc: NAT = 4*bpw;
sSizeDst: NAT = 5;
fSizeDst: NAT = 5*bpw;
SrcWord: TYPE ~ PACKED ARRAY [0..BYTES[WORD]) OF BYTE;
SrcLine: TYPE ~ PACKED ARRAY [0..fSizeSrc/bpw) OF Basics.FWORD;
srcP: POINTER TO SrcLine ¬ LOOPHOLE[src];
dstP: POINTER TO PACKED ARRAY [0..fSizeDst/bpw) OF Basics.FWORD ¬ LOOPHOLE[dst];
Fetch: UNSAFE PROC [k: [0..fSizeSrc/bpw)] RETURNS [CARD32] ~ INLINE {RETURN [Basics.Card32FromF[srcP[k]]]};
srcA: CARD32 ¬ Fetch[0];
srcB: CARD32 ¬ Fetch[1];
Munge: SAFE PROC [w: CARD32, m: CARD32, s: [0..31)] RETURNS [CARD32]
~ CHECKED INLINE{OPEN Basics; RETURN[
BITOR[BITAND[w, BITNOT[m]], BITRSHIFT[BITAND[w, m], s]]
]};
m4: CARD32 ~ 0000FFFFH;
m2: CARD32 ~ Munge[00FF00FFH, m4, 4];
m1: CARD32 ~ Munge[Munge[0F0F0F0FH, m4, 4], 00FF00FFH, 2];
Spread: SAFE PROC [w: CARD32] RETURNS [CARD32] ~ CHECKED INLINE {
w ¬ Munge[w, m4, 4];
w ¬ Munge[w, m2, 2];
w ¬ Munge[w, m1, 1];
RETURN [Basics.BITOR[w, w/2]]
};
dstA0, dstA1, dstA2, dstA3, dstA4: CARD32 ¬ 0;
dstB0, dstB1, dstB2, dstB3, dstB4: CARD32;
THROUGH [0..4) DO
srcA ¬ Fetch[0];
dstB0 ¬ Spread[srcA];
srcB ¬ Fetch[1];
The rest of this in not quite right. However...
dstB1 ¬ Spread[srcA * (2**(32-6)) + srcB / (2**6)];
srcA ¬ Fetch[2];
dstB2 ¬ Spread[srcB * (2**(32-13)) + srcA / (2**13)];
srcB ¬ Fetch[3];
dstB3 ¬ Spread[srcA * (2**(32-19)) + srcB / (2**19)];
dstB4 ¬ Spread[srcB * (2**(32-26))];
dstP[0] ¬ Basics.FFromCard32[Basics.BITOR[dstA0, dstB0]];
dstP[1] ¬ Basics.FFromCard32[Basics.BITOR[dstA1, dstB1]];
dstP[2] ¬ Basics.FFromCard32[Basics.BITOR[dstA2, dstB2]];
dstP[3] ¬ Basics.FFromCard32[Basics.BITOR[dstA3, dstB3]];
dstP[4] ¬ Basics.FFromCard32[Basics.BITOR[dstA4, dstB4]];
dstA0 ¬ dstB0;
dstA1 ¬ dstB1;
dstA2 ¬ dstB2;
dstA3 ¬ dstB3;
dstA4 ¬ dstB4;
dstP ¬ dstP + dstupl;
srcP ¬ srcP + srcupl;
ENDLOOP;
dstP[0] ¬ Basics.FFromCard32[dstB0];
dstP[1] ¬ Basics.FFromCard32[dstB1];
dstP[2] ¬ Basics.FFromCard32[dstB2];
dstP[3] ¬ Basics.FFromCard32[dstB3];
dstP[4] ¬ Basics.FFromCard32[dstB4];
RETURN [1]
};
mb4to5: SMB ~ RegisterSpecialMaskBitmap[1.25, 0, MB4to5];
MB4to5: SpecialMaskBitmapProc ~ TRUSTED {
MaskBitmapHelp[device, bitmap, bitsToDevice, 4, 4*32, ResampleCell4to5];
RETURN [TRUE]
};
mb4to5L: SMB ~ RegisterSpecialMaskBitmap[1.25, 90, MB4to5L];
MB4to5L: SpecialMaskBitmapProc ~ TRUSTED {
Line: TYPE ~ PACKED ARRAY [0..4*BITS[WORD]) OF [0..1];
buf: ARRAY [0..BITS[WORD]) OF Line;
RLCell4to5: UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [WORD] ~ UNCHECKED {
bp: POINTER ¬ @buf;
bufupl: CARD ~ UNITS[Line];
nz: CARD ¬ 0;
FOR i: NAT IN [0..4) DO
nz ¬ Basics.BITOR[nz,
BlockRotate.RotateLeft[src: src, srcPitch: srcupl, dst: bp, dstPitch: UNITS[Line]]
];
src ¬ src + srcupl*BITS[WORD];
bp ¬ bp + UNITS[WORD];
ENDLOOP;
IF nz # 0 THEN {
dstupl5: CARD ~ 5*dstupl;
bp ¬ @buf;
THROUGH [0..BITS[WORD]/4) DO
[] ¬ ResampleCell4to5[src: bp, srcupl: UNITS[Line], dst: dst, dstupl: dstupl];
bp ¬ bp + 4*UNITS[Line];
dst ¬ dst + dstupl5;
ENDLOOP;
};
RETURN [nz]
};
MaskBitmapHelp[device, bitmap, bitsToDevice, 4*BITS[WORD], BITS[WORD], RLCell4to5];
RETURN [TRUE]
};
mb4to5R: SMB ~ RegisterSpecialMaskBitmap[1.25, -90, MB4to5R];
MB4to5R: SpecialMaskBitmapProc ~ TRUSTED {
Line: TYPE ~ PACKED ARRAY [0..4*BITS[WORD]) OF [0..1];
buf: ARRAY [0..BITS[WORD]) OF Line;
RRCell4to5: UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [WORD] ~ UNCHECKED {
bp: POINTER ¬ @buf;
bufupl: CARD ~ UNITS[Line];
nz: CARD ¬ 0;
src ¬ src + srcupl*BITS[WORD]*4;
FOR i: NAT IN [0..4) DO
src ¬ src - srcupl*BITS[WORD];
nz ¬ Basics.BITOR[nz,
BlockRotate.RotateRight[src: src, srcPitch: srcupl, dst: bp, dstPitch: UNITS[Line]]
];
bp ¬ bp + UNITS[WORD];
ENDLOOP;
IF nz # 0 THEN {
dstupl5: CARD ~ 5*dstupl;
bp ¬ @buf;
THROUGH [0..BITS[WORD]/4) DO
[] ¬ ResampleCell4to5[src: bp, srcupl: UNITS[Line], dst: dst, dstupl: dstupl];
bp ¬ bp + 4*UNITS[Line];
dst ¬ dst + dstupl5;
ENDLOOP;
};
RETURN [nz]
};
MaskBitmapHelp[device, bitmap, bitsToDevice, 4*BITS[WORD], BITS[WORD], RRCell4to5];
RETURN [TRUE]
};
mb4to5I: SMB ~ RegisterSpecialMaskBitmap[1.25, 180, MB4to5I];
MB4to5I: SpecialMaskBitmapProc ~ TRUSTED {
Line: TYPE ~ PACKED ARRAY [0..4*BITS[WORD]) OF [0..1];
buf: ARRAY [0..BITS[WORD]) OF Line;
ICell4to5: UNSAFE PROC [src: POINTER, srcupl: CARD, dst: POINTER, dstupl: CARD] RETURNS [WORD] ~ UNCHECKED {
nz: CARD ¬ 0;
bp: POINTER ¬ @buf + 4*UNITS[WORD];
FOR i: NAT IN [0..4) DO
bp ¬ bp - UNITS[WORD];
nz ¬ Basics.BITOR[nz,
BlockRotate180[src: src, srcupl: srcupl, dst: bp, dstupl: UNITS[Line]]
];
src ¬ src + UNITS[WORD];
ENDLOOP;
IF nz # 0 THEN {
dstupl5: CARD ~ 5*dstupl;
bp ¬ @buf;
THROUGH [0..BITS[WORD]/4) DO
[] ¬ ResampleCell4to5[src: bp, srcupl: UNITS[Line], dst: dst, dstupl: dstupl];
bp ¬ bp + 4*UNITS[Line];
dst ¬ dst + dstupl5;
ENDLOOP;
};
RETURN [nz]
};
MaskBitmapHelp[device, bitmap, bitsToDevice, 32, 4*32, ICell4to5];
RETURN [TRUE]
};
More general cases for MaskBitmap
BoxyMaskBitmap: PROC [device: Device, bitmap: SampleMap, bitsToDevice: Transformation] RETURNS [BOOL ¬ FALSE] = {
IF (bitsToDevice.form # 0
AND (device.state.allow.unorderedBoxes OR bitsToDevice.form = 3)
AND IsAllInteger[bitsToDevice]) THEN {
clipper: DeviceClipper ~ device.worksState.clipper;
a: INTEGER = Real.Round[bitsToDevice.a];
b: INTEGER = Real.Round[bitsToDevice.b];
c: INT = Real.Round[bitsToDevice.c];
d: INTEGER = Real.Round[bitsToDevice.d];
e: INTEGER = Real.Round[bitsToDevice.e];
f: INT = Real.Round[bitsToDevice.f];
Map: PROC [p: SF.Vec] RETURNS [IntVec] = { -- for SMul
RETURN[[ImagerSys.SMul[a, p.s] + ImagerSys.SMul[b, p.f] + c, ImagerSys.SMul[d, p.s] + ImagerSys.SMul[e, p.f] + f]]
};
srcBox: SF.Box = ImagerSample.GetBox[bitmap]; -- Overall bounds, in source bitmap coordinates;
dstBox: SF.Box = ClippedBounds[clipper.clipBox, Map[srcBox.min], Map[srcBox.max]]; -- Overall bounds, in device coordinates;
GenerateBoxes: --SF.BoxGenerator-- PROC [boxAction: SF.BoxAction] = {
FOR each: LIST OF SF.Box ¬ clipper.clipMask, each.rest UNTIL each = NIL DO --SF.BoxList
Come here once for each box in the clipping region.
visibleBox: SF.Box = SF.Intersect[dstBox, each.first];
IF SF.Nonempty[visibleBox] THEN {
Come here once for each box in the clipping region that intersects the bitmap area.
srcVisibleRealBox: ImagerBox.Box = ImagerBox.BoxFromRectangle[
ImagerTransformation.InverseTransformRectangle[
bitsToDevice,
[x: visibleBox.min.s, y: visibleBox.min.f, w: visibleBox.max.s - visibleBox.min.s, h: visibleBox.max.f - visibleBox.min.f]]];
srcVisibleBox: SF.Box = ClippedBounds[
clipBox: srcBox,
p0: [Real.Floor[srcVisibleRealBox.xmin], Real.Floor[srcVisibleRealBox.ymin]],
p1: [Real.Ceiling[srcVisibleRealBox.xmax], Real.Ceiling[srcVisibleRealBox.ymax]]];
ProcessPartiallyVisibleBox: --SF.BoxAction-- PROC [box: SF.Box] = {
This box indicates a region of 1-bits in the source bitmap coordinates;
transform to device coordinates, clip it and pass it to the device.
clippedBox: SF.Box = ClippedBounds[
visibleBox, Map[box.min], Map[box.max]];
IF SF.Nonempty[clippedBox] THEN boxAction[clippedBox];
};
visibleBitmap: SampleMap ¬ ImagerSample.Clip[bitmap, srcVisibleBox];
ImagerSample.BoxesFromBitmap[map: visibleBitmap, boxAction: ProcessPartiallyVisibleBox];
TRUSTED { ImagerSample.ReleaseDescriptor[visibleBitmap] };
};
ENDLOOP;
};
IF SF.Nonempty[dstBox] THEN device.class.MaskBoxes[device: device, bounds: dstBox, boxes: GenerateBoxes];
RETURN[TRUE];
};
};
MakeMultiple: PROC [x, m: CARD] RETURNS [CARD] ~ {
Rounds x up to a multiple of m.
mod: CARD ~ x MOD m;
RETURN [IF mod = 0 THEN x ELSE x + (m-mod)]
};
minScaleForFoxySwitch: CHAR['m..'m] ~ ImagerSwitches.Define[switch: 'm, name: $maskpixelswitchover, doc: "scale value for which maskpixel may use other than simple point-sampling", defaultValue: NEW[REAL ¬ 1.5]];
MinScaleForFoxy: PROC RETURNS [REAL] ~ {
WITH ImagerSwitches.Value[minScaleForFoxySwitch] SELECT FROM
r: REF REAL => RETURN [r^];
ENDCASE => RETURN [1.0];
};
FoxyMaskBitmap: PROC [device: Device, bitmap: SampleMap, bitsToDevice: Transformation] RETURNS [BOOL ¬ FALSE] ~ {
IF (device.state.allow.multipleCoverage AND ImagerTransformation.SingularValues[bitsToDevice].y >= MinScaleForFoxy[]) THEN {
clipper: DeviceClipper ~ device.worksState.clipper;
box: SF.Box ¬ ImagerSample.GetBox[bitmap];
clip: ImagerBox.Box ¬ ImagerBox.BoxFromRectangle[bitsToDevice.InverseTransformRectangle[[x: clipper.clipBox.min.s, y: clipper.clipBox.min.f, w: SF.SizeS[clipper.clipBox], h: SF.SizeF[clipper.clipBox]]]];
IF box.min.s < clip.xmin THEN box.min.s ¬ MIN[Real.Floor[clip.xmin], INTEGER.LAST];
IF box.min.f < clip.ymin THEN box.min.f ¬ MIN[Real.Floor[clip.ymin], INTEGER.LAST];
IF box.max.s > clip.xmax THEN box.max.s ¬ MAX[Real.Ceiling[clip.xmax], INTEGER.FIRST];
IF box.max.f > clip.ymax THEN box.max.f ¬ MAX[Real.Ceiling[clip.ymax], INTEGER.FIRST];
IF SF.Nonempty[box] THEN {
bytes: NAT ~ (box.max.f-box.min.f+7)/8;
textBuf: REF TEXT ~ RefText.ObtainScratch[nChars: bytes];
i: NAT ¬ 0;
String: XStringProc ~ {
i ¬ 0;
WHILE i < bytes DO
charAction[Char.Make[set: 0, code: ORD[textBuf[i]]]];
i ¬ i + 1;
ENDLOOP;
};
charToDevice: Transformation ¬ ImagerTransformation.TranslateTo[bitsToDevice, [0, 0]];
fontAtom: Font ~ ImagerFont.Modify[bitFont, charToDevice];
N.B. charToDevice gets side-effected in HardChar; don't destroy it too soon!
cp: ImagerDeviceVector.DVec ~ NEW[ImagerDeviceVector.DVecRep];
TryFastCP: PROC = {
IF NOT cp.scaled THEN {
IF ABS[cp.fv.x] < worryReal AND ABS[cp.fv.y] < worryReal THEN {
cp.scaled ¬ TRUE;
cp.sv.s ¬ ImagerScaled.FromReal[cp.fv.x];
cp.sv.f ¬ ImagerScaled.FromReal[cp.fv.y];
};
};
};
deviceEsc: VEC = ImagerTransformation.TransformVec[bitsToDevice, [0, 8]]; -- fixed width
FloatCP: PROC ~ {
IF cp.scaled THEN {
cp.scaled ¬ FALSE;
cp.fv.x ¬ ImagerScaled.Float[cp.sv.s];
cp.fv.y ¬ ImagerScaled.Float[cp.sv.f];
};
};
DoMetrics: PROC [char: XChar] = {
FloatCP[];
cp.fv.x ¬ cp.fv.x + deviceEsc.x;
cp.fv.y ¬ cp.fv.y + deviceEsc.y;
TryFastCP[];
};
HardChar: PROC [char: XChar] = {
Path: PathProc ~ {
BitPath[code: Char.Code[char], m: charToDevice, moveTo: moveTo, lineTo: lineTo];
};
FloatCP[];
ImagerTransformation.ApplyTranslateTo[charToDevice, cp.fv];
device.works.MaskFill[device: device, path: Path, oddWrap: FALSE, pathToDevice: NIL];
DoMetrics[char];
};
HardMetrics: PROC [charMask: ImagerMaskCache.CharMask] = {
IF cp.scaled AND charMask.char = Char.Make[0, 0]
THEN TRUSTED {
This arm is a fast case for skipping over runs of zeros. Control gets here because the bitFont marks character 0
Make local copies of the uplevel references so they can live in registers during the loop:
ds: Basics.LongNumber ~ charMask.escapement.s;
df: Basics.LongNumber ~ charMask.escapement.f;
lbytes: INTEGER ~ bytes;
ltextBuf: POINTER TO Basics.RawBytes ~ LOOPHOLE[textBuf, POINTER]+UNITS[TEXT[0]];
li: INTEGER ¬ i;
s: Basics.LongNumber ¬ cp.sv.s;
f: Basics.LongNumber ¬ cp.sv.f;
WHILE li < lbytes AND ltextBuf[li] = 0 DO
Should check for overflow here...
s.li ¬ s.li + ds.li;
f.li ¬ f.li + df.li;
li ¬ li + 1;
ENDLOOP;
i ¬ li-1; -- the minus 1 is because the String proc is going to bump i again.
cp.sv.s ¬ LOOPHOLE[s];
cp.sv.f ¬ LOOPHOLE[f];
}
ELSE {
DoMetrics[charMask.char];
};
};
textBufAsSampleMap: SampleMap ¬ NIL;
TRUSTED {
pointer: LONG POINTER ~ LOOPHOLE[textBuf, LONG POINTER] + SIZE[TEXT[0]];
textBufAsSampleMap ¬ ImagerSample.ObtainUnsafeDescriptor[size: [s: 1, f: box.max.f-box.min.f], bitsPerSample: 1, bitsPerLine: bytes*8, base: [word: pointer, bit: 0], ref: textBuf, words: WORDS[TEXT[textBuf.maxLength]]-WORDS[TEXT[0]]];
};
ImagerSample.Clear[textBufAsSampleMap];
FOR s: INTEGER IN [box.min.s..box.max.s) DO
ImagerSample.BasicTransfer[dst: textBufAsSampleMap, src: bitmap, srcMin: [s, box.min.f], size: [1, box.max.f-box.min.f]];
cp.scaled ¬ FALSE;
cp.fv ¬ ImagerTransformation.Transform[bitsToDevice, [s, box.min.f]];
TryFastCP[];
device.works.Show[
device: device,
fontAtom: fontAtom,
string: String,
cp: cp,
hardChar: HardChar, -- Needed rarely
hardMetrics: HardMetrics, -- handles runs of zeros - this "hard" case is actually easy!
easyMetrics: ordinary,
noImage: FALSE
];
ENDLOOP;
TRUSTED { ImagerSample.ReleaseDescriptor[textBufAsSampleMap] };
ImagerTransformation.Destroy[charToDevice]; -- don't destroy it too soon!
RefText.ReleaseScratch[textBuf];
FREE[@cp];
};
RETURN[TRUE];
};
};
MaskBitmap
StandardMaskBitmap: PUBLIC PROC [device: Device, bitmap: SampleMap, bitsToDevice: Transformation] = {
First check the registered special cases; these require device support for MaskBitmap.
form: NAT ~ bitsToDevice.form;
IF ABS[bitsToDevice.c] < bigTranslate AND ABS[bitsToDevice.f] < bigTranslate AND (device.state.allow.bitmap OR device.state.allow.unorderedBoxes) AND form < 11 THEN {
FOR tail: LIST OF SMB ¬ smbRegistry[bitsToDevice.form], tail.rest UNTIL tail = NIL DO
t: Transformation ~ tail.first.templateT;
Eql: PROC [a, b: REAL] RETURNS [BOOL] ~ INLINE {
Equal, with a little fuzz
RETURN [IF a > 0.0 THEN (16.0+a) = (16.0+b) ELSE (16.0-a) = (16.0-b)]
};
IF form > 2 OR (Eql[bitsToDevice.a, t.a] AND Eql[bitsToDevice.b, t.b] AND Eql[bitsToDevice.d, t.d] AND Eql[bitsToDevice.e, t.e]) THEN {
IF tail.first.specialMaskBitmap[device, bitmap, bitsToDevice] THEN RETURN;
};
ENDLOOP;
};
Maybe boxes are good:
IF BoxyMaskBitmap[device, bitmap, bitsToDevice] THEN RETURN;
Finally the two general-purpose cases.
IF FoxyMaskBitmap[device, bitmap, bitsToDevice] THEN RETURN;
HardMaskSampledBits[device, bitmap, bitsToDevice];
};
MaskPixelArray
bitsPerChunk: INT ¬ 1048576;
StandardMaskPixelArray: PUBLIC PROC [device: Device, bitmap: PixelArray, clientToDevice: Transformation] ~ {
maskToDevice: Transformation ¬ ImagerTransformation.Concat[bitmap.m, clientToDevice];
clipper: DeviceClipper = device.worksState.clipper;
srcVisibleRealBox: ImagerBox.Box = ImagerBox.BoxFromRectangle[
ImagerTransformation.InverseTransformRectangle[
maskToDevice,
[x: clipper.clipBox.min.s, y: clipper.clipBox.min.f, w: clipper.clipBox.max.s - clipper.clipBox.min.s, h: clipper.clipBox.max.f - clipper.clipBox.min.f]]];
srcVisibleBox: SF.Box = ClippedBounds[
clipBox: [max: [bitmap.sSize, bitmap.fSize]],
p0: [Real.Floor[srcVisibleRealBox.xmin], Real.Floor[srcVisibleRealBox.ymin]],
p1: [Real.Ceiling[srcVisibleRealBox.xmax], Real.Ceiling[srcVisibleRealBox.ymax]]];
sSizeV: NAT ~ srcVisibleBox.max.s-srcVisibleBox.min.s;
fSizeV: NAT ~ srcVisibleBox.max.f-srcVisibleBox.min.f;
IF sSizeV > 0 AND fSizeV > 0 THEN {
goodblock: NAT ~ 64; -- Chosen to reduce block fragmentation for fast rotation cases
sBufSize: NAT ~ MIN[
sSizeV,
MAX[((bitsPerChunk+fSizeV-1)/fSizeV+(goodblock-1))/goodblock, 1]*goodblock
];
scratch: ImagerSample.SampleMap ~ ImagerSample.ObtainScratchMap[box: [max: [sBufSize, fSizeV]]];
band: ImagerSample.SampleMap ¬ scratch;
ImagerTransformation.ApplyPreTranslate[maskToDevice, [srcVisibleBox.min.s, srcVisibleBox.min.f]];
FOR s: INT ¬ 0, s+sBufSize WHILE s < sSizeV DO
IF sSizeV-s < sBufSize THEN band ¬ ImagerSample.Clip[scratch, [max: [s: sSizeV-s, f: fSizeV]]];
ImagerPixelArray.Transfer[pa: bitmap, dst: band, s: s+srcVisibleBox.min.s, f: srcVisibleBox.min.f];
device.works.MaskBitmap[
device: device,
bitmap: band,
bitsToDevice: maskToDevice
];
ImagerTransformation.ApplyPreTranslate[maskToDevice, [sBufSize, 0]];
ENDLOOP;
IF band # scratch THEN TRUSTED { ImagerSample.ReleaseDescriptor[band] };
ImagerSample.ReleaseScratchMap[scratch];
};
ImagerTransformation.Destroy[maskToDevice];
};
ObtainPaddedBitmap: PROC [bitmap: SampleMap, pad: INTEGER ¬ 1] RETURNS [padded: SampleMap] ~ {
box: SF.Box ¬ ImagerSample.GetBox[bitmap];
box.min.s ¬ box.min.s - pad;
box.min.f ¬ box.min.f - pad;
box.max.s ¬ box.max.s + pad;
box.max.f ¬ box.max.f + pad;
padded ¬ ImagerSample.ObtainScratchMap[box];
ImagerSample.Clear[padded];
ImagerSample.Transfer[dst: padded, src: bitmap];
};
HardMaskSampledBits: PROC [device: Device, bitmap: SampleMap, bitsToDevice: Transformation] = {
padded: SampleMap ~ ObtainPaddedBitmap[bitmap];
MaskBoundary: PathProc = {
srcBox: SF.Box = ImagerSample.GetBox[bitmap];
x0: REAL = srcBox.min.s-0.125;
y0: REAL = srcBox.min.f-0.125;
x1: REAL = srcBox.max.s+0.125;
y1: REAL = srcBox.max.f+0.125;
moveTo[[x0, y0]];
lineTo[[x1, y0]];
lineTo[[x1, y1]];
lineTo[[x0, y1]];
};
Nest1: PROC [bounds: SF.Box, boxGenerator: SF.BoxGenerator] = {
Nest2: --SF.BoxGenerator-- PROC [boxAction: SF.BoxAction] = {
pixelMap: ImagerPixel.PixelMap ¬ ImagerPixel.MakePixelMap[s0: padded];
Nest3: ImagerPixel.ResampleAction = TRUSTED {
[pixels: PixelBuffer, min: SF.Vec]
count: INTEGER = pixels.length;
b: POINTER TO Basics.RawWords ~ ImagerSample.PointerToSamples[buffer: pixels[0], start: 0, count: count];
i: INTEGER ¬ 0;
DO
WHILE i < count AND b[i] = 0 DO i ¬ i + 1 ENDLOOP;
IF i = count THEN EXIT ELSE {
i0: INTEGER ¬ i;
WHILE i < count AND b[i] = 1 DO i ¬ i + 1 ENDLOOP;
boxAction[[min: [min.s, min.f + i0], max: [min.s + 1, min.f + i]]];
};
ENDLOOP;
};
ImagerPixel.Resample[self: pixelMap, m: bitsToDevice, interpolate: FALSE, boxes: boxGenerator, bounds: bounds, action: Nest3];
};
device.class.MaskBoxes[device: device, bounds: bounds, boxes: Nest2];
};
clipper: DeviceClipper ~ device.worksState.clipper;
BoxesFromPath[action: Nest1, path: MaskBoundary, oddWrap: FALSE, pathToDevice: bitsToDevice, clipper: clipper];
ImagerSample.ReleaseScratchMap[padded];
};
MaskBoxes
StandardMaskBoxes: PUBLIC PROC [device: Device, bounds: SF.Box, boxes: SF.BoxGenerator] = {
clipper: DeviceClipper ~ device.worksState.clipper;
IF clipper.clipMask # NIL THEN {
IF SF.Inside[bounds, clipper.clipMask.first] AND device.state.allow.unorderedBoxes
THEN {device.class.MaskBoxes[device: device, bounds: bounds, boxes: boxes]}
ELSE {
manhattan: ManhattanPolygon ~ ImagerManhattan.DestructiveIntersection[ImagerManhattan.CreateFromBoxes[boxes], clipper.clipMask];
IF manhattan # NIL THEN {
ManhattanBoxes: SF.BoxGenerator ~ {
FOR each: LIST OF SF.Box ¬ manhattan, each.rest UNTIL each=NIL DO
boxAction[each.first];
ENDLOOP;
};
bounds: SF.Box ~ ImagerManhattan.BoundingBox[manhattan];
device.class.MaskBoxes[device: device, bounds: bounds, boxes: ManhattanBoxes];
ImagerManhattan.Destroy[manhattan];
};
};
};
};
MaskCharMask
StandardMaskCharMask: PUBLIC PROC [device: Device, charMask: ImagerMaskCache.CharMask, cp: ImagerDeviceVector.DVec] RETURNS [ok: BOOL] = {
clipper: DeviceClipper ~ device.worksState.clipper;
IF clipper.clipMask = NIL THEN RETURN[ok: TRUE];
IF NOT cp.scaled THEN RETURN [FALSE];
IF charMask # NIL THEN {DO -- retry here if a culled representation was found.
s: INTEGER = ImagerScaled.Round[cp.sv.s];
f: INTEGER = ImagerScaled.Round[cp.sv.f];
sMin: INT = charMask.box.min.s + s;
sMax: INT = charMask.box.max.s + s;
fMin: INT = charMask.box.min.f + f;
fMax: INT = charMask.box.max.f + f;
IF sMin >= FIRST[INTEGER] AND sMax <= LAST[INTEGER]
AND fMin >= FIRST[INTEGER] AND fMax <= LAST[INTEGER] THEN {
box: SF.Box = [[INTEGER[sMin], INTEGER[fMin]], [INTEGER[sMax], INTEGER[fMax]]];
dstBox: SF.Box = SF.Intersect[box, clipper.clipBox];
IF SF.Empty[dstBox] THEN RETURN[ok: TRUE];
IF charMask.rep = culled THEN {
only the metrics were in cache, but we should be able to get the mask, too
charMask ¬ ImagerFontWorks.CaptureChar[
font: charMask.font,
char: charMask.char,
parameters: ImagerMaskCache.GetParameters[device.parm.fontCache],
metricsOnly: FALSE
];
IF charMask.rep = culled THEN ERROR; -- We really want that mask!
ImagerMaskCache.Store[device.parm.fontCache, charMask];
LOOP;
};
IF charMask.rep = maskNotCacheable THEN RETURN[ok: FALSE]; -- only the metrics were in cache
IF SF.Inside[box, clipper.clipMask.first]
AND
((charMask.rep = raster AND device.state.allow.rasterChar)
OR (charMask.rep = runs AND device.state.allow.runGroupChar)) THEN {
All visible: go for it
device.class.MaskChar[device: device, delta: [s, f], mask: charMask];
RETURN[ok: TRUE];
};
Partly visible: clip it
IF charMask.rep = raster AND device.state.allow.bitmap
THEN {
bitmap: SampleMap ¬ ImagerMaskCache.BitmapFromCharMask[charMask];
Boxes: PROC [action: PROC [SF.Box]] = {
ImagerManhattan.ClipBoxToMask[box: dstBox, mask: clipper.clipMask, action: action]
};
device.class.MaskBitmap[device: device, bitmap: bitmap, delta: [s, f], bounds: dstBox, boxes: Boxes];
TRUSTED {ImagerSample.ReleaseDescriptor[bitmap]};
}
ELSE {
FOR each: LIST OF SF.Box ¬ clipper.clipMask, each.rest UNTIL each = NIL DO
bounds: SF.Box ~ SFInline.Intersect[dstBox, each.first];
IF SFInline.Nonempty[bounds] THEN {
GenerateBoxes: --SF.BoxGenerator-- PROC [boxAction: SF.BoxAction] = {
ImagerMaskCache.BoxesFromCharMask[charMask: charMask, boxAction: boxAction, delta: [s, f], clip: bounds];
};
device.class.MaskBoxes[device: device, bounds: bounds, boxes: GenerateBoxes];
};
ENDLOOP;
};
RETURN[ok: TRUE];
};
IF sMax < clipper.clipBox.min.s
OR sMin > clipper.clipBox.max.s
OR fMax < clipper.clipBox.min.f
OR fMin > clipper.clipBox.max.f THEN RETURN[ok: TRUE]; -- character was culled
EXIT ENDLOOP
};
RETURN[ok: FALSE];
};
Show
hashMask: CARDINAL ¬ 255; -- for experimenting with hash functions
ByteHashFromLONG: PROC [ln: Basics.LongNumber] RETURNS [BYTE] = {
c: CARDINAL ¬ Basics.BITXOR[ln.hi, ln.lo];
RETURN [Basics.BITAND[Basics.BITXOR[c, c/256], hashMask]]
};
Ord: PROC [char: XChar] RETURNS [CARDINAL] = INLINE {RETURN [LOOPHOLE[char]]};
NoOverflow: PROC [a, b: ImagerScaled.Value] RETURNS [BOOL] = INLINE {
OPEN ImagerScaled; -- USING IntRep
RETURN [
IF IntRep[a] >= 0
THEN (IntRep[b] <= LAST[INT]-IntRep[a])
ELSE (IntRep[b] >= FIRST[INT]-IntRep[a])
]
};
StandardShow: PUBLIC PROC [device: Device, fontAtom: Font, string: XStringProc, cp: ImagerDeviceVector.DVec, hardChar: ImagerFont.XCharProc, hardMetrics: PROC [charMask: ImagerMaskCache.CharMask], easyMetrics: EasyMetrics, noImage: BOOL] = {
IF device.parm.fontCache # NIL THEN {
fontAtomHash: CARDINAL ¬ ByteHashFromLONG[LOOPHOLE[fontAtom]]; -- some function of current font atom
charArray: ImagerMaskCache.SmallCache = ImagerMaskCache.ObtainSmallCache[device.parm.fontCache];
ByteHash: PROC [char: XChar] RETURNS [BYTE] = INLINE {
RETURN [Basics.BITXOR[fontAtomHash, Basics.BITXOR[Ord[char], Char.Set[char]]] MOD 256]
};
MediumCharAction: PROC [char: XChar] = {
m: ImagerMaskCache.CharMask;
IF cp.scaled AND (m ¬ CachedLookup[char]) # NIL THEN {
IF noImage OR device.works.MaskCharMask[device: device, charMask: m, cp: cp] THEN {
IF (SELECT easyMetrics FROM all => TRUE, ordinary => m.flags = ordinaryMetrics, ENDCASE => FALSE) THEN {
Be careful about overflow.
IF NoOverflow[cp.sv.s, m.escapement.s] AND NoOverflow[cp.sv.f, m.escapement.f] THEN {
cp.sv.s ¬ ImagerScaled.PLUS[cp.sv.s, m.escapement.s];
cp.sv.f ¬ ImagerScaled.PLUS[cp.sv.f, m.escapement.f];
RETURN
};
};
hardMetrics[m];
RETURN;
};
};
hardChar[char]; -- do this one the hard way.
};
CachedLookup: PROC [char: XChar] RETURNS [m: ImagerMaskCache.CharMask ¬ NIL] = {
IF charArray # NIL THEN {
m ¬ charArray[ByteHash[char]];
IF m # NIL AND m.char = char AND m.font = fontAtom THEN RETURN [m];
};
WITH m ¬ ImagerMaskCache.Fetch[device.parm.fontCache, fontAtom, char] SELECT FROM
r: RasterMask => {
IF MAX[SF.SizeS[r.box], SF.SizeF[r.box], ABS[ImagerScaled.Floor[r.escapement.s]], ABS[ImagerScaled.Floor[r.escapement.f]]] < worryNat/8 THEN {
IF charArray # NIL THEN charArray[ByteHash[char]] ¬ r;
};
};
ENDCASE => IF m = NIL THEN {
clipper: DeviceClipper ~ device.worksState.clipper;
IF Char.Set[char] = 1B THEN RETURN[NIL]; -- Character set 1B is `reserved' for PostScript user-defined characters. The BuildChar procedure in a PS user-defined font will do the capturing for us, so we must avoid ImagerTypeface.CaptureChar. Returning NIL causes hardChar to be executed in MediumCharAction. It is assumed that the hardChar proc in the PS user-defined case will execute BuildChar and store the captured mask in the cache if appropriate, just as the following two statements would.
m ¬ ImagerFontWorks.CaptureChar[-- never returns NIL
font: fontAtom,
char: char,
parameters: ImagerMaskCache.GetParameters[device.parm.fontCache],
metricsOnly: NOT (-- guess about visibility (no disaster if wrong)
We could take noImage into account here, but since the primary use of noImage is in pass 1 of CORRECT, it is likely that we would need the mask anyway pretty soon. - mfp
cp.scaled AND
ImagerScaled.Floor[cp.sv.s] IN [clipper.clipBox.min.s..clipper.clipBox.max.s) AND
ImagerScaled.Floor[cp.sv.f] IN [clipper.clipBox.min.f..clipper.clipBox.max.f))
];
ImagerMaskCache.Store[device.parm.fontCache, m];
We won't bother to try putting it into the small cache
};
};
box: SF.Box ¬ [];
NewBox: PROC [p: SF.Vec] RETURNS [SF.Box] = {
clipper: DeviceClipper ~ device.worksState.clipper;
FOR each: LIST OF SF.Box ¬ clipper.clipMask, each.rest UNTIL each = NIL DO
box: SF.Box = each.first;
IF SF.In[p, box] THEN RETURN [box];
IF box.min.s > p.s THEN EXIT;
ENDLOOP;
RETURN [[min: SF.maxVec, max: SF.minVec]];
};
IF device.state.allow.rawBitmaps AND charArray # NIL THEN {
head: LIST OF ImagerSample.RawDescriptor ¬ IF device.state.scratchRawBitmapList = NIL THEN device.state.scratchRawBitmapList ¬ LIST[[[], 0, NIL]] ELSE device.state.scratchRawBitmapList;
last: LIST OF ImagerSample.RawDescriptor ¬ head;
save: LIST OF ImagerSample.RawDescriptor ¬ NIL;
Flush: PROC = TRUSTED INLINE {
save ¬ last.rest;
last.rest ¬ NIL;
device.class.MaskRawBitmaps[device: device, list: head.rest];
last.rest ¬ save;
last ¬ head;
};
CachedRasterLookup: PROC [char: XChar] RETURNS [m: RasterMask ¬ NIL] = {
WITH ImagerMaskCache.Fetch[device.parm.fontCache, fontAtom, char] SELECT FROM
r: RasterMask => {
IF MAX[SF.SizeS[r.box], SF.SizeF[r.box], ABS[ImagerScaled.Floor[r.escapement.s]], ABS[ImagerScaled.Floor[r.escapement.f]]] < worryNat/8 THEN {
h: BYTE = ByteHash[char];
old: RasterMask ~ charArray[h];
IF old # NIL THEN Flush[];
This is to ensure that we always hang onto a REF for all the masks that we might have pointers into, just so they won't get freed out from under us. The ones in the charArray are OK, but we better flush now; just in case the old one is in use.
M.B. will need to update reference counts here. - mfp
charArray[h] ¬ m ¬ r;
};
};
ENDCASE => NULL;
};
FastCharAction: PROC [char: XChar] = {
localCP: ImagerDeviceVector.DVec = cp;
IF localCP.scaled THEN {
localCPs: ImagerScaled.Value = localCP.sv.s;
localCPf: ImagerScaled.Value = localCP.sv.f;
m: RasterMask ¬ charArray[ByteHash[char]];
SELECT TRUE FROM
m = NIL, m.char # char, m.font # fontAtom => {
m ¬ CachedRasterLookup[char];
IF m = NIL THEN GO TO notEasy;
};
ENDCASE;
{
deltaS: INTEGER = ImagerScaled.Round[localCPs];
deltaF: INTEGER = ImagerScaled.Round[localCPf];
charBoxMinS: INTEGER = m.box.min.s + deltaS;
charBoxMinF: INTEGER = m.box.min.f + deltaF;
charBoxMaxS: INTEGER = m.box.max.s + deltaS;
charBoxMaxF: INTEGER = m.box.max.f + deltaF;
clipper: DeviceClipper ~ device.worksState.clipper;
IF NOT noImage THEN {
Test for complete containment
IF charBoxMinS >= box.min.s AND
charBoxMinF >= box.min.f AND
charBoxMaxS <= box.max.s AND
charBoxMaxF <= box.max.f THEN GO TO quick;
Test for any intersection in the clipping box
IF charBoxMinS >= clipper.clipBox.max.s OR
charBoxMinF >= clipper.clipBox.max.f OR
charBoxMaxS <= clipper.clipBox.min.s OR
charBoxMaxF <= clipper.clipBox.min.f THEN GO TO notVisible;
FOR each: LIST OF SF.Box ¬ clipper.clipMask, each.rest
UNTIL each = NIL DO
Scan the clipping list looking for a containing box. If one is found, then use it for the fast case. This is a specialized expansion of NewBox, and should be significantly faster.
IF charBoxMinS >= each.first.min.s AND
charBoxMinF >= each.first.min.f AND
charBoxMaxS <= each.first.max.s AND
charBoxMaxF <= each.first.max.f THEN {
box ¬ each.first;
GO TO quick;
};
ENDLOOP;
GO TO notQuick;
EXITS
quick => {
fSize: INTEGER = charBoxMaxF-charBoxMinF;
IF fSize > 0 THEN TRUSTED {
bplMask: CARDINAL = LAST[CARDINAL]-CARDINAL[bitsPerWord-1];
fNat: NAT = Basics.BITAND[LOOPHOLE[fSize+(bitsPerWord-1)], bplMask];
rest: LIST OF ImagerSample.RawDescriptor ¬ last.rest;
IF rest = NIL THEN last.rest ¬ rest ¬ LIST[[[], 0, NIL]];
last ¬ rest;
rest.first.box ¬ [
min: [s: charBoxMinS, f: charBoxMinF],
max: [s: charBoxMaxS, f: charBoxMaxF]];
rest.first.bitsPerLine ¬ fNat;
rest.first.basePointer ¬
LOOPHOLE[m, POINTER]
+ SIZE[ImagerMaskCache.CharMaskRep.raster[0]];
At this point, the mask REF is in charArray, and we ensure that it stays there until the Flush. Thus the raster storage won't get freed prematurely. This is why it is OK to form this pointer. - mfp
};
SELECT easyMetrics FROM
all => {};
ordinary => IF m.flags # ordinaryMetrics THEN GO TO noAdd;
ENDCASE => GO TO noAdd;
Can't overflow here because we were not clipped; we don't cache characters with huge escapements.
GO TO addAndReturn;
};
notQuick => {
IF last#head THEN Flush[]; -- Don't need unless we insist the masks are done in order; it should be OK to do them out of order because they are all the same color.
[] ¬ device.works.MaskCharMask[
device: device, charMask: m,
cp: localCP]; -- should never fail
};
notVisible => {};
};
SELECT easyMetrics FROM
all => {};
ordinary => IF m.flags # ordinaryMetrics THEN GO TO noAdd;
ENDCASE => GO TO noAdd;
IF NoOverflow[localCPs, m.escapement.s]
AND NoOverflow[localCPf, m.escapement.f] THEN
Be careful about overflow.
GO TO addAndReturn;
EXITS
noAdd => {};
addAndReturn => {
cp.sv.s ¬ ImagerScaled.PLUS[localCPs, m.escapement.s];
cp.sv.f ¬ ImagerScaled.PLUS[localCPf, m.escapement.f];
RETURN;
};
};
hardMetrics[m];
RETURN;
EXITS notEasy => {};
};
IF last#head THEN Flush[];
MediumCharAction[char]; -- do this one the slower way.
};
string[FastCharAction];
IF last # head THEN Flush[];
ImagerMaskCache.ReleaseSmallCache[device.parm.fontCache, charArray];
RETURN;
};
string[MediumCharAction];
IF charArray # NIL THEN
ImagerMaskCache.ReleaseSmallCache[device.parm.fontCache, charArray];
RETURN;
};
string[hardChar];
};
BitFont
bitFont: Font ~ ImagerTypeface.MakeFont[MakeBitTypeface[], ImagerTransformation.Scale[1]];
MakeBitTypeface: PROC RETURNS [Typeface] ~ INLINE {
class: ImagerTypeface.TypefaceClass ¬ NEW[ImagerTypeface.TypefaceClassRep ¬ [type: $BitTypeface, Contains: BitContains, NextChar: BitNextChar, Escapement: BitEscapement, Amplified: BitAmplified, Correction: BitCorrection, BoundingBox: BitBoundingBox, FontBoundingBox: BitFontBoundingBox, Ligature: BitLigature, NextLigature: BitNextLigature, Kern: BitKern, NextKern: BitNextKern, MapChar: BitMapChar]];
typeface: Typeface ¬ NEW[ImagerTypeface.TypefaceRep ¬ [class: class, data: NIL]];
RETURN [typeface]
};
BitContains: PROC [self: Typeface, char: XChar] RETURNS [BOOL] ~ {RETURN [Char.Set[char] = 0]};
BitNextChar: PROC [self: Typeface, char: XChar] RETURNS [next: XChar] ~ {RETURN [IF char = nullXChar THEN Char.Make[0, 0] ELSE IF ORD[char] >= 255 THEN nullXChar ELSE SUCC[char]]};
BitEscapement: PROC [self: Typeface, char: XChar] RETURNS [VEC] ~ {RETURN [[0, 8]]};
BitAmplified: PROC [self: Typeface, char: XChar] RETURNS [BOOL] ~ {RETURN [FALSE]};
BitCorrection: PROC [self: Typeface, char: XChar] RETURNS [ImagerFont.CorrectionType] ~ {RETURN [IF char = Char.Make[0, 0] THEN space ELSE mask]};
BitBoundingBox: PROC [self: Typeface, char: XChar] RETURNS [Extents] ~ {
RETURN [[leftExtent: 0, rightExtent: 1, descent: 0, ascent: 8]];
};
BitFontBoundingBox: PROC [self: Typeface] RETURNS [Extents] ~ {
RETURN [[leftExtent: 0, rightExtent: 1, descent: 0, ascent: 8]];
};
BitLigature: PROC [self: Typeface, char, successor: XChar] RETURNS [XChar] ~ {
RETURN [nullXChar];
};
BitNextLigature: PROC [self: Typeface, char, successor: XChar] RETURNS [XChar] ~ {
RETURN [nullXChar];
};
BitKern: PROC [self: Typeface, char, successor: XChar] RETURNS [VEC] ~ {
RETURN [[0.0, 0.0]];
};
BitNextKern: PROC [self: Typeface, char, successor: XChar] RETURNS [XChar] ~ {
RETURN [nullXChar];
};
BitMapChar: ImagerFontWorks.MapCharProc ~ {
PROC [font: Font, char: XChar, parameters: Parameters, moveTo: MoveToProc, lineTo: LineToProc, curveTo: CurveToProc, arcTo: ArcToProc, conicTo: ConicToProc] RETURNS [success: BOOL ¬ TRUE]
BitPath[code: Char.Code[char], m: font.charToClient, moveTo: moveTo, lineTo: lineTo];
};
bitPen: ImagerPen.Pen ~ BitPen[];
BitPen: PROC RETURNS [ImagerPen.Pen] ~ {
p: ImagerPen.Pen ¬ NEW[ImagerPen.PenRep[4]];
p.bounds.x ¬ 0.5;
p.bounds.y ¬ 0.5;
p[0] ¬ [ 0.5, 0.5 ];
p[1] ¬ [-0.5, 0.5 ];
p[2] ¬ [-0.5, -0.5 ];
p[3] ¬ [ 0.5, -0.5 ];
RETURN [p]
};
BitPath: PROC [code: BYTE, m: Transformation, moveTo: ImagerPath.MoveToProc, lineTo: ImagerPath.LineToProc] ~ {
Trace out a path describing the bits in device space, bloated by just enough so that they will overlap or at least touch no matter how the current position gets rounded to the grid.
p0: VEC ¬ ImagerTransformation.Transform[m, [0, 0]];
v1: VEC ~ ImagerTransformation.TransformVec[m, [1, 0]];
v2: VEC ~ ImagerTransformation.TransformVec[m, [0, 1]];
P: ImagerPath.PathProc ~ {
[moveTo: MoveToProc, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc]
ImagerStroke.MapBitPath[byte: code, p0: p0, v1: v1, v2: v2, moveTo: moveTo, lineTo: lineTo];
};
epsilon: REAL ~ 3.90625e-3; -- 2.0**(-8)
The following adjustment is to move the bloated path just a teensy bit in the correct direction to disambiguate certain edges that tend to fall very close to pixel centers; this provides more uniform rendering for patterns that are the same except for rotations by multiples of 90 degrees. The value of epsilon is chosen to be large enough so that it does not get lost in the floating-point fuzz when the value of p0 is around 10000. This is because of the viewOrigin used by ImagerMaskCaptureImpl.
IF v1.x + v2.x > 0 THEN p0.x ¬ p0.x + epsilon;
IF v1.x + v2.x < 0 THEN p0.x ¬ p0.x - epsilon;
IF v1.y + v2.y > 0 THEN p0.y ¬ p0.y + epsilon;
IF v1.y + v2.y < 0 THEN p0.y ¬ p0.y - epsilon;
ImagerStroke.ConvolvePenWithPath[path: P, pen: bitPen, moveTo: moveTo, lineTo: lineTo];
};
ImagerDeviceInterchange
Some boring scratch-memory management.
InterchangeState: TYPE ~ REF InterchangeStateRep;
InterchangeStateRep: TYPE ~ ImagerDeviceInterchange.InterchangeStateRep;
interchangeStateCache: InterchangeState ¬ NIL;
ObtainInterchangeState: PUBLIC PROC RETURNS [InterchangeState] ~ {
Locked: ENTRY PROC RETURNS [iState: InterchangeState] ~ INLINE { iState ¬ interchangeStateCache; interchangeStateCache ¬ NIL };
iState: InterchangeState ¬ Locked[];
IF iState = NIL THEN { iState ¬ NEW[InterchangeStateRep] };
RETURN [iState]
};
DestroyInterchangeState: PUBLIC PROC [iState: InterchangeState] ~ {
IF iState # NIL THEN {
iState.device ¬ NIL;
IF iState.clientToView # NIL THEN {
ImagerTransformation.Destroy[iState.clientToView];
iState.clientToView ¬ NIL;
};
IF iState.viewToSurface # NIL THEN {
ImagerTransformation.Destroy[iState.viewToSurface];
iState.viewToSurface ¬ NIL;
};
IF iState.surfaceToDevice # NIL THEN {
ImagerTransformation.Destroy[iState.surfaceToDevice];
iState.surfaceToDevice ¬ NIL;
};
iState.color ¬ NIL;
{ P: ENTRY PROC ~ INLINE { interchangeStateCache ¬ iState }; P[] };
};
};
MakeDevice
standardWorksClass: ImagerDevice.WorksClass ~ NEW[ImagerDevice.WorksClassRep ¬ [
Clip: StandardClip,
MaskFill: StandardMaskFill,
MaskRectangle: StandardMaskRectangle,
MaskStroke: StandardMaskStroke,
MaskVector: StandardMaskVector,
MaskDashedStroke: StandardMaskDashedStroke,
MaskBitmap: StandardMaskBitmap,
MaskPixelArray: StandardMaskPixelArray,
MaskBoxes: StandardMaskBoxes,
MaskCharMask: StandardMaskCharMask,
Show: StandardShow
]];
MakeDevice: PUBLIC PROC [class: ImagerDevice.DeviceClass, parm: ImagerDevice.DeviceParm, state: ImagerDevice.DeviceState, data: REF] RETURNS [ImagerDevice.Device] ~ {
device: ImagerDevice.Device ¬ NEW[ImagerDevice.DeviceRepr ¬ [
works: standardWorksClass,
worksState: [clipper: NEW[ImagerDevice.DeviceClipperRep], clipperToDevice: NIL, clientClipper: NIL],
class: class,
parm: parm,
state: IF state # NIL THEN state ELSE
NEW[ImagerDevice.DeviceStateRep ¬ [
allow: [], -- SetColor will set
bounds: [min: [s: 0, f: 0], max: [s: parm.sSize, f: parm.fSize]],
scratchRawBitmapList: NIL
]],
data: data
]];
RETURN [device]
};
END.