/* * 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. */ 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 mStrokes = new ArrayList(); 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 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 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 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 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 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 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 CREATOR = new Parcelable.Creator() { 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; } }