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