-- CurveTracer.mesa
-- Michael Plass  August 3, 1982 2:41 pm

-- This provides an alternative method for getting a sampled curve from an AIS image, appropriate if the image consists of a line drawing.  The basic idea is to first blur the image so that the backround just reaches the center of lines of the desired width, and then to trace out the 'valley' thus defined.
-- CurveTracer is driven by JaM.  Here are the calls:
-- .ctraceopen	<string> => .	Reads the AIS file.
-- .ctraceclose => .		Closes the AIS file.
-- .csamp x y w => intensity.  Returns the intensity of the blurred image at the given point.
-- .ctrace	x1 y1 x2 y2 w t n => .  Traces a curve.  The expected line width is w.  The trace will start at (x1,y1), and will end somewhere.  One possible end condition is that the trace comes close to (x2,y2) or (x1,y1), another is that n points have been sampled. A third possibility is that the sampled value exceeds the threshold t. The trace starts out in the approximate direction of (x2,y2).

DIRECTORY
  AIS USING [Error, FRef, WRef, OpenFile, CloseFile, OpenWindow, ReadSample, CloseWindow],
  Complex,
  Curve,
  Inline USING [LowHalf],
  Real USING [RoundI],
  JaMFnsDefs,
  IO
  ;

CurveTracer: CEDAR PROGRAM IMPORTS AIS, Complex, Curve, Inline, Real, JaMFnsDefs, IO =
BEGIN

-- This stuff is borrowed from ContourMain
ais: AIS.FRef ← NIL;
window: AIS.WRef ← NIL;

AISToScreen: PROC [x,y: REAL] RETURNS[nx,ny: REAL] ={
	nx ← x; ny ← window.lastScan-y;
	};

ScreenToAIS: PROC [x,y: REAL] RETURNS[nx,ny: REAL] ={
	nx ← x; ny ← window.lastScan-y;
	};

OpenAIS: PROCEDURE = {
	ENABLE AIS.Error =>{JaMFnsDefs.JaMExec["(AIS file error) .print"]; CONTINUE};
	s:STRING←[64];
 	JaMFnsDefs.PopString[s];
 	ais←AIS.OpenFile[s,FALSE];
	window ← AIS.OpenWindow[ais];
	pieceLine ← piecePixel ← -LAST[NAT]; -- invalidate cache
	};

Free: PROCEDURE = {
	ENABLE AIS.Error =>{JaMFnsDefs.JaMExec["(AIS file error) .print"]; CONTINUE};
	AIS.CloseWindow[window]; window ← NIL;
	AIS.CloseFile[ais];  ais ← NIL;
	};
-- End of borrowed code

NarrowToNat: PROC [n: INT] RETURNS [NAT] =
    BEGIN
    IF n<0 THEN n←0;
    IF n>LAST[NAT] THEN n←LAST[NAT];
    RETURN[Inline.LowHalf[n]];
    END;

-- Here is a cache of a part of the blurred image.
pieceSize: NAT = 100;
BlurredLine: TYPE = ARRAY [0..pieceSize) OF NAT;
blurredPiece:  ARRAY [0..pieceSize) OF REF BlurredLine;
pieceLine, piecePixel: INTEGER;
blurriness: NAT ← 0;
GetPiece: PROC [line,pixel: INT] =
	BEGIN
	FOR i:NAT IN [0..pieceSize) DO
		FOR j:NAT IN [0..pieceSize) DO
			blurredPiece[i][j] ← 8*
				(IF i+line IN [window.firstScan..window.lastScan]
				AND j+pixel IN [window.firstPixel..window.lastPixel]
				THEN AIS.ReadSample[window, Inline.LowHalf[line+i], Inline.LowHalf[pixel+j]]
				ELSE 255);
			ENDLOOP;
		ENDLOOP;
	pieceLine ← line;
	piecePixel ← pixel;
	END;

BlurPiece: PROC = 
	BEGIN
	ENABLE AIS.Error =>{JaMFnsDefs.JaMExec["(AIS error) .print"]; CONTINUE};
	prevline,line,nextline: BlurredLine;
	FOR j:NAT IN [0..pieceSize) DO prevline[j] ← blurredPiece[0][j] ENDLOOP;
	FOR i:NAT IN [1..pieceSize-1) DO
		FOR j:NAT IN [0..pieceSize) DO line[j] ← blurredPiece[i][j] ENDLOOP;
		FOR j:NAT IN [0..pieceSize) DO nextline[j] ← blurredPiece[i+1][j] ENDLOOP;
		FOR j:NAT IN [1..pieceSize-1) DO
			blurredPiece[i][j] ← (
				prevline[j-1] + prevline[j] + prevline[j+1] +
				    line[j-1] +     line[j] +     line[j+1] +
				nextline[j-1] + nextline[j] + nextline[j+1])/9;
			ENDLOOP;
		prevline ← line;
		ENDLOOP;
	END;

GetBlurredPiece: PROC[x,y: REAL, b: NAT] = 
	BEGIN -- x and y are in screen coordinates, b is the blurriness
	-- The piece is centered around (x,y)
	line,pixel: REAL;
	[pixel,line] ← ScreenToAIS[x,y];
	line ← line - pieceSize/2;
	pixel ← pixel - pieceSize/2;
	GetPiece [Real.RoundI[line], Real.RoundI[pixel]];
	THROUGH [1..b] DO
		BlurPiece;
		ENDLOOP; 
	blurriness ← b;
	END;

