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.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 GestureStrokePreviewParams()52 private GestureStrokePreviewParams() { 53 mMinSamplingDistance = 0.0d; 54 mMaxInterpolationAngularThreshold = 55 degreeToRadian(DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD); 56 mMaxInterpolationDistanceThreshold = mMinSamplingDistance; 57 mMaxInterpolationSegments = 4; 58 } 59 degreeToRadian(final int degree)60 private static double degreeToRadian(final int degree) { 61 return (double)degree / 180.0d * Math.PI; 62 } 63 GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr)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 GestureStrokeWithPreviewPoints(final int pointerId, final GestureStrokeParams strokeParams, final GestureStrokePreviewParams previewParams)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 reset()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 getGestureStrokeId()100 public int getGestureStrokeId() { 101 return mStrokeId; 102 } 103 needsSampling(final int x, final int y)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 addPointOnKeyboard(final int x, final int y, final int time, final boolean isMajorEvent)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 appendPreviewStroke(final ResizableIntArray eventTimes, final ResizableIntArray xCoords, final ResizableIntArray yCoords)128 public void appendPreviewStroke(final ResizableIntArray eventTimes, 129 final ResizableIntArray xCoords, final ResizableIntArray yCoords) { 130 final int length = mPreviewEventTimes.getLength() - mLastPreviewSize; 131 if (length <= 0) { 132 return; 133 } 134 eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length); 135 xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length); 136 yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length); 137 mLastPreviewSize = mPreviewEventTimes.getLength(); 138 } 139 140 /** 141 * Calculate interpolated points between the last interpolated point and the end of the trail. 142 * And return the start index of the last interpolated segment of input arrays because it 143 * may need to recalculate the interpolated points in the segment if further segments are 144 * added to this stroke. 145 * 146 * @param lastInterpolatedIndex the start index of the last interpolated segment of 147 * <code>eventTimes</code>, <code>xCoords</code>, and <code>yCoords</code>. 148 * @param eventTimes the event time array of gesture trail to be drawn. 149 * @param xCoords the x-coordinates array of gesture trail to be drawn. 150 * @param yCoords the y-coordinates array of gesture trail to be drawn. 151 * @return the start index of the last interpolated segment of input arrays. 152 */ interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex, final ResizableIntArray eventTimes, final ResizableIntArray xCoords, final ResizableIntArray yCoords, final ResizableIntArray types)153 public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex, 154 final ResizableIntArray eventTimes, final ResizableIntArray xCoords, 155 final ResizableIntArray yCoords, final ResizableIntArray types) { 156 final int size = mPreviewEventTimes.getLength(); 157 final int[] pt = mPreviewEventTimes.getPrimitiveArray(); 158 final int[] px = mPreviewXCoordinates.getPrimitiveArray(); 159 final int[] py = mPreviewYCoordinates.getPrimitiveArray(); 160 mInterpolator.reset(px, py, 0, size); 161 // The last segment of gesture stroke needs to be interpolated again because the slope of 162 // the tangent at the last point isn't determined. 163 int lastInterpolatedDrawIndex = lastInterpolatedIndex; 164 int d1 = lastInterpolatedIndex; 165 for (int p2 = mLastInterpolatedPreviewIndex + 1; p2 < size; p2++) { 166 final int p1 = p2 - 1; 167 final int p0 = p1 - 1; 168 final int p3 = p2 + 1; 169 mLastInterpolatedPreviewIndex = p1; 170 lastInterpolatedDrawIndex = d1; 171 mInterpolator.setInterval(p0, p1, p2, p3); 172 final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X); 173 final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X); 174 final double deltaAngle = Math.abs(angularDiff(m2, m1)); 175 final int segmentsByAngle = (int)Math.ceil( 176 deltaAngle / mPreviewParams.mMaxInterpolationAngularThreshold); 177 final double deltaDistance = Math.hypot(mInterpolator.mP1X - mInterpolator.mP2X, 178 mInterpolator.mP1Y - mInterpolator.mP2Y); 179 final int segmentsByDistance = (int)Math.ceil(deltaDistance 180 / mPreviewParams.mMaxInterpolationDistanceThreshold); 181 final int segments = Math.min(mPreviewParams.mMaxInterpolationSegments, 182 Math.max(segmentsByAngle, segmentsByDistance)); 183 final int t1 = eventTimes.get(d1); 184 final int dt = pt[p2] - pt[p1]; 185 d1++; 186 for (int i = 1; i < segments; i++) { 187 final float t = i / (float)segments; 188 mInterpolator.interpolate(t); 189 eventTimes.add(d1, (int)(dt * t) + t1); 190 xCoords.add(d1, (int)mInterpolator.mInterpolatedX); 191 yCoords.add(d1, (int)mInterpolator.mInterpolatedY); 192 if (GestureTrail.DBG_SHOW_POINTS) { 193 types.add(d1, GestureTrail.POINT_TYPE_INTERPOLATED); 194 } 195 d1++; 196 } 197 eventTimes.add(d1, pt[p2]); 198 xCoords.add(d1, px[p2]); 199 yCoords.add(d1, py[p2]); 200 if (GestureTrail.DBG_SHOW_POINTS) { 201 types.add(d1, GestureTrail.POINT_TYPE_SAMPLED); 202 } 203 } 204 return lastInterpolatedDrawIndex; 205 } 206 207 private static final double TWO_PI = Math.PI * 2.0d; 208 209 /** 210 * Calculate the angular of rotation from <code>a0</code> to <code>a1</code>. 211 * 212 * @param a1 the angular to which the rotation ends. 213 * @param a0 the angular from which the rotation starts. 214 * @return the angular rotation value from a0 to a1, normalized to [-PI, +PI]. 215 */ angularDiff(final double a1, final double a0)216 private static double angularDiff(final double a1, final double a0) { 217 double deltaAngle = a1 - a0; 218 while (deltaAngle > Math.PI) { 219 deltaAngle -= TWO_PI; 220 } 221 while (deltaAngle < -Math.PI) { 222 deltaAngle += TWO_PI; 223 } 224 return deltaAngle; 225 } 226 } 227