Subversion Repositories planix.SVN

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
/*
2
 *  li_recognizer.c
3
 *
4
 *	Copyright 2000 Compaq Computer Corporation.
5
 *	Copying or modifying this code for any purpose is permitted,
6
 *	provided that this copyright notice is preserved in its entirety
7
 *	in all copies or modifications.
8
 *	COMPAQ COMPUTER CORPORATION MAKES NO WARRANTIES, EXPRESSED OR
9
 *	IMPLIED, AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR
10
 *
11
 *
12
 *  Adapted from cmu_recognizer.c by Jay Kistler.
13
 *  
14
 *  Where is the CMU copyright???? Gotta track it down - Jim Gettys
15
 *
16
 *  Credit to Dean Rubine, Jim Kempf, and Ari Rapkin.
17
 */
18
 
19
#include <u.h>
20
#include <libc.h>
21
#include <stdio.h>
22
#include <draw.h>
23
#include <scribble.h>
24
#include "scribbleimpl.h"
25
 
26
#include "hre_internal.h"
27
#include "li_recognizer_internal.h"
28
 
29
int lidebug = 0;
30
 
31
#define LI_MAGIC 0xACCBADDD
32
 
33
#define CHECK_LI_MAGIC(_a) \
34
  ((_a) != nil && ((li_recognizer*)(_a))->li_magic == LI_MAGIC)
35
 
36
 
37
static void lialg_initialize(rClassifier *);
38
static int lialg_read_classifier_digest(rClassifier *);
39
static int lialg_canonicalize_examples(rClassifier *);
40
static char *lialg_recognize_stroke(rClassifier *, point_list *);
41
static void lialg_compute_lpf_parameters(void);
42
 
43
 
44
char* li_err_msg = nil;
45
 
46
#define bcopy(s1,s2,n) memmove(s2,s1,n)
47
 
48
/*Freeing classifier*/
49
 
50
static void
51
free_rClassifier(rClassifier* rc);
52
 
53
/*
54
 * Point List Support
55
*/
56
 
57
static point_list*
58
add_example(point_list* l,int npts,pen_point* pts)
59
{
60
	pen_point* lpts = mallocz(npts*sizeof(pen_point), 1);
61
	point_list *p = malloc(sizeof(point_list));
62
 
63
	p->npts = npts;
64
	p->pts = lpts;
65
	p->next = l;       /*Order doesn't matter, so we stick on end.*/
66
 
67
	/*Copy points.*/
68
 
69
	bcopy(pts, lpts, npts * sizeof(pen_point));
70
 
71
	return(p);
72
}
73
 
74
 
75
static void
76
delete_examples(point_list* l)
77
{
78
	point_list* p;
79
 
80
	for( ; l != nil; l = p ) {
81
		p = l->next;
82
		free(l->pts);
83
		free(l);
84
	}
85
}
86
 
87
/*
88
 * Local functions
89
 */
90
 
91
/*
92
 * recognize_internal-Form Vector, use Classifier to classify, return char.
93
 */
94
 
95
static char*
96
recognize_internal(rClassifier* rec, Stroke* str, int*)
97
{
98
	char *res;
99
	point_list *stroke;
100
 
101
	stroke = add_example(nil, str->npts, str->pts);
102
	if (stroke == nil) return(nil);
103
 
104
	res = lialg_recognize_stroke(rec, stroke);
105
 
106
	delete_examples(stroke);
107
	return(res);
108
}
109
 
110
/*
111
 * file_path-Construct pathname, check for proper extension.
112
 */
113
 
114
static int
115
  file_path(char* dir,char* filename,char* pathname)
116
{
117
	char* dot;
118
 
119
	/*Check for proper extension on file name.*/
120
 
121
	dot = strrchr(filename,'.');
122
 
123
	if( dot == nil ) {
124
		return(-1);
125
	}
126
 
127
	/*Determine whether a gesture or character classifier.*/
128
 
129
	if( strcmp(dot,LI_CLASSIFIER_EXTENSION) != 0 ) {
130
		return(-1);
131
	}
132
 
133
	/*Concatenate directory and filename into pathname.*/
134
 
135
	strcpy(pathname,dir);
136
	strcat(pathname,"/");
137
	strcat(pathname,filename);
138
 
139
	return(0);
140
}
141
 
142
/*read_classifier_points-Read points so classifier can be extended.*/
143
 
144
static int 
145
read_classifier_points(FILE* fd,int nclss,point_list** ex,char** cnames)
146
{
147
	int i,j,k;
148
	char buf[BUFSIZ];
149
	int nex = 0;
150
	char* names[MAXSCLASSES];
151
	point_list* examples[MAXSCLASSES];
152
	pen_point* pts;
153
	int npts;
154
 
155
	/*Initialize*/
156
 
157
		for( i = 0; i < MAXSCLASSES; i++ ) {
158
				names[i] = nil;
159
				examples[i] = nil;
160
		}
161
 
162
		/*Go thru classes.*/
163
 
164
		for( k = 0; k < nclss; k++ ) {
165
 
166
				/*Read class name and number of examples.*/
167
 
168
				if( fscanf(fd,"%d %s",&nex,buf) != 2 )
169
					goto unallocate;
170
 
171
				/*Save class name*/
172
 
173
				names[k] = strdup(buf);
174
 
175
				/*Read examples.*/
176
 
177
				for( i = 0; i < nex; i++ ) {
178
 
179
					/*Read number of points.*/
180
 
181
					if( fscanf(fd,"%d",&npts) != 1 )
182
								goto unallocate; /*Boy would I like exceptions!*/
183
 
184
					/*Allocate array for points.*/
185
 
186
					if( (pts = mallocz(npts*sizeof(pen_point), 1)) == nil )
187
								goto unallocate;
188
 
189
					/*Read in points.*/
190
 
191
					for( j = 0; j < npts; j++ ) {
192
								int x,y;
193
								if( fscanf(fd,"%d %d",&x,&y) != 2 ) {
194
									delete_pen_point_array(pts);
195
									goto unallocate;
196
								}
197
								pts[j].Point = Pt(x, y);
198
					}
199
 
200
					/*Add example*/
201
 
202
					if( (examples[k] = add_example(examples[k],npts,pts)) == nil ) {
203
								delete_pen_point_array(pts);
204
								goto unallocate;
205
					}
206
 
207
					delete_pen_point_array(pts);
208
 
209
				}
210
		}
211
 
212
/* ari -- end of list of classes */
213
/* fprint(2, "]\n"); */
214
 
215
		/*Transfer to recognizer.*/
216
 
217
		bcopy(examples,ex,sizeof(examples));
218
		bcopy(names,cnames,sizeof(names));
219
 
220
		return(0);
221
 
222
		/*Error. Deallocate memory and return.*/
223
 
224
unallocate:
225
		for( ; k >= 0; k-- ) {
226
				delete_examples(examples[k]);
227
				free(names[k]);
228
		}
229
 
230
		return(-1);
231
}
232
 
233
/*read_classifier-Read a classifier file.*/
234
 
235
static int read_classifier(FILE* fd,rClassifier* rc)
236
{
237
 
238
	li_err_msg = nil;
239
 
240
	/*Read in classifier file.*/
241
 
242
	if(fscanf(fd, "%d", &rc->nclasses) != 1)
243
		return -1;
244
 
245
	/*Read in the example points, so classifier can be extended.*/
246
 
247
	if( read_classifier_points(fd,rc->nclasses,rc->ex,rc->cnames) != 0 )
248
		return -1;
249
 
250
	return(0);
251
}
252
 
253
/*
254
 * Extension Functions
255
*/
256
 
257
/* getClasses and clearState are by Ari */
258
 
259
static int
260
recognizer_getClasses (recognizer r, char ***list, int *nc)
261
{
262
	int i, nclasses;
263
	li_recognizer* rec;
264
	char **ret;
265
 
266
		rec = (li_recognizer*)r->recognizer_specific;
267
 
268
		/*Check for LI recognizer.*/
269
 
270
		if( !CHECK_LI_MAGIC(rec) ) {
271
				li_err_msg = "Not a LI recognizer";
272
				return(-1);
273
	}
274
 
275
	*nc = nclasses = rec->li_rc.nclasses;
276
	ret = malloc(nclasses*sizeof(char*));
277
 
278
	for (i = 0; i < nclasses; i++)
279
				ret[i] = rec->li_rc.cnames[i];   /* only the 1st char of the cname */
280
	*list = ret;
281
		return 0;
282
}
283
 
284
static int
285
recognizer_clearState (recognizer)
286
{
287
  /*This operation isn't supported by the LI recognizer.*/
288
 
289
  li_err_msg = "Clearing state is not supported by the LI recognizer";
290
 
291
  return(-1);
292
}
293
 
294
static bool isa_li(recognizer r)
295
{ return(CHECK_LI_MAGIC(r)); }
296
 
297
static int
298
recognizer_train(recognizer, rc*, uint, Stroke*, rec_element*, bool)
299
{
300
  /*This operation isn't supported by the LI recognizer.*/
301
 
302
  li_err_msg = "Training is not supported by the LI recognizer";
303
 
304
  return(-1);
305
}
306
 
307
int
308
li_recognizer_get_example (recognizer r,
309
						   int		class, 
310
						   int		instance,
311
						   char		**name, 
312
						   pen_point	**points,
313
						   int		*npts)
314
{
315
	li_recognizer   *rec = (li_recognizer*)r->recognizer_specific;
316
	int nclasses = rec->li_rc.nclasses;
317
	point_list	    *pl;
318
 
319
		if( !CHECK_LI_MAGIC(rec) ) {
320
				li_err_msg = "Not a LI recognizer";
321
				return(-1);
322
		}
323
		if (class > nclasses)
324
				return -1;
325
		pl = rec->li_rc.canonex[class];
326
		while (instance && pl)
327
		{
328
				pl = pl->next;
329
				instance--;
330
		}
331
		if (!pl)
332
				return -1;
333
		*name = rec->li_rc.cnames[class];
334
		*points = pl->pts;
335
		*npts = pl->npts;
336
		return pl->npts; /* I hope [sjm] */
337
}
338
 
339
/*
340
 * API Functions
341
 */
342
 
343
 
344
/*li_recognizer_load-Load a classifier file.*/
345
 
