1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.keyboard.internal; 18 19 import com.android.inputmethod.latin.common.ResizableIntArray; 20 21 /** 22 * This class holds drawing points to represent a gesture stroke on the screen. 23 */ 24 public final class GestureStrokeDrawingPoints { 25 public static final int PREVIEW_CAPACITY = 256; 26 27 private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY); 28 private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); 29 private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); 30 31 private final GestureStrokeDrawingParams mDrawingParams; 32 33 private int mStrokeId; 34 private int mLastPreviewSize; 35 private final HermiteInterpolator mInterpolator = new HermiteInterpolator(); 36 private int mLastInterpolatedPreviewIndex; 37 38 private int mLastX; 39 private int mLastY; 40 private double mDistanceFromLastSample; 41 GestureStrokeDrawingPoints(final GestureStrokeDrawingParams drawingParams)42 public GestureStrokeDrawingPoints(final GestureStrokeDrawingParams drawingParams) { 43 mDrawingParams = drawingParams; 44 } 45 reset()46 private void reset() { 47 mStrokeId++; 48 mLastPreviewSize = 0; 49 mLastInterpolatedPreviewIndex = 0; 50 mPreviewEventTimes.setLength(0); 51 mPreviewXCoordinates.setLength(0); 52 mPreviewYCoordinates.setLength(0); 53 } 54 getGestureStrokeId()55 public int getGestureStrokeId() { 56 return mStrokeId; 57 } 58 onDownEvent(final int x, final int y, final int elapsedTimeSinceFirstDown)59 public void onDownEvent(final int x, final int y, final int elapsedTimeSinceFirstDown) { 60 reset(); 61 onMoveEvent(x, y, elapsedTimeSinceFirstDown); 62 } 63 needsSampling(final int x, final int y)64 private boolean needsSampling(final int x, final int y) { 65 mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY); 66 mLastX = x; 67 mLastY = y; 68 final boolean isDownEvent = (mPreviewEventTimes.getLength() == 0); 69 if (mDistanceFromLastSample >= mDrawingParams.mMinSamplingDistance || isDownEvent) { 70 mDistanceFromLastSample = 0.0d; 71 return true; 72 } 73 return false; 74 } 75 onMoveEvent(final int x, final int y, final int elapsedTimeSinceFirstDown)76 public void onMoveEvent(final int x, final int y, final int elapsedTimeSinceFirstDown) { 77 if (needsSampling(x, y)) { 78 mPreviewEventTimes.add(elapsedTimeSinceFirstDown); 79 mPreviewXCoordinates.add(x); 80 mPreviewYCoordinates.add(y); 81 } 82 } 83 84 /** 85 * Append sampled preview points. 86 * 87 * @param eventTimes the event time array of gesture trail to be drawn. 88 * @param xCoords the x-coordinates array of gesture trail to be drawn. 89 * @param yCoords the y-coordinates array of gesture trail to be drawn. 90 * @param types the point types array of gesture trail. This is valid only when 91 * {@link GestureTrailDrawingPoints#DEBUG_SHOW_POINTS} is true. 92 */ appendPreviewStroke(final ResizableIntArray eventTimes, final ResizableIntArray xCoords, final ResizableIntArray yCoords, final ResizableIntArray types)93 public void appendPreviewStroke(final ResizableIntArray eventTimes, 94 final ResizableIntArray xCoords, final ResizableIntArray yCoords, 95 final ResizableIntArray types) { 96 final int length = mPreviewEventTimes.getLength() - mLastPreviewSize; 97 if (length <= 0) { 98 return; 99 } 100 eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length); 101 xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length); 102 yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length); 103 if (GestureTrailDrawingPoints.DEBUG_SHOW_POINTS) { 104 types.fill(GestureTrailDrawingPoints.POINT_TYPE_SAMPLED, types.getLength(), length); 105 } 106 mLastPreviewSize = mPreviewEventTimes.getLength(); 107 } 108 109 /** 110 * Calculate interpolated points between the last interpolated point and the end of the trail. 111 * And return the start index of the last interpolated segment of input arrays because it 112 * may need to recalculate the interpolated points in the segment if further segments are 113 * added to this stroke. 114 * 115 * @param lastInterpolatedIndex the start index of the last interpolated segment of 116 * <code>eventTimes</code>, <code>xCoords</code>, and <code>yCoords</code>. 117 * @param eventTimes the event time array of gesture trail to be drawn. 118 * @param xCoords the x-coordinates array of gesture trail to be drawn. 119 * @param yCoords the y-coordinates array of gesture trail to be drawn. 120 * @param types the point types array of gesture trail. This is valid only when 121 * {@link GestureTrailDrawingPoints#DEBUG_SHOW_POINTS} is true. 122 * @return the start index of the last interpolated segment of input arrays. 123 */ interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex, final ResizableIntArray eventTimes, final ResizableIntArray xCoords, final ResizableIntArray yCoords, final ResizableIntArray types)124 public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex, 125 final ResizableIntArray eventTimes, final ResizableIntArray xCoords, 126 final ResizableIntArray yCoords, final ResizableIntArray types) { 127 final int size = mPreviewEventTimes.getLength(); 128 final int[] pt = mPreviewEventTimes.getPrimitiveArray(); 129 final int[] px = mPreviewXCoordinates.getPrimitiveArray(); 130 final int[] py = mPreviewYCoordinates.getPrimitiveArray(); 131 mInterpolator.reset(px, py, 0, size); 132 // The last segment of gesture stroke needs to be interpolated again because the slope of 133 // the tangent at the last point isn't determined. 134 int lastInterpolatedDrawIndex = lastInterpolatedIndex; 135 int d1 = lastInterpolatedIndex; 136 for (int p2 = mLastInterpolatedPreviewIndex + 1; p2 < size; p2++) { 137 final int p1 = p2 - 1; 138 final int p0 = p1 - 1; 139 final int p3 = p2 + 1; 140 mLastInterpolatedPreviewIndex = p1; 141 lastInterpolatedDrawIndex = d1; 142 mInterpolator.setInterval(p0, p1, p2, p3); 143 final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X); 144 final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X); 145 final double deltaAngle = Math.abs(angularDiff(m2, m1)); 146 final int segmentsByAngle = (int)Math.ceil( 147 deltaAngle / mDrawingParams.mMaxInterpolationAngularThreshold); 148 final double deltaDistance = Math.hypot(mInterpolator.mP1X - mInterpolator.mP2X, 149 mInterpolator.mP1Y - mInterpolator.mP2Y); 150 final int segmentsByDistance = (int)Math.ceil(deltaDistance 151 / mDrawingParams.mMaxInterpolationDistanceThreshold); 152 final int segments = Math.min(mDrawingParams.mMaxInterpolationSegments, 153 Math.max(segmentsByAngle, segmentsByDistance)); 154 final int t1 = eventTimes.get(d1); 155 final int dt = pt[p2] - pt[p1]; 156 d1++; 157 for (int i = 1; i < segments; i++) { 158 final float t = i / (float)segments; 159 mInterpolator.interpolate(t); 160 eventTimes.addAt(d1, (int)(dt * t) + t1); 161 xCoords.addAt(d1, (int)mInterpolator.mInterpolatedX); 162 yCoords.addAt(d1, (int)mInterpolator.mInterpolatedY); 163 if (GestureTrailDrawingPoints.DEBUG_SHOW_POINTS) { 164 types.addAt(d1, GestureTrailDrawingPoints.POINT_TYPE_INTERPOLATED); 165 } 166 d1++; 167 } 168 eventTimes.addAt(d1, pt[p2]); 169 xCoords.addAt(d1, px[p2]); 170 yCoords.addAt(d1, py[p2]); 171 if (GestureTrailDrawingPoints.DEBUG_SHOW_POINTS) { 172 types.addAt(d1, GestureTrailDrawingPoints.POINT_TYPE_SAMPLED); 173 } 174 } 175 return lastInterpolatedDrawIndex; 176 } 177 178 private static final double TWO_PI = Math.PI * 2.0d; 179 180 /** 181 * Calculate the angular of rotation from <code>a0</code> to <code>a1</code>. 182 * 183 * @param a1 the angular to which the rotation ends. 184 * @param a0 the angular from which the rotation starts. 185 * @return the angular rotation value from a0 to a1, normalized to [-PI, +PI]. 186 */ angularDiff(final double a1, final double a0)187 private static double angularDiff(final double a1, final double a0) { 188 double deltaAngle = a1 - a0; 189 while (deltaAngle > Math.PI) { 190 deltaAngle -= TWO_PI; 191 } 192 while (deltaAngle < -Math.PI) { 193 deltaAngle += TWO_PI; 194 } 195 return deltaAngle; 196 } 197 } 198