]> Pileus Git - ~andy/freeotp/commitdiff
Add native camera support
authorNathaniel McCallum <npmccallum@redhat.com>
Wed, 4 Dec 2013 21:25:51 +0000 (16:25 -0500)
committerNathaniel McCallum <npmccallum@redhat.com>
Wed, 4 Dec 2013 21:25:51 +0000 (16:25 -0500)
57 files changed:
AndroidManifest.xml
res/layout/camera.xml [new file with mode: 0644]
res/values/strings.xml
src/com/google/zxing/BarcodeFormat.java [new file with mode: 0644]
src/com/google/zxing/Binarizer.java [new file with mode: 0644]
src/com/google/zxing/BinaryBitmap.java [new file with mode: 0644]
src/com/google/zxing/ChecksumException.java [new file with mode: 0644]
src/com/google/zxing/DecodeHintType.java [new file with mode: 0644]
src/com/google/zxing/FormatException.java [new file with mode: 0644]
src/com/google/zxing/InvertedLuminanceSource.java [new file with mode: 0644]
src/com/google/zxing/LuminanceSource.java [new file with mode: 0644]
src/com/google/zxing/NotFoundException.java [new file with mode: 0644]
src/com/google/zxing/PlanarYUVLuminanceSource.java [new file with mode: 0644]
src/com/google/zxing/Reader.java [new file with mode: 0644]
src/com/google/zxing/ReaderException.java [new file with mode: 0644]
src/com/google/zxing/Result.java [new file with mode: 0644]
src/com/google/zxing/ResultMetadataType.java [new file with mode: 0644]
src/com/google/zxing/ResultPoint.java [new file with mode: 0644]
src/com/google/zxing/ResultPointCallback.java [new file with mode: 0644]
src/com/google/zxing/common/BitArray.java [new file with mode: 0644]
src/com/google/zxing/common/BitMatrix.java [new file with mode: 0755]
src/com/google/zxing/common/BitSource.java [new file with mode: 0755]
src/com/google/zxing/common/CharacterSetECI.java [new file with mode: 0644]
src/com/google/zxing/common/DecoderResult.java [new file with mode: 0644]
src/com/google/zxing/common/DefaultGridSampler.java [new file with mode: 0644]
src/com/google/zxing/common/DetectorResult.java [new file with mode: 0644]
src/com/google/zxing/common/GlobalHistogramBinarizer.java [new file with mode: 0644]
src/com/google/zxing/common/GridSampler.java [new file with mode: 0644]
src/com/google/zxing/common/HybridBinarizer.java [new file with mode: 0644]
src/com/google/zxing/common/PerspectiveTransform.java [new file with mode: 0644]
src/com/google/zxing/common/StringUtils.java [new file with mode: 0644]
src/com/google/zxing/common/detector/MathUtils.java [new file with mode: 0644]
src/com/google/zxing/common/reedsolomon/GenericGF.java [new file with mode: 0644]
src/com/google/zxing/common/reedsolomon/GenericGFPoly.java [new file with mode: 0644]
src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java [new file with mode: 0644]
src/com/google/zxing/common/reedsolomon/ReedSolomonException.java [new file with mode: 0644]
src/com/google/zxing/datamatrix/decoder/Version.java [new file with mode: 0644]
src/com/google/zxing/qrcode/QRCodeReader.java [new file with mode: 0644]
src/com/google/zxing/qrcode/decoder/BitMatrixParser.java [new file with mode: 0644]
src/com/google/zxing/qrcode/decoder/DataBlock.java [new file with mode: 0755]
src/com/google/zxing/qrcode/decoder/DataMask.java [new file with mode: 0755]
src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java [new file with mode: 0644]
src/com/google/zxing/qrcode/decoder/Decoder.java [new file with mode: 0644]
src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java [new file with mode: 0644]
src/com/google/zxing/qrcode/decoder/FormatInformation.java [new file with mode: 0644]
src/com/google/zxing/qrcode/decoder/Mode.java [new file with mode: 0644]
src/com/google/zxing/qrcode/decoder/QRCodeDecoderMetaData.java [new file with mode: 0644]
src/com/google/zxing/qrcode/decoder/Version.java [new file with mode: 0755]
src/com/google/zxing/qrcode/detector/AlignmentPattern.java [new file with mode: 0644]
src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java [new file with mode: 0644]
src/com/google/zxing/qrcode/detector/Detector.java [new file with mode: 0644]
src/com/google/zxing/qrcode/detector/FinderPattern.java [new file with mode: 0644]
src/com/google/zxing/qrcode/detector/FinderPatternFinder.java [new file with mode: 0755]
src/com/google/zxing/qrcode/detector/FinderPatternInfo.java [new file with mode: 0644]
src/org/fedorahosted/freeotp/CameraDialogFragment.java [new file with mode: 0644]
src/org/fedorahosted/freeotp/DecodeAsyncTask.java [new file with mode: 0644]
src/org/fedorahosted/freeotp/MainActivity.java

index 8c2c6027a91ead42e957c3c283f25099c7e507e0..616e0aebfe9ddaef83c2bdab71b495e7a4be4e58 100644 (file)
         android:minSdkVersion="11"
         android:targetSdkVersion="19" />
 
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-feature android:name="android.hardware.camera" android:required="false" />
+    <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
+
     <application
         android:allowBackup="true"
         android:icon="@drawable/ic_launcher"
diff --git a/res/layout/camera.xml b/res/layout/camera.xml
new file mode 100644 (file)
index 0000000..7d784e8
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/black" >
+    <SurfaceView
+        android:id="@+id/camera_surfaceview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <TextView
+        android:id="@+id/camera_textview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:visibility="invisible"
+        android:textColor="@android:color/primary_text_dark"
+        android:text="@string/error_camera_open"/>
+</FrameLayout>
index 23d354f16b06fe88daad240fe51a10e7e3f0682d..3c00645e919020a715de632646c1b37f02c1bf0c 100644 (file)
@@ -12,6 +12,7 @@
     <string name="no_keys">No OTP keys installed.</string>
     <string name="add_token">Add Token</string>
     <string name="scan_qr_code">Scan QR Code</string>
+    <string name="manual_entry">Manual Entry</string>
     <string name="interval">Interval</string>
     <string name="counter">Counter</string>
     <string name="issuer">Issuer</string>
@@ -33,6 +34,8 @@
     <string name="link_ask_for_help">&lt;a href=&quot;http://lists.fedorahosted.org/mailman/listinfo/freeotp-devel&quot;&gt;Ask for Help&lt;/a&gt;</string>
     <string name="link_apache2">&lt;a href=&quot;http://www.apache.org/licenses/LICENSE-2.0.html&quot;&gt;Apache 2.0&lt;/a&gt;</string>
 
+    <string name="error_camera_open">Error while opening camera!</string>
+
     <plurals name="tokens_selected">
         <item quantity="one">%d token selected</item>
         <item quantity="other">%d tokens selected</item>