346
static int li_recognizer_load(recognizer r, char* dir, char* filename)
347
{
348
		FILE *fd;
349
		char* pathname;
350
		li_recognizer* rec;
351
		rClassifier* rc;
352
 
353
		rec = (li_recognizer*)r->recognizer_specific;
354
 
355
		/*Make sure recognizer's OK*/
356
 
357
		if( !CHECK_LI_MAGIC(rec) ) {
358
				li_err_msg = "Not a LI recognizer";
359
				return(-1);
360
		}
361
 
362
		rc = &(rec->li_rc);
363
 
364
		/*Check parameters.*/
365
 
366
		if( filename == nil ) {
367
				li_err_msg = "Invalid parameters";
368
				return(-1);
369
		}
370
 
371
		/*We let the directory be null.*/
372
 
373
		if( dir == nil || (int)strlen(dir) <= 0 ) {
374
				dir = ".";
375
		}
376
 
377
		if(0)fprint(2, "dir = %s, filename = %s\n",
378
				dir, filename);
379
 
380
		/*Make full pathname and check filename*/
381
 
382
		pathname = malloc((strlen(dir) + strlen(filename) + 2)*sizeof(char));
383
		if( file_path(dir, filename, pathname) == -1 ) {
384
				free(pathname);
385
				li_err_msg = "Not a LI recognizer classifier file";
386
				return -1;
387
		}
388
 
389
		/* Try to short-circuit the full classifier-file processing. */
390
		rc->file_name = pathname;
391
		if (lialg_read_classifier_digest(rc) == 0)
392
				return(0);
393
		rc->file_name = nil;
394
 
395
		/*Open the file*/
396
 
397
		if( (fd = fopen(pathname,"r")) == nil ) {
398
				li_err_msg = "Can't open classifier file";
399
				if(0)fprint(2, "Can't open %s.\n", pathname);
400
				free(pathname);
401
				return(-1);
402
		}
403
 
404
		/*If rClassifier is OK, then delete it first.*/
405
 
406
		if( rc->file_name != nil ) {
407
				free_rClassifier(rc);
408
		}
409
 
410
		/*Read classifier.*/
411
 
412
		if( read_classifier(fd,rc) < 0 ) {
413
				free(pathname);
414
				return(-1);
415
		}
416
 
417
		/*Close file.*/
418
 
419
		fclose(fd);
420
 
421
		/*Add classifier name.*/
422
 
423
		rc->file_name = pathname;
424
 
425
		/* Canonicalize examples. */
426
		if (lialg_canonicalize_examples(rc) != 0) {
427
				free(pathname);
428
				rc->file_name = nil;
429
				return -1;
430
		}
431
 
432
		return(0);
433
}
434
 
435
/*li_recognizer_save-Save a classifier file.*/
436
 
437
static int li_recognizer_save(recognizer, char*, char*)
438
{ 
439
		/*This operation isn't supported by the LI recognizer.*/
440
 
441
		li_err_msg = "Saving is not supported by the LI recognizer";
442
		return -1;
443
}
444
 
445
static wordset
446
li_recognizer_load_dictionary(recognizer, char*, char*)
447
{
448
		/*This operation isn't supported by the LI recognizer.*/
449
 
450
		li_err_msg = "Dictionaries are not supported by the LI recognizer";
451
		return nil;
452
}
453
 
454
static int
455
li_recognizer_save_dictionary(recognizer, char*, char*, wordset)
456
{
457
		/*This operation isn't supported by the LI recognizer.*/
458
		li_err_msg = "Dictionaries are not supported by the LI recognizer";
459
		return -1;
460
}
461
 
462
static int
463
li_recognizer_free_dictionary(recognizer, wordset)
464
{
465
  /*This operation isn't supported by the LI recognizer.*/
466
 
467
  li_err_msg = "Dictionaries are not supported by the LI recognizer";
468
 
469
  return -1;
470
 
471
}
472
 
473
static int
474
li_recognizer_add_to_dictionary(recognizer, letterset*, wordset)
475
{
476
		/*This operation isn't supported by the LI recognizer.*/
477
		li_err_msg = "Dictionaries are not supported by the LI recognizer";
478
		return -1;
479
}
480
 
481
static int
482
li_recognizer_delete_from_dictionary(recognizer, letterset*, wordset)
483
{
484
		/*This operation isn't supported by the LI recognizer.*/
485
		li_err_msg = "Dictionaries are not supported by the LI recognizer";
486
		return -1;
487
}
488
 
489
static char*
490
li_recognizer_error(recognizer rec)
491
{
492
	char* ret = li_err_msg;
493
 
494
	/*Check for LI recognizer.*/
495
 
496
	if( !CHECK_LI_MAGIC(rec->recognizer_specific) ) {
497
				li_err_msg = "Not a LI recognizer";
498
				return nil;
499
	}
500
	li_err_msg = nil;
501
	return ret;
502
}
503
 
504
static int 
505
li_recognizer_clear(recognizer r, bool)
506
{
507
		li_recognizer* rec; 
508
 
509
		rec = (li_recognizer*)r->recognizer_specific;
510
		/*Check for LI recognizer.*/
511
		if( !CHECK_LI_MAGIC(rec) ) {
512
				li_err_msg = "Not a LI recognizer";
513
				return 0;
514
		}
515
		return 0;
516
}
517
 
518
static int 
519
li_recognizer_set_context(recognizer, rc*)
520
{
521
		/*This operation isn't supported by the LI recognizer.*/
522
		li_err_msg = "Contexts are not supported by the LI recognizer";
523
		return -1;
524
}
525
 
526
static rc*
527
li_recognizer_get_context(recognizer)
528
{
529
		/*This operation isn't supported by the LI recognizer.*/
530
		li_err_msg = "Contexts are not supported by the LI recognizer";
531
		return nil;
532
}
533
 
534
static int 
535
li_recognizer_get_buffer(recognizer, uint*, Stroke**)
536
{
537
		/*This operation isn't supported by the LI recognizer.*/
538
		li_err_msg = "Buffer get/set are not supported by the LI recognizer";
539
		return -1;
540
}
541
 
542
static int 
543
li_recognizer_set_buffer(recognizer, uint, Stroke*)
544
{
545
		/*This operation isn't supported by the LI recognizer.*/
546
		li_err_msg = "Buffer get/set are not supported by the LI recognizer";
547
		return -1;
548
}
549
 
550
static int
551
li_recognizer_translate(recognizer r, uint ncs, Stroke* tps, bool, int* nret, rec_alternative** ret)
552
{
553
	char* clss;
554
	li_recognizer* rec; 
555
	int conf;
556
	rClassifier* rc;
557
 
558
	rec = (li_recognizer*)r->recognizer_specific;
559
 
560
	*nret = 0;
561
	*ret = nil;
562
 
563
	/*Check for LI recognizer.*/
564
 
565
	if( !CHECK_LI_MAGIC(rec) ) {
566
				li_err_msg = "Not a LI recognizer";
567
				return(-1);
568
	}
569
 
570
		rc = &(rec->li_rc);
571
 
572
	/*Check for valid parameters.*/
573
	if (ncs < 1) {
574
				li_err_msg = "Invalid parameters: ncs";
575
				return(-1);
576
	}
577
	if( tps == nil) {
578
				li_err_msg = "Invalid parameters: tps";
579
				return(-1);
580
	}
581
	if( nret == nil) {
582
				li_err_msg = "Invalid parameters: nret";
583
				return(-1);
584
	}
585
	if( ret == nil) {
586
				li_err_msg = "Invalid parameters: ret";
587
				return(-1);
588
	}
589
 
590
	/*
591
	 * Go through the stroke array and recognize. Since this is a single
592
	 *   stroke recognizer, each stroke is treated as a separate
593
	 *   character or gesture. We allow only characters or gestures
594
	 *   to be recognized at one time, since otherwise, handling
595
	 *   the display of segmentation would be difficult. 
596
	*/
597
	clss = recognize_internal(rc,tps,&conf);
598
	if (clss == nil) {
599
				*nret = 1;
600
				return(0);
601
	}
602
 
603
	/*Return values.*/
604
	*nret = 1;
605
	return(*clss);
606
}
607
 
608
 
609
static rec_fn*
610
li_recognizer_get_extension_functions(recognizer rec)
611
{
612
	rec_fn* ret;
613
 
614
	/*Check for LI recognizer.*/
615
 
616
	if( !CHECK_LI_MAGIC(rec->recognizer_specific) ) {
617
				li_err_msg = "Not a LI recognizer";
618
				return(nil);
619
	}
620
 
621
	ret = make_rec_fn_array(LI_NUM_EX_FNS);
622
 
623
/* ari -- clearState & getClasses are mine */
624
	ret[LI_GET_CLASSES] =	(rec_fn)recognizer_getClasses;
625
	ret[LI_CLEAR] =			(rec_fn)recognizer_clearState;
626
	ret[LI_ISA_LI] =		(rec_fn)isa_li;
627
	ret[LI_TRAIN] =			(rec_fn)recognizer_train;
628
 
629
	return(ret);
630
}
631
 
632
static char**
633
li_recognizer_get_gesture_names(recognizer)
634
{
635
		/*This operation isn't supported by the LI recognizer.*/
636
		li_err_msg = "Gestures are not supported by the LI recognizer";
637
		return nil;
638
}
639
 
640
static xgesture
641
li_recognizer_set_gesture_action(recognizer, char*, xgesture, void*)
642
{
643
		/*This operation isn't supported by the LI recognizer.*/
644
		li_err_msg = "Gestures are not supported by the LI recognizer";
645
		return nil;
646
}
647
 
648
/*
649
 * Exported Functions
650
*/
651
 
652
/*RECOGNIZER_INITIALIZE-Initialize the recognizer.*/
653
 
654
/* note from ari:  this expands via pre-processor to
655
 *
656
 * recognizer __recognizer_internal_initialize(rec_info* ri)
657
 */
658
 
