135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li/*
2d6a463a9f23b3901bf729f2f27a6bb8f78b95248Romain Guy * Copyright (C) 2009 The Android Open Source Project
335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li *
435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * Licensed under the Apache License, Version 2.0 (the "License");
535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * you may not use this file except in compliance with the License.
635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * You may obtain a copy of the License at
735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li *
835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li *      http://www.apache.org/licenses/LICENSE-2.0
935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li *
1035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * Unless required by applicable law or agreed to in writing, software
1135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * distributed under the License is distributed on an "AS IS" BASIS,
1235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * See the License for the specific language governing permissions and
1435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * limitations under the License.
1535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li */
1635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
17db567c390bd56c05614eaa83c02dbb99f97ad9ccRomain Guypackage android.gesture;
1835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
1935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport android.graphics.Canvas;
2035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport android.graphics.Paint;
2135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport android.graphics.Path;
2235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport android.graphics.RectF;
2335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
2435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport java.io.IOException;
25b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guyimport java.io.DataOutputStream;
26b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guyimport java.io.DataInputStream;
2735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport java.util.ArrayList;
2835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
2935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li/**
306fc1f151341c628125e7d20c1667a23862f32ce4Yang Li * A gesture stroke started on a touch down and ended on a touch up. A stroke
316fc1f151341c628125e7d20c1667a23862f32ce4Yang Li * consists of a sequence of timed points. One or multiple strokes form a gesture.
3235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li */
3335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Lipublic class GestureStroke {
34c7f930f5a9a62470775e913945c771ca57e0b10fYang Li    static final float TOUCH_TOLERANCE = 3;
35d6a463a9f23b3901bf729f2f27a6bb8f78b95248Romain Guy
3635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    public final RectF boundingBox;
3735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
3835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    public final float length;
3935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    public final float[] points;
4035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
4135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    private final long[] timestamps;
4235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    private Path mCachedPath;
4335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
4435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    /**
456fc1f151341c628125e7d20c1667a23862f32ce4Yang Li     * A constructor that constructs a gesture stroke from a list of gesture points.
4635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li     *
47c534727972c3835ed997e84a349f259915ef2cddRomain Guy     * @param points
4835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li     */
49c534727972c3835ed997e84a349f259915ef2cddRomain Guy    public GestureStroke(ArrayList<GesturePoint> points) {
50c534727972c3835ed997e84a349f259915ef2cddRomain Guy        final int count = points.size();
51c534727972c3835ed997e84a349f259915ef2cddRomain Guy        final float[] tmpPoints = new float[count * 2];
52c534727972c3835ed997e84a349f259915ef2cddRomain Guy        final long[] times = new long[count];
5335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
5435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        RectF bx = null;
5535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        float len = 0;
5635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        int index = 0;
5735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
5835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        for (int i = 0; i < count; i++) {
59c534727972c3835ed997e84a349f259915ef2cddRomain Guy            final GesturePoint p = points.get(i);
60c534727972c3835ed997e84a349f259915ef2cddRomain Guy            tmpPoints[i * 2] = p.x;
61c534727972c3835ed997e84a349f259915ef2cddRomain Guy            tmpPoints[i * 2 + 1] = p.y;
6235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li            times[index] = p.timestamp;
6335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
6435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li            if (bx == null) {
6535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                bx = new RectF();
66c534727972c3835ed997e84a349f259915ef2cddRomain Guy                bx.top = p.y;
67c534727972c3835ed997e84a349f259915ef2cddRomain Guy                bx.left = p.x;
68c534727972c3835ed997e84a349f259915ef2cddRomain Guy                bx.right = p.x;
69c534727972c3835ed997e84a349f259915ef2cddRomain Guy                bx.bottom = p.y;
7035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                len = 0;
7135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li            } else {
72c534727972c3835ed997e84a349f259915ef2cddRomain Guy                len += Math.sqrt(Math.pow(p.x - tmpPoints[(i - 1) * 2], 2)
73c534727972c3835ed997e84a349f259915ef2cddRomain Guy                        + Math.pow(p.y - tmpPoints[(i -1 ) * 2 + 1], 2));
74c534727972c3835ed997e84a349f259915ef2cddRomain Guy                bx.union(p.x, p.y);
7535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li            }
7635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li            index++;
7735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        }
7835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
7935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        timestamps = times;
80c534727972c3835ed997e84a349f259915ef2cddRomain Guy        this.points = tmpPoints;
8135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        boundingBox = bx;
8235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        length = len;
8335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    }
8435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
8535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    /**
866fc1f151341c628125e7d20c1667a23862f32ce4Yang Li     * A faster constructor specially for cloning a stroke.
876fc1f151341c628125e7d20c1667a23862f32ce4Yang Li     */
886fc1f151341c628125e7d20c1667a23862f32ce4Yang Li    private GestureStroke(RectF bbx, float len, float[] pts, long[] times) {
896fc1f151341c628125e7d20c1667a23862f32ce4Yang Li        boundingBox = new RectF(bbx.left, bbx.top, bbx.right, bbx.bottom);
906fc1f151341c628125e7d20c1667a23862f32ce4Yang Li        length = len;
916fc1f151341c628125e7d20c1667a23862f32ce4Yang Li        points = pts.clone();
926fc1f151341c628125e7d20c1667a23862f32ce4Yang Li        timestamps = times.clone();
936fc1f151341c628125e7d20c1667a23862f32ce4Yang Li    }
946fc1f151341c628125e7d20c1667a23862f32ce4Yang Li
956fc1f151341c628125e7d20c1667a23862f32ce4Yang Li    @Override
966fc1f151341c628125e7d20c1667a23862f32ce4Yang Li    public Object clone() {
976fc1f151341c628125e7d20c1667a23862f32ce4Yang Li        return new GestureStroke(boundingBox, length, points, timestamps);
986fc1f151341c628125e7d20c1667a23862f32ce4Yang Li    }
996fc1f151341c628125e7d20c1667a23862f32ce4Yang Li
1006fc1f151341c628125e7d20c1667a23862f32ce4Yang Li    /**
1016fc1f151341c628125e7d20c1667a23862f32ce4Yang Li     * Draws the stroke with a given canvas and paint.
10235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li     *
10335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li     * @param canvas
10435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li     */
10535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    void draw(Canvas canvas, Paint paint) {
10635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        if (mCachedPath == null) {
10782f3495b146b267f3786997752cef25310176349Romain Guy            makePath();
10882f3495b146b267f3786997752cef25310176349Romain Guy        }
109c534727972c3835ed997e84a349f259915ef2cddRomain Guy
11082f3495b146b267f3786997752cef25310176349Romain Guy        canvas.drawPath(mCachedPath, paint);
11182f3495b146b267f3786997752cef25310176349Romain Guy    }
112c534727972c3835ed997e84a349f259915ef2cddRomain Guy
11382f3495b146b267f3786997752cef25310176349Romain Guy    public Path getPath() {
11482f3495b146b267f3786997752cef25310176349Romain Guy        if (mCachedPath == null) {
11582f3495b146b267f3786997752cef25310176349Romain Guy            makePath();
11682f3495b146b267f3786997752cef25310176349Romain Guy        }
11782f3495b146b267f3786997752cef25310176349Romain Guy
11882f3495b146b267f3786997752cef25310176349Romain Guy        return mCachedPath;
11982f3495b146b267f3786997752cef25310176349Romain Guy    }
12082f3495b146b267f3786997752cef25310176349Romain Guy
12182f3495b146b267f3786997752cef25310176349Romain Guy    private void makePath() {
12282f3495b146b267f3786997752cef25310176349Romain Guy        final float[] localPoints = points;
12382f3495b146b267f3786997752cef25310176349Romain Guy        final int count = localPoints.length;
12482f3495b146b267f3786997752cef25310176349Romain Guy
12582f3495b146b267f3786997752cef25310176349Romain Guy        Path path = null;
12682f3495b146b267f3786997752cef25310176349Romain Guy
12782f3495b146b267f3786997752cef25310176349Romain Guy        float mX = 0;
12882f3495b146b267f3786997752cef25310176349Romain Guy        float mY = 0;
129c534727972c3835ed997e84a349f259915ef2cddRomain Guy
13082f3495b146b267f3786997752cef25310176349Romain Guy        for (int i = 0; i < count; i += 2) {
13182f3495b146b267f3786997752cef25310176349Romain Guy            float x = localPoints[i];
13282f3495b146b267f3786997752cef25310176349Romain Guy            float y = localPoints[i + 1];
13382f3495b146b267f3786997752cef25310176349Romain Guy            if (path == null) {
13482f3495b146b267f3786997752cef25310176349Romain Guy                path = new Path();
13582f3495b146b267f3786997752cef25310176349Romain Guy                path.moveTo(x, y);
13682f3495b146b267f3786997752cef25310176349Romain Guy                mX = x;
13782f3495b146b267f3786997752cef25310176349Romain Guy                mY = y;
13882f3495b146b267f3786997752cef25310176349Romain Guy            } else {
13982f3495b146b267f3786997752cef25310176349Romain Guy                float dx = Math.abs(x - mX);
14082f3495b146b267f3786997752cef25310176349Romain Guy                float dy = Math.abs(y - mY);
14182f3495b146b267f3786997752cef25310176349Romain Guy                if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
14282f3495b146b267f3786997752cef25310176349Romain Guy                    path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
14335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                    mX = x;
14435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                    mY = y;
14535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                }
14635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li            }
14735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        }
14835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
14982f3495b146b267f3786997752cef25310176349Romain Guy        mCachedPath = path;
15035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    }
15135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
15235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    /**
1536fc1f151341c628125e7d20c1667a23862f32ce4Yang Li     * Converts the stroke to a Path of a given number of points.
15435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li     *
15535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li     * @param width the width of the bounding box of the target path
15635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li     * @param height the height of the bounding box of the target path
15735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li     * @param numSample the number of points needed
158db567c390bd56c05614eaa83c02dbb99f97ad9ccRomain Guy     *
15935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li     * @return the path
16035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li     */
16135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    public Path toPath(float width, float height, int numSample) {
16246c53129c6f27c9193ab195a69cb50591b8c1fa2Romain Guy        final float[] pts = GestureUtils.temporalSampling(this, numSample);
163c534727972c3835ed997e84a349f259915ef2cddRomain Guy        final RectF rect = boundingBox;
164c534727972c3835ed997e84a349f259915ef2cddRomain Guy
16546c53129c6f27c9193ab195a69cb50591b8c1fa2Romain Guy        GestureUtils.translate(pts, -rect.left, -rect.top);
166f40f074c43fcef627131d4b631251192761b4daaRomain Guy
167f40f074c43fcef627131d4b631251192761b4daaRomain Guy        float sx = width / rect.width();
168f40f074c43fcef627131d4b631251192761b4daaRomain Guy        float sy = height / rect.height();
169f40f074c43fcef627131d4b631251192761b4daaRomain Guy        float scale = sx > sy ? sy : sx;
17046c53129c6f27c9193ab195a69cb50591b8c1fa2Romain Guy        GestureUtils.scale(pts, scale, scale);
17135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
17235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        float mX = 0;
17335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        float mY = 0;
174c534727972c3835ed997e84a349f259915ef2cddRomain Guy
175c534727972c3835ed997e84a349f259915ef2cddRomain Guy        Path path = null;
176c534727972c3835ed997e84a349f259915ef2cddRomain Guy
177c534727972c3835ed997e84a349f259915ef2cddRomain Guy        final int count = pts.length;
178c534727972c3835ed997e84a349f259915ef2cddRomain Guy
17935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        for (int i = 0; i < count; i += 2) {
18035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li            float x = pts[i];
18135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li            float y = pts[i + 1];
18235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li            if (path == null) {
18335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                path = new Path();
18435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                path.moveTo(x, y);
18535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                mX = x;
18635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                mY = y;
18735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li            } else {
18835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                float dx = Math.abs(x - mX);
18935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                float dy = Math.abs(y - mY);
19082f3495b146b267f3786997752cef25310176349Romain Guy                if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
19135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                    path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
19235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                    mX = x;
19335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                    mY = y;
19435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li                }
19535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li            }
19635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        }
197c534727972c3835ed997e84a349f259915ef2cddRomain Guy
19835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        return path;
19935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    }
20035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
201b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy    void serialize(DataOutputStream out) throws IOException {
202b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy        final float[] pts = points;
203b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy        final long[] times = timestamps;
204b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy        final int count = points.length;
205b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy
206b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy        // Write number of points
207b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy        out.writeInt(count / 2);
208b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy
209b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy        for (int i = 0; i < count; i += 2) {
210b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy            // Write X
211b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy            out.writeFloat(pts[i]);
212b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy            // Write Y
213b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy            out.writeFloat(pts[i + 1]);
214b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy            // Write timestamp
215b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy            out.writeLong(times[i / 2]);
216b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy        }
21735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    }
21835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
219b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy    static GestureStroke deserialize(DataInputStream in) throws IOException {
220b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy        // Number of points
221b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy        final int count = in.readInt();
222b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy
223b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy        final ArrayList<GesturePoint> points = new ArrayList<GesturePoint>(count);
224b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy        for (int i = 0; i < count; i++) {
225b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy            points.add(GesturePoint.deserialize(in));
22635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        }
227c534727972c3835ed997e84a349f259915ef2cddRomain Guy
22835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li        return new GestureStroke(points);
229b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy    }
23035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li
23135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    /**
2326fc1f151341c628125e7d20c1667a23862f32ce4Yang Li     * Invalidates the cached path that is used to render the stroke.
23335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li     */
234db567c390bd56c05614eaa83c02dbb99f97ad9ccRomain Guy    public void clearPath() {
235db567c390bd56c05614eaa83c02dbb99f97ad9ccRomain Guy        if (mCachedPath != null) mCachedPath.rewind();
23635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li    }
237e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li
238e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li    /**
2396fc1f151341c628125e7d20c1667a23862f32ce4Yang Li     * Computes an oriented bounding box of the stroke.
2406fc1f151341c628125e7d20c1667a23862f32ce4Yang Li     *
241e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li     * @return OrientedBoundingBox
242e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li     */
243e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li    public OrientedBoundingBox computeOrientedBoundingBox() {
24446c53129c6f27c9193ab195a69cb50591b8c1fa2Romain Guy        return GestureUtils.computeOrientedBoundingBox(points);
245e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li    }
24635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li}
247