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