1 /*
2  * Copyright (C) 2008-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.Bitmap;
20 import android.graphics.Canvas;
21 import android.graphics.Paint;
22 import android.graphics.Path;
23 import android.graphics.RectF;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.util.Log;
27 
28 import java.io.IOException;
29 import java.io.DataOutputStream;
30 import java.io.DataInputStream;
31 import java.io.ByteArrayOutputStream;
32 import java.io.ByteArrayInputStream;
33 import java.util.ArrayList;
34 import java.util.concurrent.atomic.AtomicInteger;
35 
36 /**
37  * A gesture is a hand-drawn shape on a touch screen. It can have one or multiple strokes.
38  * Each stroke is a sequence of timed points. A user-defined gesture can be recognized by
39  * a GestureLibrary.
40  */
41 
42 public class Gesture implements Parcelable {
43     private static final long GESTURE_ID_BASE = System.currentTimeMillis();
44 
45     private static final int BITMAP_RENDERING_WIDTH = 2;
46 
47     private static final boolean BITMAP_RENDERING_ANTIALIAS = true;
48     private static final boolean BITMAP_RENDERING_DITHER = true;
49 
50     private static final AtomicInteger sGestureCount = new AtomicInteger(0);
51 
52     private final RectF mBoundingBox = new RectF();
53 
54     // the same as its instance ID
55     private long mGestureID;
56 
57     private final ArrayList<GestureStroke> mStrokes = new ArrayList<GestureStroke>();
58 
Gesture()59     public Gesture() {
60         mGestureID = GESTURE_ID_BASE + sGestureCount.incrementAndGet();
61     }
62 
63     @Override
clone()64     public Object clone() {
65         Gesture gesture = new Gesture();
66         gesture.mBoundingBox.set(mBoundingBox.left, mBoundingBox.top,
67                                         mBoundingBox.right, mBoundingBox.bottom);
68         final int count = mStrokes.size();
69         for (int i = 0; i < count; i++) {
70             GestureStroke stroke = mStrokes.get(i);
71             gesture.mStrokes.add((GestureStroke)stroke.clone());
72         }
73         return gesture;
74     }
75 
76     /**
77      * @return all the strokes of the gesture
78      */
getStrokes()79     public ArrayList<GestureStroke> getStrokes() {
80         return mStrokes;
81     }
82 
83     /**
84      * @return the number of strokes included by this gesture
85      */
getStrokesCount()86     public int getStrokesCount() {
87         return mStrokes.size();
88     }
89 
90     /**
91      * Adds a stroke to the gesture.
92      *
93      * @param stroke
94      */
addStroke(GestureStroke stroke)95     public void addStroke(GestureStroke stroke) {
96         mStrokes.add(stroke);
97         mBoundingBox.union(stroke.boundingBox);
98     }
99 
100     /**
101      * Calculates the total length of the gesture. When there are multiple strokes in
102      * the gesture, this returns the sum of the lengths of all the strokes.
103      *
104      * @return the length of the gesture
105      */
getLength()106     public float getLength() {
107         int len = 0;
108         final ArrayList<GestureStroke> strokes = mStrokes;
109         final int count = strokes.size();
110 
111         for (int i = 0; i < count; i++) {
112             len += strokes.get(i).length;
113         }
114 
115         return len;
116     }
117 
118     /**
119      * @return the bounding box of the gesture
120      */
getBoundingBox()121     public RectF getBoundingBox() {
122         return mBoundingBox;
123     }
124 
toPath()125     public Path toPath() {
126         return toPath(null);
127     }
128 
toPath(Path path)129     public Path toPath(Path path) {
130         if (path == null) path = new Path();
131 
132         final ArrayList<GestureStroke> strokes = mStrokes;
133         final int count = strokes.size();
134 
135         for (int i = 0; i < count; i++) {
136             path.addPath(strokes.get(i).getPath());
137         }
138 
139         return path;
140     }
141 
toPath(int width, int height, int edge, int numSample)142     public Path toPath(int width, int height, int edge, int numSample) {
143         return toPath(null, width, height, edge, numSample);
144     }
145 
toPath(Path path, int width, int height, int edge, int numSample)146     public Path toPath(Path path, int width, int height, int edge, int numSample) {
147         if (path == null) path = new Path();
148 
149         final ArrayList<GestureStroke> strokes = mStrokes;
150         final int count = strokes.size();
151 
152         for (int i = 0; i < count; i++) {
153             path.addPath(strokes.get(i).toPath(width - 2 * edge, height - 2 * edge, numSample));
154         }
155 
156         return path;
157     }
158 
159     /**
160      * Sets the id of the gesture.
161      *
162      * @param id
163      */
setID(long id)164     void setID(long id) {
165         mGestureID = id;
166     }
167 
168     /**
169      * @return the id of the gesture
170      */
getID()171     public long getID() {
172         return mGestureID;
173     }
174 
175     /**
176      * Creates a bitmap of the gesture with a transparent background.
177      *
178      * @param width width of the target bitmap
179      * @param height height of the target bitmap
180      * @param edge the edge
181      * @param numSample
182      * @param color
183      * @return the bitmap
184      */
toBitmap(int width, int height, int edge, int numSample, int color)185     public Bitmap toBitmap(int width, int height, int edge, int numSample, int color) {
186         final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
187         final Canvas canvas = new Canvas(bitmap);
188 
189         canvas.translate(edge, edge);
190 
191         final Paint paint = new Paint();
192         paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS);
193         paint.setDither(BITMAP_RENDERING_DITHER);
194         paint.setColor(color);
195         paint.setStyle(Paint.Style.STROKE);
196         paint.setStrokeJoin(Paint.Join.ROUND);
197         paint.setStrokeCap(Paint.Cap.ROUND);
198         paint.setStrokeWidth(BITMAP_RENDERING_WIDTH);
199 
200         final ArrayList<GestureStroke> strokes = mStrokes;
201         final int count = strokes.size();
202 
203         for (int i = 0; i < count; i++) {
204             Path path = strokes.get(i).toPath(width - 2 * edge, height - 2 * edge, numSample);
205             canvas.drawPath(path, paint);
206         }
207 
208         return bitmap;
209     }
210 
211     /**
212      * Creates a bitmap of the gesture with a transparent background.
213      *
214      * @param width
215      * @param height
216      * @param inset
217      * @param color
218      * @return the bitmap
219      */
toBitmap(int width, int height, int inset, int color)220     public Bitmap toBitmap(int width, int height, int inset, int color) {
221         final Bitmap bitmap = Bitmap.createBitmap(width, height,
222                 Bitmap.Config.ARGB_8888);
223         final Canvas canvas = new Canvas(bitmap);
224 
225         final Paint paint = new Paint();
226         paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS);
227         paint.setDither(BITMAP_RENDERING_DITHER);
228         paint.setColor(color);
229         paint.setStyle(Paint.Style.STROKE);
230         paint.setStrokeJoin(Paint.Join.ROUND);
231         paint.setStrokeCap(Paint.Cap.ROUND);
232         paint.setStrokeWidth(BITMAP_RENDERING_WIDTH);
233 
234         final Path path = toPath();
235         final RectF bounds = new RectF();
236         path.computeBounds(bounds, true);
237 
238         final float sx = (width - 2 * inset) / bounds.width();
239         final float sy = (height - 2 * inset) / bounds.height();
240         final float scale = sx > sy ? sy : sx;
241         paint.setStrokeWidth(2.0f / scale);
242 
243         path.offset(-bounds.left + (width - bounds.width() * scale) / 2.0f,
244                 -bounds.top + (height - bounds.height() * scale) / 2.0f);
245 
246         canvas.translate(inset, inset);
247         canvas.scale(scale, scale);
248 
249         canvas.drawPath(path, paint);
250 
251         return bitmap;
252     }
253 
serialize(DataOutputStream out)254     void serialize(DataOutputStream out) throws IOException {
255         final ArrayList<GestureStroke> strokes = mStrokes;
256         final int count = strokes.size();
257 
258         // Write gesture ID
259         out.writeLong(mGestureID);
260         // Write number of strokes
261         out.writeInt(count);
262 
263         for (int i = 0; i < count; i++) {
264             strokes.get(i).serialize(out);
265         }
266     }
267 
deserialize(DataInputStream in)268     static Gesture deserialize(DataInputStream in) throws IOException {
269         final Gesture gesture = new Gesture();
270 
271         // Gesture ID
272         gesture.mGestureID = in.readLong();
273         // Number of strokes
274         final int count = in.readInt();
275 
276         for (int i = 0; i < count; i++) {
277             gesture.addStroke(GestureStroke.deserialize(in));
278         }
279 
280         return gesture;
281     }
282 
283     public static final @android.annotation.NonNull Parcelable.Creator<Gesture> CREATOR = new Parcelable.Creator<Gesture>() {
284         public Gesture createFromParcel(Parcel in) {
285             Gesture gesture = null;
286             final long gestureID = in.readLong();
287 
288             final DataInputStream inStream = new DataInputStream(
289                     new ByteArrayInputStream(in.createByteArray()));
290 
291             try {
292                 gesture = deserialize(inStream);
293             } catch (IOException e) {
294                 Log.e(GestureConstants.LOG_TAG, "Error reading Gesture from parcel:", e);
295             } finally {
296                 GestureUtils.closeStream(inStream);
297             }
298 
299             if (gesture != null) {
300                 gesture.mGestureID = gestureID;
301             }
302 
303             return gesture;
304         }
305 
306         public Gesture[] newArray(int size) {
307             return new Gesture[size];
308         }
309     };
310 
writeToParcel(Parcel out, int flags)311     public void writeToParcel(Parcel out, int flags) {
312         out.writeLong(mGestureID);
313 
314         boolean result = false;
315         final ByteArrayOutputStream byteStream =
316                 new ByteArrayOutputStream(GestureConstants.IO_BUFFER_SIZE);
317         final DataOutputStream outStream = new DataOutputStream(byteStream);
318 
319         try {
320             serialize(outStream);
321             result = true;
322         } catch (IOException e) {
323             Log.e(GestureConstants.LOG_TAG, "Error writing Gesture to parcel:", e);
324         } finally {
325             GestureUtils.closeStream(outStream);
326             GestureUtils.closeStream(byteStream);
327         }
328 
329         if (result) {
330             out.writeByteArray(byteStream.toByteArray());
331         }
332     }
333 
describeContents()334     public int describeContents() {
335         return 0;
336     }
337 }
338 
339