ImagerScanConverterImpl.mesa
Copyright Ó 1986, 1987, 1988, 1989, 1990, 1991, 1994 by Xerox Corporation. All rights reserved.
Michael Plass, January 24, 1994 8:54 am PST
Tim Diebert: January 20, 1990 1:02:33 pm PST
Russ Atkinson (RRA) October 11, 1990 9:34 am PDT
Willie-s, March 9, 1994 3:12 pm PST
DIRECTORY
Basics,
ImagerManhattan USING [CreateFromBoxes],
ImagerPath USING [ConicToProc, CurveToProc, LineToProc, MoveToProc, PathProc, Transform],
ImagerSample USING [EdgeAction, FillBoxes, SampleMap],
ImagerScanConverter USING [],
ImagerScanConverterExtras USING [],
ImagerScanConverterPrivate USING [Area, AreaRep, AreaState, Edge, EdgeRep, Edges, ExternalHalfPlane, Pair, RealBox, Region],
ImagerTransformation USING [Transformation],
RealFns USING [SqRt],
RealInline,
ImagerScaled,
SF USING [Box, BoxAction, BoxGenerator, Intersect, maxBox, Nonempty],
Vector2 USING [VEC];
ImagerScanConverterImpl: CEDAR MONITOR
IMPORTS Basics, ImagerManhattan, ImagerPath, RealFns, RealInline, ImagerScaled, SF, ImagerSample
EXPORTS ImagerScanConverter, ImagerScanConverterExtras
~ BEGIN OPEN ImagerScanConverterPrivate;
PathProc: TYPE ~ ImagerPath.PathProc;
Transformation: TYPE ~ ImagerTransformation.Transformation;
VEC: TYPE ~ Vector2.VEC;
checking: BOOL ¬ TRUE;
Enables some consistency checks that shouldn't be needed when the bugs are out.
Change to a compile-time FALSE for production.
RealAbsDif: PROC [x, y: REAL] RETURNS [REAL] = INLINE {
RETURN [RealInline.Abs[x - y]];
};
flatHack: REAL ¬ 1.0;
Used to control "flatness" in Flat[] and relatives
SetFlatHack: PROC [millis: INT] = {
IF millis IN [0..10000] THEN
flatHack ¬ millis * 0.001;
};
SetScanConversionMode: PUBLIC PROC [devicePath: DevicePath, mode: INTEGER] ~ {
devicePath.mode ¬ mode
};
Union: PROC [a, b: Region] RETURNS [Region] ~ INLINE {
RETURN [LOOPHOLE[Basics.BITOR[LOOPHOLE[a], LOOPHOLE[b]]]];
};
Odd: PROC [i: INTEGER] RETURNS [BOOL] ~ INLINE {
RETURN [VAL[LOOPHOLE[i, CARDINAL] MOD 2]]
};
IEEEFloat: TYPE = MACHINE DEPENDENT RECORD [
signAndExpt: [0..200H),
fraction: [0..CARD32.LAST/200H]
];
hiddenBit: CARD32 ~ LOOPHOLE[IEEEFloat[signAndExpt: 1, fraction: 0]];
Round: PROC [r: REAL] RETURNS [INT] ~ {
This rounds r, breaking ties toward plus infinity.
A fast case for an interesting range [3.90625e-3 .. 8388608) ...
f: IEEEFloat ~ LOOPHOLE[r];
c: CARDINAL ~ LOOPHOLE[149 - f.signAndExpt];
Valid: TYPE ~ [0..31);
IF c < 31 THEN {
k: Valid ~ LOOPHOLE[c, Valid];
RETURN LOOPHOLE[
Basics.BITRSHIFT[hiddenBit + f.fraction + Basics.BITLSHIFT[1, k], k+1]
]
};
IF LOOPHOLE[r, CARD] > 0 THEN RETURN [0];
Fallback for negative and huge numbers.
RETURN [RealInline.MCFloor[r+0.5]] -- Note this would give the wrong answer for r = 0.5-ulp, because adding 0.5 causes a roundup.
};
ScaledFromReal: PROC [r: REAL] RETURNS [SCALED] ~ {
Valid: TYPE ~ [0..31);
f: IEEEFloat ~ LOOPHOLE[r];
c: CARDINAL ¬ LOOPHOLE[(f.signAndExpt + 16) - 150];
IF c < 8 THEN {
RETURN LOOPHOLE[Basics.BITLSHIFT[hiddenBit + f.fraction, LOOPHOLE[c, Valid]]]
};
c ¬ Basics.BITNOT[c];
IF c < 31 THEN {
k: Valid ~ LOOPHOLE[c, Valid];
RETURN LOOPHOLE[
Basics.BITRSHIFT[hiddenBit + f.fraction + Basics.BITLSHIFT[1, k], k+1]]
};
IF LOOPHOLE[r, INT] >= 0 THEN RETURN LOOPHOLE[0];
RETURN LOOPHOLE[RealInline.MCFloor[r*65536.0+0.5]]
};
scratch1: Area ¬ NIL;
scratch2: Area ¬ NIL;
defaultTolerance: REAL ¬ 0.5;
dLimit: INTEGER ¬ 15;
depthExceededCounter: CARD ¬ 0;
nonStop: BOOL ¬ FALSE; -- set to TRUE to get old (possibly runaway) behavior.
DepthExceeded: PROC [stop: BOOL ¬ TRUE] RETURNS [BOOL] ~ {
depthExceededCounter ¬ depthExceededCounter + 1; -- Set break here to tinker.
RETURN [stop AND NOT nonStop]
};
TooDeep: PROC [limit: INTEGER] RETURNS [BOOL] ~ INLINE {
RETURN[IF limit > 0 THEN FALSE ELSE DepthExceeded[]];
};
defaultMode: INTEGER ¬ 0;
Create: PUBLIC ENTRY PROC RETURNS [DevicePath] ~ {
area: Area ¬ NIL;
SELECT TRUE FROM
scratch1 # NIL => {area ¬ scratch1; scratch1 ¬ NIL};
scratch2 # NIL => {area ¬ scratch2; scratch2 ¬ NIL};
ENDCASE => {
area ¬ NEW[AreaRep ¬ [
bounds: [],
realBounds: [],
tightBounds: [],
state: nil,
moveCount: 0,
firstPt: [0, 0],
firstRegion: ALL[FALSE],
lastPt: [0, 0],
lastRegion: ALL[FALSE],
totalCrossings: 0,
increasingEdges: NIL,
decreasingEdges: NIL,
freeEdges: NIL,
mode: defaultMode,
tolerance: defaultTolerance,
cLimit: dLimit,
pLimit: dLimit
]];
};
area.tolerance ¬ defaultTolerance;
area.mode ¬ ORD[ImagerSwitches.BoolValue[scanFatSwitch]];
RETURN [area]
};
Destroy: PUBLIC ENTRY PROC [devicePath: DevicePath] ~ {
IF devicePath # NIL THEN {
devicePath.mode ¬ defaultMode;
devicePath.tolerance ¬ defaultTolerance;
devicePath.cLimit ¬ dLimit;
devicePath.pLimit ¬ dLimit;
devicePath.increasingEdges ¬ FreeEdges[devicePath, devicePath.increasingEdges];
devicePath.decreasingEdges ¬ FreeEdges[devicePath, devicePath.decreasingEdges];
SELECT TRUE FROM
scratch1 = NIL => {scratch1 ¬ devicePath};
scratch2 = NIL => {scratch2 ¬ devicePath};
ENDCASE => NULL;
};
};
FreeEdges: PROC [area: Area, edges: Edges] RETURNS [nil: Edges ¬ NIL] ~ {
IF edges # NIL THEN {
last: Edges ¬ edges;
DO
last.f0 ¬ ImagerScaled.zero;
last.df ¬ ImagerScaled.zero;
IF last.link = NIL THEN EXIT ELSE last ¬ last.link;
ENDLOOP;
last.link ¬ area.freeEdges;
area.freeEdges ¬ edges;
};
};
SetBounds: PUBLIC PROC [devicePath: DevicePath, box: SF.Box] ~ {
area: Area ~ devicePath;
IF box.min.s < -32767 THEN box.min.s ¬ -32767;
IF box.max.s < box.min.s THEN box.max.s ¬ box.min.s;
IF box.max.s > 32767 THEN box.max.s ¬ 32767;
IF box.min.f < -32767 THEN box.min.f ¬ -32767;
IF box.max.f < box.min.f THEN box.max.f ¬ box.min.f;
IF box.max.f > 32767 THEN box.max.f ¬ 32767;
area.bounds ¬ box;
area.realBounds ¬ [min: [s: box.min.s, f: box.min.f], max: [s: box.max.s, f: box.max.f]];
area.tightBounds ¬ [min: [s: LAST[INTEGER], f: LAST[INTEGER]], max: [s: FIRST[INTEGER], f: FIRST[INTEGER]]];
area.state ¬ awaitMove;
area.moveCount ¬ 0;
area.firstPt ¬ [0, 0];
area.lastPt ¬ [0, 0];
area.totalCrossings ¬ 0;
area.increasingEdges ¬ FreeEdges[area, area.increasingEdges];
area.decreasingEdges ¬ FreeEdges[area, area.decreasingEdges];
area.cLimit ¬ dLimit;
area.pLimit ¬ dLimit;
};
RegionOf: PROC [area: Area, pt: Pair] RETURNS [Region] ~ INLINE {
region: Region ~ [
sLo: pt.s < area.realBounds.min.s,
sHi: pt.s > area.realBounds.max.s,
fLo: pt.f < area.realBounds.min.f,
fHi: pt.f > area.realBounds.max.f
];
RETURN [region]
};
MoveTo: PUBLIC PROC [devicePath: DevicePath, pt: Pair] ~ {
area: Area ~ devicePath;
Close[area];
area.firstPt ¬ area.lastPt ¬ pt;
area.firstRegion ¬ area.lastRegion ¬ RegionOf[area, pt];
area.state ¬ awaitLine;
area.moveCount ¬ area.moveCount + 1;
};
Close: PROC [area: Area] ~ {
IF area.state = awaitLine THEN LineTo[area, area.firstPt];
area.state ¬ awaitMove;
};
LineTo: PUBLIC PROC [devicePath: DevicePath, pt: Pair] ~ { InLineTo[devicePath, pt] };
InLineTo: PROC [area: Area, pt: Pair] ~ INLINE {
Just like LineTo, but conserves a register window if whole segment is visible
IF area.lastRegion = ALL[FALSE]
AND pt.s >= area.realBounds.min.s
AND pt.s <= area.realBounds.max.s
AND pt.f >= area.realBounds.min.f
AND pt.f <= area.realBounds.max.f
THEN {
Whole segment is visible
AppendSeg[area, area.lastPt, pt];
area.lastPt ¬ pt;
}
ELSE {
region: Region ~ RegionOf[area, pt];
ClipSeg[area: area, p0: area.lastPt, r0: area.lastRegion, p1: pt, r1: region];
area.lastPt ¬ pt;
area.lastRegion ¬ region;
};
};
Half: PROC [r: REAL] RETURNS [REAL] ~ INLINE {
RETURN [r*0.5]
};
Mid: PROC [p, q: Pair] RETURNS [Pair] ~ INLINE {
RETURN [[Half[p.s+q.s], Half[p.f+q.f]]];
};
maxd: NAT ~ 16; -- power of two for fast MOD
optRatio: REAL ~ 0.5857864; -- area ratio between optimized two-segment approximation and naive two-segment approximation
Tolerance: PROC [i: INT] RETURNS [old: INT] ~ {
old ¬ Round[defaultTolerance*100.0];
defaultTolerance ¬ i/100.0;
};
ParToDepth: PROC [area: Area, p0, p1, p2: Pair] RETURNS [[0..maxd]] ~ {
This procedure predicts up front how many subdivisions of a parabolic segment are needed to render it with reasonable accuracy. This works by taking advantage of the fact that the area of the control triangle decreases by a factor of 8 for each subdivision step (and hence the aggregate area of all the control triangles decreases by a factor of 4). The first stage decreases the area by a larger factor, because we use the two-segment that minimizes the absolute area between the approximation and the parabola.
s01: REAL ~ p0.s-p1.s;
f01: REAL ~ p0.f-p1.f;
s21: REAL ~ p2.s-p1.s;
f21: REAL ~ p2.f-p1.f;
i: CARDINAL ¬ 0;
a: REAL ¬ ABS[s01*f21-f01*s21]; -- twice the area of control triangle
budget: REAL ¬ area.tolerance * RealInline.AbsMax[p2.s-p0.s, p2.f-p0.f];
IF budget < 0.25 THEN budget ¬ 0.25; -- A minimum budget, for real small stuff.
IF a > budget THEN {
i ¬ i + 1;
a ¬ a * (0.25 * optRatio);
};
WHILE a > budget AND i < maxd DO
i ¬ i + 1;
a ¬ a * 0.25;
ENDLOOP;
IF i < 4 AND s01*s21 + f01*f21 > 0.0 THEN {
-- dot product indicates angle is acute - do at least 16 segments in this (probably rare) case
i ¬ 4;
};
RETURN [i]
};
ParTo: PUBLIC PROC [devicePath: DevicePath, p1, p2: Pair] ~ {
area: Area ~ devicePath;
p0: Pair ¬ area.lastPt;
stack: ARRAY [0..maxd) OF ARRAY [0..1] OF Pair;
n: ARRAY [0..maxd) OF CARDINAL; -- This array could be eliminated by working out the right invariants.
i: CARDINAL[0..maxd) ¬ 1;
r0: Region ~ area.lastRegion;
r1: Region ~ RegionOf[area, p1];
r2: Region ~ RegionOf[area, p2];
allVisible: BOOLEAN ~ Union[Union[r0, r1], r2] = ALL[FALSE];
n[0] ¬ ParToDepth[area, p0, p1, p2];
IF n[0] = 0
OR (area.lastRegion # ALL[FALSE]
AND r1 = area.lastRegion
AND r2 = area.lastRegion)
THEN {
The whole curve is either very flat or is contained within a single outlying region, and so it is harmless to replace it with a straight line. On a path that goes way off page, this will still do subdivision around the boundaries of the outlying regions, but that will introduce comparitively few extra segments.
IF allVisible
THEN {
AppendSeg[area, p0, p2];
area.lastPt ¬ p2;
}
ELSE { LineTo[area, p2] };
RETURN;
};
stack[0] ¬ [p2, p1];
WHILE i # 0 DO
i ¬ (CARDINAL[i] - 1) MOD maxd; -- note: the mod is present merely to skip bounds checks; it should never have an effect.
p2 ¬ stack[i][0];
p1 ¬ stack[i][1];
IF n[i] = 1
THEN {
This picks the two-line-segment approximation to the parabola that minimizes the absolute area between the approximation and the parabola, (keeping the same endpoints).
w02: REAL ~ 0.1767766953;
w1: REAL ~ 0.6464466094;
pw: Pair ~ [
s: (p0.s+p2.s)*w02 + p1.s*w1,
f: (p0.f+p2.f)*w02 + p1.f*w1];
IF allVisible
THEN {
AppendSeg[area, p0, pw];
AppendSeg[area, pw, p2];
area.lastPt ¬ p2;
}
ELSE { LineTo[area, pw]; LineTo[area, p2] };
p0 ¬ p2;
}
ELSE {
p10: Pair ~ Mid[p0, p1];
p12: Pair ~ Mid[p1, p2];
p012: Pair ~ Mid[p10, p12];
newn: CARDINAL ~ n[i] - 1;
stack[i][1] ¬ p12; -- same as stack[i] ¬ [p2, p12];
n[i] ¬ newn;
i ¬ (CARDINAL[i] + 1) MOD maxd;
stack[i] ¬ [p012, p10];
n[i] ¬ newn;
i ¬ (CARDINAL[i] + 1) MOD maxd;
};
ENDLOOP;
};
CurveTo: PUBLIC PROC [devicePath: DevicePath, p1, p2, p3: Pair] ~ {
area: Area ~ devicePath;
R: PROC [p, q: REAL] RETURNS [REAL] ~ INLINE { RETURN [q + Half[q-p]] };
Ext: PROC [p, q: Pair] RETURNS [Pair] ~ INLINE { RETURN [[R[p.s, q.s], R[p.f, q.f]]] };
p0: Pair ~ area.lastPt;
q1: Pair ~ Ext[p0, p1];
q2: Pair ~ Ext[p3, p2];
IF RealAbsDif[q1.s, q2.s]+RealAbsDif[q1.f, q2.f] < flatHack OR TooDeep[area.cLimit]
THEN { ParTo[area, Mid[q1, q2], p3] }
ELSE {
p01: Pair ~ Mid[p0, p1];
p12: Pair ~ Mid[p1, p2];
p23: Pair ~ Mid[p2, p3];
p012: Pair ~ Mid[p01, p12];
p123: Pair ~ Mid[p12, p23];
p0123: Pair ~ Mid[p012, p123];
area.cLimit ¬ area.cLimit - 1;
CurveTo[area, p01, p012, p0123];
CurveTo[area, p123, p23, p3];
area.cLimit ¬ area.cLimit + 1;
};
};
ConicTo: PUBLIC PROC [devicePath: DevicePath, p1, p2: Pair, r: REAL] ~ {
area: Area ~ devicePath;
SELECT r FROM
> 0.9999 => { LineTo[area, p1]; LineTo[area, p2] };
<= 0.0 => { LineTo[area, p2] };
ENDCASE => {
p0: Pair ~ area.lastPt;
p02: Pair ~ Mid[p0, p2];
m: Pair ~ [p1.s-p02.s, p1.f-p02.f];
flatness: REAL = (RealInline.Abs[m.s]+RealInline.Abs[m.f])*RealAbsDif[r, 0.5];
IF flatness < flatHack OR TooDeep[area.cLimit]
THEN { ParTo[area, p1, p2] }
ELSE {
q: Pair ~ [m.s*r+p02.s, m.f*r+p02.f];
rNew: REAL ~ 1.0/(1.0+RealFns.SqRt[2.0*(1-r)]);
area.cLimit ¬ area.cLimit - 1;
ConicTo[area, [(p1.s-p0.s)*r+p0.s, (p1.f-p0.f)*r+p0.f], q, rNew];
ConicTo[area, [(p1.s-p2.s)*r+p2.s, (p1.f-p2.f)*r+p2.f], p2, rNew];
area.cLimit ¬ area.cLimit + 1;
};
};
};
ScaledIntMul: PROC [s: ImagerScaled.Value, c: INTEGER] RETURNS [ImagerScaled.Value] ~ INLINE {
num: Basics.LongNumber ¬ LOOPHOLE[s];
num.li ¬ num.li * c;
RETURN [LOOPHOLE[num]]
};
appendSegCount: CARD ¬ 0;
AppendSegCount: PROC RETURNS [c: CARD] ~ {
c ¬ appendSegCount;
appendSegCount ¬ 0;
};
wrapUnit: INTEGER ~ 2**(BITS[NAT]/2);
AppendSeg: PROC [area: Area, point0, point1: Pair] ~ TRUSTED INLINE {
IF area.mode # 0
THEN AppendFatSeg[area, @point0, @point1]
ELSE AppendSegInner[area, @point0, @point1, wrapUnit]
};
AppendFatSeg: UNSAFE PROC [area: Area, point0, point1: POINTER TO Pair] ~ UNCHECKED {
AppendSegInner[area, point0, point1, wrapUnit];
IF point0.s > point1.s THEN {
t: POINTER TO Pair ¬ point0; point0 ¬ point1; point1 ¬ t;
};
BEGIN
s0m: REAL ~ point0.s-0.5;
f0m: REAL ~ point0.f-0.5;
s0p: REAL ~ point0.s+0.5;
f0p: REAL ~ point0.f+0.5;
s1m: REAL ~ point1.s-0.5;
f1m: REAL ~ point1.f-0.5;
s1p: REAL ~ point1.s+0.5;
f1p: REAL ~ point1.f+0.5;
P: UNSAFE PROC [p0s, p0f, p1s, p1f: REAL, dw: INT] ~ UNCHECKED INLINE {
p0: Pair ¬ [p0s, p0f];
p1: Pair ¬ [p1s, p1f];
AppendSegInner[area, @p0, @p1, dw];
};
IF point0.f < point1.f
THEN {
P[s0m, f0m, s0p, f0m, 1];
P[s0p, f0m, s1p, f1m, 1];
P[s0m, f0p, s1m, f1p, -1];
P[s1m, f1p, s1p, f1p, -1];
}
ELSE {
P[s0m, f0m, s1m, f1m, 1];
P[s1m, f1m, s1p, f1m, 1];
P[s0m, f0p, s0p, f0p, -1];
P[s0p, f0p, s1p, f1p, -1];
};
END;
};
Low32: PROC [d: DREAL] RETURNS [INT32] = INLINE {
This returns the low-order 32 bits of the double precision floating point number d.
RETURN [LOOPHOLE[d, PACKED ARRAY [0..1] OF INT32][1]];
};
FindMagic: PROC [unit: INT] RETURNS [DREAL] ~ {
scaleMagic: DREAL ¬ 3;
QRound: PROC [r: REAL] RETURNS [INT] ~ {
d: DREAL ¬ r + scaleMagic;
RETURN [Low32[d]];
};
UNTIL QRound[1] = unit DO
scaleMagic ¬ scaleMagic+scaleMagic;
ENDLOOP;
RETURN [scaleMagic]
};
SCALED: TYPE ~ INT32;
scaledHalf: SCALED ~ LOOPHOLE[ImagerScaled.half];
scaledUnit: SCALED ~ LOOPHOLE[ImagerScaled.one];
sMagic: ARRAY [0..1] OF DREAL ¬ [FindMagic[1], FindMagic[scaledUnit]];
ScaledSlope: UNSAFE PROC [
p0, p1: POINTER TO Pair,
scaleMagic: POINTER TO ARRAY [0..1] OF DREAL,
sAdjust: POINTER TO ARRAY [0..1] OF SCALED, -- inputs
fAdjust: POINTER TO ARRAY [0..1] OF SCALED -- outputs
] RETURNS [SCALED] ~ UNCHECKED INLINE {
num: DREAL ~ DREAL[p1.f]-DREAL[p0.f];
denom: DREAL ~ DREAL[p1.s]-DREAL[p0.s];
slope: DREAL ~ num/denom;
magic0: DREAL ~ scaleMagic[0];
p: DREAL ¬ slope + scaleMagic[1];
scaledSlope: SCALED ~ Low32[p];
p ¬ slope*sAdjust[0] + magic0;
fAdjust[0] ¬ Low32[p];
p ¬ slope*sAdjust[1] + magic0;
fAdjust[1] ¬ Low32[p];
RETURN [scaledSlope];
};
MCScaledSlope: UNSAFE PROC [p0, p1: POINTER TO Pair, scaleMagic: POINTER TO ARRAY [0..1] OF DREAL, sAdjust, fAdjust: POINTER TO ARRAY [0..1] OF SCALED] RETURNS [SCALED] ~ UNCHECKED MACHINE CODE {
Another version of ScaledSlope - it avoids quite a few memory references, but should do exactly the same thing.
"+static int MCScaledSlope (p0, p1, scaleMagic, sAdjust, fAdjust) float p0[]; float p1[]; double scaleMagic[]; int sAdjust[]; int fAdjust[]; {
double num = (double)(p1[1]) - (double)(p0[1]);
double denom = (double)(p1[0]) - (double)(p0[0]);
double slope = num/denom;
double magic0 = scaleMagic[0];
double p = slope + scaleMagic[1];
int* pp = (int*)(&p);
int scaledSlope = pp[1];
p = slope*sAdjust[0] + magic0;
fAdjust[0] = pp[1];
p = slope*sAdjust[1] + magic0;
fAdjust[1] = pp[1];
return(scaledSlope);
}
.MCScaledSlope";
};
AppendSegInner: UNSAFE PROC [area: Area, p0: POINTER TO Pair, p1: POINTER TO Pair, wrapDelta: INT] ~ UNCHECKED {
sMin: INTEGER ¬ Round[p0.s];
sMax: INTEGER ¬ Round[p1.s];
appendSegCount ¬ appendSegCount + 1;
IF sMin > sMax THEN {
{ p: POINTER TO Pair ¬ p0; p0 ¬ p1; p1 ¬ p };
{ s: INTEGER ¬ sMin; sMin ¬ sMax; sMax ¬ s };
wrapDelta ¬ -wrapDelta;
};
IF sMin < sMax THEN {
sCount: INTEGER ~ sMax-sMin;
sAdjust: ARRAY [0..1] OF SCALED ¬ [
sMin*scaledUnit + scaledHalf - ScaledFromReal[p0.s],
(sCount-1)*scaledUnit
];
fAdjust: ARRAY [0..1] OF SCALED; -- set by next line to sAdjust * (p1.f-p0.f)/(p1.s-p0.s)
scaledSlope: SCALED ~ MCScaledSlope[p0, p1, @sMagic, @sAdjust, @fAdjust];
scaledSlope: SCALED ~ ScaledSlope[p0, p1, @sMagic, @sAdjust, @fAdjust];
ef0: SCALED ~ ScaledFromReal[p0.f] + fAdjust[0] + scaledHalf;
ef1: SCALED ~ ef0 + fAdjust[1];
fMin: INTEGER ¬ ImagerScaled.Floor[LOOPHOLE[ef0]];
fMax: INTEGER ¬ ImagerScaled.Floor[LOOPHOLE[ef1]];
new: Edges ¬ NIL;
IF fMin > fMax THEN {t: INTEGER ¬ fMin; fMin ¬ fMax; fMax ¬ t};
area.totalCrossings ¬ area.totalCrossings + sCount;
IF area.tightBounds.min.f > fMin THEN area.tightBounds.min.f ¬ fMin;
IF area.tightBounds.max.f < fMax THEN area.tightBounds.max.f ¬ fMax;
IF area.tightBounds.min.s > sMin THEN area.tightBounds.min.s ¬ sMin;
IF area.tightBounds.max.s < sMax THEN area.tightBounds.max.s ¬ sMax;
IF area.freeEdges = NIL
THEN { new ¬ NEW[EdgeRep] }
ELSE {
new ¬ area.freeEdges;
area.freeEdges ¬ new.link;
};
new.sMin ¬ sMin;
new.sCount ¬ LOOPHOLE[sCount];
new.f0 ¬ LOOPHOLE[ef0];
new.df ¬ LOOPHOLE[scaledSlope];
new.wrapDelta ¬ wrapDelta;
IF wrapDelta > 0
THEN {
new.link ¬ area.increasingEdges;
area.increasingEdges ¬ new;
}
ELSE {
new.link ¬ area.decreasingEdges;
area.decreasingEdges ¬ new;
};
};
};
Trouble: SIGNAL = CODE; -- raised when an error check fails; resuming may well work OK, but something isn't quite right.
Check: PROC [truth: BOOL] ~ { IF NOT truth THEN SIGNAL Trouble };
Interpolate: PROC [x0, y0, x1, y1, x: REAL] RETURNS [y: REAL] ~ {
This procedure attempts to improve the numeric stability of the result by calculating the intersection from the closer of the two endpoints. We assume x1#x0.
dx0: REAL ~ x-x0;
dx1: REAL ~ x1-x;
IF checking THEN Check[ (x IN [x0..x1] OR x IN [x1..x0]) ];
IF RealInline.Abs[dx0] <= RealInline.Abs[dx1]
THEN y ¬ y0+(y1-y0)*(dx0/(x1-x0))
ELSE y ¬ y1-(y1-y0)*(dx1/(x1-x0));
IF checking THEN Check[ (y IN [y0..y1] OR y IN [y1..y0]) ];
};
ClipSeg: PROC [area: Area, p0: Pair, r0: Region, p1: Pair, r1: Region] ~ {
sRegion: Region ~ [sLo: TRUE, sHi: TRUE, fLo: FALSE, fHi: FALSE];
clip: ARRAY ExternalHalfPlane OF REAL ~ [
sLo: area.realBounds.min.s,
sHi: area.realBounds.max.s,
fLo: area.realBounds.min.f,
fHi: area.realBounds.max.f
];
FOR b: ExternalHalfPlane IN ExternalHalfPlane DO
SELECT TRUE FROM
r0[b] AND r1[b] => {
IF sRegion[b]
THEN { p0.s ¬ p1.s ¬ clip[b] }
ELSE { p0.f ¬ p1.f ¬ clip[b] };
r0[b] ¬ FALSE;
r1[b] ¬ FALSE;
};
r0[b] OR r1[b] => {
cut: Pair ~ IF sRegion[b]
THEN [s: clip[b], f: Interpolate[p0.s, p0.f, p1.s, p1.f, clip[b]]]
ELSE [s: Interpolate[p0.f, p0.s, p1.f, p1.s, clip[b]], f: clip[b]];
IF r0[b]
THEN {
IF sRegion[b]
THEN AppendSeg[area, [s: cut.s, f: p0.f], cut]
ELSE AppendSeg[area, [s: p0.s, f: cut.f], cut];
p0 ¬ cut;
r0 ¬ RegionOf[area, cut];
}
ELSE {
IF sRegion[b]
THEN AppendSeg[area, cut, [s: cut.s, f: p1.f]]
ELSE AppendSeg[area, cut, [s: p1.s, f: cut.f]];
p1 ¬ cut;
r1 ¬ RegionOf[area, cut];
};
};
ENDCASE => NULL;
ENDLOOP;
IF checking THEN Check[ (Union[r0, r1] = ALL[FALSE]) ];
AppendSeg[area, p0, p1]
};
SortS: PROC [e: Edges] RETURNS [Edges] ~ INLINE {
Sort by increasing sMin
RETURN [MSort[e: e, sCompare: TRUE, terminator: NIL]];
};
MSort: PROC [e: Edges, sCompare: BOOL, terminator: Edge] RETURNS [Edges] ~ {
Does a list-merge sort on e, by either s or f; terminator is used as the list terminator, and must be reachable from e.
n: INT ¬ 0;
x: Edges ¬ e;
y: Edges ¬ terminator;
tail: Edges ¬ e;
WHILE x # terminator DO
x ¬ x.link;
n ¬ n + 1;
IF x = terminator THEN EXIT;
x ¬ x.link;
n ¬ n + 1;
tail ¬ tail.link;
ENDLOOP;
IF n <= 10 THEN RETURN [ISort[e, sCompare, terminator]];
x ¬ e;
y ¬ tail.link;
tail.link ¬ terminator;
x ¬ MSort[x, sCompare, terminator];
y ¬ MSort[y, sCompare, terminator];
Finally, merge the two sorted lists
RETURN [Merge[x, y, sCompare, terminator]]
};
Merge: PROC [x, y: Edges, sCompare: BOOL, terminator: Edge] RETURNS [Edges] ~ {
GT: PROC [a, b: Edge] RETURNS [BOOL] ~ INLINE {
RETURN [IF sCompare THEN (a.sMin > b.sMin) ELSE (ImagerScaled.Floor[a.f0] > ImagerScaled.Floor[b.f0])]
};
new: Edges ¬ x;
tail: Edges ¬ terminator;
IF x = terminator THEN RETURN [y];
IF y = terminator THEN RETURN [x];
IF GT[x, y] THEN {new ¬ y; y ¬ x; x ¬ new};
Start from y, which we do by swapping x and y.
DO
We first assume that we have just appended from x, but need to advance x to the next element and check for emptiness. Once this is done we try to stay within x as long as the predicate allows. By doing this we reduce the amount of RC assignments of the form "tail.link ← ...", which speeds things up considerably. (At least it did in the old days when we did reference-counting CG - MFP.)
DO
tail ¬ x; x ¬ x.link;
IF x = terminator THEN {tail.link ¬ y; RETURN[new]};
IF GT[x, y] THEN EXIT;
ENDLOOP;
tail.link ¬ y;
We have just appended from y, so append to the list from y as long as reasonable.
DO
tail ¬ y; y ¬ y.link;
IF y = terminator THEN {tail.link ¬ x; RETURN[new]};
IF GT[y, x] THEN EXIT;
ENDLOOP;
tail.link ¬ x;
ENDLOOP;
};
ISort: PROC [e: Edges, sCompare: BOOL, terminator: Edge] RETURNS [Edges] ~ {
Sort the head of e (punctuated by terminator) by increasing sMin or f0
unconsumed: Edges ¬ e;
new: Edges ¬ terminator;
WHILE unconsumed # terminator DO
link: Edges ~ unconsumed.link;
current: Edges ~ unconsumed;
a: Edges ¬ new;
v: INTEGER ~ IF sCompare THEN current.sMin ELSE ImagerScaled.Floor[current.f0];
p: Edges ¬ terminator;
IF sCompare
THEN { WHILE a#terminator AND v > a.sMin DO p ¬ a; a ¬ a.link ENDLOOP }
ELSE { WHILE a#terminator AND v > ImagerScaled.Floor[a.f0] DO p ¬ a; a ¬ a.link ENDLOOP };
IF p = terminator
THEN { current.link ¬ new; new ¬ current }
ELSE { current.link ¬ p.link; p.link ¬ current };
unconsumed ¬ link;
ENDLOOP;
RETURN [new];
};
SortF: PROC [e: Edges, s: INTEGER] RETURNS [Edges] ~ {
Sort the head of e (those at front of list with sMin = s) by increasing f0
n: INT ¬ 0;
null: Edges ¬ e;
WHILE null # NIL AND null.sMin = s DO null ¬ null.link; n ¬ n + 1 ENDLOOP;
IF n <= 10 THEN RETURN [ISort[e: e, sCompare: FALSE, terminator: null]];
RETURN [MSort[e: e, sCompare: FALSE, terminator: null]];
};
CountCrossings: PROC [e: Edges] RETURNS [n: INT ¬ 0] ~ {
WHILE e#NIL DO n ¬ n + e.sCount; e ¬ e.link ENDLOOP;
};
CheckMonotone: PROC [area: Area] ~ {
sSize: INT ¬ 0;
IF area.state = awaitLine THEN Close[area];
sSize ¬ area.tightBounds.max.s-area.tightBounds.min.s;
IF sSize <= 0 OR (area.moveCount = 1 AND area.totalCrossings = sSize+sSize)
THEN area.state ¬ completeMonotone
ELSE area.state ¬ complete;
area.increasingEdges ¬ SortS[area.increasingEdges];
area.decreasingEdges ¬ SortS[area.decreasingEdges];
IF checking AND CountCrossings[area.increasingEdges] # CountCrossings[area.decreasingEdges] THEN ERROR;
};
AdvanceTo: PROC [e: EdgeRep, s: INTEGER] RETURNS [EdgeRep] ~ {
WHILE e.sMin+e.sCount <= s AND e.link#NIL DO
e ¬ e.link­;
ENDLOOP;
IF e.sMin+e.sCount <= s
THEN {
e.sCount ¬ 0;
e.sMin ¬ LAST[INTEGER];
}
ELSE {
WHILE e.sMin < s DO
e.sMin ¬ e.sMin + 1;
e.sCount ¬ e.sCount - 1;
e.f0 ¬ ImagerScaled.PLUS[e.f0, e.df];
ENDLOOP;
};
RETURN [e];
};
CopyEdges: PROC [area: Area, src: Edges, sMin, sMax: INTEGER] RETURNS [result: Edges] ~ {
head: Edges ~ IF area.freeEdges = NIL THEN NEW[EdgeRep] ELSE area.freeEdges;
last: Edges ¬ head;
WHILE src # NIL DO
s0: INTEGER ¬ src.sMin;
s1: INTEGER ¬ s0+src.sCount;
IF s0 < sMin THEN s0 ¬ sMin;
IF s1 > sMax THEN s1 ¬ sMax;
IF s0 < s1 THEN {
IF last.link = NIL THEN last.link ¬ NEW[EdgeRep];
last ¬ last.link;
last.sMin ¬ src.sMin;
last.sCount ¬ src.sCount;
last.f0 ¬ src.f0;
last.df ¬ src.df;
last.wrapDelta ¬ src.wrapDelta;
};
src ¬ src.link;
ENDLOOP;
{
t: Edges ~ last.link;
last.link ¬ NIL;
result ¬ head.link;
head.link ¬ t;
area.freeEdges ¬ head;
};
};
AdvanceEdge: PROC [p: Edges, bbMinS: INTEGER] = {
WHILE p # NIL AND p.sMin < bbMinS DO
edge: Edge ¬ p;
sMin: INTEGER ¬ edge.sMin;
IF sMin < bbMinS THEN {
sCount: INTEGER ¬ edge.sCount;
f0: ImagerScaled.Value ¬ edge.f0;
df: ImagerScaled.Value = edge.df;
WHILE sMin < bbMinS DO
sMin ¬ sMin + 1;
sCount ¬ sCount - 1;
f0 ¬ ImagerScaled.PLUS[f0, df];
ENDLOOP;
edge.f0 ¬ f0;
edge.sCount ¬ LOOPHOLE[sCount]; -- Shouldn't trap here if CopyEdges did it's job
edge.sMin ¬ sMin;
};
p ¬ edge;
p ¬ p.link;
ENDLOOP;
};
Validate: PROC [area: Area] RETURNS [nPieces: INT ¬ 0] ~ {
-- This is to call from the dubugger; it can be commented out for production.
nCross: PACKED ARRAY [0..500] OF INTEGER ¬ ALL[0];
s: INT ¬ INT.FIRST;
FOR e: Edges ¬ area.increasingEdges, e.link UNTIL e = NIL DO
FOR s: INT IN [e.sMin..e.sMin+e.sCount) DO
IF s IN [0..500] THEN nCross[s] ¬ nCross[s] + 1;
ENDLOOP;
IF e.sMin < s THEN ERROR;
s ¬ e.sMin;
nPieces ¬ nPieces + 1;
ENDLOOP;
s ¬ INT.FIRST;
FOR e: Edges ¬ area.decreasingEdges, e.link UNTIL e = NIL DO
FOR s: INT IN [e.sMin..e.sMin+e.sCount) DO
IF s IN [0..500] THEN nCross[s] ¬ nCross[s] - 1;
ENDLOOP;
IF e.sMin < s THEN ERROR;
s ¬ e.sMin;
nPieces ¬ nPieces + 1;
ENDLOOP;
FOR i: INT IN [0..500] DO IF nCross[i] # 0 THEN ERROR ENDLOOP;
s ¬ INT.FIRST;
};
Dummy: PROC [edges: Edges] RETURNS [EdgeRep] ~ INLINE {
RETURN [[sMin: FIRST[INTEGER], sCount: 0, f0: ImagerScaled.zero, df: ImagerScaled.zero, wrapDelta: 0, link: edges]]
};
ConvertMonotone: PROC [area: Area, boxAction: SF.BoxAction, clipBox: SF.Box ¬ SF.maxBox] ~ {
bb: SF.Box ~ SF.Intersect[clipBox, area.tightBounds];
e: ARRAY [0..1] OF EdgeRep ¬ [
AdvanceTo[Dummy[area.increasingEdges], bb.min.s],
AdvanceTo[Dummy[area.decreasingEdges], bb.min.s]];
s: INTEGER ¬ e[0].sMin;
assert: BOOL[TRUE..TRUE] ¬ (s = e[1].sMin);
currentBox: SF.Box ¬ [];
WHILE s < bb.max.s DO
f0, f1: INTEGER;
IF ImagerScaled.Floor[e[0].f0] > ImagerScaled.Floor[e[1].f0] THEN {
t: EdgeRep ¬ e[0];
e[0] ¬ e[1];
e[1] ¬ t;
};
f0 ¬ MAX[ImagerScaled.Floor[e[0].f0], bb.min.f];
f1 ¬ MIN[ImagerScaled.Floor[e[1].f0], bb.max.f];
IF s = currentBox.max.s AND f0 = currentBox.min.f AND f1 = currentBox.max.f
THEN {
Can just extend the current box.
IF e[0].df=ImagerScaled.zero AND e[1].df=ImagerScaled.zero THEN {
Both edges are parallel to s axis; skip to the end of the shorter one.
d0: INTEGER ~ e[0].sCount;
d1: INTEGER ~ e[1].sCount;
d: INTEGER ~ MIN[d0, d1, bb.max.s-s] - 1;
e[0].sCount ¬ LOOPHOLE[d0-d];
e[1].sCount ¬ LOOPHOLE[d1-d];
s ¬ s + d;
};
currentBox.max.s ¬ s + 1;
}
ELSE {
Emit the current box and start a new one.
IF SF.Nonempty[currentBox] THEN boxAction[currentBox];
currentBox ¬ [[s, f0], [s+1, f1]]
};
IF (e[0].sCount ¬ e[0].sCount-1) = 0
THEN { IF e[0].link = NIL THEN EXIT ELSE e[0] ¬ e[0].link­ }
ELSE { e[0].f0 ¬ ImagerScaled.PLUS[e[0].f0, e[0].df] };
IF (e[1].sCount ¬ e[1].sCount-1) = 0
THEN { IF e[1].link = NIL THEN EXIT ELSE e[1] ¬ e[1].link­ }
ELSE { e[1].f0 ¬ ImagerScaled.PLUS[e[1].f0, e[1].df] };
s ¬ s + 1;
ENDLOOP;
IF SF.Nonempty[currentBox] THEN boxAction[currentBox];
};
Fill: PROC [dst: ImagerSample.SampleMap, area: Area, oddWrap: BOOL ¬ FALSE] ~ {
boxes: SF.BoxGenerator ~ { BoxesFromArea[area, boxAction, SF.maxBox, oddWrap] };
ImagerSample.FillBoxes[dst, boxes];
};
Public Stuff
DevicePath: TYPE ~ REF DevicePathRep;
DevicePathRep: PUBLIC TYPE ~ AreaRep;
CreatePath: PUBLIC PROC [path: PathProc, transformation: Transformation, clipBox: SF.Box] RETURNS [DevicePath] ~ {
devicePath: DevicePath ~ Create[];
SetPath[devicePath, path, transformation, clipBox];
RETURN [devicePath]
};
SetPath: PUBLIC PROC [devicePath: DevicePath, path: PathProc, transformation: Transformation ¬ NIL, clipBox: SF.Box] ~ {
area: Area ~ devicePath;
T: PROC [p: VEC] RETURNS [Pair] ~ INLINE { RETURN [[s: p.x, f: p.y]] };
moveTo: ImagerPath.MoveToProc ~ { MoveTo[area, T[p]] };
lineTo: ImagerPath.LineToProc ~ { InLineTo[area, T[p1]] };
curveTo: ImagerPath.CurveToProc ~ { CurveTo[area, T[p1], T[p2], T[p3]] };
conicTo: ImagerPath.ConicToProc ~ { ConicTo[area, T[p1], T[p2], r] };
SetBounds[area, clipBox];
ImagerPath.Transform[path: path, m: transformation, moveTo: moveTo, lineTo: lineTo, curveTo: curveTo, conicTo: conicTo];
};
ObjectsFromPath: PUBLIC PROC [path: PathProc, clipBox: SF.Box, objectProc: PROC [box: SF.Box, boxGenerator: SF.BoxGenerator], devicePath: DevicePath] ~ {
area: Area ~ devicePath;
T: PROC [p: VEC] RETURNS [Pair] ~ INLINE { RETURN [[s: p.x, f: p.y]] };
boxGenerator: SF.BoxGenerator ~ { BoxesFromArea[area, boxAction, clipBox, FALSE] };
moveTo: ImagerPath.MoveToProc ~ {
Close[area];
IF SF.Nonempty[area.tightBounds] THEN {
objectProc[area.tightBounds, boxGenerator];
SetBounds[area, clipBox];
};
MoveTo[area, T[p]];
};
lineTo: ImagerPath.LineToProc ~ { InLineTo[area, T[p1]] };
curveTo: ImagerPath.CurveToProc ~ { CurveTo[area, T[p1], T[p2], T[p3]] };
conicTo: ImagerPath.ConicToProc ~ { ConicTo[area, T[p1], T[p2], r] };
SetBounds[area, clipBox];
path[moveTo: moveTo, lineTo: lineTo, curveTo: curveTo, conicTo: conicTo, arcTo: NIL];
Close[area];
IF SF.Nonempty[area.tightBounds] THEN {
objectProc[area.tightBounds, boxGenerator];
SetBounds[area, clipBox];
};
};
BoundEdges: PROC [area: Area, e: Edges] ~ INLINE {
Not currently used.
UNTIL e = NIL DO
ef0: ImagerScaled.Value ~ e.f0;
sCount: INTEGER ~ e.sCount;
edf: ImagerScaled.Value ~ e.df;
fMin: INTEGER ¬ ImagerScaled.Floor[ef0];
fMax: INTEGER ¬ ImagerScaled.Floor[ImagerScaled.PLUS[ef0, ScaledIntMul[edf, sCount-1]]];
IF fMin > fMax THEN {t: INTEGER ¬ fMin; fMin ¬ fMax; fMax ¬ t};
IF area.tightBounds.min.f > fMin THEN area.tightBounds.min.f ¬ fMin;
IF area.tightBounds.max.f < fMax THEN area.tightBounds.max.f ¬ fMax;
e ¬ e.link;
ENDLOOP;
};
BoundingBox: PUBLIC PROC [devicePath: DevicePath, clipBox: SF.Box] RETURNS [SF.Box] ~ {
IF devicePath.state < complete THEN CheckMonotone[devicePath];
RETURN [SF.Intersect[devicePath.tightBounds, clipBox]]
};
Monotone: PUBLIC PROC [devicePath: DevicePath] RETURNS [BOOL] ~ {
IF devicePath.state < complete THEN CheckMonotone[devicePath];
RETURN [devicePath.state = completeMonotone]
};
GenerateEdges: PUBLIC PROC [devicePath: DevicePath, edgeAction: ImagerSample.EdgeAction] ~ {
IF devicePath.state < complete THEN CheckMonotone[devicePath];
{
e: ARRAY [0..1] OF Edge ¬ [devicePath.increasingEdges, devicePath.decreasingEdges];
s0: INTEGER ¬ IF e[0] # NIL THEN e[0].sMin ELSE LAST[INTEGER];
s1: INTEGER ¬ IF e[1] # NIL THEN e[1].sMin ELSE LAST[INTEGER];
UNTIL e[0] = NIL AND e[1] = NIL DO
IF s0 <= s1
THEN {
edgeAction[which: 0, sMin: e[0].sMin, sCount: e[0].sCount, f0: e[0].f0, df: e[0].df];
e[0] ¬ e[0].link;
s0 ¬ IF e[0] # NIL THEN e[0].sMin ELSE LAST[INTEGER];
}
ELSE {
edgeAction[which: 1, sMin: e[1].sMin, sCount: e[1].sCount, f0: e[1].f0, df: e[1].df];
e[1] ¬ e[1].link;
s1 ¬ IF e[1] # NIL THEN e[1].sMin ELSE LAST[INTEGER];
};
ENDLOOP;
};
};
NumberOfBoxes: PUBLIC PROC [devicePath: DevicePath] RETURNS [INT] ~ {
RETURN [devicePath.totalCrossings/2]
};
BoxesFromArea: PROC [area: Area, boxAction: SF.BoxAction, clipBox: SF.Box ¬ SF.maxBox, oddWrap: BOOL ¬ FALSE] ~ INLINE {
ConvertToBoxes[devicePath: area, oddWrap: oddWrap, clipBox: clipBox, boxAction: boxAction];
};
ConvertToBoxes: PUBLIC PROC [devicePath: DevicePath, oddWrap: BOOL,
clipBox: SF.Box, boxAction: SF.BoxAction] ~ {
area: Area ~ devicePath;
IF area.state < complete THEN CheckMonotone[area];
IF area.state = completeMonotone
THEN ConvertMonotone[area, boxAction, clipBox]
ELSE {
wrap: INTEGER ¬ 0;
wrapMask: CARDINAL ~ IF oddWrap THEN 2*wrapUnit-1 ELSE LAST[CARDINAL];
ActiveWrap: PROC RETURNS [BOOL] ~ INLINE {
RETURN [Basics.BITAND[LOOPHOLE[wrap], wrapMask] = 0]
};
needSort: BOOL ¬ TRUE;
bbMinS: INTEGER = MAX[clipBox.min.s, area.tightBounds.min.s];
bbMinF: INTEGER = MAX[clipBox.min.f, area.tightBounds.min.f];
bbMaxS: INTEGER = MIN[clipBox.max.s, area.tightBounds.max.s];
bbMaxF: INTEGER = MIN[clipBox.max.f, area.tightBounds.max.f];
e: Edges ¬ Merge[
x: CopyEdges[area: area, src: area.increasingEdges, sMin: bbMinS, sMax: bbMaxS],
y: CopyEdges[area: area, src: area.decreasingEdges, sMin: bbMinS, sMax: bbMaxS],
sCompare: TRUE,
terminator: NIL
];
Advance all the edges crossing the current scan line so they start at the current scan line.
AdvanceEdge[e, bbMinS];
WHILE e#NIL DO
g: Edges ¬ e;
s: INTEGER ~ g.sMin;
f0: INTEGER ¬ LAST[INTEGER]; -- The start of a run of pixels
fScaledPrev: Basics.LongNumber ¬ [li[0]]; -- previous fScaled
p: Edges ¬ NIL;
IF s >= bbMaxS THEN {
[] ¬ FreeEdges[area, g];
RETURN;
};
IF needSort THEN {
e ¬ g ¬ SortF[g, s];
needSort ¬ FALSE;
};
UNTIL g = NIL OR g.sMin > s DO
residual: INTEGER ~ g.sCount - 1;
fScaled: ImagerScaled.Value ¬ g.f0;
f: INTEGER ¬ ImagerScaled.Floor[fScaled];
IF f < bbMinF THEN f ¬ bbMinF ELSE IF f > bbMaxF THEN f ¬ bbMaxF;
IF ActiveWrap[] THEN { f0 ¬ f };
wrap ¬ wrap + g.wrapDelta;
IF ActiveWrap[] THEN { IF f > f0 THEN boxAction[[[s, f0], [s+1, f]]] };
Advance to the next value of s, discarding completed segments.
IF residual = 0
THEN {
r: Edges ~ g.link;
IF p = NIL THEN e ¬ r ELSE p.link ¬ r;
g.link ¬ area.freeEdges;
area.freeEdges ¬ g;
g ¬ r;
}
ELSE {
g.f0 ¬ fScaled ¬ ImagerScaled.PLUS[fScaled, g.df];
g.sMin ¬ s + 1;
g.sCount ¬ LOOPHOLE[residual];
IF p # NIL THEN { IF fScaledPrev.li > LOOPHOLE[fScaled] THEN needSort ¬ TRUE };
p ¬ g;
fScaledPrev ¬ fScaled;
g ¬ g.link;
};
ENDLOOP;
IF g#NIL AND g.sMin = e.sMin THEN needSort ¬ TRUE;
IF checking AND wrap # 0 THEN ERROR;
ENDLOOP;
};
};
ConvertToManhattanPolygon: PUBLIC PROC [devicePath: DevicePath, oddWrap: BOOL, clipBox: SF.Box] RETURNS [LIST OF SF.Box] ~ {
boxGenerator: SF.BoxGenerator ~ {BoxesFromArea[devicePath, boxAction, clipBox, oddWrap]};
RETURN [ImagerManhattan.CreateFromBoxes[boxGenerator]]
};
SetTolerance: PUBLIC PROC [devicePath: DevicePath, tolerance: REAL ¬ 1.0] = {
devicePath.tolerance ¬ tolerance;
};
PathToLines: PUBLIC PROC [moveTo: ImagerPath.MoveToProc, lineTo: ImagerPath.LineToProc, path: PathProc, transformation: Transformation ¬ NIL, tolerance: REAL] = {
This is needed for Postscript's FlattenPath operation. There is a lot of overlap with the code above, and it is tempting to try to merge them. However, the code above also has a bounding box, which it can use for simplifying parts of the path that will not be visible. As it stands, this code is independent of the rest of this module.
cLimit, pLimit: INTEGER ¬ dLimit;
LineToLines: PROC [p1: Pair] = INLINE { lineTo[[x: p1.s, y: p1.f]] };
ParToLines: PROC [p0, p1, p2: Pair] = {
Flat: PROC RETURNS [BOOL] = INLINE {
s01: REAL = p0.s - p1.s;
f01: REAL = p0.f - p1.f;
s21: REAL = p2.s - p1.s;
f21: REAL = p2.f - p1.f;
IF s01 * s21 + f01 * f21 > 0.0 THEN
RETURN [FALSE] -- dot product indicates angle is acute
ELSE {
d: REAL = RealInline.AbsMax[p2.s - p0.s, p2.f - p0.f];
IF d < flatHack THEN RETURN [TRUE];
IF RealAbsDif[s01 * f21, s21 * f01] < tolerance * d THEN RETURN[TRUE];
RETURN[FALSE];
};
};
IF Flat[] OR TooDeep[pLimit]
THEN {
This picks the two-line-segment approximation to the parabola that minimizes the absolute area between the approximation and the parabola, (keeping the same endpoints).
w02: REAL = 0.1767766953;
w1: REAL = 0.6464466094;
s012: REAL = (p0.s + p2.s) * w02 + p1.s * w1;
f012: REAL = (p0.f + p2.f) * w02 + p1.f * w1;
LineToLines[[s012, f012]];
LineToLines[p2];
}
ELSE {
p10: Pair = Mid[p0, p1];
p12: Pair = Mid[p1, p2];
p012: Pair = Mid[p10, p12];
pLimit ¬ pLimit - 1;
ParToLines[p0, p10, p012];
ParToLines[p012, p12, p2];
pLimit ¬ pLimit + 1;
};
};
CurveToLines: PROC [p0, p1, p2, p3: Pair] = {
R: PROC [p, q: REAL] RETURNS [REAL] = INLINE {RETURN[q + Half[q - p]]};
Ext: PROC [p, q: Pair] RETURNS [Pair] = INLINE {RETURN[[R[p.s, q.s], R[p.f, q.f]]]};
q1: Pair = Ext[p0, p1];
q2: Pair = Ext[p3, p2];
IF RealAbsDif[q1.s, q2.s] + RealAbsDif[q1.f, q2.f] < 1.0 OR TooDeep[cLimit]
THEN {
ParToLines[p0, Mid[q1, q2], p3]
}
ELSE {
p01: Pair = Mid[p0, p1];
p12: Pair = Mid[p1, p2];
p23: Pair = Mid[p2, p3];
p012: Pair = Mid[p01, p12];
p123: Pair = Mid[p12, p23];
p0123: Pair = Mid[p012, p123];
cLimit ¬ cLimit - 1;
CurveToLines[p0, p01, p012, p0123];
CurveToLines[p0123, p123, p23, p3];
cLimit ¬ cLimit + 1;
};
};
ConicToLines: PROC [p0, p1, p2: Pair, r: REAL] = {
SELECT r FROM
> 0.9999 => {LineToLines[p1]; LineToLines[p2]};
<= 0.0 => {LineToLines[p2]};
ENDCASE => {
p02: Pair = Mid[p0, p2];
m: Pair = [p1.s - p02.s, p1.f - p02.f];
IF (RealInline.Abs[m.s] + RealInline.Abs[m.f]) * RealAbsDif[r, 0.5] < flatHack OR TooDeep[cLimit]
THEN {ParToLines[p0, p1, p2]}
ELSE {
q: Pair = [m.s * r + p02.s, m.f * r + p02.f];
rNew: REAL = 1.0 / (1.0 + RealFns.SqRt[2.0 * (1 - r)]);
cLimit ¬ cLimit - 1;
ConicToLines[
p0, [(p1.s - p0.s) * r + p0.s, (p1.f - p0.f) * r + p0.f], q, rNew];
ConicToLines[
q, [(p1.s - p2.s) * r + p2.s, (p1.f - p2.f) * r + p2.f], p2, rNew];
cLimit ¬ cLimit + 1;
};
};
};
T: PROC [p: VEC] RETURNS [Pair] = INLINE {RETURN[[s: p.x, f: p.y]]};
myMoveTo: ImagerPath.MoveToProc = {moveTo[p]; lp ¬ p};
myLineTo: ImagerPath.LineToProc = {lineTo[p1]; lp ¬ p1};
myCurveTo: ImagerPath.CurveToProc = {
CurveToLines[T[lp], T[p1], T[p2], T[p3]];
lp ¬ p3;
};
myConicTo: ImagerPath.ConicToProc = {
ConicToLines[T[lp], T[p1], T[p2], r];
lp ¬ p2;
};
lp: VEC ¬ [0, 0];
ImagerPath.Transform[path: path, m: transformation, moveTo: myMoveTo, lineTo: myLineTo, curveTo: myCurveTo, conicTo: myConicTo];
};
END.