2025-04-27 07:49:33 -04:00

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;
}