659
RECOGNIZER_INITIALIZE(ri)
660
{
661
	recognizer r;
662
	li_recognizer* rec;
663
	int i;
664
 
665
	/*Check that locale matches.*/
666
 
667
	if( strcmp(ri->ri_locale,LI_SUPPORTED_LOCALE) != 0 ) {
668
		li_err_msg = "Not a supported locale";
669
/* fprint(2, "Locale error.\n");*/
670
		return(nil);
671
	}
672
 
673
	/*
674
	 * Check that character sets match. Note that this is only approximate,
675
	 * since the classifier file will have more information.
676
	*/
677
 
678
	if( ri->ri_subset != nil ) {
679
	  for(i = 0; ri->ri_subset[i] != nil; i++ ) {
680
 
681
		if( strcmp(ri->ri_subset[i],UPPERCASE) != 0 &&
682
			strcmp(ri->ri_subset[i],LOWERCASE) != 0  &&
683
			strcmp(ri->ri_subset[i],DIGITS) != 0 &&
684
			strcmp(ri->ri_subset[i],GESTURE) != 0 ) {
685
		  li_err_msg = "Not a supported character set";
686
/* fprint(2, "charset error.\n"); */
687
 
688
		  return(nil);
689
		}
690
	  }
691
	}
692
 
693
/* ari */
694
	r = make_recognizer(ri);
695
/* fprint(2, "past make_recognizer.\n"); */
696
 
697
	if( r == nil ) {
698
		li_err_msg = "Can't allocate storage";
699
 
700
		return(nil);
701
	}
702
 
703
	/*Make a LI recognizer structure.*/
704
 
705
 
706
	/* rec = (li_recognizer*)safe_malloc(sizeof(li_recognizer))) == nil ); */
707
 
708
	rec = malloc(sizeof(li_recognizer));
709
 
710
	r->recognizer_specific = rec;
711
 
712
	rec->li_rc.file_name = nil;
713
	rec->li_rc.nclasses = 0;
714
 
715
	/*Initialize the recognizer struct.*/
716
 
717
	r->recognizer_load_state = li_recognizer_load;
718
	r->recognizer_save_state = li_recognizer_save;
719
	r->recognizer_load_dictionary = li_recognizer_load_dictionary;
720
	r->recognizer_save_dictionary = li_recognizer_save_dictionary;
721
	r->recognizer_free_dictionary = li_recognizer_free_dictionary;
722
	r->recognizer_add_to_dictionary = li_recognizer_add_to_dictionary;
723
	r->recognizer_delete_from_dictionary = li_recognizer_delete_from_dictionary;
724
	r->recognizer_error = li_recognizer_error;
725
	r->recognizer_translate = li_recognizer_translate;
726
	r->recognizer_get_context = li_recognizer_get_context;
727
	r->recognizer_set_context = li_recognizer_set_context;
728
	r->recognizer_get_buffer = li_recognizer_get_buffer;
729
	r->recognizer_set_buffer = li_recognizer_set_buffer;
730
	r->recognizer_clear = li_recognizer_clear;
731
	r->recognizer_get_extension_functions = 
732
	  li_recognizer_get_extension_functions;
733
	r->recognizer_get_gesture_names = li_recognizer_get_gesture_names;
734
	r->recognizer_set_gesture_action = 
735
	  li_recognizer_set_gesture_action;
736
 
737
	/*Initialize LI Magic Number.*/
738
 
739
	rec->li_magic = LI_MAGIC;
740
 
741
	/*Initialize rClassifier.*/
742
 
743
	rec->li_rc.file_name = nil;
744
 
745
	for( i = 0; i < MAXSCLASSES; i++ ) {
746
		rec->li_rc.ex[i] = nil;
747
		rec->li_rc.cnames[i] = nil;
748
	}
749
 
750
	lialg_initialize(&rec->li_rc);
751
 
752
	/*Get rid of error message. Not needed here.*/
753
	li_err_msg = nil;
754
 
755
	return(r);
756
}
757
 
758
/*free_rClassifier-Free the rClassifier.*/
759
 
760
static void
761
free_rClassifier(rClassifier* rc)
762
{
763
	int i;
764
 
765
	if( rc->file_name != nil) {
766
		free(rc->file_name);
767
	}
768
 
769
	for( i = 0; rc->ex[i] != nil; i++) {
770
		delete_examples(rc->ex[i]);
771
		free(rc->cnames[i]);
772
	}
773
 
774
}
775
 
776
/*RECOGNIZER_FINALIZE-Deallocate the recognizer, finalize.*/
777
 
778
RECOGNIZER_FINALIZE(r)
779
{
780
		li_recognizer* rec = (li_recognizer*)r->recognizer_specific;
781
 
782
		/*Make sure this is a li_recognizer first*/
783
		if( !CHECK_LI_MAGIC(rec) ) {
784
				li_err_msg = "Not a LI recognizer";
785
				return(-1);
786
	}
787
 
788
		/*Deallocate rClassifier resources.*/
789
 
790
		free_rClassifier(&(rec->li_rc));
791
 
792
		/*Deallocate the li_recognizer struct.*/
793
		free(rec);
794
 
795
		/*Deallocate the recognizer*/
796
		delete_recognizer(r);
797
 
798
		return(0);
799
}
800
 
801
 
802
/* **************************************************
803
 
804
  Implementation of the Li/Yeung recognition algorithm
805
 
806
************************************************** */
807
 
808
#define	WORST_SCORE	0x7fffffff
809
 
810
/* Dynamic programming parameters */
811
#define	DP_BAND		3
812
#define	MIN_SIM		0
813
#define	MAX_DIST	0x7fffffff
814
#define	SIM_THLD	60	/* x 100 */
815
#define	DIST_THLD	3200	/* x 100 */
816
 
817
/* Low-pass filter parameters -- empirically derived */
818
#define	LP_FILTER_WIDTH	6
819
#define	LP_FILTER_ITERS	8
820
#define	LP_FILTER_THLD	250	/* x 100 */
821
#define	LP_FILTER_MIN	5
822
 
823
/* Pseudo-extrema parameters -- empirically derived */
824
#define	PE_AL_THLD	1500	/* x 100 */
825
#define	PE_ATCR_THLD	135	/* x 100 */
826
 
827
/* Contour-angle derivation parameters */
828
#define	T_ONE		1
829
#define	T_TWO		20
830
 
831
/* Pre-processing and canonicalization parameters */
832
#define	CANONICAL_X	108
833
#define	CANONICAL_Y	128
834
#define	DIST_SQ_THRESHOLD   (3*3)	/* copied from fv.h */
835
#define	NCANONICAL	50
836
 
837
/* Tap-handling parameters */
838
#define	TAP_CHAR	"."
839
#define	TAP_TIME_THLD	150	    /* msec */
840
#define	TAP_DIST_THLD	75	    /* dx * dx + dy * dy */
841
#define	TAP_PATHLEN	1000	    /* x 100 */
842
 
843
 
844
/* region types */
845
#define	RGN_CONVEX  0
846
#define	RGN_CONCAVE 1
847
#define	RGN_PLAIN   2
848
#define	RGN_PSEUDO  3
849
 
850
 
851
typedef struct RegionList {
852
	int start;
853
	int end;
854
	int type;
855
	struct RegionList *next;
856
} region_list;
857
 
858
 
859
/* direction-code table; indexed by dx, dy */
860
static int lialg_dctbl[3][3] = {{1, 0, 7}, {2, 0x7FFFFFFF, 6}, {3, 4, 5}};
861
 
862
/* low-pass filter weights */
863
static int lialg_lpfwts[2 * LP_FILTER_WIDTH + 1];
864
static int lialg_lpfconst = -1;
865
 
866
 
867
static int lialg_preprocess_stroke(point_list *);
868
static point_list *lialg_compute_dominant_points(point_list *);
869
static point_list *lialg_interpolate_points(point_list *);
870
static void lialg_bresline(pen_point *, pen_point *, point_list *, int *);
871
static void lialg_compute_chain_code(point_list *);
872
static void lialg_compute_unit_chain_code(point_list *);
873
static region_list *lialg_compute_regions(point_list *);
874
static point_list *lialg_compute_dompts(point_list *, region_list *);
875
static int *lialg_compute_contour_angle_set(point_list *, region_list *);
876
static void lialg_score_stroke(point_list *, point_list *, int *, int *);
877
static int lialg_compute_similarity(point_list *, point_list *);
878
static int lialg_compute_distance(point_list *, point_list *);
879
 
880
static int lialg_read_classifier_digest(rClassifier *);
881
 
882
static int lialg_canonicalize_examples(rClassifier *);
883
static int lialg_canonicalize_example_stroke(point_list *);
884
static int lialg_compute_equipoints(point_list *);
885
 
886
static int lialg_compute_pathlen(point_list *);
887
static int lialg_compute_pathlen_subset(point_list *, int, int);
888
static int lialg_filter_points(point_list *);
889
static int lialg_translate_points(point_list *, int, int, int, int);
890
static void lialg_get_bounding_box(point_list *, int *, int *, int *, int *);
891
static void lialg_compute_lpf_parameters();
892
static int isqrt(int);
893
static int likeatan(int, int);
894
static int quadr(int);
895
 
896
 
897
/*************************************************************
898
 
899
  Core routines for the Li/Yeung recognition algorithm
900
 
901
 *************************************************************/
902
 
903
static void lialg_initialize(rClassifier *rec) {
904
	int i;
905
 
906
	/* Initialize the dompts arrays. */
907
	for (i = 0; i < MAXSCLASSES; i++) {
908
		rec->dompts[i] = nil;
909
	}
910
}
911
 
912
 
913
/*
914
 *  Main recognition routine -- called by HRE API.
915
 */
916
static char *lialg_recognize_stroke(rClassifier *rec, point_list *stroke) {
917
		int i;
918
		char *name = nil;
919
		point_list *input_dompts = nil;
920
		char *best_name = nil;
921
		int best_score = WORST_SCORE;
922
		char *curr_name;
923
		point_list *curr_dompts;
924
 
925
		/*    (void)gettimeofday(&stv, nil);*/
926
 
927
		if (stroke->npts < 1) goto done;
928
 
929
		/* Check for tap. */
930
 
931
		/* First thing is to filter out ``close points.'' */
932
		if (lialg_filter_points(stroke) != 0) return(nil);
933
 
934
		/* Unfortunately, we don't have the actual time that each point */
935
		/* was recorded (i.e., dt is invalid).  Hence, we have to use a */
936
		/* heuristic based on total distance and the number of points. */
937
		if (stroke->npts == 1 || lialg_compute_pathlen(stroke) < TAP_PATHLEN)
938
			return(TAP_CHAR);
939
 
940
		/* Pre-process input stroke. */
941
		if (lialg_preprocess_stroke(stroke) != 0) goto done;
942
 
943
		/* Compute its dominant points. */
944
		input_dompts = lialg_compute_dominant_points(stroke);
945
		if (input_dompts == nil) goto done;
946
 
947
		/* Score input stroke against every class in classifier. */
948
		for (i = 0, curr_name = rec->cnames[i], curr_dompts = rec->dompts[i];
949
				i < MAXSCLASSES && curr_name != nil && curr_dompts != nil;
950
				i++, curr_name = rec->cnames[i], curr_dompts = rec->dompts[i]) {
951
				int sim;
952
				int dist;
953
				int curr_score;
954
 
955
				lialg_score_stroke(input_dompts, curr_dompts, &sim, &dist);
956
				curr_score = dist;
957
 
958
				if (lidebug && curr_score < DIST_THLD)
959
				fprint(2, "(%s, %d, %d) ", curr_name, sim, dist);
960
 
961
				/* Is it the best so far? */
962
				if (curr_score < best_score && curr_score <= DIST_THLD) {
963
				best_score = curr_score;
964
				best_name = curr_name;
965
				}
966
		}
967
 
968
		if (lidebug)
969
				fprint(2, "\n");
970
 
971
		/* No errors. */
972
		name = best_name;
973
 
974
done:
975
		delete_examples(input_dompts);
976
		return(name);
977
}
978
 
979
 
