shithub: purgatorio

ref: 09cf3c0ae6c0fed01e32a149f4668b7a7d52944b
dir: /appl/lib/strokes/buildstrokes.b/

View raw version
implement Buildstrokes;

#
# this Limbo code is derived from C code that had the following
# copyright notice, which i reproduce as requested
#
# li_strokesnizer.c
#
#	Copyright 2000 Compaq Computer Corporation.
#	Copying or modifying this code for any purpose is permitted,
#	provided that this copyright notice is preserved in its entirety
#	in all copies or modifications.
#	COMPAQ COMPUTER CORPORATION MAKES NO WARRANTIES, EXPRESSED OR
#	IMPLIED, AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR
#
#
# Adapted from cmu_strokesnizer.c by Jay Kistler.
#
# Where is the CMU copyright???? Gotta track it down - Jim Gettys
#
# Credit to Dean Rubine, Jim Kempf, and Ari Rapkin.
#

include "sys.m";
	sys: Sys;

include "strokes.m";
	strokes: Strokes;
	Classifier, Penpoint, Stroke, Region: import strokes;
	Rconvex, Rconcave, Rplain, Rpseudo: import Strokes;

lidebug: con 0;
stderr: ref Sys->FD;

init(r: Strokes)
{
	sys = load Sys Sys->PATH;
	if(lidebug)
		stderr = sys->fildes(2);
	strokes = r;
}

#
#  Implementation of the Li/Yeung recognition algorithm
#

#  Pre-processing and canonicalization parameters
CANONICAL_X: con 108;
CANONICAL_Y: con 128;
NCANONICAL: con 50;


#
# calculate canonical forms
#

canonical_example(nclasses: int, cnames: array of string, examples: array of list of ref Stroke): (string, array of ref Stroke, array of ref Stroke)
{
	canonex := array[nclasses] of ref Stroke;
	dompts := array[nclasses] of ref Stroke;

	#  make canonical examples for each class.
	for(i := 0; i < nclasses; i++){
		if(lidebug)
			sys->fprint(stderr, "canonical_example: class %s\n", cnames[i]);

		#  Make a copy of the examples.
		pts: list of ref Stroke = nil;
		nex := 0;
		for(exl := examples[i]; exl != nil; exl = tl exl){
			t := hd exl;
			pts = t.copy() :: pts;
			nex++;
		}

		#  Canonicalize each example, and derive the max x and y ranges.
		maxxrange := 0;
		maxyrange := 0;
		for(exl = pts; exl != nil; exl = tl exl){
			e := hd exl;
			ce := canonical_stroke(e);
			if(ce == nil){
				if(lidebug)
					sys->fprint(stderr, "example discarded: can't make canonical form\n");
				continue;	# try the next one
			}
			*e = *ce;
			if(e.xrange > maxxrange)
				maxxrange = e.xrange;
			if(e.yrange > maxyrange)
				maxyrange = e.yrange;
		}

		#  Normalise max ranges.
		(maxxrange, maxyrange) = normalise(maxxrange, maxyrange, CANONICAL_X, CANONICAL_Y);

		#  Re-scale each example to max ranges.
		for(exl = pts; exl != nil; exl = tl exl){
			t := hd exl;
			scalex, scaley: int;
			if(t.xrange == 0)
				scalex = 100;
			else
				scalex = (100*maxxrange + t.xrange/2) / t.xrange;
			if(t.yrange == 0)
				scaley = 100;
			else
				scaley = (100*maxyrange + t.yrange/2) / t.yrange;
			t.translate(0, 0, scalex, scaley);
		}

		#  Average the examples; leave average in first example.
		avg := hd pts;				#  careful, aliasing
		for(k := 0; k < NCANONICAL; k++){
			xsum := 0;
			ysum := 0;
			for(exl = pts; exl != nil; exl = tl exl){
				t := hd exl;
				xsum += t.pts[k].x;
				ysum += t.pts[k].y;
			}
			avg.pts[k].x = (xsum + nex/2) / nex;
			avg.pts[k].y = (ysum + nex/2) / nex;
		}

		#  rescale averaged stroke
		avg.scaleup();

		#  Re-compute the x and y ranges and center the stroke.
		avg.center();

		canonex[i] = avg;	# now it's the canonical representation

		if(lidebug){
			sys->fprint(stderr, "%s, avgpts = %d\n", cnames[i], avg.npts);
			for(j := 0; j < avg.npts; j++){
				p := avg.pts[j];
				sys->fprint(stderr, "  (%d %d)\n", p.x, p.y);
			}
		}

		dompts[i] = avg.interpolate().dominant();	# dominant points of canonical representation
	}

	return (nil, canonex, dompts);
}

