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