GestureStroke.java revision 436466d75edb5f6fd848504d998f244426ea5a09
1/*
2 * Copyright (C) 2009 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
17package android.gesture;
18
19import android.graphics.Canvas;
20import android.graphics.Matrix;
21import android.graphics.Paint;
22import android.graphics.Path;
23import android.graphics.RectF;
24
25import java.io.IOException;
26import java.io.DataOutputStream;
27import java.io.DataInputStream;
28import java.util.ArrayList;
29
30/**
31 * A gesture stroke started on a touch down and ended on a touch up.
32 */
33public class GestureStroke {
34    static final float TOUCH_TOLERANCE = 3;
35
36    public final RectF boundingBox;
37
38    public final float length;
39    public final float[] points;
40
41    private final long[] timestamps;
42    private Path mCachedPath;
43
44    /**
45     * Construct a gesture stroke from a list of gesture points
46     *
47     * @param points
48     */
49    public GestureStroke(ArrayList<GesturePoint> points) {
50        final int count = points.size();
51        final float[] tmpPoints = new float[count * 2];
52        final long[] times = new long[count];
53
54        RectF bx = null;
55        float len = 0;
56        int index = 0;
57
58        for (int i = 0; i < count; i++) {
59            final GesturePoint p = points.get(i);
60            tmpPoints[i * 2] = p.x;
61            tmpPoints[i * 2 + 1] = p.y;
62            times[index] = p.timestamp;
63
64            if (bx == null) {
65                bx = new RectF();
66                bx.top = p.y;
67                bx.left = p.x;
68                bx.right = p.x;
69                bx.bottom = p.y;
70                len = 0;
71            } else {
72                len += Math.sqrt(Math.pow(p.x - tmpPoints[(i - 1) * 2], 2)
73                        + Math.pow(p.y - tmpPoints[(i -1 ) * 2 + 1], 2));
74                bx.union(p.x, p.y);
75            }
76            index++;
77        }
78
79        timestamps = times;
80        this.points = tmpPoints;
81        boundingBox = bx;
82        length = len;
83    }
84
85    /**
86     * Draw the gesture with a given canvas and paint
87     *
88     * @param canvas
89     */
90    void draw(Canvas canvas, Paint paint) {
91        if (mCachedPath == null) {
92            makePath();
93        }
94
95        canvas.drawPath(mCachedPath, paint);
96    }
97
98    public Path getPath() {
99        if (mCachedPath == null) {
100            makePath();
101        }
102
103        return mCachedPath;
104    }
105
106    private void makePath() {
107        final float[] localPoints = points;
108        final int count = localPoints.length;
109
110        Path path = null;
111
112        float mX = 0;
113        float mY = 0;
114
115        for (int i = 0; i < count; i += 2) {
116            float x = localPoints[i];
117            float y = localPoints[i + 1];
118            if (path == null) {
119                path = new Path();
120                path.moveTo(x, y);
121                mX = x;
122                mY = y;
123            } else {
124                float dx = Math.abs(x - mX);
125                float dy = Math.abs(y - mY);
126                if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
127                    path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
128                    mX = x;
129                    mY = y;
130                }
131            }
132        }
133
134        mCachedPath = path;
135    }
136
137    /**
138     * Convert the stroke to a Path based on the number of points
139     *
140     * @param width the width of the bounding box of the target path
141     * @param height the height of the bounding box of the target path
142     * @param numSample the number of points needed
143     *
144     * @return the path
145     */
146    public Path toPath(float width, float height, int numSample) {
147        final float[] pts = GestureUtilities.temporalSampling(this, numSample);
148        final RectF rect = boundingBox;
149
150        final Matrix matrix = new Matrix();
151        matrix.setTranslate(-rect.left, -rect.top);
152        matrix.postScale(width / rect.width(), height / rect.height());
153        matrix.mapPoints(pts);
154
155        float mX = 0;
156        float mY = 0;
157
158        Path path = null;
159
160        final int count = pts.length;
161
162        for (int i = 0; i < count; i += 2) {
163            float x = pts[i];
164            float y = pts[i + 1];
165            if (path == null) {
166                path = new Path();
167                path.moveTo(x, y);
168                mX = x;
169                mY = y;
170            } else {
171                float dx = Math.abs(x - mX);
172                float dy = Math.abs(y - mY);
173                if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
174                    path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
175                    mX = x;
176                    mY = y;
177                }
178            }
179        }
180
181        return path;
182    }
183
184    void serialize(DataOutputStream out) throws IOException {
185        final float[] pts = points;
186        final long[] times = timestamps;
187        final int count = points.length;
188
189        // Write number of points
190        out.writeInt(count / 2);
191
192        for (int i = 0; i < count; i += 2) {
193            // Write X
194            out.writeFloat(pts[i]);
195            // Write Y
196            out.writeFloat(pts[i + 1]);
197            // Write timestamp
198            out.writeLong(times[i / 2]);
199        }
200    }
201
202    static GestureStroke deserialize(DataInputStream in) throws IOException {
203        // Number of points
204        final int count = in.readInt();
205
206        final ArrayList<GesturePoint> points = new ArrayList<GesturePoint>(count);
207        for (int i = 0; i < count; i++) {
208            points.add(GesturePoint.deserialize(in));
209        }
210
211        return new GestureStroke(points);
212    }
213
214    /**
215     * Invalidate the cached path that is used to render the stroke
216     */
217    public void clearPath() {
218        if (mCachedPath != null) mCachedPath.rewind();
219    }
220
221    /**
222     * Compute an oriented bounding box of the stroke
223     * @return OrientedBoundingBox
224     */
225    public OrientedBoundingBox computeOrientedBoundingBox() {
226        return GestureUtilities.computeOrientedBoundingBox(points);
227    }
228}
229