980
static int lialg_preprocess_stroke(point_list *points) {
981
	int minx, miny, maxx, maxy, xrange, yrange, scale, xoff, yoff;
982
 
983
	/* Filter out points that are too close. */
984
	/* We did this earlier, when we checked for a tap. */
985
/*
986
	if (lialg_filter_points(points) != 0) return(-1);
987
*/
988
 
989
/*    assert(points->npts > 0);*/
990
 
991
	/* Scale up to avoid conversion errors. */
992
	lialg_get_bounding_box(points, &minx, &miny, &maxx, &maxy);
993
	xrange = maxx - minx;
994
	yrange = maxy - miny;
995
	scale = ( ((100 * xrange + CANONICAL_X / 2) / CANONICAL_X) > 
996
			  ((100 * yrange + CANONICAL_Y / 2) / CANONICAL_Y))
997
	  ? (100 * CANONICAL_X + xrange / 2) / xrange
998
	  : (100 * CANONICAL_Y + yrange / 2) / yrange;
999
	if (lialg_translate_points(points, minx, miny, scale, scale) != 0)
1000
		return(-1);
1001
 
1002
	/* Center the stroke. */
1003
	lialg_get_bounding_box(points, &minx, &miny, &maxx, &maxy);
1004
	xrange = maxx - minx;
1005
	yrange = maxy - miny;
1006
	xoff = -((CANONICAL_X - xrange + 1) / 2);
1007
	yoff = -((CANONICAL_Y - yrange + 1) / 2);
1008
	if (lialg_translate_points(points, xoff, yoff, 100, 100) != 0) return(-1);
1009
 
1010
	/* Store the x and y ranges in the point list. */
1011
	xrange = maxx - minx;
1012
	yrange = maxy - miny;
1013
	points->xrange = xrange;
1014
	points->yrange = yrange;
1015
 
1016
	if (lidebug) {
1017
		int i;
1018
		fprint(2, "After pre-processing:   %d %d %d %d\n",
1019
				minx, miny, maxx, maxy);
1020
		for (i = 0; i < points->npts; i++)
1021
			fprint(2, "      (%P)\n", points->pts[i].Point);
1022
		fflush(stderr);
1023
	}
1024
 
1025
	return(0);
1026
}
1027
 
1028
 
1029
static point_list *lialg_compute_dominant_points(point_list *points) {
1030
	point_list *ipts;
1031
	region_list *regions;
1032
	point_list *dpts;
1033
 
1034
	/* Interpolate points. */
1035
	ipts = lialg_interpolate_points(points);
1036
	if (ipts == nil) return(nil);
1037
	if (lidebug) {
1038
				int j;
1039
				fprint(2, "After interpolation:  %d ipts\n", ipts->npts);
1040
				for (j = 0; j < ipts->npts; j++) {
1041
					fprint(2, "  (%P), %lud\n", ipts->pts[j].Point, ipts->pts[j].chaincode);
1042
				}
1043
				fflush(stderr);
1044
	}
1045
 
1046
	/* Compute regions. */
1047
	regions = lialg_compute_regions(ipts);
1048
/*    assert(regions != nil);*/
1049
 
1050
	/* Compute dominant points. */
1051
	dpts = lialg_compute_dompts(ipts, regions);
1052
	if (lidebug) {
1053
				int j;
1054
				fprint(2, "Dominant points:  ");
1055
				for (j = 0; j < dpts->npts; j++) {
1056
					fprint(2, "%P (%lud)  ", dpts->pts[j].Point, dpts->pts[j].chaincode);
1057
				}
1058
				fprint(2, "\n");
1059
				fflush(stderr);
1060
	}
1061
 
1062
	/* Delete region data structure. */
1063
	{
1064
				region_list *curr, *next;
1065
				for (curr = regions; curr != nil; ) {
1066
					next = curr->next;
1067
					free(curr);
1068
					curr = next;
1069
				}
1070
	}
1071
	delete_examples(ipts);
1072
	return(dpts);
1073
}
1074
 
1075
/* Input points are assumed to be integer-valued! */
1076
static point_list *lialg_interpolate_points(point_list *points) {
1077
	int i, j;
1078
	int maxpts;
1079
	point_list *newpts;
1080
 
1081
	/* Compute an upper-bound on the number of interpolated points. */
1082
	maxpts = 0;
1083
	for (i = 0; i < (points->npts - 1); i++) {
1084
				pen_point *pta = &(points->pts[i]);
1085
				pen_point *ptb = &(points->pts[i+1]);
1086
				maxpts += abs(pta->x - ptb->x) + abs(pta->y - ptb->y);
1087
	}
1088
 
1089
	/* Allocate an array of the requisite size. */
1090
	maxpts += points->npts;
1091
	/* newpts = (point_list *)safe_malloc(sizeof(point_list)); */
1092
	newpts = malloc(sizeof(point_list));
1093
	newpts->pts = mallocz(maxpts*sizeof(pen_point), 1);
1094
	if (newpts->pts == nil) {
1095
				free(newpts);
1096
				return(nil);
1097
	}
1098
	newpts->npts = maxpts;
1099
	newpts->next = nil;
1100
 
1101
	/* Interpolate each of the segments. */
1102
	j = 0;
1103
	for (i = 0; i < (points->npts - 1); i++) {
1104
				pen_point *startpt = &(points->pts[i]);
1105
				pen_point *endpt = &(points->pts[i+1]);
1106
 
1107
				lialg_bresline(startpt, endpt, newpts, &j);
1108
 
1109
				j--;	/* end point gets recorded as start point of next segment! */
1110
	}
1111
 
1112
	/* Add-in last point. */
1113
	newpts->pts[j++] = points->pts[points->npts - 1];
1114
	newpts->npts = j;
1115
 
1116
	/* Compute the chain code for P (the list of points). */
1117
	lialg_compute_unit_chain_code(newpts);
1118
 
1119
	return(newpts);
1120
}
1121
 
1122
 
1123
/* This implementation is due to Kenny Hoff. */
1124
static void lialg_bresline(pen_point *startpt, pen_point *endpt,
1125
							point_list *newpts, int *j) {
1126
	int Ax, Ay, Bx, By, dX, dY, Xincr, Yincr;
1127
 
1128
	Ax = startpt->x;
1129
	Ay = startpt->y;
1130
	Bx = endpt->x;
1131
	By = endpt->y;
1132
 
1133
	/* INITIALIZE THE COMPONENTS OF THE ALGORITHM THAT ARE NOT AFFECTED */
1134
	/* BY THE SLOPE OR DIRECTION OF THE	LINE */
1135
	dX = abs(Bx-Ax);	/* store the change in X and Y of the line endpoints */
1136
	dY = abs(By-Ay);
1137
 
1138
	/* DETERMINE "DIRECTIONS" TO INCREMENT X AND Y (REGARDLESS OF DECISION) */
1139
	if (Ax > Bx) { Xincr=-1; } else { Xincr=1; }    /* which direction in X? */
1140
	if (Ay > By) { Yincr=-1; } else { Yincr=1; }    /* which direction in Y? */
1141
 
1142
	/* DETERMINE INDEPENDENT VARIABLE (ONE THAT ALWAYS INCREMENTS BY 1 (OR -1) ) */
1143
	/* AND INITIATE APPROPRIATE LINE DRAWING ROUTINE (BASED ON FIRST OCTANT */
1144
	/* ALWAYS). THE X AND Y'S MAY BE FLIPPED IF Y IS THE INDEPENDENT VARIABLE. */
1145
	if (dX >= dY) {	    /* if X is the independent variable */
1146
				int dPr	= dY<<1;	    /* amount to increment decision if right is chosen (always) */
1147
				int dPru = dPr - (dX<<1);   /* amount to increment decision if up is chosen */
1148
				int P =	dPr - dX;	    /* decision variable start value */
1149
 
1150
				/* process each point in the line one at a time (just use dX) */
1151
				for (; dX>=0; dX--) {
1152
					newpts->pts[*j].x = Ax;
1153
					newpts->pts[*j].y = Ay;
1154
					(*j)++;
1155
 
1156
					if (P > 0) {	/* is the pixel	going right AND	up? */
1157
								Ax+=Xincr;	/* increment independent variable */
1158
								Ay+=Yincr;	/* increment dependent variable */
1159
								P+=dPru;	/* increment decision (for up) */
1160
					} else {		/* is the pixel just going right? */
1161
								Ax+=Xincr;	/* increment independent variable */
1162
								P+=dPr;		/* increment decision (for right) */
1163
					}
1164
				}
1165
	} else {		    /* if Y is the independent variable */
1166
				int dPr	= dX<<1;	    /* amount to increment decision if right is chosen (always) */
1167
				int dPru = dPr - (dY<<1);   /* amount to increment decision if up is chosen */
1168
				int P  = dPr - dY;	    /* decision variable start value */
1169
 
1170
				/* process each point in the line one at a time (just use dY) */
1171
				for (; dY>=0; dY--) {
1172
					newpts->pts[*j].x = Ax;
1173
					newpts->pts[*j].y = Ay;
1174
					(*j)++;
1175
 
1176
					if (P > 0) {	/* is the pixel going up AND right? */
1177
								Ax+=Xincr;	/* increment dependent variable */
1178
								Ay+=Yincr;	/* increment independent variable */
1179
								P+=dPru;	/* increment decision (for up) */
1180
					} else {		/* is the pixel just going up? */
1181
								Ay+=Yincr;	/* increment independent variable */
1182
								P+=dPr;		/* increment decision (for right) */
1183
					}
1184
				}
1185
	}
1186
}
1187
 
1188
static void lialg_compute_chain_code(point_list *pts) {
1189
	int i;
1190
 
1191
	for (i = 0; i < (pts->npts - 1); i++) {
1192
				pen_point *startpt = &(pts->pts[i]);
1193
				pen_point *endpt = &(pts->pts[i+1]);
1194
				int dx = endpt->x - startpt->x;
1195
				int dy = endpt->y - startpt->y;
1196
				int tmp = quadr(likeatan(dy, dx));
1197
				int dircode = (12 - tmp) % 8;
1198
 
1199
				startpt->chaincode = dircode;
1200
	}
1201
}
1202
 
1203
 
1204
static void lialg_compute_unit_chain_code(point_list *pts) {
1205
	int i;
1206
 
1207
	for (i = 0; i < (pts->npts - 1); i++) {
1208
				pen_point *startpt = &(pts->pts[i]);
1209
				pen_point *endpt = &(pts->pts[i+1]);
1210
				int dx = endpt->x - startpt->x;
1211
				int dy = endpt->y - startpt->y;
1212
				int dircode = lialg_dctbl[dx+1][dy+1];
1213
 
1214
				startpt->chaincode = dircode;
1215
	}
1216
}
1217
 
1218
 
