Home | History | Annotate | Download | only in gesture
      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      */
     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      */
     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
     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      */
    104     void draw(Canvas canvas, Paint paint) {
    105         if (mCachedPath == null) {
    106             makePath();
    107         }
    108 
    109         canvas.drawPath(mCachedPath, paint);
    110     }
    111 
    112     public Path getPath() {
    113         if (mCachedPath == null) {
    114             makePath();
    115         }
    116 
    117         return mCachedPath;
    118     }
    119 
    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      */
    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 
    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 
    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      */
    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      */
    242     public OrientedBoundingBox computeOrientedBoundingBox() {
    243         return GestureUtils.computeOrientedBoundingBox(points);
    244     }
    245 }
    246