ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/lib/strokes/buildstrokes.b/
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);
}