ContoursImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Bloomenthal, February 24, 1987 6:13:15 pm PST
DIRECTORY ColorTrixBasics, Contours, Controls, Draw2d, Imager, ImagerBackdoor, ImagerPath, IO, Real, RealFns, Rope, ThreeDIO, Vector2, Vector3d;
ContoursImpl: CEDAR PROGRAM
IMPORTS ColorTrixBasics, Controls, Draw2d, Imager, ImagerBackdoor, IO, Real, RealFns, Rope, ThreeDIO, Vector2, Vector3d
EXPORTS Contours
~ BEGIN
OPEN Contours;
Additional Types
TestProc:     TYPE ~ PROC [p0, p1, p2: Pair] RETURNS [BOOL];
Spot:      TYPE ~ RECORD [x, y: INTEGER];
SpotSequence:   TYPE ~ REF SpotSequenceRep;
SpotSequenceRep:  TYPE ~ RECORD [element: SEQUENCE length: NAT OF Spot];
NatList:     TYPE ~ REF NatListRep;
NatListRep:    TYPE ~ RECORD [n: NAT, next: NatList ← NIL];
NatListSequence:   TYPE ~ REF NatListSequenceRep;
NatListSequenceRep: TYPE ~ RECORD [element: SEQUENCE length: NAT OF NatList];
Support Procedures
ShiftPairs: PROC [pairs: PairSequence, shift: INTEGER] ~ {
Positive shift is to the left, negative to the right:
leftShift: NATIF shift < 0 THEN pairs.length+shift+1 ELSE shift;
save: PairSequence ← NEW[PairSequenceRep[leftShift]];
stop: INTEGER ← pairs.length-leftShift;
FOR n: NAT IN [0..leftShift) DO save[n] ← pairs[n]; ENDLOOP;
FOR n: NAT IN [0..stop) DO pairs[n] ← pairs[n+leftShift]; ENDLOOP;
FOR n: NAT IN [0..leftShift) DO pairs[n+stop] ← save[n]; ENDLOOP;
};
Reduce: PROC [contour: Contour, testProc: TestProc] RETURNS [Contour] ~ {
IF NOT ContourOK[contour] OR contour.pairs.length < 3
THEN RETURN[contour]
ELSE {
n: INT ← 0;
noMore: ERROR = CODE;
Store: PROC [p: Pair] ~ {
new.pairs[new.pairs.length] ← p;
new.pairs.length ← new.pairs.length+1;
};
NewPair: PROC RETURNS [Pair] ~ {
IF (n ← n+1) > contour.pairs.length THEN ERROR noMore;
RETURN[contour.pairs[n-1]];
};
new: Contour ← NEW[ContourRep ← contour^];
p0: Pair ← NewPair[];
p1: Pair ← NewPair[];
p2: Pair ← NewPair[];
new.pairs ← NEW[PairSequenceRep[contour.pairs.length]];
DO
Store[p0];
IF testProc[p0, p1, p2]
THEN { 
p0 ← p2;
p1 ← NewPair[! noMore => {Store[p0]; EXIT}];
p2 ← NewPair[! noMore => {Store[p0]; Store[p1]; EXIT}];
}
ELSE {
p0 ← p1;
p1 ← p2;
p2 ← NewPair[! noMore => {Store[p0]; Store[p1]; EXIT}];
};
ENDLOOP;
RETURN[new];
};
};
Dot: PROC [p0, p1, p2: Pair] RETURNS [REAL] ~ {
RETURN[IF p0 = p1 OR p1 = p2
THEN 0.0
ELSE ABS[Vector2.Dot[
Vector2.Unit[Vector2.Sub[p1, p0]], Vector2.Unit[Vector2.Sub[p2, p1]]]]];
};
ConvertingValues: PROC [bounds: Rectangle] RETURNS [scale, xOffset, yOffset: REAL] ~ {
scale ← (MIN[bounds.w, bounds.h]-1)/2.0;
xOffset ← bounds.x+scale-1.0;
yOffset ← bounds.y+scale-1.0;
};
SpotsFromPairs: PROC [pairs: PairSequence, rectangle: Rectangle] RETURNS [SpotSequence] ~ {
spots: SpotSequence ← NIL;
IF pairs # NIL THEN {
scale, xOffset, yOffset: REAL;
[scale, xOffset, yOffset] ← ConvertingValues[rectangle];
spots ← NEW[SpotSequenceRep[pairs.length]];
FOR n: NAT IN [0..spots.length) DO
spots[n] ← [Real.RoundI[xOffset+scale*pairs[n].x], Real.RoundI[yOffset-scale*pairs[n].y]];
ENDLOOP;
};
RETURN[spots];
};
Path: PROC [
context: Context,
contour: Contour,
moveTo: ImagerPath.MoveToProc,
lineTo: ImagerPath.LineToProc]
~ {
IF contour # NIL AND contour.pairs # NIL THEN {
start: Pair;
scale, xOffset, yOffset: REAL;
[scale, xOffset, yOffset] ← ConvertingValues[ImagerBackdoor.GetBounds[context]];
moveTo[start ← [xOffset+scale*contour.pairs[0].x, yOffset+scale*contour.pairs[0].y]];
FOR n: NAT IN [1..contour.pairs.length) DO
lineTo[[xOffset+scale*contour.pairs[n].x, yOffset+scale*contour.pairs[n].y]];
ENDLOOP;
IF contour.closed THEN lineTo[start];
};
};
CheckPercentsAndNormals: PROC [contour: Contour] ~ {
IF NOT ContourOK[contour] THEN RETURN;
IF contour.normals = NIL THEN contour.normals ← Normals[contour];
IF contour.percents = NIL THEN contour.percents ← Percents[contour];
};
PercentOfPairSequence: PROC [pairs: PairSequence, index: RealIndex] RETURNS [Pair] ~ {
Assumes circular PairSequence:
IF index.alpha = 0.0
THEN RETURN[pairs[index.n]]
ELSE {
p0: Pair ← pairs[index.n];
p1: Pair ← pairs[(index.n+1) MOD pairs.length];
RETURN[[p0.x+index.alpha*(p1.x-p0.x), p0.y+index.alpha*(p1.y-p0.y)]];
};
};
nCircles: INTEGER ~ 40;
circles: ARRAY [1..nCircles] OF PairSequence ← ALL[NIL];
NewCircle: PROC [nPairs: INTEGER] RETURNS [PairSequence] ~ {
rad: REAL ← 0.0;
drad: REAL ← 2.0*3.1415926535/REAL[nPairs];
circle: PairSequence ← NEW[PairSequenceRep[nPairs]];
circle.length ← nPairs;
FOR i: NAT IN[0..circle.length ← nPairs) DO
circle[i] ← [RealFns.Cos[rad], RealFns.Sin[rad]];
rad ← rad-drad;           -- clockwise
ENDLOOP;
RETURN[circle];
};
Attributes
ContourOK: PUBLIC PROC [contour: Contour] RETURNS [BOOL] ~ {
RETURN[contour # NIL AND contour.pairs # NIL AND contour.pairs.length > 0];
};
MinMax: PUBLIC PROC [contour: Contour] RETURNS [min, max: Pair] ~ {
IF contour # NIL AND contour.pairs # NIL AND contour.pairs.length > 0
THEN {
minx, miny: REAL ← 10000.0;
maxx, maxy: REAL ← -10000.0;
FOR n: NAT IN [0..contour.pairs.length) DO
p: Pair ← contour.pairs[n];
IF p.x < minx THEN minx ← p.x;
IF p.x > maxx THEN maxx ← p.x;
IF p.y < miny THEN miny ← p.y;
IF p.y > maxy THEN maxy ← p.y;
ENDLOOP;
RETURN[[minx, miny], [maxx, maxy]];
}
ELSE RETURN[[0.0, 0.0], [0.0, 0.0]];
};
Centroid: PUBLIC PROC [contour: Contour] RETURNS [Pair] ~ {
min, max, centroid: Pair ← [0.0, 0.0];
IF contour # NIL AND contour.pairs # NIL AND contour.pairs.length > 0 THEN {
[min, max] ← MinMax[contour];
centroid ← [0.5*(min.x+max.x), 0.5*(min.y+max.y)];
};
RETURN[centroid];
};
Area: PUBLIC PROC [contour: Contour] RETURNS [REAL] ~ {
area: REAL ← 0.0;
IF contour # NIL AND contour.pairs # NIL AND contour.closed THEN {
p0: Pair ← contour.pairs[contour.pairs.length-1];
FOR n: NAT IN [0..contour.pairs.length) DO
p1: Pair ← contour.pairs[n];
area ← area+(p1.x-p0.x)*(p0.y+p1.y);
p0 ← p1;
ENDLOOP;
};
RETURN[0.5*area];
};
Operations
Scale: PUBLIC PROC [contour: Contour, scale: REAL] RETURNS [Contour] ~ {
IF contour # NIL AND contour.pairs # NIL THEN FOR n: NAT IN [0..contour.pairs.length) DO
contour.pairs[n] ← Vector2.Mul[contour.pairs[n], scale];
ENDLOOP;
RETURN[contour];
};
Offset: PUBLIC PROC [contour: Contour, offset: Pair] RETURNS [Contour] ~ {
IF contour # NIL AND contour.pairs # NIL THEN FOR n: NAT IN [0..contour.pairs.length) DO
contour.pairs[n] ← Vector2.Add[contour.pairs[n], offset];
ENDLOOP;
RETURN[contour];
};
Center: PUBLIC PROC [contour: Contour] RETURNS [Contour] ~ {
RETURN[Offset[contour, Vector2.Neg[Centroid[contour]]]];
};
Orient: PUBLIC PROC [contour: Contour] RETURNS [Contour] ~ {
IF contour # NIL AND contour.pairs # NIL THEN {
shift: NAT ← 0;
min, max: Pair;
[min, max] ← MinMax[contour];
DO IF contour.pairs[shift].y = min.y THEN EXIT; shift ← shift+1; ENDLOOP;
ShiftPairs[contour.pairs, shift];
IF contour.normals # NIL THEN ShiftPairs[contour.normals, shift];
};
RETURN[contour];
};
Smooth: PUBLIC PROC [contour: Contour] RETURNS [Contour] ~ {
IF contour # NIL AND contour.pairs # NIL AND contour.pairs.length > 2 THEN {
nPairs: NAT ← contour.pairs.length;
start, stop: NAT;
p0, p1, p2: Pair;
IF contour.closed THEN {
start ← 0;
stop ← nPairs-1;
p1 ← contour.pairs[stop];
}
ELSE {
start ← 1;
stop ← nPairs-2;
p1 ← contour.pairs[0];
};
p2 ← contour.pairs[start];
FOR n: NAT IN [start..stop] DO
p0 ← p1;
p1 ← p2;
p2 ← contour.pairs[(n+1) MOD nPairs];
contour.pairs[n] ← [0.25*(p0.x+p1.x+p1.x+p2.x), 0.25*(p0.y+p1.y+p1.y+p2.y)];
ENDLOOP;
};
RETURN[contour];
};
Thin: PUBLIC PROC [contour: Contour, epsilon: REAL ← 3.0] RETURNS [Contour] ~ {
limit: REALABS[RealFns.Cos[epsilon]];
ret: Contour ← Reduce[contour, Thinner];
nPairs0: INTEGER ← contour.pairs.length;
nPairs1: INTEGER ← ret.pairs.length;
Thinner: TestProc ~ {RETURN[p0 = p1 OR Dot[p0, p1, p2] > limit]};
WHILE nPairs0 # nPairs1 DO
nPairs0 ← nPairs1;
nPairs1 ← (ret ← Reduce[ret, Thinner]).pairs.length;
ENDLOOP;
RETURN[ret];
};
Circles
CirclePairs: PUBLIC PROC [nPairs: INTEGER] RETURNS [PairSequence] ~ {
RETURN[Vector3d.CopyPairSequence[IF nPairs <= 0
THEN NIL
ELSE IF nPairs > nCircles
THEN NewCircle[nPairs]
ELSE IF circles[nPairs] = NIL
THEN (circles[nPairs] ← NewCircle[nPairs])
ELSE circles[nPairs]
]];
};
Circle: PUBLIC PROC [nPairs: INTEGER] RETURNS [Contour] ~ {
IF nPairs <= 0
THEN RETURN[NIL]
ELSE {
contour: Contour ← NEW[ContourRep];
contour.closed ← contour.circle ← TRUE;
contour.pairs ← contour.normals ← CirclePairs[nPairs];
RETURN[contour];
};
};
Normals
Normals: PUBLIC PROC [contour: Contour] RETURNS [PairSequence] ~ {
SELECT TRUE FROM
NOT ContourOK[contour] OR contour.pairs.length < 2 => RETURN[NIL];
contour.circle => RETURN[Vector3d.CopyPairSequence[contour.pairs]];
ENDCASE => {
MakeNormal3: PROC [p0, p1, p2: Pair] RETURNS [Pair] ~ {
v: Pair ← Vector2.Add[MakeNormal2[p0, p1], MakeNormal2[p1, p2]];
m: REAL ~ RealFns.SqRt[v.x*v.x+v.y*v.y];
RETURN[IF m # 0.0 THEN [v.x/m, v.y/m] ELSE [0.0, 0.0]];
};
MakeNormal2: PROC [p0, p1: Pair] RETURNS [Pair] ~ {
v: Pair ← Vector2.Sub[p1, p0];
m: REAL ~ RealFns.SqRt[v.x*v.x+v.y*v.y];
RETURN[IF m # 0.0 THEN [-v.y/m, v.x/m] ELSE [0.0, 0.0]];
};
nPairs: INTEGER ← contour.pairs.length;
normals: PairSequence ← NEW[PairSequenceRep[nPairs]];
normals.length ← nPairs;
FOR n: INT IN [0..nPairs) DO
p: Pair ← contour.pairs[n];
SELECT n FROM
0 => normals[n] ← IF contour.closed
THEN MakeNormal3[contour.pairs[nPairs-1], p, contour.pairs[n+1]]
ELSE MakeNormal2[p, contour.pairs[n+1]];
nPairs-1 => normals[n] ← IF contour.closed
THEN MakeNormal3[contour.pairs[n-1], p, contour.pairs[0]]
ELSE MakeNormal2[contour.pairs[n-1], p];
ENDCASE => normals[n] ←
MakeNormal3[contour.pairs[n-1], contour.pairs[n], contour.pairs[n+1]];
ENDLOOP;
RETURN[normals];
};
};
Percents
Percents: PUBLIC PROC [contour: Contour] RETURNS [RealSequence] ~ {
percents: RealSequence ← NIL;
IF contour # NIL AND contour.pairs # NIL THEN {
pairs: PairSequence ← contour.pairs;
percents ← NEW[RealSequenceRep[pairs.length]];
percents.length ← pairs.length;
percents[0] ← 0.0;
IF contour.circle
THEN {
FOR n: NAT IN [1..pairs.length) DO
percents[n] ← REAL[n]/REAL[pairs.length];
ENDLOOP;
}
ELSE {
total: REAL;
FOR n: NAT IN [1..pairs.length) DO
percents[n] ← percents[n-1]+Vector2.Length[Vector2.Sub[pairs[n-1], pairs[n]]];
ENDLOOP;
total ← percents[pairs.length-1];
IF contour.closed
THEN total ← total+Vector2.Length[Vector2.Sub[pairs[pairs.length-1], pairs[0]]];
FOR n: NAT IN [1..pairs.length) DO
percents[n] ← percents[n]/total;
ENDLOOP;
};
};
RETURN[percents];
};
AtPercent: PUBLIC PROC [contour: Contour, percent: REAL] RETURNS [RealIndex] ~ {
IF NOT ContourOK[contour]
THEN RETURN[[0, 0.0]]
ELSE {
pcs: RealSequence ← contour.percents;
nPairs: INTEGER ← contour.pairs.length;
IF pcs = NIL THEN pcs ← contour.percents ← Percents[contour];
FOR n: NAT IN [0..nPairs) DO
SELECT TRUE FROM
pcs[n] = percent => RETURN[[n, 0.0]];
pcs[n] > percent => RETURN[[n-1, (percent-pcs[n-1])/(pcs[n]-pcs[n-1])]];
ENDCASE;
ENDLOOP;
RETURN[[nPairs-1, (percent-pcs[nPairs-1])/(1.0-pcs[nPairs-1])]];
};
};
PercentPair: PUBLIC PROC [contour: Contour, percent: REAL] RETURNS [Pair] ~ {
IF NOT ContourOK[contour]
THEN RETURN[[0.0, 0.0]]
ELSE RETURN[PercentOfPairSequence[contour.pairs, AtPercent[contour, percent]]];
};
NullNormal: ERROR = CODE;
PercentNormal: PUBLIC PROC [contour: Contour, percent: REAL] RETURNS [Pair] ~ {
IF NOT ContourOK[contour]
THEN RETURN[[0.0, 0.0]]
ELSE {
n: Pair ← PercentOfPairSequence[contour.normals, AtPercent[contour, percent]];
m: REAL ~ RealFns.SqRt[n.x*n.x+n.y*n.y];
IF m = 0.0 THEN ERROR NullNormal;
RETURN[[n.x/m, n.y/m]];
};
};
Interpolation/ReSampling/Comparing
Interpolate: PUBLIC PROC [contour0, contour1: Contour, alpha: REAL] RETURNS [Contour] ~ {
IF contour1 = NIL THEN RETURN[contour0];
IF contour0 = NIL THEN RETURN[contour1];
IF alpha = 0.0 THEN RETURN[contour0];
IF alpha = 1.0 THEN RETURN[contour1];
IF contour0.pairs.length < 3 OR contour1.pairs.length < 3
THEN RETURN[NIL]
ELSE {
nPairs0: NAT ← contour0.pairs.length;
nPairs1: NAT ← contour1.pairs.length;
contour: Contour ← NEW[ContourRep];
contour.t ← contour0.t+alpha*(contour1.t-contour0.t);
contour.closed ← contour0.closed AND contour1.closed;
contour.circle ← contour0.circle AND contour1.circle;
IF contour.circle
THEN {
nPairs: INTEGER ← Real.RoundI[nPairs0+alpha*(nPairs1-nPairs0)];
contour.pairs ← contour.normals ← CirclePairs[nPairs];
contour.pairs.length ← contour.pairs.maxLength;
}
ELSE {
n0, n1, nPairs: NAT ← 0;
pcs, pcs0, pcs1: RealSequence ← NIL;
IF contour0.percents = NIL THEN contour0.percents ← Percents[contour0];
IF contour1.percents = NIL THEN contour1.percents ← Percents[contour1];
pcs ← contour.percents ← NEW[RealSequenceRep[nPairs0+nPairs1]];
pcs0 ← contour0.percents;
pcs1 ← contour1.percents;
DO
IF n0 = nPairs0 THEN {
FOR n: NAT IN [n1..nPairs1) DO
pcs[nPairs] ← pcs1[n];
nPairs ← nPairs+1;
ENDLOOP;
EXIT;
};
IF n1 = nPairs1 THEN {
FOR n: NAT IN [n0..nPairs0) DO
pcs[nPairs] ← pcs0[n];
nPairs ← nPairs+1;
ENDLOOP;
EXIT;
};
SELECT TRUE FROM
pcs0[n0] < pcs1[n1] => {pcs[nPairs] ← pcs0[n0]; n0 ← n0+1};
pcs0[n0] > pcs1[n1] => {pcs[nPairs] ← pcs1[n1]; n1 ← n1+1};
ENDCASE => {pcs[nPairs] ← pcs0[n0]; n0 ← n0+1; n1 ← n1+1};
nPairs ← nPairs+1;
ENDLOOP;
contour.pairs ← NEW[PairSequenceRep[nPairs]];
contour.pairs.length ← contour.percents.length ← nPairs;
FOR n: NAT IN [0..nPairs) DO
p0: Pair ← PercentPair[contour0, pcs[n]];
p1: Pair ← PercentPair[contour1, pcs[n]];
contour.pairs[n] ← [p0.x+alpha*(p1.x-p0.x), p0.y+alpha*(p1.y-p0.y)];
ENDLOOP;
[] ← Thin[contour];
};
RETURN[contour];
};
};
Sample: PUBLIC PROC [contour: Contour, nPairs: INTEGER] RETURNS [Contour] ~ {
new: Contour ← NEW[ContourRep ← contour^];
IF ContourOK[contour] THEN {
IF contour.percents = NIL THEN contour.percents ← Percents[contour];
new.pairs ← NEW[PairSequenceRep[nPairs]];
new.pairs.length ← nPairs;
FOR n: NAT IN [0..nPairs) DO
fraction: REALREAL[n]/REAL[nPairs];
nContour: INTEGER ← Real.RoundI[fraction];
alpha: REAL ← fraction-nContour;
percent0: REAL ← contour.percents[nContour];
percent1: REAL ← contour.percents[(nContour+1) MOD contour.pairs.length];
new.pairs[n] ← PercentPair[contour, percent0+alpha*(percent1-percent0)];
ENDLOOP;
};
RETURN[new];
};
Similar: PUBLIC PROC [contour0, contour1: Contour] RETURNS [REAL] ~ {
IF NOT ContourOK[contour0] OR NOT ContourOK[contour1] THEN RETURN[1.0];
IF contour0.pairs.length > contour1.pairs.length
THEN {c: Contour ← contour0; contour0 ← contour1; contour1 ← c};
IF contour0.circle AND contour1.circle
THEN RETURN[
1.0-REAL[contour1.pairs.length-contour0.pairs.length]/REAL[contour1.pairs.length]]
ELSE {
min: REAL ← 1000.0;
IF contour0.pairs.length > contour1.pairs.length
THEN {c: Contour ← contour0; contour0 ← contour1; contour1 ← c};
CheckPercentsAndNormals[contour0];
CheckPercentsAndNormals[contour1];
FOR n: NAT IN [0..contour0.pairs.length) DO
n0: Pair ← contour0.normals[n];
n1: Pair ← PercentNormal[contour1, contour0.percents[n] ! NullNormal => GOTO bad];
IF n0 # [0.0, 0.0] AND n1 # [0.0, 0.0] THEN min ← MIN[min, Vector2.Dot[n0, n1]];
REPEAT
bad => NULL;
ENDLOOP;
RETURN[0.5*(min+1.0)];
};
};
Spans
Spans: PUBLIC PROC [contour: Contour, rectangle: Rectangle] RETURNS [SpanSequence] ~ {
spans: SpanSequence ← NIL;
IF contour # NIL AND contour.pairs # NIL AND contour.closed THEN {
MinY:PROC RETURNS[y:NAT]~{FOR i:NAT IN[0..nSpots)DO y←MIN[spots[i].y,y];ENDLOOP};
MaxY:PROC RETURNS[y:NAT]~{FOR i:NAT IN[0..nSpots)DO y←MAX[spots[i].y,y];ENDLOOP};
PixelProc: Draw2d.PixelProc ~ {
This doesn't work now: need to cull out internal, duplicate horizontal pixels.
IF skip
THEN skip ← FALSE
ELSE {
new: NatList ← NEW[NatListRep ← [n: x]];
yList: NatList ← yLists[y-yMin];
IF yList = NIL OR yList.n > x THEN {new.next ← yList; yLists[y-yMin] ← new}
ELSE {
WHILE yList.next # NIL AND yList.next.n < x DO yList ← yList.next; ENDLOOP;
new.next ← yList.next;
yList.next ← new;
};
count ← count+1;
};
};
count: NAT ← 0;
spots: SpotSequence ← SpotsFromPairs[contour.pairs, rectangle];
nSpots: NAT ← contour.pairs.length;
s0: Spot ← spots[nSpots-1];
yMin: NAT ← MinY[];
yListLength: NAT ← MaxY[]-yMin+1;
yLists: NatListSequence ← NEW[NatListSequenceRep[yListLength]];
up0: BOOL ← s0.y < spots[0].y;
skip: BOOL;
FOR n: NAT IN [0..nSpots) DO
s1: Spot ← spots[n];
up1: BOOL ← s0.y < s1.y;
skip ← up0 = up1;
Draw2d.DoWithLine[[s0.x, s0.y], [s1.x, s1.y], PixelProc];
s0 ← s1;
up0 ← up1;
ENDLOOP;
FOR n: NAT IN [0..yListLength) DO
seg0a, seg0b, seg1a, seg1b: NatList;
IF (seg0a ← yLists[n]) = NIL THEN LOOP;
IF (seg0b ← seg0a.next) = NIL THEN LOOP;
DO
IF (seg1a ← seg0b.next) = NIL THEN EXIT;
IF (seg1b ← seg1a.next) = NIL THEN EXIT;
IF seg0b.n = seg1a.n
THEN seg0a.next ← seg1b
ELSE seg0a ← seg1a;
seg0b ← seg1b;
ENDLOOP;
ENDLOOP;
spans ← NEW[SpanSequenceRep[count]];
FOR n: NAT IN [0..yListLength) DO
yList: NatList ← yLists[n];
WHILE yList # NIL AND yList.next # NIL DO
spans[spans.length].y ← yMin+n;
spans[spans.length].x0 ← yList.n;
yList ← yList.next;
spans[spans.length].x1 ← yList.n;
yList ← yList.next;
spans.length ← spans.length+1;
ENDLOOP;
ENDLOOP;
};
RETURN[spans];
};
FillSpans: PUBLIC PROC [pm: PixelMap, spans: SpanSequence, color: CARDINAL] ~ {
IF spans # NIL THEN FOR n: NAT IN [0..spans.length) DO
span: Span ← spans[n];
ColorTrixBasics.PutLine[pm, span.x0, span.y, span.x1, span.y, color];
ENDLOOP;
};
Filling/Outlining
Fill: PUBLIC PROC [context: Context, contour: Contour, color: Color ← Imager.black] ~ {
path: Imager.PathProc ~ {Path[context, contour, moveTo, lineTo]};
Imager.SetColor[context, color];
IF ContourOK[contour] THEN Imager.MaskFill[context, path];
};
Outline: PUBLIC PROC [context: Context, contour: Contour, color: Color ← Imager.black] ~ {
path: Imager.PathProc ~ {Path[context, contour, moveTo, lineTo]};
Imager.SetColor[context, color];
IF ContourOK[contour] THEN Imager.MaskStroke[context, path];
};
OutlinePm: PUBLIC PROC [pm: PixelMap, contour: Contour, color: CARDINAL] ~ {
IF contour # NIL AND contour.pairs # NIL THEN {
spots: SpotSequence ←
SpotsFromPairs[contour.pairs, [pm.fMin, pm.sMin, pm.fSize, pm.sSize]];
s0, s1, start: Spot ← spots[0];
FOR n: NAT IN [1..contour.pairs.length) DO
s1 ← spots[n];
ColorTrixBasics.PutLine[pm, s0.x, s0.y, s1.x, s1.y, color];
s0 ← s1;
ENDLOOP;
IF contour.closed THEN ColorTrixBasics.PutLine[pm, s0.x, s0.y, start.x, start.y, color];
};
};
FillPm: PUBLIC PROC [pm: PixelMap, contour: Contour, color: CARDINAL] ~ {
FillSpans[pm, Spans[contour, [pm.fMin, pm.sMin, pm.fSize, pm.sSize]], color];
};
Copying
Copy: PUBLIC PROC [contour: Contour] RETURNS [Contour] ~ {
copy: Contour ← NIL;
IF contour # NIL THEN {
copy ← NEW[ContourRep ← contour^];
copy.pairs ← Vector3d.CopyPairSequence[contour.pairs];
IF copy.circle THEN copy.normals ← copy.pairs;
};
RETURN[copy];
};
CopySequence: PUBLIC PROC [contour: ContourSequence] RETURNS [ContourSequence] ~ {
copy: ContourSequence ← NIL;
IF contour # NIL THEN {
copy ← NEW[ContourSequenceRep[contour.length]];
copy.length ← contour.length;
FOR n: NAT IN [0..contour.length) DO copy[n] ← Copy[contour[n]]; ENDLOOP;
};
RETURN[copy];
};
Painting
Paint: PUBLIC PROC [contour: Contour, context: Context, paintNormals: BOOLFALSE] ~ {
Action: PROC ~ {
Xform: PROC [p: Pair] RETURNS [Pair] ~ {RETURN[[wScale*(p.x+1), hScale*(p.y+1)]]};
Draw2d.Clear[context];
IF ContourOK[contour] THEN {
pairs: PairSequence ← contour.pairs;
normals: PairSequence ← contour.normals;
p0, p1: Pair ← Xform[pairs[0]];
IF NOT ContourOK[contour] THEN RETURN;
FOR n: NAT IN [1..pairs.length) DO
p1 ← Xform[pairs[n]];
Draw2d.Line[context, p0, p1];
p0 ← p1;
ENDLOOP;
IF contour.closed THEN Draw2d.Line[context, p1, Xform[pairs[0]]];
IF paintNormals AND normals # NIL THEN FOR n: NAT IN [0..pairs.length) DO
p: Pair ← Xform[pairs[n]];
Draw2d.Arrow[context, p, Vector2.Add[p, Vector2.Mul[normals[n], 30.0]]];
ENDLOOP;
};
};
bounds: Imager.Rectangle ← ImagerBackdoor.GetBounds[context];
wScale: REAL ← 0.5*(bounds.w-1.0);
hScale: REAL ← 0.5*(bounds.h-1.0);
Draw2d.DoWithBuffer[context, Action];
};
To/From Controls
FromControl: PUBLIC PROC [control: Control, t: REAL ← 0.0] RETURNS [Contour] ~ {
controlPos: Controls.PosSequence;
IF control.type = contour AND (controlPos ← Controls.GetContour[control]) # NIL THEN {
wOffset: REAL ← 0.5*(control.w-1);
hOffset: REAL ← 0.5*(control.h-1);
wScale: REALIF wOffset > 0 THEN 1.0/wOffset ELSE 0.0;
hScale: REALIF hOffset > 0 THEN 1.0/hOffset ELSE 0.0;
contour: Contour ← NEW[ContourRep ← [t: t]];
contour.closed ← Controls.IsContourClosed[control];
contour.circle ← FALSE;
contour.pairs ← NEW[PairSequenceRep[controlPos.length]];
contour.pairs.length ← contour.pairs.maxLength;
FOR n: NAT IN [0..contour.pairs.length) DO
p: Controls.Pos ← controlPos[n];
contour.pairs[n] ← [(p.x-wOffset)*wScale, (p.y-hOffset)*hScale];
ENDLOOP;
RETURN[Thin[contour]];
}
ELSE RETURN[NIL];
};
ToControl: PUBLIC PROC [control: Control, contour: Contour, repaint: BOOLTRUE] ~ {
IF control.type # contour THEN RETURN;
IF contour = NIL
THEN Controls.Reset[control]
ELSE {
wScale: REAL ← 0.5*(control.w-1);
hScale: REAL ← 0.5*(control.h-1);
controlPos: Controls.PosSequence←NEW[Controls.PosSequenceRep[contour.pairs.length]];
controlPos.length ← contour.pairs.length;
FOR n: NAT IN [0..contour.pairs.length) DO
p: Pair ← contour.pairs[n];
controlPos[n] ← [Real.RoundI[p.x*wScale+wScale], Real.RoundI[p.y*hScale+hScale]];
ENDLOOP;
Controls.SetContour[control, controlPos, contour.closed, repaint];
};
};
IO
Write: PUBLIC PROC [stream: STREAM, contour: Contour] ~ {
IO.PutF[stream, IF contour.closed THEN "closed\n" ELSE "open\n"];
IO.PutF[stream, IF contour.circle THEN "circle\n" ELSE "notcircle\n"];
IO.PutF[stream, "%g\n", IO.real[contour.t]];
IO.PutF[stream, "%g\n", IO.int[contour.pairs.length]];
FOR n: NAT IN [0..contour.pairs.length) DO
IO.PutF[stream, "%g\t%g\n", IO.real[contour.pairs[n].x], IO.real[contour.pairs[n].y]];
ENDLOOP;
IO.Close[stream];
};
Read: PUBLIC PROC [stream: STREAM] RETURNS [Contour] ~ {
contour: Contour ← NIL;
IF stream # NIL THEN {
contour ← NEW[ContourRep];
contour.closed ← Rope.Equal[ThreeDIO.GetWord[ThreeDIO.GetLine[stream]], "closed"];
contour.circle ← Rope.Equal[ThreeDIO.GetWord[ThreeDIO.GetLine[stream]], "circle"];
contour.t ← ThreeDIO.GetReal[ThreeDIO.GetLine[stream]];
contour.pairs ← NEW[PairSequenceRep[ThreeDIO.GetInteger[ThreeDIO.GetLine[stream]]]];
contour.pairs.length ← contour.pairs.maxLength;
FOR n: NAT IN[0..contour.pairs.length) DO
line: ThreeDIO.Line ← ThreeDIO.GetLine[stream];
contour.pairs[n].x ← ThreeDIO.GetReal[line];
contour.pairs[n].y ← ThreeDIO.GetReal[line];
ENDLOOP;
IO.Close[stream];
};
RETURN[contour];
};
END.