1219
static region_list *lialg_compute_regions(point_list *pts) {
1220
		region_list *regions;
1221
		region_list *curr_reg;
1222
		int *R[2 + LP_FILTER_ITERS];
1223
		int *junk;
1224
		int *curr, *next;
1225
		int i, j;
1226
 
1227
		/* Initialize low-pass filter parameters if necessary. */
1228
		if (lialg_lpfconst == -1)
1229
				lialg_compute_lpf_parameters();
1230
 
1231
	/* Allocate a 2 x pts->npts array for use in computing the (filtered) Angle set, A_n. */
1232
	junk = malloc((2 + LP_FILTER_ITERS) * pts->npts*sizeof(int));
1233
	for (i = 0; i < (2 + LP_FILTER_ITERS); i++)
1234
		R[i] = junk + (i * pts->npts);
1235
	curr = R[0];
1236
 
1237
	/* Compute the Angle set, A, in the first element of array R. */
1238
	/* Values in R are in degrees, x 100. */
1239
	curr[0] = 18000;				/* a_0 */
1240
	for (i = 1; i < (pts->npts - 1); i++) {
1241
				int d_i = pts->pts[i].chaincode;
1242
				int d_iminusone = pts->pts[i-1].chaincode;
1243
				int a_i;
1244
 
1245
				if (d_iminusone < d_i)
1246
					d_iminusone += 8;
1247
 
1248
				a_i = (d_iminusone - d_i) % 8;
1249
 
1250
				/* convert to degrees, x 100 */
1251
				curr[i] = ((12 - a_i) % 8) * 45 * 100;
1252
	}
1253
	curr[pts->npts - 1]	= 18000;		/* a_L-1 */
1254
 
1255
	/* Perform a number of filtering iterations. */
1256
	next = R[1];
1257
	for (j = 0; j < LP_FILTER_ITERS; j++, curr = R[j], next = R[j+1]) {
1258
				for (i = 0; i < pts->npts; i++) {
1259
					int k;
1260
 
1261
					next[i] = 0;
1262
 
1263
					for (k = i - LP_FILTER_WIDTH; k <= i + LP_FILTER_WIDTH; k++) {
1264
						int oldval = (k < 0 || k >= pts->npts) ? 18000 : curr[k];
1265
						next[i]	+= oldval * lialg_lpfwts[k - (i	- LP_FILTER_WIDTH)];	/* overflow? */
1266
					}
1267
 
1268
					next[i] /= lialg_lpfconst;
1269
				}
1270
	}
1271
 
1272
	/* Do final thresholding around PI. */
1273
	/* curr and next are set-up correctly at end of previous loop! */
1274
	for (i = 0; i < pts->npts; i++)
1275
				next[i] = (abs(curr[i] - 18000) < LP_FILTER_THLD) ? 18000 : curr[i];
1276
	curr = next;
1277
 
1278
	/* Debugging. */
1279
	if (lidebug > 1) {
1280
				for (i = 0; i < pts->npts; i++) {
1281
					fprint(2, "%3d:  (%P)  %lud  ",
1282
								i, pts->pts[i].Point, pts->pts[i].chaincode);
1283
					for (j = 0; j < 2 + LP_FILTER_ITERS; j++)
1284
						fprint(2, "%d  ", R[j][i]);
1285
					fprint(2, "\n");
1286
				}
1287
	}
1288
 
1289
	/* Do the region segmentation. */
1290
	{
1291
				int start, end;
1292
				int currtype;
1293
 
1294
#define	RGN_TYPE(val) (((val)==18000)?RGN_PLAIN:((val)<18000?RGN_CONCAVE:RGN_CONVEX))
1295
 
1296
				start = 0;
1297
				currtype = RGN_TYPE(curr[0]);
1298
				regions = malloc(sizeof(region_list));
1299
				curr_reg = regions;
1300
				curr_reg->start = start;
1301
				curr_reg->end = 0;
1302
				curr_reg->type = currtype;
1303
				curr_reg->next = nil;
1304
				for (i = 1; i < pts->npts; i++) {
1305
					int nexttype = RGN_TYPE(curr[i]);
1306
 
1307
					if (nexttype != currtype) {
1308
								region_list *next_reg;
1309
 
1310
								end = i - 1;
1311
								curr_reg->end = end;
1312
								if (lidebug > 1)
1313
									fprint(2, "  (%d, %d), %d\n", start, end, currtype);
1314
 
1315
								start = i;
1316
								currtype = nexttype;
1317
								next_reg = malloc(sizeof(region_list));
1318
								next_reg->start = start;
1319
								next_reg->end = 0;
1320
								next_reg->type = nexttype;
1321
								next_reg->next = nil;
1322
 
1323
								curr_reg->next = next_reg;
1324
								curr_reg = next_reg;
1325
					}
1326
				}
1327
				end = i - 1;
1328
				curr_reg->end = end;
1329
				if (lidebug > 1)
1330
					fprint(2, "  (%d, %d), %d\n", start, end, currtype);
1331
 
1332
				/* Filter out convex/concave regions that are too short. */
1333
				for (curr_reg = regions; curr_reg; curr_reg = curr_reg->next)
1334
					if (curr_reg->type == RGN_PLAIN) {
1335
								region_list *next_reg;
1336
 
1337
								for (next_reg = curr_reg->next;
1338
									 next_reg != nil &&
1339
									   (next_reg->end - next_reg->start) < LP_FILTER_MIN;
1340
									 next_reg = curr_reg->next) {
1341
									/* next_reg must not be plain, and it must be followed by a plain */
1342
									/* assert(next_reg->type != RGN_PLAIN); */
1343
									/* assert(next_reg->next != nil && (next_reg->next)->type == RGN_PLAIN); */
1344
 
1345
									curr_reg->next = (next_reg->next)->next;
1346
									curr_reg->end = (next_reg->next)->end;
1347
 
1348
									free(next_reg->next);
1349
									free(next_reg);
1350
								}
1351
					}
1352
 
1353
				/* Add-in pseudo-extremes. */
1354
				{
1355
					region_list *tmp, *prev_reg;
1356
 
1357
					tmp = regions;
1358
					regions = nil;
1359
					prev_reg = nil;
1360
					for (curr_reg = tmp; curr_reg; curr_reg = curr_reg->next) {
1361
						if (curr_reg->type == RGN_PLAIN) {
1362
							int arclen = lialg_compute_pathlen_subset(pts,
1363
																		curr_reg->start,
1364
																		curr_reg->end);
1365
							int dx = pts->pts[curr_reg->end].x -
1366
							  pts->pts[curr_reg->start].x;
1367
							int dy = pts->pts[curr_reg->end].y -
1368
							  pts->pts[curr_reg->start].y;
1369
							int chordlen = isqrt(10000 * (dx * dx + dy * dy));
1370
							int atcr = (chordlen == 0) ? 0 : (100 * arclen + chordlen / 2) / chordlen;
1371
 
1372
							if (lidebug)
1373
								fprint(2, "%d, %d, %d\n", arclen, chordlen, atcr);
1374
 
1375
							/* Split region if necessary. */
1376
							if (arclen >= PE_AL_THLD && atcr >= PE_ATCR_THLD) {
1377
								int mid = curr_reg->start + (curr_reg->end - curr_reg->start) / 2;
1378
								int end = curr_reg->end;
1379
								region_list *saved_next = curr_reg->next;
1380
 
1381
								curr_reg->end = mid - 1;
1382
								if (prev_reg == nil)
1383
									regions = curr_reg;
1384
								else
1385
									prev_reg->next = curr_reg;
1386
								prev_reg = curr_reg;
1387
 
1388
								/* curr_reg = (region_list *)safe_malloc(sizeof(region_list));*/
1389
								curr_reg = malloc(sizeof(region_list));
1390
								curr_reg->start = mid;
1391
								curr_reg->end = mid;
1392
								curr_reg->type = RGN_PSEUDO;
1393
								curr_reg->next = nil;
1394
								prev_reg->next = curr_reg;
1395
								prev_reg = curr_reg;
1396
 
1397
								/* curr_reg = (region_list *)malloc(sizeof(region_list)); */
1398
								curr_reg = malloc(sizeof(region_list));
1399
								curr_reg->start = mid + 1;
1400
								curr_reg->end = end;
1401
								curr_reg->type = RGN_PLAIN;
1402
								curr_reg->next = nil;
1403
								prev_reg->next = curr_reg;
1404
								prev_reg = curr_reg;
1405
 
1406
								curr_reg->next = saved_next;
1407
								continue;
1408
							}
1409
						}
1410
 
1411
						if (prev_reg == nil)
1412
							regions = curr_reg;
1413
						else
1414
							prev_reg->next = curr_reg;
1415
						prev_reg = curr_reg;
1416
					}
1417
				}
1418
		}
1419
 
1420
	free(junk);
1421
	return(regions);
1422
}
1423
 
1424
 
1425
static point_list *lialg_compute_dompts(point_list *pts, region_list *regions) {
1426
	point_list *dpts;
1427
	int ndpts;
1428
	int *cas;
1429
	int nonplain;
1430
	region_list *r;
1431
		region_list *curr;
1432
		int dp;
1433
		int previx;
1434
		int currix;
1435
 
1436
	/* Compute contour angle set. */
1437
	cas = lialg_compute_contour_angle_set(pts, regions);
1438
 
1439
	/* Dominant points include:  start_pt, end_pt, extrema_of_non_plain_regions, midpts of the preceding. */
1440
	nonplain = 0;
1441
	for (r = regions; r != nil; r = r->next)
1442
				if (r->type != RGN_PLAIN)
1443
						nonplain++;
1444
	ndpts = 2 * (2 + nonplain) - 1;
1445
	/* dpts = (point_list *)safe_malloc(sizeof(point_list)); */
1446
	dpts = malloc(sizeof(point_list));
1447
	dpts->pts = mallocz(ndpts*sizeof(pen_point), 1);
1448
	if (dpts->pts == nil) {
1449
				free(dpts);
1450
				return(nil);
1451
	}
1452
	dpts->npts = ndpts;
1453
	dpts->next = nil;
1454
 
1455
	/* Pick out dominant points. */
1456
 
1457
		/* Record start point. */
1458
		dp = 0;
1459
		previx = 0;
1460
		dpts->pts[dp++] = pts->pts[previx];
1461
 
1462
		for (curr = regions; curr != nil; curr = curr->next)
1463
			if (curr->type != RGN_PLAIN) {
1464
						int max_v = 0;
1465
						int min_v = 0x7fffffff;	/* maxint */
1466
						int max_ix = -1;
1467
						int min_ix = -1;
1468
						int i;
1469
 
1470
						for (i = curr->start; i <= curr->end; i++) {
1471
							int v = cas[i];
1472
							if (v > max_v) { max_v = v; max_ix = i; }
1473
							if (v < min_v) { min_v = v; min_ix = i; }
1474
							if (lidebug > 1)
1475
								fprint(2, "  %d\n", v);
1476
						}
1477
 
1478
						currix = (curr->type == RGN_CONVEX ? max_ix : min_ix);
1479
 
1480
						/* Record midpoint. */
1481
						dpts->pts[dp++] = pts->pts[previx + (currix - previx) / 2];
1482
 
1483
						/* Record extreme point. */
1484
						dpts->pts[dp++] = pts->pts[currix];
1485
 
1486
						previx = currix;
1487
			}
1488
 
1489
		/* Record last mid-point and end point. */
1490
		currix = pts->npts - 1;
1491
		dpts->pts[dp++] = pts->pts[previx + (currix - previx) / 2];
1492
		dpts->pts[dp] = pts->pts[currix];
1493
 
1494
	/* Compute chain-code. */
1495
	lialg_compute_chain_code(dpts);
1496
 
1497
	free(cas);
1498
	return(dpts);
1499
}
1500
 
