diff options
Diffstat (limited to 'appl/lib/strokes/buildstrokes.b')
| -rw-r--r-- | appl/lib/strokes/buildstrokes.b | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/appl/lib/strokes/buildstrokes.b b/appl/lib/strokes/buildstrokes.b new file mode 100644 index 00000000..2d8a18c4 --- /dev/null +++ b/appl/lib/strokes/buildstrokes.b @@ -0,0 +1,260 @@ +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); +} |
