Merge change 279 into donut
* changes: Add sketch gesture demo application.
This commit is contained in:
30
tests/sketch/AndroidManifest.xml
Executable file
30
tests/sketch/AndroidManifest.xml
Executable file
@@ -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>
|
||||
BIN
tests/sketch/res/drawable/icon.png
Executable file
BIN
tests/sketch/res/drawable/icon.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
33
tests/sketch/res/layout/demo.xml
Executable file
33
tests/sketch/res/layout/demo.xml
Executable file
@@ -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>
|
||||
58
tests/sketch/res/layout/gestureviewer.xml
Executable file
58
tests/sketch/res/layout/gestureviewer.xml
Executable file
@@ -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>
|
||||
34
tests/sketch/res/layout/newgesture_dialog.xml
Executable file
34
tests/sketch/res/layout/newgesture_dialog.xml
Executable file
@@ -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>
|
||||
28
tests/sketch/res/values/strings.xml
Executable file
28
tests/sketch/res/values/strings.xml
Executable file
@@ -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>
|
||||
361
tests/sketch/src/com/android/gesture/Gesture.java
Executable file
361
tests/sketch/src/com/android/gesture/Gesture.java
Executable file
@@ -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;
|
||||
}
|
||||
}
|
||||
280
tests/sketch/src/com/android/gesture/GestureLib.java
Executable file
280
tests/sketch/src/com/android/gesture/GestureLib.java
Executable file
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
25
tests/sketch/src/com/android/gesture/GestureListener.java
Executable file
25
tests/sketch/src/com/android/gesture/GestureListener.java
Executable file
@@ -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);
|
||||
}
|
||||
371
tests/sketch/src/com/android/gesture/GesturePad.java
Executable file
371
tests/sketch/src/com/android/gesture/GesturePad.java
Executable file
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
225
tests/sketch/src/com/android/gesture/example/GestureEntryDemo.java
Executable file
225
tests/sketch/src/com/android/gesture/example/GestureEntryDemo.java
Executable file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
262
tests/sketch/src/com/android/gesture/example/GestureLibViewer.java
Executable file
262
tests/sketch/src/com/android/gesture/example/GestureLibViewer.java
Executable file
@@ -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();
|
||||
}
|
||||
}
|
||||
42
tests/sketch/src/com/android/gesture/recognizer/Classifier.java
Executable file
42
tests/sketch/src/com/android/gesture/recognizer/Classifier.java
Executable file
@@ -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);
|
||||
}
|
||||
78
tests/sketch/src/com/android/gesture/recognizer/Instance.java
Executable file
78
tests/sketch/src/com/android/gesture/recognizer/Instance.java
Executable file
@@ -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);
|
||||
}
|
||||
}
|
||||
91
tests/sketch/src/com/android/gesture/recognizer/NearestNeighbor.java
Executable file
91
tests/sketch/src/com/android/gesture/recognizer/NearestNeighbor.java
Executable file
@@ -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;
|
||||
}
|
||||
}
|
||||
36
tests/sketch/src/com/android/gesture/recognizer/Prediction.java
Executable file
36
tests/sketch/src/com/android/gesture/recognizer/Prediction.java
Executable file
@@ -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;
|
||||
}
|
||||
}
|
||||
190
tests/sketch/src/com/android/gesture/recognizer/RecognitionUtil.java
Executable file
190
tests/sketch/src/com/android/gesture/recognizer/RecognitionUtil.java
Executable file
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user