Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b1533116 authored by Tadashi G. Takaoka's avatar Tadashi G. Takaoka Committed by Android Git Automerger
Browse files

am b08506c2: am 9413e957: Merge "Interpolate gesture preview trails"

* commit 'b08506c2':
  Interpolate gesture preview trails
parents 91d468f5 b08506c2
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ final class GesturePreviewTrail {
    // The wall time of the zero value in {@link #mEventTimes}
    private long mCurrentTimeBase;
    private int mTrailStartIndex;
    private int mLastInterpolatedDrawIndex;

    static final class Params {
        public final int mTrailColor;
@@ -96,6 +97,17 @@ final class GesturePreviewTrail {
        }
        final int[] eventTimes = mEventTimes.getPrimitiveArray();
        final int strokeId = stroke.getGestureStrokeId();
        // Because interpolation algorithm in {@link GestureStrokeWithPreviewPoints} can't determine
        // the interpolated points in the last segment of gesture stroke, it may need recalculation
        // of interpolation when new segments are added to the stroke.
        // {@link #mLastInterpolatedDrawIndex} holds the start index of the last segment. It may
        // be updated by the interpolation
        // {@link GestureStrokeWithPreviewPoints#interpolatePreviewStroke}
        // or by animation {@link #drawGestureTrail(Canvas,Paint,Rect,Params)} below.
        final int lastInterpolatedIndex = (strokeId == mCurrentStrokeId)
                ? mLastInterpolatedDrawIndex : trailSize;
        mLastInterpolatedDrawIndex = stroke.interpolateStrokeAndReturnStartIndexOfLastSegment(
                lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates);
        if (strokeId != mCurrentStrokeId) {
            final int elapsedTime = (int)(downTime - mCurrentTimeBase);
            for (int i = mTrailStartIndex; i < trailSize; i++) {
@@ -216,6 +228,10 @@ final class GesturePreviewTrail {
                System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
                System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
                System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
                // The start index of the last segment of the stroke
                // {@link mLastInterpolatedDrawIndex} should also be updated because all array
                // elements have just been shifted for compaction.
                mLastInterpolatedDrawIndex = Math.max(mLastInterpolatedDrawIndex - startIndex, 0);
            }
            mEventTimes.setLength(newSize);
            mXCoordinates.setLength(newSize);
+122 −16
Original line number Diff line number Diff line
@@ -21,19 +21,32 @@ import com.android.inputmethod.latin.ResizableIntArray;
public final class GestureStrokeWithPreviewPoints extends GestureStroke {
    public static final int PREVIEW_CAPACITY = 256;

    private static final boolean ENABLE_INTERPOLATION = true;

    private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY);
    private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
    private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);

    private int mStrokeId;
    private int mLastPreviewSize;
    private final HermiteInterpolator mInterpolator = new HermiteInterpolator();
    private int mLastInterpolatedPreviewIndex;

    private int mMinPreviewSampleLengthSquare;
    private int mMinPreviewSamplingDistanceSquared;
    private int mLastX;
    private int mLastY;
    private double mMinPreviewSamplingDistance;
    private double mDistanceFromLastSample;

    // TODO: Move this to resource.
    private static final float MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH = 0.1f;
    // TODO: Move these constants to resource.
    // The minimum linear distance between sample points for preview in keyWidth unit.
    private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH = 0.1f;
    // The minimum trail distance between sample points for preview in keyWidth unit when using
    // interpolation.
    private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH_WITH_INTERPOLATION = 0.2f;
    // The angular threshold to use interpolation in radian. PI/12 is 15 degree.
    private static final double INTERPOLATION_ANGULAR_THRESHOLD = Math.PI / 12.0d;
    private static final int MAX_INTERPOLATION_PARTITION = 4;

    public GestureStrokeWithPreviewPoints(final int pointerId, final GestureStrokeParams params) {
        super(pointerId, params);
@@ -44,6 +57,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
        super.reset();
        mStrokeId++;
        mLastPreviewSize = 0;
        mLastInterpolatedPreviewIndex = 0;
        mPreviewEventTimes.setLength(0);
        mPreviewXCoordinates.setLength(0);
        mPreviewYCoordinates.setLength(0);
@@ -53,35 +67,49 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
        return mStrokeId;
    }

    public int getGestureStrokePreviewSize() {
        return mPreviewEventTimes.getLength();
    }

    @Override
    public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) {
        super.setKeyboardGeometry(keyWidth, keyboardHeight);
        final float sampleLength = keyWidth * MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH;
        mMinPreviewSampleLengthSquare = (int)(sampleLength * sampleLength);
        final float samplingRatioToKeyWidth = ENABLE_INTERPOLATION
                ? MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH_WITH_INTERPOLATION
                : MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH;
        mMinPreviewSamplingDistance = keyWidth * samplingRatioToKeyWidth;
        mMinPreviewSamplingDistanceSquared = (int)(
                mMinPreviewSamplingDistance * mMinPreviewSamplingDistance);
    }

    private boolean needsSampling(final int x, final int y, final boolean isMajorEvent) {
        if (ENABLE_INTERPOLATION) {
            mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY);
            mLastX = x;
            mLastY = y;
            if (mDistanceFromLastSample >= mMinPreviewSamplingDistance) {
                mDistanceFromLastSample = 0.0d;
                return true;
            }
            return false;
        }

    private boolean needsSampling(final int x, final int y) {
        final int dx = x - mLastX;
        final int dy = y - mLastY;
        return dx * dx + dy * dy >= mMinPreviewSampleLengthSquare;
        if (isMajorEvent || dx * dx + dy * dy >= mMinPreviewSamplingDistanceSquared) {
            mLastX = x;
            mLastY = y;
            return true;
        }
        return false;
    }

    @Override
    public boolean addPointOnKeyboard(final int x, final int y, final int time,
            final boolean isMajorEvent) {
        final boolean onValidArea = super.addPointOnKeyboard(x, y, time, isMajorEvent);
        if (isMajorEvent || needsSampling(x, y)) {
        if (needsSampling(x, y, isMajorEvent)) {
            mPreviewEventTimes.add(time);
            mPreviewXCoordinates.add(x);
            mPreviewYCoordinates.add(y);
            mLastX = x;
            mLastY = y;
        }
        return onValidArea;
        return super.addPointOnKeyboard(x, y, time, isMajorEvent);

    }

    public void appendPreviewStroke(final ResizableIntArray eventTimes,
@@ -95,4 +123,82 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
        yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length);
        mLastPreviewSize = mPreviewEventTimes.getLength();
    }

    /**
     * Calculate interpolated points between the last interpolated point and the end of the trail.
     * And return the start index of the last interpolated segment of input arrays because it
     * may need to recalculate the interpolated points in the segment if further segments are
     * added to this stroke.
     *
     * @param lastInterpolatedIndex the start index of the last interpolated segment of
     *        <code>eventTimes</code>, <code>xCoords</code>, and <code>yCoords</code>.
     * @param eventTimes the event time array of gesture preview trail to be drawn.
     * @param xCoords the x-coordinates array of gesture preview trail to be drawn.
     * @param yCoords the y-coordinates array of gesture preview trail to be drawn.
     * @return the start index of the last interpolated segment of input arrays.
     */
    public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex,
            final ResizableIntArray eventTimes, final ResizableIntArray xCoords,
            final ResizableIntArray yCoords) {
        if (!ENABLE_INTERPOLATION) {
            return lastInterpolatedIndex;
        }
        final int size = mPreviewEventTimes.getLength();
        final int[] pt = mPreviewEventTimes.getPrimitiveArray();
        final int[] px = mPreviewXCoordinates.getPrimitiveArray();
        final int[] py = mPreviewYCoordinates.getPrimitiveArray();
        mInterpolator.reset(px, py, 0, size);
        // The last segment of gesture stroke needs to be interpolated again because the slope of
        // the tangent at the last point isn't determined.
        int lastInterpolatedDrawIndex = lastInterpolatedIndex;
        int d1 = lastInterpolatedIndex;
        for (int p2 = mLastInterpolatedPreviewIndex + 1; p2 < size; p2++) {
            final int p1 = p2 - 1;
            final int p0 = p1 - 1;
            final int p3 = p2 + 1;
            mLastInterpolatedPreviewIndex = p1;
            lastInterpolatedDrawIndex = d1;
            mInterpolator.setInterval(p0, p1, p2, p3);
            final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X);
            final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X);
            final double dm = Math.abs(angularDiff(m2, m1));
            final int partition = Math.min((int)Math.ceil(dm / INTERPOLATION_ANGULAR_THRESHOLD),
                    MAX_INTERPOLATION_PARTITION);
            final int t1 = eventTimes.get(d1);
            final int dt = pt[p2] - pt[p1];
            d1++;
            for (int i = 1; i < partition; i++) {
                final float t = i / (float)partition;
                mInterpolator.interpolate(t);
                eventTimes.add(d1, (int)(dt * t) + t1);
                xCoords.add(d1, (int)mInterpolator.mInterpolatedX);
                yCoords.add(d1, (int)mInterpolator.mInterpolatedY);
                d1++;
            }
            eventTimes.add(d1, pt[p2]);
            xCoords.add(d1, px[p2]);
            yCoords.add(d1, py[p2]);
        }
        return lastInterpolatedDrawIndex;
    }

    private static final double TWO_PI = Math.PI * 2.0d;

    /**
     * Calculate the angular of rotation from <code>a0</code> to <code>a1</code>.
     *
     * @param a1 the angular to which the rotation ends.
     * @param a0 the angular from which the rotation starts.
     * @return the angular rotation value from a0 to a1, normalized to [-PI, +PI].
     */
    private static double angularDiff(final double a1, final double a0) {
        double deltaAngle = a1 - a0;
        while (deltaAngle > Math.PI) {
            deltaAngle -= TWO_PI;
        }
        while (deltaAngle < -Math.PI) {
            deltaAngle += TWO_PI;
        }
        return deltaAngle;
    }
}
+166 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.inputmethod.keyboard.internal;

