Draw2dImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Bloomenthal, October 21, 1986 1:21:46 pm PDT
DIRECTORY Draw2d, Imager, ImagerBackdoor, ImagerDevice, ImagerFont, ImagerPath, ImagerOps, ImagerRasterPrivate, ImagerState, ImagerTransformation, IO, Real, Rope, Vector2;
Draw2dImpl: CEDAR PROGRAM
IMPORTS Imager, ImagerBackdoor, ImagerFont, ImagerOps, ImagerPath, ImagerRasterPrivate, ImagerTransformation, Real, Vector2
EXPORTS Draw2d, Imager
~ BEGIN
Type Declarations
ROPE:   TYPE ~ Rope.ROPE;
Context:  TYPE ~ Imager.Context;
VEC:    TYPE ~ Imager.VEC;     -- RECORD [x, y: REAL]
DrawType: TYPE ~ Draw2d.DrawType;
MarkType: TYPE ~ Draw2d.MarkType;
PixelProc:  TYPE ~ Draw2d.PixelProc;
General Procedures
Clear: PUBLIC PROC [context: Context] ~ {
Imager.SetColor[context, Imager.white];
Imager.MaskRectangle[context, ImagerBackdoor.GetBounds[context]];
Imager.SetColor[context, Imager.black];
};
Label: PUBLIC PROC [context: Context, vec: VEC, rope: ROPE] ~ {
font: Imager.Font ← NIL;
font ← ImagerBackdoor.GetFont[context ! Imager.Error => CONTINUE];
IF font = NIL THEN Imager.SetFont[context, ImagerFont.Find["xerox/tiogafonts/helvetica10"]];
Imager.SetXY[context, vec];
Imager.ShowRope[context, rope];
};
DoWithBuffer: PUBLIC PROC [context: Context, action: PROC, clear: BOOLTRUE] ~ {
rect: Imager.Rectangle ← ImagerBackdoor.GetBounds[context];
color: Imager.Color ← IF clear THEN Imager.white ELSE NIL;
Imager.SetStrokeWidth[context, 0.0];
ImagerOps.DoWithBuffer[context, action, 0, 0, Real.RoundI[rect.w], Real.RoundI[rect.h], color];
};
Line Drawing Procedures
Line: PUBLIC PROC [context: Context, vec0, vec1: VEC, drawType: DrawType ← solid] ~ {
SELECT drawType FROM
solid => Solid[context, vec0, vec1];
dotted => Dot[context, vec0, vec1];
dashed => Dash[context, vec0, vec1];
ENDCASE => NULL;
};
DoWithLine: PUBLIC PROC [vec0, vec1: VEC, pixelProc: PixelProc] ~ {
x0: INTEGER ← Real.RoundI[vec0.x];
y0: INTEGER ← Real.RoundI[vec0.y];
x1: INTEGER ← Real.RoundI[vec1.x];
y1: INTEGER ← Real.RoundI[vec1.y];
dyPos: BOOL ~ y1 > y0;
dxPos: BOOL ~ x1 > x0;
dy: INTEGERIF dyPos THEN y1-y0 ELSE y0-y1;
dx: INTEGERIF dxPos THEN x1-x0 ELSE x0-x1;
eincy: INTEGER ~ dy+dy;
eincx: INTEGER ~ dx+dx;
x, y, incx, incy: INTEGER;
SELECT TRUE FROM
dy = 0 => {
IF NOT dxPos THEN {t: INTEGER ← x0; x0 ← x1; x1 ← t};
FOR x: INTEGER IN [x0..x1] DO IF NOT pixelProc[x, y0] THEN RETURN; ENDLOOP;
};
dx = 0 => {
IF NOT dyPos THEN {t: INTEGER ← y0; y0 ← y1; y1 ← t};
FOR y: INTEGER IN [y0..y1] DO IF NOT pixelProc[x0, y] THEN RETURN; ENDLOOP;
};
dy > dx => {
e: INTEGER ← eincx-dy;
IF dyPos
THEN {y ← y0; x ← x0; incx ← IF dxPos THEN 1 ELSE -1}
ELSE {y ← y1; x ← x1; incx ← IF dxPos THEN -1 ELSE 1};
WHILE dy >= 0 DO
IF NOT pixelProc[x, y] THEN RETURN;
IF e > 0 THEN {x ← x+incx; e ← e-eincy};
e ← e+eincx;
y ← y+1;
dy ← dy-1;
ENDLOOP;
};
ENDCASE => {
e: INTEGER ← eincy-dx;
IF dxPos
THEN {x ← x0; y ← y0; incy ← IF dyPos THEN 1 ELSE -1}
ELSE {x ← x1; y ← y1; incy ← IF dyPos THEN -1 ELSE 1};
WHILE dx >= 0 DO
IF NOT pixelProc[x, y] THEN RETURN;
IF e > 0 THEN {y ← y+incy; e ← e-eincx};
e ← e+eincy;
x ← x+1;
dx ← dx-1;
ENDLOOP;
};
};
Dot: PROC [context: Context, vec0, vec1: VEC] ~ {
stepInc: VEC;
delta: VEC ← [vec1.x-vec0.x, vec1.y-vec0.y];
nSteps: INTEGER ← Real.RoundI[0.2*Real.SqRt[delta.x*delta.x+delta.y*delta.y]];
IF nSteps < 1 THEN RETURN;
stepInc ← [delta.x/nSteps, delta.y/nSteps];
FOR n: NAT IN [0..nSteps) DO
Imager.MaskRectangle[context, [vec0.x, vec0.y, 1, 1]];
vec0 ← [vec0.x+stepInc.x, vec0.y+stepInc.y];
ENDLOOP;
};
Dash: PROC [context: Context, vec0, vec1: VEC] ~ {
delta: VEC ← [vec1.x-vec0.x, vec1.y-vec0.y];
nSteps: INTEGER ← Real.RoundI[0.10*Real.SqRt[delta.x*delta.x+delta.y*delta.y]];
IF nSteps > 0 THEN {
stepInc: VEC ← [delta.x/nSteps, delta.y/nSteps];
drawInc: VEC ← [0.5*stepInc.x, 0.5*stepInc.y];
FOR n: NAT IN[0..nSteps) DO
Solid[context, vec0, [vec0.x+drawInc.x, vec0.y+drawInc.y]];
vec0 ← [vec0.x+stepInc.x, vec0.y+stepInc.y];
ENDLOOP;
};
};
Data:  TYPE ~ ImagerRasterPrivate.Data;
State:  TYPE ~ ImagerState.State;
StateRep: PUBLIC TYPE ~ ImagerState.StateRep;      -- export to Imager.StateRep
ContextOK: PROC [context: Context] RETURNS [Data] ~ {
state: State ~ context.state;
needs: ImagerRasterPrivate.Flags ~
[clientToDevice: TRUE, clientClipper: TRUE, deviceColor: TRUE, devicePriority: TRUE];
WITH context.data SELECT FROM
data: Data => {
device: ImagerDevice.Device ~ data.device;
IF state.changed # ImagerState.notChanged THEN ImagerRasterPrivate.NoteStateChanges[data, state];
IF ImagerRasterPrivate.AndFlags[data.valid, needs] # needs THEN ImagerRasterPrivate.ValidateIfNeeded[data, state, needs];
IF state.np.strokeWidth = 0 AND data.clientClipBoxOnly AND device.class.MaskBoxes#NIL THEN RETURN[data];
};
ENDCASE => NULL;
RETURN[NIL];
};
Solid: PROC [context: Context, vec0, vec1: VEC] ~ {    -- Bresenham's method
data: Data ContextOK[context];
IF data # NIL THEN {
Boxes: PROC [box: ImagerDevice.BoxProc] ~ {
DoSolid[d0, d1, data.clientClipBox, box];
};
device: ImagerDevice.Device ~ data.device;
bounds: ImagerDevice.DeviceBox ← data.clientClipBox;
d0: VEC ← ImagerTransformation.Transform[data.clientToDevice, vec0];
d1: VEC ← ImagerTransformation.Transform[data.clientToDevice, vec1];
IF bounds.smin = bounds.smax OR bounds.fmin = bounds.fmax THEN RETURN;
bounds.smin ← bounds.smin+1;    -- simplify clipping
bounds.fmin ← bounds.fmin+1;
bounds.smax ← bounds.smax-1;
bounds.fmax ← bounds.fmax-1;
IF d0.x < bounds.smin THEN {
IF d1.x < bounds.smin THEN RETURN;
d0.y ← d0.y+(bounds.smin-d0.x)*(d1.y-d0.y)/(d1.x-d0.x);
d0.x ← bounds.smin;
}
ELSE IF d0.x > bounds.smax THEN {
IF d1.x > bounds.smax THEN RETURN;
d0.y ← d0.y+(bounds.smax-d0.x)*(d1.y-d0.y)/(d1.x-d0.x);
d0.x ← bounds.smax;
};
IF d1.x < bounds.smin THEN {
IF d0.x < bounds.smin THEN RETURN;
d1.y ← d1.y+(bounds.smin-d1.x)*(d0.y-d1.y)/(d0.x-d1.x);
d1.x ← bounds.smin;
}
ELSE IF d1.x > bounds.smax THEN {
IF d0.x > bounds.smax THEN RETURN;
d1.y ← d1.y+(bounds.smax-d1.x)*(d0.y-d1.y)/(d0.x-d1.x);
d1.x ← bounds.smax;
};
IF d0.y < bounds.fmin THEN {
IF d1.y < bounds.fmin THEN RETURN;
d0.x ← d0.x+(bounds.fmin-d0.y)*(d1.x-d0.x)/(d1.y-d0.y);
d0.y ← bounds.fmin;
}
ELSE IF d0.y > bounds.fmax THEN {
IF d1.y > bounds.fmax THEN RETURN;
d0.x ← d0.x+(bounds.fmax-d0.y)*(d1.x-d0.x)/(d1.y-d0.y);
d0.y ← bounds.fmax;
};
IF d1.y < bounds.fmin THEN {
IF d0.y < bounds.fmin THEN RETURN;
d1.x ← d1.x+(bounds.fmin-d1.y)*(d0.x-d1.x)/(d0.y-d1.y);
d1.y ← bounds.fmin;
}
ELSE IF d1.y > bounds.fmax THEN {
IF d0.y > bounds.fmax THEN RETURN;
d1.x ← d1.x+(bounds.fmax-d1.y)*(d0.x-d1.x)/(d0.y-d1.y);
d1.y ← bounds.fmax;
};
device.class.MaskBoxes[device: device, bounds: bounds, boxes: Boxes];
}
ELSE Imager.MaskVector[context, vec0, vec1];
};
DoSolid: PROC [d0, d1: VEC, clip: ImagerDevice.DeviceBox, box: ImagerDevice.BoxProc] ~ {
s0: INTEGER ← Real.RoundI[d0.x];
f0: INTEGER ← Real.RoundI[d0.y];
s1: INTEGER ~ Real.RoundI[d1.x];
f1: INTEGER ~ Real.RoundI[d1.y];
dsPos: BOOL ~ s1 > s0;
dfPos: BOOL ~ f1 > f0;
ds: INTEGERIF dsPos THEN s1-s0 ELSE s0-s1;
df: INTEGERIF dfPos THEN f1-f0 ELSE f0-f1;
eincs: INTEGER ~ ds+ds;
eincf: INTEGER ~ df+df;
len: INTEGER ← 0;
s, f, incf, incs: INTEGER;
SELECT TRUE FROM
ds = 0 => {
f ← IF dfPos THEN f0 ELSE f1;
box[[smin: s0, fmin: f, smax: s0+1, fmax: f+df]];
};
df = 0 => {
s ← IF dsPos THEN s0 ELSE s1;
box[[smin: s, fmin: f0, smax: s+ds, fmax: f0+1]];
};
ds > df => {
e: INTEGER ← eincf-ds;
IF dsPos
THEN {s ← s0; f ← f0; incf ← IF dfPos THEN 1 ELSE -1}
ELSE {s ← s1; f ← f1; incf ← IF dfPos THEN -1 ELSE 1};
WHILE ds >= 0 DO
len ← len+1;
IF e > 0 THEN {
box[[smin: s, fmin: f, smax: s+len, fmax: f+1]];
f ← f+incf;
e ← e-eincs;
s ← s+len;
len ← 0;
};
e ← e+eincf;
ds ← ds-1;
ENDLOOP;
IF len > 0 THEN box[[smin: s, fmin: f, smax: s+len, fmax: f+1]];
};
ENDCASE => {
e: INTEGER ← eincs-df;
IF dfPos
THEN {f ← f0; s ← s0; incs ← IF dsPos THEN 1 ELSE -1}
ELSE {f ← f1; s ← s1; incs ← IF dsPos THEN -1 ELSE 1};
WHILE df >= 0 DO
len ← len+1;
IF e > 0 THEN {
box[[smin: s, fmin: f, smax: s+1, fmax: f+len]];
s ← s+incs;
e ← e-eincf;
f ← f+len;
len ← 0;
};
e ← e+eincs;
df ← df-1;
ENDLOOP;
IF len > 0 THEN box[[smin: s, fmin: f, smax: s+1, fmax: f+len]];
};
};
Miscellaneous Procedures
Mark: PUBLIC PROC [context: Context, vec: VEC, markType: MarkType ← cross] ~ {
Action: PROC ~ {
Dot: PROC ~ {Imager.MaskRectangle[context, [vec.x-1, vec.y-1, 3, 3]]};
X: PROC ~ {
Solid[context, [vec.x-4.0, vec.y-4.0], [vec.x+4.0, vec.y+4.0]];
Solid[context, [vec.x+4.0, vec.y-4.0], [vec.x-4.0, vec.y+4.0]];
};
Cross: PROC ~ {
Solid[context, [vec.x-5.0, vec.y], [vec.x+5.0, vec.y]];
Solid[context, [vec.x, vec.y-5.0], [vec.x, vec.y+5.0]];
};
SELECT markType FROM
dot => Dot[];
x => X[];
cross => Cross[];
asterisk => {Cross[]; X[];};
ENDCASE => NULL;
};
Imager.DoSave[context, Action];
};
Square: PUBLIC PROC [context: Context, vec: VEC, size: REAL] ~ {
size2: REAL ← size+size;
Imager.MaskRectangle[context, [vec.x-size-1, vec.y-size, size2, size2]];
};
Circle: PUBLIC PROC [context: Context, vec: VEC, radius: REAL, fill: BOOLFALSE] ~ {
t: Imager.Trajectory ← ImagerPath.MoveTo[[vec.x+radius, vec.y]];
t ← ImagerPath.ArcTo[t, [vec.x, vec.y+radius], [vec.x-radius, vec.y]];
t ← ImagerPath.ArcTo[t, [vec.x, vec.y-radius], [vec.x+radius, vec.y]];
Imager.SetStrokeWidth[context, 1.15];
IF fill THEN Imager.MaskFillTrajectory[context, t]
ELSE Imager.MaskStrokeTrajectory[context, t, TRUE];
};
Arrow: PUBLIC PROC [
context: Context,
tail, head: VEC,
drawType: DrawType ← solid,
vary: BOOLTRUE]
~ {
v: VEC ← Vector2.Sub[head, tail];
mul: REALIF v.x*v.x+v.y*v.y > 75.0*75.0 THEN -0.175*75.0/Vector2.Length[v] ELSE -0.175;
vv: VEC ← Vector2.Mul[v, mul];
p0: VEC ← Vector2.Add[Vector2.Add[head, vv], [0.5*vv.y, -0.5*vv.x]];
p1: VEC ← Vector2.Add[p0, [-vv.y, vv.x]];
Line[context, head, tail, drawType];
Line[context, head, p0, drawType];
Line[context, head, p1, drawType];
};
END.
..
IdentityTransform: PROC [matrix: ImagerTransformation.Transformation] RETURNS [BOOL] ~ {
RETURN[matrix.a = 1.0 AND matrix.e = 1.0
AND matrix.b = 0.0 AND matrix.c = 0.0 AND matrix.d = 0.0 AND matrix.f = 0.0];
};
Circle: PUBLIC PROC [context: Context, vec: VEC, radius: REAL, fill: BOOLFALSE] ~ {
data: Data ContextOK[context];
IF data # NIL AND NOT fill
THEN DoCircle[context, vec, radius, fill]
ELSE {
t: Imager.Trajectory ← ImagerPath.MoveTo[[vec.x+radius, vec.y]];
t ← ImagerPath.ArcTo[t, [vec.x, vec.y+radius], [vec.x-radius, vec.y]];
t ← ImagerPath.ArcTo[t, [vec.x, vec.y-radius], [vec.x+radius, vec.y]];
Imager.SetStrokeWidth[context, 1.15];
IF fill THEN Imager.MaskFillTrajectory[context, t]
ELSE Imager.MaskStrokeTrajectory[context, t, TRUE];
};
};
DoCircle: PROC [context: Context, vec: VEC, radius: REAL, fill: BOOL] ~ {
Imager.Trajectory is slightly faster for fill; this is about 1.7 faster for no fill.
r: INTEGER ← Real.RoundI[radius];
xc: INTEGER ← Real.RoundI[vec.x];
yc: INTEGER ← Real.RoundI[vec.y];
x: INTEGER ← 0;
y: INTEGER ← r;
d: INTEGER ← 3-r-r;
IF fill
THEN WHILE x <= y DO
xPlusX: INTEGER ← xc+x;
xMinusX: INTEGER ← xc-x;
xPlusY: INTEGER ← xc+y;
xMinusY: INTEGER ← xc-y;
yPlusX: INTEGER ← yc+x;
yMinusX: INTEGER ← yc-x;
yPlusY: INTEGER ← yc+y;
yMinusY: INTEGER ← yc-y;
Solid[context, [xMinusX, yPlusY],  [xPlusX, yPlusY]];
Solid[context, [xMinusY, yPlusX],  [xPlusY, yPlusX]];
Solid[context, [xMinusX, yMinusY], [xPlusX, yMinusY]];
Solid[context, [xMinusY, yMinusX], [xPlusY, yMinusX]];
The following produces somewhat diamond-shaped circles, why?
IF d < 0
THEN d ← d+4*(x+6)
ELSE {
d ← d+4*(x-y)+10;
y ← y-1;
};
x ← x+1;
ENDLOOP
ELSE WHILE x <= y DO
xPlusX: INTEGER ← xc+x;
xMinusX: INTEGER ← xc-x;
xPlusY: INTEGER ← xc+y;
xMinusY: INTEGER ← xc-y;
yPlusX: INTEGER ← yc+x;
yMinusX: INTEGER ← yc-x;
yPlusY: INTEGER ← yc+y;
yMinusY: INTEGER ← yc-y;
Imager.MaskRectangle[context, [xMinusX, yPlusY, 1, 1]];
Imager.MaskRectangle[context, [xPlusX, yPlusY, 1, 1]];
Imager.MaskRectangle[context, [xMinusY, yPlusX, 1, 1]];
Imager.MaskRectangle[context, [xPlusY, yPlusX, 1, 1]];
Imager.MaskRectangle[context, [xMinusX, yMinusY, 1, 1]];
Imager.MaskRectangle[context, [xPlusX, yMinusY, 1, 1]];
Imager.MaskRectangle[context, [xMinusY, yMinusX, 1, 1]];
Imager.MaskRectangle[context, [xPlusY, yMinusX, 1, 1]];
IF d < 0
THEN d ← d+4*(x+6)
ELSE {
d ← d+4*(x-y)+10;
y ← y-1;
};
x ← x+1;
ENDLOOP;
};