166 lines
6.5 KiB
C
166 lines
6.5 KiB
C
/************************ moth\src\scratchout.c ****************************\
|
|
* *
|
|
* Function for recognizing scratch-out gesture. *
|
|
* *
|
|
* Originally written by Tom Wick and modified by Greg and later by Petr. *
|
|
* *
|
|
\***************************************************************************/
|
|
|
|
|
|
#include "mothp.h"
|
|
|
|
/////////////////////////////////////////////////////////
|
|
//
|
|
// %%Function: CSingleChar.ScratchOut
|
|
//
|
|
// %%Description:
|
|
// These algorigthms were tested by me (twick) running:
|
|
// > D:\TabletPC\tpg\hwx\Debug\fff2nni.exe -ap \\tpg\reco\test\usa\alltst03.ste d:\s\junk.nni
|
|
//
|
|
// Total count of strokes: 3530495
|
|
// Hit percentage ONE: 596/3530495 = 0.016881%
|
|
// Hit percentage TWO: 511/3530495 = 0.014474%
|
|
// Hit percentage THREE: 2862/3530495 = 0.081065%
|
|
// Hit percentage FOUR: 535/3530495 = 0.015154%
|
|
// Hit percentage FIVE: 135/3530495 = 0.003824%
|
|
//
|
|
// The descriptions of the algorithms I tested here are below. By xDistance I mean total linear absolute distance
|
|
// traveled in the x direction. By xExtent I mean total x span of the bounding rectangle of the stroke. (Same for y).
|
|
//
|
|
// ONE: xDistance > 4 xExtent, yExtent must be < 4/5 xExtent
|
|
// Everything below here must have xExtent > 500, so a tiny stroke won't fire (period, comma)
|
|
// TWO: same as ONE, but with xExtent > 500
|
|
// THREE: Tom + Greg's ((s - a) / (b - s)) > (y / x) algorithm, with a = 96/35, b = 39/7 (this is similar to algorithm 1)
|
|
// FOUR: Tom + Greg's ((s - a) / (b - s)) > (y / x) algorithm, with a = 23/9, b = 67/9 (1/10 at 3, square at 5)
|
|
// FIVE: Tom + Greg's ((s - a) / (b - s)) > (y / x) algorithm, with a = 7/3, b = 29/3 (1/10 at 3, square at 6)
|
|
//
|
|
// Unfortunately, we're actually using "algorithm SIX", which I never ran tests on. It is a softer test than FIVE or FOUR,
|
|
// but a bit more strict than THREE.
|
|
// SIX: Tom + Greg's ((s - a) / (b - s)) > (y / x) algorithm, with a = 31/18, b = 185/18 (1/10 at 2.5, square at 6)
|
|
//
|
|
// Let me explain the algorithm a bit. This allows us to get less strict for flatness of the scratch-out gesture as we get
|
|
// more and more total x distance traveled. You pick two reference points:
|
|
// First, you decide a minimum flatness you want to enforce. We typically chose yExtent/xExtent of 1/10, because it's
|
|
// tough to get much flatter than that. Then you decide the ratio of xDistance to xExtent you want to fire as a
|
|
// scratch out at this flatness.
|
|
// (For THREE and FOUR and FIVE, we chose 3 (xDistance/xExtent), so you'd have to draw (at least) a flat Z to get
|
|
// these to fire. For SIX, we chose 2.5, so you'd have to only cross the extent of the stroke 2.5 times, not a full 3.)
|
|
// Second, you decide a maximum flatness you want to enforce. Mostly it makes sense (and easier math) to make this
|
|
// yExtent/xExtent of 1, so the stroke is square. Then you decide the ratio of xDistance to xExtent you want to
|
|
// fire as a scratch out at this flatness.
|
|
// (For THREE, we chose yExtent/xExtent of 4/5 at xDistance/xExtent = 4, because ONE and TWO were our model.
|
|
// For FOUR, we chose yExtent/xExtent of 1 at xDistance/xExtent = 5.
|
|
// For FIVE and for SIX, we chose yExtent/xExtent of 1 at xDistance/xExtent = 6.)
|
|
//
|
|
// So now you need to solve two equations for what we called a and b. Here s means (xDistance / xExtent).
|
|
// The equations are yExtent/xExtent = (s - a) / (b - s).
|
|
//
|
|
// The stroke is recognized as a scratch out if:
|
|
// b is less or equal to s (this happens if you _really_ blacken an area with ink)
|
|
// OR
|
|
// (s - a) / (b - s) > yExtent / xExtent
|
|
//
|
|
//
|
|
/////////////////////////////////////////////////////////
|
|
|
|
int
|
|
ScratchoutGestureReco(POINT *pPts, // I: Array of points (single stroke)
|
|
int cPts, // I: Number of points in stroke
|
|
GEST_ALTERNATE *pGestAlt, // O: Array to contain alternates
|
|
int cMaxAlts, // I: Max alternates to return
|
|
DWORD *pdwEnabledGestures) // I: Currently enabled gestures
|
|
{
|
|
LONG xMin, xMax, xExtent, xDistance = 0, xCur, xPrev;
|
|
LONG yMin, yMax, yExtent, yDistance = 0, yCur, yPrev;
|
|
INT iPoint;
|
|
int cAlts = 0;
|
|
double a, b, r, s, fx;
|
|
|
|
/* If there's no room in the alt list, we're done. */
|
|
|
|
if (cMaxAlts <= 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Calculate the x and y extents of the stroke.
|
|
// Calculate the x and y distances traveled by the stroke.
|
|
|
|
xMin = xMax = xPrev = pPts[0].x;
|
|
yMin = yMax = yPrev = pPts[0].y;
|
|
|
|
for (iPoint = 1; iPoint < cPts; iPoint++)
|
|
{
|
|
xCur = pPts[iPoint].x;
|
|
yCur = pPts[iPoint].y;
|
|
xMin = min(xMin, xCur);
|
|
xMax = max(xMax, xCur);
|
|
yMin = min(yMin, yCur);
|
|
yMax = max(yMax, yCur);
|
|
xDistance += abs(xCur - xPrev);
|
|
yDistance += abs(yCur - yPrev);
|
|
xPrev = xCur;
|
|
yPrev = yCur;
|
|
}
|
|
|
|
xExtent = (xMax - xMin);
|
|
yExtent = (yMax - yMin);
|
|
|
|
|
|
if (5 * yDistance > xDistance) // Petr added this to emphasize that the
|
|
{ // distance traveled in the x-direction
|
|
return 0; // must be significantly bigger than the
|
|
} // distance traveled in the y-direction
|
|
|
|
if (xExtent <= 0) // This could happen only if xExtent = yExtent = 0;
|
|
{ // i.e. the stroke is an array of identical points
|
|
return 0; // which is already taken care of by TAP recognition.
|
|
} // So this check is really not needed (but makes PREFIX happy)
|
|
|
|
r = (double) yExtent / xExtent; // Aspect ratio
|
|
s = (double) xDistance / xExtent; // Number of times we went back and forth
|
|
|
|
// ALGORITHM THREE: Tom + Greg's: a = 96/35, b = 39/7
|
|
|
|
// ALGORITHM FOUR: Tom + Greg's: a = 23/9, b = 67/9 (square at 5)
|
|
|
|
// ALGORITHM FIVE: Tom + Greg's: a = 7/3, b = 29/3
|
|
|
|
// ALGORITHM SIX: Tom + Greg's: a = 31/18, b = 185/18
|
|
|
|
// Algorithm SEVEN: a = 2, b = 10
|
|
|
|
// Each algorithm requires "a" passes at zero height and "(a+b)/2" passes
|
|
// when the aspect ratio of the bounding box is 1:1. If s >= b, pretty
|
|
// much anything will work.
|
|
|
|
a = (double)2;
|
|
b = (double)10;
|
|
if (b > s)
|
|
{
|
|
fx = ((s - a) / (b - s));
|
|
if (fx <= r)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We recognized it; add it to the alt list
|
|
// If SCRATCHOUT isn't enabled, return gesture NULL
|
|
|
|
if (IsSet(GESTURE_SCRATCHOUT-GESTURE_NULL, pdwEnabledGestures))
|
|
{
|
|
pGestAlt->wcGestID = GESTURE_SCRATCHOUT;
|
|
}
|
|
else
|
|
{
|
|
pGestAlt->wcGestID = GESTURE_NULL;
|
|
}
|
|
pGestAlt->eScore = 1.0;
|
|
pGestAlt->confidence = CFL_STRONG;
|
|
pGestAlt->hotPoint = pPts[0]; // No hotpoint for this one, of course
|
|
|
|
return 1;
|
|
}
|