import com.android.inputmethod.annotations.UsedForTesting;

/**
 * Interpolates XY-coordinates using Cubic Hermite Curve.
 */
public final class HermiteInterpolator {
    private int[] mXCoords;
    private int[] mYCoords;
    private int mMinPos;
    private int mMaxPos;

    // Working variable to calculate interpolated value.
    /** The coordinates of the start point of the interval. */
    public int mP1X, mP1Y;
    /** The coordinates of the end point of the interval. */
    public int mP2X, mP2Y;
    /** The slope of the tangent at the start point. */
    public float mSlope1X, mSlope1Y;
    /** The slope of the tangent at the end point. */
    public float mSlope2X, mSlope2Y;
    /** The interpolated coordinates.
     * The return variables of {@link #interpolate(float)} to avoid instantiations.
     */
    public float mInterpolatedX, mInterpolatedY;

    public HermiteInterpolator() {
        // Nothing to do with here.
    }

    /**
     * Reset this interpolator to point XY-coordinates data.
     * @param xCoords the array of x-coordinates. Valid data are in left-open interval
     *                <code>[minPos, maxPos)</code>.
     * @param yCoords the array of y-coordinates. Valid data are in left-open interval
     *                <code>[minPos, maxPos)</code>.
     * @param minPos the minimum index of left-open interval of valid data.
     * @param maxPos the maximum index of left-open interval of valid data.
     */
    @UsedForTesting
    public void reset(final int[] xCoords, final int[] yCoords, final int minPos,
            final int maxPos) {
        mXCoords = xCoords;
        mYCoords = yCoords;
        mMinPos = minPos;
        mMaxPos = maxPos;
    }

