mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1576 lines
42 KiB
1576 lines
42 KiB
#include "crane.h"
|
|
#include "algo.h" // Must include first to get new definitions
|
|
#include "common.h"
|
|
#include "cranep.h"
|
|
|
|
QHEAD *gapqhList[30];
|
|
QNODE *gapqnList[30];
|
|
|
|
#define FRAME_MAXSTEPS 128
|
|
|
|
// Index of the four points that make up the bounds of the line in a self relitive
|
|
// bounding box. We store the index of each point and its offset.
|
|
|
|
typedef struct BOUNDS {
|
|
int dX; // Delta X and Y of line segment
|
|
int dY;
|
|
int iAlongPlus; // Along the line in plus direction
|
|
int oAlongPlus;
|
|
int iAlongMinus; // Along to the line in minus direction
|
|
int oAlongMinus;
|
|
int iAwayPlus; // Away from the line in plus direction
|
|
int oAwayPlus;
|
|
int iAwayMinus; // Away from the line in minus direction
|
|
int oAwayMinus;
|
|
} BOUNDS;
|
|
|
|
// Map simple features into index used for two feature map table
|
|
|
|
static const aMapFeat[] =
|
|
{
|
|
0, 1, 2, 3, 4, // FEAT_RIGHT, FEAT_DOWN_RIGHT, FEAT_DOWN, FEAT_OTHER, FEAT_COMPLEX
|
|
5, 5, 5, 5, // FEAT_CLOCKWISE_4, FEAT_CLOCKWISE_3, FEAT_CLOCKWISE_2, FEAT_CLOCKWISE_1
|
|
6, 6, 6, 6, // FEAT_C_CLOCKWISE_1, FEAT_C_CLOCKWISE_2, FEAT_C_CLOCKWISE_3, FEAT_C_CLOCKWISE_4
|
|
};
|
|
|
|
// Map two feature features into correct feature codes
|
|
|
|
static const aMapTwoFeat[7][7] =
|
|
{
|
|
// Right Down-Right Down Other Complex Clockwise Counter-Clockwise
|
|
{ FEAT_2F_OTHER, FEAT_2F_OTHER, FEAT_2F_OTHER, FEAT_2F_OTHER, FEAT_COMPLEX, FEAT_2F_RI_CW, FEAT_2F_RI_CC }, // Right
|
|
{ FEAT_2F_OTHER, FEAT_2F_OTHER, FEAT_2F_OTHER, FEAT_2F_OTHER, FEAT_COMPLEX, FEAT_2F_DR_CW, FEAT_2F_DR_CC }, // Down-Right
|
|
{ FEAT_2F_OTHER, FEAT_2F_OTHER, FEAT_2F_OTHER, FEAT_2F_OTHER, FEAT_COMPLEX, FEAT_2F_DN_CW, FEAT_2F_DN_CC }, // Down
|
|
{ FEAT_2F_OTHER, FEAT_2F_OTHER, FEAT_2F_OTHER, FEAT_2F_OTHER, FEAT_COMPLEX, FEAT_2F_OT_CW, FEAT_2F_OT_CC }, // Other
|
|
{ FEAT_COMPLEX, FEAT_COMPLEX, FEAT_COMPLEX, FEAT_COMPLEX, FEAT_COMPLEX, FEAT_COMPLEX, FEAT_COMPLEX }, // Complex
|
|
{ FEAT_2F_CW_RI, FEAT_2F_CW_DR, FEAT_2F_CW_DN, FEAT_2F_CW_OT, FEAT_COMPLEX, FEAT_2F_CW_CW, FEAT_2F_CW_CC }, // Clockwise
|
|
{ FEAT_2F_OTHER, FEAT_2F_CC_DR, FEAT_2F_CC_DN, FEAT_2F_CC_OT, FEAT_COMPLEX, FEAT_2F_CC_CW, FEAT_2F_CC_CC }, // C-Clockwise
|
|
};
|
|
|
|
// Find the points that define the bounding box reletive to the line between
|
|
// the start and end points.
|
|
|
|
void FindBounds(int cXYStart, int cXYEnd, XY *pXY, BOUNDS *pBounds)
|
|
{
|
|
int iXY;
|
|
int dX, dY;
|
|
BOUNDS initial; // Store initial values for later normalization
|
|
|
|
ASSERT(cXYStart <= cXYEnd);
|
|
|
|
// Get delta X and delta Y of (end - start) for our calculations.
|
|
|
|
dX = pXY[cXYEnd].x - pXY[cXYStart].x;
|
|
dY = pXY[cXYEnd].y - pXY[cXYStart].y;
|
|
|
|
if (dX == 0 && dY == 0)
|
|
{
|
|
dX = 1; // If both dX and dY are 0, force dX to be 1 so we have a real line
|
|
}
|
|
|
|
// Set initial limits.
|
|
|
|
initial.dX = dX;
|
|
initial.dY = dY;
|
|
initial.iAlongPlus = cXYEnd;
|
|
initial.oAlongPlus = pXY[cXYEnd].y * dY + pXY[cXYEnd].x * dX;
|
|
initial.iAlongMinus = cXYStart;
|
|
initial.oAlongMinus = pXY[cXYStart].y * dY + pXY[cXYStart].x * dX;
|
|
initial.iAwayPlus = cXYStart;
|
|
initial.oAwayPlus = pXY[cXYStart].y * dX - pXY[cXYStart].x * dY;
|
|
initial.iAwayMinus = cXYStart;
|
|
initial.oAwayMinus = initial.oAwayPlus;
|
|
|
|
*pBounds = initial;
|
|
|
|
// Now process all the other points to see how they fall relitive to the line between
|
|
// the start and the end.
|
|
|
|
for (iXY = cXYStart + 1; iXY < cXYEnd; ++iXY)
|
|
{
|
|
int along, away;
|
|
|
|
// First calculate the along and away offset.
|
|
|
|
along = pXY[iXY].y * dY + pXY[iXY].x * dX;
|
|
away = pXY[iXY].y * dX - pXY[iXY].x * dY;
|
|
|
|
// Now see how it compares to the current limits.
|
|
|
|
if (along > pBounds->oAlongPlus)
|
|
{
|
|
pBounds->oAlongPlus = along;
|
|
pBounds->iAlongPlus = iXY;
|
|
}
|
|
else if (along < pBounds->oAlongMinus)
|
|
{
|
|
pBounds->oAlongMinus = along;
|
|
pBounds->iAlongMinus = iXY;
|
|
}
|
|
|
|
if (away > pBounds->oAwayPlus)
|
|
{
|
|
pBounds->oAwayPlus = away;
|
|
pBounds->iAwayPlus = iXY;
|
|
}
|
|
else if (away < pBounds->oAwayMinus)
|
|
{
|
|
pBounds->oAwayMinus = away;
|
|
pBounds->iAwayMinus = iXY;
|
|
}
|
|
}
|
|
|
|
// Finally normalize the offsets.
|
|
|
|
pBounds->oAlongPlus -= initial.oAlongPlus;
|
|
pBounds->oAlongMinus -= initial.oAlongMinus;
|
|
pBounds->oAwayPlus -= initial.oAwayPlus;
|
|
pBounds->oAwayMinus -= initial.oAwayMinus;
|
|
|
|
}
|
|
|
|
// Recursively step line segements
|
|
// iRecursion is available for debug/tunning use, and not otherwise used.
|
|
|
|
void StepLineSegment(int cXYStart, int cXYEnd, XY *pXY, int *pCStep, STEP *rgStep, int iRecursion)
|
|
{
|
|
BOUNDS bounds;
|
|
int points[6];
|
|
int ii;
|
|
int normalize;
|
|
int ratio;
|
|
|
|
// Check for 1 or two point lines.
|
|
|
|
if (cXYEnd - cXYStart < 2)
|
|
{
|
|
int dX, dY;
|
|
int deltaAngle;
|
|
|
|
ASSERT(cXYEnd >= cXYStart);
|
|
|
|
if (cXYEnd == cXYStart)
|
|
{
|
|
ASSERT(cXYStart == 0); // Only should be passed one point if stroke is one point.
|
|
ASSERT(*pCStep == 0);
|
|
|
|
// We have to fake a feature for one point.
|
|
|
|
rgStep[*pCStep].length = 0;
|
|
rgStep[*pCStep].angle = 0;
|
|
ANGLEDIFF(0, rgStep[*pCStep].angle, deltaAngle);
|
|
rgStep[*pCStep].deltaAngle = (short)deltaAngle; // Delta from 0.
|
|
|
|
|
|
if (*pCStep < FRAME_MAXSTEPS - 1)
|
|
{
|
|
++*pCStep;
|
|
}
|
|
else
|
|
{
|
|
//ASSERT(*pCStep < FRAME_MAXSTEPS - 1);
|
|
}
|
|
|
|
rgStep[*pCStep].x = pXY[cXYEnd].x;
|
|
rgStep[*pCStep].y = pXY[cXYEnd].y;
|
|
|
|
return;
|
|
}
|
|
|
|
// Two points always form a line (as long as they arn't the same point).
|
|
// So go ahead and add it. Calculate slope times 10 and the direction of
|
|
// segment. Also length and angle
|
|
|
|
dX = pXY[cXYEnd].x - pXY[cXYStart].x;
|
|
dY = pXY[cXYEnd].y - pXY[cXYStart].y;
|
|
rgStep[*pCStep].length = (short)Distance(dX, dY);
|
|
rgStep[*pCStep].angle = (short)Arctan2(dY, dX);
|
|
if (*pCStep == 0)
|
|
{
|
|
ANGLEDIFF(0, rgStep[*pCStep].angle, deltaAngle);
|
|
rgStep[*pCStep].deltaAngle = (short)deltaAngle; // Delta from 0.
|
|
}
|
|
else
|
|
{
|
|
ANGLEDIFF(rgStep[*pCStep - 1].angle, rgStep[*pCStep].angle, deltaAngle);
|
|
rgStep[*pCStep].deltaAngle = (short)deltaAngle; // Delta from last angle
|
|
}
|
|
|
|
|
|
if (*pCStep < FRAME_MAXSTEPS - 1)
|
|
{
|
|
++*pCStep;
|
|
}
|
|
else
|
|
{
|
|
//ASSERT(*pCStep < FRAME_MAXSTEPS - 1);
|
|
}
|
|
|
|
rgStep[*pCStep].x = pXY[cXYEnd].x;
|
|
rgStep[*pCStep].y = pXY[cXYEnd].y;
|
|
|
|
return;
|
|
}
|
|
|
|
// Find initial bounds.
|
|
|
|
FindBounds(cXYStart, cXYEnd, pXY, &bounds);
|
|
|
|
// Figure out which points to use as boundries. Build them up in an array.
|
|
|
|
points[0] = cXYStart;
|
|
ii = 1;
|
|
|
|
// Start with 'along' points. We assume any extent in the 'along' direction
|
|
// requires new points.
|
|
|
|
if (bounds.iAlongMinus != cXYStart)
|
|
{
|
|
points[ii++] = bounds.iAlongMinus;
|
|
}
|
|
if (bounds.iAlongPlus != cXYEnd)
|
|
{
|
|
points[ii++] = bounds.iAlongPlus;
|
|
}
|
|
|
|
// Now the 'away' points. These require a minimum ratio to be selected.
|
|
// Since the ration is usually a fraction, and we want to handle integers,
|
|
// multiply by 1000.
|
|
|
|
normalize = bounds.dX * bounds.dX + bounds.dY * bounds.dY;
|
|
normalize = max(1,normalize);
|
|
ratio = (bounds.oAwayPlus * 1000) / normalize;
|
|
if (ratio >= 180) // Ratio > ??
|
|
{
|
|
points[ii++] = bounds.iAwayPlus;
|
|
}
|
|
|
|
ratio = -(bounds.oAwayMinus * 1000) / normalize;
|
|
if (ratio >= 180) // Ratio > ??
|
|
{
|
|
points[ii++] = bounds.iAwayMinus;
|
|
}
|
|
|
|
// See if we need to recurse or not.
|
|
|
|
if (ii == 1)
|
|
{
|
|
int deltaAngle;
|
|
|
|
// We have a straight line. Add the end point, and we are done with this path down.
|
|
// Calculate slope times 10 and the direction of segment
|
|
|
|
rgStep[*pCStep].length = (short)Distance(bounds.dX, bounds.dY);
|
|
rgStep[*pCStep].angle = (short)Arctan2(bounds.dY, bounds.dX);
|
|
if (*pCStep == 0)
|
|
{
|
|
ANGLEDIFF(0, rgStep[*pCStep].angle, deltaAngle);
|
|
rgStep[*pCStep].deltaAngle = (short)deltaAngle; // Delta from 0.
|
|
}
|
|
else
|
|
{
|
|
ANGLEDIFF(rgStep[*pCStep - 1].angle, rgStep[*pCStep].angle, deltaAngle);
|
|
rgStep[*pCStep].deltaAngle = (short)deltaAngle; // Delta from last angle
|
|
}
|
|
|
|
if (*pCStep < FRAME_MAXSTEPS - 1)
|
|
{
|
|
++*pCStep;
|
|
}
|
|
else
|
|
{
|
|
//ASSERT(*pCStep < FRAME_MAXSTEPS - 1);
|
|
}
|
|
|
|
rgStep[*pCStep].x = pXY[cXYEnd].x;
|
|
rgStep[*pCStep].y = pXY[cXYEnd].y;
|
|
}
|
|
else
|
|
{
|
|
int jj;
|
|
|
|
// Have at least two sub-segments, process them. First sort into index order.
|
|
// Note that start point is already in the correct position.
|
|
|
|
//
|
|
// privsort(points + 1, ii - 1);
|
|
//
|
|
// The following code replaces the call to privsort because
|
|
// it's such a tiny array (5 or less I think) and privsort is so big.
|
|
//
|
|
|
|
{
|
|
int bSort;
|
|
|
|
do
|
|
{
|
|
bSort = FALSE;
|
|
|
|
for (jj = 1; jj < ii - 1; jj++)
|
|
{
|
|
if (points[jj] > points[jj + 1])
|
|
{
|
|
int tmp;
|
|
|
|
tmp = points[jj];
|
|
points[jj] = points[jj + 1];
|
|
points[jj + 1] = tmp;
|
|
bSort = TRUE;
|
|
}
|
|
}
|
|
|
|
} while (bSort);
|
|
}
|
|
|
|
// Add end point, it will also be automatically in the correct position.
|
|
|
|
points[ii] = cXYEnd;
|
|
|
|
// Sequence through each adjacent pair of point to process the segments.
|
|
|
|
for (jj = 0; jj < ii; ++jj)
|
|
{
|
|
// We can have duplicate points, ignore them.
|
|
if (points[jj] != points[jj + 1])
|
|
{
|
|
StepLineSegment(points[jj], points[jj + 1], pXY, pCStep, rgStep, iRecursion + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the 'splash' of random points sometimes caused by pen down or pen up.
|
|
|
|
UINT RemovePenNoise(XY *rgxy, UINT cxy, BOOL fBegin, UINT ixyStop)
|
|
{
|
|
UINT ixy;
|
|
int dx, dy;
|
|
BOOL fSmall;
|
|
UINT ixyEnd;
|
|
|
|
ixyEnd = cxy - 1;
|
|
|
|
// Limit splash zone if the stroke is smaller than 4 times the normal splash zone.
|
|
|
|
dx = rgxy[0].x - rgxy[ixyEnd].x;
|
|
dy = rgxy[0].y - rgxy[ixyEnd].y;
|
|
dx /= 4;
|
|
dy /= 4;
|
|
fSmall = LineInSplash(dx, dy) ? 1 : 0;
|
|
|
|
if (fBegin)
|
|
{
|
|
for (ixy = 0; ixy < ixyStop; ixy++)
|
|
{
|
|
dx = rgxy[ixy].x - rgxy[ixy + 1].x;
|
|
dy = rgxy[ixy].y - rgxy[ixy + 1].y;
|
|
if (!LineSmall(dx, dy))
|
|
{
|
|
// Next jump is too big to just remove, check distance from start point.
|
|
|
|
dx = rgxy[0].x - rgxy[ixy + 1].x;
|
|
dy = rgxy[0].y - rgxy[ixy + 1].y;
|
|
if (fSmall || !LineInSplash(dx, dy))
|
|
{
|
|
// Doesn't fall in splash zone.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (ixy = ixyEnd; ixy > ixyStop; ixy--)
|
|
{
|
|
dx = rgxy[ixy].x - rgxy[ixy - 1].x;
|
|
dy = rgxy[ixy].y - rgxy[ixy - 1].y;
|
|
if (!LineSmall(dx, dy))
|
|
{
|
|
// Next jump is too big to just remove, check distance from end point.
|
|
|
|
dx = rgxy[ixyEnd].x - rgxy[ixy - 1].x;
|
|
dy = rgxy[ixyEnd].y - rgxy[ixy - 1].y;
|
|
if (fSmall || !LineInSplash(dx, dy))
|
|
{
|
|
// Doesn't fall in splash zone.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return(ixy);
|
|
}
|
|
|
|
UINT DebounceStrokePoints(XY *rgxy, UINT cxy)
|
|
{
|
|
UINT iBounceBegin, iBounceEnd;
|
|
|
|
// Verify that we have enough points that we can debounce.
|
|
if (cxy < 3) {
|
|
return(cxy);
|
|
}
|
|
|
|
// Remove pen noise from pen-down and pen-up
|
|
iBounceBegin = RemovePenNoise(rgxy, cxy, TRUE, cxy - 2);
|
|
iBounceEnd = RemovePenNoise(rgxy, cxy, FALSE, iBounceBegin + 1);
|
|
|
|
if (iBounceBegin > 0 || iBounceEnd < cxy - 1)
|
|
{
|
|
ASSERT(iBounceBegin < iBounceEnd);
|
|
ASSERT(iBounceEnd < cxy);
|
|
|
|
cxy = iBounceEnd - iBounceBegin + 1;
|
|
memmove((VOID *)rgxy, (VOID *)(&rgxy[iBounceBegin]), sizeof(XY) * cxy);
|
|
}
|
|
|
|
return(cxy);
|
|
}
|
|
|
|
int StepsFromFRAME(FRAME *frame, STEP *rgStep, int cstepmax)
|
|
{
|
|
int cStep;
|
|
int cXY;
|
|
XY *pXY;
|
|
|
|
// Get pointer to data and count of points
|
|
|
|
cXY = frame->info.cPnt;
|
|
pXY = frame->rgrawxy;
|
|
|
|
// If a previous recognizer used this buffer, release it first
|
|
|
|
if (frame->rgsmoothxy)
|
|
ExternFree(frame->rgsmoothxy);
|
|
|
|
// Smoothing has been replaced by de-splashing. JRB: Clean up to call de-splash directly.
|
|
|
|
frame->rgsmoothxy = (XY *) ExternAlloc(cXY * sizeof(XY));
|
|
if (frame->rgsmoothxy == (XY *) NULL)
|
|
return 0;
|
|
|
|
memcpy(frame->rgsmoothxy, pXY, cXY * sizeof(XY));
|
|
frame->csmoothxy = cXY;
|
|
frame->csmoothxy = DebounceStrokePoints(frame->rgsmoothxy, frame->csmoothxy);
|
|
cXY = frame->csmoothxy;
|
|
pXY = frame->rgsmoothxy;
|
|
|
|
// Recursivly segment the line.
|
|
|
|
cStep = 0;
|
|
rgStep[0].x = pXY[0].x;
|
|
rgStep[0].y = pXY[0].y;
|
|
StepLineSegment(0, cXY - 1, pXY, &cStep, rgStep, 0);
|
|
|
|
return(cStep);
|
|
}
|
|
|
|
// Convert an angle into a feature.
|
|
|
|
BYTE Code(int angle)
|
|
{
|
|
if (angle < 12)
|
|
return FEAT_RIGHT;
|
|
else if (angle < 70)
|
|
return FEAT_DOWN_RIGHT;
|
|
else if (angle < 160)
|
|
return FEAT_DOWN;
|
|
else if (angle < 300)
|
|
return FEAT_OTHER; // very rare directions
|
|
else
|
|
return FEAT_RIGHT;
|
|
}
|
|
|
|
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
// We set this to 0xFF, this should be changed if the number of valid codes is excpected
|
|
// to be more than 255
|
|
#define FEATURE_NULL 0xFF // avoid any valid code.
|
|
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
|
|
// Convert steps into first pass at features.
|
|
// Note that cFrame is not used.
|
|
// JRB: Rewrite so that we just create one feature, instead of creating many and undoing it!!!
|
|
|
|
VOID AddStepsKPROTO(KPROTO *pKProto, STEP *rgStep, int cStep, int cFrame, FRAME *frame)
|
|
{
|
|
int iStep; // Step being processed
|
|
int cFeat; // Count of features so far in character
|
|
int iStartStep; // Start of current feature.
|
|
int iEndStep; // Current quess at end of step.
|
|
int cumAngle; // Cumulitive change in angle for the current feature.
|
|
int netAngle; // Net angle traversed by stroke (sum of step angles)
|
|
int absAngle; // Total angle of stroke (sum of abs. val. of step angles)
|
|
int nLength; // Total length of stroke
|
|
int x, y;
|
|
PRIMITIVE *pFeat = NULL;
|
|
|
|
ASSERT(cStep > 0);
|
|
|
|
// Spot to add next feature.
|
|
|
|
cFeat = pKProto->cfeat;
|
|
pFeat = (PRIMITIVE *)0;
|
|
|
|
// Loop over all the steps finding features and gathering information about them.
|
|
|
|
iStartStep = 0;
|
|
iEndStep = 0;
|
|
x = rgStep[0].x;
|
|
y = rgStep[0].y;
|
|
netAngle = 0;
|
|
absAngle = 0;
|
|
nLength = 0;
|
|
|
|
for (iStep = 0; iStep < cStep; iStep++)
|
|
{
|
|
// Handle start of a feature.
|
|
|
|
if (iStep == iEndStep)
|
|
{
|
|
// Get pointer to feature structure.
|
|
|
|
pFeat = &(pKProto->rgfeat[cFeat]);
|
|
|
|
// The array of primitives is CPRIMMAX in size so
|
|
// the largest index it can handle is (CPRIMMAX-1).
|
|
|
|
if (cFeat < (CPRIMMAX - 1))
|
|
{
|
|
cFeat++;
|
|
}
|
|
else
|
|
{
|
|
//ASSERT(cFeat < (CPRIMMAX - 1));
|
|
}
|
|
|
|
// Record start of feature as initial point or end of last feature.
|
|
|
|
pFeat->x1 = (short)x;
|
|
pFeat->y1 = (short)y;
|
|
|
|
// Feature not known yet
|
|
|
|
pFeat->code = FEATURE_NULL;
|
|
|
|
// Default to making feature the rest of the line.
|
|
|
|
iStartStep = iStep;
|
|
iEndStep = cStep;
|
|
cumAngle = 0;
|
|
}
|
|
|
|
ASSERT(pFeat);
|
|
|
|
// End of current step
|
|
|
|
x = rgStep[iStep + 1].x;
|
|
y = rgStep[iStep + 1].y;
|
|
|
|
// Add the step length to the total length
|
|
|
|
nLength += Distance(x - rgStep[iStep].x, y - rgStep[iStep].y);
|
|
|
|
// If not first step in this feature, add angle from last step.
|
|
|
|
if (iStep > iStartStep)
|
|
{
|
|
cumAngle += rgStep[iStep].deltaAngle;
|
|
}
|
|
|
|
// If not first step, add angle from last step.
|
|
|
|
if (iStep > 0)
|
|
{
|
|
netAngle += rgStep[iStep].deltaAngle;
|
|
if (rgStep[iStep].deltaAngle >= 0)
|
|
{
|
|
absAngle += rgStep[iStep].deltaAngle;
|
|
}
|
|
else
|
|
{
|
|
absAngle -= rgStep[iStep].deltaAngle;
|
|
}
|
|
}
|
|
|
|
// Decide if we need to split and start a new step. Can only be an inflection
|
|
// if there are at least three segements left. Also if we are already breaking after
|
|
// this step, we shouldn't check until we are past the break.
|
|
|
|
if (cStep - iStep >= 3 && iEndStep != iStep + 1)
|
|
{
|
|
int angle1_2, angle2_3;
|
|
|
|
// Figure angles between first & second segments and second & third segments.
|
|
|
|
angle1_2 = rgStep[iStep + 1].deltaAngle;
|
|
angle2_3 = rgStep[iStep + 2].deltaAngle;
|
|
|
|
// Should never have two consecutive steps with exactly the same angle.
|
|
|
|
// ASSERT(angle1_2 != 0);
|
|
// ASSERT(angle2_3 != 0);
|
|
|
|
// Are they in different directions? E.g. are signs different.
|
|
// JRB: What about 180 or angles close to it?
|
|
|
|
if (angle1_2 * angle2_3 < 0)
|
|
{
|
|
int abs1_2, abs2_3;
|
|
|
|
// We have an inflection, which side do we break on? First get absolute
|
|
// value of both angles.
|
|
|
|
if (angle1_2 < 0)
|
|
{
|
|
abs1_2 = -angle1_2;
|
|
abs2_3 = angle2_3;
|
|
}
|
|
else
|
|
{
|
|
abs1_2 = angle1_2;
|
|
abs2_3 = -angle2_3;
|
|
}
|
|
|
|
// Now which is larger (e.g. tighter turn)
|
|
|
|
if (abs1_2 >= abs2_3)
|
|
{
|
|
iEndStep = iStep + 1; // First is tighter, break there.
|
|
}
|
|
else
|
|
{
|
|
iEndStep = iStep + 2; // Second is tighter, break there.
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Handle end of a feature.
|
|
|
|
if (iStep == iEndStep - 1)
|
|
{
|
|
int dX, dY;
|
|
|
|
// Record end of feature.
|
|
|
|
pFeat->x2 = (short)x;
|
|
pFeat->y2 = (short)y;
|
|
|
|
// Figure out feature code.
|
|
|
|
if (cumAngle == 0)
|
|
{
|
|
pFeat->code = Code(rgStep[iStep].angle); // Streight line
|
|
}
|
|
else if (cumAngle > 0)
|
|
{
|
|
// Clockwise, how strong?
|
|
|
|
if (cumAngle >= 390)
|
|
{
|
|
pFeat->code = FEAT_COMPLEX;
|
|
}
|
|
else if (cumAngle >= 300)
|
|
{
|
|
pFeat->code = FEAT_CLOCKWISE_4;
|
|
}
|
|
else if (cumAngle >= 182)
|
|
{
|
|
pFeat->code = FEAT_CLOCKWISE_3;
|
|
}
|
|
else if (cumAngle >= 120)
|
|
{
|
|
pFeat->code = FEAT_CLOCKWISE_2;
|
|
}
|
|
else if (cumAngle >= 38)
|
|
{
|
|
pFeat->code = FEAT_CLOCKWISE_1;
|
|
}
|
|
else
|
|
{
|
|
// Close enough to streight.
|
|
dX = pFeat->x2 - pFeat->x1;
|
|
dY = pFeat->y2 - pFeat->y1;
|
|
pFeat->code = Code(Arctan2(dY, dX));
|
|
}
|
|
}
|
|
else // cumAngle < 0
|
|
{
|
|
// Counter clockwise, how strong?
|
|
if (cumAngle <= -360)
|
|
{
|
|
pFeat->code = FEAT_COMPLEX;
|
|
}
|
|
else if (cumAngle <= -182)
|
|
{
|
|
pFeat->code = FEAT_C_CLOCKWISE_4;
|
|
}
|
|
else if (cumAngle <= -115)
|
|
{
|
|
pFeat->code = FEAT_C_CLOCKWISE_3;
|
|
}
|
|
else if (cumAngle <= -60)
|
|
{
|
|
pFeat->code = FEAT_C_CLOCKWISE_2;
|
|
}
|
|
else if (cumAngle <= -38)
|
|
{
|
|
pFeat->code = FEAT_C_CLOCKWISE_1;
|
|
}
|
|
else
|
|
{
|
|
// Close enough to streight.
|
|
dX = pFeat->x2 - pFeat->x1;
|
|
dY = pFeat->y2 - pFeat->y1;
|
|
pFeat->code = Code(Arctan2(dY, dX));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT(pFeat->code != FEATURE_NULL);
|
|
|
|
// Map multi-feature strokes down to a single feature.
|
|
|
|
pFeat = &(pKProto->rgfeat[pKProto->cfeat]);
|
|
pFeat->cFeatures = cFeat - pKProto->cfeat;
|
|
pFeat->cSteps = (BYTE)cStep;
|
|
pFeat->fDakuTen = 0;
|
|
pFeat->nLength = nLength;
|
|
pFeat->netAngle = netAngle;
|
|
pFeat->absAngle = absAngle;
|
|
pFeat->boundingBox = *RectFRAME(frame);
|
|
switch (pFeat->cFeatures) {
|
|
case 0 : case 1 :
|
|
// Zero or one feature. Zero should not actually happen, but if it does ...
|
|
goto hadSingle;
|
|
|
|
case 2 : {
|
|
BYTE feat0, feat1;
|
|
|
|
// Two features, look up what the replacement feature is.
|
|
feat0 = pFeat[0].code;
|
|
feat1 = pFeat[1].code;
|
|
pFeat->code = (BYTE)aMapTwoFeat[aMapFeat[feat0]][aMapFeat[feat1]];
|
|
break;
|
|
}
|
|
|
|
case 3 : {
|
|
BYTE feat0, feat1, feat2;
|
|
|
|
// A few special cases, otherwise call it complex.
|
|
feat0 = pFeat[0].code;
|
|
feat1 = pFeat[1].code;
|
|
feat2 = pFeat[2].code;
|
|
switch (aMapFeat[feat0]) {
|
|
case 0 : // Right
|
|
switch (aMapFeat[feat1]) {
|
|
case 3 : // Down
|
|
pFeat->code = (aMapFeat[feat2] == 5)
|
|
? FEAT_3F_RI_DN_CW : FEAT_COMPLEX; // Clockwise
|
|
break;
|
|
|
|
case 5 : // Clockwise
|
|
pFeat->code = (feat2 == FEAT_DOWN)
|
|
? FEAT_3F_RI_CW_DN : FEAT_COMPLEX; // Down
|
|
break;
|
|
|
|
case 6 : // Counter clockwise
|
|
pFeat->code = (feat2 == FEAT_DOWN) // Down
|
|
? FEAT_3F_RI_CC_DN
|
|
: (aMapFeat[feat2] == 5)
|
|
? FEAT_3F_RI_CC_CW : FEAT_COMPLEX; // Clockwise
|
|
break;
|
|
|
|
default :
|
|
pFeat->code = FEAT_COMPLEX;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 2 : // Down
|
|
if (feat1 == FEAT_RIGHT) { // Right
|
|
// Counter clockwise
|
|
pFeat->code = (aMapFeat[feat2] == 6)
|
|
? FEAT_3F_DN_RI_CC : FEAT_COMPLEX;
|
|
} else if (aMapFeat[feat1] == 5) { // Clockwise
|
|
switch (aMapFeat[feat2]) {
|
|
case 0 : pFeat->code = FEAT_3F_DN_CW_RI; break; // Right
|
|
case 5 : pFeat->code = FEAT_3F_DN_CW_CW; break; // Clockwise
|
|
case 6 : pFeat->code = FEAT_3F_DN_CW_CC; break; // Counter clockwise
|
|
default : pFeat->code = FEAT_COMPLEX; break;
|
|
}
|
|
} else {
|
|
pFeat->code = FEAT_COMPLEX;
|
|
}
|
|
break;
|
|
|
|
case 5 : // Clockwise
|
|
// Any and Clockwise
|
|
pFeat->code = (aMapFeat[feat2] == 5)
|
|
? FEAT_3F_CW_XX_CW : FEAT_COMPLEX;
|
|
break;
|
|
|
|
case 6 : // Counter Clockwise
|
|
// Clockwise and Right
|
|
pFeat->code = (aMapFeat[feat1] == 5 && feat2 == FEAT_RIGHT)
|
|
? FEAT_3F_CC_CW_RI : FEAT_COMPLEX;
|
|
break;
|
|
|
|
default:
|
|
pFeat->code = FEAT_COMPLEX;
|
|
}
|
|
}
|
|
|
|
default:
|
|
// Anything else (> 3) is always complex.
|
|
pKProto->rgfeat[cFeat].code = FEAT_COMPLEX;
|
|
break;
|
|
}
|
|
|
|
// Actually merge the position information and fix cFeat.
|
|
pFeat->x2 = pKProto->rgfeat[cFeat - 1].x2;
|
|
pFeat->y2 = pKProto->rgfeat[cFeat - 1].y2;
|
|
cFeat = pKProto->cfeat + 1;
|
|
|
|
hadSingle: // Jump here to skip merging of features.
|
|
|
|
// Record number of features after our additions.
|
|
pKProto->cfeat = (WORD)cFeat;
|
|
}
|
|
|
|
// Clean up the stepped stroke. cFrame is not used.
|
|
|
|
int CraneSmoothSteps(STEP *rgStep, int cStep, int cFrame)
|
|
{
|
|
int ii;
|
|
int cRemove;
|
|
int totalLength, hookLength;
|
|
int maxLength, maxSegment;
|
|
int length;
|
|
int lengths[6]; // First three and last three segment lengths. JRB: already calculated
|
|
|
|
// Make sure we have enough segments to think about removing some.
|
|
|
|
if (cStep < 2)
|
|
{
|
|
return cStep; // Only one segment, don't want to remove it.
|
|
}
|
|
else if (cStep == 2)
|
|
{
|
|
int percentLength;
|
|
int angle;
|
|
int partA, partB;
|
|
int experA, experB;
|
|
|
|
// Special processing for two step strokes. The only thing we may do
|
|
// is drop one of the steps. We calculate the length of the first step
|
|
// as a percent of the total line length. We also calculate the angle
|
|
// between the lines. This gives us a rectangle with the ranges,
|
|
// percentLength 0 - 100 and angle -179 to 180. We want to find if
|
|
// this sample falls in one of the four corners of this that indicates
|
|
// an extranious hook. The four lines (and there equations) we use are:
|
|
// Lower left -180, 40 -> -30, 0 -- angle, percentLength
|
|
// Lower right 180, 40 -> 30, 0
|
|
// Upper left -180, 60 -> -30, 100
|
|
// Upper right 180, 60 -> 30, 100
|
|
// The matching formulas for the areas beyound the lines are:
|
|
// Lower left 4 * angle + 15 * percentLength <= - 120
|
|
// Lower right 4 * angle - 15 * percentLength >= 120
|
|
// Upper left 4 * angle - 15 * percentLength <= -1620
|
|
// Upper right 4 * angle + 15 * percentLength >= 1620
|
|
|
|
totalLength = rgStep[0].length + rgStep[1].length;
|
|
percentLength = (rgStep[0].length * 100) / totalLength;
|
|
angle = rgStep[1].deltaAngle;
|
|
|
|
// Precalculate the pieces.
|
|
|
|
partA = 4 * angle;
|
|
partB = 15 * percentLength;
|
|
experA = partA + partB;
|
|
experB = partA - partB;
|
|
|
|
// Looks OK, keep both segments.
|
|
// Test the two corners that indicate the first segment is two small.
|
|
// Then test the other two, to check the second segment.
|
|
|
|
if (experA <= -120 || experB >= 120)
|
|
{
|
|
// Delete the first segment.
|
|
|
|
rgStep[0] = rgStep[1];
|
|
rgStep[1] = rgStep[2];
|
|
|
|
return 1;
|
|
}
|
|
else if (experB <= -1620 || experA >= 1620)
|
|
{
|
|
// Delete the second segment.
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 2;
|
|
}
|
|
|
|
// Check for hooks.
|
|
|
|
totalLength = 0;
|
|
maxLength = 0;
|
|
maxSegment = -1;
|
|
for (ii = 0; ii < cStep; ++ii)
|
|
{
|
|
int fromEnd;
|
|
|
|
// Figure segment length
|
|
|
|
length = rgStep[ii].length;
|
|
|
|
// Keep track of longest segment.
|
|
|
|
if (maxLength < length)
|
|
{
|
|
maxLength = length;
|
|
maxSegment = ii;
|
|
}
|
|
|
|
// Sum all segment lengths.
|
|
|
|
totalLength += length;
|
|
|
|
// Record the lengths that we will need later.
|
|
|
|
if (ii < 3)
|
|
{
|
|
lengths[ii] = length;
|
|
}
|
|
fromEnd = cStep - 1 - ii;
|
|
if (fromEnd < 3)
|
|
{
|
|
lengths[5 - fromEnd] = length;
|
|
}
|
|
}
|
|
|
|
// Check for a streight line with small curves at one or both ends.
|
|
|
|
ASSERT(maxSegment >= 0);
|
|
if ((maxLength * 100) / totalLength > 80)
|
|
{
|
|
// throw away all other segments.
|
|
|
|
rgStep[0] = rgStep[maxSegment];
|
|
rgStep[1] = rgStep[maxSegment + 1];
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Try to find hook at the end. First try to remove two segments.
|
|
|
|
cRemove = 0;
|
|
if (cStep >= 3)
|
|
{
|
|
hookLength = lengths[5] + lengths[4];
|
|
|
|
if (hookLength * 10 < totalLength)
|
|
{
|
|
cRemove = 2;
|
|
}
|
|
}
|
|
|
|
// If we couldn't remove two segments, try one.
|
|
|
|
if (cRemove == 0 && lengths[5] * 10 < totalLength)
|
|
{
|
|
cRemove = 1;
|
|
}
|
|
|
|
// Drop points at the end as needed. Verify that there are enough points to continue.
|
|
|
|
cStep -= cRemove;
|
|
if (cStep < 2)
|
|
{
|
|
return cStep; // Only one segment left.
|
|
}
|
|
|
|
// Try to find hook at the start. First try to remove two segments.
|
|
|
|
cRemove = 0;
|
|
if (cStep >= 3)
|
|
{
|
|
hookLength = lengths[0] + lengths[1];
|
|
|
|
if (hookLength * 10 < totalLength)
|
|
{
|
|
cRemove = 2;
|
|
}
|
|
}
|
|
|
|
// If we couldn't remove two segments, try one.
|
|
|
|
if (cRemove == 0 && lengths[0] * 10 < totalLength)
|
|
{
|
|
cRemove = 1;
|
|
}
|
|
|
|
// Remove extra steps from start of array.
|
|
|
|
if (cRemove > 0)
|
|
{
|
|
int from, to;
|
|
|
|
to = 0;
|
|
from = cRemove;
|
|
while (from <= cStep)
|
|
{
|
|
rgStep[to++] = rgStep[from++];
|
|
}
|
|
|
|
cStep -= cRemove;
|
|
}
|
|
|
|
return cStep;
|
|
}
|
|
|
|
int IsDakuten(RECT *pBoundingBox, FRAME *pFrame1, FRAME *pFrame2, KPROTO *pKProto)
|
|
{
|
|
int cXY;
|
|
XY *pXY;
|
|
int x1_1, y1_1, x2_1, y2_1; // Start and end of first stroke.
|
|
int x1_2, y1_2, x2_2, y2_2; // Start and end of second stroke.
|
|
int x1Per, y1Per, x2Per, y2Per;
|
|
int dX, dY;
|
|
int cFeat;
|
|
PRIMITIVE *pFeat;
|
|
|
|
// Figure size of bounding box.
|
|
dX = pBoundingBox->right - pBoundingBox->left;
|
|
dY = pBoundingBox->bottom - pBoundingBox->top;
|
|
|
|
// Get pointer to data and count of points of first stroke.
|
|
cXY = pFrame1->info.cPnt;
|
|
pXY = pFrame1->rgrawxy;
|
|
|
|
// Get end points
|
|
x1_1 = pXY[0].x;
|
|
y1_1 = pXY[0].y;
|
|
x2_1 = pXY[cXY - 1].x;
|
|
y2_1 = pXY[cXY - 1].y;
|
|
|
|
// Get pointer to data and count of points of second stroke.
|
|
cXY = pFrame2->info.cPnt;
|
|
pXY = pFrame2->rgrawxy;
|
|
|
|
// Get end points
|
|
x1_2 = pXY[0].x;
|
|
y1_2 = pXY[0].y;
|
|
x2_2 = pXY[cXY - 1].x;
|
|
y2_2 = pXY[cXY - 1].y;
|
|
|
|
// Sometimesd strokes are reveresed.
|
|
if (x1_1 > x1_2 && x2_1 > x2_2) {
|
|
// Try switching strokes.
|
|
return IsDakuten(pBoundingBox, pFrame2, pFrame1, pKProto);
|
|
}
|
|
|
|
// Get end positions relitive to the upper right corner of the bounding box.
|
|
// Scale by 100 so we can get percents as integers.
|
|
x1Per = ((pBoundingBox->right - x1_1) * 100) / dX;
|
|
y1Per = ((y1_1 - pBoundingBox->top) * 100) / dY;
|
|
x2Per = ((pBoundingBox->right - x2_1) * 100) / dX;
|
|
y2Per = ((y2_1 - pBoundingBox->top) * 100) / dY;
|
|
|
|
// Check if end points match expected locations for first stroke.
|
|
if (x1Per > 70 || y1Per > 40 || x2Per > 60 || y2Per > 60) {
|
|
// First stroke wrong position.
|
|
return FALSE;
|
|
}
|
|
|
|
// Compare relitive positions of strokes.
|
|
x1Per = ((x1_2 - x1_1) * 100) / dX;
|
|
y1Per = ((y1_2 - y1_1) * 100) / dY;
|
|
x2Per = ((x2_2 - x2_1) * 100) / dX;
|
|
y2Per = ((y2_2 - y2_1) * 100) / dY;
|
|
if (x1Per <= 0 || 50 <= x1Per || y1Per <= -30 || 25 <= y1Per
|
|
|| x2Per <= 0 || 55 <= x2Per || y2Per <= -35 || 30 <= y2Per
|
|
) {
|
|
// Reletive positions of the strokes wrong.
|
|
return FALSE;
|
|
}
|
|
|
|
// Get end positions relitive to the upper right corner of the bounding box.
|
|
// Scale by 100 so we can get percents as integers.
|
|
x1Per = ((pBoundingBox->right - x1_2) * 100) / dX;
|
|
y1Per = ((y1_2 - pBoundingBox->top) * 100) / dY;
|
|
x2Per = ((pBoundingBox->right - x2_2) * 100) / dX;
|
|
y2Per = ((y2_2 - pBoundingBox->top) * 100) / dY;
|
|
|
|
// Check if end points match expected locations for second stroke.
|
|
if (x1Per > 40 || y1Per > 45 || x2Per > 25 || y2Per > 55) {
|
|
// Second stroke wrong position.
|
|
return FALSE;
|
|
}
|
|
|
|
{
|
|
BOUNDS bounds;
|
|
int normalize;
|
|
int ratioPlus, ratioMinus;
|
|
|
|
// End point tests pass, verify that we really have stokes that look mostly like lines.
|
|
// We ignore the along direction because that does not seem to be a problem
|
|
// We keep the away in loose bound since we only need to catch ones way out of line.
|
|
// Since the ratio is usually a fraction, and we want to handle integers,
|
|
// multiply by 1000.
|
|
// Note that for very small lines, we don't care what the look like.
|
|
FindBounds(0, pFrame1->info.cPnt - 1, pFrame1->rgrawxy, &bounds);
|
|
if (bounds.dX > 20 || bounds.dX < -20 || bounds.dY > 20 || bounds.dY < -20) { // Is it large?
|
|
normalize = bounds.dX * bounds.dX + bounds.dY * bounds.dY;
|
|
normalize = max(1,normalize);
|
|
ratioPlus = (bounds.oAwayPlus * 1000) / normalize;
|
|
ratioMinus = -(bounds.oAwayMinus * 1000) / normalize;
|
|
if (ratioPlus >= 1000 || ratioMinus >= 1000) { // Ratio > 1/1
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
FindBounds(0, pFrame2->info.cPnt - 1, pFrame2->rgrawxy, &bounds);
|
|
if (bounds.dX > 20 || bounds.dX < -20 || bounds.dY > 20 || bounds.dY < -20) { // Is it large?
|
|
normalize = bounds.dX * bounds.dX + bounds.dY * bounds.dY;
|
|
normalize = max(1,normalize);
|
|
ratioPlus = (bounds.oAwayPlus * 1000) / normalize;
|
|
ratioMinus = -(bounds.oAwayMinus * 1000) / normalize;
|
|
if (ratioPlus >= 1000 || ratioMinus >= 1000) { // Ratio > 1/1
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Passes all the test. So add the two stokes to the feature list.
|
|
cFeat = pKProto->cfeat;
|
|
|
|
//// Handle first stroke
|
|
|
|
// The array of primitives is CPRIMMAX in size so
|
|
// the largest index it can handle is (CPRIMMAX-1).
|
|
pFeat = &(pKProto->rgfeat[cFeat]);
|
|
if (cFeat < (CPRIMMAX - 1)) {
|
|
cFeat++;
|
|
} else {
|
|
//ASSERT(cFeat < (CPRIMMAX - 1));
|
|
}
|
|
|
|
// Record start & end of feature.
|
|
pFeat->x1 = (short)x1_1;
|
|
pFeat->y1 = (short)y1_1;
|
|
pFeat->x2 = (short)x2_1;
|
|
pFeat->y2 = (short)y2_2;
|
|
|
|
// Feature is small down right.
|
|
pFeat->code = FEAT_DOWN_RIGHT;
|
|
|
|
// Record CART info
|
|
pFeat->cFeatures = 1;
|
|
pFeat->cSteps = 1;
|
|
pFeat->fDakuTen = 1;
|
|
pFeat->nLength = Distance(pFeat->x2 - pFeat->x1, pFeat->y2 - pFeat->y1);
|
|
pFeat->netAngle = 0;
|
|
pFeat->absAngle = 0;
|
|
pFeat->boundingBox = *RectFRAME(pFrame1);
|
|
|
|
/// Now on to stroke two
|
|
|
|
// The array of primitives is CPRIMMAX in size so
|
|
// the largest index it can handle is (CPRIMMAX-1).
|
|
pFeat = &(pKProto->rgfeat[cFeat]);
|
|
if (cFeat < (CPRIMMAX - 1)) {
|
|
cFeat++;
|
|
} else {
|
|
//ASSERT(cFeat < (CPRIMMAX - 1));
|
|
}
|
|
|
|
// Record start & end of feature.
|
|
pFeat->x1 = (short)x1_2;
|
|
pFeat->y1 = (short)y1_2;
|
|
pFeat->x2 = (short)x2_2;
|
|
pFeat->y2 = (short)y2_2;
|
|
|
|
// Feature is small down right.
|
|
pFeat->code = FEAT_DOWN_RIGHT;
|
|
|
|
// Record number of features after our additions.
|
|
pKProto->cfeat = (WORD)cFeat;
|
|
|
|
// Record CART info
|
|
pFeat->cFeatures = 1;
|
|
pFeat->cSteps = 1;
|
|
pFeat->fDakuTen = 1;
|
|
pFeat->nLength = Distance(pFeat->x2 - pFeat->x1, pFeat->y2 - pFeat->y1);
|
|
pFeat->netAngle = 0;
|
|
pFeat->absAngle = 0;
|
|
pFeat->boundingBox = *RectFRAME(pFrame2);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int FeaturizeGLYPH(GLYPH *glyph, PRIMITIVE *rgprim, RECT *pRect)
|
|
{
|
|
KPROTO *kproto;
|
|
int cstepmax, cframe, cstep, cprim;
|
|
STEP *rgstep;
|
|
FRAME *frame;
|
|
int iframe;
|
|
|
|
ASSERT(glyph);
|
|
|
|
kproto = (KPROTO *) ExternAlloc(sizeof(KPROTO));
|
|
|
|
if (kproto == (KPROTO *) NULL)
|
|
return 0;
|
|
|
|
memset(kproto, '\0', sizeof(KPROTO));
|
|
kproto->rgfeat = rgprim;
|
|
GetRectGLYPH(glyph, &kproto->rect);
|
|
*pRect = kproto->rect; // Pass bounding rect up.
|
|
|
|
rgstep = (STEP *) ExternAlloc(FRAME_MAXSTEPS * sizeof(STEP));
|
|
if (rgstep == (STEP *) NULL)
|
|
goto cleanup;
|
|
|
|
cstepmax = FRAME_MAXSTEPS;
|
|
cframe = CframeGLYPH(glyph);
|
|
|
|
for (iframe = 0; iframe < cframe; iframe++)
|
|
{
|
|
frame = FrameAtGLYPH(glyph, iframe);
|
|
|
|
// Special check for dakuten. If we have 3 to 6 strokes and are looking
|
|
// at the next to last stroke, we need to check if the last two strokes form
|
|
// a dakuten.
|
|
|
|
if (3 <= cframe && cframe <= 6 && iframe == cframe - 2)
|
|
{
|
|
FRAME *frame2;
|
|
|
|
// Get the last frame as well
|
|
|
|
frame2 = FrameAtGLYPH(glyph, iframe + 1);
|
|
|
|
// Check the strokes, adds two features if it is a match.
|
|
|
|
if (IsDakuten(&kproto->rect, frame, frame2, kproto))
|
|
{
|
|
// OK, Have it abort the rest of the processing on the last two strokes.
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
cstep = StepsFromFRAME(frame, rgstep, cstepmax);
|
|
if (cstep == 0)
|
|
{
|
|
kproto->cfeat = 0; // Indicate a failure condition
|
|
goto cleanu2;
|
|
}
|
|
|
|
cstep = CraneSmoothSteps(rgstep, cstep, cframe);
|
|
AddStepsKPROTO(kproto, rgstep, cstep, cframe, frame);
|
|
}
|
|
|
|
cleanu2:
|
|
ExternFree(rgstep);
|
|
|
|
cleanup:
|
|
cprim = kproto->cfeat;
|
|
ExternFree(kproto);
|
|
|
|
return cprim;
|
|
}
|
|
|
|
BOOL AllocFeature(SAMPLE *, int);
|
|
|
|
BOOL MakeFeatures(SAMPLE *_this, void *pv)
|
|
{
|
|
PRIMITIVE rgprim[CPRIMMAX];
|
|
RECT rc;
|
|
int ifeat;
|
|
int iprim, cprim;
|
|
int xShift, yShift; // Normalize position
|
|
double xScale, yScale; // Normalize size
|
|
double xyRatio; // Ratio of xy (scaled to fit in a WORD)
|
|
|
|
cprim = FeaturizeGLYPH((GLYPH *) pv, rgprim, &rc);
|
|
if (!cprim)
|
|
return FALSE;
|
|
|
|
// Figure normilization constants.
|
|
|
|
xShift = -rc.left;
|
|
xScale = 1000.0 / (double)(rc.right - rc.left);
|
|
yShift = -rc.top;
|
|
yScale = 1000.0 / (double)(rc.bottom - rc.top);
|
|
xyRatio = (xScale / yScale) * 256.0;
|
|
|
|
// Stroke count MUST be set before any per stroke data can be allocated
|
|
|
|
_this->cstrk = (short)CframeGLYPH((GLYPH *) pv);
|
|
|
|
// Now allocate per stroke features (currently, this is all of them)
|
|
|
|
for (ifeat = 0; ifeat < FEATURE_COUNT; ifeat++)
|
|
{
|
|
if (!AllocFeature(_this, ifeat))
|
|
return FALSE;
|
|
}
|
|
|
|
// Set per character information (stroke count, dakuten, et.al.)
|
|
|
|
_this->fDakuten = rgprim[cprim - 1].fDakuTen;
|
|
|
|
// Set per stroke information (angle, stepping, end point position, et.al.)
|
|
|
|
for (iprim = 0; iprim < cprim; iprim++)
|
|
{
|
|
((SHORT *) _this->apfeat[FEATURE_ANGLE_NET]->data)[iprim]
|
|
= max(min(rgprim[iprim].netAngle, 32767), -32768);
|
|
((USHORT *) _this->apfeat[FEATURE_ANGLE_ABS]->data)[iprim]
|
|
= min(rgprim[iprim].absAngle, 0x0000ffff);
|
|
((USHORT *) _this->apfeat[FEATURE_LENGTH]->data)[iprim]
|
|
= min(rgprim[iprim].nLength, 0x0000ffff);
|
|
((BYTE *) _this->apfeat[FEATURE_STEPS]->data)[iprim]
|
|
= rgprim[iprim].cSteps;
|
|
((BYTE *) _this->apfeat[FEATURE_FEATURES]->data)[iprim]
|
|
= rgprim[iprim].cFeatures;
|
|
|
|
((END_POINTS *) _this->apfeat[FEATURE_XPOS]->data)[iprim].start
|
|
= (short) ((double) (rgprim[iprim].x1 + xShift) * xScale);
|
|
((END_POINTS *) _this->apfeat[FEATURE_XPOS]->data)[iprim].end
|
|
= (short) ((double) (rgprim[iprim].x2 + xShift) * xScale);
|
|
((END_POINTS *) _this->apfeat[FEATURE_YPOS]->data)[iprim].start
|
|
= (short) ((double) (rgprim[iprim].y1 + yShift) * yScale);
|
|
((END_POINTS *) _this->apfeat[FEATURE_YPOS]->data)[iprim].end
|
|
= (short) ((double) (rgprim[iprim].y2 + yShift) * yScale);
|
|
|
|
((RECTS *) _this->apfeat[FEATURE_STROKE_BBOX]->data)[iprim].x1
|
|
= (short) ((double) (rgprim[iprim].boundingBox.left + xShift) * xScale);
|
|
((RECTS *) _this->apfeat[FEATURE_STROKE_BBOX]->data)[iprim].y1
|
|
= (short) ((double) (rgprim[iprim].boundingBox.top + yShift) * yScale);
|
|
((RECTS *) _this->apfeat[FEATURE_STROKE_BBOX]->data)[iprim].x2
|
|
= (short) ((double) (rgprim[iprim].boundingBox.right + xShift) * xScale);
|
|
((RECTS *) _this->apfeat[FEATURE_STROKE_BBOX]->data)[iprim].y2
|
|
= (short) ((double) (rgprim[iprim].boundingBox.bottom + yShift) * yScale);
|
|
};
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CraneLoadFromPointer(LOCRUN_INFO *pLocRunInfo, QHEAD ** ppHead,QNODE **ppNode,BYTE *pbRes)
|
|
{
|
|
int *pOffsetQBT;
|
|
int *pOffsetQBH;
|
|
int iLoad;
|
|
const CRANEDB_HEADER *pHeader = (CRANEDB_HEADER *)pbRes;
|
|
|
|
if ( (pHeader->fileType != CRANEDB_FILE_TYPE)
|
|
|| (pHeader->headerSize < sizeof(*pHeader))
|
|
|| (pHeader->minFileVer > CRANEDB_CUR_FILE_VERSION)
|
|
|| (pHeader->curFileVer < CRANEDB_OLD_FILE_VERSION)
|
|
|| memcmp (pHeader->adwSignature, pLocRunInfo->adwSignature,sizeof(pHeader->adwSignature))
|
|
)
|
|
{
|
|
return FALSE;
|
|
}
|
|
pOffsetQBT = (int *)(pbRes + pHeader->headerSize);
|
|
pOffsetQBH = pOffsetQBT + 29;
|
|
|
|
for (iLoad = 1; iLoad <= 30; iLoad++)
|
|
{
|
|
ppHead[iLoad - 1] = (QHEAD *) (((BYTE *) pOffsetQBT) + pOffsetQBH[iLoad - 1]);
|
|
ppNode[iLoad - 1] = (QNODE *) (((BYTE *) pOffsetQBT) + pOffsetQBT[iLoad - 1]);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CraneLoadRes(HINSTANCE hInst, int resNumber, int resType, LOCRUN_INFO *pLocRunInfo)
|
|
{
|
|
HANDLE hres;
|
|
HGLOBAL hglb;
|
|
|
|
BYTE * pBase;
|
|
hres = FindResource(hInst, (LPCTSTR) resNumber, (LPCTSTR) resType);
|
|
|
|
ASSERT(hres);
|
|
|
|
if (hres == NULL)
|
|
return FALSE;
|
|
|
|
hglb = LoadResource(hInst, hres);
|
|
|
|
ASSERT(hglb);
|
|
|
|
if (hglb == NULL)
|
|
return FALSE;
|
|
|
|
pBase = (BYTE *) LockResource(hglb);
|
|
|
|
if (pBase == NULL)
|
|
return FALSE;
|
|
|
|
return CraneLoadFromPointer(pLocRunInfo, gapqhList, gapqnList, pBase);
|
|
}
|
|
|
|
QNODE *SkipAltList(UNIQART *puni)
|
|
{
|
|
while (puni->unicode)
|
|
puni++;
|
|
|
|
return (QNODE *) ++puni;
|
|
}
|
|
|
|
//FILE *pDebugFile = 0;
|
|
|
|
QNODE *CraneFindAnswer(SAMPLE *psample, QNODE *pnode)
|
|
{
|
|
SAMPLE_INFO sample;
|
|
|
|
if ((pnode->uniqart.qart.flag & 0xf0) != QART_QUESTION)
|
|
return pnode;
|
|
|
|
// Now ask the question
|
|
|
|
sample.pSample = psample;
|
|
AnswerQuestion(pnode->uniqart.qart.question, pnode->param1, pnode->param2, &sample, 1);
|
|
|
|
//if (pDebugFile) {
|
|
// fwprintf(pDebugFile, L" CART: (%2d:%5d:%5d) -> %5d ?< %5d\n", pnode->uniqart.qart,
|
|
// pnode->param1, pnode->param2, sample.iAnswer, pnode->value
|
|
// );
|
|
//}
|
|
if (sample.iAnswer <= pnode->value)
|
|
{
|
|
if (pnode->uniqart.qart.flag & QART_NOBRANCH)
|
|
return CraneFindAnswer(psample, SkipAltList((UNIQART *) pnode->extra));
|
|
else
|
|
return CraneFindAnswer(psample, (QNODE *) pnode->extra);
|
|
}
|
|
else
|
|
{
|
|
if (pnode->uniqart.qart.flag & QART_NOBRANCH)
|
|
return CraneFindAnswer(psample, (QNODE *) &pnode->offset);
|
|
else
|
|
return CraneFindAnswer(psample, (QNODE *) (((BYTE *) pnode->extra) + pnode->offset));
|
|
}
|
|
}
|
|
|
|
BOOL CraneMatch(ALT_LIST *pAlt,
|
|
int cAlt,
|
|
GLYPH *pGlyph,
|
|
CHARSET *pCS,
|
|
DRECTS *pdrcs,
|
|
FLOAT eCARTWeight,
|
|
LOCRUN_INFO *pLocRunInfo)
|
|
{
|
|
int cstrk = CframeGLYPH(pGlyph) - 1; // Our arrays are zero based
|
|
int index;
|
|
WORD wStart;
|
|
WORD wFinal;
|
|
QNODE *pqnode;
|
|
SAMPLE sample;
|
|
|
|
// See if Afterburner even has something with which to work
|
|
|
|
if ((!pAlt->cAlt) || (cstrk < 0) || (cstrk >= 30))
|
|
return FALSE;
|
|
|
|
// Get the page number of the top choice from the previous shape matcher
|
|
|
|
index = HIGH_INDEX(pAlt->awchList[0]);
|
|
|
|
if ((index < 0) || (index >= HIGH_INDEX_LIMIT - 1))
|
|
return FALSE;
|
|
|
|
wStart = gapqhList[cstrk]->awIndex[index];
|
|
wFinal = gapqhList[cstrk]->awIndex[index + 1];
|
|
|
|
// Note that wStart may equal wFinal, in which case there are no valid selections.
|
|
// Next get the low byte from the top choice, shift it to the left-most byte position.
|
|
// Also, assume we didn't find a CART to run.
|
|
|
|
index = (pAlt->awchList[0] & 0x00ff) << 24;
|
|
pqnode = (QNODE *) NULL;
|
|
|
|
while (wStart < wFinal)
|
|
{
|
|
if ((gapqhList[cstrk]->aqIndex[wStart] & 0xff000000) == ((DWORD) index))
|
|
{
|
|
pqnode = (QNODE *) &(((BYTE *) gapqnList[cstrk])[gapqhList[cstrk]->aqIndex[wStart] & 0x00ffffff]);
|
|
break;
|
|
}
|
|
|
|
if (gapqhList[cstrk]->aqIndex[wStart] > ((DWORD) index))
|
|
break;
|
|
|
|
wStart++;
|
|
}
|
|
|
|
// If no tree was found, exit
|
|
|
|
if (pqnode == (QNODE *) NULL)
|
|
return FALSE;
|
|
|
|
// OK, now we know we have a CART to run. Featurize the ink and run the tree
|
|
|
|
InitFeatures(&sample);
|
|
sample.drcs = *pdrcs;
|
|
wFinal = MakeFeatures(&sample, pGlyph) ? *((WORD *) CraneFindAnswer(&sample, pqnode)) : 0;
|
|
FreeFeatures(&sample);
|
|
|
|
// Did CART give us back a meaningful result?
|
|
|
|
if (!wFinal)
|
|
return FALSE;
|
|
|
|
// Short cut processing if we are top 1 already.
|
|
if (wFinal == pAlt->awchList[0]) {
|
|
pAlt->aeScore[0] += (FLOAT)eCARTWeight;
|
|
return TRUE;
|
|
}
|
|
|
|
// Did CART give us a code point that is enabled by the current recmask
|
|
|
|
{
|
|
if (!IsAllowedChar(pLocRunInfo, pCS, wFinal))
|
|
{
|
|
return FALSE; // Can't stick it in.
|
|
}
|
|
}
|
|
|
|
// Now, walk through the previous ALT_LIST and see if this character was already proposed
|
|
|
|
index = 0;
|
|
while (((UINT) index) < pAlt->cAlt)
|
|
{
|
|
if (pAlt->awchList[index] == wFinal)
|
|
break;
|
|
|
|
index++;
|
|
}
|
|
|
|
if ((UINT) index >= pAlt->cAlt) {
|
|
if (pAlt->cAlt < (UINT) cAlt) {
|
|
pAlt->cAlt++;
|
|
} else {
|
|
index--;
|
|
}
|
|
}
|
|
|
|
while (index)
|
|
{
|
|
pAlt->awchList[index] = pAlt->awchList[index - 1];
|
|
pAlt->aeScore[index] = pAlt->aeScore[index - 1];
|
|
index--;
|
|
}
|
|
|
|
pAlt->awchList[0] = wFinal;
|
|
pAlt->aeScore[0] = pAlt->aeScore[1] + (FLOAT)eCARTWeight;
|
|
return TRUE;
|
|
}
|