Wrote a benchmark activity for Android which reads images recursively from the SD...
authordswitkin <dswitkin@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Fri, 24 Oct 2008 22:05:07 +0000 (22:05 +0000)
committerdswitkin <dswitkin@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Fri, 24 Oct 2008 22:05:07 +0000 (22:05 +0000)
git-svn-id: http://zxing.googlecode.com/svn/trunk@640 59b500cc-1b3d-0410-9834-0bbf25fbcc57

androidtest/AndroidManifest.xml
androidtest/res/layout/benchmark.xml [new file with mode: 0755]
androidtest/res/layout/test.xml
androidtest/res/values/ids.xml
androidtest/res/values/strings.xml
androidtest/src/com/google/zxing/client/androidtest/BenchmarkActivity.java [new file with mode: 0755]
androidtest/src/com/google/zxing/client/androidtest/BenchmarkItem.java [new file with mode: 0644]
androidtest/src/com/google/zxing/client/androidtest/BenchmarkThread.java [new file with mode: 0755]
androidtest/src/com/google/zxing/client/androidtest/RGBMonochromeBitmapSource.java [new file with mode: 0644]
androidtest/src/com/google/zxing/client/androidtest/ZXingTestActivity.java

index 35846c2..8d76489 100755 (executable)
@@ -28,6 +28,8 @@
     <activity android:name="CameraTestActivity"
               android:screenOrientation="landscape">
     </activity>
+    <activity android:name="BenchmarkActivity"
+              android:label="@string/benchmark_name"/>
   </application>
   <uses-permission android:name="android.permission.CAMERA"/>
 </manifest>
diff --git a/androidtest/res/layout/benchmark.xml b/androidtest/res/layout/benchmark.xml
new file mode 100755 (executable)
index 0000000..eba476c
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2008 ZXing authors
+
+ 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.
+ -->
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="fill_parent"
+             android:layout_height="fill_parent"
+             android:gravity="center_horizontal"
+             android:padding="10px">
+  <TableRow>
+    <Button android:id="@+id/benchmark_run"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/benchmark_run"/>
+  </TableRow>
+  <TableRow>
+    <TextView android:id="@+id/benchmark_help"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="false"
+            android:text="@string/benchmark_help"/>
+  </TableRow>
+</TableLayout>
index e0c752a..bf1e964 100755 (executable)
              android:layout_height="fill_parent"
              android:gravity="center_horizontal"
              android:padding="10px">
+  <TableRow>
+    <Button android:id="@+id/test_camera"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/test_camera"/>
+    <Button android:id="@+id/run_benchmark"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/benchmark_run"/>
+  </TableRow>
   <TableRow>
     <Button android:id="@+id/scan_product"
             android:layout_width="wrap_content"
             android:text="@string/scan_qr_code"/>
   </TableRow>
   <TableRow>
-    <Button android:id="@+id/test_camera"
+    <Button android:id="@+id/scan_anything"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="@string/test_camera"/>
+            android:text="@string/scan_anything"/>
     <Button android:id="@+id/search_book_contents"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
index 72bf92f..8d091b4 100755 (executable)
@@ -15,8 +15,8 @@
  limitations under the License.
  -->
 <resources>
-  <!-- Messages IDs -->
   <item type="id" name="auto_focus"/>
+  <item type="id" name="benchmark_done"/>
   <item type="id" name="quit"/>
   <item type="id" name="save"/>
   <item type="id" name="save_succeeded"/>
index e786edd..575c71b 100755 (executable)
@@ -16,6 +16,7 @@
  -->
 <resources>
   <string name="app_name">ZXing Test</string>
+  <string name="scan_anything">Scan anything</string>
   <string name="scan_product">Scan product</string>
   <string name="scan_qr_code">Scan QR Code</string>
   <string name="search_book_contents">Search Book Contents</string>
@@ -33,4 +34,9 @@
   <string name="status_message">Press the shutter button to save images to the SD card for testing purposes. Press DPAD_CENTER to trigger autofocus.</string>
   <string name="save_succeeded">Save succeeded</string>
   <string name="save_failed">Save failed - is the SD card installed?</string>
