| /* |
| * Copyright (C) 2008-2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.gesture; |
| |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Paint; |
| import android.graphics.Path; |
| import android.graphics.RectF; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.util.Log; |
| |
| import java.io.IOException; |
| import java.io.DataOutputStream; |
| import java.io.DataInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.ByteArrayInputStream; |
| import java.util.ArrayList; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * A gesture is a hand-drawn shape on a touch screen. It can have one or multiple strokes. |
| * Each stroke is a sequence of timed points. A user-defined gesture can be recognized by |
| * a GestureLibrary and a built-in alphabet gesture can be recognized by a LetterRecognizer. |
| */ |
| |
| public class Gesture implements Parcelable { |
| private static final long GESTURE_ID_BASE = System.currentTimeMillis(); |
| |
| private static final int BITMAP_RENDERING_WIDTH = 2; |
| |
| private static final boolean BITMAP_RENDERING_ANTIALIAS = true; |
| private static final boolean BITMAP_RENDERING_DITHER = true; |
| |
| private static final AtomicInteger sGestureCount = new AtomicInteger(0); |
| |
| private final RectF mBoundingBox = new RectF(); |
| |
| // the same as its instance ID |
| private long mGestureID; |
| |
| private final ArrayList<GestureStroke> mStrokes = new ArrayList<GestureStroke>(); |
| |
| public Gesture() { |
| mGestureID = GESTURE_ID_BASE + sGestureCount.incrementAndGet(); |
| } |
| |
| @Override |
| public Object clone() { |
| Gesture gesture = new Gesture(); |
| gesture.mBoundingBox.set(mBoundingBox.left, mBoundingBox.top, |
| mBoundingBox.right, mBoundingBox.bottom); |
| final int count = mStrokes.size(); |
| for (int i = 0; i < count; i++) { |
| GestureStroke stroke = mStrokes.get(i); |
| gesture.mStrokes.add((GestureStroke)stroke.clone()); |
| } |
| return gesture; |
| } |
| |
| /** |
| * @return all the strokes of the gesture |
| */ |
| public ArrayList<GestureStroke> getStrokes() { |
| return mStrokes; |
| } |
| |
| /** |
| * @return the number of strokes included by this gesture |
| */ |
| public int getStrokesCount() { |
| return mStrokes.size(); |
| } |
| |
| /** |
| * Adds a stroke to the gesture. |
| * |
| * @param stroke |
| */ |
| public void addStroke(GestureStroke stroke) { |
| mStrokes.add(stroke); |
| mBoundingBox.union(stroke.boundingBox); |
| } |
| |
| /** |
| * Calculates the total length of the gesture. When there are multiple strokes in |
| * the gesture, this returns the sum of the lengths of all the strokes. |
| * |
| * @return the length of the gesture |
| */ |
| public float getLength() { |
| int len = 0; |
| final ArrayList<GestureStroke> strokes = mStrokes; |
| final int count = strokes.size(); |
| |
| for (int i = 0; i < count; i++) { |
| len += strokes.get(i).length; |
| } |
| |
| return len; |
| } |
| |
| /** |
| * @return the bounding box of the gesture |
| */ |
| public RectF getBoundingBox() { |
| return mBoundingBox; |
| } |
| |
| public Path toPath() { |
| return toPath(null); |
| } |
| |
| public Path toPath(Path path) { |
| if (path == null) path = new Path(); |
| |
| final ArrayList<GestureStroke> strokes = mStrokes; |
| final int count = strokes.size(); |
| |
| for (int i = 0; i < count; i++) { |
| path.addPath(strokes.get(i).getPath()); |
| } |
| |
| return path; |
| } |
| |
| public Path toPath(int width, int height, int edge, int numSample) { |
| return toPath(null, width, height, edge, numSample); |
| } |
| |
| public Path toPath(Path path, int width, int height, int edge, int numSample) { |
| if (path == null) path = new Path(); |
| |
| final ArrayList<GestureStroke> strokes = mStrokes; |
| final int count = strokes.size(); |
| |
| for (int i = 0; i < count; i++) { |
| path.addPath(strokes.get(i).toPath(width - 2 * edge, height - 2 * edge, numSample)); |
| } |
| |
| return path; |
| } |
| |
| /** |
| * Sets the id of the gesture. |
| * |
| * @param id |
| */ |
| void setID(long id) { |
| mGestureID = id; |
| } |
| |
| /** |
| * @return the id of the gesture |
| */ |
| public long getID() { |
| return mGestureID; |
| } |
| |
| /** |
| * Creates a bitmap of the gesture with a transparent background. |
| * |
| * @param width width of the target bitmap |
| * @param height height of the target bitmap |
| * @param edge the edge |
| * @param numSample |
| * @param color |
| * @return the bitmap |
| */ |
| public Bitmap toBitmap(int width, int height, int edge, int numSample, int color) { |
| final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); |
| final Canvas canvas = new Canvas(bitmap); |
| |
| canvas.translate(edge, edge); |
| |
| final Paint paint = new Paint(); |
| paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS); |
| paint.setDither(BITMAP_RENDERING_DITHER); |
| paint.setColor(color); |
| paint.setStyle(Paint.Style.STROKE); |
| paint.setStrokeJoin(Paint.Join.ROUND); |
| paint.setStrokeCap(Paint.Cap.ROUND); |
| paint.setStrokeWidth(BITMAP_RENDERING_WIDTH); |
| |
| final ArrayList<GestureStroke> strokes = mStrokes; |
| final int count = strokes.size(); |
| |
| for (int i = 0; i < count; i++) { |
| Path path = strokes.get(i).toPath(width - 2 * edge, height - 2 * edge, numSample); |
| canvas.drawPath(path, paint); |
| } |
| |
| return bitmap; |
| } |
| |
| /** |
| * Creates a bitmap of the gesture with a transparent background. |
| * |
| * @param width |
| * @param height |
| * @param inset |
| * @param color |
| * @return the bitmap |
| */ |
| public Bitmap toBitmap(int width, int height, int inset, int color) { |
| final Bitmap bitmap = Bitmap.createBitmap(width, height, |
| Bitmap.Config.ARGB_8888); |
| final Canvas canvas = new Canvas(bitmap); |
| |
| final Paint paint = new Paint(); |
| paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS); |
| paint.setDither(BITMAP_RENDERING_DITHER); |
| paint.setColor(color); |
| paint.setStyle(Paint.Style.STROKE); |
| paint.setStrokeJoin(Paint.Join.ROUND); |
| paint.setStrokeCap(Paint.Cap.ROUND); |
| paint.setStrokeWidth(BITMAP_RENDERING_WIDTH); |
| |
| final Path path = toPath(); |
| final RectF bounds = new RectF(); |
| path.computeBounds(bounds, true); |
| |
| final float sx = (width - 2 * inset) / bounds.width(); |
| final float sy = (height - 2 * inset) / bounds.height(); |
| final float scale = sx > sy ? sy : sx; |
| paint.setStrokeWidth(2.0f / scale); |
| |
| path.offset(-bounds.left + (width - bounds.width() * scale) / 2.0f, |
| -bounds.top + (height - bounds.height() * scale) / 2.0f); |
| |
| canvas.translate(inset, inset); |
| canvas.scale(scale, scale); |
| |
| canvas.drawPath(path, paint); |
| |
| return bitmap; |
| } |
| |
| void serialize(DataOutputStream out) throws IOException { |
| final ArrayList<GestureStroke> strokes = mStrokes; |
| final int count = strokes.size(); |
| |
| // Write gesture ID |
| out.writeLong(mGestureID); |
| // Write number of strokes |
| out.writeInt(count); |
| |
| for (int i = 0; i < count; i++) { |
| strokes.get(i).serialize(out); |
| } |
| } |
| |
| static Gesture deserialize(DataInputStream in) throws IOException { |
| final Gesture gesture = new Gesture(); |
| |
| // Gesture ID |
| gesture.mGestureID = in.readLong(); |
| // Number of strokes |
| final int count = in.readInt(); |
| |
| for (int i = 0; i < count; i++) { |
| gesture.addStroke(GestureStroke.deserialize(in)); |
| } |
| |
| return gesture; |
| } |
| |
| public static final Parcelable.Creator<Gesture> CREATOR = new Parcelable.Creator<Gesture>() { |
| public Gesture createFromParcel(Parcel in) { |
| Gesture gesture = null; |
| final long gestureID = in.readLong(); |
| |
| final DataInputStream inStream = new DataInputStream( |
| new ByteArrayInputStream(in.createByteArray())); |
| |
| try { |
| gesture = deserialize(inStream); |
| } catch (IOException e) { |
| Log.e(GestureConstants.LOG_TAG, "Error reading Gesture from parcel:", e); |
| } finally { |
| GestureUtils.closeStream(inStream); |
| } |
| |
| if (gesture != null) { |
| gesture.mGestureID = gestureID; |
| } |
| |
| return gesture; |
| } |
| |
| public Gesture[] newArray(int size) { |
| return new Gesture[size]; |
| } |
| }; |
| |
| public void writeToParcel(Parcel out, int flags) { |
| out.writeLong(mGestureID); |
| |
| boolean result = false; |
| final ByteArrayOutputStream byteStream = |
| new ByteArrayOutputStream(GestureConstants.IO_BUFFER_SIZE); |
| final DataOutputStream outStream = new DataOutputStream(byteStream); |
| |
| try { |
| serialize(outStream); |
| result = true; |
| } catch (IOException e) { |
| Log.e(GestureConstants.LOG_TAG, "Error writing Gesture to parcel:", e); |
| } finally { |
| GestureUtils.closeStream(outStream); |
| GestureUtils.closeStream(byteStream); |
| } |
| |
| if (result) { |
| out.writeByteArray(byteStream.toByteArray()); |
| } |
| } |
| |
| public int describeContents() { |
| return 0; |
| } |
| } |
| |