1501
 
1502
static int *lialg_compute_contour_angle_set(point_list *pts,
1503
											   region_list *regions) {
1504
		int *V;
1505
		region_list *curr_reg;
1506
		int i;
1507
 
1508
		V = malloc(pts->npts*sizeof(int));
1509
 
1510
		V[0] = 18000;
1511
		for (curr_reg = regions; curr_reg != nil; curr_reg = curr_reg->next) {
1512
				for (i = curr_reg->start; i <= curr_reg->end; i++) {
1513
					if (curr_reg->type == RGN_PLAIN) {
1514
								V[i] = 18000;
1515
					} else {
1516
								/* For now, simply choose the mid-point. */
1517
								int isMidPt = i == (curr_reg->start +
1518
													 (curr_reg->end - curr_reg->start) / 2);
1519
								V[i] = (curr_reg->type == RGN_CONVEX)
1520
								  ? (isMidPt ? 18000 : 0)
1521
								  : (isMidPt ? 0 : 18000);
1522
					}
1523
				}
1524
	}
1525
	V[pts->npts - 1] = 18000;
1526
 
1527
	return(V);
1528
}
1529
 
1530
 
1531
/*
1532
 *  First compute the similarity between the two strings.
1533
 *  If it's above a threshold, compute the distance between
1534
 *  the two and return it as the ``score.''
1535
 *  Otherwise, return the constant WORST_SCORE.
1536
 *
1537
 */
1538
static void lialg_score_stroke(point_list *input_dompts, point_list *curr_dompts, int *sim, int *dist) {
1539
	*sim = MIN_SIM;
1540
	*dist = MAX_DIST;
1541
 
1542
	*sim = lialg_compute_similarity(input_dompts, curr_dompts);
1543
	if (*sim < SIM_THLD) goto done;
1544
 
1545
	*dist = lialg_compute_distance(input_dompts, curr_dompts);
1546
 
1547
done:
1548
	if (lidebug)
1549
		fprint(2, "%d, %d\n", *sim, *dist);
1550
}
1551
 
1552
 
1553
static int lialg_compute_similarity(point_list *input_dompts, point_list *curr_dompts) {
1554
	int sim;
1555
	point_list *A, *B;
1556
	int N, M;
1557
	int **G;
1558
	int *junk;
1559
	int i, j;
1560
 
1561
	/* A is the	longer sequence, length	N. */
1562
	/* B is the shorter sequence, length M. */
1563
		if (input_dompts->npts >= curr_dompts->npts) {
1564
				A = input_dompts;
1565
				N = input_dompts->npts;
1566
				B = curr_dompts;
1567
				M = curr_dompts->npts;
1568
	} else {
1569
				A = curr_dompts;
1570
				N = curr_dompts->npts;
1571
				B = input_dompts;
1572
				M = input_dompts->npts;
1573
		}
1574
 
1575
		/* Allocate and initialize the Gain matrix, G. */
1576
		/* The size of G is M x (N + 1). */
1577
		/* Note that row 0 is unused. */
1578
		/* Similarities are x 10. */
1579
		G = malloc(M*sizeof(int *));
1580
		junk = malloc(M * (N + 1) * sizeof(int));
1581
		for (i = 0; i < M; i++)
1582
			G[i] = junk + (i * (N + 1));
1583
 
1584
		for (i = 1; i < M; i++) {
1585
			int bval = B->pts[i-1].chaincode;
1586
 
1587
			/* Source column. */
1588
			G[i][0] = 0;
1589
 
1590
			for (j = 1; j < N; j++) {
1591
				int aval = A->pts[j-1].chaincode;
1592
				int diff = abs(bval - aval);
1593
				if (diff > 4) diff = 8 - diff;
1594
 
1595
				G[i][j] = (diff == 0)
1596
				  ? 10
1597
				  : (diff == 1)
1598
				  ? 6
1599
				  : 0;
1600
			}
1601
 
1602
			/* Sink column. */
1603
			G[i][N] = 0;
1604
		}
1605
 
1606
	/* Do the DP algorithm. */
1607
	/* Proceed in column order, from highest column to the lowest. */
1608
	/* Within each column, proceed from the highest row to the lowest. */
1609
	/* Skip the highest column. */
1610
		for (j = N - 1; j >= 0; j--)
1611
			for (i = M - 1; i > 0; i--) {
1612
				int max = G[i][j + 1];
1613
 
1614
				if (i < (M - 1)) {
1615
					int tmp = G[i + 1][j + 1];
1616
					if (tmp > max) max = tmp;
1617
				}
1618
 
1619
				G[i][j] += max;
1620
		}
1621
 
1622
		sim = (10 * G[1][0] + (N - 1) / 2) / (N - 1);
1623
 
1624
		if (G != nil)
1625
				free(G);
1626
		if (junk != nil)
1627
				free(junk);
1628
		return(sim);
1629
}
1630
 
1631
 
1632
static int lialg_compute_distance(point_list *input_dompts,
1633
								   point_list *curr_dompts) {
1634
		int dist;
1635
		point_list *A, *B;
1636
		int N, M;
1637
		int **C;
1638
		int *junk;
1639
		int *BE;
1640
		int *TE;
1641
		int i, j;
1642
 
1643
		/* A is the	longer sequence, length	N. */
1644
		/* B is the shorter sequence, length M. */
1645
		if (input_dompts->npts >= curr_dompts->npts) {
1646
				A = input_dompts;
1647
				N = input_dompts->npts;
1648
				B = curr_dompts;
1649
				M = curr_dompts->npts;
1650
		}
1651
		else {
1652
				A = curr_dompts;
1653
				N = curr_dompts->npts;
1654
				B = input_dompts;
1655
				M = input_dompts->npts;
1656
		}
1657
 
1658
		/* Construct the helper vectors, BE and TE, which say for each column */
1659
		/* what are the ``bottom'' and ``top'' rows of interest. */
1660
		BE = malloc((N + 1)*sizeof(int));
1661
		TE = malloc((N + 1)*sizeof(int));
1662
 
1663
		for (j = 1; j <= N; j++) {
1664
				int bot, top;
1665
 
1666
				bot = j + (M - DP_BAND);
1667
				if (bot > M) bot = M;
1668
				BE[j] = bot;
1669
 
1670
				top = j - (N - DP_BAND);
1671
				if (top < 1) top = 1;
1672
				TE[j] = top;
1673
		}
1674
 
1675
		/* Allocate and initialize the Cost matrix, C. */
1676
		/* The size of C is (M + 1) x (N + 1). */
1677
		/* Note that row and column 0 are unused. */
1678
		/* Costs are x 100. */
1679
		/*	C = (int **)safe_malloc((M + 1) * sizeof(int *)); */
1680
		C = malloc((M + 1)*sizeof( int *));
1681
		junk = malloc((M + 1) * (N + 1)*sizeof(int));
1682
		for (i = 0; i <= M; i++)
1683
				C[i] = junk + (i * (N + 1));
1684
 
1685
		for (i = 1; i <= M; i++) {
1686
				int bx = B->pts[i-1].x;
1687
				int by = B->pts[i-1].y;
1688
 
1689
				for (j = 1; j <= N; j++) {
1690
						int ax = A->pts[j-1].x;
1691
						int ay = A->pts[j-1].y;
1692
						int dx = bx - ax;
1693
						int dy = by - ay;
1694
						int dist = isqrt(10000 * (dx * dx + dy * dy));
1695
 
1696
						C[i][j] = dist;
1697
				}
1698
		}
1699
 
1700
		/* Do the DP algorithm. */
1701
		/* Proceed in column order, from highest column to the lowest. */
1702
		/* Within each column, proceed from the highest row to the lowest. */
1703
		for (j = N; j > 0; j--)
1704
				for (i = M; i > 0; i--) {
1705
						int min = MAX_DIST;
1706
 
1707
						if (i > BE[j] || i < TE[j] || (j == N && i == M))
1708
								continue;
1709
 
1710
						if (j < N) {
1711
								if (i >= TE[j+1]) {
1712
										int tmp = C[i][j+1];
1713
										if (tmp < min)
1714
												min = tmp;
1715
								}
1716
 
1717
								if (i < M) {
1718
										int tmp = C[i+1][j+1];
1719
										if (tmp < min)
1720
												min = tmp;
1721
								}
1722
						}
1723
 
1724
						if (i < BE[j]) {
1725
								int tmp = C[i+1][j];
1726
								if (tmp < min) min = tmp;
1727
						}
1728
 
1729
						C[i][j] += min;
1730
				}
1731
 
1732
		dist = (C[1][1] + N / 2) / N;
1733
 
1734
		if (C != nil) free(C);
1735
		if (junk != nil) free(junk);
1736
		if (BE != nil) free(BE);
1737
		if (TE != nil) free(TE);
1738
		return(dist);
1739
}
1740
 
1741
 
1742
/*************************************************************
1743
 
1744
  Digest-processing routines
1745
 
1746
 *************************************************************/
1747
 