BlurredSample: PROC [a: Complex.Vec, b: NAT] RETURNS [intensity: REAL] = 
	BEGIN OPEN a; -- a is in screen coordinates, b is the blurriness
	line, pixel: REAL;
	[pixel,line] ← ScreenToAIS[x,y];
	IF b # blurriness
	OR NOT (     line IN [pieceLine+b..pieceSize+pieceLine-b)
	        AND pixel IN [piecePixel+b..pieceSize+piecePixel-b))
		THEN GetBlurredPiece[x,y,b];
	intensity ← blurredPiece[Real.RoundI[line]-pieceLine][Real.RoundI[pixel]-piecePixel]/8.0;
	IF FALSE THEN {IO.Put[Curve.TTY[],IO.real[a.x],IO.char[' ]];
		IO.Put[Curve.TTY[],IO.real[a.y],IO.char[' ],IO.real[intensity]];
		IO.Put[Curve.TTY[],IO.string["\n"]]};
	END;

Sample: PROC = {
	x,y,b: REAL;
	b ← MIN[pieceSize/2 - 1, ABS[JaMFnsDefs.GetReal[]]];
	y ← JaMFnsDefs.GetReal[];
	x ← JaMFnsDefs.GetReal[];
	JaMFnsDefs.PushReal[BlurredSample[[x,y],Real.RoundI[b]]];
	};

DistSqr: PROC[a,b: Complex.Vec] RETURNS [REAL] =
	{RETURN[Complex.SqrAbs[Complex.Sub[a,b]]]};

Direction: TYPE =  [0..8);
delta: ARRAY  Direction OF Complex.Vec = [[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1],[0,-1],[1,-1]];

AwayFrom: PROC[i,j: INTEGER] RETURNS [BOOLEAN] =
	BEGIN
	i ← i-j;
	WHILE i<-2 DO i←i+8 ENDLOOP;
	WHILE i>5 DO i←i-8 ENDLOOP;
	RETURN[i>2];
	END;

CurveTrace: PROC = {
	x1,y1,x2,y2,w,t: REAL;
	b, n: NAT;
	closestApproach: REAL ← 10.0E+20;
	stopper: Complex.Vec;
	CloseEnough: PROC [a: Complex.Vec] RETURNS [BOOLEAN] = 
		BEGIN OPEN a;
		IF MAX[ABS[a.x-stopper.x],ABS[a.y-stopper.y]] <= .001 THEN RETURN[TRUE];
		IF x IN [x2-w/2..x2+w/2] AND y IN [y2-w/2..y2+w/2] THEN
			BEGIN
			dSqr: REAL ← DistSqr[a,[x2,y2]];
			IF dSqr>closestApproach THEN RETURN[TRUE];
			closestApproach ← dSqr;
			END;
		RETURN[FALSE];
		END;

	current: Complex.Vec;
	cameFrom: Direction ← FIRST[Direction];

	n ← JaMFnsDefs.PopInteger[];
	t ← JaMFnsDefs.GetReal[];
	w ← ABS[JaMFnsDefs.GetReal[]];
	b ← Real.RoundI[MIN[pieceSize/2.0 - 1, w]];
	y2 ← JaMFnsDefs.GetReal[];
	x2 ← JaMFnsDefs.GetReal[];
	y1 ← JaMFnsDefs.GetReal[];
	x1 ← JaMFnsDefs.GetReal[];
	current ← [x1,y1];
	stopper ← [x2,y2];
	FOR d: Direction IN Direction DO 
		IF DistSqr[[x2,y2],Complex.Add[current,delta[d]]] >
			DistSqr[[x2,y2],Complex.Add[current,delta[cameFrom]]] THEN
			cameFrom ← d
		ENDLOOP; 
	FOR i:NAT IN [1..n] UNTIL CloseEnough[current] DO
		high: REAL ← 0;
		low: REAL ← 1000000;
		new: Complex.Vec;
		newd: Direction;
		IF i>=b THEN Curve.AddSample[Curve.defaultHandle, current.x, current.y];
		IF i=b THEN stopper ← current;
		FOR d: Direction IN Direction DO 
			IF AwayFrom[cameFrom,d] THEN
				BEGIN
				p: Complex.Vec ← Complex.Add[current,delta[d]];
				value: REAL ← BlurredSample[p,b];
				IF value < low THEN {new ← p; low ← value; newd ← d};
				IF value > high THEN high ← value;
				END;
			ENDLOOP;
		cameFrom ← IF newd<4 THEN newd+4 ELSE newd-4;
		IF high-low < .1 THEN EXIT;
		IF low > t THEN EXIT;
		IF JaMFnsDefs.GetJaMBreak[] THEN EXIT;
		current ← new;
		ENDLOOP; 
	};

FOR i:NAT IN [0..pieceSize) DO
	blurredPiece[i] ← NEW[BlurredLine];
	ENDLOOP;

JaMFnsDefs.Register[".ctraceopen"L,OpenAIS];
JaMFnsDefs.Register[".ctraceclose"L,Free];
JaMFnsDefs.Register[".csamp"L,Sample];
JaMFnsDefs.Register[".ctrace"L,CurveTrace];



END.