GestureStroke.java revision f40f074c43fcef627131d4b631251192761b4daa
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. 31 */ 32public class GestureStroke { 33 static final float TOUCH_TOLERANCE = 3; 34 35 public final RectF boundingBox; 36 37 public final float length; 38 public final float[] points; 39 40 private final long[] timestamps; 41 private Path mCachedPath; 42 43 /** 44 * Construct a gesture stroke from a list of gesture points 45 * 46 * @param points 47 */ 48 public GestureStroke(ArrayList<GesturePoint> points) { 49 final int count = points.size(); 50 final float[] tmpPoints = new float[count * 2]; 51 final long[] times = new long[count]; 52 53 RectF bx = null; 54 float len = 0; 55 int index = 0; 56 57 for (int i = 0; i < count; i++) { 58 final GesturePoint p = points.get(i); 59 tmpPoints[i * 2] = p.x; 60 tmpPoints[i * 2 + 1] = p.y; 61 times[index] = p.timestamp; 62 63 if (bx == null) { 64 bx = new RectF(); 65 bx.top = p.y; 66 bx.left = p.x; 67 bx.right = p.x; 68 bx.bottom = p.y; 69 len = 0; 70 } else { 71 len += Math.sqrt(Math.pow(p.x - tmpPoints[(i - 1) * 2], 2) 72 + Math.pow(p.y - tmpPoints[(i -1 ) * 2 + 1], 2)); 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 * Draw the gesture with a given canvas and paint 86 * 87 * @param canvas 88 */ 89 void draw(Canvas canvas, Paint paint) { 90 if (mCachedPath == null) { 91 makePath(); 92 } 93 94 canvas.drawPath(mCachedPath, paint); 95 } 96 97 public Path getPath() { 98 if (mCachedPath == null) { 99 makePath(); 100 } 101 102 return mCachedPath; 103 } 104 105 private void makePath() { 106 final float[] localPoints = points; 107 final int count = localPoints.length; 108 109 Path path = null; 110 111 float mX = 0; 112 float mY = 0; 113 114 for (int i = 0; i < count; i += 2) { 115 float x = localPoints[i]; 116 float y = localPoints[i + 1]; 117 if (path == null) { 118 path = new Path(); 119 path.moveTo(x, y); 120 mX = x; 121 mY = y; 122 } else { 123 float dx = Math.abs(x - mX); 124 float dy = Math.abs(y - mY); 125 if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { 126 path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); 127 mX = x; 128 mY = y; 129 } 130 } 131 } 132 133 mCachedPath = path; 134 } 135 136 /** 137 * Convert the stroke to a Path based on the number of points 138 * 139 * @param width the width of the bounding box of the target path 140 * @param height the height of the bounding box of the target path 141 * @param numSample the number of points needed 142 * 143 * @return the path 144 */ 145 public Path toPath(float width, float height, int numSample) { 146 final float[] pts = GestureUtilities.temporalSampling(this, numSample); 147 final RectF rect = boundingBox; 148 149 GestureUtilities.translate(pts, -rect.left, -rect.top); 150 151 float sx = width / rect.width(); 152 float sy = height / rect.height(); 153 float scale = sx > sy ? sy : sx; 154 GestureUtilities.scale(pts, scale, scale); 155 156 float mX = 0; 157 float mY = 0; 158 159 Path path = null; 160 161 final int count = pts.length; 162 163 for (int i = 0; i < count; i += 2) { 164 float x = pts[i]; 165 float y = pts[i + 1]; 166 if (path == null) { 167 path = new Path(); 168 path.moveTo(x, y); 169 mX = x; 170 mY = y; 171 } else { 172 float dx = Math.abs(x - mX); 173 float dy = Math.abs(y - mY); 174 if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { 175 path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); 176 mX = x; 177 mY = y; 178 } 179 } 180 } 181 182 return path; 183 } 184 185 void serialize(DataOutputStream out) throws IOException { 186 final float[] pts = points; 187 final long[] times = timestamps; 188 final int count = points.length; 189 190 // Write number of points 191 out.writeInt(count / 2); 192 193 for (int i = 0; i < count; i += 2) { 194 // Write X 195 out.writeFloat(pts[i]); 196 // Write Y 197 out.writeFloat(pts[i + 1]); 198 // Write timestamp 199 out.writeLong(times[i / 2]); 200 } 201 } 202 203 static GestureStroke deserialize(DataInputStream in) throws IOException { 204 // Number of points 205 final int count = in.readInt(); 206 207 final ArrayList<GesturePoint> points = new ArrayList<GesturePoint>(count); 208 for (int i = 0; i < count; i++) { 209 points.add(GesturePoint.deserialize(in)); 210 } 211 212 return new GestureStroke(points); 213 } 214 215 /** 216 * Invalidate the cached path that is used to render the stroke 217 */ 218 public void clearPath() { 219 if (mCachedPath != null) mCachedPath.rewind(); 220 } 221 222 /** 223 * Compute an oriented bounding box of the stroke 224 * @return OrientedBoundingBox 225 */ 226 public OrientedBoundingBox computeOrientedBoundingBox() { 227 return GestureUtilities.computeOrientedBoundingBox(points); 228 } 229} 230