GestureStroke.java revision 436466d75edb5f6fd848504d998f244426ea5a09
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.Matrix; 21import android.graphics.Paint; 22import android.graphics.Path; 23import android.graphics.RectF; 24 25import java.io.IOException; 26import java.io.DataOutputStream; 27import java.io.DataInputStream; 28import java.util.ArrayList; 29 30/** 31 * A gesture stroke started on a touch down and ended on a touch up. 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 * Construct 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 * Draw the gesture with a given canvas and paint 87 * 88 * @param canvas 89 */ 90 void draw(Canvas canvas, Paint paint) { 91 if (mCachedPath == null) { 92 makePath(); 93 } 94 95 canvas.drawPath(mCachedPath, paint); 96 } 97 98 public Path getPath() { 99 if (mCachedPath == null) { 100 makePath(); 101 } 102 103 return mCachedPath; 104 } 105 106 private void makePath() { 107 final float[] localPoints = points; 108 final int count = localPoints.length; 109 110 Path path = null; 111 112 float mX = 0; 113 float mY = 0; 114 115 for (int i = 0; i < count; i += 2) { 116 float x = localPoints[i]; 117 float y = localPoints[i + 1]; 118 if (path == null) { 119 path = new Path(); 120 path.moveTo(x, y); 121 mX = x; 122 mY = y; 123 } else { 124 float dx = Math.abs(x - mX); 125 float dy = Math.abs(y - mY); 126 if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { 127 path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); 128 mX = x; 129 mY = y; 130 } 131 } 132 } 133 134 mCachedPath = path; 135 } 136 137 /** 138 * Convert the stroke to a Path based on the number of points 139 * 140 * @param width the width of the bounding box of the target path 141 * @param height the height of the bounding box of the target path 142 * @param numSample the number of points needed 143 * 144 * @return the path 145 */ 146 public Path toPath(float width, float height, int numSample) { 147 final float[] pts = GestureUtilities.temporalSampling(this, numSample); 148 final RectF rect = boundingBox; 149 150 final Matrix matrix = new Matrix(); 151 matrix.setTranslate(-rect.left, -rect.top); 152 matrix.postScale(width / rect.width(), height / rect.height()); 153 matrix.mapPoints(pts); 154 155 float mX = 0; 156 float mY = 0; 157 158 Path path = null; 159 160 final int count = pts.length; 161 162 for (int i = 0; i < count; i += 2) { 163 float x = pts[i]; 164 float y = pts[i + 1]; 165 if (path == null) { 166 path = new Path(); 167 path.moveTo(x, y); 168 mX = x; 169 mY = y; 170 } else { 171 float dx = Math.abs(x - mX); 172 float dy = Math.abs(y - mY); 173 if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { 174 path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); 175 mX = x; 176 mY = y; 177 } 178 } 179 } 180 181 return path; 182 } 183 184 void serialize(DataOutputStream out) throws IOException { 185 final float[] pts = points; 186 final long[] times = timestamps; 187 final int count = points.length; 188 189 // Write number of points 190 out.writeInt(count / 2); 191 192 for (int i = 0; i < count; i += 2) { 193 // Write X 194 out.writeFloat(pts[i]); 195 // Write Y 196 out.writeFloat(pts[i + 1]); 197 // Write timestamp 198 out.writeLong(times[i / 2]); 199 } 200 } 201 202 static GestureStroke deserialize(DataInputStream in) throws IOException { 203 // Number of points 204 final int count = in.readInt(); 205 206 final ArrayList<GesturePoint> points = new ArrayList<GesturePoint>(count); 207 for (int i = 0; i < count; i++) { 208 points.add(GesturePoint.deserialize(in)); 209 } 210 211 return new GestureStroke(points); 212 } 213 214 /** 215 * Invalidate the cached path that is used to render the stroke 216 */ 217 public void clearPath() { 218 if (mCachedPath != null) mCachedPath.rewind(); 219 } 220 221 /** 222 * Compute an oriented bounding box of the stroke 223 * @return OrientedBoundingBox 224 */ 225 public OrientedBoundingBox computeOrientedBoundingBox() { 226 return GestureUtilities.computeOrientedBoundingBox(points); 227 } 228} 229