Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

qimpenstroke.cpp

Go to the documentation of this file.
00001 /**********************************************************************
00002 ** Copyright (C) 2000 Trolltech AS.  All rights reserved.
00003 **
00004 ** This file is part of Qtopia Environment.
00005 **
00006 ** This file may be distributed and/or modified under the terms of the
00007 ** GNU General Public License version 2 as published by the Free Software
00008 ** Foundation and appearing in the file LICENSE.GPL included in the
00009 ** packaging of this file.
00010 **
00011 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00012 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00013 **
00014 ** See http://www.trolltech.com/gpl/ for GPL licensing information.
00015 **
00016 ** Contact info@trolltech.com if any conditions of this licensing are
00017 ** not clear to you.
00018 **
00019 **********************************************************************/
00020 
00021 #include <qfile.h>
00022 #include <qtl.h>
00023 #include <math.h>
00024 #include <limits.h>
00025 #include <qdatastream.h>
00026 #include "qimpenstroke.h"
00027 #include "opie2/odebug.h"
00028 
00029 #define QIMPEN_CORRELATION_POINTS   25
00030 //#define DEBUG_QIMPEN
00031 
00039 QIMPenStroke::QIMPenStroke()
00040 {
00041 }
00042 
00043 QIMPenStroke::QIMPenStroke( const QIMPenStroke &st )
00044 {
00045     startPoint = st.startPoint;
00046     lastPoint = st.lastPoint;
00047     links = st.links.copy();
00048 }
00049 
00050 QIMPenStroke &QIMPenStroke::operator=( const QIMPenStroke &s )
00051 {
00052     clear();
00053     //odebug << "copy strokes " << s.links.count() << oendl;
00054     startPoint = s.startPoint;
00055     lastPoint = s.lastPoint;
00056     links = s.links.copy();
00057 
00058     return *this;
00059 }
00060 
00061 void QIMPenStroke::clear()
00062 {
00063     startPoint = QPoint(0,0);
00064     lastPoint = QPoint( 0, 0 );
00065     links.resize( 0 );
00066     tsig.resize( 0 );
00067     dsig.resize( 0 );
00068     asig.resize( 0 );
00069 }
00070 
00074 void QIMPenStroke::beginInput( QPoint p )
00075 {
00076     clear();
00077     startPoint = p;
00078     bounding = QRect();
00079     internalAddPoint( p );
00080 }
00081 
00086 bool QIMPenStroke::addPoint( QPoint p )
00087 {
00088     if ( links.count() > 500 ) // sanity check (that the user is sane).
00089         return FALSE;
00090 
00091     int dx = p.x() - lastPoint.x();
00092     int dy = p.y() - lastPoint.y();
00093     if ( QABS( dx ) > 1 || QABS( dy ) > 1 ) {
00094         // The point is not adjacent to the previous point, so we fill
00095         // in with a straight line.  Some kind of non-linear
00096         // interpolation might be better.
00097         int x = lastPoint.x();
00098         int y = lastPoint.y();
00099         int ix = 1;
00100         int iy = 1;
00101         if ( dx < 0 ) {
00102             ix = -1;
00103             dx = -dx;
00104         }
00105         if ( dy < 0 ) {
00106             iy = -1;
00107             dy = -dy;
00108         }
00109         int d = 0;
00110         if ( dx < dy ) {
00111             d = dx;
00112             do {
00113                 y += iy;
00114                 d += dx;
00115                 if ( d > dy ) {
00116                     x += ix;
00117                     d -= dy;
00118                 }
00119                 internalAddPoint( QPoint( x, y ) );
00120             } while ( y != p.y() );
00121         } else {
00122             d = dy;
00123             do {
00124                 x += ix;
00125                 d += dy;
00126                 if ( d > dx ) {
00127                     y += iy;
00128                     d -= dx;
00129                 }
00130                 internalAddPoint( QPoint( x, y ) );
00131             } while ( x != p.x() );
00132         }
00133     } else {
00134         internalAddPoint( p );
00135     }
00136 
00137     return TRUE;
00138 }
00139 
00143 void QIMPenStroke::endInput()
00144 {
00145     if ( links.count() < 3 ) {
00146         QIMPenGlyphLink gl;
00147         links.resize(1);
00148         gl.dx = 1;
00149         gl.dy = 0;
00150         links[0] = gl;
00151     }
00152 
00153     //odebug << "Points: " << links.count() << oendl; 
00154 }
00155 
00160 unsigned int QIMPenStroke::match( QIMPenStroke *pen )
00161 {
00162     double lratio;
00163 
00164     if ( links.count() > pen->links.count() )
00165         lratio = (links.count()+2) / (pen->links.count()+2);
00166     else
00167         lratio =  (pen->links.count()+2) / (links.count()+2);
00168 
00169     lratio -= 1.0;
00170 
00171     if ( lratio > 2.0 ) {
00172 #ifdef DEBUG_QIMPEN
00173         odebug << "stroke length too different" << oendl;
00174 #endif
00175         return 400000;
00176     }
00177 
00178     createSignatures();
00179     pen->createSignatures();
00180 
00181     // Starting point offset
00182     int vdiff = QABS(startPoint.y() - pen->startPoint.y());
00183 
00184     // Insanely offset?
00185     if ( vdiff > 18 ) {
00186         return 400000;
00187     }
00188 
00189     vdiff -= 4;
00190     if ( vdiff < 0 )
00191         vdiff = 0;
00192 
00193     // Ending point offset
00194     int evdiff = QABS(lastPoint.y() - pen->lastPoint.y());
00195     // Insanely offset?
00196     if ( evdiff > 20 ) {
00197         return 400000;
00198     }
00199 
00200     evdiff -= 5;
00201     if ( evdiff < 0 )
00202         evdiff = 0;
00203 
00204     // do a correlation with the three available signatures.
00205     int err1 = INT_MAX;
00206     int err2 = INT_MAX;
00207     int err3 = INT_MAX;
00208 
00209     // base has extra points at the start and end to enable
00210     // correlation of a sliding window with the pen supplied.
00211     QArray<int> base = createBase( tsig, 2 );
00212     for ( int i = 0; i < 4; i++ ) {
00213         int e = calcError( base, pen->tsig, i, TRUE );
00214         if ( e < err1 )
00215             err1 = e;
00216     }
00217     if ( err1 > 40 ) {  // no need for more matching
00218 #ifdef DEBUG_QIMPEN
00219         odebug << "tsig too great: " << err1 << oendl;
00220 #endif
00221         return 400000;
00222     }
00223 
00224     // maybe a sliding window is worthwhile for these too.
00225     err2 = calcError( dsig, pen->dsig, 0, FALSE );
00226     if ( err2 > 100 ) {
00227 #ifdef DEBUG_QIMPEN
00228         odebug << "dsig too great: " <<  err2 << oendl;
00229 #endif
00230         return 400000;
00231     }
00232 
00233     err3 = calcError( asig, pen->asig, 0, TRUE );
00234     if ( err3 > 60 ) {
00235 #ifdef DEBUG_QIMPEN
00236         odebug << "asig too great: " << err3 << oendl;
00237 #endif
00238         return 400000;
00239     }
00240 
00241     // Some magic numbers here - the addition reduces the weighting of
00242     // the error and compensates for the different error scales.  I
00243     // consider the tangent signature to be the best indicator, so it
00244     // has the most weight.  This ain't rocket science.
00245     // Basically, these numbers are the tuning factors.
00246     unsigned int err = (err1+1) * ( err2 + 60 ) * ( err3 + 20 ) +
00247                         vdiff * 1000 + evdiff * 500 +
00248                         (unsigned int)(lratio * 5000.0);
00249 
00250 #ifdef DEBUG_QIMPEN
00251     odebug << "err " << err << "( " << err1 << ", " << err2 << ", " << err3 << ", " << vdiff << oendl;
00252 #endif
00253 
00254     return err;
00255 }
00256 
00260 QRect QIMPenStroke::boundingRect()
00261 {
00262     if ( !bounding.isValid() ) {
00263         int x = startPoint.x();
00264         int y = startPoint.y();
00265         bounding = QRect( x, y, 1, 1 );
00266 
00267         for ( unsigned i = 0; i < links.count(); i++ ) {
00268             x += links[i].dx;
00269             y += links[i].dy;
00270             if ( x < bounding.left() )
00271                 bounding.setLeft( x );
00272             if ( x > bounding.right() )
00273                 bounding.setRight( x );
00274             if ( y < bounding.top() )
00275                 bounding.setTop( y );
00276             if ( y > bounding.bottom() )
00277                 bounding.setBottom( y );
00278         }
00279     }
00280 
00281     return bounding;
00282 }
00283 
00284 
00293 int QIMPenStroke::calcError( const QArray<int> &base,
00294                            const QArray<int> &win, int off, bool t )
00295 {
00296     int err = 0;
00297 
00298     for ( unsigned i = 0; i < win.count(); i++ ) {
00299         int d = QABS( base[i+off] - win[i] );
00300         if ( t && d > 128 )
00301             d -= 256;
00302         err += QABS( d );
00303     }
00304 
00305     err /= win.count();
00306 
00307     return err;
00308 }
00309 
00313 void QIMPenStroke::createSignatures()
00314 {
00315     if ( tsig.isEmpty() )
00316         createTanSignature();
00317     if ( asig.isEmpty() )
00318         createAngleSignature();
00319     if ( dsig.isEmpty() )
00320         createDistSignature();
00321 }
00322 
00326 void QIMPenStroke::createTanSignature()
00327 {
00328     int dist = 5; // number of points to include in calculation
00329     if ( (int)links.count() <= dist ) {
00330         tsig.resize(1);
00331         int dx = 0;
00332         int dy = 0;
00333         for ( unsigned j = 0; j < links.count(); j++ ) {
00334             dx += links[j].dx;
00335             dy += links[j].dy;
00336         }
00337         tsig[0] = arcTan( dy, dx );
00338     } else {
00339         tsig.resize( (links.count()-dist+1) / 2 );
00340         int idx = 0;
00341         for ( unsigned i = 0; i < links.count() - dist; i += 2 ) {
00342             int dx = 0;
00343             int dy = 0;
00344             for ( int j = 0; j < dist; j++ ) {
00345                 dx += links[i+j].dx;
00346                 dy += links[i+j].dy;
00347             }
00348             tsig[idx++] = arcTan( dy, dx );
00349         }
00350     }
00351 
00352     tsig = scale( tsig, QIMPEN_CORRELATION_POINTS, TRUE );
00353 //    smooth(tsig);
00354 }
00355 
00359 void QIMPenStroke::createAngleSignature()
00360 {
00361     QPoint c = calcCenter();
00362 
00363     int dist = 3; // number of points to include in calculation
00364     if ( (int)links.count() <= dist ) {
00365         asig.resize(1);
00366         asig[0] = 1;
00367     } else {
00368         asig.resize( links.count() );
00369         QPoint current(0, 0);
00370         int idx = 0;
00371         for ( unsigned i = 0; i < links.count(); i++ ) {
00372             int dx = c.x() - current.x();
00373             int dy = c.y() - current.y();
00374             int md = QMAX( QABS(dx), QABS(dy) );
00375             if ( md > 5 ) {
00376                 dx = dx * 5 / md;
00377                 dy = dy * 5 / md;
00378             }
00379             asig[idx++] = arcTan( dy, dx );
00380             current += QPoint( links[i].dx, links[i].dy );
00381         }
00382     }
00383 
00384     asig = scale( asig, QIMPEN_CORRELATION_POINTS, TRUE );
00385 
00386 /*
00387     if ( tsig.isEmpty() )
00388         createTanSignature();
00389 
00390     if ( tsig.count() < 5 ) {
00391         asig.resize( 1 );
00392         asig[0] = 0;
00393     } else {
00394         asig.resize( tsig.count() - 5 );
00395 
00396         for ( unsigned i = 0; i < asig.count(); i++ ) {
00397             asig[i] = QABS(tsig[i] - tsig[i+5]);
00398         }
00399     }
00400 */
00401 }
00402 
00407 void QIMPenStroke::createDistSignature()
00408 {
00409     dsig.resize( (links.count()+1)/2 );
00410     QPoint c = calcCenter();
00411     QPoint pt( 0, 0 );
00412 
00413     int minval = INT_MAX;
00414     int maxval = 0;
00415     int idx = 0;
00416     for ( unsigned i = 0; i < links.count(); i += 2 ) {
00417         int dx = c.x() - pt.x();
00418         int dy = c.y() - pt.y();
00419         if ( dx == 0 && dy == 0 )
00420             dsig[idx] = 0;
00421         else
00422             dsig[idx] = dx*dx + dy*dy;
00423 
00424         if ( dsig[idx] > maxval )
00425             maxval = dsig[idx];
00426         if ( dsig[idx] < minval )
00427             minval = dsig[idx];
00428         pt.rx() += links[i].dx;
00429         pt.ry() += links[i].dy;
00430         idx++;
00431     }
00432 
00433     // normalise 0-255
00434     int div = maxval - minval;
00435     if ( div == 0 ) div = 1;
00436     for ( unsigned i = 0; i < dsig.count(); i++ ) {
00437         dsig[i] = (dsig[i] - minval ) * 255 / div;
00438     }
00439 
00440     dsig = scale( dsig, QIMPEN_CORRELATION_POINTS );
00441 }
00442 
00443 
00449 QArray<int> QIMPenStroke::scale( const QArray<int> &s, unsigned count, bool t )
00450 {
00451     QArray<int> d(count);
00452 
00453     unsigned si = 0;
00454     if ( s.count() > count ) {
00455         unsigned next = 0;
00456         for ( unsigned i = 0; i < count; i++ ) {
00457             next = (i+1) * s.count() / count;
00458             int maxval = 0;
00459             if ( t ) {
00460                 for ( unsigned j = si; j < next; j++ ) {
00461                     maxval = s[j] > maxval ? s[j] : maxval;
00462                 }
00463             }
00464             int sum = 0;
00465             for ( unsigned j = si; j < next; j++ ) {
00466                 if ( t && maxval - s[j] > 128 )
00467                     sum += 256;
00468                 sum += s[j];
00469             }
00470             d[i] = sum / (next-si);
00471             if ( t && d[i] > 256 )
00472                 d[i] %= 256;
00473             si = next;
00474         }
00475     } else {
00476         for ( unsigned i = 0; i < count; i++ ) {
00477             si = i * s.count() / count;
00478             d[i] = s[si];
00479         }
00480     }
00481 
00482     return d;
00483 }
00484 
00488 void QIMPenStroke::internalAddPoint( QPoint p )
00489 {
00490     if ( p == lastPoint )
00491         return;
00492 
00493     if ( !lastPoint.isNull() ) {
00494         QIMPenGlyphLink gl;
00495         gl.dx = p.x() - lastPoint.x();
00496         gl.dy = p.y() - lastPoint.y();
00497         links.resize( links.size() + 1 );   //### resize by 1 is bad
00498         links[links.size() - 1] = gl;
00499     }
00500 
00501     lastPoint = p;
00502     bounding = QRect();
00503 }
00504 
00508 QPoint QIMPenStroke::calcCenter()
00509 {
00510     QPoint pt( 0, 0 );
00511     int ax = 0;
00512     int ay = 0;
00513 
00514     for ( unsigned i = 0; i < links.count(); i++ ) {
00515         pt.rx() += links[i].dx;
00516         pt.ry() += links[i].dy;
00517         ax += pt.x();
00518         ay += pt.y();
00519     }
00520 
00521     ax /= (int)links.count();
00522     ay /= (int)links.count();
00523 
00524     return QPoint( ax, ay );
00525 }
00526 
00532 int QIMPenStroke::arcTan( int dy, int dx )
00533 {
00534     if ( dx == 0 ) {
00535         if ( dy >= 0 )
00536             return 64;
00537         else
00538             return 192;
00539     }
00540 
00541     if ( dy == 0 ) {
00542         if ( dx >= 0 )
00543             return 0;
00544         else
00545             return 128;
00546     }
00547 
00548     static int table[5][5] = {
00549         { 32, 19, 13, 10, 8  },
00550         { 45, 32, 24, 19, 16 },
00551         { 51, 40, 32, 26, 22 },
00552         { 54, 45, 37, 32, 27 },
00553         { 56, 49, 42, 37, 32 } };
00554 
00555     if ( dy > 0 ) {
00556         if ( dx > 0 )
00557             return table[dy-1][dx-1];
00558         else
00559             return 128 - table[dy-1][QABS(dx)-1];
00560     } else {
00561         if ( dx > 0 )
00562             return 256 - table[QABS(dy)-1][dx-1];
00563         else
00564             return 128 + table[QABS(dy)-1][QABS(dx)-1];
00565     }
00566 
00567     return 0;
00568 }
00569 
00570 
00575 QArray<int> QIMPenStroke::createBase( const QArray<int> a, int e )
00576 {
00577     QArray<int> ra( a.count() + 2*e );
00578 
00579     for ( int i = 0; i < e; i++ ) {
00580         ra[i] = a[e - i - 1];
00581         ra[a.count() + i] = a[a.count() - i - 1];
00582     }
00583     for ( unsigned i = 0; i < a.count(); i++ ) {
00584         ra[i+e] = a[i];
00585     }
00586 
00587     return ra;
00588 }
00589 
00590 
00594 void QIMPenStroke::smooth( QArray<int> &sig)
00595 {
00596     QArray<int> nsig = sig.copy();
00597 
00598     int a;
00599     for ( unsigned i = 1; i < sig.count()-2; i++ ) {
00600         a = 0;
00601         for ( int j = -1; j <= 1; j++ ) {
00602             a += sig[ i + j ];
00603         }
00604         nsig[i] = a / 3;
00605     }
00606 
00607     sig = nsig;
00608 }
00609 
00613 QDataStream &operator<< (QDataStream &s, const QIMPenStroke &ws)
00614 {
00615     s << ws.startPoint;
00616     s << ws.links.count();
00617     for ( unsigned i = 0; i < ws.links.count(); i++ ) {
00618         s << (Q_INT8)ws.links[i].dx;
00619         s << (Q_INT8)ws.links[i].dy;
00620     }
00621 
00622     return s;
00623 }
00624 
00628 QDataStream &operator>> (QDataStream &s, QIMPenStroke &ws)
00629 {
00630     Q_INT8 i8;
00631     s >> ws.startPoint;
00632     ws.lastPoint = ws.startPoint;
00633     unsigned size;
00634     s >> size;
00635     ws.links.resize( size );
00636     for ( unsigned i = 0; i < size; i++ ) {
00637         s >> i8;
00638         ws.links[i].dx = i8;
00639         s >> i8;
00640         ws.links[i].dy = i8;
00641         ws.lastPoint += QPoint( ws.links[i].dx, ws.links[i].dy );
00642     }
00643 
00644     return s;
00645 }
00646 
00647 

Generated on Sat Nov 5 16:16:01 2005 for OPIE by  doxygen 1.4.2