• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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