• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.gesture;
18 
19 import android.graphics.Canvas;
20 import android.graphics.Paint;
21 import android.graphics.Path;
22 import android.graphics.RectF;
23 
24 import java.io.IOException;
25 import java.io.DataOutputStream;
26 import java.io.DataInputStream;
27 import java.util.ArrayList;
28 
29 /**
30  * A gesture stroke started on a touch down and ended on a touch up. A stroke
31  * consists of a sequence of timed points. One or multiple strokes form a gesture.
32  */
33 public 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      * A constructor that constructs a gesture stroke from a list of gesture points.
46      *
47      * @param points
48      */
GestureStroke(ArrayList<GesturePoint> points)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      * A faster constructor specially for cloning a stroke.
87      */
GestureStroke(RectF bbx, float len, float[] pts, long[] times)88     private GestureStroke(RectF bbx, float len, float[] pts, long[] times) {
89         boundingBox = new RectF(bbx.left, bbx.top, bbx.right, bbx.bottom);
90         length = len;
91         points = pts.clone();
92         timestamps = times.clone();
93     }
94 
95     @Override
clone()96     public Object clone() {
97         return new GestureStroke(boundingBox, length, points, timestamps);
98     }
99 
100     /**
101      * Draws the stroke with a given canvas and paint.
102      *
103      * @param canvas
104      */
draw(Canvas canvas, Paint paint)105     void draw(Canvas canvas, Paint paint) {
106         if (mCachedPath == null) {
107             makePath();
108         }
109 
110         canvas.drawPath(mCachedPath, paint);
111     }
112 
getPath()113     public Path getPath() {
114         if (mCachedPath == null) {
115             makePath();
116         }
117 
118         return mCachedPath;
119     }
120 
makePath()121     private void makePath() {
122         final float[] localPoints = points;
123         final int count = localPoints.length;
124 
125         Path path = null;
126 
127         float mX = 0;
128         float mY = 0;
129 
130         for (int i = 0; i < count; i += 2) {
131             float x = localPoints[i];
132             float y = localPoints[i + 1];
133             if (path == null) {
134                 path = new Path();
135                 path.moveTo(x, y);
136                 mX = x;
137                 mY = y;
138             } else {
139                 float dx = Math.abs(x - mX);
140                 float dy = Math.abs(y - mY);
141                 if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
142                     path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
143                     mX = x;
144                     mY = y;
145                 }
146             }
147         }
148 
149         mCachedPath = path;
150     }
151 
152     /**
153      * Converts the stroke to a Path of a given number of points.
154      *
155      * @param width the width of the bounding box of the target path
156      * @param height the height of the bounding box of the target path
157      * @param numSample the number of points needed
158      *
159      * @return the path
160      */
toPath(float width, float height, int numSample)161     public Path toPath(float width, float height, int numSample) {
162         final float[] pts = GestureUtils.temporalSampling(this, numSample);
163         final RectF rect = boundingBox;
164 
165         GestureUtils.translate(pts, -rect.left, -rect.top);
166 
167         float sx = width / rect.width();
168         float sy = height / rect.height();
169         float scale = sx > sy ? sy : sx;
170         GestureUtils.scale(pts, scale, scale);
171 
172         float mX = 0;
173         float mY = 0;
174 
175         Path path = null;
176 
177         final int count = pts.length;
178 
179         for (int i = 0; i < count; i += 2) {
180             float x = pts[i];
181             float y = pts[i + 1];
182             if (path == null) {
183                 path = new Path();
184                 path.moveTo(x, y);
185                 mX = x;
186                 mY = y;
187             } else {
188                 float dx = Math.abs(x - mX);
189                 float dy = Math.abs(y - mY);
190                 if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
191                     path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
192                     mX = x;
193                     mY = y;
194                 }
195             }
196         }
197 
198         return path;
199     }
200 
serialize(DataOutputStream out)201     void serialize(DataOutputStream out) throws IOException {
202         final float[] pts = points;
203         final long[] times = timestamps;
204         final int count = points.length;
205 
206         // Write number of points
207         out.writeInt(count / 2);
208 
209         for (int i = 0; i < count; i += 2) {
210             // Write X
211             out.writeFloat(pts[i]);
212             // Write Y
213             out.writeFloat(pts[i + 1]);
214             // Write timestamp
215             out.writeLong(times[i / 2]);
216         }
217     }
218 
deserialize(DataInputStream in)219     static GestureStroke deserialize(DataInputStream in) throws IOException {
220         // Number of points
221         final int count = in.readInt();
222 
223         final ArrayList<GesturePoint> points = new ArrayList<GesturePoint>(count);
224         for (int i = 0; i < count; i++) {
225             points.add(GesturePoint.deserialize(in));
226         }
227 
228         return new GestureStroke(points);
229     }
230 
231     /**
232      * Invalidates the cached path that is used to render the stroke.
233      */
clearPath()234     public void clearPath() {
235         if (mCachedPath != null) mCachedPath.rewind();
236     }
237 
238     /**
239      * Computes an oriented bounding box of the stroke.
240      *
241      * @return OrientedBoundingBox
242      */
computeOrientedBoundingBox()243     public OrientedBoundingBox computeOrientedBoundingBox() {
244         return GestureUtils.computeOrientedBoundingBox(points);
245     }
246 }
247