    /**
     * Set interpolation interval.
     * <p>
     * The start and end coordinates of the interval will be set in {@link #mP1X}, {@link #mP1Y},
     * {@link #mP2X}, and {@link #mP2Y}. The slope of the tangents at start and end points will be
     * set in {@link #mSlope1X}, {@link #mSlope1Y}, {@link #mSlope2X}, and {@link #mSlope2Y}.
     *
     * @param p0 the index just before interpolation interval. If <code>p1</code> points the start
     *           of valid points, <code>p0</code> must be less than <code>minPos</code> of
     *           {@link #reset(int[],int[],int,int)}.
     * @param p1 the start index of interpolation interval.
     * @param p2 the end index of interpolation interval.
     * @param p3 the index just after interpolation interval. If <code>p2</code> points the end of
     *           valid points, <code>p3</code> must be equal or greater than <code>maxPos</code> of
     *           {@link #reset(int[],int[],int,int)}.
     */
    @UsedForTesting
    public void setInterval(final int p0, final int p1, final int p2, final int p3) {
        mP1X = mXCoords[p1];
        mP1Y = mYCoords[p1];
        mP2X = mXCoords[p2];
        mP2Y = mYCoords[p2];
        // A(ax,ay) is the vector p1->p2.
        final int ax = mP2X - mP1X;
        final int ay = mP2Y - mP1Y;

        // Calculate the slope of the tangent at p1.
        if (p0 >= mMinPos) {
            // p1 has previous valid point p0.
            // The slope of the tangent is half of the vector p0->p2.
            mSlope1X = (mP2X - mXCoords[p0]) / 2.0f;
            mSlope1Y = (mP2Y - mYCoords[p0]) / 2.0f;
        } else if (p3 < mMaxPos) {
            // p1 has no previous valid point, but p2 has next valid point p3.
            // B(bx,by) is the slope vector of the tangent at p2.
            final float bx = (mXCoords[p3] - mP1X) / 2.0f;
            final float by = (mYCoords[p3] - mP1Y) / 2.0f;
            final float crossProdAB = ax * by - ay * bx;
            final float dotProdAB = ax * bx + ay * by;
            final float normASquare = ax * ax + ay * ay;
            final float invHalfNormASquare = 1.0f / normASquare / 2.0f;
            // The slope of the tangent is the mirror image of vector B to vector A.
            mSlope1X = invHalfNormASquare * (dotProdAB * ax + crossProdAB * ay);
            mSlope1Y = invHalfNormASquare * (dotProdAB * ay - crossProdAB * ax);
        } else {
            // p1 and p2 have no previous valid point. (Interval has only point p1 and p2)
            mSlope1X = ax;
            mSlope1Y = ay;
        }

        // Calculate the slope of the tangent at p2.
        if (p3 < mMaxPos) {
            // p2 has next valid point p3.
            // The slope of the tangent is half of the vector p1->p3.
            mSlope2X = (mXCoords[p3] - mP1X) / 2.0f;
            mSlope2Y = (mYCoords[p3] - mP1Y) / 2.0f;
        } else if (p0 >= mMinPos) {
            // p2 has no next valid point, but p1 has previous valid point p0.
            // B(bx,by) is the slope vector of the tangent at p1.
            final float bx = (mP2X - mXCoords[p0]) / 2.0f;
            final float by = (mP2Y - mYCoords[p0]) / 2.0f;
            final float crossProdAB = ax * by - ay * bx;
            final float dotProdAB = ax * bx + ay * by;
            final float normASquare = ax * ax + ay * ay;
            final float invHalfNormASquare = 1.0f / normASquare / 2.0f;
            // The slope of the tangent is the mirror image of vector B to vector A.
            mSlope2X = invHalfNormASquare * (dotProdAB * ax + crossProdAB * ay);
            mSlope2Y = invHalfNormASquare * (dotProdAB * ay - crossProdAB * ax);
        } else {
            // p1 and p2 has no previous valid point. (Interval has only point p1 and p2)
            mSlope2X = ax;
            mSlope2Y = ay;
        }
    }

