summaryrefslogtreecommitdiff
path: root/inputmethods/handwriting/qimpenstroke.cpp
Unidiff
Diffstat (limited to 'inputmethods/handwriting/qimpenstroke.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r--inputmethods/handwriting/qimpenstroke.cpp646
1 files changed, 646 insertions, 0 deletions
diff --git a/inputmethods/handwriting/qimpenstroke.cpp b/inputmethods/handwriting/qimpenstroke.cpp
new file mode 100644
index 0000000..3567d6d
--- a/dev/null
+++ b/inputmethods/handwriting/qimpenstroke.cpp
@@ -0,0 +1,646 @@
1/**********************************************************************
2** Copyright (C) 2000 Trolltech AS. All rights reserved.
3**
4** This file is part of Qtopia Environment.
5**
6** This file may be distributed and/or modified under the terms of the
7** GNU General Public License version 2 as published by the Free Software
8** Foundation and appearing in the file LICENSE.GPL included in the
9** packaging of this file.
10**
11** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
12** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
13**
14** See http://www.trolltech.com/gpl/ for GPL licensing information.
15**
16** Contact info@trolltech.com if any conditions of this licensing are
17** not clear to you.
18**
19**********************************************************************/
20
21#include <qfile.h>
22#include <qtl.h>
23#include <math.h>
24#include <limits.h>
25#include <qdatastream.h>
26#include "qimpenstroke.h"
27
28#define QIMPEN_CORRELATION_POINTS 25
29//#define DEBUG_QIMPEN
30
31/*!
32 \class QIMPenStroke qimpenstroke.h
33
34 Handles a single stroke. Can calculate closeness of match to
35 another stroke.
36*/
37
38QIMPenStroke::QIMPenStroke()
39{
40}
41
42QIMPenStroke::QIMPenStroke( const QIMPenStroke &st )
43{
44 startPoint = st.startPoint;
45 lastPoint = st.lastPoint;
46 links = st.links.copy();
47}
48
49QIMPenStroke &QIMPenStroke::operator=( const QIMPenStroke &s )
50{
51 clear();
52 //qDebug( "copy strokes %d", s.links.count() );
53 startPoint = s.startPoint;
54 lastPoint = s.lastPoint;
55 links = s.links.copy();
56
57 return *this;
58}
59
60void QIMPenStroke::clear()
61{
62 startPoint = QPoint(0,0);
63 lastPoint = QPoint( 0, 0 );
64 links.resize( 0 );
65 tsig.resize( 0 );
66 dsig.resize( 0 );
67 asig.resize( 0 );
68}
69
70/*!
71 Begin inputting a new stroke.
72*/
73void QIMPenStroke::beginInput( QPoint p )
74{
75 clear();
76 startPoint = p;
77 bounding = QRect();
78 internalAddPoint( p );
79}
80
81/*!
82 Add a point to the stroke's shape.
83 Returns TRUE if the point was successfully added.
84*/
85bool QIMPenStroke::addPoint( QPoint p )
86{
87 if ( links.count() > 500 ) // sanity check (that the user is sane).
88 return FALSE;
89
90 int dx = p.x() - lastPoint.x();
91 int dy = p.y() - lastPoint.y();
92 if ( QABS( dx ) > 1 || QABS( dy ) > 1 ) {
93 // The point is not adjacent to the previous point, so we fill
94 // in with a straight line. Some kind of non-linear
95 // interpolation might be better.
96 int x = lastPoint.x();
97 int y = lastPoint.y();
98 int ix = 1;
99 int iy = 1;
100 if ( dx < 0 ) {
101 ix = -1;
102 dx = -dx;
103 }
104 if ( dy < 0 ) {
105 iy = -1;
106 dy = -dy;
107 }
108 int d = 0;
109 if ( dx < dy ) {
110 d = dx;
111 do {
112 y += iy;
113 d += dx;
114 if ( d > dy ) {
115 x += ix;
116 d -= dy;
117 }
118 internalAddPoint( QPoint( x, y ) );
119 } while ( y != p.y() );
120 } else {
121 d = dy;
122 do {
123 x += ix;
124 d += dy;
125 if ( d > dx ) {
126 y += iy;
127 d -= dx;
128 }
129 internalAddPoint( QPoint( x, y ) );
130 } while ( x != p.x() );
131 }
132 } else {
133 internalAddPoint( p );
134 }
135
136 return TRUE;
137}
138
139/*!
140 Finish inputting a stroke.
141*/
142void QIMPenStroke::endInput()
143{
144 if ( links.count() < 3 ) {
145 QIMPenGlyphLink gl;
146 links.resize(1);
147 gl.dx = 1;
148 gl.dy = 0;
149 links[0] = gl;
150 }
151
152 //qDebug("Points: %d", links.count() );
153}
154
155/*!
156 Return an indicator of the closeness of this stroke to \a pen.
157 Lower value is better.
158*/
159unsigned int QIMPenStroke::match( QIMPenStroke *pen )
160{
161 double lratio;
162
163 if ( links.count() > pen->links.count() )
164 lratio = (links.count()+2) / (pen->links.count()+2);
165 else
166 lratio = (pen->links.count()+2) / (links.count()+2);
167
168 lratio -= 1.0;
169
170 if ( lratio > 2.0 ) {
171#ifdef DEBUG_QIMPEN
172 qDebug( "stroke length too different" );
173#endif
174 return 400000;
175 }
176
177 createSignatures();
178 pen->createSignatures();
179
180 // Starting point offset
181 int vdiff = QABS(startPoint.y() - pen->startPoint.y());
182
183 // Insanely offset?
184 if ( vdiff > 18 ) {
185 return 400000;
186 }
187
188 vdiff -= 4;
189 if ( vdiff < 0 )
190 vdiff = 0;
191
192 // Ending point offset
193 int evdiff = QABS(lastPoint.y() - pen->lastPoint.y());
194 // Insanely offset?
195 if ( evdiff > 20 ) {
196 return 400000;
197 }
198
199 evdiff -= 5;
200 if ( evdiff < 0 )
201 evdiff = 0;
202
203 // do a correlation with the three available signatures.
204 int err1 = INT_MAX;
205 int err2 = INT_MAX;
206 int err3 = INT_MAX;
207
208 // base has extra points at the start and end to enable
209 // correlation of a sliding window with the pen supplied.
210 QArray<int> base = createBase( tsig, 2 );
211 for ( int i = 0; i < 4; i++ ) {
212 int e = calcError( base, pen->tsig, i, TRUE );
213 if ( e < err1 )
214 err1 = e;
215 }
216 if ( err1 > 40 ) { // no need for more matching
217#ifdef DEBUG_QIMPEN
218 qDebug( "tsig too great: %d", err1 );
219#endif
220 return 400000;
221 }
222
223 // maybe a sliding window is worthwhile for these too.
224 err2 = calcError( dsig, pen->dsig, 0, FALSE );
225 if ( err2 > 100 ) {
226#ifdef DEBUG_QIMPEN
227 qDebug( "dsig too great: %d", err2 );
228#endif
229 return 400000;
230 }
231
232 err3 = calcError( asig, pen->asig, 0, TRUE );
233 if ( err3 > 60 ) {
234#ifdef DEBUG_QIMPEN
235 qDebug( "asig too great: %d", err3 );
236#endif
237 return 400000;
238 }
239
240 // Some magic numbers here - the addition reduces the weighting of
241 // the error and compensates for the different error scales. I
242 // consider the tangent signature to be the best indicator, so it
243 // has the most weight. This ain't rocket science.
244 // Basically, these numbers are the tuning factors.
245 unsigned int err = (err1+1) * ( err2 + 60 ) * ( err3 + 20 ) +
246 vdiff * 1000 + evdiff * 500 +
247 (unsigned int)(lratio * 5000.0);
248
249#ifdef DEBUG_QIMPEN
250 qDebug( "err %d ( %d, %d, %d, %d)", err, err1, err2, err3, vdiff );
251#endif
252
253 return err;
254}
255
256/*!
257 Return the bounding rect of this stroke.
258*/
259QRect QIMPenStroke::boundingRect()
260{
261 if ( !bounding.isValid() ) {
262 int x = startPoint.x();
263 int y = startPoint.y();
264 bounding = QRect( x, y, 1, 1 );
265
266 for ( unsigned i = 0; i < links.count(); i++ ) {
267 x += links[i].dx;
268 y += links[i].dy;
269 if ( x < bounding.left() )
270 bounding.setLeft( x );
271 if ( x > bounding.right() )
272 bounding.setRight( x );
273 if ( y < bounding.top() )
274 bounding.setTop( y );
275 if ( y > bounding.bottom() )
276 bounding.setBottom( y );
277 }
278 }
279
280 return bounding;
281}
282
283
284/*!
285 Perform a correlation of the supplied arrays. \a base should have
286 win.count() + 2 * off points to enable sliding \a win over the
287 \a base data. If \a t is TRUE, the comparison takes into account
288 the circular nature of the angular data.
289 Returns the best (lowest error) match.
290*/
291
292int QIMPenStroke::calcError( const QArray<int> &base,
293 const QArray<int> &win, int off, bool t )
294{
295 int err = 0;
296
297 for ( unsigned i = 0; i < win.count(); i++ ) {
298 int d = QABS( base[i+off] - win[i] );
299 if ( t && d > 128 )
300 d -= 256;
301 err += QABS( d );
302 }
303
304 err /= win.count();
305
306 return err;
307}
308
309/*!
310 Creates signatures used in matching if not already created.
311*/
312void QIMPenStroke::createSignatures()
313{
314 if ( tsig.isEmpty() )
315 createTanSignature();
316 if ( asig.isEmpty() )
317 createAngleSignature();
318 if ( dsig.isEmpty() )
319 createDistSignature();
320}
321
322/*!
323 Create a signature of the tangents to the user's stroke.
324*/
325void QIMPenStroke::createTanSignature()
326{
327 int dist = 5; // number of points to include in calculation
328 if ( (int)links.count() <= dist ) {
329 tsig.resize(1);
330 int dx = 0;
331 int dy = 0;
332 for ( unsigned j = 0; j < links.count(); j++ ) {
333 dx += links[j].dx;
334 dy += links[j].dy;
335 }
336 tsig[0] = arcTan( dy, dx );
337 } else {
338 tsig.resize( (links.count()-dist+1) / 2 );
339 int idx = 0;
340 for ( unsigned i = 0; i < links.count() - dist; i += 2 ) {
341 int dx = 0;
342 int dy = 0;
343 for ( int j = 0; j < dist; j++ ) {
344 dx += links[i+j].dx;
345 dy += links[i+j].dy;
346 }
347 tsig[idx++] = arcTan( dy, dx );
348 }
349 }
350
351 tsig = scale( tsig, QIMPEN_CORRELATION_POINTS, TRUE );
352// smooth(tsig);
353}
354
355/*!
356 Create a signature of the change in angle.
357*/
358void QIMPenStroke::createAngleSignature()
359{
360 QPoint c = calcCenter();
361
362 int dist = 3; // number of points to include in calculation
363 if ( (int)links.count() <= dist ) {
364 asig.resize(1);
365 asig[0] = 1;
366 } else {
367 asig.resize( links.count() );
368 QPoint current(0, 0);
369 int idx = 0;
370 for ( unsigned i = 0; i < links.count(); i++ ) {
371 int dx = c.x() - current.x();
372 int dy = c.y() - current.y();
373 int md = QMAX( QABS(dx), QABS(dy) );
374 if ( md > 5 ) {
375 dx = dx * 5 / md;
376 dy = dy * 5 / md;
377 }
378 asig[idx++] = arcTan( dy, dx );
379 current += QPoint( links[i].dx, links[i].dy );
380 }
381 }
382
383 asig = scale( asig, QIMPEN_CORRELATION_POINTS, TRUE );
384
385/*
386 if ( tsig.isEmpty() )
387 createTanSignature();
388
389 if ( tsig.count() < 5 ) {
390 asig.resize( 1 );
391 asig[0] = 0;
392 } else {
393 asig.resize( tsig.count() - 5 );
394
395 for ( unsigned i = 0; i < asig.count(); i++ ) {
396 asig[i] = QABS(tsig[i] - tsig[i+5]);
397 }
398 }
399*/
400}
401
402/*!
403 Create a signature of the distance from the char's center of gravity
404 to its points.
405*/
406void QIMPenStroke::createDistSignature()
407{
408 dsig.resize( (links.count()+1)/2 );
409 QPoint c = calcCenter();
410 QPoint pt( 0, 0 );
411
412 int minval = INT_MAX;
413 int maxval = 0;
414 int idx = 0;
415 for ( unsigned i = 0; i < links.count(); i += 2 ) {
416 int dx = c.x() - pt.x();
417 int dy = c.y() - pt.y();
418 if ( dx == 0 && dy == 0 )
419 dsig[idx] = 0;
420 else
421 dsig[idx] = dx*dx + dy*dy;
422
423 if ( dsig[idx] > maxval )
424 maxval = dsig[idx];
425 if ( dsig[idx] < minval )
426 minval = dsig[idx];
427 pt.rx() += links[i].dx;
428 pt.ry() += links[i].dy;
429 idx++;
430 }
431
432 // normalise 0-255
433 int div = maxval - minval;
434 if ( div == 0 ) div = 1;
435 for ( unsigned i = 0; i < dsig.count(); i++ ) {
436 dsig[i] = (dsig[i] - minval ) * 255 / div;
437 }
438
439 dsig = scale( dsig, QIMPEN_CORRELATION_POINTS );
440}
441
442
443/*!
444 Scale the points in a array to \a count points.
445 This is braindead at the moment (no smooth scaling) and fixing this is
446 probably one of the simpler ways to improve performance.
447*/
448QArray<int> QIMPenStroke::scale( const QArray<int> &s, unsigned count, bool t )
449{
450 QArray<int> d(count);
451
452 unsigned si = 0;
453 if ( s.count() > count ) {
454 unsigned next = 0;
455 for ( unsigned i = 0; i < count; i++ ) {
456 next = (i+1) * s.count() / count;
457 int maxval = 0;
458 if ( t ) {
459 for ( unsigned j = si; j < next; j++ ) {
460 maxval = s[j] > maxval ? s[j] : maxval;
461 }
462 }
463 int sum = 0;
464 for ( unsigned j = si; j < next; j++ ) {
465 if ( t && maxval - s[j] > 128 )
466 sum += 256;
467 sum += s[j];
468 }
469 d[i] = sum / (next-si);
470 if ( t && d[i] > 256 )
471 d[i] %= 256;
472 si = next;
473 }
474 } else {
475 for ( unsigned i = 0; i < count; i++ ) {
476 si = i * s.count() / count;
477 d[i] = s[si];
478 }
479 }
480
481 return d;
482}
483
484/*!
485 Add another point to the stroke's shape.
486*/
487void QIMPenStroke::internalAddPoint( QPoint p )
488{
489 if ( p == lastPoint )
490 return;
491
492 if ( !lastPoint.isNull() ) {
493 QIMPenGlyphLink gl;
494 gl.dx = p.x() - lastPoint.x();
495 gl.dy = p.y() - lastPoint.y();
496 links.resize( links.size() + 1 ); //### resize by 1 is bad
497 links[links.size() - 1] = gl;
498 }
499
500 lastPoint = p;
501 bounding = QRect();
502}
503
504/*!
505 Calculate the center of gravity of the stroke.
506*/
507QPoint QIMPenStroke::calcCenter()
508{
509 QPoint pt( 0, 0 );
510 int ax = 0;
511 int ay = 0;
512
513 for ( unsigned i = 0; i < links.count(); i++ ) {
514 pt.rx() += links[i].dx;
515 pt.ry() += links[i].dy;
516 ax += pt.x();
517 ay += pt.y();
518 }
519
520 ax /= (int)links.count();
521 ay /= (int)links.count();
522
523 return QPoint( ax, ay );
524}
525
526/*!
527 Calculate the arctan of the lengths supplied.
528 The angle returned is in the range 0-255.
529 \a dy and \a dx MUST be in the range 0-5 - I dont even check :-P
530*/
531int QIMPenStroke::arcTan( int dy, int dx )
532{
533 if ( dx == 0 ) {
534 if ( dy >= 0 )
535 return 64;
536 else
537 return 192;
538 }
539
540 if ( dy == 0 ) {
541 if ( dx >= 0 )
542 return 0;
543 else
544 return 128;
545 }
546
547 static int table[5][5] = {
548 { 32, 19, 13, 10, 8 },
549 { 45, 32, 24, 19, 16 },
550 { 51, 40, 32, 26, 22 },
551 { 54, 45, 37, 32, 27 },
552 { 56, 49, 42, 37, 32 } };
553
554 if ( dy > 0 ) {
555 if ( dx > 0 )
556 return table[dy-1][dx-1];
557 else
558 return 128 - table[dy-1][QABS(dx)-1];
559 } else {
560 if ( dx > 0 )
561 return 256 - table[QABS(dy)-1][dx-1];
562 else
563 return 128 + table[QABS(dy)-1][QABS(dx)-1];
564 }
565
566 return 0;
567}
568
569
570/*!
571 Silly name. Create an array that has \a e points extra at the start and
572 end to enable a sliding correlation to be performed.
573*/
574QArray<int> QIMPenStroke::createBase( const QArray<int> a, int e )
575{
576 QArray<int> ra( a.count() + 2*e );
577
578 for ( int i = 0; i < e; i++ ) {
579 ra[i] = a[e - i - 1];
580 ra[a.count() + i] = a[a.count() - i - 1];
581 }
582 for ( unsigned i = 0; i < a.count(); i++ ) {
583 ra[i+e] = a[i];
584 }
585
586 return ra;
587}
588
589
590/*!
591 Smooth the points in an array. Probably a bad idea.
592*/
593void QIMPenStroke::smooth( QArray<int> &sig)
594{
595 QArray<int> nsig = sig.copy();
596
597 int a;
598 for ( unsigned i = 1; i < sig.count()-2; i++ ) {
599 a = 0;
600 for ( int j = -1; j <= 1; j++ ) {
601 a += sig[ i + j ];
602 }
603 nsig[i] = a / 3;
604 }
605
606 sig = nsig;
607}
608
609/*!
610 Write the character's data to the stream.
611*/
612QDataStream &operator<< (QDataStream &s, const QIMPenStroke &ws)
613{
614 s << ws.startPoint;
615 s << ws.links.count();
616 for ( unsigned i = 0; i < ws.links.count(); i++ ) {
617 s << (Q_INT8)ws.links[i].dx;
618 s << (Q_INT8)ws.links[i].dy;
619 }
620
621 return s;
622}
623
624/*!
625 Read the character's data from the stream.
626*/
627QDataStream &operator>> (QDataStream &s, QIMPenStroke &ws)
628{
629 Q_INT8 i8;
630 s >> ws.startPoint;
631 ws.lastPoint = ws.startPoint;
632 unsigned size;
633 s >> size;
634 ws.links.resize( size );
635 for ( unsigned i = 0; i < size; i++ ) {
636 s >> i8;
637 ws.links[i].dx = i8;
638 s >> i8;
639 ws.links[i].dy = i8;
640 ws.lastPoint += QPoint( ws.links[i].dx, ws.links[i].dy );
641 }
642
643 return s;
644}
645
646