/*
* Copyright (C) 2012 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 android.content.res.TypedArray;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.ResizableIntArray;
public final class GestureStrokeWithPreviewPoints extends GestureStroke {
public static final int PREVIEW_CAPACITY = 256;
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 final GestureStrokePreviewParams mPreviewParams;
private int mStrokeId;
private int mLastPreviewSize;
private final HermiteInterpolator mInterpolator = new HermiteInterpolator();
private int mLastInterpolatedPreviewIndex;
private int mLastX;
private int mLastY;
private double mDistanceFromLastSample;
public static final class GestureStrokePreviewParams {
public final double mMinSamplingDistance; // in pixel
public final double mMaxInterpolationAngularThreshold; // in radian
public final double mMaxInterpolationDistanceThreshold; // in pixel
public final int mMaxInterpolationSegments;
public static final GestureStrokePreviewParams DEFAULT = new GestureStrokePreviewParams();
private static final int DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD = 15; // in degree
private GestureStrokePreviewParams() {
mMinSamplingDistance = 0.0d;
mMaxInterpolationAngularThreshold =
degreeToRadian(DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD);
mMaxInterpolationDistanceThreshold = mMinSamplingDistance;
mMaxInterpolationSegments = 4;
}
private static double degreeToRadian(final int degree) {
return (double)degree / 180.0d * Math.PI;
}
public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) {
mMinSamplingDistance = mainKeyboardViewAttr.getDimension(
R.styleable.MainKeyboardView_gesturePreviewTrailMinSamplingDistance,
(float)DEFAULT.mMinSamplingDistance);
final int interpolationAngularDegree = mainKeyboardViewAttr.getInteger(R.styleable
.MainKeyboardView_gesturePreviewTrailMaxInterpolationAngularThreshold, 0);
mMaxInterpolationAngularThreshold = (interpolationAngularDegree <= 0)
? DEFAULT.mMaxInterpolationAngularThreshold
: degreeToRadian(interpolationAngularDegree);
mMaxInterpolationDistanceThreshold = mainKeyboardViewAttr.getDimension(R.styleable
.MainKeyboardView_gesturePreviewTrailMaxInterpolationDistanceThreshold,
(float)DEFAULT.mMaxInterpolationDistanceThreshold);
mMaxInterpolationSegments = mainKeyboardViewAttr.getInteger(
R.styleable.MainKeyboardView_gesturePreviewTrailMaxInterpolationSegments,
DEFAULT.mMaxInterpolationSegments);
}
}
public GestureStrokeWithPreviewPoints(final int pointerId,
final GestureStrokeParams strokeParams,
final GestureStrokePreviewParams previewParams) {
super(pointerId, strokeParams);
mPreviewParams = previewParams;
}
@Override
protected void reset() {
super.reset();
mStrokeId++;
mLastPreviewSize = 0;
mLastInterpolatedPreviewIndex = 0;
mPreviewEventTimes.setLength(0);
mPreviewXCoordinates.setLength(0);
mPreviewYCoordinates.setLength(0);
}
public int getGestureStrokeId() {
return mStrokeId;
}
private boolean needsSampling(final int x, final int y) {
mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY);
mLastX = x;
mLastY = y;
final boolean isDownEvent = (mPreviewEventTimes.getLength() == 0);
if (mDistanceFromLastSample >= mPreviewParams.mMinSamplingDistance || isDownEvent) {
mDistanceFromLastSample = 0.0d;
return true;
}
return false;
}
@Override
public boolean addPointOnKeyboard(final int x, final int y, final int time,
final boolean isMajorEvent) {
if (needsSampling(x, y)) {
mPreviewEventTimes.add(time);
mPreviewXCoordinates.add(x);
mPreviewYCoordinates.add(y);
}
return super.addPointOnKeyboard(x, y, time, isMajorEvent);
}
public void appendPreviewStroke(final ResizableIntArray eventTimes,
final ResizableIntArray xCoords, final ResizableIntArray yCoords) {
final int length = mPreviewEventTimes.getLength() - mLastPreviewSize;
if (length <= 0) {
return;
}
eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length);
xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length);
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
* eventTimes
, xCoords
, and yCoords
.
* @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, final ResizableIntArray types) {
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 deltaAngle = Math.abs(angularDiff(m2, m1));
final int segmentsByAngle = (int)Math.ceil(
deltaAngle / mPreviewParams.mMaxInterpolationAngularThreshold);
final double deltaDistance = Math.hypot(mInterpolator.mP1X - mInterpolator.mP2X,
mInterpolator.mP1Y - mInterpolator.mP2Y);
final int segmentsByDistance = (int)Math.ceil(deltaDistance
/ mPreviewParams.mMaxInterpolationDistanceThreshold);
final int segments = Math.min(mPreviewParams.mMaxInterpolationSegments,
Math.max(segmentsByAngle, segmentsByDistance));
final int t1 = eventTimes.get(d1);
final int dt = pt[p2] - pt[p1];
d1++;
for (int i = 1; i < segments; i++) {
final float t = i / (float)segments;
mInterpolator.interpolate(t);
eventTimes.add(d1, (int)(dt * t) + t1);
xCoords.add(d1, (int)mInterpolator.mInterpolatedX);
yCoords.add(d1, (int)mInterpolator.mInterpolatedY);
if (GesturePreviewTrail.DBG_SHOW_POINTS) {
types.add(d1, GesturePreviewTrail.POINT_TYPE_INTERPOLATED);
}
d1++;
}
eventTimes.add(d1, pt[p2]);
xCoords.add(d1, px[p2]);
yCoords.add(d1, py[p2]);
if (GesturePreviewTrail.DBG_SHOW_POINTS) {
types.add(d1, GesturePreviewTrail.POINT_TYPE_SAMPLED);
}
}
return lastInterpolatedDrawIndex;
}
private static final double TWO_PI = Math.PI * 2.0d;
/**
* Calculate the angular of rotation from a0
to a1
.
*
* @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;
}
}