+
+  <string name="benchmark_name">ZXing Benchmark</string>
+  <string name="benchmark_help">Place images in /sdcard/zxingbenchmark, then check \"adb logcat\" for results. Turn on Airplane Mode first for more reliable results.</string>
+  <string name="benchmark_run">Run benchmark</string>
+  <string name="benchmark_running">Benchmark running...\u2026</string>
 </resources>
diff --git a/androidtest/src/com/google/zxing/client/androidtest/BenchmarkActivity.java b/androidtest/src/com/google/zxing/client/androidtest/BenchmarkActivity.java
new file mode 100755 (executable)
index 0000000..06e9131
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2008 ZXing authors
+ *
+ * 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.google.zxing.client.androidtest;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import com.google.zxing.BarcodeFormat;
+
+import java.util.Vector;
+
+public class BenchmarkActivity extends Activity {
+
+  private static final String PATH = "/sdcard/zxingbenchmark";
+  private static final String TAG = "ZXingBenchmark";
+
+  private Button mRunBenchmarkButton;
+  private TextView mTextView;
+  private BenchmarkThread mBenchmarkThread;
+
+  @Override
+  public void onCreate(Bundle icicle) {
+    super.onCreate(icicle);
+
+    setContentView(R.layout.benchmark);
+
+    mRunBenchmarkButton = (Button) findViewById(R.id.benchmark_run);
+    mRunBenchmarkButton.setOnClickListener(mRunBenchmark);
+    mTextView = (TextView) findViewById(R.id.benchmark_help);
+
+    mBenchmarkThread = null;
+  }
+
+  public Button.OnClickListener mRunBenchmark = new Button.OnClickListener() {
+    public void onClick(View v) {
+      if (mBenchmarkThread == null) {
+        mRunBenchmarkButton.setEnabled(false);
+        mTextView.setText(R.string.benchmark_running);
+        mBenchmarkThread = new BenchmarkThread(BenchmarkActivity.this, PATH);
+        mBenchmarkThread.start();
+      }
+    }
+  };
+
+  public Handler mHandler = new Handler() {
+    public void handleMessage(Message message) {
+      switch (message.what) {
+        case R.id.benchmark_done:
+          handleBenchmarkDone(message);
+          mBenchmarkThread = null;
+          mRunBenchmarkButton.setEnabled(true);
+          mTextView.setText(R.string.benchmark_help);
+          break;
+        default:
+          break;
+      }
+    }
+  };
+
+  private void handleBenchmarkDone(Message message) {
+    Vector<BenchmarkItem> items = (Vector<BenchmarkItem>) message.obj;
+    int count = 0;
+    for (int x = 0; x < items.size(); x++) {
+      BenchmarkItem item = items.get(x);
+      if (item != null) {
+        BarcodeFormat format = item.getFormat();
+        Log.v(TAG, (item.getDecoded() ? "DECODED: " : "FAILED: ") + item.getPath());
+        Log.v(TAG, "  Ran " + item.getCount() + " tests on " +
+            (format != null ? format.toString() : "unknown") + " format with an average of " +
+            item.getAverageMilliseconds() + " ms");
+        count++;
+      }
+    }
+    Log.v(TAG, "TOTAL: Decoded " + count + " images");
+  }
+
+}
diff --git a/androidtest/src/com/google/zxing/client/androidtest/BenchmarkItem.java b/androidtest/src/com/google/zxing/client/androidtest/BenchmarkItem.java
new file mode 100644 (file)
index 0000000..3bd68e1
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.google.zxing.client.androidtest;
+
+import com.google.zxing.BarcodeFormat;
+
+public class BenchmarkItem {
+
+  private String mPath;
+  private int[] mTimes;
+  private int mPosition;
+  private boolean mDecoded;
+  private BarcodeFormat mFormat;
+
+  public BenchmarkItem(String path, int runs) {
+    mPath = path;
+    mTimes = new int[runs];
+    mPosition = 0;
+    mDecoded = false;
+    mFormat = null;
+  }
+
+  // I'm storing these separately instead of as a running total so I can add features like
+  // calculating the min and max later, or ignoring outliers.
+  public void addResult(int milliseconds) {
+    mTimes[mPosition] = milliseconds;
+    mPosition++;
+  }
+
+  public void setDecoded(boolean decoded) {
+    mDecoded = decoded;
+  }
+
+  public void setFormat(BarcodeFormat format) {
+    mFormat = format;
+  }
+
+  public String getPath() {
+    return mPath;
+  }
+
+  public int getAverageMilliseconds() {
+    int size = mTimes.length;
+    int total = 0;
+    for (int x = 0; x < size; x++) {
+      total += mTimes[x];
+    }
+    if (size > 0) {
+      return total / size;
+    } else {
+      return 0;
+    }
+  }
+
+  public int getCount() {
+    return mTimes.length;
+  }
+
+  public boolean getDecoded() {
+    return mDecoded;
+  }
+
+  public BarcodeFormat getFormat() {
+    return mFormat;
+  }
+
+}
diff --git a/androidtest/src/com/google/zxing/client/androidtest/BenchmarkThread.java b/androidtest/src/com/google/zxing/client/androidtest/BenchmarkThread.java
new file mode 100755 (executable)
index 0000000..182d730
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2008 ZXing authors
+ *
+ * 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.google.zxing.client.androidtest;
+
+import android.os.Message;
+import android.util.Log;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.ReaderException;
+import com.google.zxing.Result;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Date;
+import java.util.Vector;
+
+final class BenchmarkThread extends Thread {
+
+  private static final String TAG = "BenchmarkThread";
+  private static final int RUNS = 10;
+
+  private BenchmarkActivity mActivity;
+  private String mPath;
+  private MultiFormatReader mMultiFormatReader;
+
+  BenchmarkThread(BenchmarkActivity activity, String path) {
+    mActivity = activity;
+    mPath = path;
+  }
+
+  @Override
+  public void run() {
+    mMultiFormatReader = new MultiFormatReader();
+    mMultiFormatReader.setHints(null);
+
+    Vector<BenchmarkItem> items = new Vector<BenchmarkItem>();
+    walkTree(mPath, items);
+    Message message = Message.obtain(mActivity.mHandler, R.id.benchmark_done);
+    message.obj = items;
+    message.sendToTarget();
+  }
+
+  // Recurse to allow subdirectories
+  private void walkTree(String path, Vector<BenchmarkItem> items) {
+    File file = new File(path);
+    if (file.isDirectory()) {
+      String[] files = file.list();
+      for (int x = 0; x < files.length; x++) {
+        walkTree(file.getAbsolutePath() + "/" + files[x], items);
+      }
+    } else {
+      BenchmarkItem item = decode(path);
+      if (item != null) {
+        items.addElement(item);
+      }
+    }
+  }
+
+  private BenchmarkItem decode(String path) {
+    RGBMonochromeBitmapSource source = null;
+    try {
+      source = new RGBMonochromeBitmapSource(path);
+    } catch (FileNotFoundException e) {
+      Log.e(TAG, e.toString());
+      return null;
+    }
+
+    BenchmarkItem item = new BenchmarkItem(path, RUNS);
+    for (int x = 0; x < RUNS; x++) {
+      Date startDate = new Date();
+      boolean success;
+      Result result = null;
+      try {
+        result = mMultiFormatReader.decodeWithState(source);
+        success = true;
+      } catch (ReaderException e) {
+        success = false;
+      }
+      Date endDate = new Date();
+      if (x == 0) {
+        item.setDecoded(success);
+        item.setFormat(result != null ? result.getBarcodeFormat() : null);
+      }
+      item.addResult((int) (endDate.getTime() - startDate.getTime()));
+    }
+    return item;
+  }
+
+}
diff --git a/androidtest/src/com/google/zxing/client/androidtest/RGBMonochromeBitmapSource.java b/androidtest/src/com/google/zxing/client/androidtest/RGBMonochromeBitmapSource.java
new file mode 100644 (file)
index 0000000..623e28b
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.google.zxing.client.androidtest;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import com.google.zxing.common.BaseMonochromeBitmapSource;
+
+import java.io.FileNotFoundException;
+
+public class RGBMonochromeBitmapSource extends BaseMonochromeBitmapSource {
+
+  private int mWidth;
+  private int mHeight;
+  private byte[] mLuminances;
+
+  public RGBMonochromeBitmapSource(String path) throws FileNotFoundException {
+    Bitmap bitmap = BitmapFactory.decodeFile(path);
+    if (bitmap == null) {
+      throw new FileNotFoundException("Couldn't open " + path);
+    }
+
+    int width = bitmap.getWidth();
+    int height = bitmap.getHeight();
+    int[] pixels = new int[width * height];
+    bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
+
+    // In order to measure pure decoding speed, we convert the entire image to a greyscale array up
+    // front, which is the same as the Y channel of the YUVMonochromeBitmapSource in the real app.
+    mLuminances = new byte[width * height];
+    mWidth = width;
+    mHeight = height;
+    for (int y = 0; y < height; y++) {
+      int offset = y * height;
+      for (int x = 0; x < width; x++) {
+        int pixel = pixels[offset + x];
+        int r = (pixel >> 16) & 0xff;
+        int g = (pixel >> 8) & 0xff;
+        int b = pixel & 0xff;
+        if (r == g && g == b) {
+          // Image is already greyscale, so pick any channel
+          mLuminances[offset + x] = (byte) r;
+        } else {
+          // Calculate luminance cheaply, favoring green
+          mLuminances[offset + x] = (byte) ((r + g + g + b) >> 2);
+        }
+      }
+    }
+  }
+
+  public int getHeight() {
+    return mHeight;
+  }
+
+  public int getWidth() {
+    return mWidth;
+  }
+
+  public int getLuminance(int x, int y) {
+    return mLuminances[y * mWidth + x] & 0xff;
+  }
+
+  public void cacheRowForLuminance(int y) {
+
+  }
+
+  public void cacheColumnForLuminance(int x) {
+
+  }
+
+}
index 9df7be9..a989a5a 100755 (executable)
@@ -32,14 +32,20 @@ public class ZXingTestActivity extends Activity {
 
     setContentView(R.layout.test);
 
+    Button test_camera = (Button) findViewById(R.id.test_camera);
+    test_camera.setOnClickListener(mTestCamera);
+
+    Button run_benchmark = (Button) findViewById(R.id.run_benchmark);
+    run_benchmark.setOnClickListener(mRunBenchmark);
+
     Button scan_product = (Button) findViewById(R.id.scan_product);
     scan_product.setOnClickListener(mScanProduct);
 
     Button scan_qr_code = (Button) findViewById(R.id.scan_qr_code);
     scan_qr_code.setOnClickListener(mScanQRCode);
 
-    Button test_camera = (Button) findViewById(R.id.test_camera);
-    test_camera.setOnClickListener(mTestCamera);
+    Button scan_anything = (Button) findViewById(R.id.scan_anything);
+    scan_anything.setOnClickListener(mScanAnything);
 
     Button search_book_contents = (Button) findViewById(R.id.search_book_contents);
     search_book_contents.setOnClickListener(mSearchBookContents);
@@ -66,6 +72,22 @@ public class ZXingTestActivity extends Activity {
     encode_bad_data.setOnClickListener(mEncodeBadData);
   }
 
+  public Button.OnClickListener mTestCamera = new Button.OnClickListener() {
+    public void onClick(View v) {
+      Intent intent = new Intent(Intent.ACTION_VIEW);
+      intent.setClassName(ZXingTestActivity.this, CameraTestActivity.class.getName());
+      startActivity(intent);
+    }
+  };
+
+  public Button.OnClickListener mRunBenchmark = new Button.OnClickListener() {
+    public void onClick(View v) {
+      Intent intent = new Intent(Intent.ACTION_VIEW);
+      intent.setClassName(ZXingTestActivity.this, BenchmarkActivity.class.getName());
+      startActivity(intent);
+    }
+  };
+
   public Button.OnClickListener mScanProduct = new Button.OnClickListener() {
     public void onClick(View v) {
       Intent intent = new Intent("com.google.zxing.client.android.SCAN");
@@ -82,11 +104,10 @@ public class ZXingTestActivity extends Activity {
     }
   };
 
-  public Button.OnClickListener mTestCamera = new Button.OnClickListener() {
+  public Button.OnClickListener mScanAnything = new Button.OnClickListener() {
     public void onClick(View v) {
-      Intent intent = new Intent(Intent.ACTION_VIEW);
-      intent.setClassName(ZXingTestActivity.this, CameraTestActivity.class.getName());
-      startActivity(intent);
+      Intent intent = new Intent("com.google.zxing.client.android.SCAN");
+      startActivityForResult(intent, 0);
     }
   };