summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorJoe Onorato <joeo@android.com>2009-04-17 14:18:46 -0700
committerJoe Onorato <joeo@android.com>2009-04-17 14:18:46 -0700
commit6e93a3db56d6add29b43077718a4cad9ccfc047f (patch)
treebddbb122cf6414d6a8ed97a623b86be0d14c4480 /tests
parent21b5817aaa2f0a61edff8752ed85130aa8cf7def (diff)
downloadframeworks_base-6e93a3db56d6add29b43077718a4cad9ccfc047f.zip
frameworks_base-6e93a3db56d6add29b43077718a4cad9ccfc047f.tar.gz
frameworks_base-6e93a3db56d6add29b43077718a4cad9ccfc047f.tar.bz2
Add sketch gesture demo application.
Initial checkin, there's no Android.mk yet.
Diffstat (limited to 'tests')
-rwxr-xr-xtests/sketch/AndroidManifest.xml30
-rwxr-xr-xtests/sketch/res/drawable/icon.pngbin0 -> 3180 bytes
-rwxr-xr-xtests/sketch/res/layout/demo.xml33
-rwxr-xr-xtests/sketch/res/layout/gestureviewer.xml58
-rwxr-xr-xtests/sketch/res/layout/newgesture_dialog.xml34
-rwxr-xr-xtests/sketch/res/values/strings.xml28
-rwxr-xr-xtests/sketch/src/com/android/gesture/Gesture.java361
-rwxr-xr-xtests/sketch/src/com/android/gesture/GestureLib.java280
-rwxr-xr-xtests/sketch/src/com/android/gesture/GestureListener.java25
-rwxr-xr-xtests/sketch/src/com/android/gesture/GesturePad.java371
-rwxr-xr-xtests/sketch/src/com/android/gesture/example/GestureEntryDemo.java225
-rwxr-xr-xtests/sketch/src/com/android/gesture/example/GestureLibViewer.java262
-rwxr-xr-xtests/sketch/src/com/android/gesture/recognizer/Classifier.java42
-rwxr-xr-xtests/sketch/src/com/android/gesture/recognizer/Instance.java78
-rwxr-xr-xtests/sketch/src/com/android/gesture/recognizer/NearestNeighbor.java91
-rwxr-xr-xtests/sketch/src/com/android/gesture/recognizer/Prediction.java36
-rwxr-xr-xtests/sketch/src/com/android/gesture/recognizer/RecognitionUtil.java190
17 files changed, 2144 insertions, 0 deletions
diff --git a/tests/sketch/AndroidManifest.xml b/tests/sketch/AndroidManifest.xml
new file mode 100755
index 0000000..c44b54e
--- /dev/null
+++ b/tests/sketch/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.gesture"
+ android:versionCode="1"
+ android:versionName="1.0.0">
+ <application android:icon="@drawable/icon" android:label="@string/app_name">
+ <activity android:name="com.android.gesture.example.GestureEntryDemo"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name="com.android.gesture.example.GestureLibViewer"/>
+ </application>
+</manifest>
diff --git a/tests/sketch/res/drawable/icon.png b/tests/sketch/res/drawable/icon.png
new file mode 100755
index 0000000..7502484
--- /dev/null
+++ b/tests/sketch/res/drawable/icon.png
Binary files differ
diff --git a/tests/sketch/res/layout/demo.xml b/tests/sketch/res/layout/demo.xml
new file mode 100755
index 0000000..e516229
--- /dev/null
+++ b/tests/sketch/res/layout/demo.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <Spinner
+ android:id="@+id/spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:drawSelectorOnTop="true"
+ android:prompt="@string/recognition_result"/>
+
+ <com.android.gesture.GesturePad
+ android:id="@+id/drawingpad"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+
+</LinearLayout>
diff --git a/tests/sketch/res/layout/gestureviewer.xml b/tests/sketch/res/layout/gestureviewer.xml
new file mode 100755
index 0000000..5302d34
--- /dev/null
+++ b/tests/sketch/res/layout/gestureviewer.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <Spinner
+ android:id="@+id/spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:drawSelectorOnTop="true"
+ android:prompt="@string/recognition_result"/>
+
+ <com.android.gesture.GesturePad
+ android:id="@+id/drawingpad"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <Button
+ android:id="@+id/previous"
+ android:text="@string/previous"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+ <Button
+ android:id="@+id/remove"
+ android:text="@string/remove"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+ <Button
+ android:id="@+id/next"
+ android:text="@string/next"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/tests/sketch/res/layout/newgesture_dialog.xml b/tests/sketch/res/layout/newgesture_dialog.xml
new file mode 100755
index 0000000..6e45d81
--- /dev/null
+++ b/tests/sketch/res/layout/newgesture_dialog.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <EditText
+ android:id="@+id/gesturename_edit"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ android:scrollHorizontally="true"
+ android:autoText="false"
+ android:capitalize="none"
+ android:gravity="fill_horizontal"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+</LinearLayout>
diff --git a/tests/sketch/res/values/strings.xml b/tests/sketch/res/values/strings.xml
new file mode 100755
index 0000000..4c6aa20
--- /dev/null
+++ b/tests/sketch/res/values/strings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <string name="app_name">Gesture Demo</string>
+ <string name="recognition_result">Recognition Result</string>
+ <string name="clear">Clear</string>
+ <string name="newgesture">Add</string>
+ <string name="viewgesture">View</string>
+ <string name="newgesture_dialog_ok">OK</string>
+ <string name="newgesture_dialog_cancel">Cancel</string>
+ <string name="newgesture_text_entry">Gesture Name</string>
+ <string name="previous">Previous</string>
+ <string name="remove">Remove</string>
+ <string name="next">Next</string>
+</resources>
diff --git a/tests/sketch/src/com/android/gesture/Gesture.java b/tests/sketch/src/com/android/gesture/Gesture.java
new file mode 100755
index 0000000..29c07ad
--- /dev/null
+++ b/tests/sketch/src/com/android/gesture/Gesture.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2008-2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gesture;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.gesture.recognizer.RecognitionUtil;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+
+/**
+ * A single stroke gesture.
+ */
+
+public class Gesture implements Parcelable {
+
+ private RectF mBBX;
+ private float mLength = 0;
+ private int mColor;
+ private float mWidth;
+ private ArrayList<PointF> mPtsBuffer = new ArrayList<PointF>();
+ private long mTimestamp = 0;
+ private long mID;
+
+ private static final long systemStartupTime = System.currentTimeMillis();
+ private static int instanceCount = 0;
+
+ public Gesture() {
+ mID = systemStartupTime + instanceCount++;
+ }
+
+ public void setColor(int c) {
+ mColor = c;
+ }
+
+ public void setStrokeWidth(float w) {
+ mWidth = w;
+ }
+
+ public int getColor() {
+ return mColor;
+ }
+
+ public float getStrokeWidth() {
+ return mWidth;
+ }
+
+ public ArrayList<PointF> getPoints() {
+ return this.mPtsBuffer;
+ }
+
+ public int numOfPoints() {
+ return this.mPtsBuffer.size();
+ }
+
+ public void addPoint(float x, float y) {
+ mPtsBuffer.add(new PointF(x, y));
+ if (mBBX == null) {
+ mBBX = new RectF();
+ mBBX.top = y;
+ mBBX.left = x;
+ mBBX.right = x;
+ mBBX.bottom = y;
+ mLength = 0;
+ }
+ else {
+ PointF lst = mPtsBuffer.get(mPtsBuffer.size()-2);
+ mLength += Math.sqrt(Math.pow(x-lst.x, 2)+Math.pow(y-lst.y, 2));
+ mBBX.union(x, y);
+ }
+ mTimestamp = System.currentTimeMillis();
+ }
+
+ /**
+ * @return the length of the gesture
+ */
+ public float getLength() {
+ return this.mLength;
+ }
+
+ public RectF getBBX() {
+ return mBBX;
+ }
+
+ public void setID(long id) {
+ mID = id;
+ }
+
+ public long getID() {
+ return mID;
+ }
+
+ public long getTimeStamp() {
+ return mTimestamp;
+ }
+
+ public void setTimestamp(long t) {
+ this.mTimestamp = t;
+ }
+
+ /**
+ * draw the gesture
+ * @param canvas
+ */
+ public void draw(Canvas canvas) {
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setDither(true);
+ paint.setColor(mColor);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeJoin(Paint.Join.ROUND);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+ paint.setStrokeWidth(mWidth);
+
+ Path path = null;
+ float mX = 0, mY = 0;
+ Iterator<PointF> it = mPtsBuffer.iterator();
+ while (it.hasNext()) {
+ PointF p = it.next();
+ float x = p.x;
+ float y = p.y;
+ if (path == null) {
+ path = new Path();
+ path.moveTo(x, y);
+ mX = x;
+ mY = y;
+ } else {
+ float dx = Math.abs(x - mX);
+ float dy = Math.abs(y - mY);
+ if (dx >= 3 || dy >= 3) {
+ path.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
+ mX = x;
+ mY = y;
+ }
+ }
+ }
+
+ canvas.drawPath(path, paint);
+ }
+
+ /**
+ * convert the gesture to a Path
+ * @param width the width of the bounding box of the target path
+ * @param height the height of the bounding box of the target path
+ * @param numSample the num of points needed
+ * @return the path
+ */
+ public Path toPath(float width, float height, int numSample) {
+ float[] pts = RecognitionUtil.resample(this, numSample);
+ RectF rect = this.getBBX();
+ float scale = height / rect.height();
+ Matrix matrix = new Matrix();
+ matrix.setTranslate(-rect.left, -rect.top);
+ Matrix scalem = new Matrix();
+ scalem.setScale(scale, scale);
+ matrix.postConcat(scalem);
+ Matrix translate = new Matrix();
+ matrix.postConcat(translate);
+ matrix.mapPoints(pts);
+
+ Path path = null;
+ float mX = 0, mY = 0;
+ for (int i=0; i<pts.length-1; i+=2) {
+ float x = pts[i];
+ float y = pts[i+1];
+ if (path == null) {
+ path = new Path();
+ path.moveTo(x, y);
+ mX = x;
+ mY = y;
+ } else {
+ float dx = Math.abs(x - mX);
+ float dy = Math.abs(y - mY);
+ if (dx >= 3 || dy >= 3) {
+ path.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
+ mX = x;
+ mY = y;
+ }
+ }
+ }
+ return path;
+ }
+
+ /**
+ * get a bitmap thumbnail of the gesture with a transparent background
+ * @param w
+ * @param h
+ * @param edge
+ * @param numSample
+ * @param foreground
+ * @return
+ */
+ public Bitmap toBitmap(int w, int h,
+ int edge, int numSample) {
+ RectF bbx = this.getBBX();
+ Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+ Path path = this.toPath(w - 2 * edge, h - 2 * edge, numSample);
+ Canvas c = new Canvas(bitmap);
+ //c.drawColor(background);
+ c.translate(edge, edge);
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setDither(true);
+ paint.setColor(mColor);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeJoin(Paint.Join.ROUND);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+ paint.setStrokeWidth(2);
+ c.drawPath(path, paint);
+ return bitmap;
+ }
+
+ /**
+ * save the gesture as XML
+ * @param namespace
+ * @param serializer
+ * @throws IOException
+ */
+ public void toXML(String namespace, XmlSerializer serializer) throws IOException {
+ serializer.startTag(namespace, "stroke");
+ serializer.attribute(namespace, "timestamp", Long.toString(mTimestamp));
+ serializer.attribute(namespace, "id", Long.toString(mID));
+ serializer.attribute(namespace, "color", Integer.toString(mColor));
+ serializer.attribute(namespace, "width", Float.toString(mWidth));
+ Iterator it = this.mPtsBuffer.iterator();
+ String pts = "";
+ while (it.hasNext()) {
+ PointF fp = (PointF)it.next();
+ if (pts.length() > 0)
+ pts += ",";
+ pts += fp.x + "," + fp.y;
+ }
+ serializer.text(pts);
+ serializer.endTag(namespace, "stroke");
+ }
+
+
+ public void createFromString(String str) {
+ StringTokenizer st = new StringTokenizer(str, "#");
+
+ String para = st.nextToken();
+ StringTokenizer innerst = new StringTokenizer(para, ",");
+ this.mBBX = new RectF();
+ this.mBBX.left = Float.parseFloat(innerst.nextToken());
+ this.mBBX.top = Float.parseFloat(innerst.nextToken());
+ this.mBBX.right = Float.parseFloat(innerst.nextToken());
+ this.mBBX.bottom = Float.parseFloat(innerst.nextToken());
+
+ para = st.nextToken();
+ innerst = new StringTokenizer(para, ",");
+ while (innerst.hasMoreTokens()) {
+ String s = innerst.nextToken().trim();
+ if (s.length()==0)
+ break;
+ float x = Float.parseFloat(s);
+ float y = Float.parseFloat(innerst.nextToken());
+ this.mPtsBuffer.add(new PointF(x, y));
+ }
+
+ para = st.nextToken();
+ this.mColor = Integer.parseInt(para);
+
+ para = st.nextToken();
+ this.mWidth = Float.parseFloat(para);
+
+ para = st.nextToken();
+ this.mLength = Float.parseFloat(para);
+
+ para = st.nextToken();
+ this.mTimestamp = Long.parseLong(para);
+ }
+
+ @Override
+ public String toString() {
+ String str = "";
+
+ str += "#" + this.mBBX.left + "," + this.mBBX.top + "," +
+ this.mBBX.right + "," + this.mBBX.bottom;
+
+ str += "#";
+ Iterator<PointF> it = this.mPtsBuffer.iterator();
+ while (it.hasNext()) {
+ PointF fp = it.next();
+ str += fp.x + "," + fp.y + ",";
+ }
+
+ str += "#";
+ str += this.mColor;
+
+ str += "#";
+ str += this.mWidth;
+
+ str += "#";
+ str += this.mLength;
+
+ str += "#";
+ str += this.mTimestamp;
+
+ return str;
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public Gesture createFromParcel(Parcel in) {
+ String str = in.readString();
+ Gesture stk = new Gesture();
+ stk.createFromString(str);
+ return stk;
+ }
+
+ public Gesture[] newArray(int size) {
+ return new Gesture[size];
+ }
+ };
+
+ public static Gesture buildFromArray(byte[] bytes) {
+ String str = new String(bytes);
+ Gesture stk = new Gesture();
+ stk.createFromString(str);
+ return stk;
+ }
+
+ public static byte[] saveToArray(Gesture stk) {
+ String str = stk.toString();
+ return str.getBytes();
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(this.toString());
+ }
+
+ public int describeContents() {
+ return CONTENTS_FILE_DESCRIPTOR;
+ }
+}
diff --git a/tests/sketch/src/com/android/gesture/GestureLib.java b/tests/sketch/src/com/android/gesture/GestureLib.java
new file mode 100755
index 0000000..d0a25f2
--- /dev/null
+++ b/tests/sketch/src/com/android/gesture/GestureLib.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2008-2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gesture;
+
+import android.util.Log;
+import android.util.Xml;
+import android.util.Xml.Encoding;
+
+import com.android.gesture.recognizer.Classifier;
+import com.android.gesture.recognizer.Instance;
+import com.android.gesture.recognizer.NearestNeighbor;
+import com.android.gesture.recognizer.Prediction;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+public class GestureLib {
+
+ private static final String LOGTAG = "GestureLib";
+ private static String namespace = "ink";
+ private final String datapath;
+ private HashMap<String, ArrayList<Gesture>> name2gestures =
+ new HashMap<String, ArrayList<Gesture>>();
+ private Classifier mClassifier;
+
+ public GestureLib(String path) {
+ datapath = path;
+ mClassifier = new NearestNeighbor();
+ }
+
+ public Classifier getClassifier() {
+ return mClassifier;
+ }
+
+ /**
+ * get all the labels in the library
+ * @return a set of strings
+ */
+ public Set<String> getLabels() {
+ return name2gestures.keySet();
+ }
+
+ public ArrayList<Prediction> recognize(Gesture gesture) {
+ Instance instance = Instance.createInstance(gesture, null);
+ return mClassifier.classify(instance);
+ }
+
+ public void addGesture(String name, Gesture gesture) {
+ Log.v(LOGTAG, "add an example for gesture: " + name);
+ ArrayList<Gesture> gestures = name2gestures.get(name);
+ if (gestures == null) {
+ gestures = new ArrayList<Gesture>();
+ name2gestures.put(name, gestures);
+ }
+ gestures.add(gesture);
+ mClassifier.addInstance(
+ Instance.createInstance(gesture, name));
+ }
+
+ public void removeGesture(String name, Gesture gesture) {
+ ArrayList<Gesture> gestures = name2gestures.get(name);
+ if (gestures == null) {
+ return;
+ }
+
+ gestures.remove(gesture);
+
+ // if there are no more samples, remove the entry automatically
+ if (gestures.isEmpty()) {
+ name2gestures.remove(name);
+ }
+
+ mClassifier.removeInstance(gesture.getID());
+ }
+
+ public ArrayList<Gesture> getGestures(String label) {
+ ArrayList<Gesture> gestures = name2gestures.get(label);
+ if (gestures != null)
+ return (ArrayList<Gesture>)gestures.clone();
+ else
+ return null;
+ }
+
+ public void load() {
+ String filename = datapath
+ + File.separator + "gestures.xml";
+ File f = new File(filename);
+ if (f.exists()) {
+ try {
+ loadInk(filename, null);
+ }
+ catch (SAXException ex) {
+ ex.printStackTrace();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ public void save() {
+ try {
+ compactSave();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ private void compactSave() throws IOException {
+ File f = new File(datapath);
+ if (f.exists() == false) {
+ f.mkdirs();
+ }
+ String filename = datapath + File.separator + "gestures.xml";
+ Log.v(LOGTAG, "save to " + filename);
+ BufferedOutputStream fos = new BufferedOutputStream(
+ new FileOutputStream(filename));
+
+ PrintWriter writer = new PrintWriter(fos);
+ XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(writer);
+ serializer.startDocument(Encoding.ISO_8859_1.name(), null);
+ serializer.startTag(namespace, "gestures");
+ Iterator<String> it = name2gestures.keySet().iterator();
+ while (it.hasNext()) {
+ String key = it.next();
+ ArrayList<Gesture> gestures = name2gestures.get(key);
+ saveGestures(serializer, key, gestures);
+ }
+
+ serializer.endTag(namespace, "gestures");
+ serializer.endDocument();
+ serializer.flush();
+ writer.close();
+ fos.close();
+ }
+
+ private static void saveGestures(XmlSerializer serializer,
+ String name, ArrayList<Gesture> strokes) throws IOException {
+ serializer.startTag(namespace, "gesture");
+ serializer.startTag(namespace, "name");
+ serializer.text(name);
+ serializer.endTag(namespace, "name");
+ Iterator<Gesture> it = strokes.iterator();
+ while (it.hasNext()) {
+ Gesture stk = it.next();
+ stk.toXML(namespace, serializer);
+ }
+ serializer.endTag(namespace, "gesture");
+ }
+
+ private void loadInk(String filename, String label) throws SAXException, IOException {
+ Log.v(LOGTAG, "load from " + filename);
+ BufferedInputStream in = new BufferedInputStream(
+ new FileInputStream(filename));
+ Xml.parse(in, Encoding.ISO_8859_1, new CompactInkHandler());
+ in.close();
+ }
+
+ class CompactInkHandler implements ContentHandler {
+
+ Gesture currentGesture = null;
+ StringBuffer buffer = null;
+ String gestureName;
+ ArrayList<Gesture> gestures;
+
+ CompactInkHandler() {
+ }
+
+ // Receive notification of character data.
+ public void characters(char[] ch, int start, int length) {
+ buffer.append(ch, start, length);
+ }
+
+ //Receive notification of the end of a document.
+ public void endDocument() {
+ }
+
+ // Receive notification of the end of an element.
+ public void endElement(String uri, String localName, String qName) {
+ if (localName.equals("gesture")) {
+ name2gestures.put(gestureName, gestures);
+ gestures = null;
+ } else if (localName.equals("name")) {
+ gestureName = buffer.toString();
+ } else if (localName.equals("stroke")) {
+ StringTokenizer tokenizer = new StringTokenizer(buffer.toString(), ",");
+ while (tokenizer.hasMoreTokens()) {
+ String str = tokenizer.nextToken();
+ float x = Float.parseFloat(str);
+ str = tokenizer.nextToken();
+ float y = Float.parseFloat(str);
+ try
+ {
+ currentGesture.addPoint(x, y);
+ }
+ catch(Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+ gestures.add(currentGesture);
+ mClassifier.addInstance(
+ Instance.createInstance(currentGesture, gestureName));
+ currentGesture = null;
+ }
+ }
+
+ // End the scope of a prefix-URI mapping.
+ public void endPrefixMapping(String prefix) {
+ }
+
+ //Receive notification of ignorable whitespace in element content.
+ public void ignorableWhitespace(char[] ch, int start, int length) {
+ }
+
+ //Receive notification of a processing instruction.
+ public void processingInstruction(String target, String data) {
+ }
+
+ // Receive an object for locating the origin of SAX document events.
+ public void setDocumentLocator(Locator locator) {
+ }
+
+ // Receive notification of a skipped entity.
+ public void skippedEntity(String name) {
+ }
+
+ // Receive notification of the beginning of a document.
+ public void startDocument() {
+ }
+
+ // Receive notification of the beginning of an element.
+ public void startElement(String uri, String localName, String qName, Attributes atts) {
+ if (localName.equals("gesture")) {
+ gestures = new ArrayList<Gesture>();
+ } else if (localName.equals("name")) {
+ buffer = new StringBuffer();
+ } else if (localName.equals("stroke")) {
+ currentGesture = new Gesture();
+ currentGesture.setTimestamp(Long.parseLong(atts.getValue(namespace, "timestamp")));
+ currentGesture.setColor(Integer.parseInt(atts.getValue(namespace, "color")));
+ currentGesture.setStrokeWidth(Float.parseFloat(atts.getValue(namespace, "width")));
+ buffer = new StringBuffer();
+ }
+ }
+
+ public void startPrefixMapping(String prefix, String uri) {
+ }
+ }
+}
diff --git a/tests/sketch/src/com/android/gesture/GestureListener.java b/tests/sketch/src/com/android/gesture/GestureListener.java
new file mode 100755
index 0000000..ebb4149
--- /dev/null
+++ b/tests/sketch/src/com/android/gesture/GestureListener.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2008-2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gesture;
+
+import android.view.MotionEvent;
+
+public interface GestureListener {
+ public void onStartGesture(GesturePad pad, MotionEvent event);
+ public void onGesture(GesturePad pad, MotionEvent event);
+ public void onFinishGesture(GesturePad pad, MotionEvent event);
+}
diff --git a/tests/sketch/src/com/android/gesture/GesturePad.java b/tests/sketch/src/com/android/gesture/GesturePad.java
new file mode 100755
index 0000000..45a09e6
--- /dev/null
+++ b/tests/sketch/src/com/android/gesture/GesturePad.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gesture;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * A view for rendering and processing gestures
+ */
+
+public class GesturePad extends View {
+
+ public static final float TOUCH_TOLERANCE = 4;
+ public static final int default_foreground = Color.argb(255, 255, 255, 0);
+ private int background = Color.argb(0, 0, 0, 0);
+ private int foreground = default_foreground;
+ private int uncertain_foreground = Color.argb(55, 255, 255, 0);
+ private Bitmap mBitmap;
+ private Canvas mCanvas;
+ private Path mPath;
+ private Paint mBitmapPaint;
+ private Paint mPaint;
+ private Paint mDebugPaint;
+ private float mX, mY;
+ private boolean mEnableInput = true;
+ private boolean mEnableRendering = true;
+ private boolean mCacheGesture = true;
+ private Gesture mCurrentGesture = null;
+ ArrayList<GestureListener> mGestureListeners = new ArrayList<GestureListener>();
+
+ private boolean mShouldFadingOut = true;
+ private boolean mIsFadingOut = false;
+ private float mFadingAlpha = 1;
+
+ private boolean reconstruct = false;
+
+ private ArrayList<Path> debug = new ArrayList<Path>();
+ private Handler mHandler = new Handler();
+
+ private Runnable mFadingOut = new Runnable() {
+ public void run() {
+ mFadingAlpha -= 0.03f;
+ if (mFadingAlpha <= 0) {
+ mIsFadingOut = false;
+ mPath.reset();
+ } else {
+ mHandler.postDelayed(this, 100);
+ }
+ invalidate();
+ }
+ };
+
+ public GesturePad(Context context) {
+ super(context);
+ init();
+ }
+
+ public GesturePad(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public boolean isEnableRendering() {
+ return this.mEnableRendering;
+ }
+
+ public Gesture getCurrentGesture() {
+ return mCurrentGesture;
+ }
+
+ public Paint getPaint() {
+ return mPaint;
+ }
+
+ public void setColor(int c) {
+ this.foreground = c;
+ }
+
+ public void setFadingAlpha(float f) {
+ mFadingAlpha = f;
+ }
+
+ public void setCurrentGesture(Gesture stk) {
+ this.mCurrentGesture = stk;
+ reconstruct = true;
+ }
+
+ private void init() {
+ mDebugPaint = new Paint();
+ mDebugPaint.setColor(Color.WHITE);
+ mDebugPaint.setStrokeWidth(4);
+ mDebugPaint.setAntiAlias(true);
+ mDebugPaint.setStyle(Paint.Style.STROKE);
+
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setDither(true);
+ mPaint.setColor(foreground);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeJoin(Paint.Join.ROUND);
+ mPaint.setStrokeCap(Paint.Cap.ROUND);
+ mPaint.setStrokeWidth(12);
+
+ mBitmapPaint = new Paint(Paint.DITHER_FLAG);
+ mPath = new Path();
+
+ reconstruct = false;
+ }
+
+ public void cacheGesture(boolean b) {
+ mCacheGesture = b;
+ }
+
+ public void enableRendering(boolean b) {
+ mEnableRendering = b;
+ }
+
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ // TODO Auto-generated method stub
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ if (w <=0 || h <=0)
+ return;
+
+ int width = w>oldw? w : oldw;
+ int height = h>oldh? h : oldh;
+ Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(newBitmap);
+
+ if (mBitmap != null) {
+ mCanvas.drawColor(background);
+ mCanvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
+ mCanvas.drawPath(mPath, mPaint);
+ }
+
+ mBitmap = newBitmap;
+ }
+
+ public void addGestureListener(GestureListener l) {
+ this.mGestureListeners.add(l);
+ }
+
+ public void removeGestureListener(GestureListener l) {
+ this.mGestureListeners.remove(l);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawColor(background);
+
+ if (mCacheGesture)
+ canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
+
+ if (mIsFadingOut) {
+ int color = foreground;
+ int alpha = (int)(Color.alpha(color) * mFadingAlpha);
+ mPaint.setColor(Color.argb(alpha,
+ Color.red(color),
+ Color.green(color),
+ Color.blue(color)));
+ } else if (mEnableRendering == false) {
+ mPaint.setColor(uncertain_foreground);
+ } else {
+ mPaint.setColor(foreground);
+ }
+
+ if (reconstruct) {
+
+ if (this.mCurrentGesture != null) {
+ float xedge = 30;
+ float yedge = 30;
+ float w = this.getWidth() - 2 * xedge;
+ float h = this.getHeight() - 2 * yedge;
+ float sx = w / this.mCurrentGesture.getBBX().width();
+ float sy = h / mCurrentGesture.getBBX().height();
+ float scale = sx>sy?sy:sx;
+ convertFromStroke(mCurrentGesture);
+ Matrix matrix = new Matrix();
+ matrix.preTranslate(-mCurrentGesture.getBBX().centerX(), -mCurrentGesture.getBBX().centerY());
+ matrix.postScale(scale, scale);
+ matrix.postTranslate(this.getWidth()/2, this.getHeight()/2);
+ this.mPath.transform(matrix);
+ } else {
+ mPath.reset();
+ }
+
+ reconstruct = false;
+ }
+
+ canvas.drawPath(mPath, mPaint);
+
+ Iterator<Path> it = debug.iterator();
+ while (it.hasNext()) {
+ Path path = it.next();
+ canvas.drawPath(path, mDebugPaint);
+ }
+ }
+
+ public void clearDebugPath() {
+ debug.clear();
+ }
+
+ public void addDebugPath(Path path) {
+ debug.add(path);
+ }
+
+ public void addDebugPath(ArrayList<Path> paths) {
+ debug.addAll(paths);
+ }
+
+ public void clear() {
+ mPath = new Path();
+ this.mCurrentGesture = null;
+ mCanvas.drawColor(background);
+ this.invalidate();
+ }
+
+ private void convertFromStroke(Gesture stk) {
+ mPath = null;
+ Iterator it = stk.getPoints().iterator();
+ while (it.hasNext()) {
+ PointF p = (PointF) it.next();
+ if (mPath == null) {
+ mPath = new Path();
+ mPath.moveTo(p.x, p.y);
+ mX = p.x;
+ mY = p.y;
+ } else {
+ float dx = Math.abs(p.x - mX);
+ float dy = Math.abs(p.y - mY);
+ if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
+ mPath.quadTo(mX, mY, (p.x + mX)/2, (p.y + mY)/2);
+ mX = p.x;
+ mY = p.y;
+ }
+ }
+ }
+ mPath.lineTo(mX, mY);
+ }
+
+ public void setEnableInput(boolean b) {
+ mEnableInput = b;
+ }
+
+ public boolean isEnableInput() {
+ return mEnableInput;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+
+ if(mEnableInput == false)
+ return true;
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ touch_start(event);
+ invalidate();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ touch_move(event);
+ invalidate();
+ break;
+ case MotionEvent.ACTION_UP:
+ touch_up(event);
+ invalidate();
+ break;
+ }
+ return true;
+ }
+
+ private void touch_start(MotionEvent event) {
+ mIsFadingOut = false;
+ mHandler.removeCallbacks(mFadingOut);
+
+ float x = event.getX();
+ float y = event.getY();
+
+ mCurrentGesture = new Gesture();
+ mCurrentGesture.addPoint(x, y);
+
+ mPath.reset();
+ mPath.moveTo(x, y);
+ mX = x;
+ mY = y;
+
+ Iterator<GestureListener> it = mGestureListeners.iterator();
+ while (it.hasNext()) {
+ it.next().onStartGesture(this, event);
+ }
+ }
+
+ private void touch_move(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+
+ float dx = Math.abs(x - mX);
+ float dy = Math.abs(y - mY);
+ if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
+ mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
+ mX = x;
+ mY = y;
+ }
+
+ mCurrentGesture.addPoint(x, y);
+
+ Iterator<GestureListener> it = mGestureListeners.iterator();
+ while (it.hasNext()) {
+ it.next().onGesture(this, event);
+ }
+ }
+
+ public void setFadingOut(boolean b) {
+ mShouldFadingOut = b;
+ mIsFadingOut = false;
+ }
+
+ public boolean shouldFadingOut() {
+ return mShouldFadingOut;
+ }
+
+ private void touch_up(MotionEvent event) {
+ mPath.lineTo(mX, mY);
+
+ if (mCacheGesture)
+ mCanvas.drawPath(mPath, mPaint);
+
+ // kill this so we don't double draw
+ if (shouldFadingOut()) {
+ mFadingAlpha = 1;
+ mIsFadingOut = true;
+ mHandler.removeCallbacks(mFadingOut);
+ mHandler.postDelayed(mFadingOut, 100);
+ }
+
+ Iterator<GestureListener> it = mGestureListeners.iterator();
+ while (it.hasNext()) {
+ it.next().onFinishGesture(this, event);
+ }
+ }
+
+}
diff --git a/tests/sketch/src/com/android/gesture/example/GestureEntryDemo.java b/tests/sketch/src/com/android/gesture/example/GestureEntryDemo.java
new file mode 100755
index 0000000..0f8d317
--- /dev/null
+++ b/tests/sketch/src/com/android/gesture/example/GestureEntryDemo.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gesture.example;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.AdapterView.OnItemSelectedListener;
+
+import com.android.gesture.Gesture;
+import com.android.gesture.GestureLib;
+import com.android.gesture.GestureListener;
+import com.android.gesture.GesturePad;
+import com.android.gesture.R;
+import com.android.gesture.recognizer.Prediction;
+
+import java.util.ArrayList;
+
+/**
+ * The demo shows how to construct a gesture-based user interface on Android.
+ */
+
+public class GestureEntryDemo extends Activity {
+
+ private static final int DIALOG_NEW_ENTRY = 1;
+ private static final int NEW_ID = Menu.FIRST;
+ private static final int VIEW_ID = Menu.FIRST + 1;
+
+ GesturePad mView;
+ Spinner mResult;
+ GestureLib mRecognizer;
+ boolean mChangedByRecognizer = false;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.demo);
+
+ // init the recognizer
+ mRecognizer = new GestureLib("/sdcard/gestureentry");
+ mRecognizer.load();
+
+ // create the spinner for showing the recognition results
+ // the spinner also allows a user to correct a prediction
+ mResult = (Spinner) findViewById(R.id.spinner);
+ mResult.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ // TODO Auto-generated method stub
+ // correct the recognition result by adding the new example
+ if (mChangedByRecognizer == false) {
+ mRecognizer.addGesture(parent.getSelectedItem().toString(),
+ mView.getCurrentGesture());
+ } else {
+ mChangedByRecognizer = false;
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // TODO Auto-generated method stub
+
+ }
+
+ });
+
+ // create the area for drawing a gesture
+ mView = (GesturePad)this.findViewById(R.id.drawingpad);
+ mView.cacheGesture(false);
+ mView.setFadingOut(false);
+ mView.addGestureListener(new GestureListener() {
+ @Override
+ public void onFinishGesture(GesturePad patch, MotionEvent event) {
+ // TODO Auto-generated method stub
+ recognize(patch.getCurrentGesture());
+ }
+ @Override
+ public void onGesture(GesturePad patch, MotionEvent event) {
+ // TODO Auto-generated method stub
+
+ }
+ @Override
+ public void onStartGesture(GesturePad patch, MotionEvent event) {
+ // TODO Auto-generated method stub
+
+ }
+ });
+
+
+ if (savedInstanceState != null) {
+ mView.setCurrentGesture(
+ (Gesture)savedInstanceState.getParcelable("gesture"));
+ }
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ // create the dialog for adding a new entry
+ LayoutInflater factory = LayoutInflater.from(this);
+ final View textEntryView =
+ factory.inflate(R.layout.newgesture_dialog, null);
+ return new AlertDialog.Builder(GestureEntryDemo.this)
+ .setTitle(R.string.newgesture_text_entry)
+ .setView(textEntryView)
+ .setPositiveButton(R.string.newgesture_dialog_ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ /* User clicked OK so do some stuff */
+ EditText edittext =
+ (EditText)((AlertDialog)dialog).findViewById(R.id.gesturename_edit);
+ String text = edittext.getText().toString().trim();
+ if (text.length() > 0) {
+ mRecognizer.addGesture(text, mView.getCurrentGesture());
+ }
+ }
+ })
+ .setNegativeButton(R.string.newgesture_dialog_cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ /* User clicked cancel so do some stuff */
+ }
+ })
+ .create();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // TODO Auto-generated method stub
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, NEW_ID, 0, R.string.newgesture)
+ .setShortcut('0', 'n')
+ .setIcon(android.R.drawable.ic_menu_add);
+ menu.add(0, VIEW_ID, 0, R.string.viewgesture)
+ .setShortcut('1', 'v')
+ .setIcon(android.R.drawable.ic_menu_view);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle all of the possible menu actions.
+ switch (item.getItemId()) {
+ case NEW_ID:
+ // if there has been a gesture on the canvas
+ if (mView.getCurrentGesture() != null) {
+ showDialog(DIALOG_NEW_ENTRY);
+ }
+ break;
+
+ case VIEW_ID:
+ startActivityForResult(
+ new Intent(this, GestureLibViewer.class), VIEW_ID);
+ break;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mRecognizer.load();
+ mView.clear();
+ }
+
+ @Override
+ protected void onPause() {
+ // TODO Auto-generated method stub
+ super.onPause();
+ mRecognizer.save();
+ }
+
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ // TODO Auto-generated method stub
+ super.onPrepareDialog(id, dialog);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ // TODO Auto-generated method stub
+ super.onSaveInstanceState(outState);
+ outState.putParcelable("gesture", mView.getCurrentGesture());
+ mRecognizer.save();
+ }
+
+ public void recognize(Gesture ink) {
+ mChangedByRecognizer = true;
+ ArrayList<Prediction> predictions = mRecognizer.recognize(ink);
+ ArrayAdapter adapter = new ArrayAdapter(this,
+ android.R.layout.simple_spinner_item, predictions);
+ adapter.setDropDownViewResource(
+ android.R.layout.simple_spinner_dropdown_item);
+ mResult.setAdapter(adapter);
+ }
+
+}
diff --git a/tests/sketch/src/com/android/gesture/example/GestureLibViewer.java b/tests/sketch/src/com/android/gesture/example/GestureLibViewer.java
new file mode 100755
index 0000000..a9893bc
--- /dev/null
+++ b/tests/sketch/src/com/android/gesture/example/GestureLibViewer.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gesture.example;
+
+import android.app.Activity;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.AdapterView.OnItemSelectedListener;
+
+import com.android.gesture.Gesture;
+import com.android.gesture.GestureLib;
+import com.android.gesture.GesturePad;
+import com.android.gesture.R;
+import com.android.gesture.recognizer.Instance;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * GestureLibViewer is for viewing existing gestures and
+ * removing unwanted gestures.
+ */
+
+public class GestureLibViewer extends Activity {
+
+ GesturePad mView;
+ Spinner mResult;
+ GestureLib mRecognizer;
+ ArrayList<Gesture> mSamples;
+ int mCurrentGestureIndex;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.gestureviewer);
+
+ // create the area for drawing a glyph
+ mView = (GesturePad)this.findViewById(R.id.drawingpad);
+ mView.cacheGesture(false);
+ mView.setFadingOut(false);
+ mView.setEnableInput(false);
+
+ // init the recognizer
+ mRecognizer = new GestureLib("/sdcard/gestureentry");
+ mRecognizer.load();
+
+ mResult = (Spinner) findViewById(R.id.spinner);
+ ArrayList<String> list = new ArrayList<String>();
+ list.addAll(mRecognizer.getLabels());
+ Collections.sort(list);
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_spinner_item,
+ list);
+ adapter.setDropDownViewResource(
+ android.R.layout.simple_spinner_dropdown_item);
+ mResult.setAdapter(adapter);
+ mSamples = mRecognizer.getGestures(list.get(0));
+ if (mSamples.isEmpty() == false) {
+ mCurrentGestureIndex = 0;
+ Gesture gesture = mSamples.get(mCurrentGestureIndex);
+ mView.setCurrentGesture(gesture);
+ mView.clearDebugPath();
+ mView.addDebugPath(
+ toPath(mRecognizer.getClassifier().getInstance(gesture.getID())));
+ }
+
+ mResult.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ // TODO Auto-generated method stub
+ mSamples = mRecognizer.getGestures(
+ (String)mResult.getSelectedItem());
+ if (mSamples.isEmpty() == false) {
+ mCurrentGestureIndex = 0;
+ Gesture gesture = mSamples.get(mCurrentGestureIndex);
+ mView.setCurrentGesture(gesture);
+ mView.clearDebugPath();
+ mView.addDebugPath(
+ toPath(mRecognizer.getClassifier().getInstance(gesture.getID())));
+ }
+ mView.invalidate();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // TODO Auto-generated method stub
+
+ }
+
+ });
+
+ Button remove = (Button)this.findViewById(R.id.remove);
+ remove.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // TODO Auto-generated method stub
+ if (mSamples.isEmpty())
+ return;
+
+ String name = (String)mResult.getSelectedItem();
+ Gesture gesture = mSamples.get(mCurrentGestureIndex);
+ mRecognizer.removeGesture(name, gesture);
+
+ mSamples = mRecognizer.getGestures(name);
+
+ if (mSamples == null) {
+ // delete the entire entry
+ mCurrentGestureIndex = 0;
+ ArrayList<String> list = new ArrayList<String>();
+ list.addAll(mRecognizer.getLabels());
+ Collections.sort(list);
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(
+ GestureLibViewer.this,
+ android.R.layout.simple_spinner_item,
+ list);
+ adapter.setDropDownViewResource(
+ android.R.layout.simple_spinner_dropdown_item);
+ mResult.setAdapter(adapter);
+ } else {
+ if (mCurrentGestureIndex > mSamples.size()-1) {
+ mCurrentGestureIndex--;
+ }
+ gesture = mSamples.get(mCurrentGestureIndex);
+ mView.setCurrentGesture(gesture);
+ mView.clearDebugPath();
+ mView.addDebugPath(
+ toPath(mRecognizer.getClassifier().getInstance(gesture.getID())));
+ mView.invalidate();
+ }
+ }
+ });
+
+ Button next = (Button)this.findViewById(R.id.next);
+ next.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // TODO Auto-generated method stub
+ if (mCurrentGestureIndex >= mSamples.size()-1)
+ return;
+
+ mCurrentGestureIndex++;
+ Gesture gesture = mSamples.get(mCurrentGestureIndex);
+ mView.setCurrentGesture(gesture);
+ mView.clearDebugPath();
+ mView.addDebugPath(
+ toPath(mRecognizer.getClassifier().getInstance(gesture.getID())));
+ mView.invalidate();
+ }
+ });
+
+ Button previous = (Button)this.findViewById(R.id.previous);
+ previous.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // TODO Auto-generated method stub
+ if (mCurrentGestureIndex >= 1 &&
+ mSamples.isEmpty() == false) {
+ mCurrentGestureIndex--;
+ Gesture gesture = mSamples.get(mCurrentGestureIndex);
+ mView.setCurrentGesture(gesture);
+ mView.clearDebugPath();
+ mView.addDebugPath(
+ toPath(mRecognizer.getClassifier().getInstance(gesture.getID())));
+ mView.invalidate();
+ }
+ }
+ });
+ }
+
+ public static ArrayList<Path> toPath(Instance instance) {
+ ArrayList<Path> paths = new ArrayList();
+ Path path = null;
+ float minx = 0, miny = 0;
+ float mX = 0, mY = 0;
+ for (int i=0; i<instance.vector.length; i+=2) {
+ float x = instance.vector[i];
+ float y = instance.vector[i+1];
+ if (x < minx)
+ minx = x;
+ if (y < miny)
+ miny = y;
+ if (path == null) {
+ path = new Path();
+ path.moveTo(x, y);
+ mX = x;
+ mY = y;
+ } else {
+ float dx = Math.abs(x - mX);
+ float dy = Math.abs(y - mY);
+ if (dx >= 3 || dy >= 3) {
+ path.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
+ mX = x;
+ mY = y;
+ }
+ }
+ }
+ Matrix matrix = new Matrix();
+ matrix.setTranslate(-minx + 10, -miny + 10);
+ path.transform(matrix);
+ paths.add(path);
+
+ path = new Path();
+ path.moveTo(instance.vector[0]-5, instance.vector[1]-5);
+ path.lineTo(instance.vector[0]-5, instance.vector[1]+5);
+ path.lineTo(instance.vector[0]+5, instance.vector[1]+5);
+ path.lineTo(instance.vector[0]+5, instance.vector[1]-5);
+ path.close();
+ path.transform(matrix);
+ paths.add(path);
+
+ return paths;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ // TODO Auto-generated method stub
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ mRecognizer.save();
+ this.setResult(RESULT_OK);
+ finish();
+ return true;
+ }
+ else
+ return false;
+ }
+
+ @Override
+ protected void onPause() {
+ // TODO Auto-generated method stub
+ super.onPause();
+ mRecognizer.save();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ // TODO Auto-generated method stub
+ super.onSaveInstanceState(outState);
+ mRecognizer.save();
+ }
+}
diff --git a/tests/sketch/src/com/android/gesture/recognizer/Classifier.java b/tests/sketch/src/com/android/gesture/recognizer/Classifier.java
new file mode 100755
index 0000000..584e0a5
--- /dev/null
+++ b/tests/sketch/src/com/android/gesture/recognizer/Classifier.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gesture.recognizer;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * The abstract class of Classifier
+ */
+public abstract class Classifier {
+
+ HashMap<Long, Instance> mInstances = new HashMap<Long, Instance>();
+
+ public void addInstance(Instance instance) {
+ mInstances.put(instance.id, instance);
+ }
+
+ public Instance getInstance(long id) {
+ return mInstances.get(id);
+ }
+
+ public void removeInstance(long id) {
+ mInstances.remove(id);
+ }
+
+ public abstract ArrayList<Prediction> classify(Instance instance);
+}
diff --git a/tests/sketch/src/com/android/gesture/recognizer/Instance.java b/tests/sketch/src/com/android/gesture/recognizer/Instance.java
new file mode 100755
index 0000000..2eaa1c2
--- /dev/null
+++ b/tests/sketch/src/com/android/gesture/recognizer/Instance.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.gesture.recognizer;
+
+import android.graphics.PointF;
+
+import com.android.gesture.Gesture;
+
+/**
+ * An instance represents a sample if the label is available or a query if
+ * the label is null.
+ */
+public class Instance {
+
+ private final static float[] targetOrientations = {
+ 0, 45, 90, 135, 180, -0, -45, -90, -135, -180
+ };
+
+ // the feature vector
+ public final float[] vector;
+ // the label can be null
+ public final String label;
+ // the length of the vector
+ public final float length;
+ // the id of the instance
+ public final long id;
+
+ Instance(long d, float[] v, String l) {
+ id = d;
+ vector = v;
+ label = l;
+ float sum = 0;
+ for (int i = 0; i < vector.length; i++) {
+ sum += vector[i] * vector[i];
+ }
+ length = (float)Math.sqrt(sum);
+ }
+
+ public static Instance createInstance(Gesture gesture, String label) {
+ float[] pts = RecognitionUtil.resample(gesture, 64);
+ PointF center = RecognitionUtil.computeCentroid(pts);
+ float inductiveOrientation = (float)Math.atan2(pts[1] - center.y,
+ pts[0] - center.x);
+ inductiveOrientation *= 180 / Math.PI;
+
+ float minDeviation = Float.MAX_VALUE;
+ for (int i=0; i<targetOrientations.length; i++) {
+ float delta = targetOrientations[i] - inductiveOrientation;
+ if (Math.abs(delta) < Math.abs(minDeviation)) {
+ minDeviation = delta;
+ }
+ }
+
+ android.graphics.Matrix m = new android.graphics.Matrix();
+ m.setTranslate(-center.x, -center.y);
+ android.graphics.Matrix rotation = new android.graphics.Matrix();
+ rotation.setRotate(minDeviation);
+ m.postConcat(rotation);
+ m.mapPoints(pts);
+
+ return new Instance(gesture.getID(), pts, label);
+ }
+}
diff --git a/tests/sketch/src/com/android/gesture/recognizer/NearestNeighbor.java b/tests/sketch/src/com/android/gesture/recognizer/NearestNeighbor.java
new file mode 100755
index 0000000..2035fad
--- /dev/null
+++ b/tests/sketch/src/com/android/gesture/recognizer/NearestNeighbor.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gesture.recognizer;
+
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.TreeMap;
+
+public class NearestNeighbor extends Classifier {
+
+ private static final String LOGTAG = "NearestNeighbor";
+ private static final double variance = 0.25; // std = 0.5
+
+ public ArrayList<Prediction> classify(Instance instance) {
+
+ ArrayList<Prediction> list = new ArrayList<Prediction>();
+ Iterator<Instance> it = mInstances.values().iterator();
+ Log.v(LOGTAG, mInstances.size() + " instances found");
+ TreeMap<String, Double> label2score = new TreeMap<String, Double>();
+ while (it.hasNext()) {
+ Instance sample = it.next();
+ double dis = RecognitionUtil.cosineDistance(sample, instance);
+ double weight = Math.exp(-dis*dis/(2 * variance));
+ Log.v(LOGTAG, sample.label + " = " + dis + " weight = " + weight);
+ Double score = label2score.get(sample.label);
+ if (score == null) {
+ score = weight;
+ }
+ else {
+ score += weight;
+ }
+ label2score.put(sample.label, score);
+ }
+
+ double sum = 0;
+ Iterator it2 = label2score.keySet().iterator();
+ while (it2.hasNext()) {
+ String name = (String)it2.next();
+ double score = label2score.get(name);
+ sum += score;
+ list.add(new Prediction(name, score));
+ }
+
+ it2 = list.iterator();
+ while (it2.hasNext()) {
+ Prediction name = (Prediction)it2.next();
+ name.score /= sum;
+ }
+
+
+ Collections.sort(list, new Comparator<Prediction>() {
+ @Override
+ public int compare(Prediction object1, Prediction object2) {
+ // TODO Auto-generated method stub
+ double score1 = object1.score;
+ double score2 = object2.score;
+ if (score1 > score2)
+ return -1;
+ else if (score1 < score2)
+ return 1;
+ else
+ return 0;
+ }
+ });
+
+ it2 = list.iterator();
+ while (it2.hasNext()) {
+ Prediction name = (Prediction)it2.next();
+ Log.v(LOGTAG, "prediction [" + name.label + " = " + name.score + "]");
+ }
+
+ return list;
+ }
+}
diff --git a/tests/sketch/src/com/android/gesture/recognizer/Prediction.java b/tests/sketch/src/com/android/gesture/recognizer/Prediction.java
new file mode 100755
index 0000000..c318754
--- /dev/null
+++ b/tests/sketch/src/com/android/gesture/recognizer/Prediction.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gesture.recognizer;
+
+/**
+ *
+ * A recognition result that includes the label and its score
+ */
+public class Prediction {
+ public final String label;
+ public double score;
+
+ public Prediction(String l, double s) {
+ label = l;
+ score = s;
+ }
+
+ @Override
+ public String toString() {
+ return label;
+ }
+}
diff --git a/tests/sketch/src/com/android/gesture/recognizer/RecognitionUtil.java b/tests/sketch/src/com/android/gesture/recognizer/RecognitionUtil.java
new file mode 100755
index 0000000..9146b95
--- /dev/null
+++ b/tests/sketch/src/com/android/gesture/recognizer/RecognitionUtil.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2008-2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gesture.recognizer;
+
+import android.graphics.PointF;
+
+import com.android.gesture.Gesture;
+
+import java.util.Iterator;
+
+/**
+ *
+ * Utilities for recognition.
+ */
+
+public class RecognitionUtil {
+
+ /**
+ * Re-sample a list of points to a given number
+ * @param stk
+ * @param num
+ * @return
+ */
+ public static float[] resample(Gesture gesture, int num) {
+ final float increment = gesture.getLength()/(num - 1);
+ float[] newstk = new float[num*2];
+ float distanceSoFar = 0;
+ Iterator<PointF> it = gesture.getPoints().iterator();
+ PointF lstPoint = it.next();
+ int index = 0;
+ PointF currentPoint = null;
+ try
+ {
+ newstk[index] = lstPoint.x;
+ index++;
+ newstk[index] = lstPoint.y;
+ index++;
+ while (it.hasNext()) {
+ if (currentPoint == null)
+ currentPoint = it.next();
+ float deltaX = currentPoint.x - lstPoint.x;
+ float deltaY = currentPoint.y - lstPoint.y;
+ float distance = (float)Math.sqrt(deltaX*deltaX+deltaY*deltaY);
+ if (distanceSoFar+distance >= increment) {
+ float ratio = (increment - distanceSoFar) / distance;
+ float nx = lstPoint.x + ratio * deltaX;
+ float ny = lstPoint.y + ratio * deltaY;
+ newstk[index] = nx;
+ index++;
+ newstk[index] = ny;
+ index++;
+ lstPoint = new PointF(nx, ny);
+ distanceSoFar = 0;
+ }
+ else {
+ lstPoint = currentPoint;
+ currentPoint = null;
+ distanceSoFar += distance;
+ }
+ }
+ }
+ catch(Exception ex) {
+ ex.printStackTrace();
+ }
+
+ for(int i = index; i < newstk.length -1; i+=2) {
+ newstk[i] = lstPoint.x;
+ newstk[i+1] = lstPoint.y;
+ }
+ return newstk;
+ }
+
+ /**
+ * Calculate the centroid of a list of points
+ * @param points
+ * @return the centroid
+ */
+ public static PointF computeCentroid(float[] points) {
+ float centerX = 0;
+ float centerY = 0;
+ for(int i=0; i<points.length; i++)
+ {
+ centerX += points[i];
+ i++;
+ centerY += points[i];
+ }
+ centerX = 2 * centerX/points.length;
+ centerY = 2 * centerY/points.length;
+ return new PointF(centerX, centerY);
+ }
+
+ /**
+ * calculate the variance-covariance matrix, treat each point as a sample
+ * @param points
+ * @return
+ */
+ public static double[][] computeCoVariance(float[] points) {
+ double[][] array = new double[2][2];
+ array[0][0] = 0;
+ array[0][1] = 0;
+ array[1][0] = 0;
+ array[1][1] = 0;
+ for(int i=0; i<points.length; i++)
+ {
+ float x = points[i];
+ i++;
+ float y = points[i];
+ array[0][0] += x * x;
+ array[0][1] += x * y;
+ array[1][0] = array[0][1];
+ array[1][1] += y * y;
+ }
+ array[0][0] /= (points.length/2);
+ array[0][1] /= (points.length/2);
+ array[1][0] /= (points.length/2);
+ array[1][1] /= (points.length/2);
+
+ return array;
+ }
+
+
+ public static float computeTotalLength(float[] points) {
+ float sum = 0;
+ for (int i=0; i<points.length - 4; i+=2) {
+ float dx = points[i+2] - points[i];
+ float dy = points[i+3] - points[i+1];
+ sum += Math.sqrt(dx*dx + dy*dy);
+ }
+ return sum;
+ }
+
+ public static double computeStraightness(float[] points) {
+ float totalLen = computeTotalLength(points);
+ float dx = points[2] - points[0];
+ float dy = points[3] - points[1];
+ return Math.sqrt(dx*dx + dy*dy) / totalLen;
+ }
+
+ public static double computeStraightness(float[] points, float totalLen) {
+ float dx = points[2] - points[0];
+ float dy = points[3] - points[1];
+ return Math.sqrt(dx*dx + dy*dy) / totalLen;
+ }
+
+ public static double averageEuclidDistance(float[] stk1, float[] stk2) {
+ double distance = 0;
+ for (int i = 0; i < stk1.length; i += 2) {
+ distance += PointF.length(stk1[i] - stk2[i], stk1[i+1] - stk2[i+1]);
+ }
+ return distance/stk1.length;
+ }
+
+ public static double squaredEuclidDistance(float[] stk1, float[] stk2) {
+ double squaredDistance = 0;
+ for (int i = 0; i < stk1.length; i++) {
+ float difference = stk1[i] - stk2[i];
+ squaredDistance += difference * difference;
+ }
+ return squaredDistance/stk1.length;
+ }
+
+ /**
+ * Calculate the cosine distance between two instances
+ * @param in1
+ * @param in2
+ * @return the angle between 0 and Math.PI
+ */
+ public static double cosineDistance(Instance in1, Instance in2) {
+ float sum = 0;
+ for (int i = 0; i < in1.vector.length; i++) {
+ sum += in1.vector[i] * in2.vector[i];
+ }
+ return Math.acos(sum / (in1.length * in2.length));
+ }
+
+}