diff --git a/src/com/google/zxing/BarcodeFormat.java b/src/com/google/zxing/BarcodeFormat.java
new file mode 100644 (file)
index 0000000..7f6a0ef
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * Enumerates barcode formats known to this package. Please keep alphabetized.
+ *
+ * @author Sean Owen
+ */
+public enum BarcodeFormat {
+
+  /** Aztec 2D barcode format. */
+  AZTEC,
+
+  /** CODABAR 1D format. */
+  CODABAR,
+
+  /** Code 39 1D format. */
+  CODE_39,
+
+  /** Code 93 1D format. */
+  CODE_93,
+
+  /** Code 128 1D format. */
+  CODE_128,
+
+  /** Data Matrix 2D barcode format. */
+  DATA_MATRIX,
+
+  /** EAN-8 1D format. */
+  EAN_8,
+
+  /** EAN-13 1D format. */
+  EAN_13,
+
+  /** ITF (Interleaved Two of Five) 1D format. */
+  ITF,
+
+  /** MaxiCode 2D barcode format. */
+  MAXICODE,
+
+  /** PDF417 format. */
+  PDF_417,
+
+  /** QR Code 2D barcode format. */
+  QR_CODE,
+
+  /** RSS 14 */
+  RSS_14,
+
+  /** RSS EXPANDED */
+  RSS_EXPANDED,
+
+  /** UPC-A 1D format. */
+  UPC_A,
+
+  /** UPC-E 1D format. */
+  UPC_E,
+
+  /** UPC/EAN extension format. Not a stand-alone format. */
+  UPC_EAN_EXTENSION
+
+}
diff --git a/src/com/google/zxing/Binarizer.java b/src/com/google/zxing/Binarizer.java
new file mode 100644 (file)
index 0000000..25564c7
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2009 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;
+
+import com.google.zxing.common.BitArray;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * This class hierarchy provides a set of methods to convert luminance data to 1 bit data.
+ * It allows the algorithm to vary polymorphically, for example allowing a very expensive
+ * thresholding technique for servers and a fast one for mobile. It also permits the implementation
+ * to vary, e.g. a JNI version for Android and a Java fallback version for other platforms.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public abstract class Binarizer {
+
+  private final LuminanceSource source;
+
+  protected Binarizer(LuminanceSource source) {
+    this.source = source;
+  }
+
+  public final LuminanceSource getLuminanceSource() {
+    return source;
+  }
+
+  /**
+   * Converts one row of luminance data to 1 bit data. May actually do the conversion, or return
+   * cached data. Callers should assume this method is expensive and call it as seldom as possible.
+   * This method is intended for decoding 1D barcodes and may choose to apply sharpening.
+   * For callers which only examine one row of pixels at a time, the same BitArray should be reused
+   * and passed in with each call for performance. However it is legal to keep more than one row
+   * at a time if needed.
+   *
+   * @param y The row to fetch, 0 <= y < bitmap height.
+   * @param row An optional preallocated array. If null or too small, it will be ignored.
+   *            If used, the Binarizer will call BitArray.clear(). Always use the returned object.
+   * @return The array of bits for this row (true means black).
+   */
+  public abstract BitArray getBlackRow(int y, BitArray row) throws NotFoundException;
+
+  /**
+   * Converts a 2D array of luminance data to 1 bit data. As above, assume this method is expensive
+   * and do not call it repeatedly. This method is intended for decoding 2D barcodes and may or
+   * may not apply sharpening. Therefore, a row from this matrix may not be identical to one
+   * fetched using getBlackRow(), so don't mix and match between them.
+   *
+   * @return The 2D array of bits for the image (true means black).
+   */
+  public abstract BitMatrix getBlackMatrix() throws NotFoundException;
+
+  /**
+   * Creates a new object with the same type as this Binarizer implementation, but with pristine
+   * state. This is needed because Binarizer implementations may be stateful, e.g. keeping a cache
+   * of 1 bit data. See Effective Java for why we can't use Java's clone() method.
+   *
+   * @param source The LuminanceSource this Binarizer will operate on.
+   * @return A new concrete Binarizer implementation object.
+   */
+  public abstract Binarizer createBinarizer(LuminanceSource source);
+
+  public final int getWidth() {
+    return source.getWidth();
+  }
+
+  public final int getHeight() {
+    return source.getHeight();
+  }
+
+}
diff --git a/src/com/google/zxing/BinaryBitmap.java b/src/com/google/zxing/BinaryBitmap.java
new file mode 100644 (file)
index 0000000..c75eb04
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2009 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;
+
+import com.google.zxing.common.BitArray;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * This class is the core bitmap class used by ZXing to represent 1 bit data. Reader objects
+ * accept a BinaryBitmap and attempt to decode it.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class BinaryBitmap {
+
+  private final Binarizer binarizer;
+  private BitMatrix matrix;
+
+  public BinaryBitmap(Binarizer binarizer) {
+    if (binarizer == null) {
+      throw new IllegalArgumentException("Binarizer must be non-null.");
+    }
+    this.binarizer = binarizer;
+  }
+
+  /**
+   * @return The width of the bitmap.
+   */
+  public int getWidth() {
+    return binarizer.getWidth();
+  }
+
+  /**
+   * @return The height of the bitmap.
+   */
+  public int getHeight() {
+    return binarizer.getHeight();
+  }
+
+  /**
+   * Converts one row of luminance data to 1 bit data. May actually do the conversion, or return
+   * cached data. Callers should assume this method is expensive and call it as seldom as possible.
+   * This method is intended for decoding 1D barcodes and may choose to apply sharpening.
+   *
+   * @param y The row to fetch, 0 <= y < bitmap height.
+   * @param row An optional preallocated array. If null or too small, it will be ignored.
+   *            If used, the Binarizer will call BitArray.clear(). Always use the returned object.
+   * @return The array of bits for this row (true means black).
+   */
+  public BitArray getBlackRow(int y, BitArray row) throws NotFoundException {
+    return binarizer.getBlackRow(y, row);
+  }
+
+  /**
+   * Converts a 2D array of luminance data to 1 bit. As above, assume this method is expensive
+   * and do not call it repeatedly. This method is intended for decoding 2D barcodes and may or
+   * may not apply sharpening. Therefore, a row from this matrix may not be identical to one
+   * fetched using getBlackRow(), so don't mix and match between them.
+   *
+   * @return The 2D array of bits for the image (true means black).
+   */
+  public BitMatrix getBlackMatrix() throws NotFoundException {
+    // The matrix is created on demand the first time it is requested, then cached. There are two
+    // reasons for this:
+    // 1. This work will never be done if the caller only installs 1D Reader objects, or if a
+    //    1D Reader finds a barcode before the 2D Readers run.
+    // 2. This work will only be done once even if the caller installs multiple 2D Readers.
+    if (matrix == null) {
+      matrix = binarizer.getBlackMatrix();
+    }
+    return matrix;
+  }
+
+  /**
+   * @return Whether this bitmap can be cropped.
+   */
+  public boolean isCropSupported() {
+    return binarizer.getLuminanceSource().isCropSupported();
+  }
+
+  /**
+   * Returns a new object with cropped image data. Implementations may keep a reference to the
+   * original data rather than a copy. Only callable if isCropSupported() is true.
+   *
+   * @param left The left coordinate, 0 <= left < getWidth().
+   * @param top The top coordinate, 0 <= top <= getHeight().
+   * @param width The width of the rectangle to crop.
+   * @param height The height of the rectangle to crop.
+   * @return A cropped version of this object.
+   */
+  public BinaryBitmap crop(int left, int top, int width, int height) {
+    LuminanceSource newSource = binarizer.getLuminanceSource().crop(left, top, width, height);
+    return new BinaryBitmap(binarizer.createBinarizer(newSource));
+  }
+
+  /**
+   * @return Whether this bitmap supports counter-clockwise rotation.
+   */
+  public boolean isRotateSupported() {
+    return binarizer.getLuminanceSource().isRotateSupported();
+  }
+
+  /**
+   * Returns a new object with rotated image data by 90 degrees counterclockwise.
+   * Only callable if {@link #isRotateSupported()} is true.
+   *
+   * @return A rotated version of this object.
+   */
+  public BinaryBitmap rotateCounterClockwise() {
+    LuminanceSource newSource = binarizer.getLuminanceSource().rotateCounterClockwise();
+    return new BinaryBitmap(binarizer.createBinarizer(newSource));
+  }
+
+  /**
+   * Returns a new object with rotated image data by 45 degrees counterclockwise.
+   * Only callable if {@link #isRotateSupported()} is true.
+   *
+   * @return A rotated version of this object.
+   */
+  public BinaryBitmap rotateCounterClockwise45() {
+    LuminanceSource newSource = binarizer.getLuminanceSource().rotateCounterClockwise45();
+    return new BinaryBitmap(binarizer.createBinarizer(newSource));
+  }
+
+}
diff --git a/src/com/google/zxing/ChecksumException.java b/src/com/google/zxing/ChecksumException.java
new file mode 100644 (file)
index 0000000..dedb4be
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * Thrown when a barcode was successfully detected and decoded, but
+ * was not returned because its checksum feature failed.
+ *
+ * @author Sean Owen
+ */
+public final class ChecksumException extends ReaderException {
+
+  private static final ChecksumException instance = new ChecksumException();
+
+  private ChecksumException() {
+    // do nothing
+  }
+
+  public static ChecksumException getChecksumInstance() {
+    return instance;
+  }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/DecodeHintType.java b/src/com/google/zxing/DecodeHintType.java
new file mode 100644 (file)
index 0000000..54a1c80
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2007 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;
+
+import java.util.List;
+
+/**
+ * Encapsulates a type of hint that a caller may pass to a barcode reader to help it
+ * more quickly or accurately decode it. It is up to implementations to decide what,
+ * if anything, to do with the information that is supplied.
+ *
+ * @author Sean Owen
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @see Reader#decode(BinaryBitmap,java.util.Map)
+ */
+public enum DecodeHintType {
+
+  /**
+   * Unspecified, application-specific hint. Maps to an unspecified {@link Object}.
+   */
+ OTHER(Object.class),
+
+  /**
+   * Image is a pure monochrome image of a barcode. Doesn't matter what it maps to;
+   * use {@link Boolean#TRUE}.
+   */
+  PURE_BARCODE(Void.class),
+
+  /**
+   * Image is known to be of one of a few possible formats.
+   * Maps to a {@link List} of {@link BarcodeFormat}s.
+   */
+  POSSIBLE_FORMATS(List.class),
+
+  /**
+   * Spend more time to try to find a barcode; optimize for accuracy, not speed.
+   * Doesn't matter what it maps to; use {@link Boolean#TRUE}.
+   */
+  TRY_HARDER(Void.class),
+
+  /**
+   * Specifies what character encoding to use when decoding, where applicable (type String)
+   */
+  CHARACTER_SET(String.class),
+
+  /**
+   * Allowed lengths of encoded data -- reject anything else. Maps to an {@code int[]}.
+   */
+  ALLOWED_LENGTHS(int[].class),
+
+  /**
+   * Assume Code 39 codes employ a check digit. Doesn't matter what it maps to;
+   * use {@link Boolean#TRUE}.
+   */
+  ASSUME_CODE_39_CHECK_DIGIT(Void.class),
+
+  /**
+   * Assume the barcode is being processed as a GS1 barcode, and modify behavior as needed.
+   * For example this affects FNC1 handling for Code 128 (aka GS1-128). Doesn't matter what it maps to;
+   * use {@link Boolean#TRUE}.
+   */
+  ASSUME_GS1(Void.class),
+
+  /**
+   * If true, return the start and end digits in a Codabar barcode instead of stripping them. They
+   * are alpha, whereas the rest are numeric. By default, they are stripped, but this causes them
+   * to not be. Doesn't matter what it maps to; use {@link Boolean#TRUE}.
+   */
+  RETURN_CODABAR_START_END(Void.class),
+
+  /**
+   * The caller needs to be notified via callback when a possible {@link ResultPoint}
+   * is found. Maps to a {@link ResultPointCallback}.
+   */
+  NEED_RESULT_POINT_CALLBACK(ResultPointCallback.class),
+
+  // End of enumeration values.
+  ;
+
+  /**
+   * Data type the hint is expecting.
+   * Among the possible values the {@link Void} stands out as being used for
+   * hints that do not expect a value to be supplied (flag hints). Such hints
+   * will possibly have their value ignored, or replaced by a
+   * {@link Boolean#TRUE}. Hint suppliers should probably use
+   * {@link Boolean#TRUE} as directed by the actual hint documentation.
+   */
+  private final Class<?> valueType;
+
+  DecodeHintType(Class<?> valueType) {
+    this.valueType = valueType;
+  }
+  
+  public Class<?> getValueType() {
+    return valueType;
+  }
+
+}
diff --git a/src/com/google/zxing/FormatException.java b/src/com/google/zxing/FormatException.java
new file mode 100644 (file)
index 0000000..6967e93
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * Thrown when a barcode was successfully detected, but some aspect of
+ * the content did not conform to the barcode's format rules. This could have
+ * been due to a mis-detection.
+ *
+ * @author Sean Owen
+ */
+public final class FormatException extends ReaderException {
+
+  private static final FormatException instance = new FormatException();
+
+  private FormatException() {
+    // do nothing
+  }
+
+  public static FormatException getFormatInstance() {
+    return instance;
+  }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/InvertedLuminanceSource.java b/src/com/google/zxing/InvertedLuminanceSource.java
new file mode 100644 (file)
index 0000000..edaa119
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2013 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;
+
+/**
+ * A wrapper implementation of {@link LuminanceSource} which inverts the luminances it returns -- black becomes
+ * white and vice versa, and each value becomes (255-value).
+ * 
+ * @author Sean Owen
+ */
+public final class InvertedLuminanceSource extends LuminanceSource {
+
+  private final LuminanceSource delegate;
+
+  public InvertedLuminanceSource(LuminanceSource delegate) {
+    super(delegate.getWidth(), delegate.getHeight());
+    this.delegate = delegate;
+  }
+
+  @Override
+  public byte[] getRow(int y, byte[] row) {
+    row = delegate.getRow(y, row);
+    int width = getWidth();
+    for (int i = 0; i < width; i++) {
+      row[i] = (byte) (255 - (row[i] & 0xFF));
+    }
+    return row;
+  }
+
+  @Override
+  public byte[] getMatrix() {
+    byte[] matrix = delegate.getMatrix();
+    int length = getWidth() * getHeight();
+    byte[] invertedMatrix = new byte[length];
+    for (int i = 0; i < length; i++) {
+      invertedMatrix[i] = (byte) (255 - (matrix[i] & 0xFF));
+    }
+    return invertedMatrix;
+  }
+  
+  @Override
+  public boolean isCropSupported() {
+    return delegate.isCropSupported();
+  }
+
+  @Override
+  public LuminanceSource crop(int left, int top, int width, int height) {
+    return new InvertedLuminanceSource(delegate.crop(left, top, width, height));
+  }
+
+  @Override
+  public boolean isRotateSupported() {
+    return delegate.isRotateSupported();
+  }
+
+  /**
+   * @return original delegate {@link LuminanceSource} since invert undoes itself
+   */
+  @Override
+  public LuminanceSource invert() {
+    return delegate;
+  }
+
+  @Override
+  public LuminanceSource rotateCounterClockwise() {
+    return new InvertedLuminanceSource(delegate.rotateCounterClockwise());
+  }
+
+  @Override
+  public LuminanceSource rotateCounterClockwise45() {
+    return new InvertedLuminanceSource(delegate.rotateCounterClockwise45());
+  }
+
+}
diff --git a/src/com/google/zxing/LuminanceSource.java b/src/com/google/zxing/LuminanceSource.java
new file mode 100644 (file)
index 0000000..1c46a55
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2009 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;
+
+/**
+ * The purpose of this class hierarchy is to abstract different bitmap implementations across
+ * platforms into a standard interface for requesting greyscale luminance values. The interface
+ * only provides immutable methods; therefore crop and rotation create copies. This is to ensure
+ * that one Reader does not modify the original luminance source and leave it in an unknown state
+ * for other Readers in the chain.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public abstract class LuminanceSource {
+
+  private final int width;
+  private final int height;
+
+  protected LuminanceSource(int width, int height) {
+    this.width = width;
+    this.height = height;
+  }
+
+  /**
+   * Fetches one row of luminance data from the underlying platform's bitmap. Values range from
+   * 0 (black) to 255 (white). Because Java does not have an unsigned byte type, callers will have
+   * to bitwise and with 0xff for each value. It is preferable for implementations of this method
+   * to only fetch this row rather than the whole image, since no 2D Readers may be installed and
+   * getMatrix() may never be called.
+   *
+   * @param y The row to fetch, 0 <= y < getHeight().
+   * @param row An optional preallocated array. If null or too small, it will be ignored.
+   *            Always use the returned object, and ignore the .length of the array.
+   * @return An array containing the luminance data.
+   */
+  public abstract byte[] getRow(int y, byte[] row);
+
+  /**
+   * Fetches luminance data for the underlying bitmap. Values should be fetched using:
+   * int luminance = array[y * width + x] & 0xff;
+   *
+   * @return A row-major 2D array of luminance values. Do not use result.length as it may be
+   *         larger than width * height bytes on some platforms. Do not modify the contents
+   *         of the result.
+   */
+  public abstract byte[] getMatrix();
+
+  /**
+   * @return The width of the bitmap.
+   */
+  public final int getWidth() {
+    return width;
+  }
+
+  /**
+   * @return The height of the bitmap.
+   */
+  public final int getHeight() {
+    return height;
+  }
+
+  /**
+   * @return Whether this subclass supports cropping.
+   */
+  public boolean isCropSupported() {
+    return false;
+  }
+
+  /**
+   * Returns a new object with cropped image data. Implementations may keep a reference to the
+   * original data rather than a copy. Only callable if isCropSupported() is true.
+   *
+   * @param left The left coordinate, 0 <= left < getWidth().
+   * @param top The top coordinate, 0 <= top <= getHeight().
+   * @param width The width of the rectangle to crop.
+   * @param height The height of the rectangle to crop.
+   * @return A cropped version of this object.
+   */
+  public LuminanceSource crop(int left, int top, int width, int height) {
+    throw new UnsupportedOperationException("This luminance source does not support cropping.");
+  }
+
+  /**
+   * @return Whether this subclass supports counter-clockwise rotation.
+   */
+  public boolean isRotateSupported() {
+    return false;
+  }
+
+  /**
+   * @return a wrapper of this {@code LuminanceSource} which inverts the luminances it returns -- black becomes
+   *  white and vice versa, and each value becomes (255-value).
+   */
+  public LuminanceSource invert() {
+    return new InvertedLuminanceSource(this);
+  }
+
+  /**
+   * Returns a new object with rotated image data by 90 degrees counterclockwise.
+   * Only callable if {@link #isRotateSupported()} is true.
+   *
+   * @return A rotated version of this object.
+   */
+  public LuminanceSource rotateCounterClockwise() {
+    throw new UnsupportedOperationException("This luminance source does not support rotation by 90 degrees.");
+  }
+
+  /**
+   * Returns a new object with rotated image data by 45 degrees counterclockwise.
+   * Only callable if {@link #isRotateSupported()} is true.
+   *
+   * @return A rotated version of this object.
+   */
+  public LuminanceSource rotateCounterClockwise45() {
+    throw new UnsupportedOperationException("This luminance source does not support rotation by 45 degrees.");
+  }
+
+  @Override
+  public final String toString() {
+    byte[] row = new byte[width];
+    StringBuilder result = new StringBuilder(height * (width + 1));
+    for (int y = 0; y < height; y++) {
+      row = getRow(y, row);
+      for (int x = 0; x < width; x++) {
+        int luminance = row[x] & 0xFF;
+        char c;
+        if (luminance < 0x40) {
+          c = '#';
+        } else if (luminance < 0x80) {
+          c = '+';
+        } else if (luminance < 0xC0) {
+          c = '.';
+        } else {
+          c = ' ';
+        }
+        result.append(c);
+      }
+      result.append('\n');
+    }
+    return result.toString();
+  }
+
+}
diff --git a/src/com/google/zxing/NotFoundException.java b/src/com/google/zxing/NotFoundException.java
new file mode 100644 (file)
index 0000000..dedab8d
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * Thrown when a barcode was not found in the image. It might have been
+ * partially detected but could not be confirmed.
+ *
+ * @author Sean Owen
+ */
+public final class NotFoundException extends ReaderException {
+
+  private static final NotFoundException instance = new NotFoundException();
+
+  private NotFoundException() {
+    // do nothing
+  }
+
+  public static NotFoundException getNotFoundInstance() {
+    return instance;
+  }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/PlanarYUVLuminanceSource.java b/src/com/google/zxing/PlanarYUVLuminanceSource.java
new file mode 100644 (file)
index 0000000..2049e59
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2009 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;
+
+/**
+ * This object extends LuminanceSource around an array of YUV data returned from the camera driver,
+ * with the option to crop to a rectangle within the full data. This can be used to exclude
+ * superfluous pixels around the perimeter and speed up decoding.
+ *
+ * It works for any pixel format where the Y channel is planar and appears first, including
+ * YCbCr_420_SP and YCbCr_422_SP.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class PlanarYUVLuminanceSource extends LuminanceSource {
+
+  private static final int THUMBNAIL_SCALE_FACTOR = 2;
+  
+  private final byte[] yuvData;
+  private final int dataWidth;
+  private final int dataHeight;
+  private final int left;
+  private final int top;
+
+  public PlanarYUVLuminanceSource(byte[] yuvData,
+                                  int dataWidth,
+                                  int dataHeight,
+                                  int left,
+                                  int top,
+                                  int width,
+                                  int height,
+                                  boolean reverseHorizontal) {
+    super(width, height);
+
+    if (left + width > dataWidth || top + height > dataHeight) {
+      throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
+    }
+
+    this.yuvData = yuvData;
+    this.dataWidth = dataWidth;
+    this.dataHeight = dataHeight;
+    this.left = left;
+    this.top = top;
+    if (reverseHorizontal) {
+      reverseHorizontal(width, height);
+    }
+  }
+
+  @Override
+  public byte[] getRow(int y, byte[] row) {
+    if (y < 0 || y >= getHeight()) {
+      throw new IllegalArgumentException("Requested row is outside the image: " + y);
+    }
+    int width = getWidth();
+    if (row == null || row.length < width) {
+      row = new byte[width];
+    }
+    int offset = (y + top) * dataWidth + left;
+    System.arraycopy(yuvData, offset, row, 0, width);
+    return row;
+  }
+
+  @Override
+  public byte[] getMatrix() {
+    int width = getWidth();
+    int height = getHeight();
+
+    // If the caller asks for the entire underlying image, save the copy and give them the
+    // original data. The docs specifically warn that result.length must be ignored.
+    if (width == dataWidth && height == dataHeight) {
+      return yuvData;
+    }
+
+    int area = width * height;
+    byte[] matrix = new byte[area];
+    int inputOffset = top * dataWidth + left;
+
+    // If the width matches the full width of the underlying data, perform a single copy.
+    if (width == dataWidth) {
+      System.arraycopy(yuvData, inputOffset, matrix, 0, area);
+      return matrix;
+    }
+
+    // Otherwise copy one cropped row at a time.
+    byte[] yuv = yuvData;
+    for (int y = 0; y < height; y++) {
+      int outputOffset = y * width;
+      System.arraycopy(yuv, inputOffset, matrix, outputOffset, width);
+      inputOffset += dataWidth;
+    }
+    return matrix;
+  }
+
+  @Override
+  public boolean isCropSupported() {
+    return true;
+  }
+
+  @Override
+  public LuminanceSource crop(int left, int top, int width, int height) {
+    return new PlanarYUVLuminanceSource(yuvData,
+                                        dataWidth,
+                                        dataHeight,
+                                        this.left + left,
+                                        this.top + top,
+                                        width,
+                                        height,
+                                        false);
+  }
+
+  public int[] renderThumbnail() {
+    int width = getWidth() / THUMBNAIL_SCALE_FACTOR;
+    int height = getHeight() / THUMBNAIL_SCALE_FACTOR;
+    int[] pixels = new int[width * height];
+    byte[] yuv = yuvData;
+    int inputOffset = top * dataWidth + left;
+
+    for (int y = 0; y < height; y++) {
+      int outputOffset = y * width;
+      for (int x = 0; x < width; x++) {
+        int grey = yuv[inputOffset + x * THUMBNAIL_SCALE_FACTOR] & 0xff;
+        pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101);
+      }
+      inputOffset += dataWidth * THUMBNAIL_SCALE_FACTOR;
+    }
+    return pixels;
+  }
+  
+  /**
+   * @return width of image from {@link #renderThumbnail()}
+   */
+  public int getThumbnailWidth() {
+    return getWidth() / THUMBNAIL_SCALE_FACTOR;
+  }
+  
+  /**
+   * @return height of image from {@link #renderThumbnail()}
+   */  
+  public int getThumbnailHeight() {
+    return getHeight() / THUMBNAIL_SCALE_FACTOR;
+  }
+
+  private void reverseHorizontal(int width, int height) {
+    byte[] yuvData = this.yuvData;
+    for (int y = 0, rowStart = top * dataWidth + left; y < height; y++, rowStart += dataWidth) {
+      int middle = rowStart + width / 2;
+      for (int x1 = rowStart, x2 = rowStart + width - 1; x1 < middle; x1++, x2--) {
+        byte temp = yuvData[x1];
+        yuvData[x1] = yuvData[x2];
+        yuvData[x2] = temp;
+      }
+    }
+  }
+
+}
diff --git a/src/com/google/zxing/Reader.java b/src/com/google/zxing/Reader.java
new file mode 100644 (file)
index 0000000..b47702a
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2007 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;
+
+import java.util.Map;
+
+/**
+ * Implementations of this interface can decode an image of a barcode in some format into
+ * the String it encodes. For example, {@link com.google.zxing.qrcode.QRCodeReader} can
+ * decode a QR code. The decoder may optionally receive hints from the caller which may help
+ * it decode more quickly or accurately.
+ *
+ * See {@link com.google.zxing.MultiFormatReader}, which attempts to determine what barcode
+ * format is present within the image as well, and then decodes it accordingly.
+ *
+ * @author Sean Owen
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public interface Reader {
+
+  /**
+   * Locates and decodes a barcode in some format within an image.
+   *
+   * @param image image of barcode to decode
+   * @return String which the barcode encodes
+   * @throws NotFoundException if the barcode cannot be located or decoded for any reason
+   */
+  Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException;
+
+  /**
+   * Locates and decodes a barcode in some format within an image. This method also accepts
+   * hints, each possibly associated to some data, which may help the implementation decode.
+   *
+   * @param image image of barcode to decode
+   * @param hints passed as a {@link java.util.Map} from {@link com.google.zxing.DecodeHintType}
+   * to arbitrary data. The
+   * meaning of the data depends upon the hint type. The implementation may or may not do
+   * anything with these hints.
+   * @return String which the barcode encodes
+   * @throws NotFoundException if the barcode cannot be located or decoded for any reason
+   */
+  Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
+      throws NotFoundException, ChecksumException, FormatException;
+
+  /**
+   * Resets any internal state the implementation has after a decode, to prepare it
+   * for reuse.
+   */
+  void reset();
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/ReaderException.java b/src/com/google/zxing/ReaderException.java
new file mode 100644 (file)
index 0000000..9bb0dd4
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * The general exception class throw when something goes wrong during decoding of a barcode.
+ * This includes, but is not limited to, failing checksums / error correction algorithms, being
+ * unable to locate finder timing patterns, and so on.
+ *
+ * @author Sean Owen
+ */
+public abstract class ReaderException extends Exception {
+
+  ReaderException() {
+    // do nothing
+  }
+
+  // Prevent stack traces from being taken
+  // srowen says: huh, my IDE is saying this is not an override. native methods can't be overridden?
+  // This, at least, does not hurt. Because we use a singleton pattern here, it doesn't matter anyhow.
+  @Override
+  public final Throwable fillInStackTrace() {
+    return null;
+  }
+
+}
diff --git a/src/com/google/zxing/Result.java b/src/com/google/zxing/Result.java
new file mode 100644 (file)
index 0000000..e9afeec
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2007 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;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * <p>Encapsulates the result of decoding a barcode within an image.</p>
+ *
+ * @author Sean Owen
+ */
+public final class Result {
+
+  private final String text;
+  private final byte[] rawBytes;
+  private ResultPoint[] resultPoints;
+  private final BarcodeFormat format;
+  private Map<ResultMetadataType,Object> resultMetadata;
+  private final long timestamp;
+
+  public Result(String text,
+                byte[] rawBytes,
+                ResultPoint[] resultPoints,
+                BarcodeFormat format) {
+    this(text, rawBytes, resultPoints, format, System.currentTimeMillis());
+  }
+
+  public Result(String text,
+                byte[] rawBytes,
+                ResultPoint[] resultPoints,
+                BarcodeFormat format,
+                long timestamp) {
+    this.text = text;
+    this.rawBytes = rawBytes;
+    this.resultPoints = resultPoints;
+    this.format = format;
+    this.resultMetadata = null;
+    this.timestamp = timestamp;
+  }
+
+  /**
+   * @return raw text encoded by the barcode
+   */
+  public String getText() {
+    return text;
+  }
+
+  /**
+   * @return raw bytes encoded by the barcode, if applicable, otherwise {@code null}
+   */
+  public byte[] getRawBytes() {
+    return rawBytes;
+  }
+
+  /**
+   * @return points related to the barcode in the image. These are typically points
+   *         identifying finder patterns or the corners of the barcode. The exact meaning is
+   *         specific to the type of barcode that was decoded.
+   */
+  public ResultPoint[] getResultPoints() {
+    return resultPoints;
+  }
+
+  /**
+   * @return {@link BarcodeFormat} representing the format of the barcode that was decoded
+   */
+  public BarcodeFormat getBarcodeFormat() {
+    return format;
+  }
+
+  /**
+   * @return {@link Map} mapping {@link ResultMetadataType} keys to values. May be
+   *   {@code null}. This contains optional metadata about what was detected about the barcode,
+   *   like orientation.
+   */
+  public Map<ResultMetadataType,Object> getResultMetadata() {
+    return resultMetadata;
+  }
+
+  public void putMetadata(ResultMetadataType type, Object value) {
+    if (resultMetadata == null) {
+      resultMetadata = new EnumMap<ResultMetadataType, Object>(ResultMetadataType.class);
+    }
+    resultMetadata.put(type, value);
+  }
+
+  public void putAllMetadata(Map<ResultMetadataType,Object> metadata) {
+    if (metadata != null) {
+      if (resultMetadata == null) {
+        resultMetadata = metadata;
+      } else {
+        resultMetadata.putAll(metadata);
+      }
+    }
+  }
+
+  public void addResultPoints(ResultPoint[] newPoints) {
+    ResultPoint[] oldPoints = resultPoints;
+    if (oldPoints == null) {
+      resultPoints = newPoints;
+    } else if (newPoints != null && newPoints.length > 0) {
+      ResultPoint[] allPoints = new ResultPoint[oldPoints.length + newPoints.length];
+      System.arraycopy(oldPoints, 0, allPoints, 0, oldPoints.length);
+      System.arraycopy(newPoints, 0, allPoints, oldPoints.length, newPoints.length);
+      resultPoints = allPoints;
+    }
+  }
+
+  public long getTimestamp() {
+    return timestamp;
+  }
+
+  @Override
+  public String toString() {
+    return text;
+  }
+
+}
diff --git a/src/com/google/zxing/ResultMetadataType.java b/src/com/google/zxing/ResultMetadataType.java
new file mode 100644 (file)
index 0000000..cb6fe9a
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright 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;
+
+/**
+ * Represents some type of metadata about the result of the decoding that the decoder
+ * wishes to communicate back to the caller.
+ *
+ * @author Sean Owen
+ */
+public enum ResultMetadataType {
+
+  /**
+   * Unspecified, application-specific metadata. Maps to an unspecified {@link Object}.
+   */
+  OTHER,
+
+  /**
+   * Denotes the likely approximate orientation of the barcode in the image. This value
+   * is given as degrees rotated clockwise from the normal, upright orientation.
+   * For example a 1D barcode which was found by reading top-to-bottom would be
+   * said to have orientation "90". This key maps to an {@link Integer} whose
+   * value is in the range [0,360).
+   */
+  ORIENTATION,
+
+  /**
+   * <p>2D barcode formats typically encode text, but allow for a sort of 'byte mode'
+   * which is sometimes used to encode binary data. While {@link Result} makes available
+   * the complete raw bytes in the barcode for these formats, it does not offer the bytes
+   * from the byte segments alone.</p>
+   *
+   * <p>This maps to a {@link java.util.List} of byte arrays corresponding to the
+   * raw bytes in the byte segments in the barcode, in order.</p>
+   */
+  BYTE_SEGMENTS,
+
+  /**
+   * Error correction level used, if applicable. The value type depends on the
+   * format, but is typically a String.
+   */
+  ERROR_CORRECTION_LEVEL,
+
+  /**
+   * For some periodicals, indicates the issue number as an {@link Integer}.
+   */
+  ISSUE_NUMBER,
+
+  /**
+   * For some products, indicates the suggested retail price in the barcode as a
+   * formatted {@link String}.
+   */
+  SUGGESTED_PRICE ,
+
+  /**
+   * For some products, the possible country of manufacture as a {@link String} denoting the
+   * ISO country code. Some map to multiple possible countries, like "US/CA".
+   */
+  POSSIBLE_COUNTRY,
+
+  /**
+   * For some products, the extension text
+   */
+  UPC_EAN_EXTENSION,
+
+  /**
+   * PDF417-specific metadata
+   */
+  PDF417_EXTRA_METADATA,
+
+}
diff --git a/src/com/google/zxing/ResultPoint.java b/src/com/google/zxing/ResultPoint.java
new file mode 100644 (file)
index 0000000..a69d9c0
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2007 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;
+
+import com.google.zxing.common.detector.MathUtils;
+
+/**
+ * <p>Encapsulates a point of interest in an image containing a barcode. Typically, this
+ * would be the location of a finder pattern or the corner of the barcode, for example.</p>
+ *
+ * @author Sean Owen
+ */
+public class ResultPoint {
+
+  private final float x;
+  private final float y;
+
+  public ResultPoint(float x, float y) {
+    this.x = x;
+    this.y = y;
+  }
+
+  public final float getX() {
+    return x;
+  }
+
+  public final float getY() {
+    return y;
+  }
+
+  @Override
+  public final boolean equals(Object other) {
+    if (other instanceof ResultPoint) {
+      ResultPoint otherPoint = (ResultPoint) other;
+      return x == otherPoint.x && y == otherPoint.y;
+    }
+    return false;
+  }
+
+  @Override
+  public final int hashCode() {
+    return 31 * Float.floatToIntBits(x) + Float.floatToIntBits(y);
+  }
+
+  @Override
+  public final String toString() {
+    StringBuilder result = new StringBuilder(25);
+    result.append('(');
+    result.append(x);
+    result.append(',');
+    result.append(y);
+    result.append(')');
+    return result.toString();
+  }
+
+  /**
+   * <p>Orders an array of three ResultPoints in an order [A,B,C] such that AB < AC and
+   * BC < AC and the angle between BC and BA is less than 180 degrees.
+   */
+  public static void orderBestPatterns(ResultPoint[] patterns) {
+
+    // Find distances between pattern centers
+    float zeroOneDistance = distance(patterns[0], patterns[1]);
+    float oneTwoDistance = distance(patterns[1], patterns[2]);
+    float zeroTwoDistance = distance(patterns[0], patterns[2]);
+
+    ResultPoint pointA;
+    ResultPoint pointB;
+    ResultPoint pointC;
+    // Assume one closest to other two is B; A and C will just be guesses at first
+    if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance) {
+      pointB = patterns[0];
+      pointA = patterns[1];
+      pointC = patterns[2];
+    } else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance) {
+      pointB = patterns[1];
+      pointA = patterns[0];
+      pointC = patterns[2];
+    } else {
+      pointB = patterns[2];
+      pointA = patterns[0];
+      pointC = patterns[1];
+    }
+
+    // Use cross product to figure out whether A and C are correct or flipped.
+    // This asks whether BC x BA has a positive z component, which is the arrangement
+    // we want for A, B, C. If it's negative, then we've got it flipped around and
+    // should swap A and C.
+    if (crossProductZ(pointA, pointB, pointC) < 0.0f) {
+      ResultPoint temp = pointA;
+      pointA = pointC;
+      pointC = temp;
+    }
+
+    patterns[0] = pointA;
+    patterns[1] = pointB;
+    patterns[2] = pointC;
+  }
+
+
+  /**
+   * @return distance between two points
+   */
+  public static float distance(ResultPoint pattern1, ResultPoint pattern2) {
+    return MathUtils.distance(pattern1.x, pattern1.y, pattern2.x, pattern2.y);
+  }
+
+  /**
+   * Returns the z component of the cross product between vectors BC and BA.
+   */
+  private static float crossProductZ(ResultPoint pointA,
+                                     ResultPoint pointB,
+                                     ResultPoint pointC) {
+    float bX = pointB.x;
+    float bY = pointB.y;
+    return ((pointC.x - bX) * (pointA.y - bY)) - ((pointC.y - bY) * (pointA.x - bX));
+  }
+
+
+}
diff --git a/src/com/google/zxing/ResultPointCallback.java b/src/com/google/zxing/ResultPointCallback.java
new file mode 100644 (file)
index 0000000..0c85410
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2009 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;
+
+/**
+ * Callback which is invoked when a possible result point (significant
+ * point in the barcode image such as a corner) is found.
+ *
+ * @see DecodeHintType#NEED_RESULT_POINT_CALLBACK
+ */
+public interface ResultPointCallback {
+
+  void foundPossibleResultPoint(ResultPoint point);
+
+}
diff --git a/src/com/google/zxing/common/BitArray.java b/src/com/google/zxing/common/BitArray.java
new file mode 100644 (file)
index 0000000..26f652c
--- /dev/null
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2007 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.common;
+
+/**
+ * <p>A simple, fast array of bits, represented compactly by an array of ints internally.</p>
+ *
+ * @author Sean Owen
+ */
+public final class BitArray {
+
+  private int[] bits;
+  private int size;
+
+  public BitArray() {
+    this.size = 0;
+    this.bits = new int[1];
+  }
+
+  public BitArray(int size) {
+    this.size = size;
+    this.bits = makeArray(size);
+  }
+
+  // For testing only
+  BitArray(int[] bits, int size) {
+    this.bits = bits;
+    this.size = size;
+  }
+
+  public int getSize() {
+    return size;
+  }
+
+  public int getSizeInBytes() {
+    return (size + 7) / 8;
+  }
+
+  private void ensureCapacity(int size) {
+    if (size > bits.length * 32) {
+      int[] newBits = makeArray(size);
+      System.arraycopy(bits, 0, newBits, 0, bits.length);
+      this.bits = newBits;
+    }
+  }
+
+  /**
+   * @param i bit to get
+   * @return true iff bit i is set
+   */
+  public boolean get(int i) {
+    return (bits[i / 32] & (1 << (i & 0x1F))) != 0;
+  }
+
+  /**
+   * Sets bit i.
+   *
+   * @param i bit to set
+   */
+  public void set(int i) {
+    bits[i / 32] |= 1 << (i & 0x1F);
+  }
+
+  /**
+   * Flips bit i.
+   *
+   * @param i bit to set
+   */
+  public void flip(int i) {
+    bits[i / 32] ^= 1 << (i & 0x1F);
+  }
+
+  /**
+   * @param from first bit to check
+   * @return index of first bit that is set, starting from the given index, or size if none are set
+   *  at or beyond this given index
+   * @see #getNextUnset(int)
+   */
+  public int getNextSet(int from) {
+    if (from >= size) {
+      return size;
+    }
+    int bitsOffset = from / 32;
+    int currentBits = bits[bitsOffset];
+    // mask off lesser bits first
+    currentBits &= ~((1 << (from & 0x1F)) - 1);
+    while (currentBits == 0) {
+      if (++bitsOffset == bits.length) {
+        return size;
+      }
+      currentBits = bits[bitsOffset];
+    }
+    int result = (bitsOffset * 32) + Integer.numberOfTrailingZeros(currentBits);
+    return result > size ? size : result;
+  }
+
+  /**
+   * @see #getNextSet(int)
+   */
+  public int getNextUnset(int from) {
+    if (from >= size) {
+      return size;
+    }
+    int bitsOffset = from / 32;
+    int currentBits = ~bits[bitsOffset];
+    // mask off lesser bits first
+    currentBits &= ~((1 << (from & 0x1F)) - 1);
+    while (currentBits == 0) {
+      if (++bitsOffset == bits.length) {
+        return size;
+      }
+      currentBits = ~bits[bitsOffset];
+    }
+    int result = (bitsOffset * 32) + Integer.numberOfTrailingZeros(currentBits);
+    return result > size ? size : result;
+  }
+
+  /**
+   * Sets a block of 32 bits, starting at bit i.
+   *
+   * @param i first bit to set
+   * @param newBits the new value of the next 32 bits. Note again that the least-significant bit
+   * corresponds to bit i, the next-least-significant to i+1, and so on.
+   */
+  public void setBulk(int i, int newBits) {
+    bits[i / 32] = newBits;
+  }
+
+  /**
+   * Sets a range of bits.
+   *
+   * @param start start of range, inclusive.
+   * @param end end of range, exclusive
+   */
+  public void setRange(int start, int end) {
+    if (end < start) {
+      throw new IllegalArgumentException();
+    }
+    if (end == start) {
+      return;
+    }
+    end--; // will be easier to treat this as the last actually set bit -- inclusive
+    int firstInt = start / 32;
+    int lastInt = end / 32;
+    for (int i = firstInt; i <= lastInt; i++) {
+      int firstBit = i > firstInt ? 0 : start & 0x1F;
+      int lastBit = i < lastInt ? 31 : end & 0x1F;
+      int mask;
+      if (firstBit == 0 && lastBit == 31) {
+        mask = -1;
+      } else {
+        mask = 0;
+        for (int j = firstBit; j <= lastBit; j++) {
+          mask |= 1 << j;
+        }
+      }
+      bits[i] |= mask;
+    }
+  }
+
+  /**
+   * Clears all bits (sets to false).
+   */
+  public void clear() {
+    int max = bits.length;
+    for (int i = 0; i < max; i++) {
+      bits[i] = 0;
+    }
+  }
+
+  /**
+   * Efficient method to check if a range of bits is set, or not set.
+   *
+   * @param start start of range, inclusive.
+   * @param end end of range, exclusive
+   * @param value if true, checks that bits in range are set, otherwise checks that they are not set
+   * @return true iff all bits are set or not set in range, according to value argument
+   * @throws IllegalArgumentException if end is less than or equal to start
+   */
+  public boolean isRange(int start, int end, boolean value) {
+    if (end < start) {
+      throw new IllegalArgumentException();
+    }
+    if (end == start) {
+      return true; // empty range matches
+    }
+    end--; // will be easier to treat this as the last actually set bit -- inclusive
+    int firstInt = start / 32;
+    int lastInt = end / 32;
+    for (int i = firstInt; i <= lastInt; i++) {
+      int firstBit = i > firstInt ? 0 : start & 0x1F;
+      int lastBit = i < lastInt ? 31 : end & 0x1F;
+      int mask;
+      if (firstBit == 0 && lastBit == 31) {
+        mask = -1;
+      } else {
+        mask = 0;
+        for (int j = firstBit; j <= lastBit; j++) {
+          mask |= 1 << j;
+        }
+      }
+
+      // Return false if we're looking for 1s and the masked bits[i] isn't all 1s (that is,
+      // equals the mask, or we're looking for 0s and the masked portion is not all 0s
+      if ((bits[i] & mask) != (value ? mask : 0)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  public void appendBit(boolean bit) {
+    ensureCapacity(size + 1);
+    if (bit) {
+      bits[size / 32] |= 1 << (size & 0x1F);
+    }
+    size++;
+  }
+
+  /**
+   * Appends the least-significant bits, from value, in order from most-significant to
+   * least-significant. For example, appending 6 bits from 0x000001E will append the bits
+   * 0, 1, 1, 1, 1, 0 in that order.
+   */
+  public void appendBits(int value, int numBits) {
+    if (numBits < 0 || numBits > 32) {
+      throw new IllegalArgumentException("Num bits must be between 0 and 32");
+    }
+    ensureCapacity(size + numBits);
+    for (int numBitsLeft = numBits; numBitsLeft > 0; numBitsLeft--) {
+      appendBit(((value >> (numBitsLeft - 1)) & 0x01) == 1);
+    }
+  }
+
+  public void appendBitArray(BitArray other) {
+    int otherSize = other.size;
+    ensureCapacity(size + otherSize);
+    for (int i = 0; i < otherSize; i++) {
+      appendBit(other.get(i));
+    }
+  }
+
+  public void xor(BitArray other) {
+    if (bits.length != other.bits.length) {
+      throw new IllegalArgumentException("Sizes don't match");
+    }
+    for (int i = 0; i < bits.length; i++) {
+      // The last byte could be incomplete (i.e. not have 8 bits in
+      // it) but there is no problem since 0 XOR 0 == 0.
+      bits[i] ^= other.bits[i];
+    }
+  }
+
+  /**
+   *
+   * @param bitOffset first bit to start writing
+   * @param array array to write into. Bytes are written most-significant byte first. This is the opposite
+   *  of the internal representation, which is exposed by {@link #getBitArray()}
+   * @param offset position in array to start writing
+   * @param numBytes how many bytes to write
+   */
+  public void toBytes(int bitOffset, byte[] array, int offset, int numBytes) {
+    for (int i = 0; i < numBytes; i++) {
+      int theByte = 0;
+      for (int j = 0; j < 8; j++) {
+        if (get(bitOffset)) {
+          theByte |= 1 << (7 - j);
+        }
+        bitOffset++;
+      }
+      array[offset + i] = (byte) theByte;
+    }
+  }
+
+  /**
+   * @return underlying array of ints. The first element holds the first 32 bits, and the least
+   *         significant bit is bit 0.
+   */
+  public int[] getBitArray() {
+    return bits;
+  }
+
+  /**
+   * Reverses all bits in the array.
+   */
+  public void reverse() {
+    int[] newBits = new int[bits.length];
+    // reverse all int's first
+    int len = ((size-1) / 32);
+    int oldBitsLen = len + 1;
+    for (int i = 0; i < oldBitsLen; i++) {
+      long x = (long) bits[i];
+      x = ((x >>  1) & 0x55555555L) | ((x & 0x55555555L) <<  1);
+      x = ((x >>  2) & 0x33333333L) | ((x & 0x33333333L) <<  2);
+      x = ((x >>  4) & 0x0f0f0f0fL) | ((x & 0x0f0f0f0fL) <<  4);
+      x = ((x >>  8) & 0x00ff00ffL) | ((x & 0x00ff00ffL) <<  8);
+      x = ((x >> 16) & 0x0000ffffL) | ((x & 0x0000ffffL) << 16);
+      newBits[len - i] = (int) x;
+    }
+    // now correct the int's if the bit size isn't a multiple of 32
+    if (size != oldBitsLen * 32) {
+      int leftOffset = oldBitsLen * 32 - size;
+      int mask = 1;
+      for (int i = 0; i < 31 - leftOffset; i++) {
+        mask = (mask << 1) | 1;
+      }
+      int currentInt = (newBits[0] >> leftOffset) & mask;
+      for (int i = 1; i < oldBitsLen; i++) {
+        int nextInt = newBits[i];
+        currentInt |= nextInt << (32 - leftOffset);
+        newBits[i - 1] = currentInt;
+        currentInt = (nextInt >> leftOffset) & mask;
+      }
+      newBits[oldBitsLen - 1] = currentInt;
+    }
+    bits = newBits;
+  }
+
+  private static int[] makeArray(int size) {
+    return new int[(size + 31) / 32];
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder result = new StringBuilder(size);
+    for (int i = 0; i < size; i++) {
+      if ((i & 0x07) == 0) {
+        result.append(' ');
+      }
+      result.append(get(i) ? 'X' : '.');
+    }
+    return result.toString();
+  }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/BitMatrix.java b/src/com/google/zxing/common/BitMatrix.java
new file mode 100755 (executable)
index 0000000..31f46e0
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2007 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.common;
+
+/**
+ * <p>Represents a 2D matrix of bits. In function arguments below, and throughout the common
+ * module, x is the column position, and y is the row position. The ordering is always x, y.
+ * The origin is at the top-left.</p>
+ *
+ * <p>Internally the bits are represented in a 1-D array of 32-bit ints. However, each row begins
+ * with a new int. This is done intentionally so that we can copy out a row into a BitArray very
+ * efficiently.</p>
+ *
+ * <p>The ordering of bits is row-major. Within each int, the least significant bits are used first,
+ * meaning they represent lower x values. This is compatible with BitArray's implementation.</p>
+ *
+ * @author Sean Owen
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class BitMatrix {
+
+  private final int width;
+  private final int height;
+  private final int rowSize;
+  private final int[] bits;
+
+  // A helper to construct a square matrix.
+  public BitMatrix(int dimension) {
+    this(dimension, dimension);
+  }
+
+  public BitMatrix(int width, int height) {
+    if (width < 1 || height < 1) {
+      throw new IllegalArgumentException("Both dimensions must be greater than 0");
+    }
+    this.width = width;
+    this.height = height;
+    this.rowSize = (width + 31) >> 5;
+    bits = new int[rowSize * height];
+  }
+
+  /**
+   * <p>Gets the requested bit, where true means black.</p>
+   *
+   * @param x The horizontal component (i.e. which column)
+   * @param y The vertical component (i.e. which row)
+   * @return value of given bit in matrix
+   */
+  public boolean get(int x, int y) {
+    int offset = y * rowSize + (x >> 5);
+    return ((bits[offset] >>> (x & 0x1f)) & 1) != 0;
+  }
+
+  /**
+   * <p>Sets the given bit to true.</p>
+   *
+   * @param x The horizontal component (i.e. which column)
+   * @param y The vertical component (i.e. which row)
+   */
+  public void set(int x, int y) {
+    int offset = y * rowSize + (x >> 5);
+    bits[offset] |= 1 << (x & 0x1f);
+  }
+
+  /**
+   * <p>Flips the given bit.</p>
+   *
+   * @param x The horizontal component (i.e. which column)
+   * @param y The vertical component (i.e. which row)
+   */
+  public void flip(int x, int y) {
+    int offset = y * rowSize + (x >> 5);
+    bits[offset] ^= 1 << (x & 0x1f);
+  }
+
+  /**
+   * Clears all bits (sets to false).
+   */
+  public void clear() {
+    int max = bits.length;
+    for (int i = 0; i < max; i++) {
+      bits[i] = 0;
+    }
+  }
+
+  /**
+   * <p>Sets a square region of the bit matrix to true.</p>
+   *
+   * @param left The horizontal position to begin at (inclusive)
+   * @param top The vertical position to begin at (inclusive)
+   * @param width The width of the region
+   * @param height The height of the region
+   */
+  public void setRegion(int left, int top, int width, int height) {
+    if (top < 0 || left < 0) {
+      throw new IllegalArgumentException("Left and top must be nonnegative");
+    }
+    if (height < 1 || width < 1) {
+      throw new IllegalArgumentException("Height and width must be at least 1");
+    }
+    int right = left + width;
+    int bottom = top + height;
+    if (bottom > this.height || right > this.width) {
+      throw new IllegalArgumentException("The region must fit inside the matrix");
+    }
+    for (int y = top; y < bottom; y++) {
+      int offset = y * rowSize;
+      for (int x = left; x < right; x++) {
+        bits[offset + (x >> 5)] |= 1 << (x & 0x1f);
+      }
+    }
+  }
+
+  /**
+   * A fast method to retrieve one row of data from the matrix as a BitArray.
+   *
+   * @param y The row to retrieve
+   * @param row An optional caller-allocated BitArray, will be allocated if null or too small
+   * @return The resulting BitArray - this reference should always be used even when passing
+   *         your own row
+   */
+  public BitArray getRow(int y, BitArray row) {
+    if (row == null || row.getSize() < width) {
+      row = new BitArray(width);
+    }
+    int offset = y * rowSize;
+    for (int x = 0; x < rowSize; x++) {
+      row.setBulk(x << 5, bits[offset + x]);
+    }
+    return row;
+  }
+
+  /**
+   * @param y row to set
+   * @param row {@link BitArray} to copy from
+   */
+  public void setRow(int y, BitArray row) {
+    System.arraycopy(row.getBitArray(), 0, bits, y * rowSize, rowSize);
+  }
+
+  /**
+   * This is useful in detecting the enclosing rectangle of a 'pure' barcode.
+   *
+   * @return {left,top,width,height} enclosing rectangle of all 1 bits, or null if it is all white
+   */
+  public int[] getEnclosingRectangle() {
+    int left = width;
+    int top = height;
+    int right = -1;
+    int bottom = -1;
+
+    for (int y = 0; y < height; y++) {
+      for (int x32 = 0; x32 < rowSize; x32++) {
+        int theBits = bits[y * rowSize + x32];
+        if (theBits != 0) {
+          if (y < top) {
+            top = y;
+          }
+          if (y > bottom) {
+            bottom = y;
+          }
+          if (x32 * 32 < left) {
+            int bit = 0;
+            while ((theBits << (31 - bit)) == 0) {
+              bit++;
+            }
+            if ((x32 * 32 + bit) < left) {
+              left = x32 * 32 + bit;
+            }
+          }
+          if (x32 * 32 + 31 > right) {
+            int bit = 31;
+            while ((theBits >>> bit) == 0) {
+              bit--;
+            }
+            if ((x32 * 32 + bit) > right) {
+              right = x32 * 32 + bit;
+            }
+          }
+        }
+      }
+    }
+
+    int width = right - left;
+    int height = bottom - top;
+
+    if (width < 0 || height < 0) {
+      return null;
+    }
+
+    return new int[] {left, top, width, height};
+  }
+
+  /**
+   * This is useful in detecting a corner of a 'pure' barcode.
+   *
+   * @return {x,y} coordinate of top-left-most 1 bit, or null if it is all white
+   */
+  public int[] getTopLeftOnBit() {
+    int bitsOffset = 0;
+    while (bitsOffset < bits.length && bits[bitsOffset] == 0) {
+      bitsOffset++;
+    }
+    if (bitsOffset == bits.length) {
+      return null;
+    }
+    int y = bitsOffset / rowSize;
+    int x = (bitsOffset % rowSize) << 5;
+
+    int theBits = bits[bitsOffset];
+    int bit = 0;
+    while ((theBits << (31-bit)) == 0) {
+      bit++;
+    }
+    x += bit;
+    return new int[] {x, y};
+  }
+
+  public int[] getBottomRightOnBit() {
+    int bitsOffset = bits.length - 1;
+    while (bitsOffset >= 0 && bits[bitsOffset] == 0) {
+      bitsOffset--;
+    }
+    if (bitsOffset < 0) {
+      return null;
+    }
+
+    int y = bitsOffset / rowSize;
+    int x = (bitsOffset % rowSize) << 5;
+
+    int theBits = bits[bitsOffset];
+    int bit = 31;
+    while ((theBits >>> bit) == 0) {
+      bit--;
+    }
+    x += bit;
+
+    return new int[] {x, y};
+  }
+
+  /**
+   * @return The width of the matrix
+   */
+  public int getWidth() {
+    return width;
+  }
+
+  /**
+   * @return The height of the matrix
+   */
+  public int getHeight() {
+    return height;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof BitMatrix)) {
+      return false;
+    }
+    BitMatrix other = (BitMatrix) o;
+    if (width != other.width || height != other.height ||
+        rowSize != other.rowSize || bits.length != other.bits.length) {
+      return false;
+    }
+    for (int i = 0; i < bits.length; i++) {
+      if (bits[i] != other.bits[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int hash = width;
+    hash = 31 * hash + width;
+    hash = 31 * hash + height;
+    hash = 31 * hash + rowSize;
+    for (int bit : bits) {
+      hash = 31 * hash + bit;
+    }
+    return hash;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder result = new StringBuilder(height * (width + 1));
+    for (int y = 0; y < height; y++) {
+      for (int x = 0; x < width; x++) {
+        result.append(get(x, y) ? "X " : "  ");
+      }
+      result.append('\n');
+    }
+    return result.toString();
+  }
+
+}
diff --git a/src/com/google/zxing/common/BitSource.java b/src/com/google/zxing/common/BitSource.java
new file mode 100755 (executable)
index 0000000..45a3858
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2007 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.common;
+
+/**
+ * <p>This provides an easy abstraction to read bits at a time from a sequence of bytes, where the
+ * number of bits read is not often a multiple of 8.</p>
+ *
+ * <p>This class is thread-safe but not reentrant -- unless the caller modifies the bytes array
+ * it passed in, in which case all bets are off.</p>
+ *
+ * @author Sean Owen
+ */
+public final class BitSource {
+
+  private final byte[] bytes;
+  private int byteOffset;
+  private int bitOffset;
+
+  /**
+   * @param bytes bytes from which this will read bits. Bits will be read from the first byte first.
+   * Bits are read within a byte from most-significant to least-significant bit.
+   */
+  public BitSource(byte[] bytes) {
+    this.bytes = bytes;
+  }
+
+  /**
+   * @return index of next bit in current byte which would be read by the next call to {@link #readBits(int)}.
+   */
+  public int getBitOffset() {
+    return bitOffset;
+  }
+
+  /**
+   * @return index of next byte in input byte array which would be read by the next call to {@link #readBits(int)}.
+   */
+  public int getByteOffset() {
+    return byteOffset;
+  }
+
+  /**
+   * @param numBits number of bits to read
+   * @return int representing the bits read. The bits will appear as the least-significant
+   *         bits of the int
+   * @throws IllegalArgumentException if numBits isn't in [1,32] or more than is available
+   */
+  public int readBits(int numBits) {
+    if (numBits < 1 || numBits > 32 || numBits > available()) {
+      throw new IllegalArgumentException(String.valueOf(numBits));
+    }
+
+    int result = 0;
+
+    // First, read remainder from current byte
+    if (bitOffset > 0) {
+      int bitsLeft = 8 - bitOffset;
+      int toRead = numBits < bitsLeft ? numBits : bitsLeft;
+      int bitsToNotRead = bitsLeft - toRead;
+      int mask = (0xFF >> (8 - toRead)) << bitsToNotRead;
+      result = (bytes[byteOffset] & mask) >> bitsToNotRead;
+      numBits -= toRead;
+      bitOffset += toRead;
+      if (bitOffset == 8) {
+        bitOffset = 0;
+        byteOffset++;
+      }
+    }
+
+    // Next read whole bytes
+    if (numBits > 0) {
+      while (numBits >= 8) {
+        result = (result << 8) | (bytes[byteOffset] & 0xFF);
+        byteOffset++;
+        numBits -= 8;
+      }
+
+      // Finally read a partial byte
+      if (numBits > 0) {
+        int bitsToNotRead = 8 - numBits;
+        int mask = (0xFF >> bitsToNotRead) << bitsToNotRead;
+        result = (result << numBits) | ((bytes[byteOffset] & mask) >> bitsToNotRead);
+        bitOffset += numBits;
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * @return number of bits that can be read successfully
+   */
+  public int available() {
+    return 8 * (bytes.length - byteOffset) - bitOffset;
+  }
+
+}
diff --git a/src/com/google/zxing/common/CharacterSetECI.java b/src/com/google/zxing/common/CharacterSetECI.java
new file mode 100644 (file)
index 0000000..0fc4634
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright 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.common;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.zxing.FormatException;
+
+/**
+ * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1
+ * of ISO 18004.
+ *
+ * @author Sean Owen
+ */
+public enum CharacterSetECI {
+
+  // Enum name is a Java encoding valid for java.lang and java.io
+  Cp437(new int[]{0,2}),
+  ISO8859_1(new int[]{1,3}, "ISO-8859-1"),
+  ISO8859_2(4, "ISO-8859-2"),
+  ISO8859_3(5, "ISO-8859-3"),
+  ISO8859_4(6, "ISO-8859-4"),
+  ISO8859_5(7, "ISO-8859-5"),
+  ISO8859_6(8, "ISO-8859-6"),
+  ISO8859_7(9, "ISO-8859-7"),
+  ISO8859_8(10, "ISO-8859-8"),
+  ISO8859_9(11, "ISO-8859-9"),
+  ISO8859_10(12, "ISO-8859-10"),
+  ISO8859_11(13, "ISO-8859-11"),
+  ISO8859_13(15, "ISO-8859-13"),
+  ISO8859_14(16, "ISO-8859-14"),
+  ISO8859_15(17, "ISO-8859-15"),
+  ISO8859_16(18, "ISO-8859-16"),
+  SJIS(20, "Shift_JIS"),
+  Cp1250(21, "windows-1250"),
+  Cp1251(22, "windows-1251"),
+  Cp1252(23, "windows-1252"),
+  Cp1256(24, "windows-1256"),
+  UnicodeBigUnmarked(25, "UTF-16BE", "UnicodeBig"),
+  UTF8(26, "UTF-8"),
+  ASCII(new int[] {27, 170}, "US-ASCII"),
+  Big5(28),
+  GB18030(29, "GB2312", "EUC_CN", "GBK"),
+  EUC_KR(30, "EUC-KR");
+
+  private static final Map<Integer,CharacterSetECI> VALUE_TO_ECI = new HashMap<Integer, CharacterSetECI>();
+  private static final Map<String,CharacterSetECI> NAME_TO_ECI = new HashMap<String, CharacterSetECI>();
+  static {
+    for (CharacterSetECI eci : values()) {
+      for (int value : eci.values) {
+        VALUE_TO_ECI.put(value, eci);
+      }
+      NAME_TO_ECI.put(eci.name(), eci);
+      for (String name : eci.otherEncodingNames) {
+        NAME_TO_ECI.put(name, eci);
+      }
+    }
+  }
+
+  private final int[] values;
+  private final String[] otherEncodingNames;
+
+  CharacterSetECI(int value) {
+    this(new int[] {value});
+  }
+
+  CharacterSetECI(int value, String... otherEncodingNames) {
+    this.values = new int[] {value};
+    this.otherEncodingNames = otherEncodingNames;
+  }
+
+  CharacterSetECI(int[] values, String... otherEncodingNames) {
+    this.values = values;
+    this.otherEncodingNames = otherEncodingNames;
+  }
+
+  public int getValue() {
+    return values[0];
+  }
+
+  /**
+   * @param value character set ECI value
+   * @return CharacterSetECI representing ECI of given value, or null if it is legal but
+   *   unsupported
+   * @throws IllegalArgumentException if ECI value is invalid
+   */
+  public static CharacterSetECI getCharacterSetECIByValue(int value) throws FormatException {
+    if (value < 0 || value >= 900) {
+      throw FormatException.getFormatInstance();
+    }
+    return VALUE_TO_ECI.get(value);
+  }
+
+  /**
+   * @param name character set ECI encoding name
+   * @return CharacterSetECI representing ECI for character encoding, or null if it is legal
+   *   but unsupported
+   */
+  public static CharacterSetECI getCharacterSetECIByName(String name) {
+    return NAME_TO_ECI.get(name);
+  }
+
+}
diff --git a/src/com/google/zxing/common/DecoderResult.java b/src/com/google/zxing/common/DecoderResult.java
new file mode 100644 (file)
index 0000000..2383201
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2007 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.common;
+
+import java.util.List;
+
+/**
+ * <p>Encapsulates the result of decoding a matrix of bits. This typically
+ * applies to 2D barcode formats. For now it contains the raw bytes obtained,
+ * as well as a String interpretation of those bytes, if applicable.</p>
+ *
+ * @author Sean Owen
+ */
+public final class DecoderResult {
+
+  private final byte[] rawBytes;
+  private final String text;
+  private final List<byte[]> byteSegments;
+  private final String ecLevel;
+  private Integer errorsCorrected;
+  private Integer erasures;
+  private Object other;
+
+  public DecoderResult(byte[] rawBytes,
+                       String text,
+                       List<byte[]> byteSegments,
+                       String ecLevel) {
+    this.rawBytes = rawBytes;
+    this.text = text;
+    this.byteSegments = byteSegments;
+    this.ecLevel = ecLevel;
+  }
+
+  public byte[] getRawBytes() {
+    return rawBytes;
+  }
+
+  public String getText() {
+    return text;
+  }
+
+  public List<byte[]> getByteSegments() {
+    return byteSegments;
+  }
+
+  public String getECLevel() {
+    return ecLevel;
+  }
+
+  public Integer getErrorsCorrected() {
+    return errorsCorrected;
+  }
+
+  public void setErrorsCorrected(Integer errorsCorrected) {
+    this.errorsCorrected = errorsCorrected;
+  }
+
+  public Integer getErasures() {
+    return erasures;
+  }
+
+  public void setErasures(Integer erasures) {
+    this.erasures = erasures;
+  }
+  
+  public Object getOther() {
+    return other;
+  }
+
+  public void setOther(Object other) {
+    this.other = other;
+  }
+  
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/DefaultGridSampler.java b/src/com/google/zxing/common/DefaultGridSampler.java
new file mode 100644 (file)
index 0000000..4ab43fd
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2007 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.common;
+
+import com.google.zxing.NotFoundException;
+
+/**
+ * @author Sean Owen
+ */
+public final class DefaultGridSampler extends GridSampler {
+
+  @Override
+  public BitMatrix sampleGrid(BitMatrix image,
+                              int dimensionX,
+                              int dimensionY,
+                              float p1ToX, float p1ToY,
+                              float p2ToX, float p2ToY,
+                              float p3ToX, float p3ToY,
+                              float p4ToX, float p4ToY,
+                              float p1FromX, float p1FromY,
+                              float p2FromX, float p2FromY,
+                              float p3FromX, float p3FromY,
+                              float p4FromX, float p4FromY) throws NotFoundException {
+
+    PerspectiveTransform transform = PerspectiveTransform.quadrilateralToQuadrilateral(
+        p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY,
+        p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY);
+
+    return sampleGrid(image, dimensionX, dimensionY, transform);
+  }
+
+  @Override
+  public BitMatrix sampleGrid(BitMatrix image,
+                              int dimensionX,
+                              int dimensionY,
+                              PerspectiveTransform transform) throws NotFoundException {
+    if (dimensionX <= 0 || dimensionY <= 0) {
+      throw NotFoundException.getNotFoundInstance();      
+    }
+    BitMatrix bits = new BitMatrix(dimensionX, dimensionY);
+    float[] points = new float[dimensionX << 1];
+    for (int y = 0; y < dimensionY; y++) {
+      int max = points.length;
+      float iValue = (float) y + 0.5f;
+      for (int x = 0; x < max; x += 2) {
+        points[x] = (float) (x >> 1) + 0.5f;
+        points[x + 1] = iValue;
+      }
+      transform.transformPoints(points);
+      // Quick check to see if points transformed to something inside the image;
+      // sufficient to check the endpoints
+      checkAndNudgePoints(image, points);
+      try {
+        for (int x = 0; x < max; x += 2) {
+          if (image.get((int) points[x], (int) points[x + 1])) {
+            // Black(-ish) pixel
+            bits.set(x >> 1, y);
+          }
+        }
+      } catch (ArrayIndexOutOfBoundsException aioobe) {
+        // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting
+        // transform gets "twisted" such that it maps a straight line of points to a set of points
+        // whose endpoints are in bounds, but others are not. There is probably some mathematical
+        // way to detect this about the transformation that I don't know yet.
+        // This results in an ugly runtime exception despite our clever checks above -- can't have
+        // that. We could check each point's coordinates but that feels duplicative. We settle for
+        // catching and wrapping ArrayIndexOutOfBoundsException.
+        throw NotFoundException.getNotFoundInstance();
+      }
+    }
+    return bits;
+  }
+
+}
diff --git a/src/com/google/zxing/common/DetectorResult.java b/src/com/google/zxing/common/DetectorResult.java
new file mode 100644 (file)
index 0000000..0f3cf15
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2007 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.common;
+
+import com.google.zxing.ResultPoint;
+
+/**
+ * <p>Encapsulates the result of detecting a barcode in an image. This includes the raw
+ * matrix of black/white pixels corresponding to the barcode, and possibly points of interest
+ * in the image, like the location of finder patterns or corners of the barcode in the image.</p>
+ *
+ * @author Sean Owen
+ */
+public class DetectorResult {
+
+  private final BitMatrix bits;
+  private final ResultPoint[] points;
+
+  public DetectorResult(BitMatrix bits, ResultPoint[] points) {
+    this.bits = bits;
+    this.points = points;
+  }
+
+  public final BitMatrix getBits() {
+    return bits;
+  }
+
+  public final ResultPoint[] getPoints() {
+    return points;
+  }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/GlobalHistogramBinarizer.java b/src/com/google/zxing/common/GlobalHistogramBinarizer.java
new file mode 100644 (file)
index 0000000..f967646
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2009 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.common;
+
+import com.google.zxing.Binarizer;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.NotFoundException;
+
+/**
+ * This Binarizer implementation uses the old ZXing global histogram approach. It is suitable
+ * for low-end mobile devices which don't have enough CPU or memory to use a local thresholding
+ * algorithm. However, because it picks a global black point, it cannot handle difficult shadows
+ * and gradients.
+ *
+ * Faster mobile devices and all desktop applications should probably use HybridBinarizer instead.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Sean Owen
+ */
+public class GlobalHistogramBinarizer extends Binarizer {
+
+  private static final int LUMINANCE_BITS = 5;
+  private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;
+  private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;
+  private static final byte[] EMPTY = new byte[0];
+
+  private byte[] luminances;
+  private final int[] buckets;
+
+  public GlobalHistogramBinarizer(LuminanceSource source) {
+    super(source);
+    luminances = EMPTY;
+    buckets = new int[LUMINANCE_BUCKETS];
+  }
+
+  // Applies simple sharpening to the row data to improve performance of the 1D Readers.
+  @Override
+  public BitArray getBlackRow(int y, BitArray row) throws NotFoundException {
+    LuminanceSource source = getLuminanceSource();
+    int width = source.getWidth();
+    if (row == null || row.getSize() < width) {
+      row = new BitArray(width);
+    } else {
+      row.clear();
+    }
+
+    initArrays(width);
+    byte[] localLuminances = source.getRow(y, luminances);
+    int[] localBuckets = buckets;
+    for (int x = 0; x < width; x++) {
+      int pixel = localLuminances[x] & 0xff;
+      localBuckets[pixel >> LUMINANCE_SHIFT]++;
+    }
+    int blackPoint = estimateBlackPoint(localBuckets);
+
+    int left = localLuminances[0] & 0xff;
+    int center = localLuminances[1] & 0xff;
+    for (int x = 1; x < width - 1; x++) {
+      int right = localLuminances[x + 1] & 0xff;
+      // A simple -1 4 -1 box filter with a weight of 2.
+      int luminance = ((center << 2) - left - right) >> 1;
+      if (luminance < blackPoint) {
+        row.set(x);
+      }
+      left = center;
+      center = right;
+    }
+    return row;
+  }
+
+  // Does not sharpen the data, as this call is intended to only be used by 2D Readers.
+  @Override
+  public BitMatrix getBlackMatrix() throws NotFoundException {
+    LuminanceSource source = getLuminanceSource();
+    int width = source.getWidth();
+    int height = source.getHeight();
+    BitMatrix matrix = new BitMatrix(width, height);
+
+    // Quickly calculates the histogram by sampling four rows from the image. This proved to be
+    // more robust on the blackbox tests than sampling a diagonal as we used to do.
+    initArrays(width);
+    int[] localBuckets = buckets;
+    for (int y = 1; y < 5; y++) {
+      int row = height * y / 5;
+      byte[] localLuminances = source.getRow(row, luminances);
+      int right = (width << 2) / 5;
+      for (int x = width / 5; x < right; x++) {
+        int pixel = localLuminances[x] & 0xff;
+        localBuckets[pixel >> LUMINANCE_SHIFT]++;
+      }
+    }
+    int blackPoint = estimateBlackPoint(localBuckets);
+
+    // We delay reading the entire image luminance until the black point estimation succeeds.
+    // Although we end up reading four rows twice, it is consistent with our motto of
+    // "fail quickly" which is necessary for continuous scanning.
+    byte[] localLuminances = source.getMatrix();
+    for (int y = 0; y < height; y++) {
+      int offset = y * width;
+      for (int x = 0; x< width; x++) {
+        int pixel = localLuminances[offset + x] & 0xff;
+        if (pixel < blackPoint) {
+          matrix.set(x, y);
+        }
+      }
+    }
+
+    return matrix;
+  }
+
+  @Override
+  public Binarizer createBinarizer(LuminanceSource source) {
+    return new GlobalHistogramBinarizer(source);
+  }
+
+  private void initArrays(int luminanceSize) {
+    if (luminances.length < luminanceSize) {
+      luminances = new byte[luminanceSize];
+    }
+    for (int x = 0; x < LUMINANCE_BUCKETS; x++) {
+      buckets[x] = 0;
+    }
+  }
+
+  private static int estimateBlackPoint(int[] buckets) throws NotFoundException {
+    // Find the tallest peak in the histogram.
+    int numBuckets = buckets.length;
+    int maxBucketCount = 0;
+    int firstPeak = 0;
+    int firstPeakSize = 0;
+    for (int x = 0; x < numBuckets; x++) {
+      if (buckets[x] > firstPeakSize) {
+        firstPeak = x;
+        firstPeakSize = buckets[x];
+      }
+      if (buckets[x] > maxBucketCount) {
+        maxBucketCount = buckets[x];
+      }
+    }
+
+    // Find the second-tallest peak which is somewhat far from the tallest peak.
+    int secondPeak = 0;
+    int secondPeakScore = 0;
+    for (int x = 0; x < numBuckets; x++) {
+      int distanceToBiggest = x - firstPeak;
+      // Encourage more distant second peaks by multiplying by square of distance.
+      int score = buckets[x] * distanceToBiggest * distanceToBiggest;
+      if (score > secondPeakScore) {
+        secondPeak = x;
+        secondPeakScore = score;
+      }
+    }
+
+    // Make sure firstPeak corresponds to the black peak.
+    if (firstPeak > secondPeak) {
+      int temp = firstPeak;
+      firstPeak = secondPeak;
+      secondPeak = temp;
+    }
+
+    // If there is too little contrast in the image to pick a meaningful black point, throw rather
+    // than waste time trying to decode the image, and risk false positives.
+    if (secondPeak - firstPeak <= numBuckets >> 4) {
+      throw NotFoundException.getNotFoundInstance();
+    }
+
+    // Find a valley between them that is low and closer to the white peak.
+    int bestValley = secondPeak - 1;
+    int bestValleyScore = -1;
+    for (int x = secondPeak - 1; x > firstPeak; x--) {
+      int fromFirst = x - firstPeak;
+      int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]);
+      if (score > bestValleyScore) {
+        bestValley = x;
+        bestValleyScore = score;
+      }
+    }
+
+    return bestValley << LUMINANCE_SHIFT;
+  }
+
+}
diff --git a/src/com/google/zxing/common/GridSampler.java b/src/com/google/zxing/common/GridSampler.java
new file mode 100644 (file)
index 0000000..17a42e8
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2007 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.common;
+
+import com.google.zxing.NotFoundException;
+
+/**
+ * Implementations of this class can, given locations of finder patterns for a QR code in an
+ * image, sample the right points in the image to reconstruct the QR code, accounting for
+ * perspective distortion. It is abstracted since it is relatively expensive and should be allowed
+ * to take advantage of platform-specific optimized implementations, like Sun's Java Advanced
+ * Imaging library, but which may not be available in other environments such as J2ME, and vice
+ * versa.
+ *
+ * The implementation used can be controlled by calling {@link #setGridSampler(GridSampler)}
+ * with an instance of a class which implements this interface.
+ *
+ * @author Sean Owen
+ */
+public abstract class GridSampler {
+
+  private static GridSampler gridSampler = new DefaultGridSampler();
+
+  /**
+   * Sets the implementation of GridSampler used by the library. One global
+   * instance is stored, which may sound problematic. But, the implementation provided
+   * ought to be appropriate for the entire platform, and all uses of this library
+   * in the whole lifetime of the JVM. For instance, an Android activity can swap in
+   * an implementation that takes advantage of native platform libraries.
+   * 
+   * @param newGridSampler The platform-specific object to install.
+   */
+  public static void setGridSampler(GridSampler newGridSampler) {
+    gridSampler = newGridSampler;
+  }
+
+  /**
+   * @return the current implementation of GridSampler
+   */
+  public static GridSampler getInstance() {
+    return gridSampler;
+  }
+
+  /**
+   * Samples an image for a rectangular matrix of bits of the given dimension.
+   * @param image image to sample
+   * @param dimensionX width of {@link BitMatrix} to sample from image
+   * @param dimensionY height of {@link BitMatrix} to sample from image
+   * @return {@link BitMatrix} representing a grid of points sampled from the image within a region
+   *   defined by the "from" parameters
+   * @throws NotFoundException if image can't be sampled, for example, if the transformation defined
+   *   by the given points is invalid or results in sampling outside the image boundaries
+   */
+  public abstract BitMatrix sampleGrid(BitMatrix image,
+                                       int dimensionX,
+                                       int dimensionY,
+                                       float p1ToX, float p1ToY,
+                                       float p2ToX, float p2ToY,
+                                       float p3ToX, float p3ToY,
+                                       float p4ToX, float p4ToY,
+                                       float p1FromX, float p1FromY,
+                                       float p2FromX, float p2FromY,
+                                       float p3FromX, float p3FromY,
+                                       float p4FromX, float p4FromY) throws NotFoundException;
+  
+  public abstract BitMatrix sampleGrid(BitMatrix image,
+                                       int dimensionX,
+                                       int dimensionY,
+                                       PerspectiveTransform transform) throws NotFoundException;
+
+  /**
+   * <p>Checks a set of points that have been transformed to sample points on an image against
+   * the image's dimensions to see if the point are even within the image.</p>
+   *
+   * <p>This method will actually "nudge" the endpoints back onto the image if they are found to be
+   * barely (less than 1 pixel) off the image. This accounts for imperfect detection of finder
+   * patterns in an image where the QR Code runs all the way to the image border.</p>
+   *
+   * <p>For efficiency, the method will check points from either end of the line until one is found
+   * to be within the image. Because the set of points are assumed to be linear, this is valid.</p>
+   *
+   * @param image image into which the points should map
+   * @param points actual points in x1,y1,...,xn,yn form
+   * @throws NotFoundException if an endpoint is lies outside the image boundaries
+   */
+  protected static void checkAndNudgePoints(BitMatrix image,
+                                            float[] points) throws NotFoundException {
+    int width = image.getWidth();
+    int height = image.getHeight();
+    // Check and nudge points from start until we see some that are OK:
+    boolean nudged = true;
+    for (int offset = 0; offset < points.length && nudged; offset += 2) {
+      int x = (int) points[offset];
+      int y = (int) points[offset + 1];
+      if (x < -1 || x > width || y < -1 || y > height) {
+        throw NotFoundException.getNotFoundInstance();
+      }
+      nudged = false;
+      if (x == -1) {
+        points[offset] = 0.0f;
+        nudged = true;
+      } else if (x == width) {
+        points[offset] = width - 1;
+        nudged = true;
+      }
+      if (y == -1) {
+        points[offset + 1] = 0.0f;
+        nudged = true;
+      } else if (y == height) {
+        points[offset + 1] = height - 1;
+        nudged = true;
+      }
+    }
+    // Check and nudge points from end:
+    nudged = true;
+    for (int offset = points.length - 2; offset >= 0 && nudged; offset -= 2) {
+      int x = (int) points[offset];
+      int y = (int) points[offset + 1];
+      if (x < -1 || x > width || y < -1 || y > height) {
+        throw NotFoundException.getNotFoundInstance();
+      }
+      nudged = false;
+      if (x == -1) {
+        points[offset] = 0.0f;
+        nudged = true;
+      } else if (x == width) {
+        points[offset] = width - 1;
+        nudged = true;
+      }
+      if (y == -1) {
+        points[offset + 1] = 0.0f;
+        nudged = true;
+      } else if (y == height) {
+        points[offset + 1] = height - 1;
+        nudged = true;
+      }
+    }
+  }
+
+}
diff --git a/src/com/google/zxing/common/HybridBinarizer.java b/src/com/google/zxing/common/HybridBinarizer.java
new file mode 100644 (file)
index 0000000..ba56db7
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2009 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.common;
+
+import com.google.zxing.Binarizer;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.NotFoundException;
+
+/**
+ * This class implements a local thresholding algorithm, which while slower than the
+ * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for
+ * high frequency images of barcodes with black data on white backgrounds. For this application,
+ * it does a much better job than a global blackpoint with severe shadows and gradients.
+ * However it tends to produce artifacts on lower frequency images and is therefore not
+ * a good general purpose binarizer for uses outside ZXing.
+ *
+ * This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers,
+ * and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already
+ * inherently local, and only fails for horizontal gradients. We can revisit that problem later,
+ * but for now it was not a win to use local blocks for 1D.
+ *
+ * This Binarizer is the default for the unit tests and the recommended class for library users.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class HybridBinarizer extends GlobalHistogramBinarizer {
+
+  // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels.
+  // So this is the smallest dimension in each axis we can accept.
+  private static final int BLOCK_SIZE_POWER = 3;
+  private static final int BLOCK_SIZE = 1 << BLOCK_SIZE_POWER; // ...0100...00
+  private static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1;   // ...0011...11
+  private static final int MINIMUM_DIMENSION = BLOCK_SIZE * 5;
+  private static final int MIN_DYNAMIC_RANGE = 24;
+
+  private BitMatrix matrix;
+
+  public HybridBinarizer(LuminanceSource source) {
+    super(source);
+  }
+
+  /**
+   * Calculates the final BitMatrix once for all requests. This could be called once from the
+   * constructor instead, but there are some advantages to doing it lazily, such as making
+   * profiling easier, and not doing heavy lifting when callers don't expect it.
+   */
+  @Override
+  public BitMatrix getBlackMatrix() throws NotFoundException {
+    if (matrix != null) {
+      return matrix;
+    }
+    LuminanceSource source = getLuminanceSource();
+    int width = source.getWidth();
+    int height = source.getHeight();
+    if (width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION) {
+      byte[] luminances = source.getMatrix();
+      int subWidth = width >> BLOCK_SIZE_POWER;
+      if ((width & BLOCK_SIZE_MASK) != 0) {
+        subWidth++;
+      }
+      int subHeight = height >> BLOCK_SIZE_POWER;
+      if ((height & BLOCK_SIZE_MASK) != 0) {
+        subHeight++;
+      }
+      int[][] blackPoints = calculateBlackPoints(luminances, subWidth, subHeight, width, height);
+
+      BitMatrix newMatrix = new BitMatrix(width, height);
+      calculateThresholdForBlock(luminances, subWidth, subHeight, width, height, blackPoints, newMatrix);
+      matrix = newMatrix;
+    } else {
+      // If the image is too small, fall back to the global histogram approach.
+      matrix = super.getBlackMatrix();
+    }
+    return matrix;
+  }
+
+  @Override
+  public Binarizer createBinarizer(LuminanceSource source) {
+    return new HybridBinarizer(source);
+  }
+
+  /**
+   * For each block in the image, calculate the average black point using a 5x5 grid
+   * of the blocks around it. Also handles the corner cases (fractional blocks are computed based
+   * on the last pixels in the row/column which are also used in the previous block).
+   */
+  private static void calculateThresholdForBlock(byte[] luminances,
+                                                 int subWidth,
+                                                 int subHeight,
+                                                 int width,
+                                                 int height,
+                                                 int[][] blackPoints,
+                                                 BitMatrix matrix) {
+    for (int y = 0; y < subHeight; y++) {
+      int yoffset = y << BLOCK_SIZE_POWER;
+      int maxYOffset = height - BLOCK_SIZE;
+      if (yoffset > maxYOffset) {
+        yoffset = maxYOffset;
+      }
+      for (int x = 0; x < subWidth; x++) {
+        int xoffset = x << BLOCK_SIZE_POWER;
+        int maxXOffset = width - BLOCK_SIZE;
+        if (xoffset > maxXOffset) {
+          xoffset = maxXOffset;
+        }
+        int left = cap(x, 2, subWidth - 3);
+        int top = cap(y, 2, subHeight - 3);
+        int sum = 0;
+        for (int z = -2; z <= 2; z++) {
+          int[] blackRow = blackPoints[top + z];
+          sum += blackRow[left - 2] + blackRow[left - 1] + blackRow[left] + blackRow[left + 1] + blackRow[left + 2];
+        }
+        int average = sum / 25;
+        thresholdBlock(luminances, xoffset, yoffset, average, width, matrix);
+      }
+    }
+  }
+
+  private static int cap(int value, int min, int max) {
+    return value < min ? min : value > max ? max : value;
+  }
+
+  /**
+   * Applies a single threshold to a block of pixels.
+   */
+  private static void thresholdBlock(byte[] luminances,
+                                     int xoffset,
+                                     int yoffset,
+                                     int threshold,
+                                     int stride,
+                                     BitMatrix matrix) {
+    for (int y = 0, offset = yoffset * stride + xoffset; y < BLOCK_SIZE; y++, offset += stride) {
+      for (int x = 0; x < BLOCK_SIZE; x++) {
+        // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0.
+        if ((luminances[offset + x] & 0xFF) <= threshold) {
+          matrix.set(xoffset + x, yoffset + y);
+        }
+      }
+    }
+  }
+
+  /**
+   * Calculates a single black point for each block of pixels and saves it away.
+   * See the following thread for a discussion of this algorithm:
+   *  http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0
+   */
+  private static int[][] calculateBlackPoints(byte[] luminances,
+                                              int subWidth,
+                                              int subHeight,
+                                              int width,
+                                              int height) {
+    int[][] blackPoints = new int[subHeight][subWidth];
+    for (int y = 0; y < subHeight; y++) {
+      int yoffset = y << BLOCK_SIZE_POWER;
+      int maxYOffset = height - BLOCK_SIZE;
+      if (yoffset > maxYOffset) {
+        yoffset = maxYOffset;
+      }
+      for (int x = 0; x < subWidth; x++) {
+        int xoffset = x << BLOCK_SIZE_POWER;
+        int maxXOffset = width - BLOCK_SIZE;
+        if (xoffset > maxXOffset) {
+          xoffset = maxXOffset;
+        }
+        int sum = 0;
+        int min = 0xFF;
+        int max = 0;
+        for (int yy = 0, offset = yoffset * width + xoffset; yy < BLOCK_SIZE; yy++, offset += width) {
+          for (int xx = 0; xx < BLOCK_SIZE; xx++) {
+            int pixel = luminances[offset + xx] & 0xFF;
+            sum += pixel;
+            // still looking for good contrast
+            if (pixel < min) {
+              min = pixel;
+            }
+            if (pixel > max) {
+              max = pixel;
+            }
+          }
+          // short-circuit min/max tests once dynamic range is met
+          if (max - min > MIN_DYNAMIC_RANGE) {
+            // finish the rest of the rows quickly
+            for (yy++, offset += width; yy < BLOCK_SIZE; yy++, offset += width) {
+              for (int xx = 0; xx < BLOCK_SIZE; xx++) {
+                sum += luminances[offset + xx] & 0xFF;
+              }
+            }
+          }
+        }
+
+        // The default estimate is the average of the values in the block.
+        int average = sum >> (BLOCK_SIZE_POWER * 2);
+        if (max - min <= MIN_DYNAMIC_RANGE) {
+          // If variation within the block is low, assume this is a block with only light or only
+          // dark pixels. In that case we do not want to use the average, as it would divide this
+          // low contrast area into black and white pixels, essentially creating data out of noise.
+          //
+          // The default assumption is that the block is light/background. Since no estimate for
+          // the level of dark pixels exists locally, use half the min for the block.
+          average = min >> 1;
+
+          if (y > 0 && x > 0) {
+            // Correct the "white background" assumption for blocks that have neighbors by comparing
+            // the pixels in this block to the previously calculated black points. This is based on
+            // the fact that dark barcode symbology is always surrounded by some amount of light
+            // background for which reasonable black point estimates were made. The bp estimated at
+            // the boundaries is used for the interior.
+
+            // The (min < bp) is arbitrary but works better than other heuristics that were tried.
+            int averageNeighborBlackPoint = (blackPoints[y - 1][x] + (2 * blackPoints[y][x - 1]) +
+                blackPoints[y - 1][x - 1]) >> 2;
+            if (min < averageNeighborBlackPoint) {
+              average = averageNeighborBlackPoint;
+            }
+          }
+        }
+        blackPoints[y][x] = average;
+      }
+    }
+    return blackPoints;
+  }
+
+}
diff --git a/src/com/google/zxing/common/PerspectiveTransform.java b/src/com/google/zxing/common/PerspectiveTransform.java
new file mode 100644 (file)
index 0000000..eac9a76
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2007 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.common;
+
+/**
+ * <p>This class implements a perspective transform in two dimensions. Given four source and four
+ * destination points, it will compute the transformation implied between them. The code is based
+ * directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.</p>
+ *
+ * @author Sean Owen
+ */
+public final class PerspectiveTransform {
+
+  private final float a11;
+  private final float a12;
+  private final float a13;
+  private final float a21;
+  private final float a22;
+  private final float a23;
+  private final float a31;
+  private final float a32;
+  private final float a33;
+
+  private PerspectiveTransform(float a11, float a21, float a31,
+                               float a12, float a22, float a32,
+                               float a13, float a23, float a33) {
+    this.a11 = a11;
+    this.a12 = a12;
+    this.a13 = a13;
+    this.a21 = a21;
+    this.a22 = a22;
+    this.a23 = a23;
+    this.a31 = a31;
+    this.a32 = a32;
+    this.a33 = a33;
+  }
+
+  public static PerspectiveTransform quadrilateralToQuadrilateral(float x0, float y0,
+                                                                  float x1, float y1,
+                                                                  float x2, float y2,
+                                                                  float x3, float y3,
+                                                                  float x0p, float y0p,
+                                                                  float x1p, float y1p,
+                                                                  float x2p, float y2p,
+                                                                  float x3p, float y3p) {
+
+    PerspectiveTransform qToS = quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3);
+    PerspectiveTransform sToQ = squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);
+    return sToQ.times(qToS);
+  }
+
+  public void transformPoints(float[] points) {
+    int max = points.length;
+    float a11 = this.a11;
+    float a12 = this.a12;
+    float a13 = this.a13;
+    float a21 = this.a21;
+    float a22 = this.a22;
+    float a23 = this.a23;
+    float a31 = this.a31;
+    float a32 = this.a32;
+    float a33 = this.a33;
+    for (int i = 0; i < max; i += 2) {
+      float x = points[i];
+      float y = points[i + 1];
+      float denominator = a13 * x + a23 * y + a33;
+      points[i] = (a11 * x + a21 * y + a31) / denominator;
+      points[i + 1] = (a12 * x + a22 * y + a32) / denominator;
+    }
+  }
+
+  /** Convenience method, not optimized for performance. */
+  public void transformPoints(float[] xValues, float[] yValues) {
+    int n = xValues.length;
+    for (int i = 0; i < n; i ++) {
+      float x = xValues[i];
+      float y = yValues[i];
+      float denominator = a13 * x + a23 * y + a33;
+      xValues[i] = (a11 * x + a21 * y + a31) / denominator;
+      yValues[i] = (a12 * x + a22 * y + a32) / denominator;
+    }
+  }
+
+  public static PerspectiveTransform squareToQuadrilateral(float x0, float y0,
+                                                           float x1, float y1,
+                                                           float x2, float y2,
+                                                           float x3, float y3) {
+    float dx3 = x0 - x1 + x2 - x3;
+    float dy3 = y0 - y1 + y2 - y3;
+    if (dx3 == 0.0f && dy3 == 0.0f) {
+      // Affine
+      return new PerspectiveTransform(x1 - x0, x2 - x1, x0,
+                                      y1 - y0, y2 - y1, y0,
+                                      0.0f,    0.0f,    1.0f);
+    } else {
+      float dx1 = x1 - x2;
+      float dx2 = x3 - x2;
+      float dy1 = y1 - y2;
+      float dy2 = y3 - y2;
+      float denominator = dx1 * dy2 - dx2 * dy1;
+      float a13 = (dx3 * dy2 - dx2 * dy3) / denominator;
+      float a23 = (dx1 * dy3 - dx3 * dy1) / denominator;
+      return new PerspectiveTransform(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0,
+                                      y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0,
+                                      a13,                a23,                1.0f);
+    }
+  }
+
+  public static PerspectiveTransform quadrilateralToSquare(float x0, float y0,
+                                                           float x1, float y1,
+                                                           float x2, float y2,
+                                                           float x3, float y3) {
+    // Here, the adjoint serves as the inverse:
+    return squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint();
+  }
+
+  PerspectiveTransform buildAdjoint() {
+    // Adjoint is the transpose of the cofactor matrix:
+    return new PerspectiveTransform(a22 * a33 - a23 * a32,
+        a23 * a31 - a21 * a33,
+        a21 * a32 - a22 * a31,
+        a13 * a32 - a12 * a33,
+        a11 * a33 - a13 * a31,
+        a12 * a31 - a11 * a32,
+        a12 * a23 - a13 * a22,
+        a13 * a21 - a11 * a23,
+        a11 * a22 - a12 * a21);
+  }
+
+  PerspectiveTransform times(PerspectiveTransform other) {
+    return new PerspectiveTransform(a11 * other.a11 + a21 * other.a12 + a31 * other.a13,
+        a11 * other.a21 + a21 * other.a22 + a31 * other.a23,
+        a11 * other.a31 + a21 * other.a32 + a31 * other.a33,
+        a12 * other.a11 + a22 * other.a12 + a32 * other.a13,
+        a12 * other.a21 + a22 * other.a22 + a32 * other.a23,
+        a12 * other.a31 + a22 * other.a32 + a32 * other.a33,
+        a13 * other.a11 + a23 * other.a12 + a33 * other.a13,
+        a13 * other.a21 + a23 * other.a22 + a33 * other.a23,
+        a13 * other.a31 + a23 * other.a32 + a33 * other.a33);
+
+  }
+
+}
diff --git a/src/com/google/zxing/common/StringUtils.java b/src/com/google/zxing/common/StringUtils.java
new file mode 100644 (file)
index 0000000..5861b30
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2010 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.common;
+
+import java.util.Map;
+
+import com.google.zxing.DecodeHintType;
+
+/**
+ * Common string-related functions.
+ *
+ * @author Sean Owen
+ * @author Alex Dupre
+ */
+public final class StringUtils {
+
+  private static final String PLATFORM_DEFAULT_ENCODING =
+      System.getProperty("file.encoding");
+  public static final String SHIFT_JIS = "SJIS";
+  public static final String GB2312 = "GB2312";
+  private static final String EUC_JP = "EUC_JP";
+  private static final String UTF8 = "UTF8";
+  private static final String ISO88591 = "ISO8859_1";
+  private static final boolean ASSUME_SHIFT_JIS =
+      SHIFT_JIS.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING) ||
+      EUC_JP.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING);
+
+  private StringUtils() {}
+
+  /**
+   * @param bytes bytes encoding a string, whose encoding should be guessed
+   * @param hints decode hints if applicable
+   * @return name of guessed encoding; at the moment will only guess one of:
+   *  {@link #SHIFT_JIS}, {@link #UTF8}, {@link #ISO88591}, or the platform
+   *  default encoding if none of these can possibly be correct
+   */
+  public static String guessEncoding(byte[] bytes, Map<DecodeHintType,?> hints) {
+    if (hints != null) {
+      String characterSet = (String) hints.get(DecodeHintType.CHARACTER_SET);
+      if (characterSet != null) {
+        return characterSet;
+      }
+    }
+    // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS,
+    // which should be by far the most common encodings.
+    int length = bytes.length;
+    boolean canBeISO88591 = true;
+    boolean canBeShiftJIS = true;
+    boolean canBeUTF8 = true;
+    int utf8BytesLeft = 0;
+    //int utf8LowChars = 0;
+    int utf2BytesChars = 0;
+    int utf3BytesChars = 0;
+    int utf4BytesChars = 0;
+    int sjisBytesLeft = 0;
+    //int sjisLowChars = 0;
+    int sjisKatakanaChars = 0;
+    //int sjisDoubleBytesChars = 0;
+    int sjisCurKatakanaWordLength = 0;
+    int sjisCurDoubleBytesWordLength = 0;
+    int sjisMaxKatakanaWordLength = 0;
+    int sjisMaxDoubleBytesWordLength = 0;
+    //int isoLowChars = 0;
+    //int isoHighChars = 0;
+    int isoHighOther = 0;
+
+    boolean utf8bom = bytes.length > 3 &&
+        bytes[0] == (byte) 0xEF &&
+        bytes[1] == (byte) 0xBB &&
+        bytes[2] == (byte) 0xBF;
+
+    for (int i = 0;
+         i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8);
+         i++) {
+
+      int value = bytes[i] & 0xFF;
+
+      // UTF-8 stuff
+      if (canBeUTF8) {
+        if (utf8BytesLeft > 0) {
+          if ((value & 0x80) == 0) {
+            canBeUTF8 = false;
+          } else {
+            utf8BytesLeft--;
+          }
+        } else if ((value & 0x80) != 0) {
+          if ((value & 0x40) == 0) {
+            canBeUTF8 = false;
+          } else {
+            utf8BytesLeft++;
+            if ((value & 0x20) == 0) {
+              utf2BytesChars++;
+            } else {
+              utf8BytesLeft++;
+              if ((value & 0x10) == 0) {
+                utf3BytesChars++;
+              } else {
+                utf8BytesLeft++;
+                if ((value & 0x08) == 0) {
+                  utf4BytesChars++;
+                } else {
+                  canBeUTF8 = false;
+                }
+              }
+            }
+          }
+        } //else {
+          //utf8LowChars++;
+        //}
+      }
+
+      // ISO-8859-1 stuff
+      if (canBeISO88591) {
+        if (value > 0x7F && value < 0xA0) {
+          canBeISO88591 = false;
+        } else if (value > 0x9F) {
+          if (value < 0xC0 || value == 0xD7 || value == 0xF7) {
+            isoHighOther++;
+          } //else {
+            //isoHighChars++;
+          //}
+        } //else {
+          //isoLowChars++;
+        //}
+      }
+
+      // Shift_JIS stuff
+      if (canBeShiftJIS) {
+        if (sjisBytesLeft > 0) {
+          if (value < 0x40 || value == 0x7F || value > 0xFC) {
+            canBeShiftJIS = false;
+          } else {
+            sjisBytesLeft--;
+          }
+        } else if (value == 0x80 || value == 0xA0 || value > 0xEF) {
+          canBeShiftJIS = false;
+        } else if (value > 0xA0 && value < 0xE0) {
+          sjisKatakanaChars++;
+          sjisCurDoubleBytesWordLength = 0;
+          sjisCurKatakanaWordLength++;
+          if (sjisCurKatakanaWordLength > sjisMaxKatakanaWordLength) {
+            sjisMaxKatakanaWordLength = sjisCurKatakanaWordLength;
+          }
+        } else if (value > 0x7F) {
+          sjisBytesLeft++;
+          //sjisDoubleBytesChars++;
+          sjisCurKatakanaWordLength = 0;
+          sjisCurDoubleBytesWordLength++;
+          if (sjisCurDoubleBytesWordLength > sjisMaxDoubleBytesWordLength) {
+            sjisMaxDoubleBytesWordLength = sjisCurDoubleBytesWordLength;
+          }
+        } else {
+          //sjisLowChars++;
+          sjisCurKatakanaWordLength = 0;
+          sjisCurDoubleBytesWordLength = 0;
+        }
+      }
+    }
+
+    if (canBeUTF8 && utf8BytesLeft > 0) {
+      canBeUTF8 = false;
+    }
+    if (canBeShiftJIS && sjisBytesLeft > 0) {
+      canBeShiftJIS = false;
+    }
+
+    // Easy -- if there is BOM or at least 1 valid not-single byte character (and no evidence it can't be UTF-8), done
+    if (canBeUTF8 && (utf8bom || utf2BytesChars + utf3BytesChars + utf4BytesChars > 0)) {
+      return UTF8;
+    }
+    // Easy -- if assuming Shift_JIS or at least 3 valid consecutive not-ascii characters (and no evidence it can't be), done
+    if (canBeShiftJIS && (ASSUME_SHIFT_JIS || sjisMaxKatakanaWordLength >= 3 || sjisMaxDoubleBytesWordLength >= 3)) {
+      return SHIFT_JIS;
+    }
+    // Distinguishing Shift_JIS and ISO-8859-1 can be a little tough for short words. The crude heuristic is:
+    // - If we saw
+    //   - only two consecutive katakana chars in the whole text, or
+    //   - at least 10% of bytes that could be "upper" not-alphanumeric Latin1,
+    // - then we conclude Shift_JIS, else ISO-8859-1
+    if (canBeISO88591 && canBeShiftJIS) {
+      return (sjisMaxKatakanaWordLength == 2 && sjisKatakanaChars == 2) || isoHighOther * 10 >= length
+          ? SHIFT_JIS : ISO88591;
+    }
+
+    // Otherwise, try in order ISO-8859-1, Shift JIS, UTF-8 and fall back to default platform encoding
+    if (canBeISO88591) {
+      return ISO88591;
+    }
+    if (canBeShiftJIS) {
+      return SHIFT_JIS;
+    }
+    if (canBeUTF8) {
+      return UTF8;
+    }
+    // Otherwise, we take a wild guess with platform encoding
+    return PLATFORM_DEFAULT_ENCODING;
+  }
+
+}
diff --git a/src/com/google/zxing/common/detector/MathUtils.java b/src/com/google/zxing/common/detector/MathUtils.java
new file mode 100644 (file)
index 0000000..df66850
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 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.common.detector;
+
+public final class MathUtils {
+
+  private MathUtils() {
+  }
+
+  /**
+   * Ends up being a bit faster than {@link Math#round(float)}. This merely rounds its
+   * argument to the nearest int, where x.5 rounds up to x+1.
+   */
+  public static int round(float d) {
+    return (int) (d + 0.5f);
+  }
+
+  public static float distance(float aX, float aY, float bX, float bY) {
+    float xDiff = aX - bX;
+    float yDiff = aY - bY;
+    return (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff);
+  }
+
+  public static float distance(int aX, int aY, int bX, int bY) {
+    int xDiff = aX - bX;
+    int yDiff = aY - bY;
+    return (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff);
+  }
+
+}
diff --git a/src/com/google/zxing/common/reedsolomon/GenericGF.java b/src/com/google/zxing/common/reedsolomon/GenericGF.java
new file mode 100644 (file)
index 0000000..9f2e876
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2007 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.common.reedsolomon;
+
+/**
+ * <p>This class contains utility methods for performing mathematical operations over
+ * the Galois Fields. Operations use a given primitive polynomial in calculations.</p>
+ *
+ * <p>Throughout this package, elements of the GF are represented as an {@code int}
+ * for convenience and speed (but at the cost of memory).
+ * </p>
+ *
+ * @author Sean Owen
+ * @author David Olivier
+ */
+public final class GenericGF {
+
+  public static final GenericGF AZTEC_DATA_12 = new GenericGF(0x1069, 4096, 1); // x^12 + x^6 + x^5 + x^3 + 1
+  public static final GenericGF AZTEC_DATA_10 = new GenericGF(0x409, 1024, 1); // x^10 + x^3 + 1
+  public static final GenericGF AZTEC_DATA_6 = new GenericGF(0x43, 64, 1); // x^6 + x + 1
+  public static final GenericGF AZTEC_PARAM = new GenericGF(0x13, 16, 1); // x^4 + x + 1
+  public static final GenericGF QR_CODE_FIELD_256 = new GenericGF(0x011D, 256, 0); // x^8 + x^4 + x^3 + x^2 + 1
+  public static final GenericGF DATA_MATRIX_FIELD_256 = new GenericGF(0x012D, 256, 1); // x^8 + x^5 + x^3 + x^2 + 1
+  public static final GenericGF AZTEC_DATA_8 = DATA_MATRIX_FIELD_256;
+  public static final GenericGF MAXICODE_FIELD_64 = AZTEC_DATA_6;
+
+  private static final int INITIALIZATION_THRESHOLD = 0;
+
+  private int[] expTable;
+  private int[] logTable;
+  private GenericGFPoly zero;
+  private GenericGFPoly one;
+  private final int size;
+  private final int primitive;
+  private final int generatorBase;
+  private boolean initialized = false;
+
+  /**
+   * Create a representation of GF(size) using the given primitive polynomial.
+   *
+   * @param primitive irreducible polynomial whose coefficients are represented by
+   *  the bits of an int, where the least-significant bit represents the constant
+   *  coefficient
+   * @param size the size of the field
+   * @param b the factor b in the generator polynomial can be 0- or 1-based
+   *  (g(x) = (x+a^b)(x+a^(b+1))...(x+a^(b+2t-1))).
+   *  In most cases it should be 1, but for QR code it is 0.
+   */
+  public GenericGF(int primitive, int size, int b) {
+       this.primitive = primitive;
+    this.size = size;
+    this.generatorBase = b;
+    
+    if (size <= INITIALIZATION_THRESHOLD) {
+       initialize();
+    }
+  }
+
+  private void initialize() {
+    expTable = new int[size];
+    logTable = new int[size];
+    int x = 1;
+    for (int i = 0; i < size; i++) {
+      expTable[i] = x;
+      x <<= 1; // x = x * 2; we're assuming the generator alpha is 2
+      if (x >= size) {
+        x ^= primitive;
+        x &= size-1;
+      }
+    }
+    for (int i = 0; i < size-1; i++) {
+      logTable[expTable[i]] = i;
+    }
+    // logTable[0] == 0 but this should never be used
+    zero = new GenericGFPoly(this, new int[]{0});
+    one = new GenericGFPoly(this, new int[]{1});
+    initialized = true;
+  }
+  
+  private void checkInit() {
+       if (!initialized) {
+      initialize();
+    }
+  }
+  
+  GenericGFPoly getZero() {
+       checkInit();
+       
+    return zero;
+  }
+
+  GenericGFPoly getOne() {
+       checkInit();
+       
+    return one;
+  }
+
+  /**
+   * @return the monomial representing coefficient * x^degree
+   */
+  GenericGFPoly buildMonomial(int degree, int coefficient) {
+       checkInit();
+       
+    if (degree < 0) {
+      throw new IllegalArgumentException();
+    }
+    if (coefficient == 0) {
+      return zero;
+    }
+    int[] coefficients = new int[degree + 1];
+    coefficients[0] = coefficient;
+    return new GenericGFPoly(this, coefficients);
+  }
+
+  /**
+   * Implements both addition and subtraction -- they are the same in GF(size).
+   *
+   * @return sum/difference of a and b
+   */
+  static int addOrSubtract(int a, int b) {
+    return a ^ b;
+  }
+
+  /**
+   * @return 2 to the power of a in GF(size)
+   */
+  int exp(int a) {
+       checkInit();
+       
+    return expTable[a];
+  }
+
+  /**
+   * @return base 2 log of a in GF(size)
+   */
+  int log(int a) {
+       checkInit();
+       
+    if (a == 0) {
+      throw new IllegalArgumentException();
+    }
+    return logTable[a];
+  }
+
+  /**
+   * @return multiplicative inverse of a
+   */
+  int inverse(int a) {
+       checkInit();
+       
+    if (a == 0) {
+      throw new ArithmeticException();
+    }
+    return expTable[size - logTable[a] - 1];
+  }
+
+  /**
+   * @return product of a and b in GF(size)
+   */
+  int multiply(int a, int b) {
+       checkInit();
+
+    if (a == 0 || b == 0) {
+      return 0;
+    }
+    return expTable[(logTable[a] + logTable[b]) % (size - 1)];
+  }
+
+  public int getSize() {
+       return size;
+  }
+  
+  public int getGeneratorBase() {
+    return generatorBase;
+  }
+  
+  @Override
+  public String toString() {
+    return "GF(0x" + Integer.toHexString(primitive) + ',' + size + ')';
+  }
+  
+}
diff --git a/src/com/google/zxing/common/reedsolomon/GenericGFPoly.java b/src/com/google/zxing/common/reedsolomon/GenericGFPoly.java
new file mode 100644 (file)
index 0000000..1ccc035
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2007 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.common.reedsolomon;
+
+/**
+ * <p>Represents a polynomial whose coefficients are elements of a GF.
+ * Instances of this class are immutable.</p>
+ *
+ * <p>Much credit is due to William Rucklidge since portions of this code are an indirect
+ * port of his C++ Reed-Solomon implementation.</p>
+ *
+ * @author Sean Owen
+ */
+final class GenericGFPoly {
+
+  private final GenericGF field;
+  private final int[] coefficients;
+
+  /**
+   * @param field the {@link GenericGF} instance representing the field to use
+   * to perform computations
+   * @param coefficients coefficients as ints representing elements of GF(size), arranged
+   * from most significant (highest-power term) coefficient to least significant
+   * @throws IllegalArgumentException if argument is null or empty,
+   * or if leading coefficient is 0 and this is not a
+   * constant polynomial (that is, it is not the monomial "0")
+   */
+  GenericGFPoly(GenericGF field, int[] coefficients) {
+    if (coefficients.length == 0) {
+      throw new IllegalArgumentException();
+    }
+    this.field = field;
+    int coefficientsLength = coefficients.length;
+    if (coefficientsLength > 1 && coefficients[0] == 0) {
+      // Leading term must be non-zero for anything except the constant polynomial "0"
+      int firstNonZero = 1;
+      while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0) {
+        firstNonZero++;
+      }
+      if (firstNonZero == coefficientsLength) {
+        this.coefficients = field.getZero().coefficients;
+      } else {
+        this.coefficients = new int[coefficientsLength - firstNonZero];
+        System.arraycopy(coefficients,
+            firstNonZero,
+            this.coefficients,
+            0,
+            this.coefficients.length);
+      }
+    } else {
+      this.coefficients = coefficients;
+    }
+  }
+
+  int[] getCoefficients() {
+    return coefficients;
+  }
+
+  /**
+   * @return degree of this polynomial
+   */
+  int getDegree() {
+    return coefficients.length - 1;
+  }
+
+  /**
+   * @return true iff this polynomial is the monomial "0"
+   */
+  boolean isZero() {
+    return coefficients[0] == 0;
+  }
+
+  /**
+   * @return coefficient of x^degree term in this polynomial
+   */
+  int getCoefficient(int degree) {
+    return coefficients[coefficients.length - 1 - degree];
+  }
+
+  /**
+   * @return evaluation of this polynomial at a given point
+   */
+  int evaluateAt(int a) {
+    if (a == 0) {
+      // Just return the x^0 coefficient
+      return getCoefficient(0);
+    }
+    int size = coefficients.length;
+    if (a == 1) {
+      // Just the sum of the coefficients
+      int result = 0;
+      for (int coefficient : coefficients) {
+        result = GenericGF.addOrSubtract(result, coefficient);
+      }
+      return result;
+    }
+    int result = coefficients[0];
+    for (int i = 1; i < size; i++) {
+      result = GenericGF.addOrSubtract(field.multiply(a, result), coefficients[i]);
+    }
+    return result;
+  }
+
+  GenericGFPoly addOrSubtract(GenericGFPoly other) {
+    if (!field.equals(other.field)) {
+      throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field");
+    }
+    if (isZero()) {
+      return other;
+    }
+    if (other.isZero()) {
+      return this;
+    }
+
+    int[] smallerCoefficients = this.coefficients;
+    int[] largerCoefficients = other.coefficients;
+    if (smallerCoefficients.length > largerCoefficients.length) {
+      int[] temp = smallerCoefficients;
+      smallerCoefficients = largerCoefficients;
+      largerCoefficients = temp;
+    }
+    int[] sumDiff = new int[largerCoefficients.length];
+    int lengthDiff = largerCoefficients.length - smallerCoefficients.length;
+    // Copy high-order terms only found in higher-degree polynomial's coefficients
+    System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff);
+
+    for (int i = lengthDiff; i < largerCoefficients.length; i++) {
+      sumDiff[i] = GenericGF.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
+    }
+
+    return new GenericGFPoly(field, sumDiff);
+  }
+
+  GenericGFPoly multiply(GenericGFPoly other) {
+    if (!field.equals(other.field)) {
+      throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field");
+    }
+    if (isZero() || other.isZero()) {
+      return field.getZero();
+    }
+    int[] aCoefficients = this.coefficients;
+    int aLength = aCoefficients.length;
+    int[] bCoefficients = other.coefficients;
+    int bLength = bCoefficients.length;
+    int[] product = new int[aLength + bLength - 1];
+    for (int i = 0; i < aLength; i++) {
+      int aCoeff = aCoefficients[i];
+      for (int j = 0; j < bLength; j++) {
+        product[i + j] = GenericGF.addOrSubtract(product[i + j],
+            field.multiply(aCoeff, bCoefficients[j]));
+      }
+    }
+    return new GenericGFPoly(field, product);
+  }
+
+  GenericGFPoly multiply(int scalar) {
+    if (scalar == 0) {
+      return field.getZero();
+    }
+    if (scalar == 1) {
+      return this;
+    }
+    int size = coefficients.length;
+    int[] product = new int[size];
+    for (int i = 0; i < size; i++) {
+      product[i] = field.multiply(coefficients[i], scalar);
+    }
+    return new GenericGFPoly(field, product);
+  }
+
+  GenericGFPoly multiplyByMonomial(int degree, int coefficient) {
+    if (degree < 0) {
+      throw new IllegalArgumentException();
+    }
+    if (coefficient == 0) {
+      return field.getZero();
+    }
+    int size = coefficients.length;
+    int[] product = new int[size + degree];
+    for (int i = 0; i < size; i++) {
+      product[i] = field.multiply(coefficients[i], coefficient);
+    }
+    return new GenericGFPoly(field, product);
+  }
+
+  GenericGFPoly[] divide(GenericGFPoly other) {
+    if (!field.equals(other.field)) {
+      throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field");
+    }
+    if (other.isZero()) {
+      throw new IllegalArgumentException("Divide by 0");
+    }
+
+    GenericGFPoly quotient = field.getZero();
+    GenericGFPoly remainder = this;
+
+    int denominatorLeadingTerm = other.getCoefficient(other.getDegree());
+    int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm);
+
+    while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) {
+      int degreeDifference = remainder.getDegree() - other.getDegree();
+      int scale = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm);
+      GenericGFPoly term = other.multiplyByMonomial(degreeDifference, scale);
+      GenericGFPoly iterationQuotient = field.buildMonomial(degreeDifference, scale);
+      quotient = quotient.addOrSubtract(iterationQuotient);
+      remainder = remainder.addOrSubtract(term);
+    }
+
+    return new GenericGFPoly[] { quotient, remainder };
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder result = new StringBuilder(8 * getDegree());
+    for (int degree = getDegree(); degree >= 0; degree--) {
+      int coefficient = getCoefficient(degree);
+      if (coefficient != 0) {
+        if (coefficient < 0) {
+          result.append(" - ");
+          coefficient = -coefficient;
+        } else {
+          if (result.length() > 0) {
+            result.append(" + ");
+          }
+        }
+        if (degree == 0 || coefficient != 1) {
+          int alphaPower = field.log(coefficient);
+          if (alphaPower == 0) {
+            result.append('1');
+          } else if (alphaPower == 1) {
+            result.append('a');
+          } else {
+            result.append("a^");
+            result.append(alphaPower);
+          }
+        }
+        if (degree != 0) {
+          if (degree == 1) {
+            result.append('x');
+          } else {
+            result.append("x^");
+            result.append(degree);
+          }
+        }
+      }
+    }
+    return result.toString();
+  }
+
+}
diff --git a/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java b/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java
new file mode 100644 (file)
index 0000000..19127e0
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2007 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.common.reedsolomon;
+
+/**
+ * <p>Implements Reed-Solomon decoding, as the name implies.</p>
+ *
+ * <p>The algorithm will not be explained here, but the following references were helpful
+ * in creating this implementation:</p>
+ *
+ * <ul>
+ * <li>Bruce Maggs.
+ * <a href="http://www.cs.cmu.edu/afs/cs.cmu.edu/project/pscico-guyb/realworld/www/rs_decode.ps">
+ * "Decoding Reed-Solomon Codes"</a> (see discussion of Forney's Formula)</li>
+ * <li>J.I. Hall. <a href="www.mth.msu.edu/~jhall/classes/codenotes/GRS.pdf">
+ * "Chapter 5. Generalized Reed-Solomon Codes"</a>
+ * (see discussion of Euclidean algorithm)</li>
+ * </ul>
+ *
+ * <p>Much credit is due to William Rucklidge since portions of this code are an indirect
+ * port of his C++ Reed-Solomon implementation.</p>
+ *
+ * @author Sean Owen
+ * @author William Rucklidge
+ * @author sanfordsquires
+ */
+public final class ReedSolomonDecoder {
+
+  private final GenericGF field;
+
+  public ReedSolomonDecoder(GenericGF field) {
+    this.field = field;
+  }
+
+  /**
+   * <p>Decodes given set of received codewords, which include both data and error-correction
+   * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place,
+   * in the input.</p>
+   *
+   * @param received data and error-correction codewords
+   * @param twoS number of error-correction codewords available
+   * @throws ReedSolomonException if decoding fails for any reason
+   */
+  public void decode(int[] received, int twoS) throws ReedSolomonException {
+    GenericGFPoly poly = new GenericGFPoly(field, received);
+    int[] syndromeCoefficients = new int[twoS];
+    boolean noError = true;
+    for (int i = 0; i < twoS; i++) {
+      int eval = poly.evaluateAt(field.exp(i + field.getGeneratorBase()));
+      syndromeCoefficients[syndromeCoefficients.length - 1 - i] = eval;
+      if (eval != 0) {
+        noError = false;
+      }
+    }
+    if (noError) {
+      return;
+    }
+    GenericGFPoly syndrome = new GenericGFPoly(field, syndromeCoefficients);
+    GenericGFPoly[] sigmaOmega =
+        runEuclideanAlgorithm(field.buildMonomial(twoS, 1), syndrome, twoS);
+    GenericGFPoly sigma = sigmaOmega[0];
+    GenericGFPoly omega = sigmaOmega[1];
+    int[] errorLocations = findErrorLocations(sigma);
+    int[] errorMagnitudes = findErrorMagnitudes(omega, errorLocations);
+    for (int i = 0; i < errorLocations.length; i++) {
+      int position = received.length - 1 - field.log(errorLocations[i]);
+      if (position < 0) {
+        throw new ReedSolomonException("Bad error location");
+      }
+      received[position] = GenericGF.addOrSubtract(received[position], errorMagnitudes[i]);
+    }
+  }
+
+  private GenericGFPoly[] runEuclideanAlgorithm(GenericGFPoly a, GenericGFPoly b, int R)
+      throws ReedSolomonException {
+    // Assume a's degree is >= b's
+    if (a.getDegree() < b.getDegree()) {
+      GenericGFPoly temp = a;
+      a = b;
+      b = temp;
+    }
+
+    GenericGFPoly rLast = a;
+    GenericGFPoly r = b;
+    GenericGFPoly tLast = field.getZero();
+    GenericGFPoly t = field.getOne();
+
+    // Run Euclidean algorithm until r's degree is less than R/2
+    while (r.getDegree() >= R / 2) {
+      GenericGFPoly rLastLast = rLast;
+      GenericGFPoly tLastLast = tLast;
+      rLast = r;
+      tLast = t;
+
+      // Divide rLastLast by rLast, with quotient in q and remainder in r
+      if (rLast.isZero()) {
+        // Oops, Euclidean algorithm already terminated?
+        throw new ReedSolomonException("r_{i-1} was zero");
+      }
+      r = rLastLast;
+      GenericGFPoly q = field.getZero();
+      int denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree());
+      int dltInverse = field.inverse(denominatorLeadingTerm);
+      while (r.getDegree() >= rLast.getDegree() && !r.isZero()) {
+        int degreeDiff = r.getDegree() - rLast.getDegree();
+        int scale = field.multiply(r.getCoefficient(r.getDegree()), dltInverse);
+        q = q.addOrSubtract(field.buildMonomial(degreeDiff, scale));
+        r = r.addOrSubtract(rLast.multiplyByMonomial(degreeDiff, scale));
+      }
+
+      t = q.multiply(tLast).addOrSubtract(tLastLast);
+      
+      if (r.getDegree() >= rLast.getDegree()) {
+        throw new IllegalStateException("Division algorithm failed to reduce polynomial?");
+      }
+    }
+
+    int sigmaTildeAtZero = t.getCoefficient(0);
+    if (sigmaTildeAtZero == 0) {
+      throw new ReedSolomonException("sigmaTilde(0) was zero");
+    }
+
+    int inverse = field.inverse(sigmaTildeAtZero);
+    GenericGFPoly sigma = t.multiply(inverse);
+    GenericGFPoly omega = r.multiply(inverse);
+    return new GenericGFPoly[]{sigma, omega};
+  }
+
+  private int[] findErrorLocations(GenericGFPoly errorLocator) throws ReedSolomonException {
+    // This is a direct application of Chien's search
+    int numErrors = errorLocator.getDegree();
+    if (numErrors == 1) { // shortcut
+      return new int[] { errorLocator.getCoefficient(1) };
+    }
+    int[] result = new int[numErrors];
+    int e = 0;
+    for (int i = 1; i < field.getSize() && e < numErrors; i++) {
+      if (errorLocator.evaluateAt(i) == 0) {
+        result[e] = field.inverse(i);
+        e++;
+      }
+    }
+    if (e != numErrors) {
+      throw new ReedSolomonException("Error locator degree does not match number of roots");
+    }
+    return result;
+  }
+
+  private int[] findErrorMagnitudes(GenericGFPoly errorEvaluator, int[] errorLocations) {
+    // This is directly applying Forney's Formula
+    int s = errorLocations.length;
+    int[] result = new int[s];
+    for (int i = 0; i < s; i++) {
+      int xiInverse = field.inverse(errorLocations[i]);
+      int denominator = 1;
+      for (int j = 0; j < s; j++) {
+        if (i != j) {
+          //denominator = field.multiply(denominator,
+          //    GenericGF.addOrSubtract(1, field.multiply(errorLocations[j], xiInverse)));
+          // Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug.
+          // Below is a funny-looking workaround from Steven Parkes
+          int term = field.multiply(errorLocations[j], xiInverse);
+          int termPlus1 = (term & 0x1) == 0 ? term | 1 : term & ~1;
+          denominator = field.multiply(denominator, termPlus1);
+        }
+      }
+      result[i] = field.multiply(errorEvaluator.evaluateAt(xiInverse),
+          field.inverse(denominator));
+      if (field.getGeneratorBase() != 0) {
+        result[i] = field.multiply(result[i], xiInverse);
+      }
+    }
+    return result;
+  }
+
+}
diff --git a/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java b/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java
new file mode 100644 (file)
index 0000000..d5b45a6
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2007 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.common.reedsolomon;
+
+/**
+ * <p>Thrown when an exception occurs during Reed-Solomon decoding, such as when
+ * there are too many errors to correct.</p>
+ *
+ * @author Sean Owen
+ */
+public final class ReedSolomonException extends Exception {
+
+  public ReedSolomonException(String message) {
+    super(message);
+  }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/datamatrix/decoder/Version.java b/src/com/google/zxing/datamatrix/decoder/Version.java
new file mode 100644 (file)
index 0000000..fff9e17
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2007 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.datamatrix.decoder;
+
+import com.google.zxing.FormatException;
+
+/**
+ * The Version object encapsulates attributes about a particular
+ * size Data Matrix Code.
+ *
+ * @author bbrown@google.com (Brian Brown)
+ */
+public final class Version {
+
+  private static final Version[] VERSIONS = buildVersions();
+
+  private final int versionNumber;
+  private final int symbolSizeRows;
+  private final int symbolSizeColumns;
+  private final int dataRegionSizeRows;
+  private final int dataRegionSizeColumns;
+  private final ECBlocks ecBlocks;
+  private final int totalCodewords;
+
+  private Version(int versionNumber,
+                  int symbolSizeRows,
+                  int symbolSizeColumns,
+                  int dataRegionSizeRows,
+                  int dataRegionSizeColumns,
+                  ECBlocks ecBlocks) {
+    this.versionNumber = versionNumber;
+    this.symbolSizeRows = symbolSizeRows;
+    this.symbolSizeColumns = symbolSizeColumns;
+    this.dataRegionSizeRows = dataRegionSizeRows;
+    this.dataRegionSizeColumns = dataRegionSizeColumns;
+    this.ecBlocks = ecBlocks;
+    
+    // Calculate the total number of codewords
+    int total = 0;
+    int ecCodewords = ecBlocks.getECCodewords();
+    ECB[] ecbArray = ecBlocks.getECBlocks();
+    for (ECB ecBlock : ecbArray) {
+      total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords);
+    }
+    this.totalCodewords = total;
+  }
+
+  public int getVersionNumber() {
+    return versionNumber;
+  }
+
+  public int getSymbolSizeRows() {
+    return symbolSizeRows;
+  }
+  
+  public int getSymbolSizeColumns() {
+    return symbolSizeColumns;
+  }
+  
+  public int getDataRegionSizeRows() {
+    return dataRegionSizeRows;
+  }
+  
+  public int getDataRegionSizeColumns() {
+    return dataRegionSizeColumns;
+  }
+  
+  public int getTotalCodewords() {
+    return totalCodewords;
+  }
+  
+  ECBlocks getECBlocks() {
+    return ecBlocks;
+  }
+
+  /**
+   * <p>Deduces version information from Data Matrix dimensions.</p>
+   *
+   * @param numRows Number of rows in modules
+   * @param numColumns Number of columns in modules
+   * @return Version for a Data Matrix Code of those dimensions
+   * @throws FormatException if dimensions do correspond to a valid Data Matrix size
+   */
+  public static Version getVersionForDimensions(int numRows, int numColumns) throws FormatException {
+    if ((numRows & 0x01) != 0 || (numColumns & 0x01) != 0) {
+      throw FormatException.getFormatInstance();
+    }
+
+    for (Version version : VERSIONS) {
+      if (version.symbolSizeRows == numRows && version.symbolSizeColumns == numColumns) {
+        return version;
+      }
+    }
+    
+    throw FormatException.getFormatInstance();
+  }
+
+  /**
+   * <p>Encapsulates a set of error-correction blocks in one symbol version. Most versions will
+   * use blocks of differing sizes within one version, so, this encapsulates the parameters for
+   * each set of blocks. It also holds the number of error-correction codewords per block since it
+   * will be the same across all blocks within one version.</p>
+   */
+  static final class ECBlocks {
+    private final int ecCodewords;
+    private final ECB[] ecBlocks;
+
+    private ECBlocks(int ecCodewords, ECB ecBlocks) {
+      this.ecCodewords = ecCodewords;
+      this.ecBlocks = new ECB[] { ecBlocks };
+    }
+
+    private ECBlocks(int ecCodewords, ECB ecBlocks1, ECB ecBlocks2) {
+      this.ecCodewords = ecCodewords;
+      this.ecBlocks = new ECB[] { ecBlocks1, ecBlocks2 };
+    }
+
+    int getECCodewords() {
+      return ecCodewords;
+    }
+
+    ECB[] getECBlocks() {
+      return ecBlocks;
+    }
+  }
+
+  /**
+   * <p>Encapsualtes the parameters for one error-correction block in one symbol version.
+   * This includes the number of data codewords, and the number of times a block with these
+   * parameters is used consecutively in the Data Matrix code version's format.</p>
+   */
+  static final class ECB {
+    private final int count;
+    private final int dataCodewords;
+
+    private ECB(int count, int dataCodewords) {
+      this.count = count;
+      this.dataCodewords = dataCodewords;
+    }
+
+    int getCount() {
+      return count;
+    }
+
+    int getDataCodewords() {
+      return dataCodewords;
+    }
+  }
+
+  @Override
+  public String toString() {
+    return String.valueOf(versionNumber);
+  }
+
+  /**
+   * See ISO 16022:2006 5.5.1 Table 7
+   */
+  private static Version[] buildVersions() {
+    return new Version[]{
+        new Version(1, 10, 10, 8, 8,
+            new ECBlocks(5, new ECB(1, 3))),
+        new Version(2, 12, 12, 10, 10,
+            new ECBlocks(7, new ECB(1, 5))),
+        new Version(3, 14, 14, 12, 12,
+            new ECBlocks(10, new ECB(1, 8))),
+        new Version(4, 16, 16, 14, 14,
+            new ECBlocks(12, new ECB(1, 12))),
+        new Version(5, 18, 18, 16, 16,
+            new ECBlocks(14, new ECB(1, 18))),
+        new Version(6, 20, 20, 18, 18,
+            new ECBlocks(18, new ECB(1, 22))),
+        new Version(7, 22, 22, 20, 20,
+            new ECBlocks(20, new ECB(1, 30))),
+        new Version(8, 24, 24, 22, 22,
+            new ECBlocks(24, new ECB(1, 36))),
+        new Version(9, 26, 26, 24, 24,
+            new ECBlocks(28, new ECB(1, 44))),
+        new Version(10, 32, 32, 14, 14,
+            new ECBlocks(36, new ECB(1, 62))),
+        new Version(11, 36, 36, 16, 16,
+            new ECBlocks(42, new ECB(1, 86))),
+        new Version(12, 40, 40, 18, 18,
+            new ECBlocks(48, new ECB(1, 114))),
+        new Version(13, 44, 44, 20, 20,
+            new ECBlocks(56, new ECB(1, 144))),
+        new Version(14, 48, 48, 22, 22,
+            new ECBlocks(68, new ECB(1, 174))),
+        new Version(15, 52, 52, 24, 24,
+            new ECBlocks(42, new ECB(2, 102))),
+        new Version(16, 64, 64, 14, 14,
+            new ECBlocks(56, new ECB(2, 140))),
+        new Version(17, 72, 72, 16, 16,
+            new ECBlocks(36, new ECB(4, 92))),
+        new Version(18, 80, 80, 18, 18,
+            new ECBlocks(48, new ECB(4, 114))),
+        new Version(19, 88, 88, 20, 20,
+            new ECBlocks(56, new ECB(4, 144))),
+        new Version(20, 96, 96, 22, 22,
+            new ECBlocks(68, new ECB(4, 174))),
+        new Version(21, 104, 104, 24, 24,
+            new ECBlocks(56, new ECB(6, 136))),
+        new Version(22, 120, 120, 18, 18,
+            new ECBlocks(68, new ECB(6, 175))),
+        new Version(23, 132, 132, 20, 20,
+            new ECBlocks(62, new ECB(8, 163))),
+        new Version(24, 144, 144, 22, 22,
+            new ECBlocks(62, new ECB(8, 156), new ECB(2, 155))),
+        new Version(25, 8, 18, 6, 16,
+            new ECBlocks(7, new ECB(1, 5))),
+        new Version(26, 8, 32, 6, 14,
+            new ECBlocks(11, new ECB(1, 10))),
+        new Version(27, 12, 26, 10, 24,
+            new ECBlocks(14, new ECB(1, 16))),
+        new Version(28, 12, 36, 10, 16,
+            new ECBlocks(18, new ECB(1, 22))),
+        new Version(29, 16, 36, 14, 16,
+            new ECBlocks(24, new ECB(1, 32))),
+        new Version(30, 16, 48, 14, 22,
+            new ECBlocks(28, new ECB(1, 49)))
+    };
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/QRCodeReader.java b/src/com/google/zxing/qrcode/QRCodeReader.java
new file mode 100644 (file)
index 0000000..3cb795b
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2007 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.qrcode;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.ChecksumException;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.Reader;
+import com.google.zxing.Result;
+import com.google.zxing.ResultMetadataType;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.qrcode.decoder.Decoder;
+import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData;
+import com.google.zxing.qrcode.detector.Detector;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This implementation can detect and decode QR Codes in an image.
+ *
+ * @author Sean Owen
+ */
+public class QRCodeReader implements Reader {
+
+  private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
+
+  private final Decoder decoder = new Decoder();
+
+  protected final Decoder getDecoder() {
+    return decoder;
+  }
+
+  /**
+   * Locates and decodes a QR code in an image.
+   *
+   * @return a String representing the content encoded by the QR code
+   * @throws NotFoundException if a QR code cannot be found
+   * @throws FormatException if a QR code cannot be decoded
+   * @throws ChecksumException if error correction fails
+   */
+  @Override
+  public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException {
+    return decode(image, null);
+  }
+
+  @Override
+  public final Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
+      throws NotFoundException, ChecksumException, FormatException {
+    DecoderResult decoderResult;
+    ResultPoint[] points;
+    if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
+      BitMatrix bits = extractPureBits(image.getBlackMatrix());
+      decoderResult = decoder.decode(bits, hints);
+      points = NO_POINTS;
+    } else {
+      DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
+      decoderResult = decoder.decode(detectorResult.getBits(), hints);
+      points = detectorResult.getPoints();
+    }
+
+    // If the code was mirrored: swap the bottom-left and the top-right points.
+    if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
+      ((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
+    }
+
+    Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE);
+    List<byte[]> byteSegments = decoderResult.getByteSegments();
+    if (byteSegments != null) {
+      result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
+    }
+    String ecLevel = decoderResult.getECLevel();
+    if (ecLevel != null) {
+      result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
+    }
+    return result;
+  }
+
+  @Override
+  public void reset() {
+    // do nothing
+  }
+
+  /**
+   * This method detects a code in a "pure" image -- that is, pure monochrome image
+   * which contains only an unrotated, unskewed, image of a code, with some white border
+   * around it. This is a specialized method that works exceptionally fast in this special
+   * case.
+   *
+   * @see com.google.zxing.datamatrix.DataMatrixReader#extractPureBits(BitMatrix)
+   */
+  private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException {
+
+    int[] leftTopBlack = image.getTopLeftOnBit();
+    int[] rightBottomBlack = image.getBottomRightOnBit();
+    if (leftTopBlack == null || rightBottomBlack == null) {
+      throw NotFoundException.getNotFoundInstance();
+    }
+
+    float moduleSize = moduleSize(leftTopBlack, image);
+
+    int top = leftTopBlack[1];
+    int bottom = rightBottomBlack[1];
+    int left = leftTopBlack[0];
+    int right = rightBottomBlack[0];
+    
+    // Sanity check!
+    if (left >= right || top >= bottom) {
+      throw NotFoundException.getNotFoundInstance();
+    }
+
+    if (bottom - top != right - left) {
+      // Special case, where bottom-right module wasn't black so we found something else in the last row
+      // Assume it's a square, so use height as the width
+      right = left + (bottom - top);
+    }
+
+    int matrixWidth = Math.round((right - left + 1) / moduleSize);
+    int matrixHeight = Math.round((bottom - top + 1) / moduleSize);
+    if (matrixWidth <= 0 || matrixHeight <= 0) {
+      throw NotFoundException.getNotFoundInstance();
+    }
+    if (matrixHeight != matrixWidth) {
+      // Only possibly decode square regions
+      throw NotFoundException.getNotFoundInstance();
+    }
+
+    // Push in the "border" by half the module width so that we start
+    // sampling in the middle of the module. Just in case the image is a
+    // little off, this will help recover.
+    int nudge = (int) (moduleSize / 2.0f);
+    top += nudge;
+    left += nudge;
+    
+    // But careful that this does not sample off the edge
+    int nudgedTooFarRight = left + (int) ((matrixWidth - 1) * moduleSize) - (right - 1);
+    if (nudgedTooFarRight > 0) {
+      if (nudgedTooFarRight > nudge) {
+        // Neither way fits; abort
+        throw NotFoundException.getNotFoundInstance();
+      }
+      left -= nudgedTooFarRight;
+    }
+    int nudgedTooFarDown = top + (int) ((matrixHeight - 1) * moduleSize) - (bottom - 1);
+    if (nudgedTooFarDown > 0) {
+      if (nudgedTooFarDown > nudge) {
+        // Neither way fits; abort
+        throw NotFoundException.getNotFoundInstance();
+      }
+      top -= nudgedTooFarDown;
+    }
+
+    // Now just read off the bits
+    BitMatrix bits = new BitMatrix(matrixWidth, matrixHeight);
+    for (int y = 0; y < matrixHeight; y++) {
+      int iOffset = top + (int) (y * moduleSize);
+      for (int x = 0; x < matrixWidth; x++) {
+        if (image.get(left + (int) (x * moduleSize), iOffset)) {
+          bits.set(x, y);
+        }
+      }
+    }
+    return bits;
+  }
+
+  private static float moduleSize(int[] leftTopBlack, BitMatrix image) throws NotFoundException {
+    int height = image.getHeight();
+    int width = image.getWidth();
+    int x = leftTopBlack[0];
+    int y = leftTopBlack[1];
+    boolean inBlack = true;
+    int transitions = 0;
+    while (x < width && y < height) {
+      if (inBlack != image.get(x, y)) {
+        if (++transitions == 5) {
+          break;
+        }
+        inBlack = !inBlack;
+      }
+      x++;
+      y++;
+    }
+    if (x == width || y == height) {
+      throw NotFoundException.getNotFoundInstance();
+    }
+    return (x - leftTopBlack[0]) / 7.0f;
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java b/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java
new file mode 100644 (file)
index 0000000..7d3d949
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * @author Sean Owen
+ */
+final class BitMatrixParser {
+
+  private final BitMatrix bitMatrix;
+  private Version parsedVersion;
+  private FormatInformation parsedFormatInfo;
+  private boolean mirror;
+
+  /**
+   * @param bitMatrix {@link BitMatrix} to parse
+   * @throws FormatException if dimension is not >= 21 and 1 mod 4
+   */
+  BitMatrixParser(BitMatrix bitMatrix) throws FormatException {
+    int dimension = bitMatrix.getHeight();
+    if (dimension < 21 || (dimension & 0x03) != 1) {
+      throw FormatException.getFormatInstance();
+    }
+    this.bitMatrix = bitMatrix;
+  }
+
+  /**
+   * <p>Reads format information from one of its two locations within the QR Code.</p>
+   *
+   * @return {@link FormatInformation} encapsulating the QR Code's format info
+   * @throws FormatException if both format information locations cannot be parsed as
+   * the valid encoding of format information
+   */
+  FormatInformation readFormatInformation() throws FormatException {
+
+    if (parsedFormatInfo != null) {
+      return parsedFormatInfo;
+    }
+
+    // Read top-left format info bits
+    int formatInfoBits1 = 0;
+    for (int i = 0; i < 6; i++) {
+      formatInfoBits1 = copyBit(i, 8, formatInfoBits1);
+    }
+    // .. and skip a bit in the timing pattern ...
+    formatInfoBits1 = copyBit(7, 8, formatInfoBits1);
+    formatInfoBits1 = copyBit(8, 8, formatInfoBits1);
+    formatInfoBits1 = copyBit(8, 7, formatInfoBits1);
+    // .. and skip a bit in the timing pattern ...
+    for (int j = 5; j >= 0; j--) {
+      formatInfoBits1 = copyBit(8, j, formatInfoBits1);
+    }
+
+    // Read the top-right/bottom-left pattern too
+    int dimension = bitMatrix.getHeight();
+    int formatInfoBits2 = 0;
+    int jMin = dimension - 7;
+    for (int j = dimension - 1; j >= jMin; j--) {
+      formatInfoBits2 = copyBit(8, j, formatInfoBits2);
+    }
+    for (int i = dimension - 8; i < dimension; i++) {
+      formatInfoBits2 = copyBit(i, 8, formatInfoBits2);
+    }
+
+    parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits1, formatInfoBits2);
+    if (parsedFormatInfo != null) {
+      return parsedFormatInfo;
+    }
+    throw FormatException.getFormatInstance();
+  }
+
+  /**
+   * <p>Reads version information from one of its two locations within the QR Code.</p>
+   *
+   * @return {@link Version} encapsulating the QR Code's version
+   * @throws FormatException if both version information locations cannot be parsed as
+   * the valid encoding of version information
+   */
+  Version readVersion() throws FormatException {
+
+    if (parsedVersion != null) {
+      return parsedVersion;
+    }
+
+    int dimension = bitMatrix.getHeight();
+
+    int provisionalVersion = (dimension - 17) >> 2;
+    if (provisionalVersion <= 6) {
+      return Version.getVersionForNumber(provisionalVersion);
+    }
+
+    // Read top-right version info: 3 wide by 6 tall
+    int versionBits = 0;
+    int ijMin = dimension - 11;
+    for (int j = 5; j >= 0; j--) {
+      for (int i = dimension - 9; i >= ijMin; i--) {
+        versionBits = copyBit(i, j, versionBits);
+      }
+    }
+
+    Version theParsedVersion = Version.decodeVersionInformation(versionBits);
+    if (theParsedVersion != null && theParsedVersion.getDimensionForVersion() == dimension) {
+      parsedVersion = theParsedVersion;
+      return theParsedVersion;
+    }
+
+    // Hmm, failed. Try bottom left: 6 wide by 3 tall
+    versionBits = 0;
+    for (int i = 5; i >= 0; i--) {
+      for (int j = dimension - 9; j >= ijMin; j--) {
+        versionBits = copyBit(i, j, versionBits);
+      }
+    }
+
+    theParsedVersion = Version.decodeVersionInformation(versionBits);
+    if (theParsedVersion != null && theParsedVersion.getDimensionForVersion() == dimension) {
+      parsedVersion = theParsedVersion;
+      return theParsedVersion;
+    }
+    throw FormatException.getFormatInstance();
+  }
+
+  private int copyBit(int i, int j, int versionBits) {
+    boolean bit = mirror ? bitMatrix.get(j, i) : bitMatrix.get(i, j);
+    return bit ? (versionBits << 1) | 0x1 : versionBits << 1;
+  }
+
+  /**
+   * <p>Reads the bits in the {@link BitMatrix} representing the finder pattern in the
+   * correct order in order to reconstruct the codewords bytes contained within the
+   * QR Code.</p>
+   *
+   * @return bytes encoded within the QR Code
+   * @throws FormatException if the exact number of bytes expected is not read
+   */
+  byte[] readCodewords() throws FormatException {
+
+    FormatInformation formatInfo = readFormatInformation();
+    Version version = readVersion();
+
+    // Get the data mask for the format used in this QR Code. This will exclude
+    // some bits from reading as we wind through the bit matrix.
+    DataMask dataMask = DataMask.forReference(formatInfo.getDataMask());
+    int dimension = bitMatrix.getHeight();
+    dataMask.unmaskBitMatrix(bitMatrix, dimension);
+
+    BitMatrix functionPattern = version.buildFunctionPattern();
+
+    boolean readingUp = true;
+    byte[] result = new byte[version.getTotalCodewords()];
+    int resultOffset = 0;
+    int currentByte = 0;
+    int bitsRead = 0;
+    // Read columns in pairs, from right to left
+    for (int j = dimension - 1; j > 0; j -= 2) {
+      if (j == 6) {
+        // Skip whole column with vertical alignment pattern;
+        // saves time and makes the other code proceed more cleanly
+        j--;
+      }
+      // Read alternatingly from bottom to top then top to bottom
+      for (int count = 0; count < dimension; count++) {
+        int i = readingUp ? dimension - 1 - count : count;
+        for (int col = 0; col < 2; col++) {
+          // Ignore bits covered by the function pattern
+          if (!functionPattern.get(j - col, i)) {
+            // Read a bit
+            bitsRead++;
+            currentByte <<= 1;
+            if (bitMatrix.get(j - col, i)) {
+              currentByte |= 1;
+            }
+            // If we've made a whole byte, save it off
+            if (bitsRead == 8) {
+              result[resultOffset++] = (byte) currentByte;
+              bitsRead = 0;
+              currentByte = 0;
+            }
+          }
+        }
+      }
+      readingUp ^= true; // readingUp = !readingUp; // switch directions
+    }
+    if (resultOffset != version.getTotalCodewords()) {
+      throw FormatException.getFormatInstance();
+    }
+    return result;
+  }
+
+  /**
+   * Revert the mask removal done while reading the code words. The bit matrix should revert to its original state.
+   */
+  void remask() {
+    if (parsedFormatInfo == null) {
+      return; // We have no format information, and have no data mask
+    }
+    DataMask dataMask = DataMask.forReference(parsedFormatInfo.getDataMask());
+    int dimension = bitMatrix.getHeight();
+    dataMask.unmaskBitMatrix(bitMatrix, dimension);
+  }
+
+  /**
+   * Prepare the parser for a mirrored operation.
+   * This flag has effect only on the {@link #readFormatInformation()} and the
+   * {@link #readVersion()}. Before proceeding with {@link #readCodewords()} the
+   * {@link #mirror()} method should be called.
+   * 
+   * @param mirror Whether to read version and format information mirrored.
+   */
+  void setMirror(boolean mirror) {
+    parsedVersion = null;
+    parsedFormatInfo = null;
+    this.mirror = mirror;
+  }
+
+  /** Mirror the bit matrix in order to attempt a second reading. */
+  void mirror() {
+    for (int x = 0; x < bitMatrix.getWidth(); x++) {
+      for (int y = x + 1; y < bitMatrix.getHeight(); y++) {
+        if (bitMatrix.get(x, y) != bitMatrix.get(y, x)) {
+          bitMatrix.flip(y, x);
+          bitMatrix.flip(x, y);          
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/decoder/DataBlock.java b/src/com/google/zxing/qrcode/decoder/DataBlock.java
new file mode 100755 (executable)
index 0000000..8f5cdcb
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+/**
+ * <p>Encapsulates a block of data within a QR Code. QR Codes may split their data into
+ * multiple blocks, each of which is a unit of data and error-correction codewords. Each
+ * is represented by an instance of this class.</p>
+ *
+ * @author Sean Owen
+ */
+final class DataBlock {
+
+  private final int numDataCodewords;
+  private final byte[] codewords;
+
+  private DataBlock(int numDataCodewords, byte[] codewords) {
+    this.numDataCodewords = numDataCodewords;
+    this.codewords = codewords;
+  }
+
+  /**
+   * <p>When QR Codes use multiple data blocks, they are actually interleaved.
+   * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This
+   * method will separate the data into original blocks.</p>
+   *
+   * @param rawCodewords bytes as read directly from the QR Code
+   * @param version version of the QR Code
+   * @param ecLevel error-correction level of the QR Code
+   * @return DataBlocks containing original bytes, "de-interleaved" from representation in the
+   *         QR Code
+   */
+  static DataBlock[] getDataBlocks(byte[] rawCodewords,
+                                   Version version,
+                                   ErrorCorrectionLevel ecLevel) {
+
+    if (rawCodewords.length != version.getTotalCodewords()) {
+      throw new IllegalArgumentException();
+    }
+
+    // Figure out the number and size of data blocks used by this version and
+    // error correction level
+    Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
+
+    // First count the total number of data blocks
+    int totalBlocks = 0;
+    Version.ECB[] ecBlockArray = ecBlocks.getECBlocks();
+    for (Version.ECB ecBlock : ecBlockArray) {
+      totalBlocks += ecBlock.getCount();
+    }
+
+    // Now establish DataBlocks of the appropriate size and number of data codewords
+    DataBlock[] result = new DataBlock[totalBlocks];
+    int numResultBlocks = 0;
+    for (Version.ECB ecBlock : ecBlockArray) {
+      for (int i = 0; i < ecBlock.getCount(); i++) {
+        int numDataCodewords = ecBlock.getDataCodewords();
+        int numBlockCodewords = ecBlocks.getECCodewordsPerBlock() + numDataCodewords;
+        result[numResultBlocks++] = new DataBlock(numDataCodewords, new byte[numBlockCodewords]);
+      }
+    }
+
+    // All blocks have the same amount of data, except that the last n
+    // (where n may be 0) have 1 more byte. Figure out where these start.
+    int shorterBlocksTotalCodewords = result[0].codewords.length;
+    int longerBlocksStartAt = result.length - 1;
+    while (longerBlocksStartAt >= 0) {
+      int numCodewords = result[longerBlocksStartAt].codewords.length;
+      if (numCodewords == shorterBlocksTotalCodewords) {
+        break;
+      }
+      longerBlocksStartAt--;
+    }
+    longerBlocksStartAt++;
+
+    int shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.getECCodewordsPerBlock();
+    // The last elements of result may be 1 element longer;
+    // first fill out as many elements as all of them have
+    int rawCodewordsOffset = 0;
+    for (int i = 0; i < shorterBlocksNumDataCodewords; i++) {
+      for (int j = 0; j < numResultBlocks; j++) {
+        result[j].codewords[i] = rawCodewords[rawCodewordsOffset++];
+      }
+    }
+    // Fill out the last data block in the longer ones
+    for (int j = longerBlocksStartAt; j < numResultBlocks; j++) {
+      result[j].codewords[shorterBlocksNumDataCodewords] = rawCodewords[rawCodewordsOffset++];
+    }
+    // Now add in error correction blocks
+    int max = result[0].codewords.length;
+    for (int i = shorterBlocksNumDataCodewords; i < max; i++) {
+      for (int j = 0; j < numResultBlocks; j++) {
+        int iOffset = j < longerBlocksStartAt ? i : i + 1;
+        result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++];
+      }
+    }
+    return result;
+  }
+
+  int getNumDataCodewords() {
+    return numDataCodewords;
+  }
+
+  byte[] getCodewords() {
+    return codewords;
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/decoder/DataMask.java b/src/com/google/zxing/qrcode/decoder/DataMask.java
new file mode 100755 (executable)
index 0000000..353c1a8
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * <p>Encapsulates data masks for the data bits in a QR code, per ISO 18004:2006 6.8. Implementations
+ * of this class can un-mask a raw BitMatrix. For simplicity, they will unmask the entire BitMatrix,
+ * including areas used for finder patterns, timing patterns, etc. These areas should be unused
+ * after the point they are unmasked anyway.</p>
+ *
+ * <p>Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position
+ * and j is row position. In fact, as the text says, i is row position and j is column position.</p>
+ *
+ * @author Sean Owen
+ */
+abstract class DataMask {
+
+  /**
+   * See ISO 18004:2006 6.8.1
+   */
+  private static final DataMask[] DATA_MASKS = {
+      new DataMask000(),
+      new DataMask001(),
+      new DataMask010(),
+      new DataMask011(),
+      new DataMask100(),
+      new DataMask101(),
+      new DataMask110(),
+      new DataMask111(),
+  };
+
+  private DataMask() {
+  }
+
+  /**
+   * <p>Implementations of this method reverse the data masking process applied to a QR Code and
+   * make its bits ready to read.</p>
+   *
+   * @param bits representation of QR Code bits
+   * @param dimension dimension of QR Code, represented by bits, being unmasked
+   */
+  final void unmaskBitMatrix(BitMatrix bits, int dimension) {
+    for (int i = 0; i < dimension; i++) {
+      for (int j = 0; j < dimension; j++) {
+        if (isMasked(i, j)) {
+          bits.flip(j, i);
+        }
+      }
+    }
+  }
+
+  abstract boolean isMasked(int i, int j);
+
+  /**
+   * @param reference a value between 0 and 7 indicating one of the eight possible
+   * data mask patterns a QR Code may use
+   * @return DataMask encapsulating the data mask pattern
+   */
+  static DataMask forReference(int reference) {
+    if (reference < 0 || reference > 7) {
+      throw new IllegalArgumentException();
+    }
+    return DATA_MASKS[reference];
+  }
+
+  /**
+   * 000: mask bits for which (x + y) mod 2 == 0
+   */
+  private static final class DataMask000 extends DataMask {
+    @Override
+    boolean isMasked(int i, int j) {
+      return ((i + j) & 0x01) == 0;
+    }
+  }
+
+  /**
+   * 001: mask bits for which x mod 2 == 0
+   */
+  private static final class DataMask001 extends DataMask {
+    @Override
+    boolean isMasked(int i, int j) {
+      return (i & 0x01) == 0;
+    }
+  }
+
+  /**
+   * 010: mask bits for which y mod 3 == 0
+   */
+  private static final class DataMask010 extends DataMask {
+    @Override
+    boolean isMasked(int i, int j) {
+      return j % 3 == 0;
+    }
+  }
+
+  /**
+   * 011: mask bits for which (x + y) mod 3 == 0
+   */
+  private static final class DataMask011 extends DataMask {
+    @Override
+    boolean isMasked(int i, int j) {
+      return (i + j) % 3 == 0;
+    }
+  }
+
+  /**
+   * 100: mask bits for which (x/2 + y/3) mod 2 == 0
+   */
+  private static final class DataMask100 extends DataMask {
+    @Override
+    boolean isMasked(int i, int j) {
+      return (((i >>> 1) + (j /3)) & 0x01) == 0;
+    }
+  }
+
+  /**
+   * 101: mask bits for which xy mod 2 + xy mod 3 == 0
+   */
+  private static final class DataMask101 extends DataMask {
+    @Override
+    boolean isMasked(int i, int j) {
+      int temp = i * j;
+      return (temp & 0x01) + (temp % 3) == 0;
+    }
+  }
+
+  /**
+   * 110: mask bits for which (xy mod 2 + xy mod 3) mod 2 == 0
+   */
+  private static final class DataMask110 extends DataMask {
+    @Override
+    boolean isMasked(int i, int j) {
+      int temp = i * j;
+      return (((temp & 0x01) + (temp % 3)) & 0x01) == 0;
+    }
+  }
+
+  /**
+   * 111: mask bits for which ((x+y)mod 2 + xy mod 3) mod 2 == 0
+   */
+  private static final class DataMask111 extends DataMask {
+    @Override
+    boolean isMasked(int i, int j) {
+      return ((((i + j) & 0x01) + ((i * j) % 3)) & 0x01) == 0;
+    }
+  }
+}
diff --git a/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java b/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java
new file mode 100644 (file)
index 0000000..b701f99
--- /dev/null
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitSource;
+import com.google.zxing.common.CharacterSetECI;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.StringUtils;
+
+/**
+ * <p>QR Codes can encode text as bits in one of several modes, and can use multiple modes
+ * in one QR Code. This class decodes the bits back into text.</p>
+ *
+ * <p>See ISO 18004:2006, 6.4.3 - 6.4.7</p>
+ *
+ * @author Sean Owen
+ */
+final class DecodedBitStreamParser {
+
+  /**
+   * See ISO 18004:2006, 6.4.4 Table 5
+   */
+  private static final char[] ALPHANUMERIC_CHARS = {
+      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
+      'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+      'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+      ' ', '$', '%', '*', '+', '-', '.', '/', ':'
+  };
+  private static final int GB2312_SUBSET = 1;
+
+  private DecodedBitStreamParser() {
+  }
+
+  static DecoderResult decode(byte[] bytes,
+                              Version version,
+                              ErrorCorrectionLevel ecLevel,
+                              Map<DecodeHintType,?> hints) throws FormatException {
+    BitSource bits = new BitSource(bytes);
+    StringBuilder result = new StringBuilder(50);
+    List<byte[]> byteSegments = new ArrayList<byte[]>(1);
+    try {
+      CharacterSetECI currentCharacterSetECI = null;
+      boolean fc1InEffect = false;
+      Mode mode;
+      do {
+        // While still another segment to read...
+        if (bits.available() < 4) {
+          // OK, assume we're done. Really, a TERMINATOR mode should have been recorded here
+          mode = Mode.TERMINATOR;
+        } else {
+          mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits
+        }
+        if (mode != Mode.TERMINATOR) {
+          if (mode == Mode.FNC1_FIRST_POSITION || mode == Mode.FNC1_SECOND_POSITION) {
+            // We do little with FNC1 except alter the parsed result a bit according to the spec
+            fc1InEffect = true;
+          } else if (mode == Mode.STRUCTURED_APPEND) {
+            if (bits.available() < 16) {
+              throw FormatException.getFormatInstance();
+            }
+            // not really supported; all we do is ignore it
+            // Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
+            bits.readBits(16);
+          } else if (mode == Mode.ECI) {
+            // Count doesn't apply to ECI
+            int value = parseECIValue(bits);
+            currentCharacterSetECI = CharacterSetECI.getCharacterSetECIByValue(value);
+            if (currentCharacterSetECI == null) {
+              throw FormatException.getFormatInstance();
+            }
+          } else {
+            // First handle Hanzi mode which does not start with character count
+            if (mode == Mode.HANZI) {
+              //chinese mode contains a sub set indicator right after mode indicator
+              int subset = bits.readBits(4);
+              int countHanzi = bits.readBits(mode.getCharacterCountBits(version));
+              if (subset == GB2312_SUBSET) {
+                decodeHanziSegment(bits, result, countHanzi);
+              }
+            } else {
+              // "Normal" QR code modes:
+              // How many characters will follow, encoded in this mode?
+              int count = bits.readBits(mode.getCharacterCountBits(version));
+              if (mode == Mode.NUMERIC) {
+                decodeNumericSegment(bits, result, count);
+              } else if (mode == Mode.ALPHANUMERIC) {
+                decodeAlphanumericSegment(bits, result, count, fc1InEffect);
+              } else if (mode == Mode.BYTE) {
+                decodeByteSegment(bits, result, count, currentCharacterSetECI, byteSegments, hints);
+              } else if (mode == Mode.KANJI) {
+                decodeKanjiSegment(bits, result, count);
+              } else {
+                throw FormatException.getFormatInstance();
+              }
+            }
+          }
+        }
+      } while (mode != Mode.TERMINATOR);
+    } catch (IllegalArgumentException iae) {
+      // from readBits() calls
+      throw FormatException.getFormatInstance();
+    }
+
+    return new DecoderResult(bytes,
+                             result.toString(),
+                             byteSegments.isEmpty() ? null : byteSegments,
+                             ecLevel == null ? null : ecLevel.toString());
+  }
+
+  /**
+   * See specification GBT 18284-2000
+   */
+  private static void decodeHanziSegment(BitSource bits,
+                                         StringBuilder result,
+                                         int count) throws FormatException {
+    // Don't crash trying to read more bits than we have available.
+    if (count * 13 > bits.available()) {
+      throw FormatException.getFormatInstance();
+    }
+
+    // Each character will require 2 bytes. Read the characters as 2-byte pairs
+    // and decode as GB2312 afterwards
+    byte[] buffer = new byte[2 * count];
+    int offset = 0;
+    while (count > 0) {
+      // Each 13 bits encodes a 2-byte character
+      int twoBytes = bits.readBits(13);
+      int assembledTwoBytes = ((twoBytes / 0x060) << 8) | (twoBytes % 0x060);
+      if (assembledTwoBytes < 0x003BF) {
+        // In the 0xA1A1 to 0xAAFE range
+        assembledTwoBytes += 0x0A1A1;
+      } else {
+        // In the 0xB0A1 to 0xFAFE range
+        assembledTwoBytes += 0x0A6A1;
+      }
+      buffer[offset] = (byte) ((assembledTwoBytes >> 8) & 0xFF);
+      buffer[offset + 1] = (byte) (assembledTwoBytes & 0xFF);
+      offset += 2;
+      count--;
+    }
+
+    try {
+      result.append(new String(buffer, StringUtils.GB2312));
+    } catch (UnsupportedEncodingException ignored) {
+      throw FormatException.getFormatInstance();
+    }
+  }
+
+  private static void decodeKanjiSegment(BitSource bits,
+                                         StringBuilder result,
+                                         int count) throws FormatException {
+    // Don't crash trying to read more bits than we have available.
+    if (count * 13 > bits.available()) {
+      throw FormatException.getFormatInstance();
+    }
+
+    // Each character will require 2 bytes. Read the characters as 2-byte pairs
+    // and decode as Shift_JIS afterwards
+    byte[] buffer = new byte[2 * count];
+    int offset = 0;
+    while (count > 0) {
+      // Each 13 bits encodes a 2-byte character
+      int twoBytes = bits.readBits(13);
+      int assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0);
+      if (assembledTwoBytes < 0x01F00) {
+        // In the 0x8140 to 0x9FFC range
+        assembledTwoBytes += 0x08140;
+      } else {
+        // In the 0xE040 to 0xEBBF range
+        assembledTwoBytes += 0x0C140;
+      }
+      buffer[offset] = (byte) (assembledTwoBytes >> 8);
+      buffer[offset + 1] = (byte) assembledTwoBytes;
+      offset += 2;
+      count--;
+    }
+    // Shift_JIS may not be supported in some environments:
+    try {
+      result.append(new String(buffer, StringUtils.SHIFT_JIS));
+    } catch (UnsupportedEncodingException ignored) {
+      throw FormatException.getFormatInstance();
+    }
+  }
+
+  private static void decodeByteSegment(BitSource bits,
+                                        StringBuilder result,
+                                        int count,
+                                        CharacterSetECI currentCharacterSetECI,
+                                        Collection<byte[]> byteSegments,
+                                        Map<DecodeHintType,?> hints) throws FormatException {
+    // Don't crash trying to read more bits than we have available.
+    if (count << 3 > bits.available()) {
+      throw FormatException.getFormatInstance();
+    }
+
+    byte[] readBytes = new byte[count];
+    for (int i = 0; i < count; i++) {
+      readBytes[i] = (byte) bits.readBits(8);
+    }
+    String encoding;
+    if (currentCharacterSetECI == null) {
+      // The spec isn't clear on this mode; see
+      // section 6.4.5: t does not say which encoding to assuming
+      // upon decoding. I have seen ISO-8859-1 used as well as
+      // Shift_JIS -- without anything like an ECI designator to
+      // give a hint.
+      encoding = StringUtils.guessEncoding(readBytes, hints);
+    } else {
+      encoding = currentCharacterSetECI.name();
+    }
+    try {
+      result.append(new String(readBytes, encoding));
+    } catch (UnsupportedEncodingException ignored) {
+      throw FormatException.getFormatInstance();
+    }
+    byteSegments.add(readBytes);
+  }
+
+  private static char toAlphaNumericChar(int value) throws FormatException {
+    if (value >= ALPHANUMERIC_CHARS.length) {
+      throw FormatException.getFormatInstance();
+    }
+    return ALPHANUMERIC_CHARS[value];
+  }
+
+  private static void decodeAlphanumericSegment(BitSource bits,
+                                                StringBuilder result,
+                                                int count,
+                                                boolean fc1InEffect) throws FormatException {
+    // Read two characters at a time
+    int start = result.length();
+    while (count > 1) {
+      if (bits.available() < 11) {
+        throw FormatException.getFormatInstance();
+      }
+      int nextTwoCharsBits = bits.readBits(11);
+      result.append(toAlphaNumericChar(nextTwoCharsBits / 45));
+      result.append(toAlphaNumericChar(nextTwoCharsBits % 45));
+      count -= 2;
+    }
+    if (count == 1) {
+      // special case: one character left
+      if (bits.available() < 6) {
+        throw FormatException.getFormatInstance();
+      }
+      result.append(toAlphaNumericChar(bits.readBits(6)));
+    }
+    // See section 6.4.8.1, 6.4.8.2
+    if (fc1InEffect) {
+      // We need to massage the result a bit if in an FNC1 mode:
+      for (int i = start; i < result.length(); i++) {
+        if (result.charAt(i) == '%') {
+          if (i < result.length() - 1 && result.charAt(i + 1) == '%') {
+            // %% is rendered as %
+            result.deleteCharAt(i + 1);
+          } else {
+            // In alpha mode, % should be converted to FNC1 separator 0x1D
+            result.setCharAt(i, (char) 0x1D);
+          }
+        }
+      }
+    }
+  }
+
+  private static void decodeNumericSegment(BitSource bits,
+                                           StringBuilder result,
+                                           int count) throws FormatException {
+    // Read three digits at a time
+    while (count >= 3) {
+      // Each 10 bits encodes three digits
+      if (bits.available() < 10) {
+        throw FormatException.getFormatInstance();
+      }
+      int threeDigitsBits = bits.readBits(10);
+      if (threeDigitsBits >= 1000) {
+        throw FormatException.getFormatInstance();
+      }
+      result.append(toAlphaNumericChar(threeDigitsBits / 100));
+      result.append(toAlphaNumericChar((threeDigitsBits / 10) % 10));
+      result.append(toAlphaNumericChar(threeDigitsBits % 10));
+      count -= 3;
+    }
+    if (count == 2) {
+      // Two digits left over to read, encoded in 7 bits
+      if (bits.available() < 7) {
+        throw FormatException.getFormatInstance();
+      }
+      int twoDigitsBits = bits.readBits(7);
+      if (twoDigitsBits >= 100) {
+        throw FormatException.getFormatInstance();
+      }
+      result.append(toAlphaNumericChar(twoDigitsBits / 10));
+      result.append(toAlphaNumericChar(twoDigitsBits % 10));
+    } else if (count == 1) {
+      // One digit left over to read
+      if (bits.available() < 4) {
+        throw FormatException.getFormatInstance();
+      }
+      int digitBits = bits.readBits(4);
+      if (digitBits >= 10) {
+        throw FormatException.getFormatInstance();
+      }
+      result.append(toAlphaNumericChar(digitBits));
+    }
+  }
+
+  private static int parseECIValue(BitSource bits) throws FormatException {
+    int firstByte = bits.readBits(8);
+    if ((firstByte & 0x80) == 0) {
+      // just one byte
+      return firstByte & 0x7F;
+    }
+    if ((firstByte & 0xC0) == 0x80) {
+      // two bytes
+      int secondByte = bits.readBits(8);
+      return ((firstByte & 0x3F) << 8) | secondByte;
+    }
+    if ((firstByte & 0xE0) == 0xC0) {
+      // three bytes
+      int secondThirdBytes = bits.readBits(16);
+      return ((firstByte & 0x1F) << 16) | secondThirdBytes;
+    }
+    throw FormatException.getFormatInstance();
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/decoder/Decoder.java b/src/com/google/zxing/qrcode/decoder/Decoder.java
new file mode 100644 (file)
index 0000000..7c71713
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+import java.util.Map;
+
+import com.google.zxing.ChecksumException;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.reedsolomon.GenericGF;
+import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
+import com.google.zxing.common.reedsolomon.ReedSolomonException;
+
+/**
+ * <p>The main class which implements QR Code decoding -- as opposed to locating and extracting
+ * the QR Code from an image.</p>
+ *
+ * @author Sean Owen
+ */
+public final class Decoder {
+
+  private final ReedSolomonDecoder rsDecoder;
+
+  public Decoder() {
+    rsDecoder = new ReedSolomonDecoder(GenericGF.QR_CODE_FIELD_256);
+  }
+
+  public DecoderResult decode(boolean[][] image) throws ChecksumException, FormatException {
+    return decode(image, null);
+  }
+
+  /**
+   * <p>Convenience method that can decode a QR Code represented as a 2D array of booleans.
+   * "true" is taken to mean a black module.</p>
+   *
+   * @param image booleans representing white/black QR Code modules
+   * @return text and bytes encoded within the QR Code
+   * @throws FormatException if the QR Code cannot be decoded
+   * @throws ChecksumException if error correction fails
+   */
+  public DecoderResult decode(boolean[][] image, Map<DecodeHintType,?> hints)
+      throws ChecksumException, FormatException {
+    int dimension = image.length;
+    BitMatrix bits = new BitMatrix(dimension);
+    for (int i = 0; i < dimension; i++) {
+      for (int j = 0; j < dimension; j++) {
+        if (image[i][j]) {
+          bits.set(j, i);
+        }
+      }
+    }
+    return decode(bits, hints);
+  }
+
+  public DecoderResult decode(BitMatrix bits) throws ChecksumException, FormatException {
+    return decode(bits, null);
+  }
+
+  /**
+   * <p>Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.</p>
+   *
+   * @param bits booleans representing white/black QR Code modules
+   * @return text and bytes encoded within the QR Code
+   * @throws FormatException if the QR Code cannot be decoded
+   * @throws ChecksumException if error correction fails
+   */
+  public DecoderResult decode(BitMatrix bits, Map<DecodeHintType,?> hints)
+      throws FormatException, ChecksumException {
+
+    // Construct a parser and read version, error-correction level
+    BitMatrixParser parser = new BitMatrixParser(bits);
+    FormatException fe = null;
+    ChecksumException ce = null;
+    try {
+      return decode(parser, hints);
+    } catch (FormatException e) {
+      fe = e;
+    } catch (ChecksumException e) {
+      ce = e;
+    }
+
+    try {
+
+      // Revert the bit matrix
+      parser.remask();
+
+      // Will be attempting a mirrored reading of the version and format info.
+      parser.setMirror(true);
+
+      // Preemptively read the version.
+      parser.readVersion();
+
+      // Preemptively read the format information.
+      parser.readFormatInformation();
+
+      /*
+       * Since we're here, this means we have successfully detected some kind
+       * of version and format information when mirrored. This is a good sign,
+       * that the QR code may be mirrored, and we should try once more with a
+       * mirrored content.
+       */
+      // Prepare for a mirrored reading.
+      parser.mirror();
+
+      DecoderResult result = decode(parser, hints);
+
+      // Success! Notify the caller that the code was mirrored.
+      result.setOther(new QRCodeDecoderMetaData(true));
+
+      return result;
+
+    } catch (FormatException e) {
+      // Throw the exception from the original reading
+      if (fe != null) {
+        throw fe;
+      }
+      if (ce != null) {
+        throw ce;
+      }
+      throw e;
+    } catch (ChecksumException e) {
+      // Throw the exception from the original reading
+      if (fe != null) {
+        throw fe;
+      }
+      if (ce != null) {
+        throw ce;
+      }
+      throw e;
+    }
+  }
+
+  private DecoderResult decode(BitMatrixParser parser, Map<DecodeHintType,?> hints)
+      throws FormatException, ChecksumException {
+    Version version = parser.readVersion();
+    ErrorCorrectionLevel ecLevel = parser.readFormatInformation().getErrorCorrectionLevel();
+
+    // Read codewords
+    byte[] codewords = parser.readCodewords();
+    // Separate into data blocks
+    DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel);
+
+    // Count total number of data bytes
+    int totalBytes = 0;
+    for (DataBlock dataBlock : dataBlocks) {
+      totalBytes += dataBlock.getNumDataCodewords();
+    }
+    byte[] resultBytes = new byte[totalBytes];
+    int resultOffset = 0;
+
+    // Error-correct and copy data blocks together into a stream of bytes
+    for (DataBlock dataBlock : dataBlocks) {
+      byte[] codewordBytes = dataBlock.getCodewords();
+      int numDataCodewords = dataBlock.getNumDataCodewords();
+      correctErrors(codewordBytes, numDataCodewords);
+      for (int i = 0; i < numDataCodewords; i++) {
+        resultBytes[resultOffset++] = codewordBytes[i];
+      }
+    }
+
+    // Decode the contents of that stream of bytes
+    return DecodedBitStreamParser.decode(resultBytes, version, ecLevel, hints);
+  }
+
+  /**
+   * <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
+   * correct the errors in-place using Reed-Solomon error correction.</p>
+   *
+   * @param codewordBytes data and error correction codewords
+   * @param numDataCodewords number of codewords that are data bytes
+   * @throws ChecksumException if error correction fails
+   */
+  private void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException {
+    int numCodewords = codewordBytes.length;
+    // First read into an array of ints
+    int[] codewordsInts = new int[numCodewords];
+    for (int i = 0; i < numCodewords; i++) {
+      codewordsInts[i] = codewordBytes[i] & 0xFF;
+    }
+    int numECCodewords = codewordBytes.length - numDataCodewords;
+    try {
+      rsDecoder.decode(codewordsInts, numECCodewords);
+    } catch (ReedSolomonException ignored) {
+      throw ChecksumException.getChecksumInstance();
+    }
+    // Copy back into array of bytes -- only need to worry about the bytes that were data
+    // We don't care about errors in the error-correction codewords
+    for (int i = 0; i < numDataCodewords; i++) {
+      codewordBytes[i] = (byte) codewordsInts[i];
+    }
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java b/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java
new file mode 100644 (file)
index 0000000..0f1e113
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+/**
+ * <p>See ISO 18004:2006, 6.5.1. This enum encapsulates the four error correction levels
+ * defined by the QR code standard.</p>
+ *
+ * @author Sean Owen
+ */
+public enum ErrorCorrectionLevel {
+
+  /** L = ~7% correction */
+  L(0x01),
+  /** M = ~15% correction */
+  M(0x00),
+  /** Q = ~25% correction */
+  Q(0x03),
+  /** H = ~30% correction */
+  H(0x02);
+
+  private static final ErrorCorrectionLevel[] FOR_BITS = {M, L, H, Q};
+
+  private final int bits;
+
+  ErrorCorrectionLevel(int bits) {
+    this.bits = bits;
+  }
+
+  public int getBits() {
+    return bits;
+  }
+
+  /**
+   * @param bits int containing the two bits encoding a QR Code's error correction level
+   * @return ErrorCorrectionLevel representing the encoded error correction level
+   */
+  public static ErrorCorrectionLevel forBits(int bits) {
+    if (bits < 0 || bits >= FOR_BITS.length) {
+      throw new IllegalArgumentException();
+    }
+    return FOR_BITS[bits];
+  }
+
+
+}
diff --git a/src/com/google/zxing/qrcode/decoder/FormatInformation.java b/src/com/google/zxing/qrcode/decoder/FormatInformation.java
new file mode 100644 (file)
index 0000000..894e321
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+/**
+ * <p>Encapsulates a QR Code's format information, including the data mask used and
+ * error correction level.</p>
+ *
+ * @author Sean Owen
+ * @see DataMask
+ * @see ErrorCorrectionLevel
+ */
+final class FormatInformation {
+
+  private static final int FORMAT_INFO_MASK_QR = 0x5412;
+
+  /**
+   * See ISO 18004:2006, Annex C, Table C.1
+   */
+  private static final int[][] FORMAT_INFO_DECODE_LOOKUP = {
+      {0x5412, 0x00},
+      {0x5125, 0x01},
+      {0x5E7C, 0x02},
+      {0x5B4B, 0x03},
+      {0x45F9, 0x04},
+      {0x40CE, 0x05},
+      {0x4F97, 0x06},
+      {0x4AA0, 0x07},
+      {0x77C4, 0x08},
+      {0x72F3, 0x09},
+      {0x7DAA, 0x0A},
+      {0x789D, 0x0B},
+      {0x662F, 0x0C},
+      {0x6318, 0x0D},
+      {0x6C41, 0x0E},
+      {0x6976, 0x0F},
+      {0x1689, 0x10},
+      {0x13BE, 0x11},
+      {0x1CE7, 0x12},
+      {0x19D0, 0x13},
+      {0x0762, 0x14},
+      {0x0255, 0x15},
+      {0x0D0C, 0x16},
+      {0x083B, 0x17},
+      {0x355F, 0x18},
+      {0x3068, 0x19},
+      {0x3F31, 0x1A},
+      {0x3A06, 0x1B},
+      {0x24B4, 0x1C},
+      {0x2183, 0x1D},
+      {0x2EDA, 0x1E},
+      {0x2BED, 0x1F},
+  };
+
+  /**
+   * Offset i holds the number of 1 bits in the binary representation of i
+   */
+  private static final int[] BITS_SET_IN_HALF_BYTE =
+      {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
+
+  private final ErrorCorrectionLevel errorCorrectionLevel;
+  private final byte dataMask;
+
+  private FormatInformation(int formatInfo) {
+    // Bits 3,4
+    errorCorrectionLevel = ErrorCorrectionLevel.forBits((formatInfo >> 3) & 0x03);
+    // Bottom 3 bits
+    dataMask = (byte) (formatInfo & 0x07);
+  }
+
+  static int numBitsDiffering(int a, int b) {
+    a ^= b; // a now has a 1 bit exactly where its bit differs with b's
+    // Count bits set quickly with a series of lookups:
+    return BITS_SET_IN_HALF_BYTE[a & 0x0F] +
+        BITS_SET_IN_HALF_BYTE[(a >>> 4 & 0x0F)] +
+        BITS_SET_IN_HALF_BYTE[(a >>> 8 & 0x0F)] +
+        BITS_SET_IN_HALF_BYTE[(a >>> 12 & 0x0F)] +
+        BITS_SET_IN_HALF_BYTE[(a >>> 16 & 0x0F)] +
+        BITS_SET_IN_HALF_BYTE[(a >>> 20 & 0x0F)] +
+        BITS_SET_IN_HALF_BYTE[(a >>> 24 & 0x0F)] +
+        BITS_SET_IN_HALF_BYTE[(a >>> 28 & 0x0F)];
+  }
+
+  /**
+   * @param maskedFormatInfo1 format info indicator, with mask still applied
+   * @param maskedFormatInfo2 second copy of same info; both are checked at the same time
+   *  to establish best match
+   * @return information about the format it specifies, or {@code null}
+   *  if doesn't seem to match any known pattern
+   */
+  static FormatInformation decodeFormatInformation(int maskedFormatInfo1, int maskedFormatInfo2) {
+    FormatInformation formatInfo = doDecodeFormatInformation(maskedFormatInfo1, maskedFormatInfo2);
+    if (formatInfo != null) {
+      return formatInfo;
+    }
+    // Should return null, but, some QR codes apparently
+    // do not mask this info. Try again by actually masking the pattern
+    // first
+    return doDecodeFormatInformation(maskedFormatInfo1 ^ FORMAT_INFO_MASK_QR,
+                                     maskedFormatInfo2 ^ FORMAT_INFO_MASK_QR);
+  }
+
+  private static FormatInformation doDecodeFormatInformation(int maskedFormatInfo1, int maskedFormatInfo2) {
+    // Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing
+    int bestDifference = Integer.MAX_VALUE;
+    int bestFormatInfo = 0;
+    for (int[] decodeInfo : FORMAT_INFO_DECODE_LOOKUP) {
+      int targetInfo = decodeInfo[0];
+      if (targetInfo == maskedFormatInfo1 || targetInfo == maskedFormatInfo2) {
+        // Found an exact match
+        return new FormatInformation(decodeInfo[1]);
+      }
+      int bitsDifference = numBitsDiffering(maskedFormatInfo1, targetInfo);
+      if (bitsDifference < bestDifference) {
+        bestFormatInfo = decodeInfo[1];
+        bestDifference = bitsDifference;
+      }
+      if (maskedFormatInfo1 != maskedFormatInfo2) {
+        // also try the other option
+        bitsDifference = numBitsDiffering(maskedFormatInfo2, targetInfo);
+        if (bitsDifference < bestDifference) {
+          bestFormatInfo = decodeInfo[1];
+          bestDifference = bitsDifference;
+        }
+      }
+    }
+    // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits
+    // differing means we found a match
+    if (bestDifference <= 3) {
+      return new FormatInformation(bestFormatInfo);
+    }
+    return null;
+  }
+
+  ErrorCorrectionLevel getErrorCorrectionLevel() {
+    return errorCorrectionLevel;
+  }
+
+  byte getDataMask() {
+    return dataMask;
+  }
+
+  @Override
+  public int hashCode() {
+    return (errorCorrectionLevel.ordinal() << 3) | (int) dataMask;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof FormatInformation)) {
+      return false;
+    }
+    FormatInformation other = (FormatInformation) o;
+    return this.errorCorrectionLevel == other.errorCorrectionLevel &&
+        this.dataMask == other.dataMask;
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/decoder/Mode.java b/src/com/google/zxing/qrcode/decoder/Mode.java
new file mode 100644 (file)
index 0000000..b7e9ab3
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+/**
+ * <p>See ISO 18004:2006, 6.4.1, Tables 2 and 3. This enum encapsulates the various modes in which
+ * data can be encoded to bits in the QR code standard.</p>
+ *
+ * @author Sean Owen
+ */
+public enum Mode {
+
+  TERMINATOR(new int[]{0, 0, 0}, 0x00), // Not really a mode...
+  NUMERIC(new int[]{10, 12, 14}, 0x01),
+  ALPHANUMERIC(new int[]{9, 11, 13}, 0x02),
+  STRUCTURED_APPEND(new int[]{0, 0, 0}, 0x03), // Not supported
+  BYTE(new int[]{8, 16, 16}, 0x04),
+  ECI(new int[]{0, 0, 0}, 0x07), // character counts don't apply
+  KANJI(new int[]{8, 10, 12}, 0x08),
+  FNC1_FIRST_POSITION(new int[]{0, 0, 0}, 0x05),
+  FNC1_SECOND_POSITION(new int[]{0, 0, 0}, 0x09),
+  /** See GBT 18284-2000; "Hanzi" is a transliteration of this mode name. */
+  HANZI(new int[]{8, 10, 12}, 0x0D);
+
+  private final int[] characterCountBitsForVersions;
+  private final int bits;
+
+  Mode(int[] characterCountBitsForVersions, int bits) {
+    this.characterCountBitsForVersions = characterCountBitsForVersions;
+    this.bits = bits;
+  }
+
+  /**
+   * @param bits four bits encoding a QR Code data mode
+   * @return Mode encoded by these bits
+   * @throws IllegalArgumentException if bits do not correspond to a known mode
+   */
+  public static Mode forBits(int bits) {
+    switch (bits) {
+      case 0x0:
+        return TERMINATOR;
+      case 0x1:
+        return NUMERIC;
+      case 0x2:
+        return ALPHANUMERIC;
+      case 0x3:
+        return STRUCTURED_APPEND;
+      case 0x4:
+        return BYTE;
+      case 0x5:
+        return FNC1_FIRST_POSITION;
+      case 0x7:
+        return ECI;
+      case 0x8:
+        return KANJI;
+      case 0x9:
+        return FNC1_SECOND_POSITION;
+      case 0xD:
+        // 0xD is defined in GBT 18284-2000, may not be supported in foreign country
+        return HANZI;
+      default:
+        throw new IllegalArgumentException();
+    }
+  }
+
+  /**
+   * @param version version in question
+   * @return number of bits used, in this QR Code symbol {@link Version}, to encode the
+   *         count of characters that will follow encoded in this Mode
+   */
+  public int getCharacterCountBits(Version version) {
+    int number = version.getVersionNumber();
+    int offset;
+    if (number <= 9) {
+      offset = 0;
+    } else if (number <= 26) {
+      offset = 1;
+    } else {
+      offset = 2;
+    }
+    return characterCountBitsForVersions[offset];
+  }
+
+  public int getBits() {
+    return bits;
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/decoder/QRCodeDecoderMetaData.java b/src/com/google/zxing/qrcode/decoder/QRCodeDecoderMetaData.java
new file mode 100644 (file)
index 0000000..75821e3
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 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.qrcode.decoder;
+
+import com.google.zxing.ResultPoint;
+
+/**
+ * Meta-data container for QR Code decoding. Instances of this class may be used to convey information back to the
+ * decoding caller. Callers are expected to process this.
+ * 
+ * @see com.google.zxing.common.DecoderResult#getOther()
+ */
+public final class QRCodeDecoderMetaData {
+
+  private final boolean mirrored;
+  
+  QRCodeDecoderMetaData(boolean mirrored) {
+    this.mirrored = mirrored;
+  }
+
+  /** 
+   * @return true if the QR Code was mirrored. 
+   */
+  public boolean isMirrored() {
+    return mirrored;
+  }
+
+  /**
+   * Apply the result points' order correction due to mirroring.
+   * 
+   * @param points Array of points to apply mirror correction to.
+   */
+  public void applyMirroredCorrection(ResultPoint[] points) {
+    if (!mirrored || points == null || points.length < 3) {
+      return;
+    }
+    ResultPoint bottomLeft = points[0];
+    points[0] = points[2];
+    points[2] = bottomLeft;
+    // No need to 'fix' top-left and alignment pattern.
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/decoder/Version.java b/src/com/google/zxing/qrcode/decoder/Version.java
new file mode 100755 (executable)
index 0000000..b649e20
--- /dev/null
@@ -0,0 +1,578 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * See ISO 18004:2006 Annex D
+ *
+ * @author Sean Owen
+ */
+public final class Version {
+
+  /**
+   * See ISO 18004:2006 Annex D.
+   * Element i represents the raw version bits that specify version i + 7
+   */
+  private static final int[] VERSION_DECODE_INFO = {
+      0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6,
+      0x0C762, 0x0D847, 0x0E60D, 0x0F928, 0x10B78,
+      0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683,
+      0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB,
+      0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250,
+      0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B,
+      0x2542E, 0x26A64, 0x27541, 0x28C69
+  };
+
+  private static final Version[] VERSIONS = buildVersions();
+
+  private final int versionNumber;
+  private final int[] alignmentPatternCenters;
+  private final ECBlocks[] ecBlocks;
+  private final int totalCodewords;
+
+  private Version(int versionNumber,
+                  int[] alignmentPatternCenters,
+                  ECBlocks... ecBlocks) {
+    this.versionNumber = versionNumber;
+    this.alignmentPatternCenters = alignmentPatternCenters;
+    this.ecBlocks = ecBlocks;
+    int total = 0;
+    int ecCodewords = ecBlocks[0].getECCodewordsPerBlock();
+    ECB[] ecbArray = ecBlocks[0].getECBlocks();
+    for (ECB ecBlock : ecbArray) {
+      total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords);
+    }
+    this.totalCodewords = total;
+  }
+
+  public int getVersionNumber() {
+    return versionNumber;
+  }
+
+  public int[] getAlignmentPatternCenters() {
+    return alignmentPatternCenters;
+  }
+
+  public int getTotalCodewords() {
+    return totalCodewords;
+  }
+
+  public int getDimensionForVersion() {
+    return 17 + 4 * versionNumber;
+  }
+
+  public ECBlocks getECBlocksForLevel(ErrorCorrectionLevel ecLevel) {
+    return ecBlocks[ecLevel.ordinal()];
+  }
+
+  /**
+   * <p>Deduces version information purely from QR Code dimensions.</p>
+   *
+   * @param dimension dimension in modules
+   * @return Version for a QR Code of that dimension
+   * @throws FormatException if dimension is not 1 mod 4
+   */
+  public static Version getProvisionalVersionForDimension(int dimension) throws FormatException {
+    if (dimension % 4 != 1) {
+      throw FormatException.getFormatInstance();
+    }
+    try {
+      return getVersionForNumber((dimension - 17) >> 2);
+    } catch (IllegalArgumentException ignored) {
+      throw FormatException.getFormatInstance();
+    }
+  }
+
+  public static Version getVersionForNumber(int versionNumber) {
+    if (versionNumber < 1 || versionNumber > 40) {
+      throw new IllegalArgumentException();
+    }
+    return VERSIONS[versionNumber - 1];
+  }
+
+  static Version decodeVersionInformation(int versionBits) {
+    int bestDifference = Integer.MAX_VALUE;
+    int bestVersion = 0;
+    for (int i = 0; i < VERSION_DECODE_INFO.length; i++) {
+      int targetVersion = VERSION_DECODE_INFO[i];
+      // Do the version info bits match exactly? done.
+      if (targetVersion == versionBits) {
+        return getVersionForNumber(i + 7);
+      }
+      // Otherwise see if this is the closest to a real version info bit string
+      // we have seen so far
+      int bitsDifference = FormatInformation.numBitsDiffering(versionBits, targetVersion);
+      if (bitsDifference < bestDifference) {
+        bestVersion = i + 7;
+        bestDifference = bitsDifference;
+      }
+    }
+    // We can tolerate up to 3 bits of error since no two version info codewords will
+    // differ in less than 8 bits.
+    if (bestDifference <= 3) {
+      return getVersionForNumber(bestVersion);
+    }
+    // If we didn't find a close enough match, fail
+    return null;
+  }
+
+  /**
+   * See ISO 18004:2006 Annex E
+   */
+  BitMatrix buildFunctionPattern() {
+    int dimension = getDimensionForVersion();
+    BitMatrix bitMatrix = new BitMatrix(dimension);
+
+    // Top left finder pattern + separator + format
+    bitMatrix.setRegion(0, 0, 9, 9);
+    // Top right finder pattern + separator + format
+    bitMatrix.setRegion(dimension - 8, 0, 8, 9);
+    // Bottom left finder pattern + separator + format
+    bitMatrix.setRegion(0, dimension - 8, 9, 8);
+
+    // Alignment patterns
+    int max = alignmentPatternCenters.length;
+    for (int x = 0; x < max; x++) {
+      int i = alignmentPatternCenters[x] - 2;
+      for (int y = 0; y < max; y++) {
+        if ((x == 0 && (y == 0 || y == max - 1)) || (x == max - 1 && y == 0)) {
+          // No alignment patterns near the three finder paterns
+          continue;
+        }
+        bitMatrix.setRegion(alignmentPatternCenters[y] - 2, i, 5, 5);
+      }
+    }
+
+    // Vertical timing pattern
+    bitMatrix.setRegion(6, 9, 1, dimension - 17);
+    // Horizontal timing pattern
+    bitMatrix.setRegion(9, 6, dimension - 17, 1);
+
+    if (versionNumber > 6) {
+      // Version info, top right
+      bitMatrix.setRegion(dimension - 11, 0, 3, 6);
+      // Version info, bottom left
+      bitMatrix.setRegion(0, dimension - 11, 6, 3);
+    }
+
+    return bitMatrix;
+  }
+
+  /**
+   * <p>Encapsulates a set of error-correction blocks in one symbol version. Most versions will
+   * use blocks of differing sizes within one version, so, this encapsulates the parameters for
+   * each set of blocks. It also holds the number of error-correction codewords per block since it
+   * will be the same across all blocks within one version.</p>
+   */
+  public static final class ECBlocks {
+    private final int ecCodewordsPerBlock;
+    private final ECB[] ecBlocks;
+
+    ECBlocks(int ecCodewordsPerBlock, ECB... ecBlocks) {
+      this.ecCodewordsPerBlock = ecCodewordsPerBlock;
+      this.ecBlocks = ecBlocks;
+    }
+
+    public int getECCodewordsPerBlock() {
+      return ecCodewordsPerBlock;
+    }
+
+    public int getNumBlocks() {
+      int total = 0;
+      for (ECB ecBlock : ecBlocks) {
+        total += ecBlock.getCount();
+      }
+      return total;
+    }
+
+    public int getTotalECCodewords() {
+      return ecCodewordsPerBlock * getNumBlocks();
+    }
+
+    public ECB[] getECBlocks() {
+      return ecBlocks;
+    }
+  }
+
+  /**
+   * <p>Encapsualtes the parameters for one error-correction block in one symbol version.
+   * This includes the number of data codewords, and the number of times a block with these
+   * parameters is used consecutively in the QR code version's format.</p>
+   */
+  public static final class ECB {
+    private final int count;
+    private final int dataCodewords;
+
+    ECB(int count, int dataCodewords) {
+      this.count = count;
+      this.dataCodewords = dataCodewords;
+    }
+
+    public int getCount() {
+      return count;
+    }
+
+    public int getDataCodewords() {
+      return dataCodewords;
+    }
+  }
+
+  @Override
+  public String toString() {
+    return String.valueOf(versionNumber);
+  }
+
+  /**
+   * See ISO 18004:2006 6.5.1 Table 9
+   */
+  private static Version[] buildVersions() {
+    return new Version[]{
+        new Version(1, new int[]{},
+            new ECBlocks(7, new ECB(1, 19)),
+            new ECBlocks(10, new ECB(1, 16)),
+            new ECBlocks(13, new ECB(1, 13)),
+            new ECBlocks(17, new ECB(1, 9))),
+        new Version(2, new int[]{6, 18},
+            new ECBlocks(10, new ECB(1, 34)),
+            new ECBlocks(16, new ECB(1, 28)),
+            new ECBlocks(22, new ECB(1, 22)),
+            new ECBlocks(28, new ECB(1, 16))),
+        new Version(3, new int[]{6, 22},
+            new ECBlocks(15, new ECB(1, 55)),
+            new ECBlocks(26, new ECB(1, 44)),
+            new ECBlocks(18, new ECB(2, 17)),
+            new ECBlocks(22, new ECB(2, 13))),
+        new Version(4, new int[]{6, 26},
+            new ECBlocks(20, new ECB(1, 80)),
+            new ECBlocks(18, new ECB(2, 32)),
+            new ECBlocks(26, new ECB(2, 24)),
+            new ECBlocks(16, new ECB(4, 9))),
+        new Version(5, new int[]{6, 30},
+            new ECBlocks(26, new ECB(1, 108)),
+            new ECBlocks(24, new ECB(2, 43)),
+            new ECBlocks(18, new ECB(2, 15),
+                new ECB(2, 16)),
+            new ECBlocks(22, new ECB(2, 11),
+                new ECB(2, 12))),
+        new Version(6, new int[]{6, 34},
+            new ECBlocks(18, new ECB(2, 68)),
+            new ECBlocks(16, new ECB(4, 27)),
+            new ECBlocks(24, new ECB(4, 19)),
+            new ECBlocks(28, new ECB(4, 15))),
+        new Version(7, new int[]{6, 22, 38},
+            new ECBlocks(20, new ECB(2, 78)),
+            new ECBlocks(18, new ECB(4, 31)),
+            new ECBlocks(18, new ECB(2, 14),
+                new ECB(4, 15)),
+            new ECBlocks(26, new ECB(4, 13),
+                new ECB(1, 14))),
+        new Version(8, new int[]{6, 24, 42},
+            new ECBlocks(24, new ECB(2, 97)),
+            new ECBlocks(22, new ECB(2, 38),
+                new ECB(2, 39)),
+            new ECBlocks(22, new ECB(4, 18),
+                new ECB(2, 19)),
+            new ECBlocks(26, new ECB(4, 14),
+                new ECB(2, 15))),
+        new Version(9, new int[]{6, 26, 46},
+            new ECBlocks(30, new ECB(2, 116)),
+            new ECBlocks(22, new ECB(3, 36),
+                new ECB(2, 37)),
+            new ECBlocks(20, new ECB(4, 16),
+                new ECB(4, 17)),
+            new ECBlocks(24, new ECB(4, 12),
+                new ECB(4, 13))),
+        new Version(10, new int[]{6, 28, 50},
+            new ECBlocks(18, new ECB(2, 68),
+                new ECB(2, 69)),
+            new ECBlocks(26, new ECB(4, 43),
+                new ECB(1, 44)),
+            new ECBlocks(24, new ECB(6, 19),
+                new ECB(2, 20)),
+            new ECBlocks(28, new ECB(6, 15),
+                new ECB(2, 16))),
+        new Version(11, new int[]{6, 30, 54},
+            new ECBlocks(20, new ECB(4, 81)),
+            new ECBlocks(30, new ECB(1, 50),
+                new ECB(4, 51)),
+            new ECBlocks(28, new ECB(4, 22),
+                new ECB(4, 23)),
+            new ECBlocks(24, new ECB(3, 12),
+                new ECB(8, 13))),
+        new Version(12, new int[]{6, 32, 58},
+            new ECBlocks(24, new ECB(2, 92),
+                new ECB(2, 93)),
+            new ECBlocks(22, new ECB(6, 36),
+                new ECB(2, 37)),
+            new ECBlocks(26, new ECB(4, 20),
+                new ECB(6, 21)),
+            new ECBlocks(28, new ECB(7, 14),
+                new ECB(4, 15))),
+        new Version(13, new int[]{6, 34, 62},
+            new ECBlocks(26, new ECB(4, 107)),
+            new ECBlocks(22, new ECB(8, 37),
+                new ECB(1, 38)),
+            new ECBlocks(24, new ECB(8, 20),
+                new ECB(4, 21)),
+            new ECBlocks(22, new ECB(12, 11),
+                new ECB(4, 12))),
+        new Version(14, new int[]{6, 26, 46, 66},
+            new ECBlocks(30, new ECB(3, 115),
+                new ECB(1, 116)),
+            new ECBlocks(24, new ECB(4, 40),
+                new ECB(5, 41)),
+            new ECBlocks(20, new ECB(11, 16),
+                new ECB(5, 17)),
+            new ECBlocks(24, new ECB(11, 12),
+                new ECB(5, 13))),
+        new Version(15, new int[]{6, 26, 48, 70},
+            new ECBlocks(22, new ECB(5, 87),
+                new ECB(1, 88)),
+            new ECBlocks(24, new ECB(5, 41),
+                new ECB(5, 42)),
+            new ECBlocks(30, new ECB(5, 24),
+                new ECB(7, 25)),
+            new ECBlocks(24, new ECB(11, 12),
+                new ECB(7, 13))),
+        new Version(16, new int[]{6, 26, 50, 74},
+            new ECBlocks(24, new ECB(5, 98),
+                new ECB(1, 99)),
+            new ECBlocks(28, new ECB(7, 45),
+                new ECB(3, 46)),
+            new ECBlocks(24, new ECB(15, 19),
+                new ECB(2, 20)),
+            new ECBlocks(30, new ECB(3, 15),
+                new ECB(13, 16))),
+        new Version(17, new int[]{6, 30, 54, 78},
+            new ECBlocks(28, new ECB(1, 107),
+                new ECB(5, 108)),
+            new ECBlocks(28, new ECB(10, 46),
+                new ECB(1, 47)),
+            new ECBlocks(28, new ECB(1, 22),
+                new ECB(15, 23)),
+            new ECBlocks(28, new ECB(2, 14),
+                new ECB(17, 15))),
+        new Version(18, new int[]{6, 30, 56, 82},
+            new ECBlocks(30, new ECB(5, 120),
+                new ECB(1, 121)),
+            new ECBlocks(26, new ECB(9, 43),
+                new ECB(4, 44)),
+            new ECBlocks(28, new ECB(17, 22),
+                new ECB(1, 23)),
+            new ECBlocks(28, new ECB(2, 14),
+                new ECB(19, 15))),
+        new Version(19, new int[]{6, 30, 58, 86},
+            new ECBlocks(28, new ECB(3, 113),
+                new ECB(4, 114)),
+            new ECBlocks(26, new ECB(3, 44),
+                new ECB(11, 45)),
+            new ECBlocks(26, new ECB(17, 21),
+                new ECB(4, 22)),
+            new ECBlocks(26, new ECB(9, 13),
+                new ECB(16, 14))),
+        new Version(20, new int[]{6, 34, 62, 90},
+            new ECBlocks(28, new ECB(3, 107),
+                new ECB(5, 108)),
+            new ECBlocks(26, new ECB(3, 41),
+                new ECB(13, 42)),
+            new ECBlocks(30, new ECB(15, 24),
+                new ECB(5, 25)),
+            new ECBlocks(28, new ECB(15, 15),
+                new ECB(10, 16))),
+        new Version(21, new int[]{6, 28, 50, 72, 94},
+            new ECBlocks(28, new ECB(4, 116),
+                new ECB(4, 117)),
+            new ECBlocks(26, new ECB(17, 42)),
+            new ECBlocks(28, new ECB(17, 22),
+                new ECB(6, 23)),
+            new ECBlocks(30, new ECB(19, 16),
+                new ECB(6, 17))),
+        new Version(22, new int[]{6, 26, 50, 74, 98},
+            new ECBlocks(28, new ECB(2, 111),
+                new ECB(7, 112)),
+            new ECBlocks(28, new ECB(17, 46)),
+            new ECBlocks(30, new ECB(7, 24),
+                new ECB(16, 25)),
+            new ECBlocks(24, new ECB(34, 13))),
+        new Version(23, new int[]{6, 30, 54, 78, 102},
+            new ECBlocks(30, new ECB(4, 121),
+                new ECB(5, 122)),
+            new ECBlocks(28, new ECB(4, 47),
+                new ECB(14, 48)),
+            new ECBlocks(30, new ECB(11, 24),
+                new ECB(14, 25)),
+            new ECBlocks(30, new ECB(16, 15),
+                new ECB(14, 16))),
+        new Version(24, new int[]{6, 28, 54, 80, 106},
+            new ECBlocks(30, new ECB(6, 117),
+                new ECB(4, 118)),
+            new ECBlocks(28, new ECB(6, 45),
+                new ECB(14, 46)),
+            new ECBlocks(30, new ECB(11, 24),
+                new ECB(16, 25)),
+            new ECBlocks(30, new ECB(30, 16),
+                new ECB(2, 17))),
+        new Version(25, new int[]{6, 32, 58, 84, 110},
+            new ECBlocks(26, new ECB(8, 106),
+                new ECB(4, 107)),
+            new ECBlocks(28, new ECB(8, 47),
+                new ECB(13, 48)),
+            new ECBlocks(30, new ECB(7, 24),
+                new ECB(22, 25)),
+            new ECBlocks(30, new ECB(22, 15),
+                new ECB(13, 16))),
+        new Version(26, new int[]{6, 30, 58, 86, 114},
+            new ECBlocks(28, new ECB(10, 114),
+                new ECB(2, 115)),
+            new ECBlocks(28, new ECB(19, 46),
+                new ECB(4, 47)),
+            new ECBlocks(28, new ECB(28, 22),
+                new ECB(6, 23)),
+            new ECBlocks(30, new ECB(33, 16),
+                new ECB(4, 17))),
+        new Version(27, new int[]{6, 34, 62, 90, 118},
+            new ECBlocks(30, new ECB(8, 122),
+                new ECB(4, 123)),
+            new ECBlocks(28, new ECB(22, 45),
+                new ECB(3, 46)),
+            new ECBlocks(30, new ECB(8, 23),
+                new ECB(26, 24)),
+            new ECBlocks(30, new ECB(12, 15),
+                new ECB(28, 16))),
+        new Version(28, new int[]{6, 26, 50, 74, 98, 122},
+            new ECBlocks(30, new ECB(3, 117),
+                new ECB(10, 118)),
+            new ECBlocks(28, new ECB(3, 45),
+                new ECB(23, 46)),
+            new ECBlocks(30, new ECB(4, 24),
+                new ECB(31, 25)),
+            new ECBlocks(30, new ECB(11, 15),
+                new ECB(31, 16))),
+        new Version(29, new int[]{6, 30, 54, 78, 102, 126},
+            new ECBlocks(30, new ECB(7, 116),
+                new ECB(7, 117)),
+            new ECBlocks(28, new ECB(21, 45),
+                new ECB(7, 46)),
+            new ECBlocks(30, new ECB(1, 23),
+                new ECB(37, 24)),
+            new ECBlocks(30, new ECB(19, 15),
+                new ECB(26, 16))),
+        new Version(30, new int[]{6, 26, 52, 78, 104, 130},
+            new ECBlocks(30, new ECB(5, 115),
+                new ECB(10, 116)),
+            new ECBlocks(28, new ECB(19, 47),
+                new ECB(10, 48)),
+            new ECBlocks(30, new ECB(15, 24),
+                new ECB(25, 25)),
+            new ECBlocks(30, new ECB(23, 15),
+                new ECB(25, 16))),
+        new Version(31, new int[]{6, 30, 56, 82, 108, 134},
+            new ECBlocks(30, new ECB(13, 115),
+                new ECB(3, 116)),
+            new ECBlocks(28, new ECB(2, 46),
+                new ECB(29, 47)),
+            new ECBlocks(30, new ECB(42, 24),
+                new ECB(1, 25)),
+            new ECBlocks(30, new ECB(23, 15),
+                new ECB(28, 16))),
+        new Version(32, new int[]{6, 34, 60, 86, 112, 138},
+            new ECBlocks(30, new ECB(17, 115)),
+            new ECBlocks(28, new ECB(10, 46),
+                new ECB(23, 47)),
+            new ECBlocks(30, new ECB(10, 24),
+                new ECB(35, 25)),
+            new ECBlocks(30, new ECB(19, 15),
+                new ECB(35, 16))),
+        new Version(33, new int[]{6, 30, 58, 86, 114, 142},
+            new ECBlocks(30, new ECB(17, 115),
+                new ECB(1, 116)),
+            new ECBlocks(28, new ECB(14, 46),
+                new ECB(21, 47)),
+            new ECBlocks(30, new ECB(29, 24),
+                new ECB(19, 25)),
+            new ECBlocks(30, new ECB(11, 15),
+                new ECB(46, 16))),
+        new Version(34, new int[]{6, 34, 62, 90, 118, 146},
+            new ECBlocks(30, new ECB(13, 115),
+                new ECB(6, 116)),
+            new ECBlocks(28, new ECB(14, 46),
+                new ECB(23, 47)),
+            new ECBlocks(30, new ECB(44, 24),
+                new ECB(7, 25)),
+            new ECBlocks(30, new ECB(59, 16),
+                new ECB(1, 17))),
+        new Version(35, new int[]{6, 30, 54, 78, 102, 126, 150},
+            new ECBlocks(30, new ECB(12, 121),
+                new ECB(7, 122)),
+            new ECBlocks(28, new ECB(12, 47),
+                new ECB(26, 48)),
+            new ECBlocks(30, new ECB(39, 24),
+                new ECB(14, 25)),
+            new ECBlocks(30, new ECB(22, 15),
+                new ECB(41, 16))),
+        new Version(36, new int[]{6, 24, 50, 76, 102, 128, 154},
+            new ECBlocks(30, new ECB(6, 121),
+                new ECB(14, 122)),
+            new ECBlocks(28, new ECB(6, 47),
+                new ECB(34, 48)),
+            new ECBlocks(30, new ECB(46, 24),
+                new ECB(10, 25)),
+            new ECBlocks(30, new ECB(2, 15),
+                new ECB(64, 16))),
+        new Version(37, new int[]{6, 28, 54, 80, 106, 132, 158},
+            new ECBlocks(30, new ECB(17, 122),
+                new ECB(4, 123)),
+            new ECBlocks(28, new ECB(29, 46),
+                new ECB(14, 47)),
+            new ECBlocks(30, new ECB(49, 24),
+                new ECB(10, 25)),
+            new ECBlocks(30, new ECB(24, 15),
+                new ECB(46, 16))),
+        new Version(38, new int[]{6, 32, 58, 84, 110, 136, 162},
+            new ECBlocks(30, new ECB(4, 122),
+                new ECB(18, 123)),
+            new ECBlocks(28, new ECB(13, 46),
+                new ECB(32, 47)),
+            new ECBlocks(30, new ECB(48, 24),
+                new ECB(14, 25)),
+            new ECBlocks(30, new ECB(42, 15),
+                new ECB(32, 16))),
+        new Version(39, new int[]{6, 26, 54, 82, 110, 138, 166},
+            new ECBlocks(30, new ECB(20, 117),
+                new ECB(4, 118)),
+            new ECBlocks(28, new ECB(40, 47),
+                new ECB(7, 48)),
+            new ECBlocks(30, new ECB(43, 24),
+                new ECB(22, 25)),
+            new ECBlocks(30, new ECB(10, 15),
+                new ECB(67, 16))),
+        new Version(40, new int[]{6, 30, 58, 86, 114, 142, 170},
+            new ECBlocks(30, new ECB(19, 118),
+                new ECB(6, 119)),
+            new ECBlocks(28, new ECB(18, 47),
+                new ECB(31, 48)),
+            new ECBlocks(30, new ECB(34, 24),
+                new ECB(34, 25)),
+            new ECBlocks(30, new ECB(20, 15),
+                new ECB(61, 16)))
+    };
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/detector/AlignmentPattern.java b/src/com/google/zxing/qrcode/detector/AlignmentPattern.java
new file mode 100644 (file)
index 0000000..96d9194
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2007 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.qrcode.detector;
+
+import com.google.zxing.ResultPoint;
+
+/**
+ * <p>Encapsulates an alignment pattern, which are the smaller square patterns found in
+ * all but the simplest QR Codes.</p>
+ *
+ * @author Sean Owen
+ */
+public final class AlignmentPattern extends ResultPoint {
+
+  private final float estimatedModuleSize;
+
+  AlignmentPattern(float posX, float posY, float estimatedModuleSize) {
+    super(posX, posY);
+    this.estimatedModuleSize = estimatedModuleSize;
+  }
+
+  /**
+   * <p>Determines if this alignment pattern "about equals" an alignment pattern at the stated
+   * position and size -- meaning, it is at nearly the same center with nearly the same size.</p>
+   */
+  boolean aboutEquals(float moduleSize, float i, float j) {
+    if (Math.abs(i - getY()) <= moduleSize && Math.abs(j - getX()) <= moduleSize) {
+      float moduleSizeDiff = Math.abs(moduleSize - estimatedModuleSize);
+      return moduleSizeDiff <= 1.0f || moduleSizeDiff <= estimatedModuleSize;
+    }
+    return false;
+  }
+
+  /**
+   * Combines this object's current estimate of a finder pattern position and module size
+   * with a new estimate. It returns a new {@code FinderPattern} containing an average of the two.
+   */
+  AlignmentPattern combineEstimate(float i, float j, float newModuleSize) {
+    float combinedX = (getX() + j) / 2.0f;
+    float combinedY = (getY() + i) / 2.0f;
+    float combinedModuleSize = (estimatedModuleSize + newModuleSize) / 2.0f;
+    return new AlignmentPattern(combinedX, combinedY, combinedModuleSize);
+  }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java b/src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java
new file mode 100644 (file)
index 0000000..221b207
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2007 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.qrcode.detector;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * <p>This class attempts to find alignment patterns in a QR Code. Alignment patterns look like finder
+ * patterns but are smaller and appear at regular intervals throughout the image.</p>
+ *
+ * <p>At the moment this only looks for the bottom-right alignment pattern.</p>
+ *
+ * <p>This is mostly a simplified copy of {@link FinderPatternFinder}. It is copied,
+ * pasted and stripped down here for maximum performance but does unfortunately duplicate
+ * some code.</p>
+ *
+ * <p>This class is thread-safe but not reentrant. Each thread must allocate its own object.</p>
+ *
+ * @author Sean Owen
+ */
+final class AlignmentPatternFinder {
+
+  private final BitMatrix image;
+  private final List<AlignmentPattern> possibleCenters;
+  private final int startX;
+  private final int startY;
+  private final int width;
+  private final int height;
+  private final float moduleSize;
+  private final int[] crossCheckStateCount;
+  private final ResultPointCallback resultPointCallback;
+
+  /**
+   * <p>Creates a finder that will look in a portion of the whole image.</p>
+   *
+   * @param image image to search
+   * @param startX left column from which to start searching
+   * @param startY top row from which to start searching
+   * @param width width of region to search
+   * @param height height of region to search
+   * @param moduleSize estimated module size so far
+   */
+  AlignmentPatternFinder(BitMatrix image,
+                         int startX,
+                         int startY,
+                         int width,
+                         int height,
+                         float moduleSize,
+                         ResultPointCallback resultPointCallback) {
+    this.image = image;
+    this.possibleCenters = new ArrayList<AlignmentPattern>(5);
+    this.startX = startX;
+    this.startY = startY;
+    this.width = width;
+    this.height = height;
+    this.moduleSize = moduleSize;
+    this.crossCheckStateCount = new int[3];
+    this.resultPointCallback = resultPointCallback;
+  }
+
+  /**
+   * <p>This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since
+   * it's pretty performance-critical and so is written to be fast foremost.</p>
+   *
+   * @return {@link AlignmentPattern} if found
+   * @throws NotFoundException if not found
+   */
+  AlignmentPattern find() throws NotFoundException {
+    int startX = this.startX;
+    int height = this.height;
+    int maxJ = startX + width;
+    int middleI = startY + (height >> 1);
+    // We are looking for black/white/black modules in 1:1:1 ratio;
+    // this tracks the number of black/white/black modules seen so far
+    int[] stateCount = new int[3];
+    for (int iGen = 0; iGen < height; iGen++) {
+      // Search from middle outwards
+      int i = middleI + ((iGen & 0x01) == 0 ? (iGen + 1) >> 1 : -((iGen + 1) >> 1));
+      stateCount[0] = 0;
+      stateCount[1] = 0;
+      stateCount[2] = 0;
+      int j = startX;
+      // Burn off leading white pixels before anything else; if we start in the middle of
+      // a white run, it doesn't make sense to count its length, since we don't know if the
+      // white run continued to the left of the start point
+      while (j < maxJ && !image.get(j, i)) {
+        j++;
+      }
+      int currentState = 0;
+      while (j < maxJ) {
+        if (image.get(j, i)) {
+          // Black pixel
+          if (currentState == 1) { // Counting black pixels
+            stateCount[currentState]++;
+          } else { // Counting white pixels
+            if (currentState == 2) { // A winner?
+              if (foundPatternCross(stateCount)) { // Yes
+                AlignmentPattern confirmed = handlePossibleCenter(stateCount, i, j);
+                if (confirmed != null) {
+                  return confirmed;
+                }
+              }
+              stateCount[0] = stateCount[2];
+              stateCount[1] = 1;
+              stateCount[2] = 0;
+              currentState = 1;
+            } else {
+              stateCount[++currentState]++;
+            }
+          }
+        } else { // White pixel
+          if (currentState == 1) { // Counting black pixels
+            currentState++;
+          }
+          stateCount[currentState]++;
+        }
+        j++;
+      }
+      if (foundPatternCross(stateCount)) {
+        AlignmentPattern confirmed = handlePossibleCenter(stateCount, i, maxJ);
+        if (confirmed != null) {
+          return confirmed;
+        }
+      }
+
+    }
+
+    // Hmm, nothing we saw was observed and confirmed twice. If we had
+    // any guess at all, return it.
+    if (!possibleCenters.isEmpty()) {
+      return possibleCenters.get(0);
+    }
+
+    throw NotFoundException.getNotFoundInstance();
+  }
+
+  /**
+   * Given a count of black/white/black pixels just seen and an end position,
+   * figures the location of the center of this black/white/black run.
+   */
+  private static float centerFromEnd(int[] stateCount, int end) {
+    return end - stateCount[2] - stateCount[1] / 2.0f;
+  }
+
+  /**
+   * @param stateCount count of black/white/black pixels just read
+   * @return true iff the proportions of the counts is close enough to the 1/1/1 ratios
+   *         used by alignment patterns to be considered a match
+   */
+  private boolean foundPatternCross(int[] stateCount) {
+    float moduleSize = this.moduleSize;
+    float maxVariance = moduleSize / 2.0f;
+    for (int i = 0; i < 3; i++) {
+      if (Math.abs(moduleSize - stateCount[i]) >= maxVariance) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * <p>After a horizontal scan finds a potential alignment pattern, this method
+   * "cross-checks" by scanning down vertically through the center of the possible
+   * alignment pattern to see if the same proportion is detected.</p>
+   *
+   * @param startI row where an alignment pattern was detected
+   * @param centerJ center of the section that appears to cross an alignment pattern
+   * @param maxCount maximum reasonable number of modules that should be
+   * observed in any reading state, based on the results of the horizontal scan
+   * @return vertical center of alignment pattern, or {@link Float#NaN} if not found
+   */
+  private float crossCheckVertical(int startI, int centerJ, int maxCount,
+      int originalStateCountTotal) {
+    BitMatrix image = this.image;
+
+    int maxI = image.getHeight();
+    int[] stateCount = crossCheckStateCount;
+    stateCount[0] = 0;
+    stateCount[1] = 0;
+    stateCount[2] = 0;
+
+    // Start counting up from center
+    int i = startI;
+    while (i >= 0 && image.get(centerJ, i) && stateCount[1] <= maxCount) {
+      stateCount[1]++;
+      i--;
+    }
+    // If already too many modules in this state or ran off the edge:
+    if (i < 0 || stateCount[1] > maxCount) {
+      return Float.NaN;
+    }
+    while (i >= 0 && !image.get(centerJ, i) && stateCount[0] <= maxCount) {
+      stateCount[0]++;
+      i--;
+    }
+    if (stateCount[0] > maxCount) {
+      return Float.NaN;
+    }
+
+    // Now also count down from center
+    i = startI + 1;
+    while (i < maxI && image.get(centerJ, i) && stateCount[1] <= maxCount) {
+      stateCount[1]++;
+      i++;
+    }
+    if (i == maxI || stateCount[1] > maxCount) {
+      return Float.NaN;
+    }
+    while (i < maxI && !image.get(centerJ, i) && stateCount[2] <= maxCount) {
+      stateCount[2]++;
+      i++;
+    }
+    if (stateCount[2] > maxCount) {
+      return Float.NaN;
+    }
+
+    int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2];
+    if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) {
+      return Float.NaN;
+    }
+
+    return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : Float.NaN;
+  }
+
+  /**
+   * <p>This is called when a horizontal scan finds a possible alignment pattern. It will
+   * cross check with a vertical scan, and if successful, will see if this pattern had been
+   * found on a previous horizontal scan. If so, we consider it confirmed and conclude we have
+   * found the alignment pattern.</p>
+   *
+   * @param stateCount reading state module counts from horizontal scan
+   * @param i row where alignment pattern may be found
+   * @param j end of possible alignment pattern in row
+   * @return {@link AlignmentPattern} if we have found the same pattern twice, or null if not
+   */
+  private AlignmentPattern handlePossibleCenter(int[] stateCount, int i, int j) {
+    int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2];
+    float centerJ = centerFromEnd(stateCount, j);
+    float centerI = crossCheckVertical(i, (int) centerJ, 2 * stateCount[1], stateCountTotal);
+    if (!Float.isNaN(centerI)) {
+      float estimatedModuleSize = (stateCount[0] + stateCount[1] + stateCount[2]) / 3.0f;
+      for (AlignmentPattern center : possibleCenters) {
+        // Look for about the same center and module size:
+        if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
+          return center.combineEstimate(centerI, centerJ, estimatedModuleSize);
+        }
+      }
+      // Hadn't found this before; save it
+      AlignmentPattern point = new AlignmentPattern(centerJ, centerI, estimatedModuleSize);
+      possibleCenters.add(point);
+      if (resultPointCallback != null) {
+        resultPointCallback.foundPossibleResultPoint(point);
+      }
+    }
+    return null;
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/detector/Detector.java b/src/com/google/zxing/qrcode/detector/Detector.java
new file mode 100644 (file)
index 0000000..49cc383
--- /dev/null
@@ -0,0 +1,399 @@
+/*
+ * Copyright 2007 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.qrcode.detector;
+
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.common.GridSampler;
+import com.google.zxing.common.PerspectiveTransform;
+import com.google.zxing.common.detector.MathUtils;
+import com.google.zxing.qrcode.decoder.Version;
+
+import java.util.Map;
+
+/**
+ * <p>Encapsulates logic that can detect a QR Code in an image, even if the QR Code
+ * is rotated or skewed, or partially obscured.</p>
+ *
+ * @author Sean Owen
+ */
+public class Detector {
+
+  private final BitMatrix image;
+  private ResultPointCallback resultPointCallback;
+
+  public Detector(BitMatrix image) {
+    this.image = image;
+  }
+
+  protected final BitMatrix getImage() {
+    return image;
+  }
+
+  protected final ResultPointCallback getResultPointCallback() {
+    return resultPointCallback;
+  }
+
+  /**
+   * <p>Detects a QR Code in an image, simply.</p>
+   *
+   * @return {@link DetectorResult} encapsulating results of detecting a QR Code
+   * @throws NotFoundException if no QR Code can be found
+   */
+  public DetectorResult detect() throws NotFoundException, FormatException {
+    return detect(null);
+  }
+
+  /**
+   * <p>Detects a QR Code in an image, simply.</p>
+   *
+   * @param hints optional hints to detector
+   * @return {@link NotFoundException} encapsulating results of detecting a QR Code
+   * @throws NotFoundException if QR Code cannot be found
+   * @throws FormatException if a QR Code cannot be decoded
+   */
+  public final DetectorResult detect(Map<DecodeHintType,?> hints) throws NotFoundException, FormatException {
+
+    resultPointCallback = hints == null ? null :
+        (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
+
+    FinderPatternFinder finder = new FinderPatternFinder(image, resultPointCallback);
+    FinderPatternInfo info = finder.find(hints);
+
+    return processFinderPatternInfo(info);
+  }
+
+  protected final DetectorResult processFinderPatternInfo(FinderPatternInfo info)
+      throws NotFoundException, FormatException {
+
+    FinderPattern topLeft = info.getTopLeft();
+    FinderPattern topRight = info.getTopRight();
+    FinderPattern bottomLeft = info.getBottomLeft();
+
+    float moduleSize = calculateModuleSize(topLeft, topRight, bottomLeft);
+    if (moduleSize < 1.0f) {
+      throw NotFoundException.getNotFoundInstance();
+    }
+    int dimension = computeDimension(topLeft, topRight, bottomLeft, moduleSize);
+    Version provisionalVersion = Version.getProvisionalVersionForDimension(dimension);
+    int modulesBetweenFPCenters = provisionalVersion.getDimensionForVersion() - 7;
+
+    AlignmentPattern alignmentPattern = null;
+    // Anything above version 1 has an alignment pattern
+    if (provisionalVersion.getAlignmentPatternCenters().length > 0) {
+
+      // Guess where a "bottom right" finder pattern would have been
+      float bottomRightX = topRight.getX() - topLeft.getX() + bottomLeft.getX();
+      float bottomRightY = topRight.getY() - topLeft.getY() + bottomLeft.getY();
+
+      // Estimate that alignment pattern is closer by 3 modules
+      // from "bottom right" to known top left location
+      float correctionToTopLeft = 1.0f - 3.0f / (float) modulesBetweenFPCenters;
+      int estAlignmentX = (int) (topLeft.getX() + correctionToTopLeft * (bottomRightX - topLeft.getX()));
+      int estAlignmentY = (int) (topLeft.getY() + correctionToTopLeft * (bottomRightY - topLeft.getY()));
+
+      // Kind of arbitrary -- expand search radius before giving up
+      for (int i = 4; i <= 16; i <<= 1) {
+        try {
+          alignmentPattern = findAlignmentInRegion(moduleSize,
+              estAlignmentX,
+              estAlignmentY,
+              (float) i);
+          break;
+        } catch (NotFoundException re) {
+          // try next round
+        }
+      }
+      // If we didn't find alignment pattern... well try anyway without it
+    }
+
+    PerspectiveTransform transform =
+        createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension);
+
+    BitMatrix bits = sampleGrid(image, transform, dimension);
+
+    ResultPoint[] points;
+    if (alignmentPattern == null) {
+      points = new ResultPoint[]{bottomLeft, topLeft, topRight};
+    } else {
+      points = new ResultPoint[]{bottomLeft, topLeft, topRight, alignmentPattern};
+    }
+    return new DetectorResult(bits, points);
+  }
+
+  private static PerspectiveTransform createTransform(ResultPoint topLeft,
+                                                      ResultPoint topRight,
+                                                      ResultPoint bottomLeft,
+                                                      ResultPoint alignmentPattern,
+                                                      int dimension) {
+    float dimMinusThree = (float) dimension - 3.5f;
+    float bottomRightX;
+    float bottomRightY;
+    float sourceBottomRightX;
+    float sourceBottomRightY;
+    if (alignmentPattern != null) {
+      bottomRightX = alignmentPattern.getX();
+      bottomRightY = alignmentPattern.getY();
+      sourceBottomRightX = dimMinusThree - 3.0f;
+      sourceBottomRightY = sourceBottomRightX;
+    } else {
+      // Don't have an alignment pattern, just make up the bottom-right point
+      bottomRightX = (topRight.getX() - topLeft.getX()) + bottomLeft.getX();
+      bottomRightY = (topRight.getY() - topLeft.getY()) + bottomLeft.getY();
+      sourceBottomRightX = dimMinusThree;
+      sourceBottomRightY = dimMinusThree;
+    }
+
+    return PerspectiveTransform.quadrilateralToQuadrilateral(
+        3.5f,
+        3.5f,
+        dimMinusThree,
+        3.5f,
+        sourceBottomRightX,
+        sourceBottomRightY,
+        3.5f,
+        dimMinusThree,
+        topLeft.getX(),
+        topLeft.getY(),
+        topRight.getX(),
+        topRight.getY(),
+        bottomRightX,
+        bottomRightY,
+        bottomLeft.getX(),
+        bottomLeft.getY());
+  }
+
+  private static BitMatrix sampleGrid(BitMatrix image,
+                                      PerspectiveTransform transform,
+                                      int dimension) throws NotFoundException {
+
+    GridSampler sampler = GridSampler.getInstance();
+    return sampler.sampleGrid(image, dimension, dimension, transform);
+  }
+
+  /**
+   * <p>Computes the dimension (number of modules on a size) of the QR Code based on the position
+   * of the finder patterns and estimated module size.</p>
+   */
+  private static int computeDimension(ResultPoint topLeft,
+                                      ResultPoint topRight,
+                                      ResultPoint bottomLeft,
+                                      float moduleSize) throws NotFoundException {
+    int tltrCentersDimension = MathUtils.round(ResultPoint.distance(topLeft, topRight) / moduleSize);
+    int tlblCentersDimension = MathUtils.round(ResultPoint.distance(topLeft, bottomLeft) / moduleSize);
+    int dimension = ((tltrCentersDimension + tlblCentersDimension) >> 1) + 7;
+    switch (dimension & 0x03) { // mod 4
+      case 0:
+        dimension++;
+        break;
+        // 1? do nothing
+      case 2:
+        dimension--;
+        break;
+      case 3:
+        throw NotFoundException.getNotFoundInstance();
+    }
+    return dimension;
+  }
+
+  /**
+   * <p>Computes an average estimated module size based on estimated derived from the positions
+   * of the three finder patterns.</p>
+   */
+  protected final float calculateModuleSize(ResultPoint topLeft,
+                                            ResultPoint topRight,
+                                            ResultPoint bottomLeft) {
+    // Take the average
+    return (calculateModuleSizeOneWay(topLeft, topRight) +
+        calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0f;
+  }
+
+  /**
+   * <p>Estimates module size based on two finder patterns -- it uses
+   * {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the
+   * width of each, measuring along the axis between their centers.</p>
+   */
+  private float calculateModuleSizeOneWay(ResultPoint pattern, ResultPoint otherPattern) {
+    float moduleSizeEst1 = sizeOfBlackWhiteBlackRunBothWays((int) pattern.getX(),
+        (int) pattern.getY(),
+        (int) otherPattern.getX(),
+        (int) otherPattern.getY());
+    float moduleSizeEst2 = sizeOfBlackWhiteBlackRunBothWays((int) otherPattern.getX(),
+        (int) otherPattern.getY(),
+        (int) pattern.getX(),
+        (int) pattern.getY());
+    if (Float.isNaN(moduleSizeEst1)) {
+      return moduleSizeEst2 / 7.0f;
+    }
+    if (Float.isNaN(moduleSizeEst2)) {
+      return moduleSizeEst1 / 7.0f;
+    }
+    // Average them, and divide by 7 since we've counted the width of 3 black modules,
+    // and 1 white and 1 black module on either side. Ergo, divide sum by 14.
+    return (moduleSizeEst1 + moduleSizeEst2) / 14.0f;
+  }
+
+  /**
+   * See {@link #sizeOfBlackWhiteBlackRun(int, int, int, int)}; computes the total width of
+   * a finder pattern by looking for a black-white-black run from the center in the direction
+   * of another point (another finder pattern center), and in the opposite direction too.</p>
+   */
+  private float sizeOfBlackWhiteBlackRunBothWays(int fromX, int fromY, int toX, int toY) {
+
+    float result = sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY);
+
+    // Now count other way -- don't run off image though of course
+    float scale = 1.0f;
+    int otherToX = fromX - (toX - fromX);
+    if (otherToX < 0) {
+      scale = (float) fromX / (float) (fromX - otherToX);
+      otherToX = 0;
+    } else if (otherToX >= image.getWidth()) {
+      scale = (float) (image.getWidth() - 1 - fromX) / (float) (otherToX - fromX);
+      otherToX = image.getWidth() - 1;
+    }
+    int otherToY = (int) (fromY - (toY - fromY) * scale);
+
+    scale = 1.0f;
+    if (otherToY < 0) {
+      scale = (float) fromY / (float) (fromY - otherToY);
+      otherToY = 0;
+    } else if (otherToY >= image.getHeight()) {
+      scale = (float) (image.getHeight() - 1 - fromY) / (float) (otherToY - fromY);
+      otherToY = image.getHeight() - 1;
+    }
+    otherToX = (int) (fromX + (otherToX - fromX) * scale);
+
+    result += sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY);
+
+    // Middle pixel is double-counted this way; subtract 1
+    return result - 1.0f;
+  }
+
+  /**
+   * <p>This method traces a line from a point in the image, in the direction towards another point.
+   * It begins in a black region, and keeps going until it finds white, then black, then white again.
+   * It reports the distance from the start to this point.</p>
+   *
+   * <p>This is used when figuring out how wide a finder pattern is, when the finder pattern
+   * may be skewed or rotated.</p>
+   */
+  private float sizeOfBlackWhiteBlackRun(int fromX, int fromY, int toX, int toY) {
+    // Mild variant of Bresenham's algorithm;
+    // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
+    boolean steep = Math.abs(toY - fromY) > Math.abs(toX - fromX);
+    if (steep) {
+      int temp = fromX;
+      fromX = fromY;
+      fromY = temp;
+      temp = toX;
+      toX = toY;
+      toY = temp;
+    }
+
+    int dx = Math.abs(toX - fromX);
+    int dy = Math.abs(toY - fromY);
+    int error = -dx >> 1;
+    int xstep = fromX < toX ? 1 : -1;
+    int ystep = fromY < toY ? 1 : -1;
+
+    // In black pixels, looking for white, first or second time.
+    int state = 0;
+    // Loop up until x == toX, but not beyond
+    int xLimit = toX + xstep;
+    for (int x = fromX, y = fromY; x != xLimit; x += xstep) {
+      int realX = steep ? y : x;
+      int realY = steep ? x : y;
+
+      // Does current pixel mean we have moved white to black or vice versa?
+      // Scanning black in state 0,2 and white in state 1, so if we find the wrong
+      // color, advance to next state or end if we are in state 2 already
+      if ((state == 1) == image.get(realX, realY)) {
+        if (state == 2) {
+          return MathUtils.distance(x, y, fromX, fromY);
+        }
+        state++;
+      }
+
+      error += dy;
+      if (error > 0) {
+        if (y == toY) {
+          break;
+        }
+        y += ystep;
+        error -= dx;
+      }
+    }
+    // Found black-white-black; give the benefit of the doubt that the next pixel outside the image
+    // is "white" so this last point at (toX+xStep,toY) is the right ending. This is really a
+    // small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this.
+    if (state == 2) {
+      return MathUtils.distance(toX + xstep, toY, fromX, fromY);
+    }
+    // else we didn't find even black-white-black; no estimate is really possible
+    return Float.NaN;
+  }
+
+  /**
+   * <p>Attempts to locate an alignment pattern in a limited region of the image, which is
+   * guessed to contain it. This method uses {@link AlignmentPattern}.</p>
+   *
+   * @param overallEstModuleSize estimated module size so far
+   * @param estAlignmentX x coordinate of center of area probably containing alignment pattern
+   * @param estAlignmentY y coordinate of above
+   * @param allowanceFactor number of pixels in all directions to search from the center
+   * @return {@link AlignmentPattern} if found, or null otherwise
+   * @throws NotFoundException if an unexpected error occurs during detection
+   */
+  protected final AlignmentPattern findAlignmentInRegion(float overallEstModuleSize,
+                                                         int estAlignmentX,
+                                                         int estAlignmentY,
+                                                         float allowanceFactor)
+      throws NotFoundException {
+    // Look for an alignment pattern (3 modules in size) around where it
+    // should be
+    int allowance = (int) (allowanceFactor * overallEstModuleSize);
+    int alignmentAreaLeftX = Math.max(0, estAlignmentX - allowance);
+    int alignmentAreaRightX = Math.min(image.getWidth() - 1, estAlignmentX + allowance);
+    if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) {
+      throw NotFoundException.getNotFoundInstance();
+    }
+
+    int alignmentAreaTopY = Math.max(0, estAlignmentY - allowance);
+    int alignmentAreaBottomY = Math.min(image.getHeight() - 1, estAlignmentY + allowance);
+    if (alignmentAreaBottomY - alignmentAreaTopY < overallEstModuleSize * 3) {
+      throw NotFoundException.getNotFoundInstance();
+    }
+
+    AlignmentPatternFinder alignmentFinder =
+        new AlignmentPatternFinder(
+            image,
+            alignmentAreaLeftX,
+            alignmentAreaTopY,
+            alignmentAreaRightX - alignmentAreaLeftX,
+            alignmentAreaBottomY - alignmentAreaTopY,
+            overallEstModuleSize,
+            resultPointCallback);
+    return alignmentFinder.find();
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/detector/FinderPattern.java b/src/com/google/zxing/qrcode/detector/FinderPattern.java
new file mode 100644 (file)
index 0000000..a64e7c2
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2007 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.qrcode.detector;
+
+import com.google.zxing.ResultPoint;
+
+/**
+ * <p>Encapsulates a finder pattern, which are the three square patterns found in
+ * the corners of QR Codes. It also encapsulates a count of similar finder patterns,
+ * as a convenience to the finder's bookkeeping.</p>
+ *
+ * @author Sean Owen
+ */
+public final class FinderPattern extends ResultPoint {
+
+  private final float estimatedModuleSize;
+  private final int count;
+
+  FinderPattern(float posX, float posY, float estimatedModuleSize) {
+    this(posX, posY, estimatedModuleSize, 1);
+  }
+
+  private FinderPattern(float posX, float posY, float estimatedModuleSize, int count) {
+    super(posX, posY);
+    this.estimatedModuleSize = estimatedModuleSize;
+    this.count = count;
+  }
+
+  public float getEstimatedModuleSize() {
+    return estimatedModuleSize;
+  }
+
+  int getCount() {
+    return count;
+  }
+
+  /*
+  void incrementCount() {
+    this.count++;
+  }
+   */
+
+  /**
+   * <p>Determines if this finder pattern "about equals" a finder pattern at the stated
+   * position and size -- meaning, it is at nearly the same center with nearly the same size.</p>
+   */
+  boolean aboutEquals(float moduleSize, float i, float j) {
+    if (Math.abs(i - getY()) <= moduleSize && Math.abs(j - getX()) <= moduleSize) {
+      float moduleSizeDiff = Math.abs(moduleSize - estimatedModuleSize);
+      return moduleSizeDiff <= 1.0f || moduleSizeDiff <= estimatedModuleSize;
+    }
+    return false;
+  }
+
+  /**
+   * Combines this object's current estimate of a finder pattern position and module size
+   * with a new estimate. It returns a new {@code FinderPattern} containing a weighted average
+   * based on count.
+   */
+  FinderPattern combineEstimate(float i, float j, float newModuleSize) {
+    int combinedCount = count + 1;
+    float combinedX = (count * getX() + j) / combinedCount;
+    float combinedY = (count * getY() + i) / combinedCount;
+    float combinedModuleSize = (count * estimatedModuleSize + newModuleSize) / combinedCount;
+    return new FinderPattern(combinedX, combinedY, combinedModuleSize, combinedCount);
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/detector/FinderPatternFinder.java b/src/com/google/zxing/qrcode/detector/FinderPatternFinder.java
new file mode 100755 (executable)
index 0000000..e1af262
--- /dev/null
@@ -0,0 +1,585 @@
+/*
+ * Copyright 2007 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.qrcode.detector;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * <p>This class attempts to find finder patterns in a QR Code. Finder patterns are the square
+ * markers at three corners of a QR Code.</p>
+ *
+ * <p>This class is thread-safe but not reentrant. Each thread must allocate its own object.
+ *
+ * @author Sean Owen
+ */
+public class FinderPatternFinder {
+
+  private static final int CENTER_QUORUM = 2;
+  protected static final int MIN_SKIP = 3; // 1 pixel/module times 3 modules/center
+  protected static final int MAX_MODULES = 57; // support up to version 10 for mobile clients
+  private static final int INTEGER_MATH_SHIFT = 8;
+
+  private final BitMatrix image;
+  private final List<FinderPattern> possibleCenters;
+  private boolean hasSkipped;
+  private final int[] crossCheckStateCount;
+  private final ResultPointCallback resultPointCallback;
+
+  /**
+   * <p>Creates a finder that will search the image for three finder patterns.</p>
+   *
+   * @param image image to search
+   */
+  public FinderPatternFinder(BitMatrix image) {
+    this(image, null);
+  }
+
+  public FinderPatternFinder(BitMatrix image, ResultPointCallback resultPointCallback) {
+    this.image = image;
+    this.possibleCenters = new ArrayList<FinderPattern>();
+    this.crossCheckStateCount = new int[5];
+    this.resultPointCallback = resultPointCallback;
+  }
+
+  protected final BitMatrix getImage() {
+    return image;
+  }
+
+  protected final List<FinderPattern> getPossibleCenters() {
+    return possibleCenters;
+  }
+
+  final FinderPatternInfo find(Map<DecodeHintType,?> hints) throws NotFoundException {
+    boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
+    int maxI = image.getHeight();
+    int maxJ = image.getWidth();
+    // We are looking for black/white/black/white/black modules in
+    // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far
+
+    // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the
+    // image, and then account for the center being 3 modules in size. This gives the smallest
+    // number of pixels the center could be, so skip this often. When trying harder, look for all
+    // QR versions regardless of how dense they are.
+    int iSkip = (3 * maxI) / (4 * MAX_MODULES);
+    if (iSkip < MIN_SKIP || tryHarder) {
+      iSkip = MIN_SKIP;
+    }
+
+    boolean done = false;
+    int[] stateCount = new int[5];
+    for (int i = iSkip - 1; i < maxI && !done; i += iSkip) {
+      // Get a row of black/white values
+      stateCount[0] = 0;
+      stateCount[1] = 0;
+      stateCount[2] = 0;
+      stateCount[3] = 0;
+      stateCount[4] = 0;
+      int currentState = 0;
+      for (int j = 0; j < maxJ; j++) {
+        if (image.get(j, i)) {
+          // Black pixel
+          if ((currentState & 1) == 1) { // Counting white pixels
+            currentState++;
+          }
+          stateCount[currentState]++;
+        } else { // White pixel
+          if ((currentState & 1) == 0) { // Counting black pixels
+            if (currentState == 4) { // A winner?
+              if (foundPatternCross(stateCount)) { // Yes
+                boolean confirmed = handlePossibleCenter(stateCount, i, j);
+                if (confirmed) {
+                  // Start examining every other line. Checking each line turned out to be too
+                  // expensive and didn't improve performance.
+                  iSkip = 2;
+                  if (hasSkipped) {
+                    done = haveMultiplyConfirmedCenters();
+                  } else {
+                    int rowSkip = findRowSkip();
+                    if (rowSkip > stateCount[2]) {
+                      // Skip rows between row of lower confirmed center
+                      // and top of presumed third confirmed center
+                      // but back up a bit to get a full chance of detecting
+                      // it, entire width of center of finder pattern
+
+                      // Skip by rowSkip, but back off by stateCount[2] (size of last center
+                      // of pattern we saw) to be conservative, and also back off by iSkip which
+                      // is about to be re-added
+                      i += rowSkip - stateCount[2] - iSkip;
+                      j = maxJ - 1;
+                    }
+                  }
+                } else {
+                  stateCount[0] = stateCount[2];
+                  stateCount[1] = stateCount[3];
+                  stateCount[2] = stateCount[4];
+                  stateCount[3] = 1;
+                  stateCount[4] = 0;
+                  currentState = 3;
+                  continue;
+                }
+                // Clear state to start looking again
+                currentState = 0;
+                stateCount[0] = 0;
+                stateCount[1] = 0;
+                stateCount[2] = 0;
+                stateCount[3] = 0;
+                stateCount[4] = 0;
+              } else { // No, shift counts back by two
+                stateCount[0] = stateCount[2];
+                stateCount[1] = stateCount[3];
+                stateCount[2] = stateCount[4];
+                stateCount[3] = 1;
+                stateCount[4] = 0;
+                currentState = 3;
+              }
+            } else {
+              stateCount[++currentState]++;
+            }
+          } else { // Counting white pixels
+            stateCount[currentState]++;
+          }
+        }
+      }
+      if (foundPatternCross(stateCount)) {
+        boolean confirmed = handlePossibleCenter(stateCount, i, maxJ);
+        if (confirmed) {
+          iSkip = stateCount[0];
+          if (hasSkipped) {
+            // Found a third one
+            done = haveMultiplyConfirmedCenters();
+          }
+        }
+      }
+    }
+
+    FinderPattern[] patternInfo = selectBestPatterns();
+    ResultPoint.orderBestPatterns(patternInfo);
+
+    return new FinderPatternInfo(patternInfo);
+  }
+
+  /**
+   * Given a count of black/white/black/white/black pixels just seen and an end position,
+   * figures the location of the center of this run.
+   */
+  private static float centerFromEnd(int[] stateCount, int end) {
+    return end - stateCount[4] - stateCount[3] - stateCount[2] / 2.0f;
+  }
+
+  /**
+   * @param stateCount count of black/white/black/white/black pixels just read
+   * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios
+   *         used by finder patterns to be considered a match
+   */
+  protected static boolean foundPatternCross(int[] stateCount) {
+    int totalModuleSize = 0;
+    for (int i = 0; i < 5; i++) {
+      int count = stateCount[i];
+      if (count == 0) {
+        return false;
+      }
+      totalModuleSize += count;
+    }
+    if (totalModuleSize < 7) {
+      return false;
+    }
+    int moduleSize = (totalModuleSize << INTEGER_MATH_SHIFT) / 7;
+    int maxVariance = moduleSize / 2;
+    // Allow less than 50% variance from 1-1-3-1-1 proportions
+    return Math.abs(moduleSize - (stateCount[0] << INTEGER_MATH_SHIFT)) < maxVariance &&
+        Math.abs(moduleSize - (stateCount[1] << INTEGER_MATH_SHIFT)) < maxVariance &&
+        Math.abs(3 * moduleSize - (stateCount[2] << INTEGER_MATH_SHIFT)) < 3 * maxVariance &&
+        Math.abs(moduleSize - (stateCount[3] << INTEGER_MATH_SHIFT)) < maxVariance &&
+        Math.abs(moduleSize - (stateCount[4] << INTEGER_MATH_SHIFT)) < maxVariance;
+  }
+
+  private int[] getCrossCheckStateCount() {
+    crossCheckStateCount[0] = 0;
+    crossCheckStateCount[1] = 0;
+    crossCheckStateCount[2] = 0;
+    crossCheckStateCount[3] = 0;
+    crossCheckStateCount[4] = 0;
+    return crossCheckStateCount;
+  }
+
+  /**
+   * <p>After a horizontal scan finds a potential finder pattern, this method
+   * "cross-checks" by scanning down vertically through the center of the possible
+   * finder pattern to see if the same proportion is detected.</p>
+   *
+   * @param startI row where a finder pattern was detected
+   * @param centerJ center of the section that appears to cross a finder pattern
+   * @param maxCount maximum reasonable number of modules that should be
+   * observed in any reading state, based on the results of the horizontal scan
+   * @return vertical center of finder pattern, or {@link Float#NaN} if not found
+   */
+  private float crossCheckVertical(int startI, int centerJ, int maxCount,
+      int originalStateCountTotal) {
+    BitMatrix image = this.image;
+
+    int maxI = image.getHeight();
+    int[] stateCount = getCrossCheckStateCount();
+
+    // Start counting up from center
+    int i = startI;
+    while (i >= 0 && image.get(centerJ, i)) {
+      stateCount[2]++;
+      i--;
+    }
+    if (i < 0) {
+      return Float.NaN;
+    }
+    while (i >= 0 && !image.get(centerJ, i) && stateCount[1] <= maxCount) {
+      stateCount[1]++;
+      i--;
+    }
+    // If already too many modules in this state or ran off the edge:
+    if (i < 0 || stateCount[1] > maxCount) {
+      return Float.NaN;
+    }
+    while (i >= 0 && image.get(centerJ, i) && stateCount[0] <= maxCount) {
+      stateCount[0]++;
+      i--;
+    }
+    if (stateCount[0] > maxCount) {
+      return Float.NaN;
+    }
+
+    // Now also count down from center
+    i = startI + 1;
+    while (i < maxI && image.get(centerJ, i)) {
+      stateCount[2]++;
+      i++;
+    }
+    if (i == maxI) {
+      return Float.NaN;
+    }
+    while (i < maxI && !image.get(centerJ, i) && stateCount[3] < maxCount) {
+      stateCount[3]++;
+      i++;
+    }
+    if (i == maxI || stateCount[3] >= maxCount) {
+      return Float.NaN;
+    }
+    while (i < maxI && image.get(centerJ, i) && stateCount[4] < maxCount) {
+      stateCount[4]++;
+      i++;
+    }
+    if (stateCount[4] >= maxCount) {
+      return Float.NaN;
+    }
+
+    // If we found a finder-pattern-like section, but its size is more than 40% different than
+    // the original, assume it's a false positive
+    int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] +
+        stateCount[4];
+    if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) {
+      return Float.NaN;
+    }
+
+    return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : Float.NaN;
+  }
+
+  /**
+   * <p>Like {@link #crossCheckVertical(int, int, int, int)}, and in fact is basically identical,
+   * except it reads horizontally instead of vertically. This is used to cross-cross
+   * check a vertical cross check and locate the real center of the alignment pattern.</p>
+   */
+  private float crossCheckHorizontal(int startJ, int centerI, int maxCount,
+      int originalStateCountTotal) {
+    BitMatrix image = this.image;
+
+    int maxJ = image.getWidth();
+    int[] stateCount = getCrossCheckStateCount();
+
+    int j = startJ;
+    while (j >= 0 && image.get(j, centerI)) {
+      stateCount[2]++;
+      j--;
+    }
+    if (j < 0) {
+      return Float.NaN;
+    }
+    while (j >= 0 && !image.get(j, centerI) && stateCount[1] <= maxCount) {
+      stateCount[1]++;
+      j--;
+    }
+    if (j < 0 || stateCount[1] > maxCount) {
+      return Float.NaN;
+    }
+    while (j >= 0 && image.get(j, centerI) && stateCount[0] <= maxCount) {
+      stateCount[0]++;
+      j--;
+    }
+    if (stateCount[0] > maxCount) {
+      return Float.NaN;
+    }
+
+    j = startJ + 1;
+    while (j < maxJ && image.get(j, centerI)) {
+      stateCount[2]++;
+      j++;
+    }
+    if (j == maxJ) {
+      return Float.NaN;
+    }
+    while (j < maxJ && !image.get(j, centerI) && stateCount[3] < maxCount) {
+      stateCount[3]++;
+      j++;
+    }
+    if (j == maxJ || stateCount[3] >= maxCount) {
+      return Float.NaN;
+    }
+    while (j < maxJ && image.get(j, centerI) && stateCount[4] < maxCount) {
+      stateCount[4]++;
+      j++;
+    }
+    if (stateCount[4] >= maxCount) {
+      return Float.NaN;
+    }
+
+    // If we found a finder-pattern-like section, but its size is significantly different than
+    // the original, assume it's a false positive
+    int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] +
+        stateCount[4];
+    if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) {
+      return Float.NaN;
+    }
+
+    return foundPatternCross(stateCount) ? centerFromEnd(stateCount, j) : Float.NaN;
+  }
+
+  /**
+   * <p>This is called when a horizontal scan finds a possible alignment pattern. It will
+   * cross check with a vertical scan, and if successful, will, ah, cross-cross-check
+   * with another horizontal scan. This is needed primarily to locate the real horizontal
+   * center of the pattern in cases of extreme skew.</p>
+   *
+   * <p>If that succeeds the finder pattern location is added to a list that tracks
+   * the number of times each location has been nearly-matched as a finder pattern.
+   * Each additional find is more evidence that the location is in fact a finder
+   * pattern center
+   *
+   * @param stateCount reading state module counts from horizontal scan
+   * @param i row where finder pattern may be found
+   * @param j end of possible finder pattern in row
+   * @return true if a finder pattern candidate was found this time
+   */
+  protected final boolean handlePossibleCenter(int[] stateCount, int i, int j) {
+    int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] +
+        stateCount[4];
+    float centerJ = centerFromEnd(stateCount, j);
+    float centerI = crossCheckVertical(i, (int) centerJ, stateCount[2], stateCountTotal);
+    if (!Float.isNaN(centerI)) {
+      // Re-cross check
+      centerJ = crossCheckHorizontal((int) centerJ, (int) centerI, stateCount[2], stateCountTotal);
+      if (!Float.isNaN(centerJ)) {
+        float estimatedModuleSize = stateCountTotal / 7.0f;
+        boolean found = false;
+        for (int index = 0; index < possibleCenters.size(); index++) {
+          FinderPattern center = possibleCenters.get(index);
+          // Look for about the same center and module size:
+          if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
+            possibleCenters.set(index, center.combineEstimate(centerI, centerJ, estimatedModuleSize));
+            found = true;
+            break;
+          }
+        }
+        if (!found) {
+          FinderPattern point = new FinderPattern(centerJ, centerI, estimatedModuleSize);
+          possibleCenters.add(point);
+          if (resultPointCallback != null) {
+            resultPointCallback.foundPossibleResultPoint(point);
+          }
+        }
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * @return number of rows we could safely skip during scanning, based on the first
+   *         two finder patterns that have been located. In some cases their position will
+   *         allow us to infer that the third pattern must lie below a certain point farther
+   *         down in the image.
+   */
+  private int findRowSkip() {
+    int max = possibleCenters.size();
+    if (max <= 1) {
+      return 0;
+    }
+    ResultPoint firstConfirmedCenter = null;
+    for (FinderPattern center : possibleCenters) {
+      if (center.getCount() >= CENTER_QUORUM) {
+        if (firstConfirmedCenter == null) {
+          firstConfirmedCenter = center;
+        } else {
+          // We have two confirmed centers
+          // How far down can we skip before resuming looking for the next
+          // pattern? In the worst case, only the difference between the
+          // difference in the x / y coordinates of the two centers.
+          // This is the case where you find top left last.
+          hasSkipped = true;
+          return (int) (Math.abs(firstConfirmedCenter.getX() - center.getX()) -
+              Math.abs(firstConfirmedCenter.getY() - center.getY())) / 2;
+        }
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * @return true iff we have found at least 3 finder patterns that have been detected
+   *         at least {@link #CENTER_QUORUM} times each, and, the estimated module size of the
+   *         candidates is "pretty similar"
+   */
+  private boolean haveMultiplyConfirmedCenters() {
+    int confirmedCount = 0;
+    float totalModuleSize = 0.0f;
+    int max = possibleCenters.size();
+    for (FinderPattern pattern : possibleCenters) {
+      if (pattern.getCount() >= CENTER_QUORUM) {
+        confirmedCount++;
+        totalModuleSize += pattern.getEstimatedModuleSize();
+      }
+    }
+    if (confirmedCount < 3) {
+      return false;
+    }
+    // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive"
+    // and that we need to keep looking. We detect this by asking if the estimated module sizes
+    // vary too much. We arbitrarily say that when the total deviation from average exceeds
+    // 5% of the total module size estimates, it's too much.
+    float average = totalModuleSize / max;
+    float totalDeviation = 0.0f;
+    for (FinderPattern pattern : possibleCenters) {
+      totalDeviation += Math.abs(pattern.getEstimatedModuleSize() - average);
+    }
+    return totalDeviation <= 0.05f * totalModuleSize;
+  }
+
+  /**
+   * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are
+   *         those that have been detected at least {@link #CENTER_QUORUM} times, and whose module
+   *         size differs from the average among those patterns the least
+   * @throws NotFoundException if 3 such finder patterns do not exist
+   */
+  private FinderPattern[] selectBestPatterns() throws NotFoundException {
+
+    int startSize = possibleCenters.size();
+    if (startSize < 3) {
+      // Couldn't find enough finder patterns
+      throw NotFoundException.getNotFoundInstance();
+    }
+
+    // Filter outlier possibilities whose module size is too different
+    if (startSize > 3) {
+      // But we can only afford to do so if we have at least 4 possibilities to choose from
+      float totalModuleSize = 0.0f;
+      float square = 0.0f;
+      for (FinderPattern center : possibleCenters) {
+        float size = center.getEstimatedModuleSize();
+        totalModuleSize += size;
+        square += size * size;
+      }
+      float average = totalModuleSize / startSize;
+      float stdDev = (float) Math.sqrt(square / startSize - average * average);
+
+      Collections.sort(possibleCenters, new FurthestFromAverageComparator(average));
+
+      float limit = Math.max(0.2f * average, stdDev);
+
+      for (int i = 0; i < possibleCenters.size() && possibleCenters.size() > 3; i++) {
+        FinderPattern pattern = possibleCenters.get(i);
+        if (Math.abs(pattern.getEstimatedModuleSize() - average) > limit) {
+          possibleCenters.remove(i);
+          i--;
+        }
+      }
+    }
+
+    if (possibleCenters.size() > 3) {
+      // Throw away all but those first size candidate points we found.
+
+      float totalModuleSize = 0.0f;
+      for (FinderPattern possibleCenter : possibleCenters) {
+        totalModuleSize += possibleCenter.getEstimatedModuleSize();
+      }
+
+      float average = totalModuleSize / possibleCenters.size();
+
+      Collections.sort(possibleCenters, new CenterComparator(average));
+
+      possibleCenters.subList(3, possibleCenters.size()).clear();
+    }
+
+    return new FinderPattern[]{
+        possibleCenters.get(0),
+        possibleCenters.get(1),
+        possibleCenters.get(2)
+    };
+  }
+
+  /**
+   * <p>Orders by furthest from average</p>
+   */
+  private static final class FurthestFromAverageComparator implements Comparator<FinderPattern>, Serializable {
+    private final float average;
+    private FurthestFromAverageComparator(float f) {
+      average = f;
+    }
+    @Override
+    public int compare(FinderPattern center1, FinderPattern center2) {
+      float dA = Math.abs(center2.getEstimatedModuleSize() - average);
+      float dB = Math.abs(center1.getEstimatedModuleSize() - average);
+      return dA < dB ? -1 : dA == dB ? 0 : 1;
+    }
+  }
+
+  /**
+   * <p>Orders by {@link FinderPattern#getCount()}, descending.</p>
+   */
+  private static final class CenterComparator implements Comparator<FinderPattern>, Serializable {
+    private final float average;
+    private CenterComparator(float f) {
+      average = f;
+    }
+    @Override
+    public int compare(FinderPattern center1, FinderPattern center2) {
+      if (center2.getCount() == center1.getCount()) {
+        float dA = Math.abs(center2.getEstimatedModuleSize() - average);
+        float dB = Math.abs(center1.getEstimatedModuleSize() - average);
+        return dA < dB ? 1 : dA == dB ? 0 : -1;
+      } else {
+        return center2.getCount() - center1.getCount();
+      }
+    }
+  }
+
+}
diff --git a/src/com/google/zxing/qrcode/detector/FinderPatternInfo.java b/src/com/google/zxing/qrcode/detector/FinderPatternInfo.java
new file mode 100644 (file)
index 0000000..3c34010
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2007 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.qrcode.detector;
+
+/**
+ * <p>Encapsulates information about finder patterns in an image, including the location of
+ * the three finder patterns, and their estimated module size.</p>
+ *
+ * @author Sean Owen
+ */
+public final class FinderPatternInfo {
+
+  private final FinderPattern bottomLeft;
+  private final FinderPattern topLeft;
+  private final FinderPattern topRight;
+
+  public FinderPatternInfo(FinderPattern[] patternCenters) {
+    this.bottomLeft = patternCenters[0];
+    this.topLeft = patternCenters[1];
+    this.topRight = patternCenters[2];
+  }
+
+  public FinderPattern getBottomLeft() {
+    return bottomLeft;
+  }
+
+  public FinderPattern getTopLeft() {
+    return topLeft;
+  }
+
+  public FinderPattern getTopRight() {
+    return topRight;
+  }
+
+}
diff --git a/src/org/fedorahosted/freeotp/CameraDialogFragment.java b/src/org/fedorahosted/freeotp/CameraDialogFragment.java
new file mode 100644 (file)
index 0000000..5fe6448
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * FreeOTP
+ *
+ * Authors: Nathaniel McCallum <npmccallum@redhat.com>
+ *
+ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
+ *
+ * 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 org.fedorahosted.freeotp;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.hardware.Camera;
+import android.hardware.Camera.Parameters;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+public class CameraDialogFragment extends BaseAlertDialogFragment implements SurfaceHolder.Callback {
+       public static final String FRAGMENT_TAG = "fragment_camera";
+
+       private DecodeAsyncTask mDecodeAsyncTask;
+       private Camera mCamera;
+
+       public CameraDialogFragment() {
+               super(R.string.scan_qr_code, R.layout.camera,
+                       android.R.string.cancel, R.string.manual_entry, 0);
+       }
+
+       @Override
+       protected void onViewInflated(View view) {
+               SurfaceView sv = (SurfaceView) view.findViewById(R.id.camera_surfaceview);
+               sv.getHolder().addCallback(this);
+       }
+
+       @Override
+       public void onClick(DialogInterface dialog, int which) {
+               if (which == AlertDialog.BUTTON_NEUTRAL) {
+                       new AddTokenDialog(getActivity()) {
+                               @Override
+                               public void addToken(String uri) {
+                                       ((MainActivity) getActivity()).tokenURIReceived(uri);
+                               }
+                       }.show();
+               }
+       }
+
+       @Override
+       public void onDestroyView() {
+               if (mDecodeAsyncTask != null)
+                       mDecodeAsyncTask.cancel(true);
+
+               super.onDestroyView();
+       }
+
+       @Override
+       public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+               if (mCamera == null)
+                       return;
+
+               WindowManager wm = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
+               switch (wm.getDefaultDisplay().getRotation()) {
+               case Surface.ROTATION_0:
+                       mCamera.setDisplayOrientation(90);
+                       break;
+               case Surface.ROTATION_270:
+                       mCamera.setDisplayOrientation(180);
+                       break;
+               }
+
+               mCamera.startPreview();
+       }
+
+       @Override
+       public void surfaceCreated(SurfaceHolder holder) {
+               try {
+                       mCamera = Camera.open();
+                       mCamera.setPreviewDisplay(holder);
+
+                       // Create the decoder thread
+                       mDecodeAsyncTask = new DecodeAsyncTask() {
+                               @Override
+                               protected void onPostExecute(String result) {
+                                       super.onPostExecute(result);
+                                       if (result != null)
+                                               ((MainActivity) getActivity()).tokenURIReceived(result);
+                                       mDecodeAsyncTask = null;
+                                       dismiss();
+                               }
+                       };
+                       mDecodeAsyncTask.execute();
+                       mCamera.setPreviewCallback(mDecodeAsyncTask);
+
+                       // Set auto-focus mode
+                       Parameters params = mCamera.getParameters();
+                       params.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
+                       mCamera.setParameters(params);
+               } catch (Exception e) {
+                       SurfaceView sv = (SurfaceView) getDialog().findViewById(R.id.camera_surfaceview);
+                       sv.setVisibility(View.INVISIBLE);
+                       TextView tv = (TextView) getDialog().findViewById(R.id.camera_textview);
+                       tv.setVisibility(View.VISIBLE);
+                       e.printStackTrace();
+               }
+       }
+
+       @Override
+       public void surfaceDestroyed(SurfaceHolder holder) {
+               if (mCamera == null)
+                       return;
+
+               mCamera.stopPreview();
+               mCamera.setPreviewCallback(null);
+               mCamera.release();
+               mCamera = null;
+       }
+}
diff --git a/src/org/fedorahosted/freeotp/DecodeAsyncTask.java b/src/org/fedorahosted/freeotp/DecodeAsyncTask.java
new file mode 100644 (file)
index 0000000..4f77266
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * FreeOTP
+ *
+ * Authors: Nathaniel McCallum <npmccallum@redhat.com>
+ *
+ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
+ *
+ * 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 org.fedorahosted.freeotp;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import android.hardware.Camera;
+import android.hardware.Camera.PreviewCallback;
+import android.os.AsyncTask;
+
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.ChecksumException;
+import com.google.zxing.FormatException;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.PlanarYUVLuminanceSource;
+import com.google.zxing.Reader;
+import com.google.zxing.Result;
+import com.google.zxing.common.HybridBinarizer;
+import com.google.zxing.qrcode.QRCodeReader;
+
+public class DecodeAsyncTask extends AsyncTask<Void, Void, String> implements PreviewCallback {
+       private static class Data {
+               public byte[] data;
+               Camera.Size size;
+       }
+
+       private final BlockingQueue<Data> mBlockingQueue;
+       private final Reader mReader;
+
+       public DecodeAsyncTask() {
+               mBlockingQueue = new LinkedBlockingQueue<Data>(5);
+               mReader = new QRCodeReader();
+       }
+
+       @Override
+       protected String doInBackground(Void... args) {
+               while (true) {
+                       try {
+                               Data data = mBlockingQueue.take();
+                               LuminanceSource ls = new PlanarYUVLuminanceSource(data.data,
+                                                                       data.size.width, data.size.height, 0, 0,
+                                                                       data.size.width, data.size.height, false);
+                               Result r = mReader.decode(new BinaryBitmap(new HybridBinarizer(ls)));
+                               return r.getText();
+                       } catch (InterruptedException e) {
+                               return null;
+                       } catch (NotFoundException e) {
+                       } catch (ChecksumException e) {
+                       } catch (FormatException e) {
+                       } catch (ArrayIndexOutOfBoundsException e) {
+                       } finally {
+                               mReader.reset();
+                       }
+               }
+       }
+
+       @Override
+       public void onPreviewFrame(byte[] data, Camera camera) {
+               Data d = new Data();
+               d.data = data;
+               d.size = camera.getParameters().getPreviewSize();
+               mBlockingQueue.offer(d);
+       }
+}
index 6ffe093c583a44160e0cf653d2d4b3560a9efb9d..56737ff64b9709abb90c1bd45d72dbc6ce0184a3 100644 (file)
 
 package org.fedorahosted.freeotp;
 
-import java.util.Arrays;
-import java.util.List;
-
 import org.fedorahosted.freeotp.Token.TokenUriInvalidException;
 import org.fedorahosted.freeotp.adapters.TokenAdapter;
 
 import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.ActivityNotFoundException;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.database.DataSetObserver;
-import android.net.Uri;
 import android.os.Bundle;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -60,140 +50,82 @@ import android.view.View;
 import android.widget.GridView;
 import android.widget.Toast;
 
-public class MainActivity extends Activity {
-       private static final String ACTION_SCAN = "com.google.zxing.client.android.SCAN";
-       private static final List<String> PROVIDERS = Arrays.asList(new String[] {
-               "com.google.zxing.client.android", // Barcode Scanner
-               "com.srowen.bs.android",           // Barcode Scanner+
-               "com.srowen.bs.android.simple",    // Barcode Scanner+ Simple
-               "com.google.android.apps.unveil"   // Google Goggles
-       });
-
-       private TokenAdapter ta;
-
-       private String findAppPackage(Intent i) {
-               PackageManager pm = getPackageManager();
-               List<ResolveInfo> ril = pm.queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY);
-               if (ril != null) {
-                       for (ResolveInfo ri : ril) {
-                               if (PROVIDERS.contains(ri.activityInfo.packageName))
-                                       return ri.activityInfo.packageName;
-                       }
-               }
-
-               return null;
-       }
+public class MainActivity extends Activity implements OnMenuItemClickListener {
+       private TokenAdapter mTokenAdapter;
+       private DataSetObserver mDataSetObserver;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);
 
-               ta = new TokenAdapter(this);
-               ((GridView) findViewById(R.id.grid)).setAdapter(ta);
+               mTokenAdapter = new TokenAdapter(this);
+               ((GridView) findViewById(R.id.grid)).setAdapter(mTokenAdapter);
 
-               DataSetObserver dso = new DataSetObserver() {
+               mDataSetObserver = new DataSetObserver() {
                        @Override
                        public void onChanged() {
                                super.onChanged();
-                               if (ta.getCount() == 0)
+                               if (mTokenAdapter.getCount() == 0)
                                        findViewById(android.R.id.empty).setVisibility(View.VISIBLE);
                                else
                                        findViewById(android.R.id.empty).setVisibility(View.GONE);
                        }
                };
-               ta.registerDataSetObserver(dso);
-               dso.onChanged();
+               mTokenAdapter.registerDataSetObserver(mDataSetObserver);
+               mDataSetObserver.onChanged();
     }
 
+       @Override
+       protected void onDestroy() {
+               super.onDestroy();
+               mTokenAdapter.unregisterDataSetObserver(mDataSetObserver);
+       }
+
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         getMenuInflater().inflate(R.menu.main, menu);
+        menu.findItem(R.id.action_add).setOnMenuItemClickListener(this);
+               menu.findItem(R.id.action_about).setOnMenuItemClickListener(this);
+        return true;
+    }
 
-        menu.findItem(R.id.action_add).setOnMenuItemClickListener(new OnMenuItemClickListener() {
-                       @Override
-                       public boolean onMenuItemClick(MenuItem item) {
-                               AlertDialog ad = new AddTokenDialog(MainActivity.this) {
+       @Override
+       public boolean onMenuItemClick(MenuItem item) {
+               switch (item.getItemId()) {
+               case R.id.action_add:
+                       // If the device has a camera available, try to scan for QR code
+                       PackageManager pm = getPackageManager();
+                       if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) &&
+                               pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS)) {
+                               new CameraDialogFragment().show(getFragmentManager(),
+                                               CameraDialogFragment.FRAGMENT_TAG);
+                       } else {
+                               new AddTokenDialog(this) {
                                        @Override
                                        public void addToken(String uri) {
-                                               try {
-                                                       ta.add(uri);
-                                               } catch (TokenUriInvalidException e) {
-                                                       Toast.makeText(MainActivity.this, R.string.invalid_token, Toast.LENGTH_SHORT).show();
-                                                       e.printStackTrace();
-                                               }
-                                       }
-                               };
-
-                               ad.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.scan_qr_code), new OnClickListener() {
-                                       @Override
-                                       public void onClick(DialogInterface dialog, int which) {
-                                               Intent i = new Intent(ACTION_SCAN);
-                                               i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
-                                               i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-                                               i.addCategory(Intent.CATEGORY_DEFAULT);
-                                               i.putExtra("SCAN_MODE", "QR_CODE_MODE");
-                                               i.putExtra("SAVE_HISTORY", false);
-
-                                               String pkg = findAppPackage(i);
-                                               if (pkg != null) {
-                                                       i.setPackage(pkg);
-                                                       startActivityForResult(i, 0);
-                                                       return;
-                                               }
-
-                                               new AlertDialog.Builder(MainActivity.this)
-                                                       .setTitle(R.string.install_title)
-                                                       .setMessage(R.string.install_message)
-                                                       .setPositiveButton(R.string.yes, new OnClickListener() {
-                                                               @Override
-                                                               public void onClick(DialogInterface dialogInterface, int i) {
-                                                                       Uri uri = Uri.parse("market://details?id=" + PROVIDERS.get(0));
-                                                                       Intent intent = new Intent(Intent.ACTION_VIEW, uri);
-                                                                       try {
-                                                                               startActivity(intent);
-                                                                       } catch (ActivityNotFoundException e) {
-                                                                               e.printStackTrace();
-                                                                       }
-                                                               }
-                                                       })
-                                                       .setNegativeButton(R.string.no, new OnClickListener() {
-                                                               @Override
-                                                               public void onClick(DialogInterface dialogInterface, int i) {
-                                                                       return;
-                                                               }
-                                                       })
-                                                       .create().show();
+                                               tokenURIReceived(uri);
                                        }
-                               });
-
-                               ad.show();
-
-                       return true;
+                               }.show();
                        }
-               });
 
-               menu.findItem(R.id.action_about).setOnMenuItemClickListener(new OnMenuItemClickListener() {
-                       @Override
-                       public boolean onMenuItemClick(MenuItem item) {
-                               new AboutDialogFragment().show(getFragmentManager(),
-                                               AboutDialogFragment.FRAGMENT_TAG);
-                               return true;
-                       }
-               });
+                       return true;
 
-        return true;
-    }
+               case R.id.action_about:
+                       new AboutDialogFragment().show(getFragmentManager(),
+                                       AboutDialogFragment.FRAGMENT_TAG);
+                       return true;
+               }
 
-       @Override
-       public void onActivityResult(int requestCode, int resultCode, Intent intent) {
-               if (resultCode == RESULT_OK) {
-                       try {
-                               ta.add(intent.getStringExtra("SCAN_RESULT"));
-                       } catch (TokenUriInvalidException e) {
-                               Toast.makeText(this, R.string.invalid_token, Toast.LENGTH_SHORT).show();
-                               e.printStackTrace();
-                       }
+               return false;
+       }
+
+       public void tokenURIReceived(String uri) {
+               try {
+                       mTokenAdapter.add(uri);
+               } catch (TokenUriInvalidException e) {
+                       Toast.makeText(this, R.string.invalid_token, Toast.LENGTH_SHORT).show();
+                       e.printStackTrace();
                }
        }
 }