/** * A common helper method to initialize a table of precomputed score values using a cubic curve * @param curve the curve * @param numSamples the number of samples to compute * @return the score array table */ static private double[] initCubicCurveScoreTable(CubicCurve2D curve, int numSamples) { double x1 = curve.X1; double x2 = curve.X2; double range = x2 - x1; double x = x1; double xInc = range / numSamples; // even incrementer to increment x value by when sampling across the curve double[] scoreTable = new double[numSamples]; // For use to pass into with solveCubicCurve double[] solutions = new double[1]; // Sample evenly across the curve and set the samples into the table. for (int i = 0; i < numSamples; i++) { //CurveUtils.solveCubicCurveForX(curve, Math.Min(x, x2), solutions); //double t = solutions[0]; double t = curve.GetFirstSolutionForX(Math.Min(x, x2)); //scoreTable[i] = CurveUtils.getPointOnCubicCurve(curve, t).getY(); scoreTable[i] = curve.GetYOnCurve(t); x += xInc; } return(scoreTable); }
/** * Computes the range of substrokes to use when computing matches based on looseness. * When matching, sub strokes are matched up with one another to find the best * matching. But if two substrokes are +/- beyond this range, then the comparison * is short-circuited for some computation savings. * * @param subStrokeCount the substroke count of the input character * @param looseness the looseness, 0-1 * @return the range */ private int getSubStrokesRange(int subStrokeCount) { // Return the maximum if looseness = 1.0. // Otherwise we'd have to ensure that the floating point value led to exactly the right int count. if (looseness == 1.0) { return(CharacterDescriptor.MAX_CHARACTER_SUB_STROKE_COUNT); } // We use a CubicCurve that grows slowly at first and then rapidly near the end to the maximum. double y0 = subStrokeCount * 0.25; double ctrl1X = 0.4; double ctrl1Y = 1.5 * y0; double ctrl2X = 0.75; double ctrl2Y = 1.5 * ctrl1Y; double[] solutions = new double[1]; CubicCurve2D curve = new CubicCurve2D(0, y0, ctrl1X, ctrl1Y, ctrl2X, ctrl2Y, 1, CharacterDescriptor.MAX_CHARACTER_SUB_STROKE_COUNT); //CurveUtils.solveCubicCurveForX(curve, looseness, solutions); //double t = solutions[0]; double t = curve.GetFirstSolutionForX(looseness); // We get the t value on the parametrized curve where the x value matches the looseness. // Then we compute the y value for that t. This gives the range. //return (int)Math.Round(CurveUtils.getPointOnCubicCurve(curve, t).getY()); return((int)Math.Round(curve.GetYOnCurve(t))); }
/** * Computes a range of strokes to use based on the given looseness. * Only characters whose number of strokes are within the input number of strokes * +/- this range will be considered during comparison. This helps cut down * on matching cost. * * @param strokeCount the number of input strokes * @param looseness the looseness, 0-1 * @return the range */ private int getStrokesRange(int strokeCount) { // Just return some extreme values if at minimum or maximum. // Helps to avoid possible floating point issues when near the extremes. if (looseness == 0.0) { return(0); } else if (looseness == 1.0) { return(CharacterDescriptor.MAX_CHARACTER_STROKE_COUNT); } // We use a CubicCurve that grows slowly at first and then rapidly near the end to the maximum. // This is so a looseness at or near 1.0 will return a range that will consider all characters. double ctrl1X = 0.35; double ctrl1Y = strokeCount * 0.4; double ctrl2X = 0.6; double ctrl2Y = strokeCount; double[] solutions = new double[1]; CubicCurve2D curve = new CubicCurve2D(0, 0, ctrl1X, ctrl1Y, ctrl2X, ctrl2Y, 1, CharacterDescriptor.MAX_CHARACTER_STROKE_COUNT); //CurveUtils.solveCubicCurveForX(curve, looseness, solutions); //double t = solutions[0]; double t = curve.GetFirstSolutionForX(looseness); // We get the t value on the parametrized curve where the x value matches the looseness. // Then we compute the y value for that t. This gives the range. //return (int)Math.Round(CurveUtils.getPointOnCubicCurve(curve, t).getY()); return((int)Math.Round(curve.GetYOnCurve(t))); }
/** * Builds a precomputed array of values to use when getting the score between two substroke directions. * Two directions should differ by 0 - Pi, and the score should be the (difference / Pi) * score table's length * * @return the direction score table */ static private double[] initDirectionScoreTable() { // The curve drops as the difference grows, but rises again some at the end because // a stroke that is 180 degrees from the expected direction maybe OK passable. CubicCurve2D curve = new CubicCurve2D(0, 1.0, 0.5, 1.0, 0.25, -2.0, 1.0, 1.0); return(initCubicCurveScoreTable(curve, 100)); }
/** * Builds a precomputed array of values to use when getting the score between two substroke lengths. * A ratio less than one is computed for the two lengths, and the score should be the ratio * score table's length. * * @return the length score table */ static private double[] initLengthScoreTable() { // Curve grows rapidly as the ratio grows and levels off quickly. // This is because we don't really expect lengths to vary a lot. // We are really just trying to distinguish between tiny strokes and long strokes. CubicCurve2D curve = new CubicCurve2D(0, 0, 0.25, 1.0, 0.75, 1.0, 1.0, 1.0); return(initCubicCurveScoreTable(curve, 100)); }