| /* |
| * Copyright (C) 2009 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 android.gesture; |
| |
| import android.graphics.Canvas; |
| import android.graphics.Paint; |
| import android.graphics.Path; |
| import android.graphics.RectF; |
| |
| import java.io.IOException; |
| import java.io.DataOutputStream; |
| import java.io.DataInputStream; |
| import java.util.ArrayList; |
| |
| /** |
| * A gesture stroke started on a touch down and ended on a touch up. A stroke |
| * consists of a sequence of timed points. One or multiple strokes form a gesture. |
| */ |
| public class GestureStroke { |
| static final float TOUCH_TOLERANCE = 3; |
| |
| public final RectF boundingBox; |
| |
| public final float length; |
| public final float[] points; |
| |
| private final long[] timestamps; |
| private Path mCachedPath; |
| |
| /** |
| * A constructor that constructs a gesture stroke from a list of gesture points. |
| * |
| * @param points |
| */ |
| public GestureStroke(ArrayList<GesturePoint> points) { |
| final int count = points.size(); |
| final float[] tmpPoints = new float[count * 2]; |
| final long[] times = new long[count]; |
| |
| RectF bx = null; |
| float len = 0; |
| int index = 0; |
| |
| for (int i = 0; i < count; i++) { |
| final GesturePoint p = points.get(i); |
| tmpPoints[i * 2] = p.x; |
| tmpPoints[i * 2 + 1] = p.y; |
| times[index] = p.timestamp; |
| |
| if (bx == null) { |
| bx = new RectF(); |
| bx.top = p.y; |
| bx.left = p.x; |
| bx.right = p.x; |
| bx.bottom = p.y; |
| len = 0; |
| } else { |
| len += Math.hypot(p.x - tmpPoints[(i - 1) * 2], p.y - tmpPoints[(i -1) * 2 + 1]); |
| bx.union(p.x, p.y); |
| } |
| index++; |
| } |
| |
| timestamps = times; |
| this.points = tmpPoints; |
| boundingBox = bx; |
| length = len; |
| } |
| |
| /** |
| * A faster constructor specially for cloning a stroke. |
| */ |
| private GestureStroke(RectF bbx, float len, float[] pts, long[] times) { |
| boundingBox = new RectF(bbx.left, bbx.top, bbx.right, bbx.bottom); |
| length = len; |
| points = pts.clone(); |
| timestamps = times.clone(); |
| } |
| |
| @Override |
| public Object clone() { |
| return new GestureStroke(boundingBox, length, points, timestamps); |
| } |
| |
| /** |
| * Draws the stroke with a given canvas and paint. |
| * |
| * @param canvas |
| */ |
| void draw(Canvas canvas, Paint paint) { |
| if (mCachedPath == null) { |
| makePath(); |
| } |
| |
| canvas.drawPath(mCachedPath, paint); |
| } |
| |
| public Path getPath() { |
| if (mCachedPath == null) { |
| makePath(); |
| } |
| |
| return mCachedPath; |
| } |
| |
| private void makePath() { |
| final float[] localPoints = points; |
| final int count = localPoints.length; |
| |
| Path path = null; |
| |
| float mX = 0; |
| float mY = 0; |
| |
| for (int i = 0; i < count; i += 2) { |
| float x = localPoints[i]; |
| float y = localPoints[i + 1]; |
| if (path == null) { |
| path = new Path(); |
| path.moveTo(x, y); |
| mX = x; |
| mY = y; |
| } else { |
| float dx = Math.abs(x - mX); |
| float dy = Math.abs(y - mY); |
| if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { |
| path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); |
| mX = x; |
| mY = y; |
| } |
| } |
| } |
| |
| mCachedPath = path; |
| } |
| |
| /** |
| * Converts the stroke to a Path of a given number of points. |
| * |
| * @param width the width of the bounding box of the target path |
| * @param height the height of the bounding box of the target path |
| * @param numSample the number of points needed |
| * |
| * @return the path |
| */ |
| public Path toPath(float width, float height, int numSample) { |
| final float[] pts = GestureUtils.temporalSampling(this, numSample); |
| final RectF rect = boundingBox; |
| |
| GestureUtils.translate(pts, -rect.left, -rect.top); |
| |
| float sx = width / rect.width(); |
| float sy = height / rect.height(); |
| float scale = sx > sy ? sy : sx; |
| GestureUtils.scale(pts, scale, scale); |
| |
| float mX = 0; |
| float mY = 0; |
| |
| Path path = null; |
| |
| final int count = pts.length; |
| |
| for (int i = 0; i < count; i += 2) { |
| float x = pts[i]; |
| float y = pts[i + 1]; |
| if (path == null) { |
| path = new Path(); |
| path.moveTo(x, y); |
| mX = x; |
| mY = y; |
| } else { |
| float dx = Math.abs(x - mX); |
| float dy = Math.abs(y - mY); |
| if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { |
| path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); |
| mX = x; |
| mY = y; |
| } |
| } |
| } |
| |
| return path; |
| } |
| |
| void serialize(DataOutputStream out) throws IOException { |
| final float[] pts = points; |
| final long[] times = timestamps; |
| final int count = points.length; |
| |
| // Write number of points |
| out.writeInt(count / 2); |
| |
| for (int i = 0; i < count; i += 2) { |
| // Write X |
| out.writeFloat(pts[i]); |
| // Write Y |
| out.writeFloat(pts[i + 1]); |
| // Write timestamp |
| out.writeLong(times[i / 2]); |
| } |
| } |
| |
| static GestureStroke deserialize(DataInputStream in) throws IOException { |
| // Number of points |
| final int count = in.readInt(); |
| |
| final ArrayList<GesturePoint> points = new ArrayList<GesturePoint>(count); |
| for (int i = 0; i < count; i++) { |
| points.add(GesturePoint.deserialize(in)); |
| } |
| |
| return new GestureStroke(points); |
| } |
| |
| /** |
| * Invalidates the cached path that is used to render the stroke. |
| */ |
| public void clearPath() { |
| if (mCachedPath != null) mCachedPath.rewind(); |
| } |
| |
| /** |
| * Computes an oriented bounding box of the stroke. |
| * |
| * @return OrientedBoundingBox |
| */ |
| public OrientedBoundingBox computeOrientedBoundingBox() { |
| return GestureUtils.computeOrientedBoundingBox(points); |
| } |
| } |