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