1748
static int lialg_read_classifier_digest(rClassifier *rec) {
1749
	int nclasses;
1750
	FILE *fp;
1751
 
1752
	/* Try to open the corresponding digest file. */
1753
	{
1754
				char *clx_path;
1755
				char *dot;
1756
 
1757
				/* Get a copy of the filename, with some room on the end. */
1758
				/*	clx_path = safe_malloc(strlen(rec->file_name) + 5); */
1759
				clx_path = malloc((strlen(rec->file_name) + 5) *sizeof(char));
1760
				strcpy(clx_path, rec->file_name);
1761
 
1762
				/* Truncate the path after the last dot. */
1763
				dot = strrchr(clx_path, '.');
1764
				if (dot == nil) { free(clx_path); return(-1); }
1765
				*(dot + 1) = 0;
1766
 
1767
				/* Append the classifier-digest extension. */
1768
				strcat(clx_path, "clx");
1769
 
1770
				fp = fopen(clx_path, "r");
1771
				if (fp == nil) {
1772
						free(clx_path);
1773
						return(-1);
1774
				}
1775
 
1776
				free(clx_path);
1777
	}
1778
 
1779
	/* Read-in the name and dominant points for each class. */
1780
	for (nclasses = 0; !feof(fp); nclasses++) {
1781
		point_list *dpts = nil;
1782
		char class[BUFSIZ];
1783
		int npts;
1784
		int j;
1785
 
1786
		if (fscanf(fp, "%s %d", class, &npts) != 2) {
1787
			if (feof(fp)) break;
1788
 
1789
			goto failed;
1790
		}
1791
		rec->cnames[nclasses] = strdup(class);
1792
 
1793
		/* Allocate a dominant-points list. */
1794
		/* dpts = (point_list *)safe_malloc(sizeof(point_list)); */
1795
		dpts = malloc(sizeof(point_list));
1796
		dpts->pts = mallocz(npts*sizeof(pen_point), 1);
1797
		if (dpts->pts == nil) goto failed;
1798
		dpts->npts = npts;
1799
		dpts->next = nil;
1800
 
1801
		/* Read in each point. */
1802
		for (j = 0; j < npts; j++) {
1803
			int x, y;
1804
 
1805
			if (fscanf(fp, "%d %d", &x, &y) != 2) goto failed;
1806
			dpts->pts[j].x = x;
1807
			dpts->pts[j].y = y;
1808
		}
1809
 
1810
		/* Compute the chain-code. */
1811
		lialg_compute_chain_code(dpts);
1812
 
1813
		/* Store the list in the rec data structure. */
1814
		rec->dompts[nclasses] = dpts;
1815
 
1816
		continue;
1817
 
1818
failed:
1819
		fprint(2, "read_classifier_digest failed...\n");
1820
		for (; nclasses >= 0; nclasses--) {
1821
			if (rec->cnames[nclasses] != nil) {
1822
				free(rec->cnames[nclasses]);
1823
				rec->cnames[nclasses] = nil;
1824
			}
1825
			if (rec->dompts[nclasses] != nil) {
1826
				delete_examples(rec->dompts[nclasses]);
1827
				rec->dompts[nclasses] = nil;
1828
			}
1829
		}
1830
		if (dpts != nil)
1831
			delete_examples(dpts);
1832
		fclose(fp);
1833
		return(-1);
1834
	}
1835
 
1836
	fclose(fp);
1837
	return(0);
1838
}
1839
 
1840
 
1841
/*************************************************************
1842
 
1843
  Canonicalization routines
1844
 
1845
 *************************************************************/
1846
 
1847
static int lialg_canonicalize_examples(rClassifier *rec) {
1848
	int i;
1849
	int nclasses;
1850
 
1851
	if (lidebug) {
1852
		fprint(2, "lialg_canonicalize_examples working on %s\n",
1853
				rec->file_name);
1854
	}
1855
	/* Initialize canonical-example arrays. */
1856
	for (i = 0; i < MAXSCLASSES; i++) {
1857
		rec->canonex[i] = nil;
1858
	}
1859
 
1860
	/* Figure out number of classes. */
1861
	for (nclasses = 0;
1862
		  nclasses < MAXSCLASSES && rec->cnames[nclasses] != nil;
1863
		  nclasses++)
1864
		;
1865
 
1866
	/* Canonicalize the examples for each class. */
1867
	for (i = 0; i < nclasses; i++) {
1868
		int j, k;
1869
		int nex;
1870
		point_list *pts, *tmp, *avg;
1871
		int maxxrange, maxyrange;
1872
		int minx, miny, maxx, maxy;
1873
		int avgxrange, avgyrange, avgxoff, avgyoff, avgscale;
1874
 
1875
 
1876
		if (lidebug) {
1877
			fprint(2, "lialg_canonicalize_examples working on class %s\n",
1878
					rec->cnames[i]);
1879
		}
1880
		/* Make a copy of the examples. */
1881
		pts = nil;
1882
		tmp = rec->ex[i];
1883
		for (nex = 0; tmp != nil; nex++, tmp = tmp->next) {
1884
			if ((pts = add_example(pts, tmp->npts, tmp->pts)) == nil) {
1885
				delete_examples(pts);
1886
				return(-1);
1887
			}
1888
		}
1889
 
1890
		/* Canonicalize each example, and derive the max x and y ranges. */
1891
		maxxrange = 0;
1892
		maxyrange = 0;
1893
		for (j = 0, tmp = pts; j < nex; j++, tmp = tmp->next) {
1894
			if (lialg_canonicalize_example_stroke(tmp) != 0) {
1895
  	        if (lidebug) {
1896
					fprint(2, "lialg_canonicalize_example_stroke returned error\n");
1897
				}
1898
				return(-1);
1899
			}
1900
 
1901
			if (tmp->xrange > maxxrange) maxxrange = tmp->xrange;
1902
			if (tmp->yrange > maxyrange) maxyrange = tmp->yrange;
1903
		}
1904
 
1905
		/* Normalize max ranges. */
1906
		if (((100 * maxxrange + CANONICAL_X / 2) / CANONICAL_X) >
1907
			((100 * maxyrange + CANONICAL_Y / 2) / CANONICAL_Y)) {
1908
			maxyrange = (maxyrange * CANONICAL_X + maxxrange / 2) / maxxrange;
1909
			maxxrange = CANONICAL_X;
1910
		}
1911
		else {
1912
			maxxrange = (maxxrange * CANONICAL_Y + maxyrange / 2) / maxyrange;
1913
			maxyrange = CANONICAL_Y;
1914
		}
1915
 
1916
		/* Re-scale each example to max ranges. */
1917
		for (j = 0, tmp = pts; j < nex; j++, tmp = tmp->next) {
1918
			int scalex = (tmp->xrange == 0) ? 100 : (100 * maxxrange + tmp->xrange / 2) / tmp->xrange;
1919
			int scaley = (tmp->yrange == 0) ? 100 : (100 * maxyrange + tmp->yrange / 2) / tmp->yrange;
1920
			if (lialg_translate_points(tmp, 0, 0, scalex, scaley) != 0) {
1921
				delete_examples(pts);
1922
				return(-1);
1923
			}
1924
		}
1925
 
1926
		/* Average the examples; leave average in first example. */
1927
		avg = pts;				/* careful aliasing!! */
1928
		for (k = 0; k < NCANONICAL; k++) {
1929
			int xsum = 0;
1930
			int ysum = 0;
1931
 
1932
			for (j = 0, tmp = pts; j < nex; j++, tmp = tmp->next) {
1933
						xsum += tmp->pts[k].x;
1934
						ysum += tmp->pts[k].y;
1935
			}
1936
 
1937
			avg->pts[k].x = (xsum + j / 2) / j;
1938
			avg->pts[k].y = (ysum + j / 2) / j;
1939
		}
1940
 
1941
		/* Compute BB of averaged stroke and re-scale. */
1942
		lialg_get_bounding_box(avg, &minx, &miny, &maxx, &maxy);
1943
		avgxrange = maxx - minx;
1944
		avgyrange = maxy - miny;
1945
		avgscale = (((100 * avgxrange + CANONICAL_X / 2) / CANONICAL_X) >
1946
					((100 * avgyrange + CANONICAL_Y / 2) / CANONICAL_Y))
1947
		  ? (100 * CANONICAL_X + avgxrange / 2) / avgxrange
1948
		  : (100 * CANONICAL_Y + avgyrange / 2) / avgyrange;
1949
		if (lialg_translate_points(avg, minx, miny, avgscale, avgscale) != 0) {
1950
			delete_examples(pts);
1951
			return(-1);
1952
		}
1953
 
1954
		/* Re-compute the x and y ranges and center the stroke. */
1955
		lialg_get_bounding_box(avg, &minx, &miny, &maxx, &maxy);
1956
		avgxrange = maxx - minx;
1957
		avgyrange = maxy - miny;
1958
		avgxoff = -((CANONICAL_X - avgxrange + 1) / 2);
1959
		avgyoff = -((CANONICAL_Y - avgyrange + 1) / 2);
1960
		if (lialg_translate_points(avg, avgxoff, avgyoff, 100, 100) != 0) {
1961
			delete_examples(pts);
1962
			return(-1);
1963
		}
1964
 
1965
		/* Create a point list to serve as the ``canonical representation. */
1966
		if ((rec->canonex[i] = add_example(nil, avg->npts, avg->pts)) == nil) {
1967
			delete_examples(pts);
1968
			return(-1);
1969
		}
1970
		(rec->canonex[i])->xrange = maxx - minx;
1971
		(rec->canonex[i])->yrange = maxy - miny;
1972
 
1973
		if (lidebug) {
1974
			fprint(2, "%s, avgpts = %d\n", rec->cnames[i], avg->npts);
1975
			for (j = 0; j < avg->npts; j++) {
1976
						fprint(2, "  (%P)\n", avg->pts[j].Point);
1977
			}
1978
		}
1979
 
1980
		/* Compute dominant points of canonical representation. */
1981
		rec->dompts[i] = lialg_compute_dominant_points(avg);
1982
 
1983
		/* Clean up. */
1984
		delete_examples(pts);
1985
	}
1986
 
1987
	/* Sanity check. */
1988
	for (i = 0; i < nclasses; i++) {
1989
		char *best_name = lialg_recognize_stroke(rec, rec->canonex[i]);
1990
 
1991
		if (best_name != rec->cnames[i])
1992
			fprint(2, "%s, best = %s\n", rec->cnames[i], best_name);
1993
	}
1994
 
1995
	return(0);
1996
}
1997
 
1998
 
1999
static int lialg_canonicalize_example_stroke(point_list *points) {
2000
	int minx, miny, maxx, maxy, xrange, yrange, scale;
2001
 
2002
	/* Filter out points that are too close. */
2003
	if (lialg_filter_points(points) != 0) return(-1);
2004
 
2005
	/* Must be at least two points! */
2006
	if (points->npts < 2) {
2007
		if (lidebug) {
2008
			fprint(2, "lialg_canonicalize_example_stroke: npts=%d\n",
2009
					points->npts);
2010
		}
2011
		return(-1);
2012
	}
2013
 
2014
	/* Scale up to avoid conversion errors. */
2015
	lialg_get_bounding_box(points, &minx, &miny, &maxx, &maxy);
2016
	xrange = maxx - minx;
2017
	yrange = maxy - miny;
2018
	scale = (((100 * xrange + CANONICAL_X / 2) / CANONICAL_X) >
2019
			 ((100 * yrange + CANONICAL_Y / 2) / CANONICAL_Y))
2020
	  ? (100 * CANONICAL_X + xrange / 2) / xrange
2021
	  : (100 * CANONICAL_Y + yrange / 2) / yrange;
2022
	if (lialg_translate_points(points, minx, miny, scale, scale) != 0) {
2023
		if (lidebug) {
2024
			fprint(2, "lialg_translate_points (minx=%d,miny=%d,scale=%d) returned error\n", minx, miny, scale);
2025
		}
2026
		return(-1);
2027
	}
2028
 
2029
	/* Compute an equivalent stroke with equi-distant points. */
2030
	if (lialg_compute_equipoints(points) != 0) return(-1);
2031
 
2032
	/* Re-translate the points to the origin. */
2033
	lialg_get_bounding_box(points, &minx, &miny, &maxx, &maxy);
2034
	if (lialg_translate_points(points, minx, miny, 100, 100) != 0) {
2035
		if (lidebug) {
2036
			fprint(2, "lialg_translate_points (minx=%d,miny=%d) returned error\n", minx, miny);
2037
		}
2038
		return(-1);
2039
	}
2040
 
2041
	/* Store the x and y ranges in the point list. */
2042
	xrange = maxx - minx;
2043
	yrange = maxy - miny;
2044
	points->xrange = xrange;
2045
	points->yrange = yrange;
2046
 
2047
	if (lidebug) {
2048
		int i;
2049
		fprint(2, "Canonicalized:   %d, %d, %d, %d\n", minx, miny, maxx, maxy);
2050
		for (i = 0; i < points->npts; i++)
2051
			fprint(2, "      (%P)\n", points->pts[i].Point);
2052
		fflush(stderr);
2053
	}
2054
 
2055
	return(0);
2056
}
2057
 
