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.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