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 android.content.res.TypedArray; 20 21 import com.android.inputmethod.latin.R; 22 import com.android.inputmethod.latin.utils.ResizableIntArray; 23 24 public final class GestureStrokeWithPreviewPoints extends GestureStroke { 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 GestureStrokePreviewParams mPreviewParams; 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 42 public static final class GestureStrokePreviewParams { 43 public final double mMinSamplingDistance; // in pixel 44 public final double mMaxInterpolationAngularThreshold; // in radian 45 public final double mMaxInterpolationDistanceThreshold; // in pixel 46 public final int mMaxInterpolationSegments; 47 48 public static final GestureStrokePreviewParams DEFAULT = new GestureStrokePreviewParams(); 49 50 private static final int DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD = 15; // in degree 51 52 private GestureStrokePreviewParams() { 53 mMinSamplingDistance = 0.0d; 54 mMaxInterpolationAngularThreshold = 55 degreeToRadian(DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD); 56 mMaxInterpolationDistanceThreshold = mMinSamplingDistance; 57 mMaxInterpolationSegments = 4; 58 } 59 60 private static double degreeToRadian(final int degree) { 61 return degree / 180.0d * Math.PI; 62 } 63 64 public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) { 65 mMinSamplingDistance = mainKeyboardViewAttr.getDimension( 66 R.styleable.MainKeyboardView_gestureTrailMinSamplingDistance, 67 (float)DEFAULT.mMinSamplingDistance); 68 final int interpolationAngularDegree = mainKeyboardViewAttr.getInteger(R.styleable 69 .MainKeyboardView_gestureTrailMaxInterpolationAngularThreshold, 0); 70 mMaxInterpolationAngularThreshold = (interpolationAngularDegree <= 0) 71 ? DEFAULT.mMaxInterpolationAngularThreshold 72 : degreeToRadian(interpolationAngularDegree); 73 mMaxInterpolationDistanceThreshold = mainKeyboardViewAttr.getDimension(R.styleable 74 .MainKeyboardView_gestureTrailMaxInterpolationDistanceThreshold, 75 (float)DEFAULT.mMaxInterpolationDistanceThreshold); 76 mMaxInterpolationSegments = mainKeyboardViewAttr.getInteger( 77 R.styleable.MainKeyboardView_gestureTrailMaxInterpolationSegments, 78 DEFAULT.mMaxInterpolationSegments); 79 } 80 } 81 82 public GestureStrokeWithPreviewPoints(final int pointerId, 83 final GestureStrokeParams strokeParams, 84 final GestureStrokePreviewParams previewParams) { 85 super(pointerId, strokeParams); 86 mPreviewParams = previewParams; 87 } 88 89 @Override 90 protected void reset() { 91 super.reset(); 92 mStrokeId++; 93 mLastPreviewSize = 0; 94 mLastInterpolatedPreviewIndex = 0; 95 mPreviewEventTimes.setLength(0); 96 mPreviewXCoordinates.setLength(0); 97 mPreviewYCoordinates.setLength(0); 98 } 99 100 public int getGestureStrokeId() { 101 return mStrokeId; 102 } 103 104 private boolean needsSampling(final int x, final int y) { 105 mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY); 106 mLastX = x; 107 mLastY = y; 108 final boolean isDownEvent = (mPreviewEventTimes.getLength() == 0); 109 if (mDistanceFromLastSample >= mPreviewParams.mMinSamplingDistance || isDownEvent) { 110 mDistanceFromLastSample = 0.0d; 111 return true; 112 } 113 return false; 114 } 115 116 @Override 117 public boolean addPointOnKeyboard(final int x, final int y, final int time, 118 final boolean isMajorEvent) { 119 if (needsSampling(x, y)) { 120 mPreviewEventTimes.add(time); 121 mPreviewXCoordinates.add(x); 122 mPreviewYCoordinates.add(y); 123 } 124 return super.addPointOnKeyboard(x, y, time, isMajorEvent); 125 126 } 127 128 /** 129 * Append sampled preview points. 130 * 131 * @param eventTimes the event time array of gesture trail to be drawn. 132 * @param xCoords the x-coordinates array of gesture trail to be drawn. 133 * @param yCoords the y-coordinates array of gesture trail to be drawn. 134 * @param types the point types array of gesture trail. This is valid only when 135 * {@link GestureTrail#DEBUG_SHOW_POINTS} is true. 136 */ 137 public void appendPreviewStroke(final ResizableIntArray eventTimes, 138 final ResizableIntArray xCoords, final ResizableIntArray yCoords, 139 final ResizableIntArray types) { 140 final int length = mPreviewEventTimes.getLength() - mLastPreviewSize; 141 if (length <= 0) { 142 return; 143 } 144 eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length); 145 xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length); 146 yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length); 147 if (GestureTrail.DEBUG_SHOW_POINTS) { 148 types.fill(GestureTrail.POINT_TYPE_SAMPLED, types.getLength(), length); 149 } 150 mLastPreviewSize = mPreviewEventTimes.getLength(); 151 } 152 153 /** 154 * Calculate interpolated points between the last interpolated point and the end of the trail. 155 * And return the start index of the last interpolated segment of input arrays because it 156 * may need to recalculate the interpolated points in the segment if further segments are 157 * added to this stroke. 158 * 159 * @param lastInterpolatedIndex the start index of the last interpolated segment of 160 * <code>eventTimes</code>, <code>xCoords</code>, and <code>yCoords</code>. 161 * @param eventTimes the event time array of gesture trail to be drawn. 162 * @param xCoords the x-coordinates array of gesture trail to be drawn. 163 * @param yCoords the y-coordinates array of gesture trail to be drawn. 164 * @param types the point types array of gesture trail. This is valid only when 165 * {@link GestureTrail#DEBUG_SHOW_POINTS} is true. 166 * @return the start index of the last interpolated segment of input arrays. 167 */ 168 public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex, 169 final ResizableIntArray eventTimes, final ResizableIntArray xCoords, 170 final ResizableIntArray yCoords, final ResizableIntArray types) { 171 final int size = mPreviewEventTimes.getLength(); 172 final int[] pt = mPreviewEventTimes.getPrimitiveArray(); 173 final int[] px = mPreviewXCoordinates.getPrimitiveArray(); 174 final int[] py = mPreviewYCoordinates.getPrimitiveArray(); 175 mInterpolator.reset(px, py, 0, size); 176 // The last segment of gesture stroke needs to be interpolated again because the slope of 177 // the tangent at the last point isn't determined. 178 int lastInterpolatedDrawIndex = lastInterpolatedIndex; 179 int d1 = lastInterpolatedIndex; 180 for (int p2 = mLastInterpolatedPreviewIndex + 1; p2 < size; p2++) { 181 final int p1 = p2 - 1; 182 final int p0 = p1 - 1; 183 final int p3 = p2 + 1; 184 mLastInterpolatedPreviewIndex = p1; 185 lastInterpolatedDrawIndex = d1; 186 mInterpolator.setInterval(p0, p1, p2, p3); 187 final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X); 188 final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X); 189 final double deltaAngle = Math.abs(angularDiff(m2, m1)); 190 final int segmentsByAngle = (int)Math.ceil( 191 deltaAngle / mPreviewParams.mMaxInterpolationAngularThreshold); 192 final double deltaDistance = Math.hypot(mInterpolator.mP1X - mInterpolator.mP2X, 193 mInterpolator.mP1Y - mInterpolator.mP2Y); 194 final int segmentsByDistance = (int)Math.ceil(deltaDistance 195 / mPreviewParams.mMaxInterpolationDistanceThreshold); 196 final int segments = Math.min(mPreviewParams.mMaxInterpolationSegments, 197 Math.max(segmentsByAngle, segmentsByDistance)); 198 final int t1 = eventTimes.get(d1); 199 final int dt = pt[p2] - pt[p1]; 200 d1++; 201 for (int i = 1; i < segments; i++) { 202 final float t = i / (float)segments; 203 mInterpolator.interpolate(t); 204 eventTimes.add(d1, (int)(dt * t) + t1); 205 xCoords.add(d1, (int)mInterpolator.mInterpolatedX); 206 yCoords.add(d1, (int)mInterpolator.mInterpolatedY); 207 if (GestureTrail.DEBUG_SHOW_POINTS) { 208 types.add(d1, GestureTrail.POINT_TYPE_INTERPOLATED); 209 } 210 d1++; 211 } 212 eventTimes.add(d1, pt[p2]); 213 xCoords.add(d1, px[p2]); 214 yCoords.add(d1, py[p2]); 215 if (GestureTrail.DEBUG_SHOW_POINTS) { 216 types.add(d1, GestureTrail.POINT_TYPE_SAMPLED); 217 } 218 } 219 return lastInterpolatedDrawIndex; 220 } 221 222 private static final double TWO_PI = Math.PI * 2.0d; 223 224 /** 225 * Calculate the angular of rotation from <code>a0</code> to <code>a1</code>. 226 * 227 * @param a1 the angular to which the rotation ends. 228 * @param a0 the angular from which the rotation starts. 229 * @return the angular rotation value from a0 to a1, normalized to [-PI, +PI]. 230 */ 231 private static double angularDiff(final double a1, final double a0) { 232 double deltaAngle = a1 - a0; 233 while (deltaAngle > Math.PI) { 234 deltaAngle -= TWO_PI; 235 } 236 while (deltaAngle < -Math.PI) { 237 deltaAngle += TWO_PI; 238 } 239 return deltaAngle; 240 } 241 } 242