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 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