2058
 
2059
static int lialg_compute_equipoints(point_list *points) {
2060
	pen_point *equipoints = mallocz(NCANONICAL*sizeof(pen_point), 1);
2061
	int nequipoints = 0;
2062
	int pathlen = lialg_compute_pathlen(points);
2063
	int equidist = (pathlen + (NCANONICAL - 1) / 2) / (NCANONICAL - 1);
2064
	int i;
2065
	int dist_since_last_eqpt;
2066
	int remaining_seglen;
2067
	int dist_to_next_eqpt;
2068
 
2069
	if (equipoints == nil) {
2070
		fprint(2, "can't allocate memory in lialg_compute_equipoints");
2071
		return(-1);
2072
	}
2073
 
2074
	if (lidebug) {
2075
		fprint(2, "compute_equipoints:  npts = %d, pathlen = %d, equidist = %d\n",
2076
				points->npts, pathlen, equidist);
2077
		fflush(stderr);
2078
	}
2079
 
2080
	/* First original point is an equipoint. */
2081
	equipoints[0] = points->pts[0];
2082
	nequipoints++;
2083
	dist_since_last_eqpt = 0;
2084
 
2085
	for (i = 1; i < points->npts; i++) {
2086
		int dx1 = points->pts[i].x - points->pts[i-1].x;
2087
		int dy1 = points->pts[i].y - points->pts[i-1].y;
2088
		int endx = 100 * points->pts[i-1].x;
2089
		int endy = 100 * points->pts[i-1].y;
2090
		remaining_seglen = isqrt(10000 * (dx1 * dx1 + dy1 * dy1));
2091
		dist_to_next_eqpt = equidist - dist_since_last_eqpt;
2092
 
2093
		while (remaining_seglen >= dist_to_next_eqpt) {
2094
			if (dx1 == 0) {
2095
				/* x-coordinate stays the same */
2096
				if (dy1 >= 0)
2097
					endy += dist_to_next_eqpt;
2098
				else
2099
					endy -= dist_to_next_eqpt;
2100
			}
2101
			else {
2102
				int slope = (100 * dy1 + dx1 / 2) / dx1;
2103
				int tmp = isqrt(10000 + slope * slope);
2104
				int dx = (100 * dist_to_next_eqpt + tmp / 2) / tmp;
2105
				int dy = (slope * dx + 50) / 100;
2106
 
2107
				if (dy < 0) dy = -dy;
2108
				if (dx1 >= 0)
2109
					endx += dx;
2110
				else
2111
					endx -= dx;
2112
				if (dy1 >= 0)
2113
					endy += dy;
2114
				else
2115
					endy -= dy;
2116
			}
2117
 
2118
			equipoints[nequipoints].x = (endx + 50) / 100;
2119
			equipoints[nequipoints].y = (endy + 50) / 100;
2120
			nequipoints++;
2121
/*	    assert(nequipoints <= NCANONICAL);*/
2122
			dist_since_last_eqpt = 0;
2123
			remaining_seglen -= dist_to_next_eqpt;
2124
			dist_to_next_eqpt = equidist;
2125
		}
2126
 
2127
		dist_since_last_eqpt += remaining_seglen;
2128
	}
2129
 
2130
	/* Take care of last equipoint. */
2131
	if (nequipoints == NCANONICAL) {
2132
		/* Good. */
2133
	} else if (nequipoints == (NCANONICAL - 1)) {
2134
		/* Make last original point the last equipoint. */
2135
		equipoints[nequipoints] = points->pts[points->npts - 1];
2136
	} else {
2137
	  if (lidebug) {
2138
		fprint(2,"lialg_compute_equipoints: nequipoints = %d\n", 
2139
				nequipoints);
2140
	  }
2141
/*	assert(false);*/
2142
		return(-1);
2143
	}
2144
 
2145
	points->npts = NCANONICAL;
2146
	delete_pen_point_array(points->pts);
2147
	points->pts = equipoints;
2148
	return(0);
2149
}
2150
 
2151
 
2152
/*************************************************************
2153
 
2154
  Utility routines
2155
 
2156
 *************************************************************/
2157
 
2158
/* Result is x 100. */
2159
static int lialg_compute_pathlen(point_list *points) {
2160
	return(lialg_compute_pathlen_subset(points, 0, points->npts - 1));
2161
}
2162
 
2163
 
2164
/* Result is x 100. */
2165
static int lialg_compute_pathlen_subset(point_list *points,
2166
										   int start, int end) {
2167
	int pathlen;
2168
	int i;
2169
 
2170
	pathlen = 0;
2171
	for (i = start + 1; i <= end; i++) {
2172
		int dx = points->pts[i].x - points->pts[i-1].x;
2173
		int dy = points->pts[i].y - points->pts[i-1].y;
2174
		int dist = isqrt(10000 * (dx * dx + dy * dy));
2175
		pathlen += dist;
2176
	}
2177
 
2178
	return(pathlen);
2179
}
2180
 
2181
 
2182
/* Note that this does NOT update points->xrange and points->yrange! */
2183
static int lialg_filter_points(point_list *points) {
2184
	int filtered_npts;
2185
	pen_point *filtered_pts = mallocz(points->npts*sizeof(pen_point), 1);
2186
	int i;
2187
 
2188
	if (filtered_pts == nil) {
2189
		fprint(2, "can't allocate memory in lialg_filter_points");
2190
		return(-1);
2191
	}
2192
 
2193
	filtered_pts[0] = points->pts[0];
2194
	filtered_npts = 1;
2195
	for (i = 1; i < points->npts; i++) {
2196
		int j = filtered_npts - 1;
2197
		int dx = points->pts[i].x - filtered_pts[j].x;
2198
		int dy = points->pts[i].y - filtered_pts[j].y;
2199
		int magsq = dx * dx + dy * dy;
2200
 
2201
		if (magsq >= DIST_SQ_THRESHOLD) {
2202
			filtered_pts[filtered_npts] = points->pts[i];
2203
			filtered_npts++;
2204
		}
2205
	}
2206
 
2207
	points->npts = filtered_npts;
2208
	delete_pen_point_array(points->pts);
2209
	points->pts = filtered_pts;
2210
	return(0);
2211
}
2212
 
2213
 
2214
/* scalex and scaley are x 100. */
2215
/* Note that this does NOT update points->xrange and points->yrange! */
2216
static int lialg_translate_points(point_list *points,
2217
								   int minx, int miny,
2218
								   int scalex, int scaley) {
2219
	int i;
2220
 
2221
	for (i = 0; i < points->npts; i++) {
2222
				points->pts[i].x = ((points->pts[i].x - minx) * scalex + 50) / 100;
2223
				points->pts[i].y = ((points->pts[i].y - miny) * scaley + 50) / 100;
2224
	}
2225
 
2226
	return(0);
2227
}
2228
 
2229
 
2230
static void lialg_get_bounding_box(point_list *points,
2231
									int *pminx, int *pminy,
2232
									int *pmaxx, int *pmaxy) {
2233
	int minx, miny, maxx, maxy;
2234
	int i;
2235
 
2236
	minx = maxx = points->pts[0].x;
2237
	miny = maxy = points->pts[0].y;
2238
	for (i = 1; i < points->npts; i++) {
2239
				pen_point *pt = &(points->pts[i]);
2240
				if (pt->x < minx) minx = pt->x;
2241
				else if (pt->x > maxx) maxx = pt->x;
2242
				if (pt->y < miny) miny = pt->y;
2243
				else if (pt->y > maxy) maxy = pt->y;
2244
	}
2245
 
2246
	*pminx = minx;
2247
	*pminy = miny;
2248
	*pmaxx = maxx;
2249
	*pmaxy = maxy;
2250
}
2251
 
2252
 
2253
int wtvals[] = {100, 104, 117, 143, 189, 271, 422};
2254
 
2255
static void lialg_compute_lpf_parameters(void) {
2256
	int i;
2257
 
2258
		for (i = LP_FILTER_WIDTH; i >= 0; i--) {
2259
//		double x = 0.04 * (i * i);
2260
//		double tmp = 100.0 * exp(x);
2261
//		int wt = floor((double)tmp);
2262
				int wt = wtvals[i];
2263
				lialg_lpfwts[LP_FILTER_WIDTH - i] = wt;
2264
				lialg_lpfwts[LP_FILTER_WIDTH + i] = wt;
2265
		}
2266
		lialg_lpfconst = 0;
2267
		for (i = 0; i < (2 * LP_FILTER_WIDTH + 1); i++) {
2268
				lialg_lpfconst += lialg_lpfwts[i];
2269
		}
2270
}
2271
 
2272
 
2273
/* Code from Joseph Hall (jnhall@sat.mot.com). */
2274
static int isqrt(int n) {
2275
		register int i;
2276
		register long k0, k1, nn;
2277
 
2278
		for (nn = i = n, k0 = 2; i > 0; i >>= 2, k0 <<= 1)
2279
				;
2280
		nn <<= 2;
2281
	for (;;) {
2282
				k1 = (nn / k0 + k0) >> 1;
2283
				if (((k0 ^ k1) & ~1) == 0)
2284
				break;
2285
				k0 = k1;
2286
		}
2287
		return (int) ((k1 + 1) >> 1);
2288
}
2289
 
2290
 
2291
/* Helper routines from Mark Hayter. */
2292
static int likeatan(int tantop, int tanbot) { 
2293
		int t;
2294
		/* Use tan(theta)=top/bot --> order for t */
2295
		/* t in range 0..0x40000 */
2296
 
2297
		if ((tantop == 0) && (tanbot == 0)) 
2298
				t = 0;
2299
	else
2300
		{
2301
				t = (tantop << 16) / (abs(tantop) + abs(tanbot));
2302
				if (tanbot < 0) 
2303
						t = 0x20000 - t;
2304
				else 
2305
						if (tantop < 0) t = 0x40000 + t;
2306
		}
2307
		return t;
2308
}
2309
 
2310
 
2311
static int quadr(int t) {
2312
	return (8 - (((t + 0x4000) >> 15) & 7)) & 7;
2313
}