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.cpp15
1 files changed, 8 insertions, 7 deletions
diff --git a/inputmethods/handwriting/qimpenstroke.cpp b/inputmethods/handwriting/qimpenstroke.cpp
index 3567d6d..14e435a 100644
--- a/inputmethods/handwriting/qimpenstroke.cpp
+++ b/inputmethods/handwriting/qimpenstroke.cpp
@@ -1,346 +1,347 @@
1/********************************************************************** 1/**********************************************************************
2** Copyright (C) 2000 Trolltech AS. All rights reserved. 2** Copyright (C) 2000 Trolltech AS. All rights reserved.
3** 3**
4** This file is part of Qtopia Environment. 4** This file is part of Qtopia Environment.
5** 5**
6** This file may be distributed and/or modified under the terms of the 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 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 8** Foundation and appearing in the file LICENSE.GPL included in the
9** packaging of this file. 9** packaging of this file.
10** 10**
11** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE 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. 12** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
13** 13**
14** See http://www.trolltech.com/gpl/ for GPL licensing information. 14** See http://www.trolltech.com/gpl/ for GPL licensing information.
15** 15**
16** Contact info@trolltech.com if any conditions of this licensing are 16** Contact info@trolltech.com if any conditions of this licensing are
17** not clear to you. 17** not clear to you.
18** 18**
19**********************************************************************/ 19**********************************************************************/
20 20
21#include <qfile.h> 21#include <qfile.h>
22#include <qtl.h> 22#include <qtl.h>
23#include <math.h> 23#include <math.h>
24#include <limits.h> 24#include <limits.h>
25#include <qdatastream.h> 25#include <qdatastream.h>
26#include "qimpenstroke.h" 26#include "qimpenstroke.h"
27#include "opie2/odebug.h"
27 28
28#define QIMPEN_CORRELATION_POINTS 25 29#define QIMPEN_CORRELATION_POINTS 25
29//#define DEBUG_QIMPEN 30//#define DEBUG_QIMPEN
30 31
31/*! 32/*!
32 \class QIMPenStroke qimpenstroke.h 33 \class QIMPenStroke qimpenstroke.h
33 34
34 Handles a single stroke. Can calculate closeness of match to 35 Handles a single stroke. Can calculate closeness of match to
35 another stroke. 36 another stroke.
36*/ 37*/
37 38
38QIMPenStroke::QIMPenStroke() 39QIMPenStroke::QIMPenStroke()
39{ 40{
40} 41}
41 42
42QIMPenStroke::QIMPenStroke( const QIMPenStroke &st ) 43QIMPenStroke::QIMPenStroke( const QIMPenStroke &st )
43{ 44{
44 startPoint = st.startPoint; 45 startPoint = st.startPoint;
45 lastPoint = st.lastPoint; 46 lastPoint = st.lastPoint;
46 links = st.links.copy(); 47 links = st.links.copy();
47} 48}
48 49
49QIMPenStroke &QIMPenStroke::operator=( const QIMPenStroke &s ) 50QIMPenStroke &QIMPenStroke::operator=( const QIMPenStroke &s )
50{ 51{
51 clear(); 52 clear();
52 //qDebug( "copy strokes %d", s.links.count() ); 53 //odebug << "copy strokes " << s.links.count() << oendl;
53 startPoint = s.startPoint; 54 startPoint = s.startPoint;
54 lastPoint = s.lastPoint; 55 lastPoint = s.lastPoint;
55 links = s.links.copy(); 56 links = s.links.copy();
56 57
57 return *this; 58 return *this;
58} 59}
59 60
60void QIMPenStroke::clear() 61void QIMPenStroke::clear()
61{ 62{
62 startPoint = QPoint(0,0); 63 startPoint = QPoint(0,0);
63 lastPoint = QPoint( 0, 0 ); 64 lastPoint = QPoint( 0, 0 );
64 links.resize( 0 ); 65 links.resize( 0 );
65 tsig.resize( 0 ); 66 tsig.resize( 0 );
66 dsig.resize( 0 ); 67 dsig.resize( 0 );
67 asig.resize( 0 ); 68 asig.resize( 0 );
68} 69}
69 70
70/*! 71/*!
71 Begin inputting a new stroke. 72 Begin inputting a new stroke.
72*/ 73*/
73void QIMPenStroke::beginInput( QPoint p ) 74void QIMPenStroke::beginInput( QPoint p )
74{ 75{
75 clear(); 76 clear();
76 startPoint = p; 77 startPoint = p;
77 bounding = QRect(); 78 bounding = QRect();
78 internalAddPoint( p ); 79 internalAddPoint( p );
79} 80}
80 81
81/*! 82/*!
82 Add a point to the stroke's shape. 83 Add a point to the stroke's shape.
83 Returns TRUE if the point was successfully added. 84 Returns TRUE if the point was successfully added.
84*/ 85*/
85bool QIMPenStroke::addPoint( QPoint p ) 86bool QIMPenStroke::addPoint( QPoint p )
86{ 87{
87 if ( links.count() > 500 ) // sanity check (that the user is sane). 88 if ( links.count() > 500 ) // sanity check (that the user is sane).
88 return FALSE; 89 return FALSE;
89 90
90 int dx = p.x() - lastPoint.x(); 91 int dx = p.x() - lastPoint.x();
91 int dy = p.y() - lastPoint.y(); 92 int dy = p.y() - lastPoint.y();
92 if ( QABS( dx ) > 1 || QABS( dy ) > 1 ) { 93 if ( QABS( dx ) > 1 || QABS( dy ) > 1 ) {
93 // The point is not adjacent to the previous point, so we fill 94 // The point is not adjacent to the previous point, so we fill
94 // in with a straight line. Some kind of non-linear 95 // in with a straight line. Some kind of non-linear
95 // interpolation might be better. 96 // interpolation might be better.
96 int x = lastPoint.x(); 97 int x = lastPoint.x();
97 int y = lastPoint.y(); 98 int y = lastPoint.y();
98 int ix = 1; 99 int ix = 1;
99 int iy = 1; 100 int iy = 1;
100 if ( dx < 0 ) { 101 if ( dx < 0 ) {
101 ix = -1; 102 ix = -1;
102 dx = -dx; 103 dx = -dx;
103 } 104 }
104 if ( dy < 0 ) { 105 if ( dy < 0 ) {
105 iy = -1; 106 iy = -1;
106 dy = -dy; 107 dy = -dy;
107 } 108 }
108 int d = 0; 109 int d = 0;
109 if ( dx < dy ) { 110 if ( dx < dy ) {
110 d = dx; 111 d = dx;
111 do { 112 do {
112 y += iy; 113 y += iy;
113 d += dx; 114 d += dx;
114 if ( d > dy ) { 115 if ( d > dy ) {
115 x += ix; 116 x += ix;
116 d -= dy; 117 d -= dy;
117 } 118 }
118 internalAddPoint( QPoint( x, y ) ); 119 internalAddPoint( QPoint( x, y ) );
119 } while ( y != p.y() ); 120 } while ( y != p.y() );
120 } else { 121 } else {
121 d = dy; 122 d = dy;
122 do { 123 do {
123 x += ix; 124 x += ix;
124 d += dy; 125 d += dy;
125 if ( d > dx ) { 126 if ( d > dx ) {
126 y += iy; 127 y += iy;
127 d -= dx; 128 d -= dx;
128 } 129 }
129 internalAddPoint( QPoint( x, y ) ); 130 internalAddPoint( QPoint( x, y ) );
130 } while ( x != p.x() ); 131 } while ( x != p.x() );
131 } 132 }
132 } else { 133 } else {
133 internalAddPoint( p ); 134 internalAddPoint( p );
134 } 135 }
135 136
136 return TRUE; 137 return TRUE;
137} 138}
138 139
139/*! 140/*!
140 Finish inputting a stroke. 141 Finish inputting a stroke.
141*/ 142*/
142void QIMPenStroke::endInput() 143void QIMPenStroke::endInput()
143{ 144{
144 if ( links.count() < 3 ) { 145 if ( links.count() < 3 ) {
145 QIMPenGlyphLink gl; 146 QIMPenGlyphLink gl;
146 links.resize(1); 147 links.resize(1);
147 gl.dx = 1; 148 gl.dx = 1;
148 gl.dy = 0; 149 gl.dy = 0;
149 links[0] = gl; 150 links[0] = gl;
150 } 151 }
151 152
152 //qDebug("Points: %d", links.count() ); 153 //odebug << "Points: " << links.count() << oendl;
153} 154}
154 155
155/*! 156/*!
156 Return an indicator of the closeness of this stroke to \a pen. 157 Return an indicator of the closeness of this stroke to \a pen.
157 Lower value is better. 158 Lower value is better.
158*/ 159*/
159unsigned int QIMPenStroke::match( QIMPenStroke *pen ) 160unsigned int QIMPenStroke::match( QIMPenStroke *pen )
160{ 161{
161 double lratio; 162 double lratio;
162 163
163 if ( links.count() > pen->links.count() ) 164 if ( links.count() > pen->links.count() )
164 lratio = (links.count()+2) / (pen->links.count()+2); 165 lratio = (links.count()+2) / (pen->links.count()+2);
165 else 166 else
166 lratio = (pen->links.count()+2) / (links.count()+2); 167 lratio = (pen->links.count()+2) / (links.count()+2);
167 168
168 lratio -= 1.0; 169 lratio -= 1.0;
169 170
170 if ( lratio > 2.0 ) { 171 if ( lratio > 2.0 ) {
171#ifdef DEBUG_QIMPEN 172#ifdef DEBUG_QIMPEN
172 qDebug( "stroke length too different" ); 173 odebug << "stroke length too different" << oendl;
173#endif 174#endif
174 return 400000; 175 return 400000;
175 } 176 }
176 177
177 createSignatures(); 178 createSignatures();
178 pen->createSignatures(); 179 pen->createSignatures();
179 180
180 // Starting point offset 181 // Starting point offset
181 int vdiff = QABS(startPoint.y() - pen->startPoint.y()); 182 int vdiff = QABS(startPoint.y() - pen->startPoint.y());
182 183
183 // Insanely offset? 184 // Insanely offset?
184 if ( vdiff > 18 ) { 185 if ( vdiff > 18 ) {
185 return 400000; 186 return 400000;
186 } 187 }
187 188
188 vdiff -= 4; 189 vdiff -= 4;
189 if ( vdiff < 0 ) 190 if ( vdiff < 0 )
190 vdiff = 0; 191 vdiff = 0;
191 192
192 // Ending point offset 193 // Ending point offset
193 int evdiff = QABS(lastPoint.y() - pen->lastPoint.y()); 194 int evdiff = QABS(lastPoint.y() - pen->lastPoint.y());
194 // Insanely offset? 195 // Insanely offset?
195 if ( evdiff > 20 ) { 196 if ( evdiff > 20 ) {
196 return 400000; 197 return 400000;
197 } 198 }
198 199
199 evdiff -= 5; 200 evdiff -= 5;
200 if ( evdiff < 0 ) 201 if ( evdiff < 0 )
201 evdiff = 0; 202 evdiff = 0;
202 203
203 // do a correlation with the three available signatures. 204 // do a correlation with the three available signatures.
204 int err1 = INT_MAX; 205 int err1 = INT_MAX;
205 int err2 = INT_MAX; 206 int err2 = INT_MAX;
206 int err3 = INT_MAX; 207 int err3 = INT_MAX;
207 208
208 // base has extra points at the start and end to enable 209 // base has extra points at the start and end to enable
209 // correlation of a sliding window with the pen supplied. 210 // correlation of a sliding window with the pen supplied.
210 QArray<int> base = createBase( tsig, 2 ); 211 QArray<int> base = createBase( tsig, 2 );
211 for ( int i = 0; i < 4; i++ ) { 212 for ( int i = 0; i < 4; i++ ) {
212 int e = calcError( base, pen->tsig, i, TRUE ); 213 int e = calcError( base, pen->tsig, i, TRUE );
213 if ( e < err1 ) 214 if ( e < err1 )
214 err1 = e; 215 err1 = e;
215 } 216 }
216 if ( err1 > 40 ) { // no need for more matching 217 if ( err1 > 40 ) { // no need for more matching
217#ifdef DEBUG_QIMPEN 218#ifdef DEBUG_QIMPEN
218 qDebug( "tsig too great: %d", err1 ); 219 odebug << "tsig too great: " << err1 << oendl;
219#endif 220#endif
220 return 400000; 221 return 400000;
221 } 222 }
222 223
223 // maybe a sliding window is worthwhile for these too. 224 // maybe a sliding window is worthwhile for these too.
224 err2 = calcError( dsig, pen->dsig, 0, FALSE ); 225 err2 = calcError( dsig, pen->dsig, 0, FALSE );
225 if ( err2 > 100 ) { 226 if ( err2 > 100 ) {
226#ifdef DEBUG_QIMPEN 227#ifdef DEBUG_QIMPEN
227 qDebug( "dsig too great: %d", err2 ); 228 odebug << "dsig too great: " << err2 << oendl;
228#endif 229#endif
229 return 400000; 230 return 400000;
230 } 231 }
231 232
232 err3 = calcError( asig, pen->asig, 0, TRUE ); 233 err3 = calcError( asig, pen->asig, 0, TRUE );
233 if ( err3 > 60 ) { 234 if ( err3 > 60 ) {
234#ifdef DEBUG_QIMPEN 235#ifdef DEBUG_QIMPEN
235 qDebug( "asig too great: %d", err3 ); 236 odebug << "asig too great: " << err3 << oendl;
236#endif 237#endif
237 return 400000; 238 return 400000;
238 } 239 }
239 240
240 // Some magic numbers here - the addition reduces the weighting of 241 // Some magic numbers here - the addition reduces the weighting of
241 // the error and compensates for the different error scales. I 242 // the error and compensates for the different error scales. I
242 // consider the tangent signature to be the best indicator, so it 243 // consider the tangent signature to be the best indicator, so it
243 // has the most weight. This ain't rocket science. 244 // has the most weight. This ain't rocket science.
244 // Basically, these numbers are the tuning factors. 245 // Basically, these numbers are the tuning factors.
245 unsigned int err = (err1+1) * ( err2 + 60 ) * ( err3 + 20 ) + 246 unsigned int err = (err1+1) * ( err2 + 60 ) * ( err3 + 20 ) +
246 vdiff * 1000 + evdiff * 500 + 247 vdiff * 1000 + evdiff * 500 +
247 (unsigned int)(lratio * 5000.0); 248 (unsigned int)(lratio * 5000.0);
248 249
249#ifdef DEBUG_QIMPEN 250#ifdef DEBUG_QIMPEN
250 qDebug( "err %d ( %d, %d, %d, %d)", err, err1, err2, err3, vdiff ); 251 odebug << "err " << err << "( " << err1 << ", " << err2 << ", " << err3 << ", " << vdiff << oendl;
251#endif 252#endif
252 253
253 return err; 254 return err;
254} 255}
255 256
256/*! 257/*!
257 Return the bounding rect of this stroke. 258 Return the bounding rect of this stroke.
258*/ 259*/
259QRect QIMPenStroke::boundingRect() 260QRect QIMPenStroke::boundingRect()
260{ 261{
261 if ( !bounding.isValid() ) { 262 if ( !bounding.isValid() ) {
262 int x = startPoint.x(); 263 int x = startPoint.x();
263 int y = startPoint.y(); 264 int y = startPoint.y();
264 bounding = QRect( x, y, 1, 1 ); 265 bounding = QRect( x, y, 1, 1 );
265 266
266 for ( unsigned i = 0; i < links.count(); i++ ) { 267 for ( unsigned i = 0; i < links.count(); i++ ) {
267 x += links[i].dx; 268 x += links[i].dx;
268 y += links[i].dy; 269 y += links[i].dy;
269 if ( x < bounding.left() ) 270 if ( x < bounding.left() )
270 bounding.setLeft( x ); 271 bounding.setLeft( x );
271 if ( x > bounding.right() ) 272 if ( x > bounding.right() )
272 bounding.setRight( x ); 273 bounding.setRight( x );
273 if ( y < bounding.top() ) 274 if ( y < bounding.top() )
274 bounding.setTop( y ); 275 bounding.setTop( y );
275 if ( y > bounding.bottom() ) 276 if ( y > bounding.bottom() )
276 bounding.setBottom( y ); 277 bounding.setBottom( y );
277 } 278 }
278 } 279 }
279 280
280 return bounding; 281 return bounding;
281} 282}
282 283
283 284
284/*! 285/*!
285 Perform a correlation of the supplied arrays. \a base should have 286 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 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 \a base data. If \a t is TRUE, the comparison takes into account
288 the circular nature of the angular data. 289 the circular nature of the angular data.
289 Returns the best (lowest error) match. 290 Returns the best (lowest error) match.
290*/ 291*/
291 292
292int QIMPenStroke::calcError( const QArray<int> &base, 293int QIMPenStroke::calcError( const QArray<int> &base,
293 const QArray<int> &win, int off, bool t ) 294 const QArray<int> &win, int off, bool t )
294{ 295{
295 int err = 0; 296 int err = 0;
296 297
297 for ( unsigned i = 0; i < win.count(); i++ ) { 298 for ( unsigned i = 0; i < win.count(); i++ ) {
298 int d = QABS( base[i+off] - win[i] ); 299 int d = QABS( base[i+off] - win[i] );
299 if ( t && d > 128 ) 300 if ( t && d > 128 )
300 d -= 256; 301 d -= 256;
301 err += QABS( d ); 302 err += QABS( d );
302 } 303 }
303 304
304 err /= win.count(); 305 err /= win.count();
305 306
306 return err; 307 return err;
307} 308}
308 309
309/*! 310/*!
310 Creates signatures used in matching if not already created. 311 Creates signatures used in matching if not already created.
311*/ 312*/
312void QIMPenStroke::createSignatures() 313void QIMPenStroke::createSignatures()
313{ 314{
314 if ( tsig.isEmpty() ) 315 if ( tsig.isEmpty() )
315 createTanSignature(); 316 createTanSignature();
316 if ( asig.isEmpty() ) 317 if ( asig.isEmpty() )
317 createAngleSignature(); 318 createAngleSignature();
318 if ( dsig.isEmpty() ) 319 if ( dsig.isEmpty() )
319 createDistSignature(); 320 createDistSignature();
320} 321}
321 322
322/*! 323/*!
323 Create a signature of the tangents to the user's stroke. 324 Create a signature of the tangents to the user's stroke.
324*/ 325*/
325void QIMPenStroke::createTanSignature() 326void QIMPenStroke::createTanSignature()
326{ 327{
327 int dist = 5; // number of points to include in calculation 328 int dist = 5; // number of points to include in calculation
328 if ( (int)links.count() <= dist ) { 329 if ( (int)links.count() <= dist ) {
329 tsig.resize(1); 330 tsig.resize(1);
330 int dx = 0; 331 int dx = 0;
331 int dy = 0; 332 int dy = 0;
332 for ( unsigned j = 0; j < links.count(); j++ ) { 333 for ( unsigned j = 0; j < links.count(); j++ ) {
333 dx += links[j].dx; 334 dx += links[j].dx;
334 dy += links[j].dy; 335 dy += links[j].dy;
335 } 336 }
336 tsig[0] = arcTan( dy, dx ); 337 tsig[0] = arcTan( dy, dx );
337 } else { 338 } else {
338 tsig.resize( (links.count()-dist+1) / 2 ); 339 tsig.resize( (links.count()-dist+1) / 2 );
339 int idx = 0; 340 int idx = 0;
340 for ( unsigned i = 0; i < links.count() - dist; i += 2 ) { 341 for ( unsigned i = 0; i < links.count() - dist; i += 2 ) {
341 int dx = 0; 342 int dx = 0;
342 int dy = 0; 343 int dy = 0;
343 for ( int j = 0; j < dist; j++ ) { 344 for ( int j = 0; j < dist; j++ ) {
344 dx += links[i+j].dx; 345 dx += links[i+j].dx;
345 dy += links[i+j].dy; 346 dy += links[i+j].dy;
346 } 347 }