xref: /inferno-os/appl/lib/strokes/strokes.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement Strokes;
2
3#
4# this Limbo code is derived from C code that had the following
5# copyright notice, which i reproduce as requested
6#
7# li_recognizer.c
8#
9#	Copyright 2000 Compaq Computer Corporation.
10#	Copying or modifying this code for any purpose is permitted,
11#	provided that this copyright notice is preserved in its entirety
12#	in all copies or modifications.
13#	COMPAQ COMPUTER CORPORATION MAKES NO WARRANTIES, EXPRESSED OR
14#	IMPLIED, AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR
15#
16#
17# Adapted from cmu_recognizer.c by Jay Kistler.
18#
19# Where is the CMU copyright???? Gotta track it down - Jim Gettys
20#
21# Credit to Dean Rubine, Jim Kempf, and Ari Rapkin.
22#
23#
24# the copyright notice really did end in the middle of the sentence
25#
26
27#
28# Limbo version for Inferno by forsyth@vitanuova.com, Vita Nuova, September 2001
29#
30
31#
32# the code is reasonably close to the algorithms described in
33#	``On-line Handwritten Alphanumeric Character Recognition Using Dominant Stroke in Strokes'',
34#	Xiaolin Li and Dit-Yan Yueng, Department of Computer Science,
35#	Hong Kong University of Science and Technology, Hong Kong  (23 August 1996)
36#
37
38include "sys.m";
39	sys: Sys;
40
41include "strokes.m";
42
43MAXINT: con 16r7FFFFFFF;
44
45# Dynamic programming parameters
46DP_BAND: con 3;
47SIM_THLD: con 60;	# x100
48#DIST_THLD: con 3200;	# x100
49DIST_THLD: con 3300;	# x100
50
51#  Low-pass filter parameters -- empirically derived
52LP_FILTER_WIDTH: con 6;
53LP_FILTER_ITERS: con 8;
54LP_FILTER_THLD: con 250;	# x100
55LP_FILTER_MIN: con 5;
56
57#  Pseudo-extrema parameters -- empirically derived
58PE_AL_THLD: con 1500;	# x100
59PE_ATCR_THLD: con 135;	# x100
60
61#  Pre-processing and canonicalization parameters
62CANONICAL_X: con 108;
63CANONICAL_Y: con 128;
64DIST_SQ_THRESHOLD: con 3*3;
65
66#  direction-code table; indexed by dx, dy
67dctbl := array[] of {array[] of {1, 0, 7}, array[] of {2, MAXINT, 6}, array[] of {3, 4, 5}};
68
69#  low-pass filter weights
70lpfwts := array[2 * LP_FILTER_WIDTH + 1] of int;
71lpfconst := -1;
72
73lidebug: con 0;
74stderr: ref Sys->FD;
75
76#		x := 0.04 * (i * i);
77#		wtvals[i] = floor(100.0 * exp(x));
78wtvals := array[] of {100, 104, 117, 143, 189, 271, 422};
79
80init()
81{
82	sys = load Sys Sys->PATH;
83	if(lidebug)
84		stderr = sys->fildes(2);
85	for(i := LP_FILTER_WIDTH; i >= 0; i--){
86		wt := wtvals[i];
87		lpfwts[LP_FILTER_WIDTH - i] = wt;
88		lpfwts[LP_FILTER_WIDTH + i] = wt;
89	}
90	lpfconst = 0;
91	for(i = 0; i < (2 * LP_FILTER_WIDTH + 1); i++)
92		lpfconst += lpfwts[i];
93}
94
95Stroke.new(n: int): ref Stroke
96{
97	return ref Stroke(n, array[n] of Penpoint, 0, 0);
98}
99
100Stroke.trim(ps: self ref Stroke, n: int)
101{
102	ps.npts = n;
103	ps.pts = ps.pts[0:n];
104}
105
106Stroke.copy(ps: self ref Stroke): ref Stroke
107{
108	n := ps.npts;
109	a := array[n] of Penpoint;
110	a[0:] = ps.pts[0:n];
111	return ref Stroke(n, a, ps.xrange, ps.yrange);
112}
113
114#
115# return the bounding box of a set of points
116# (note: unlike Draw->Rectangle, the region is closed)
117#
118Stroke.bbox(ps: self ref Stroke): (int, int, int, int)
119{
120	minx := maxx := ps.pts[0].x;
121	miny := maxy := ps.pts[0].y;
122	for(i := 1; i < ps.npts; i++){
123		pt := ps.pts[i];
124		if(pt.x < minx)
125			minx = pt.x;
126		if(pt.x > maxx)
127			maxx = pt.x;
128		if(pt.y < miny)
129			miny = pt.y;
130		if(pt.y > maxy)
131			maxy = pt.y;
132	}
133	return (minx, miny, maxx, maxy);	# warning: closed interval
134}
135
136Stroke.center(ps: self ref Stroke)
137{
138	(minx, miny, maxx, maxy) := ps.bbox();
139	ps.xrange = maxx-minx;
140	ps.yrange = maxy-miny;
141	avgxoff := -((CANONICAL_X - ps.xrange + 1) / 2);
142	avgyoff := -((CANONICAL_Y - ps.yrange + 1) / 2);
143	ps.translate(avgxoff, avgyoff, 100, 100);
144}
145
146Stroke.scaleup(ps: self ref Stroke): int
147{
148	(minx, miny, maxx, maxy) := ps.bbox();
149	xrange := maxx - minx;
150	yrange := maxy - miny;
151	scale: int;
152	if(((100 * xrange + CANONICAL_X / 2) / CANONICAL_X) >
153			 ((100 * yrange + CANONICAL_Y / 2) / CANONICAL_Y))
154		scale = (100 * CANONICAL_X + xrange / 2) / xrange;
155	else
156		scale = (100 * CANONICAL_Y + yrange / 2) / yrange;
157	ps.translate(minx, miny, scale, scale);
158	return scale;
159}
160
161#  scalex and scaley are x 100.
162#  Note that this does NOT update points.xrange and points.yrange!
163Stroke.translate(ps: self ref Stroke, minx: int, miny: int, scalex: int, scaley: int)
164{
165	for(i := 0; i < ps.npts; i++){
166		ps.pts[i].x = ((ps.pts[i].x - minx) * scalex + 50) / 100;
167		ps.pts[i].y = ((ps.pts[i].y - miny) * scaley + 50) / 100;
168	}
169}
170
171TAP_PATHLEN: con 10*100;		# x100
172
173Classifier.match(rec: self ref Classifier, stroke: ref Stroke): (int, string)
174{
175	if(stroke.npts < 1)
176		return (-1, nil);
177
178	#  Check for tap.
179
180	#  First thing is to filter out ``close points.''
181	stroke = stroke.filter();
182
183	#  Unfortunately, we don't have the actual time that each point
184	#  was recorded (i.e., dt is invalid).  Hence, we have to use a
185	#  heuristic based on total distance and the number of points.
186	if(stroke.npts == 1 || stroke.length() < TAP_PATHLEN)
187		return (-1, "tap");
188
189	preprocess_stroke(stroke);
190
191	#  Compute its dominant points.
192	dompts := stroke.interpolate().dominant();
193	best_dist := MAXDIST;
194	best_i := -1;
195	best_name: string;
196
197	#  Score input stroke against every class in classifier.
198	for(i := 0; i < len rec.cnames; i++){
199		name := rec.cnames[i];
200		(sim, dist) := score_stroke(dompts, rec.dompts[i]);
201		if(dist < MAXDIST)
202			sys->fprint(stderr, " (%s, %d, %d)", name, sim, dist);
203		if(dist < DIST_THLD){
204			if(lidebug)
205				sys->fprint(stderr, " (%s, %d, %d)", name, sim, dist);
206			#  Is it the best so far?
207			if(dist < best_dist){
208				best_dist = dist;
209				best_i = i;
210				best_name = name;
211			}
212		}
213	}
214
215	if(lidebug)
216		sys->fprint(stderr, "\n");
217	return (best_i, best_name);
218}
219
220preprocess_stroke(s: ref Stroke)
221{
222	#  Filter out points that are too close.
223	#  We did this earlier, when we checked for a tap.
224
225#	s = s.filter();
226
227
228#     assert(s.npts > 0);
229
230	#  Scale up to avoid conversion errors.
231	s.scaleup();
232
233	#  Center the stroke.
234	s.center();
235
236	if(lidebug){
237		(minx, miny, maxx, maxy) := s.bbox();
238		sys->fprint(stderr, "After pre-processing:  [ %d %d %d %d]\n",
239				minx, miny, maxx, maxy);
240		printpoints(stderr, s, "\n");
241	}
242}
243
244#
245# return the dominant points of Stroke s, assuming s has been through interpolation
246#
247Stroke.dominant(s: self ref Stroke): ref Stroke
248{
249	regions := s.regions();
250
251	#  Dominant points are: start, end, extrema of non plain regions, and midpoints of the preceding.
252	nonplain := 0;
253	for(r := regions; r != nil; r = r.next)
254		if(r.rtype != Rplain)
255			nonplain++;
256	dom := Stroke.new(1 + 2*nonplain + 2);
257
258	#  Pick out dominant points.
259
260	#  start point
261	dp := 0;
262	previx := 0;
263	dom.pts[dp++] = s.pts[previx];
264	currix: int;
265
266	cas := s.contourangles(regions);
267	for(r = regions; r != nil; r = r.next)
268		if(r.rtype != Rplain){
269			max_v := 0;
270			min_v := MAXINT;
271			max_ix := -1;
272			min_ix := -1;
273
274			for(i := r.start; i <= r.end; i++){
275				v := cas[i];
276				if(v > max_v){
277					max_v = v; max_ix = i;
278				}
279				if(v < min_v){
280					min_v = v; min_ix = i;
281				}
282				if(lidebug > 1)
283					sys->fprint(stderr, "  %d\n", v);
284			}
285			if(r.rtype == Rconvex)
286				currix = max_ix;
287			else
288				currix = min_ix;
289
290			dom.pts[dp++] = s.pts[(previx+currix)/2];	# midpoint
291			dom.pts[dp++] = s.pts[currix];	# extreme
292
293			previx = currix;
294		}
295
296	#  last midpoint, and end point
297	lastp := s.npts - 1;
298	dom.pts[dp++] = s.pts[(previx+lastp)/2];
299	dom.pts[dp++] = s.pts[lastp];
300	dom.trim(dp);
301
302	#  Compute chain-code.
303	compute_chain_code(dom);
304
305	return dom;
306}
307
308Stroke.contourangles(s: self ref Stroke, regions: ref Region): array of int
309{
310	V := array[s.npts] of int;
311	V[0] = 18000;
312	for(r := regions; r != nil; r = r.next){
313		for(i := r.start; i <= r.end; i++){
314			if(r.rtype == Rplain){
315				V[i] = 18000;
316			}else{
317				#  For now, simply choose the mid-point.
318				ismidpt := i == (r.start + r.end)/2;
319				if(ismidpt ^ (r.rtype!=Rconvex))
320					V[i] = 18000;
321				else
322					V[i] = 0;
323			}
324		}
325	}
326	V[s.npts - 1] = 18000;
327	return V;
328}
329
330Stroke.interpolate(s: self ref Stroke): ref Stroke
331{
332	#  Compute an upper-bound on the number of interpolated points
333	maxpts := s.npts;
334	for(i := 0; i < s.npts - 1; i++){
335		a := s.pts[i];
336		b := s.pts[i+1];
337		maxpts += abs(a.x - b.x) + abs(a.y - b.y);
338	}
339
340	#  Allocate an array of the maximum size
341	newpts := Stroke.new(maxpts);
342
343	#  Interpolate each of the segments.
344	j := 0;
345	for(i = 0; i < s.npts - 1; i++){
346		j = bresline(s.pts[i], s.pts[i+1], newpts, j);
347		j--;	#  end point gets recorded as start point of next segment!
348	}
349
350	#  Add-in last point and trim
351	newpts.pts[j++] = s.pts[s.npts - 1];
352	newpts.trim(j);
353
354	if(lidebug){
355		sys->fprint(stderr, "After interpolation:\n");
356		printpoints(stderr, newpts, "\n");
357	}
358
359	#  Compute the chain code for P (the list of points).
360	compute_unit_chain_code(newpts);
361
362	return newpts;
363}
364
365#  This implementation is due to an anonymous page on the net
366bresline(startpt: Penpoint, endpt: Penpoint, newpts: ref Stroke, j: int): int
367{
368	x0 := startpt.x;
369	x1 := endpt.x;
370	y0 := startpt.y;
371	y1 := endpt.y;
372
373	stepx := 1;
374	dx := x1-x0;
375	if(dx < 0){
376		dx = -dx;
377		stepx = -1;
378	}
379	dx <<= 1;
380	stepy := 1;
381	dy := y1-y0;
382	if(dy < 0){
383		dy = -dy;
384		stepy = -1;
385	}
386	dy <<= 1;
387	newpts.pts[j++] = (x0, y0, 0);
388	if(dx >= dy){
389		e := dy - (dx>>1);
390		while(x0 != x1){
391			if(e >= 0){
392				y0 += stepy;
393				e -= dx;
394			}
395			x0 += stepx;
396			e += dy;
397			newpts.pts[j++] = (x0, y0, 0);
398		}
399	}else{
400		e := dx - (dy>>1);
401		while(y0 != y1){
402			if(e >= 0){
403				x0 += stepx;
404				e -= dy;
405			}
406			y0 += stepy;
407			e += dx;
408			newpts.pts[j++] = (x0, y0, 0);
409		}
410	}
411	return j;
412}
413
414compute_chain_code(pts: ref Stroke)
415{
416	for(i := 0; i < pts.npts - 1; i++){
417		dx := pts.pts[i+1].x - pts.pts[i].x;
418		dy := pts.pts[i+1].y - pts.pts[i].y;
419		pts.pts[i].chaincode = (12 - quadr(likeatan(dy, dx))) % 8;
420	}
421}
422
423compute_unit_chain_code(pts: ref Stroke)
424{
425	for(i := 0; i < pts.npts - 1; i++){
426		dx := pts.pts[i+1].x - pts.pts[i].x;
427		dy := pts.pts[i+1].y - pts.pts[i].y;
428		pts.pts[i].chaincode = dctbl[dx+1][dy+1];
429	}
430}
431
432Stroke.regions(pts: self ref Stroke): ref Region
433{
434	#  Allocate a 2 x pts.npts array for use in computing the (filtered) Angle set, A_n.
435	R := array[] of {0 to LP_FILTER_ITERS+1 => array[pts.npts] of int};
436	curr := R[0];
437
438	#  Compute the Angle set, A, in the first element of array R.
439	#  Values in R are in degrees, x 100.
440	curr[0] = 18000;		#  a_0
441	for(i := 1; i < pts.npts - 1; i++){
442		d_i := pts.pts[i].chaincode;
443		d_iminusone := pts.pts[i-1].chaincode;
444		if(d_iminusone < d_i)
445			d_iminusone += 8;
446		a_i := (d_iminusone - d_i) % 8;
447		#  convert to degrees, x 100
448		curr[i] = ((12 - a_i) % 8) * 45 * 100;
449	}
450	curr[pts.npts-1] = 18000;	#  a_L-1
451
452	#  Perform a number of filtering iterations.
453	next := R[1];
454	for(j := 0; j < LP_FILTER_ITERS; ){
455		for(i = 0; i < pts.npts; i++){
456			next[i] = 0;
457			for(k := i - LP_FILTER_WIDTH; k <= i + LP_FILTER_WIDTH; k++){
458				oldval: int;
459				if(k < 0 || k >= pts.npts)
460					oldval = 18000;
461				else
462					oldval = curr[k];
463				next[i] += oldval * lpfwts[k - (i	- LP_FILTER_WIDTH)];	#  overflow?
464			}
465			next[i] /= lpfconst;
466		}
467		j++;
468		curr = R[j];
469		next = R[j+1];
470	}
471
472	#  Do final thresholding around PI.
473	#  curr and next are set-up correctly at end of previous loop!
474	for(i = 0; i < pts.npts; i++)
475		if(abs(curr[i] - 18000) < LP_FILTER_THLD)
476			next[i] = 18000;
477		else
478			next[i] = curr[i];
479	curr = next;
480
481	#  Debugging.
482	if(lidebug > 1){
483		for(i = 0; i < pts.npts; i++){
484			p := pts.pts[i];
485			sys->fprint(stderr, "%3d:  (%d %d)  %ud  ",
486				i, p.x, p.y, p.chaincode);
487			for(j = 0; j < 2 + LP_FILTER_ITERS; j++)
488				sys->fprint(stderr, "%d  ", R[j][i]);
489			sys->fprint(stderr, "\n");
490		}
491	}
492
493	#  Do the region segmentation.
494	r := regions := ref Region(regiontype(curr[0]), 0, 0, nil);
495	for(i = 1; i < pts.npts; i++){
496		t := regiontype(curr[i]);
497		if(t != r.rtype){
498			r.end = i-1;
499			if(lidebug > 1)
500				sys->fprint(stderr, "  (%d, %d) %d\n", r.start, r.end, r.rtype);
501			r.next = ref Region(t, i, 0, nil);
502			r = r.next;
503		}
504	}
505	r.end = i-1;
506	if(lidebug > 1)
507		sys->fprint(stderr, "  (%d, %d), %d\n", r.start, r.end, r.rtype);
508
509	#  Filter out convex/concave regions that are too short.
510	for(r = regions; r != nil; r = r.next)
511		if(r.rtype == Rplain){
512			while((nr := r.next) != nil && (nr.end - nr.start) < LP_FILTER_MIN){
513				#  nr must not be plain, and it must be followed by a plain
514				#  assert(nr.rtype != Rplain);
515				#  assert(nr.next != nil && (nr.next).rtype == Rplain);
516				if(nr.next == nil){
517					sys->fprint(stderr, "recog: nr.next==nil\n");	# can't happen
518					break;
519				}
520				r.next = nr.next.next;
521				r.end = nr.next.end;
522			}
523		}
524
525	#  Add-in pseudo-extremes.
526	for(r = regions; r != nil; r = r.next)
527		if(r.rtype == Rplain){
528			arclen := pts.pathlen(r.start, r.end);
529			dx := pts.pts[r.end].x - pts.pts[r.start].x;
530			dy := pts.pts[r.end].y - pts.pts[r.start].y;
531			chordlen := sqrt(100*100 * (dx*dx + dy*dy));
532			atcr := 0;
533			if(chordlen)
534				atcr = (100*arclen + chordlen/2) / chordlen;
535
536			if(lidebug)
537				sys->fprint(stderr, "%d, %d, %d\n", arclen, chordlen, atcr);
538
539			#  Split region if necessary.
540			if(arclen >= PE_AL_THLD && atcr >= PE_ATCR_THLD){
541				mid := (r.start + r.end)/2;
542				end := r.end;
543				r.end = mid - 1;
544				r = r.next = ref Region(Rpseudo, mid, mid,
545					ref Region(Rplain, mid+1, end, r.next));
546			}
547		}
548
549	return regions;
550}
551
552regiontype(val: int): int
553{
554	if(val == 18000)
555		return Rplain;
556	if(val < 18000)
557		return Rconcave;
558	return Rconvex;
559}
560
561#
562# return the similarity of two strokes and,
563# if similar, the distance between them;
564# if dissimilar, the distance is MAXDIST)
565#
566score_stroke(a: ref Stroke, b: ref Stroke): (int, int)
567{
568	sim := compute_similarity(a, b);
569	if(sim < SIM_THLD)
570		return (sim, MAXDIST);
571	return (sim, compute_distance(a, b));
572}
573
574compute_similarity(A: ref Stroke, B: ref Stroke): int
575{
576	#  A is the	longer sequence, length	N.
577	#  B is the shorter sequence, length M.
578	if(A.npts < B.npts){
579		t := A; A = B; B = t;
580	}
581	N := A.npts;
582	M := B.npts;
583
584	#  Allocate and initialize the Gain matrix, G.
585	#  The size of G is M x (N + 1).
586	#  Note that row 0 is unused.
587	#  Similarities are x 10.
588	G := array[M] of array of int;
589	for(i := 1; i < M; i++){
590		G[i] = array[N+1] of int;
591		bcode := B.pts[i-1].chaincode;
592
593		G[i][0] = 0;	# source column
594
595		for(j := 1; j < N; j++){
596			diff := abs(bcode - A.pts[j-1].chaincode);
597			if(diff > 4)
598				diff = 8 - diff;	# symmetry
599			v := 0;
600			if(diff == 0)
601				v = 10;
602			else if(diff == 1)
603				v = 6;
604			G[i][j] = v;
605		}
606
607		G[i][N] = 0;	# sink column
608	}
609
610	#  Do the DP algorithm.
611	#  Proceed in column order, from highest column to the lowest.
612	#  Within each column, proceed from the highest row to the lowest.
613	#  Skip the highest column.
614	for(j := N - 1; j >= 0; j--)
615		for(i = M - 1; i > 0; i--){
616			max := G[i][j + 1];
617			if(i < M-1){
618				t := G[i + 1][j + 1];
619				if(t > max)
620					max = t;
621			}
622			G[i][j] += max;
623		}
624
625	return (10*G[1][0] + (N-1)/2) / (N-1);
626}
627
628compute_distance(A: ref Stroke, B: ref Stroke): int
629{
630	#  A is the	longer sequence, length	N.
631	#  B is the shorter sequence, length M.
632	if(A.npts < B.npts){
633		t := A; A = B; B = t;
634	}
635	N := A.npts;
636	M := B.npts;
637
638	#  Construct the helper vectors, BE and TE, which say for each column
639	#  what are the ``bottom'' and ``top'' rows of interest.
640	BE := array[N+1] of int;
641	TE := array[N+1] of int;
642
643	for(j := 1; j <= N; j++){
644		bot := j + (M - DP_BAND);
645		if(bot > M) bot = M;
646		BE[j] = bot;
647
648		top := j - (N - DP_BAND);
649		if(top < 1) top = 1;
650		TE[j] = top;
651	}
652
653	#  Allocate and initialize the Cost matrix, C.
654	#  The size of C is (M + 1) x (N + 1).
655	#  Note that row and column 0 are unused.
656	#  Costs are x 100.
657	C := array[M+1] of array of int;
658	for(i := 1; i <= M; i++){
659		C[i] = array[N+1] of int;
660		bx := B.pts[i-1].x;
661		by := B.pts[i-1].y;
662
663		for(j = 1; j <= N; j++){
664			ax := A.pts[j-1].x;
665			ay := A.pts[j-1].y;
666			dx := bx - ax;
667			dy := by - ay;
668			dist := sqrt(10000 * (dx * dx + dy * dy));
669
670			C[i][j] = dist;
671		}
672	}
673
674	#  Do the DP algorithm.
675	#  Proceed in column order, from highest column to the lowest.
676	#  Within each column, proceed from the highest row to the lowest.
677	for(j = N; j > 0; j--)
678		for(i = M; i > 0; i--){
679			min := MAXDIST;
680			if(i > BE[j] || i < TE[j] || (j == N && i == M))
681				continue;
682			if(j < N){
683				if(i >= TE[j+1]){
684					tmp := C[i][j+1];
685					if(tmp < min)
686						min = tmp;
687				}
688				if(i < M){
689					tmp := C[i+1][j+1];
690					if(tmp < min)
691						min = tmp;
692				}
693			}
694			if(i < BE[j]){
695				tmp := C[i+1][j];
696				if(tmp < min)
697					min = tmp;
698			}
699			C[i][j] += min;
700		}
701	return (C[1][1] + N / 2) / N;	# dist
702}
703
704#  Result is x 100.
705Stroke.length(s: self ref Stroke): int
706{
707	return s.pathlen(0, s.npts-1);
708}
709
710#  Result is x 100.
711Stroke.pathlen(s: self ref Stroke, first: int, last: int): int
712{
713	l := 0;
714	for(i := first + 1; i <= last; i++){
715		dx := s.pts[i].x - s.pts[i-1].x;
716		dy := s.pts[i].y - s.pts[i-1].y;
717		l += sqrt(100*100 * (dx*dx + dy*dy));
718	}
719	return l;
720}
721
722#  Note that this does NOT update points.xrange and points.yrange!
723Stroke.filter(s: self ref Stroke): ref Stroke
724{
725	pts := array[s.npts] of Penpoint;
726	pts[0] = s.pts[0];
727	npts := 1;
728	for(i := 1; i < s.npts; i++){
729		j := npts - 1;
730		dx := s.pts[i].x - pts[j].x;
731		dy := s.pts[i].y - pts[j].y;
732		magsq := dx * dx + dy * dy;
733		if(magsq >= DIST_SQ_THRESHOLD){
734			pts[npts] = s.pts[i];
735			npts++;
736		}
737	}
738	return ref Stroke(npts, pts[0:npts], 0, 0);
739}
740
741abs(a: int): int
742{
743	if(a < 0)
744		return -a;
745	return a;
746}
747
748#  Code from Joseph Hall (jnhall@sat.mot.com).
749sqrt(n: int): int
750{
751	nn := n;
752	k0 := 2;
753	for(i := n; i > 0; i >>= 2)
754		k0 <<= 1;
755	nn <<= 2;
756	k1: int;
757	for(;;){
758		k1 = (nn / k0 + k0) >> 1;
759		if(((k0 ^ k1) & ~1) == 0)
760			break;
761		k0 = k1;
762	}
763	return (k1 + 1) >> 1;
764}
765
766#  Helper routines from Mark Hayter.
767likeatan(tantop: int, tanbot: int): int
768{
769	#  Use tan(theta)=top/bot -. order for t
770	#  t in range 0..16r40000
771
772	if(tantop == 0 && tantop == 0)
773		return 0;
774	t := (tantop << 16) / (abs(tantop) + abs(tanbot));
775	if(tanbot < 0)
776		t = 16r20000 - t;
777	else if(tantop < 0)
778		t = 16r40000 + t;
779	return t;
780}
781
782quadr(t: int): int
783{
784	return (8 - (((t + 16r4000) >> 15) & 7)) & 7;
785}
786
787printpoints(fd: ref Sys->FD, pts: ref Stroke, sep: string)
788{
789	for(j := 0; j < pts.npts; j++){
790		p := pts.pts[j];
791		sys->fprint(fd, "  (%d %d) %ud%s", p.x, p.y, pts.pts[j].chaincode, sep);
792	}
793}
794