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
17package android.gesture;
18
19import android.graphics.Bitmap;
20import android.graphics.Canvas;
21import android.graphics.Paint;
22import android.graphics.Path;
23import android.graphics.RectF;
24import android.os.Parcel;
25import android.os.Parcelable;
26import android.util.Log;
27
28import java.io.IOException;
29import java.io.DataOutputStream;
30import java.io.DataInputStream;
31import java.io.ByteArrayOutputStream;
32import java.io.ByteArrayInputStream;
33import java.util.ArrayList;
34import 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
42public 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
59    public Gesture() {
60        mGestureID = GESTURE_ID_BASE + sGestureCount.incrementAndGet();
61    }
62
63    @Override
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     */
79    public ArrayList<GestureStroke> getStrokes() {
80        return mStrokes;
81    }
82
83    /**
84     * @return the number of strokes included by this gesture
85     */
86    public int getStrokesCount() {
87        return mStrokes.size();
88    }
89
90    /**
91     * Adds a stroke to the gesture.
92     *
93     * @param stroke
94     */
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     */
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     */
121    public RectF getBoundingBox() {
122        return mBoundingBox;
123    }
124
125    public Path toPath() {
126        return toPath(null);
127    }
128
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
142    public Path toPath(int width, int height, int edge, int numSample) {
143        return toPath(null, width, height, edge, numSample);
144    }
145
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     */
164    void setID(long id) {
165        mGestureID = id;
166    }
167
168    /**
169     * @return the id of the gesture
170     */
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     */
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     */
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
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
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 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
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
334    public int describeContents() {
335        return 0;
336    }
337}
338
339