normalise(x, y: int, xrange, yrange: int): (int, int)
{
	if((100*x + xrange/2)/xrange > (100*y + yrange/2)/yrange){
		y = (y*xrange + x/2)/x;
		x = xrange;
	}else{
		x = (x*yrange + y/2)/y;
		y = yrange;
	}
	return (x, y);
}

canonical_stroke(points: ref Stroke): ref Stroke
{
	points = points.filter();
	if(points.npts < 2)
		return nil;

	#  Scale up to avoid conversion errors.
	points.scaleup();

	#  Compute an equivalent stroke with equi-distant points
	points = compute_equipoints(points);
	if(points == nil)
		return nil;

	#  Re-translate the points to the origin.
	(minx, miny, maxx, maxy) := points.bbox();
	points.translate(minx, miny, 100, 100);

	#  Store the x and y ranges in the point list.
	points.xrange = maxx - minx;
	points.yrange = maxy - miny;

	if(lidebug){
		sys->fprint(stderr, "Canonical stroke:   %d, %d, %d, %d\n", minx, miny, maxx, maxy);
		for(i := 0; i < points.npts; i++){
			p := points.pts[i];
			sys->fprint(stderr, "      (%d %d)\n", p.x, p.y);
		}
	}

	return points;
}

compute_equipoints(points: ref Stroke): ref Stroke
{
	pathlen := points.length();
	equidist := (pathlen + (NCANONICAL-1)/2) / (NCANONICAL-1);
	equipoints := array[NCANONICAL] of Penpoint;
	if(lidebug)
		sys->fprint(stderr, "compute_equipoints:  npts = %d, pathlen = %d, equidist = %d\n",
				points.npts, pathlen, equidist);

	#  First original point is an equipoint.
	equipoints[0] = points.pts[0];
	nequipoints := 1;
	dist_since_last_eqpt := 0;

	for(i := 1; i < points.npts; i++){
		dx1 := points.pts[i].x - points.pts[i-1].x;
		dy1 := points.pts[i].y - points.pts[i-1].y;
		endx := points.pts[i-1].x*100;
		endy := points.pts[i-1].y*100;
		remaining_seglen := strokes->sqrt(100*100 * (dx1*dx1 + dy1*dy1));
		dist_to_next_eqpt := equidist - dist_since_last_eqpt;
		while(remaining_seglen >= dist_to_next_eqpt){
			if(dx1 == 0){
				#  x-coordinate stays the same
				if(dy1 >= 0)
					endy += dist_to_next_eqpt;
				else
					endy -= dist_to_next_eqpt;
			}else{
				slope := (100*dy1 + dx1/2) / dx1;
				tmp := strokes->sqrt(100*100 + slope*slope);
				dx := (100*dist_to_next_eqpt + tmp/2) / tmp;
				dy := (slope*dx + 50)/100;
				if(dy < 0)
					dy = -dy;
				if(dx1 >= 0)
					endx += dx;
				else
					endx -= dx;
				if(dy1 >= 0)
					endy += dy;
				else
					endy -= dy;
			}
			equipoints[nequipoints].x = (endx + 50) / 100;
			equipoints[nequipoints].y = (endy + 50) / 100;
			nequipoints++;
			#assert(nequipoints <= NCANONICAL);
			dist_since_last_eqpt = 0;
			remaining_seglen -= dist_to_next_eqpt;
			dist_to_next_eqpt = equidist;
		}
		dist_since_last_eqpt += remaining_seglen;
	}

	#  Take care of last equipoint.
	if(nequipoints == NCANONICAL-1){
		#  Make last original point the last equipoint.
		equipoints[nequipoints++] = points.pts[points.npts - 1];
	}
	if(nequipoints != NCANONICAL){	# fell short
		if(lidebug)
			sys->fprint(stderr,"compute_equipoints: nequipoints = %d\n", nequipoints);
		# 	assert(false);
		return nil;
	}
	return ref Stroke(NCANONICAL, equipoints, 0, 0);
}