1 /* 2 * Copyright (C) 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.Canvas; 20 import android.graphics.Paint; 21 import android.graphics.Path; 22 import android.graphics.RectF; 23 24 import java.io.IOException; 25 import java.io.DataOutputStream; 26 import java.io.DataInputStream; 27 import java.util.ArrayList; 28 29 /** 30 * A gesture stroke started on a touch down and ended on a touch up. A stroke 31 * consists of a sequence of timed points. One or multiple strokes form a gesture. 32 */ 33 public class GestureStroke { 34 static final float TOUCH_TOLERANCE = 3; 35 36 public final RectF boundingBox; 37 38 public final float length; 39 public final float[] points; 40 41 private final long[] timestamps; 42 private Path mCachedPath; 43 44 /** 45 * A constructor that constructs a gesture stroke from a list of gesture points. 46 * 47 * @param points 48 */ GestureStroke(ArrayList<GesturePoint> points)49 public GestureStroke(ArrayList<GesturePoint> points) { 50 final int count = points.size(); 51 final float[] tmpPoints = new float[count * 2]; 52 final long[] times = new long[count]; 53 54 RectF bx = null; 55 float len = 0; 56 int index = 0; 57 58 for (int i = 0; i < count; i++) { 59 final GesturePoint p = points.get(i); 60 tmpPoints[i * 2] = p.x; 61 tmpPoints[i * 2 + 1] = p.y; 62 times[index] = p.timestamp; 63 64 if (bx == null) { 65 bx = new RectF(); 66 bx.top = p.y; 67 bx.left = p.x; 68 bx.right = p.x; 69 bx.bottom = p.y; 70 len = 0; 71 } else { 72 len += Math.hypot(p.x - tmpPoints[(i - 1) * 2], p.y - tmpPoints[(i -1) * 2 + 1]); 73 bx.union(p.x, p.y); 74 } 75 index++; 76 } 77 78 timestamps = times; 79 this.points = tmpPoints; 80 boundingBox = bx; 81 length = len; 82 } 83 84 /** 85 * A faster constructor specially for cloning a stroke. 86 */ GestureStroke(RectF bbx, float len, float[] pts, long[] times)87 private GestureStroke(RectF bbx, float len, float[] pts, long[] times) { 88 boundingBox = new RectF(bbx.left, bbx.top, bbx.right, bbx.bottom); 89 length = len; 90 points = pts.clone(); 91 timestamps = times.clone(); 92 } 93 94 @Override clone()95 public Object clone() { 96 return new GestureStroke(boundingBox, length, points, timestamps); 97 } 98 99 /** 100 * Draws the stroke with a given canvas and paint. 101 * 102 * @param canvas 103 */ draw(Canvas canvas, Paint paint)104 void draw(Canvas canvas, Paint paint) { 105 if (mCachedPath == null) { 106 makePath(); 107 } 108 109 canvas.drawPath(mCachedPath, paint); 110 } 111 getPath()112 public Path getPath() { 113 if (mCachedPath == null) { 114 makePath(); 115 } 116 117 return mCachedPath; 118 } 119 makePath()120 private void makePath() { 121 final float[] localPoints = points; 122 final int count = localPoints.length; 123 124 Path path = null; 125 126 float mX = 0; 127 float mY = 0; 128 129 for (int i = 0; i < count; i += 2) { 130 float x = localPoints[i]; 131 float y = localPoints[i + 1]; 132 if (path == null) { 133 path = new Path(); 134 path.moveTo(x, y); 135 mX = x; 136 mY = y; 137 } else { 138 float dx = Math.abs(x - mX); 139 float dy = Math.abs(y - mY); 140 if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { 141 path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); 142 mX = x; 143 mY = y; 144 } 145 } 146 } 147 148 mCachedPath = path; 149 } 150 151 /** 152 * Converts the stroke to a Path of a given number of points. 153 * 154 * @param width the width of the bounding box of the target path 155 * @param height the height of the bounding box of the target path 156 * @param numSample the number of points needed 157 * 158 * @return the path 159 */ toPath(float width, float height, int numSample)160 public Path toPath(float width, float height, int numSample) { 161 final float[] pts = GestureUtils.temporalSampling(this, numSample); 162 final RectF rect = boundingBox; 163 164 GestureUtils.translate(pts, -rect.left, -rect.top); 165 166 float sx = width / rect.width(); 167 float sy = height / rect.height(); 168 float scale = sx > sy ? sy : sx; 169 GestureUtils.scale(pts, scale, scale); 170 171 float mX = 0; 172 float mY = 0; 173 174 Path path = null; 175 176 final int count = pts.length; 177 178 for (int i = 0; i < count; i += 2) { 179 float x = pts[i]; 180 float y = pts[i + 1]; 181 if (path == null) { 182 path = new Path(); 183 path.moveTo(x, y); 184 mX = x; 185 mY = y; 186 } else { 187 float dx = Math.abs(x - mX); 188 float dy = Math.abs(y - mY); 189 if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { 190 path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); 191 mX = x; 192 mY = y; 193 } 194 } 195 } 196 197 return path; 198 } 199 serialize(DataOutputStream out)200 void serialize(DataOutputStream out) throws IOException { 201 final float[] pts = points; 202 final long[] times = timestamps; 203 final int count = points.length; 204 205 // Write number of points 206 out.writeInt(count / 2); 207 208 for (int i = 0; i < count; i += 2) { 209 // Write X 210 out.writeFloat(pts[i]); 211 // Write Y 212 out.writeFloat(pts[i + 1]); 213 // Write timestamp 214 out.writeLong(times[i / 2]); 215 } 216 } 217 deserialize(DataInputStream in)218 static GestureStroke deserialize(DataInputStream in) throws IOException { 219 // Number of points 220 final int count = in.readInt(); 221 222 final ArrayList<GesturePoint> points = new ArrayList<GesturePoint>(count); 223 for (int i = 0; i < count; i++) { 224 points.add(GesturePoint.deserialize(in)); 225 } 226 227 return new GestureStroke(points); 228 } 229 230 /** 231 * Invalidates the cached path that is used to render the stroke. 232 */ clearPath()233 public void clearPath() { 234 if (mCachedPath != null) mCachedPath.rewind(); 235 } 236 237 /** 238 * Computes an oriented bounding box of the stroke. 239 * 240 * @return OrientedBoundingBox 241 */ computeOrientedBoundingBox()242 public OrientedBoundingBox computeOrientedBoundingBox() { 243 return GestureUtils.computeOrientedBoundingBox(points); 244 } 245 } 246