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