    /**
     * Calculate interpolation value at <code>t</code> in unit interval <code>[0,1]</code>.
     * <p>
     * On the unit interval [0,1], given a starting point p1 at t=0 and an ending point p2 at t=1
     * with the slope of the tangent m1 at p1 and m2 at p2, the polynomial of cubic Hermite curve
     * can be defined by
     *   p(t) = (1+2t)(1-t)(1-t)*p1 + t(1-t)(1-t)*m1 + (3-2t)t^2*p2 + (t-1)t^2*m2
     * where t is an element of [0,1].
     * <p>
     * The interpolated XY-coordinates will be set in {@link #mInterpolatedX} and
     * {@link #mInterpolatedY}.
     *
     * @param t the interpolation parameter. The value must be in close interval <code>[0,1]</code>.
     */
    @UsedForTesting
    public void interpolate(final float t) {
        final float omt = 1.0f - t;
        final float tm2 = 2.0f * t;
        final float k1 = 1.0f + tm2;
        final float k2 = 3.0f - tm2;
        final float omt2 = omt * omt;
        final float t2 = t * t;
        mInterpolatedX = (k1 * mP1X + t * mSlope1X) * omt2 + (k2 * mP2X - omt * mSlope2X) * t2;
        mInterpolatedY = (k1 * mP1Y + t * mSlope1Y) * omt2 + (k2 * mP2Y - omt * mSlope2Y) * t2;
    }
}
+203 −0

File added.

Preview size limit exceeded, changes collapsed.