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 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