Home | History | Annotate | Download | only in internal
      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 
     42     public GestureStrokeDrawingPoints(final GestureStrokeDrawingParams drawingParams) {
     43         mDrawingParams = drawingParams;
     44     }
     45 
     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 
     55     public int getGestureStrokeId() {
     56         return mStrokeId;
     57     }
     58 
     59     public void onDownEvent(final int x, final int y, final int elapsedTimeSinceFirstDown) {
     60         reset();
     61         onMoveEvent(x, y, elapsedTimeSinceFirstDown);
     62     }
     63 
     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 
     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      */
     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      */
    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      */
    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