diff options
author | Dominik Schürmann <dominik@dominikschuermann.de> | 2014-01-06 22:52:45 +0100 |
---|---|---|
committer | Dominik Schürmann <dominik@dominikschuermann.de> | 2014-01-06 22:52:45 +0100 |
commit | ccb462e94fffea71649e93dd4d91ef3748f5c726 (patch) | |
tree | 5bbcf0a950be34dd0ec768ebb83440a48d7f4841 /libraries/zxing | |
parent | 7671c99b97ebf62b797ee69ef8412c973199c9f8 (diff) | |
download | open-keychain-ccb462e94fffea71649e93dd4d91ef3748f5c726.tar.gz open-keychain-ccb462e94fffea71649e93dd4d91ef3748f5c726.tar.bz2 open-keychain-ccb462e94fffea71649e93dd4d91ef3748f5c726.zip |
externalize zxing lib, fix gradle build
Diffstat (limited to 'libraries/zxing')
68 files changed, 10223 insertions, 0 deletions
diff --git a/libraries/zxing/AndroidManifest.xml b/libraries/zxing/AndroidManifest.xml new file mode 100644 index 000000000..ef720bdb6 --- /dev/null +++ b/libraries/zxing/AndroidManifest.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.zxing"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="8"
+ android:targetSdkVersion="19" />
+
+</manifest>
\ No newline at end of file diff --git a/libraries/zxing/build.gradle b/libraries/zxing/build.gradle new file mode 100644 index 000000000..21050fc98 --- /dev/null +++ b/libraries/zxing/build.gradle @@ -0,0 +1,14 @@ +apply plugin: 'android-library' + +android { + compileSdkVersion 19 + buildToolsVersion '19.0.0' + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + res.srcDirs = ['res'] + } + } +} diff --git a/libraries/zxing/project.properties b/libraries/zxing/project.properties new file mode 100644 index 000000000..91d2b0246 --- /dev/null +++ b/libraries/zxing/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-19 +android.library=true diff --git a/libraries/zxing/src/com/google/zxing/BarcodeFormat.java b/libraries/zxing/src/com/google/zxing/BarcodeFormat.java new file mode 100644 index 000000000..1e5d47958 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/BarcodeFormat.java @@ -0,0 +1,106 @@ +/* + * 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.Hashtable; + +/** + * Enumerates barcode formats known to this package. Please keep alphabetized. + * + * @author Sean Owen + */ +public final class BarcodeFormat { + + // No, we can't use an enum here. J2ME doesn't support it. + + private static final Hashtable VALUES = new Hashtable(); + + /** Aztec 2D barcode format. */ + public static final BarcodeFormat AZTEC = new BarcodeFormat("AZTEC"); + + /** CODABAR 1D format. */ + public static final BarcodeFormat CODABAR = new BarcodeFormat("CODABAR"); + + /** Code 39 1D format. */ + public static final BarcodeFormat CODE_39 = new BarcodeFormat("CODE_39"); + + /** Code 93 1D format. */ + public static final BarcodeFormat CODE_93 = new BarcodeFormat("CODE_93"); + + /** Code 128 1D format. */ + public static final BarcodeFormat CODE_128 = new BarcodeFormat("CODE_128"); + + /** Data Matrix 2D barcode format. */ + public static final BarcodeFormat DATA_MATRIX = new BarcodeFormat("DATA_MATRIX"); + + /** EAN-8 1D format. */ + public static final BarcodeFormat EAN_8 = new BarcodeFormat("EAN_8"); + + /** EAN-13 1D format. */ + public static final BarcodeFormat EAN_13 = new BarcodeFormat("EAN_13"); + + /** ITF (Interleaved Two of Five) 1D format. */ + public static final BarcodeFormat ITF = new BarcodeFormat("ITF"); + + /** PDF417 format. */ + public static final BarcodeFormat PDF_417 = new BarcodeFormat("PDF_417"); + + /** QR Code 2D barcode format. */ + public static final BarcodeFormat QR_CODE = new BarcodeFormat("QR_CODE"); + + /** RSS 14 */ + public static final BarcodeFormat RSS_14 = new BarcodeFormat("RSS_14"); + + /** RSS EXPANDED */ + public static final BarcodeFormat RSS_EXPANDED = new BarcodeFormat("RSS_EXPANDED"); + + /** UPC-A 1D format. */ + public static final BarcodeFormat UPC_A = new BarcodeFormat("UPC_A"); + + /** UPC-E 1D format. */ + public static final BarcodeFormat UPC_E = new BarcodeFormat("UPC_E"); + + /** UPC/EAN extension format. Not a stand-alone format. */ + public static final BarcodeFormat UPC_EAN_EXTENSION = new BarcodeFormat("UPC_EAN_EXTENSION"); + + private final String name; + + private BarcodeFormat(String name) { + this.name = name; + VALUES.put(name, this); + } + + public String getName() { + return name; + } + + public String toString() { + return name; + } + + public static BarcodeFormat valueOf(String name) { + if (name == null || name.length() == 0) { + throw new IllegalArgumentException(); + } + BarcodeFormat format = (BarcodeFormat) VALUES.get(name); + if (format == null) { + throw new IllegalArgumentException(); + } + return format; + } + +} diff --git a/libraries/zxing/src/com/google/zxing/Binarizer.java b/libraries/zxing/src/com/google/zxing/Binarizer.java new file mode 100644 index 000000000..912a3b556 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/Binarizer.java @@ -0,0 +1,80 @@ +/* + * 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) { + if (source == null) { + throw new IllegalArgumentException("Source must be non-null."); + } + this.source = source; + } + + public 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); + +} diff --git a/libraries/zxing/src/com/google/zxing/BinaryBitmap.java b/libraries/zxing/src/com/google/zxing/BinaryBitmap.java new file mode 100644 index 000000000..b97e46705 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/BinaryBitmap.java @@ -0,0 +1,128 @@ +/* + * 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; + matrix = null; + } + + /** + * @return The width of the bitmap. + */ + public int getWidth() { + return binarizer.getLuminanceSource().getWidth(); + } + + /** + * @return The height of the bitmap. + */ + public int getHeight() { + return binarizer.getLuminanceSource().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. Only callable if isRotateSupported() is true. + * + * @return A rotated version of this object. + */ + public BinaryBitmap rotateCounterClockwise() { + LuminanceSource newSource = binarizer.getLuminanceSource().rotateCounterClockwise(); + return new BinaryBitmap(binarizer.createBinarizer(newSource)); + } + +} diff --git a/libraries/zxing/src/com/google/zxing/ChecksumException.java b/libraries/zxing/src/com/google/zxing/ChecksumException.java new file mode 100644 index 000000000..dedb4be99 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/ChecksumException.java @@ -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/libraries/zxing/src/com/google/zxing/DecodeHintType.java b/libraries/zxing/src/com/google/zxing/DecodeHintType.java new file mode 100644 index 000000000..20b922ca1 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/DecodeHintType.java @@ -0,0 +1,79 @@ +/* + * 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; + +/** + * 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.Hashtable) + */ +public final class DecodeHintType { + + // No, we can't use an enum here. J2ME doesn't support it. + + /** + * Unspecified, application-specific hint. Maps to an unspecified {@link Object}. + */ + public static final DecodeHintType OTHER = new DecodeHintType(); + + /** + * Image is a pure monochrome image of a barcode. Doesn't matter what it maps to; + * use {@link Boolean#TRUE}. + */ + public static final DecodeHintType PURE_BARCODE = new DecodeHintType(); + + /** + * Image is known to be of one of a few possible formats. + * Maps to a {@link java.util.Vector} of {@link BarcodeFormat}s. + */ + public static final DecodeHintType POSSIBLE_FORMATS = new DecodeHintType(); + + /** + * 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}. + */ + public static final DecodeHintType TRY_HARDER = new DecodeHintType(); + + /** + * Specifies what character encoding to use when decoding, where applicable (type String) + */ + public static final DecodeHintType CHARACTER_SET = new DecodeHintType(); + + /** + * Allowed lengths of encoded data -- reject anything else. Maps to an int[]. + */ + public static final DecodeHintType ALLOWED_LENGTHS = new DecodeHintType(); + + /** + * Assume Code 39 codes employ a check digit. Maps to {@link Boolean}. + */ + public static final DecodeHintType ASSUME_CODE_39_CHECK_DIGIT = new DecodeHintType(); + + /** + * The caller needs to be notified via callback when a possible {@link ResultPoint} + * is found. Maps to a {@link ResultPointCallback}. + */ + public static final DecodeHintType NEED_RESULT_POINT_CALLBACK = new DecodeHintType(); + + private DecodeHintType() { + } + +} diff --git a/libraries/zxing/src/com/google/zxing/EncodeHintType.java b/libraries/zxing/src/com/google/zxing/EncodeHintType.java new file mode 100644 index 000000000..35afc1530 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/EncodeHintType.java @@ -0,0 +1,39 @@ +/* + * 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; + +/** + * These are a set of hints that you may pass to Writers to specify their behavior. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class EncodeHintType { + + /** + * Specifies what degree of error correction to use, for example in QR Codes (type Integer). + */ + public static final EncodeHintType ERROR_CORRECTION = new EncodeHintType(); + + /** + * Specifies what character encoding to use where applicable (type String) + */ + public static final EncodeHintType CHARACTER_SET = new EncodeHintType(); + + private EncodeHintType() { + } + +} diff --git a/libraries/zxing/src/com/google/zxing/FormatException.java b/libraries/zxing/src/com/google/zxing/FormatException.java new file mode 100644 index 000000000..6967e93de --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/FormatException.java @@ -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/libraries/zxing/src/com/google/zxing/LuminanceSource.java b/libraries/zxing/src/com/google/zxing/LuminanceSource.java new file mode 100644 index 000000000..4b6d4539f --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/LuminanceSource.java @@ -0,0 +1,113 @@ +/* + * 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 RuntimeException("This luminance source does not support cropping."); + } + + /** + * @return Whether this subclass supports counter-clockwise rotation. + */ + public boolean isRotateSupported() { + return false; + } + + /** + * Returns a new object with rotated image data. Only callable if isRotateSupported() is true. + * + * @return A rotated version of this object. + */ + public LuminanceSource rotateCounterClockwise() { + throw new RuntimeException("This luminance source does not support rotation."); + } + +} diff --git a/libraries/zxing/src/com/google/zxing/NotFoundException.java b/libraries/zxing/src/com/google/zxing/NotFoundException.java new file mode 100644 index 000000000..dedab8dfc --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/NotFoundException.java @@ -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/libraries/zxing/src/com/google/zxing/Reader.java b/libraries/zxing/src/com/google/zxing/Reader.java new file mode 100644 index 000000000..47e843ba6 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/Reader.java @@ -0,0 +1,64 @@ +/* + * 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.Hashtable; + +/** + * 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.Hashtable} 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, Hashtable 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/libraries/zxing/src/com/google/zxing/ReaderException.java b/libraries/zxing/src/com/google/zxing/ReaderException.java new file mode 100644 index 000000000..224a497e5 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/ReaderException.java @@ -0,0 +1,98 @@ +/* + * 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 { + + // TODO: Currently we throw up to 400 ReaderExceptions while scanning a single 240x240 image before + // rejecting it. This involves a lot of overhead and memory allocation, and affects both performance + // and latency on continuous scan clients. In the future, we should change all the decoders not to + // throw exceptions for routine events, like not finding a barcode on a given row. Instead, we + // should return error codes back to the callers, and simply delete this class. In the mean time, I + // have altered this class to be as lightweight as possible, by ignoring the exception string, and + // by disabling the generation of stack traces, which is especially time consuming. These are just + // temporary measures, pending the big cleanup. + + //private static final ReaderException instance = new ReaderException(); + + // EXCEPTION TRACKING SUPPORT + // Identifies who is throwing exceptions and how often. To use: + // + // 1. Uncomment these lines and the code below which uses them. + // 2. Uncomment the two corresponding lines in j2se/CommandLineRunner.decode() + // 3. Change core to build as Java 1.5 temporarily +// private static int exceptionCount = 0; +// private static Map<String,Integer> throwers = new HashMap<String,Integer>(32); + + ReaderException() { + // do nothing + } + + //public static ReaderException getInstance() { +// Exception e = new Exception(); +// // Take the stack frame before this one. +// StackTraceElement stack = e.getStackTrace()[1]; +// String key = stack.getClassName() + "." + stack.getMethodName() + "(), line " + +// stack.getLineNumber(); +// if (throwers.containsKey(key)) { +// Integer value = throwers.get(key); +// value++; +// throwers.put(key, value); +// } else { +// throwers.put(key, 1); +// } +// exceptionCount++; + + //return instance; + //} + +// public static int getExceptionCountAndReset() { +// int temp = exceptionCount; +// exceptionCount = 0; +// return temp; +// } +// +// public static String getThrowersAndReset() { +// StringBuilder builder = new StringBuilder(1024); +// Object[] keys = throwers.keySet().toArray(); +// for (int x = 0; x < keys.length; x++) { +// String key = (String) keys[x]; +// Integer value = throwers.get(key); +// builder.append(key); +// builder.append(": "); +// builder.append(value); +// builder.append("\n"); +// } +// throwers.clear(); +// return builder.toString(); +// } + + // 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. + public final Throwable fillInStackTrace() { + return null; + } + +} diff --git a/libraries/zxing/src/com/google/zxing/Result.java b/libraries/zxing/src/com/google/zxing/Result.java new file mode 100644 index 000000000..ee1af527e --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/Result.java @@ -0,0 +1,143 @@ +/* + * 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.Enumeration; +import java.util.Hashtable; + +/** + * <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 Hashtable 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) { + if (text == null && rawBytes == null) { + throw new IllegalArgumentException("Text and bytes are null"); + } + 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, if applicable, otherwise <code>null</code> + */ + public String getText() { + return text; + } + + /** + * @return raw bytes encoded by the barcode, if applicable, otherwise <code>null</code> + */ + 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 Hashtable} mapping {@link ResultMetadataType} keys to values. May be + * <code>null</code>. This contains optional metadata about what was detected about the barcode, + * like orientation. + */ + public Hashtable getResultMetadata() { + return resultMetadata; + } + + public void putMetadata(ResultMetadataType type, Object value) { + if (resultMetadata == null) { + resultMetadata = new Hashtable(3); + } + resultMetadata.put(type, value); + } + + public void putAllMetadata(Hashtable metadata) { + if (metadata != null) { + if (resultMetadata == null) { + resultMetadata = metadata; + } else { + Enumeration e = metadata.keys(); + while (e.hasMoreElements()) { + ResultMetadataType key = (ResultMetadataType) e.nextElement(); + Object value = metadata.get(key); + resultMetadata.put(key, value); + } + } + } + } + + public void addResultPoints(ResultPoint[] newPoints) { + if (resultPoints == null) { + resultPoints = newPoints; + } else if (newPoints != null && newPoints.length > 0) { + ResultPoint[] allPoints = new ResultPoint[resultPoints.length + newPoints.length]; + System.arraycopy(resultPoints, 0, allPoints, 0, resultPoints.length); + System.arraycopy(newPoints, 0, allPoints, resultPoints.length, newPoints.length); + resultPoints = allPoints; + } + } + + public long getTimestamp() { + return timestamp; + } + + public String toString() { + if (text == null) { + return "[" + rawBytes.length + " bytes]"; + } else { + return text; + } + } + +} diff --git a/libraries/zxing/src/com/google/zxing/ResultMetadataType.java b/libraries/zxing/src/com/google/zxing/ResultMetadataType.java new file mode 100644 index 000000000..33d69d9c5 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/ResultMetadataType.java @@ -0,0 +1,109 @@ +/* + * 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; + +import java.util.Hashtable; + +/** + * 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 final class ResultMetadataType { + + // No, we can't use an enum here. J2ME doesn't support it. + + private static final Hashtable VALUES = new Hashtable(); + + // No, we can't use an enum here. J2ME doesn't support it. + + /** + * Unspecified, application-specific metadata. Maps to an unspecified {@link Object}. + */ + public static final ResultMetadataType OTHER = new ResultMetadataType("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). + */ + public static final ResultMetadataType ORIENTATION = new ResultMetadataType("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.Vector} of byte arrays corresponding to the + * raw bytes in the byte segments in the barcode, in order.</p> + */ + public static final ResultMetadataType BYTE_SEGMENTS = new ResultMetadataType("BYTE_SEGMENTS"); + + /** + * Error correction level used, if applicable. The value type depends on the + * format, but is typically a String. + */ + public static final ResultMetadataType ERROR_CORRECTION_LEVEL = new ResultMetadataType("ERROR_CORRECTION_LEVEL"); + + /** + * For some periodicals, indicates the issue number as an {@link Integer}. + */ + public static final ResultMetadataType ISSUE_NUMBER = new ResultMetadataType("ISSUE_NUMBER"); + + /** + * For some products, indicates the suggested retail price in the barcode as a + * formatted {@link String}. + */ + public static final ResultMetadataType SUGGESTED_PRICE = new ResultMetadataType("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". + */ + public static final ResultMetadataType POSSIBLE_COUNTRY = new ResultMetadataType("POSSIBLE_COUNTRY"); + + private final String name; + + private ResultMetadataType(String name) { + this.name = name; + VALUES.put(name, this); + } + + public String getName() { + return name; + } + + public String toString() { + return name; + } + + public static ResultMetadataType valueOf(String name) { + if (name == null || name.length() == 0) { + throw new IllegalArgumentException(); + } + ResultMetadataType format = (ResultMetadataType) VALUES.get(name); + if (format == null) { + throw new IllegalArgumentException(); + } + return format; + } + +} diff --git a/libraries/zxing/src/com/google/zxing/ResultPoint.java b/libraries/zxing/src/com/google/zxing/ResultPoint.java new file mode 100644 index 000000000..366ae3855 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/ResultPoint.java @@ -0,0 +1,129 @@ +/* + * 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; + +/** + * <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; + } + + public boolean equals(Object other) { + if (other instanceof ResultPoint) { + ResultPoint otherPoint = (ResultPoint) other; + return x == otherPoint.x && y == otherPoint.y; + } + return false; + } + + public int hashCode() { + return 31 * Float.floatToIntBits(x) + Float.floatToIntBits(y); + } + + public String toString() { + StringBuffer result = new StringBuffer(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) { + float xDiff = pattern1.getX() - pattern2.getX(); + float yDiff = pattern1.getY() - pattern2.getY(); + return (float) Math.sqrt((double) (xDiff * xDiff + yDiff * yDiff)); + } + + /** + * 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/libraries/zxing/src/com/google/zxing/ResultPointCallback.java b/libraries/zxing/src/com/google/zxing/ResultPointCallback.java new file mode 100644 index 000000000..0c85410bc --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/ResultPointCallback.java @@ -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/libraries/zxing/src/com/google/zxing/Writer.java b/libraries/zxing/src/com/google/zxing/Writer.java new file mode 100644 index 000000000..6474ca7e2 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/Writer.java @@ -0,0 +1,54 @@ +/* + * 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; + +import com.google.zxing.common.BitMatrix; + +import java.util.Hashtable; + +/** + * The base class for all objects which encode/generate a barcode image. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public interface Writer { + + /** + * Encode a barcode using the default settings. + * + * @param contents The contents to encode in the barcode + * @param format The barcode format to generate + * @param width The preferred width in pixels + * @param height The preferred height in pixels + * @return The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white) + */ + BitMatrix encode(String contents, BarcodeFormat format, int width, int height) + throws WriterException; + + /** + * + * @param contents The contents to encode in the barcode + * @param format The barcode format to generate + * @param width The preferred width in pixels + * @param height The preferred height in pixels + * @param hints Additional parameters to supply to the encoder + * @return The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white) + */ + BitMatrix encode(String contents, BarcodeFormat format, int width, int height, Hashtable hints) + throws WriterException; + +} diff --git a/libraries/zxing/src/com/google/zxing/WriterException.java b/libraries/zxing/src/com/google/zxing/WriterException.java new file mode 100644 index 000000000..0c19af01d --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/WriterException.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * A base class which covers the range of exceptions which may occur when encoding a barcode using + * the Writer framework. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class WriterException extends Exception { + + public WriterException() { + super(); + } + + public WriterException(String message) { + super(message); + } + +} diff --git a/libraries/zxing/src/com/google/zxing/common/BitArray.java b/libraries/zxing/src/com/google/zxing/common/BitArray.java new file mode 100644 index 000000000..6eb0d57c6 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/BitArray.java @@ -0,0 +1,246 @@ +/*
+ * 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 {
+ // I have changed these members to be public so ProGuard can inline get() and set(). Ideally
+ // they'd be private and we'd use the -allowaccessmodification flag, but Dalvik rejects the
+ // resulting binary at runtime on Android. If we find a solution to this, these should be changed
+ // back to private.
+ public int[] bits;
+ public int size;
+
+ public BitArray() {
+ this.size = 0;
+ this.bits = new int[1];
+ }
+
+ public BitArray(int size) {
+ this.size = size;
+ this.bits = makeArray(size);
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public int getSizeInBytes() {
+ return (size + 7) >> 3;
+ }
+
+ private void ensureCapacity(int size) {
+ if (size > bits.length << 5) {
+ 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 >> 5] & (1 << (i & 0x1F))) != 0;
+ }
+
+ /**
+ * Sets bit i.
+ *
+ * @param i bit to set
+ */
+ public void set(int i) {
+ bits[i >> 5] |= 1 << (i & 0x1F);
+ }
+
+ /**
+ * Flips bit i.
+ *
+ * @param i bit to set
+ */
+ public void flip(int i) {
+ bits[i >> 5] ^= 1 << (i & 0x1F);
+ }
+
+ /**
+ * 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 >> 5] = newBits;
+ }
+
+ /**
+ * 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 >> 5;
+ int lastInt = end >> 5;
+ 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 >> 5] |= (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.getSize();
+ 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];
+ int size = this.size;
+ for (int i = 0; i < size; i++) {
+ if (get(size - i - 1)) {
+ newBits[i >> 5] |= 1 << (i & 0x1F);
+ }
+ }
+ bits = newBits;
+ }
+
+ private static int[] makeArray(int size) {
+ return new int[(size + 31) >> 5];
+ }
+
+ public String toString() {
+ StringBuffer result = new StringBuffer(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/libraries/zxing/src/com/google/zxing/common/BitMatrix.java b/libraries/zxing/src/com/google/zxing/common/BitMatrix.java new file mode 100644 index 000000000..8bf75b289 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/BitMatrix.java @@ -0,0 +1,247 @@ +/*
+ * 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 {
+ // Just like BitArray, these need to be public so ProGuard can inline them.
+ public final int width;
+ public final int height;
+ public final int rowSize;
+ public 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ 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;
+ }
+
+ public int hashCode() {
+ int hash = width;
+ hash = 31 * hash + width;
+ hash = 31 * hash + height;
+ hash = 31 * hash + rowSize;
+ for (int i = 0; i < bits.length; i++) {
+ hash = 31 * hash + bits[i];
+ }
+ return hash;
+ }
+
+ public String toString() {
+ StringBuffer result = new StringBuffer(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/libraries/zxing/src/com/google/zxing/common/BitSource.java b/libraries/zxing/src/com/google/zxing/common/BitSource.java new file mode 100644 index 000000000..a61ac5105 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/BitSource.java @@ -0,0 +1,97 @@ +/*
+ * 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;
+ }
+
+ /**
+ * @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]
+ */
+ public int readBits(int numBits) {
+ if (numBits < 1 || numBits > 32) {
+ throw new IllegalArgumentException();
+ }
+
+ 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/libraries/zxing/src/com/google/zxing/common/CharacterSetECI.java b/libraries/zxing/src/com/google/zxing/common/CharacterSetECI.java new file mode 100644 index 000000000..42b7fa9f6 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/CharacterSetECI.java @@ -0,0 +1,110 @@ +/* + * 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.Hashtable; + +/** + * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 + * of ISO 18004. + * + * @author Sean Owen + */ +public final class CharacterSetECI extends ECI { + + private static Hashtable VALUE_TO_ECI; + private static Hashtable NAME_TO_ECI; + + private static void initialize() { + VALUE_TO_ECI = new Hashtable(29); + NAME_TO_ECI = new Hashtable(29); + // TODO figure out if these values are even right! + addCharacterSet(0, "Cp437"); + addCharacterSet(1, new String[] {"ISO8859_1", "ISO-8859-1"}); + addCharacterSet(2, "Cp437"); + addCharacterSet(3, new String[] {"ISO8859_1", "ISO-8859-1"}); + addCharacterSet(4, "ISO8859_2"); + addCharacterSet(5, "ISO8859_3"); + addCharacterSet(6, "ISO8859_4"); + addCharacterSet(7, "ISO8859_5"); + addCharacterSet(8, "ISO8859_6"); + addCharacterSet(9, "ISO8859_7"); + addCharacterSet(10, "ISO8859_8"); + addCharacterSet(11, "ISO8859_9"); + addCharacterSet(12, "ISO8859_10"); + addCharacterSet(13, "ISO8859_11"); + addCharacterSet(15, "ISO8859_13"); + addCharacterSet(16, "ISO8859_14"); + addCharacterSet(17, "ISO8859_15"); + addCharacterSet(18, "ISO8859_16"); + addCharacterSet(20, new String[] {"SJIS", "Shift_JIS"}); + } + + private final String encodingName; + + private CharacterSetECI(int value, String encodingName) { + super(value); + this.encodingName = encodingName; + } + + public String getEncodingName() { + return encodingName; + } + + private static void addCharacterSet(int value, String encodingName) { + CharacterSetECI eci = new CharacterSetECI(value, encodingName); + VALUE_TO_ECI.put(new Integer(value), eci); // can't use valueOf + NAME_TO_ECI.put(encodingName, eci); + } + + private static void addCharacterSet(int value, String[] encodingNames) { + CharacterSetECI eci = new CharacterSetECI(value, encodingNames[0]); + VALUE_TO_ECI.put(new Integer(value), eci); // can't use valueOf + for (int i = 0; i < encodingNames.length; i++) { + NAME_TO_ECI.put(encodingNames[i], eci); + } + } + + /** + * @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) { + if (VALUE_TO_ECI == null) { + initialize(); + } + if (value < 0 || value >= 900) { + throw new IllegalArgumentException("Bad ECI value: " + value); + } + return (CharacterSetECI) VALUE_TO_ECI.get(new Integer(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) { + if (NAME_TO_ECI == null) { + initialize(); + } + return (CharacterSetECI) NAME_TO_ECI.get(name); + } + +}
\ No newline at end of file diff --git a/libraries/zxing/src/com/google/zxing/common/Collections.java b/libraries/zxing/src/com/google/zxing/common/Collections.java new file mode 100644 index 000000000..319ebfe6d --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/Collections.java @@ -0,0 +1,53 @@ +/* + * 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.Vector; + +/** + * <p>This is basically a substitute for <code>java.util.Collections</code>, which is not + * present in MIDP 2.0 / CLDC 1.1.</p> + * + * @author Sean Owen + */ +public final class Collections { + + private Collections() { + } + + /** + * Sorts its argument (destructively) using insert sort; in the context of this package + * insertion sort is simple and efficient given its relatively small inputs. + * + * @param vector vector to sort + * @param comparator comparator to define sort ordering + */ + public static void insertionSort(Vector vector, Comparator comparator) { + int max = vector.size(); + for (int i = 1; i < max; i++) { + Object value = vector.elementAt(i); + int j = i - 1; + Object valueB; + while (j >= 0 && comparator.compare((valueB = vector.elementAt(j)), value) > 0) { + vector.setElementAt(valueB, j + 1); + j--; + } + vector.setElementAt(value, j + 1); + } + } + +} diff --git a/libraries/zxing/src/com/google/zxing/common/Comparator.java b/libraries/zxing/src/com/google/zxing/common/Comparator.java new file mode 100644 index 000000000..e1be15e31 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/Comparator.java @@ -0,0 +1,27 @@ +/* + * 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; + +/** + * This is merely a clone of <code>Comparator</code> since it is not available in + * CLDC 1.1 / MIDP 2.0. + */ +public interface Comparator { + + int compare(Object o1, Object o2); + +}
\ No newline at end of file diff --git a/libraries/zxing/src/com/google/zxing/common/DecoderResult.java b/libraries/zxing/src/com/google/zxing/common/DecoderResult.java new file mode 100644 index 000000000..7e0855333 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/DecoderResult.java @@ -0,0 +1,61 @@ +/* + * 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.Vector; + +/** + * <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 Vector byteSegments; + private final String ecLevel; + + public DecoderResult(byte[] rawBytes, String text, Vector byteSegments, String ecLevel) { + if (rawBytes == null && text == null) { + throw new IllegalArgumentException(); + } + this.rawBytes = rawBytes; + this.text = text; + this.byteSegments = byteSegments; + this.ecLevel = ecLevel; + } + + public byte[] getRawBytes() { + return rawBytes; + } + + public String getText() { + return text; + } + + public Vector getByteSegments() { + return byteSegments; + } + + public String getECLevel() { + return ecLevel; + } + +}
\ No newline at end of file diff --git a/libraries/zxing/src/com/google/zxing/common/DefaultGridSampler.java b/libraries/zxing/src/com/google/zxing/common/DefaultGridSampler.java new file mode 100644 index 000000000..74c9e7c6b --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/DefaultGridSampler.java @@ -0,0 +1,86 @@ +/* + * 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 { + + 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); + } + + 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/libraries/zxing/src/com/google/zxing/common/DetectorResult.java b/libraries/zxing/src/com/google/zxing/common/DetectorResult.java new file mode 100644 index 000000000..ea4794d17 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/DetectorResult.java @@ -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 BitMatrix getBits() { + return bits; + } + + public ResultPoint[] getPoints() { + return points; + } + +}
\ No newline at end of file diff --git a/libraries/zxing/src/com/google/zxing/common/ECI.java b/libraries/zxing/src/com/google/zxing/common/ECI.java new file mode 100644 index 000000000..444c779c2 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/ECI.java @@ -0,0 +1,52 @@ +/* + * 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; + +/** + * Superclass of classes encapsulating types ECIs, according to "Extended Channel Interpretations" + * 5.3 of ISO 18004. + * + * @author Sean Owen + */ +public abstract class ECI { + + private final int value; + + ECI(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + /** + * @param value ECI value + * @return ECI representing ECI of given value, or null if it is legal but unsupported + * @throws IllegalArgumentException if ECI value is invalid + */ + public static ECI getECIByValue(int value) { + if (value < 0 || value > 999999) { + throw new IllegalArgumentException("Bad ECI value: " + value); + } + if (value < 900) { // Character set ECIs use 000000 - 000899 + return CharacterSetECI.getCharacterSetECIByValue(value); + } + return null; + } + +} diff --git a/libraries/zxing/src/com/google/zxing/common/GlobalHistogramBinarizer.java b/libraries/zxing/src/com/google/zxing/common/GlobalHistogramBinarizer.java new file mode 100644 index 000000000..4fa2a887b --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/GlobalHistogramBinarizer.java @@ -0,0 +1,194 @@ +/* + * 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 byte[] luminances = null; + private int[] buckets = null; + + public GlobalHistogramBinarizer(LuminanceSource source) { + super(source); + } + + // Applies simple sharpening to the row data to improve performance of the 1D Readers. + 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. + 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; + } + + public Binarizer createBinarizer(LuminanceSource source) { + return new GlobalHistogramBinarizer(source); + } + + private void initArrays(int luminanceSize) { + if (luminances == null || luminances.length < luminanceSize) { + luminances = new byte[luminanceSize]; + } + if (buckets == null) { + buckets = new int[LUMINANCE_BUCKETS]; + } else { + 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/libraries/zxing/src/com/google/zxing/common/GridSampler.java b/libraries/zxing/src/com/google/zxing/common/GridSampler.java new file mode 100644 index 000000000..7f26c264e --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/GridSampler.java @@ -0,0 +1,156 @@ +/* + * 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) { + if (newGridSampler == null) { + throw new IllegalArgumentException(); + } + 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/libraries/zxing/src/com/google/zxing/common/HybridBinarizer.java b/libraries/zxing/src/com/google/zxing/common/HybridBinarizer.java new file mode 100644 index 000000000..b482c1a22 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/HybridBinarizer.java @@ -0,0 +1,185 @@ +/* + * 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 MINIMUM_DIMENSION = 40; + + private BitMatrix matrix = null; + + public HybridBinarizer(LuminanceSource source) { + super(source); + } + + public BitMatrix getBlackMatrix() throws NotFoundException { + binarizeEntireImage(); + return matrix; + } + + public Binarizer createBinarizer(LuminanceSource source) { + return new HybridBinarizer(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. + private void binarizeEntireImage() throws NotFoundException { + if (matrix == null) { + LuminanceSource source = getLuminanceSource(); + if (source.getWidth() >= MINIMUM_DIMENSION && source.getHeight() >= MINIMUM_DIMENSION) { + byte[] luminances = source.getMatrix(); + int width = source.getWidth(); + int height = source.getHeight(); + int subWidth = width >> 3; + if ((width & 0x07) != 0) { + subWidth++; + } + int subHeight = height >> 3; + if ((height & 0x07) != 0) { + subHeight++; + } + int[][] blackPoints = calculateBlackPoints(luminances, subWidth, subHeight, width, height); + + matrix = new BitMatrix(width, height); + calculateThresholdForBlock(luminances, subWidth, subHeight, width, height, blackPoints, matrix); + } else { + // If the image is too small, fall back to the global histogram approach. + matrix = super.getBlackMatrix(); + } + } + } + + // For each 8x8 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 8 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 << 3; + if ((yoffset + 8) >= height) { + yoffset = height - 8; + } + for (int x = 0; x < subWidth; x++) { + int xoffset = x << 3; + if ((xoffset + 8) >= width) { + xoffset = width - 8; + } + int left = x > 1 ? x : 2; + left = left < subWidth - 2 ? left : subWidth - 3; + int top = y > 1 ? y : 2; + top = top < subHeight - 2 ? top : subHeight - 3; + int sum = 0; + for (int z = -2; z <= 2; z++) { + int[] blackRow = blackPoints[top + z]; + sum += blackRow[left - 2]; + sum += blackRow[left - 1]; + sum += blackRow[left]; + sum += blackRow[left + 1]; + sum += blackRow[left + 2]; + } + int average = sum / 25; + threshold8x8Block(luminances, xoffset, yoffset, average, width, matrix); + } + } + } + + // Applies a single threshold to an 8x8 block of pixels. + private static void threshold8x8Block(byte[] luminances, int xoffset, int yoffset, int threshold, + int stride, BitMatrix matrix) { + for (int y = 0; y < 8; y++) { + int offset = (yoffset + y) * stride + xoffset; + for (int x = 0; x < 8; x++) { + int pixel = luminances[offset + x] & 0xff; + if (pixel < threshold) { + matrix.set(xoffset + x, yoffset + y); + } + } + } + } + + // Calculates a single black point for each 8x8 block of pixels and saves it away. + 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 << 3; + if ((yoffset + 8) >= height) { + yoffset = height - 8; + } + for (int x = 0; x < subWidth; x++) { + int xoffset = x << 3; + if ((xoffset + 8) >= width) { + xoffset = width - 8; + } + int sum = 0; + int min = 255; + int max = 0; + for (int yy = 0; yy < 8; yy++) { + int offset = (yoffset + yy) * width + xoffset; + for (int xx = 0; xx < 8; xx++) { + int pixel = luminances[offset + xx] & 0xff; + sum += pixel; + if (pixel < min) { + min = pixel; + } + if (pixel > max) { + max = pixel; + } + } + } + + // If the contrast is inadequate, use half the minimum, so that this block will be + // treated as part of the white background, but won't drag down neighboring blocks + // too much. + int average; + if (max - min > 24) { + average = sum >> 6; + } else { + // When min == max == 0, let average be 1 so all is black + average = max == 0 ? 1 : min >> 1; + } + blackPoints[y][x] = average; + } + } + return blackPoints; + } + +} diff --git a/libraries/zxing/src/com/google/zxing/common/PerspectiveTransform.java b/libraries/zxing/src/com/google/zxing/common/PerspectiveTransform.java new file mode 100644 index 000000000..9e65baff1 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/PerspectiveTransform.java @@ -0,0 +1,148 @@ +/*
+ * 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, a12, a13, a21, a22, a23, a31, a32, 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 dy2 = y3 - y2;
+ float dy3 = y0 - y1 + y2 - y3;
+ if (dy2 == 0.0f && dy3 == 0.0f) {
+ 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 dx3 = x0 - x1 + x2 - x3;
+ float dy1 = y1 - 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/libraries/zxing/src/com/google/zxing/common/StringUtils.java b/libraries/zxing/src/com/google/zxing/common/StringUtils.java new file mode 100644 index 000000000..97999f997 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/StringUtils.java @@ -0,0 +1,192 @@ +/* + * 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.Hashtable; + +import com.google.zxing.DecodeHintType; + +/** + * Common string-related functions. + * + * @author Sean Owen + */ +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, Hashtable hints) { + if (hints != null) { + String characterSet = (String) hints.get(DecodeHintType.CHARACTER_SET); + if (characterSet != null) { + return characterSet; + } + } + // Does it start with the UTF-8 byte order mark? then guess it's UTF-8 + if (bytes.length > 3 && + bytes[0] == (byte) 0xEF && + bytes[1] == (byte) 0xBB && + bytes[2] == (byte) 0xBF) { + return UTF8; + } + // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS, + // which should be by far the most common encodings. ISO-8859-1 + // should not have bytes in the 0x80 - 0x9F range, while Shift_JIS + // uses this as a first byte of a two-byte character. If we see this + // followed by a valid second byte in Shift_JIS, assume it is Shift_JIS. + // If we see something else in that second byte, we'll make the risky guess + // that it's UTF-8. + int length = bytes.length; + boolean canBeISO88591 = true; + boolean canBeShiftJIS = true; + boolean canBeUTF8 = true; + int utf8BytesLeft = 0; + int maybeDoubleByteCount = 0; + int maybeSingleByteKatakanaCount = 0; + boolean sawLatin1Supplement = false; + boolean sawUTF8Start = false; + boolean lastWasPossibleDoubleByteStart = false; + + for (int i = 0; + i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8); + i++) { + + int value = bytes[i] & 0xFF; + + // UTF-8 stuff + if (value >= 0x80 && value <= 0xBF) { + if (utf8BytesLeft > 0) { + utf8BytesLeft--; + } + } else { + if (utf8BytesLeft > 0) { + canBeUTF8 = false; + } + if (value >= 0xC0 && value <= 0xFD) { + sawUTF8Start = true; + int valueCopy = value; + while ((valueCopy & 0x40) != 0) { + utf8BytesLeft++; + valueCopy <<= 1; + } + } + } + + // ISO-8859-1 stuff + + if ((value == 0xC2 || value == 0xC3) && i < length - 1) { + // This is really a poor hack. The slightly more exotic characters people might want to put in + // a QR Code, by which I mean the Latin-1 supplement characters (e.g. u-umlaut) have encodings + // that start with 0xC2 followed by [0xA0,0xBF], or start with 0xC3 followed by [0x80,0xBF]. + int nextValue = bytes[i + 1] & 0xFF; + if (nextValue <= 0xBF && + ((value == 0xC2 && nextValue >= 0xA0) || (value == 0xC3 && nextValue >= 0x80))) { + sawLatin1Supplement = true; + } + } + if (value >= 0x7F && value <= 0x9F) { + canBeISO88591 = false; + } + + // Shift_JIS stuff + + if (value >= 0xA1 && value <= 0xDF) { + // count the number of characters that might be a Shift_JIS single-byte Katakana character + if (!lastWasPossibleDoubleByteStart) { + maybeSingleByteKatakanaCount++; + } + } + if (!lastWasPossibleDoubleByteStart && + ((value >= 0xF0 && value <= 0xFF) || value == 0x80 || value == 0xA0)) { + canBeShiftJIS = false; + } + if ((value >= 0x81 && value <= 0x9F) || (value >= 0xE0 && value <= 0xEF)) { + // These start double-byte characters in Shift_JIS. Let's see if it's followed by a valid + // second byte. + if (lastWasPossibleDoubleByteStart) { + // If we just checked this and the last byte for being a valid double-byte + // char, don't check starting on this byte. If this and the last byte + // formed a valid pair, then this shouldn't be checked to see if it starts + // a double byte pair of course. + lastWasPossibleDoubleByteStart = false; + } else { + // ... otherwise do check to see if this plus the next byte form a valid + // double byte pair encoding a character. + lastWasPossibleDoubleByteStart = true; + if (i >= bytes.length - 1) { + canBeShiftJIS = false; + } else { + int nextValue = bytes[i + 1] & 0xFF; + if (nextValue < 0x40 || nextValue > 0xFC) { + canBeShiftJIS = false; + } else { + maybeDoubleByteCount++; + } + // There is some conflicting information out there about which bytes can follow which in + // double-byte Shift_JIS characters. The rule above seems to be the one that matches practice. + } + } + } else { + lastWasPossibleDoubleByteStart = false; + } + } + if (utf8BytesLeft > 0) { + canBeUTF8 = false; + } + + // Easy -- if assuming Shift_JIS and no evidence it can't be, done + if (canBeShiftJIS && ASSUME_SHIFT_JIS) { + return SHIFT_JIS; + } + if (canBeUTF8 && sawUTF8Start) { + return UTF8; + } + // Distinguishing Shift_JIS and ISO-8859-1 can be a little tough. The crude heuristic is: + // - If we saw + // - at least 3 bytes that starts a double-byte value (bytes that are rare in ISO-8859-1), or + // - over 5% of bytes could be single-byte Katakana (also rare in ISO-8859-1), + // - and, saw no sequences that are invalid in Shift_JIS, then we conclude Shift_JIS + if (canBeShiftJIS && (maybeDoubleByteCount >= 3 || 20 * maybeSingleByteKatakanaCount > length)) { + return SHIFT_JIS; + } + // Otherwise, we default to ISO-8859-1 unless we know it can't be + if (!sawLatin1Supplement && canBeISO88591) { + return ISO88591; + } + // Otherwise, we take a wild guess with platform encoding + return PLATFORM_DEFAULT_ENCODING; + } + +} diff --git a/libraries/zxing/src/com/google/zxing/common/detector/MonochromeRectangleDetector.java b/libraries/zxing/src/com/google/zxing/common/detector/MonochromeRectangleDetector.java new file mode 100644 index 000000000..950a22364 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/detector/MonochromeRectangleDetector.java @@ -0,0 +1,209 @@ +/* + * 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.detector; + +import com.google.zxing.NotFoundException; +import com.google.zxing.ResultPoint; +import com.google.zxing.common.BitMatrix; + +/** + * <p>A somewhat generic detector that looks for a barcode-like rectangular region within an image. + * It looks within a mostly white region of an image for a region of black and white, but mostly + * black. It returns the four corners of the region, as best it can determine.</p> + * + * @author Sean Owen + */ +public final class MonochromeRectangleDetector { + + private static final int MAX_MODULES = 32; + + private final BitMatrix image; + + public MonochromeRectangleDetector(BitMatrix image) { + this.image = image; + } + + /** + * <p>Detects a rectangular region of black and white -- mostly black -- with a region of mostly + * white, in an image.</p> + * + * @return {@link ResultPoint}[] describing the corners of the rectangular region. The first and + * last points are opposed on the diagonal, as are the second and third. The first point will be + * the topmost point and the last, the bottommost. The second point will be leftmost and the + * third, the rightmost + * @throws NotFoundException if no Data Matrix Code can be found + */ + public ResultPoint[] detect() throws NotFoundException { + int height = image.getHeight(); + int width = image.getWidth(); + int halfHeight = height >> 1; + int halfWidth = width >> 1; + int deltaY = Math.max(1, height / (MAX_MODULES << 3)); + int deltaX = Math.max(1, width / (MAX_MODULES << 3)); + + int top = 0; + int bottom = height; + int left = 0; + int right = width; + ResultPoint pointA = findCornerFromCenter(halfWidth, 0, left, right, + halfHeight, -deltaY, top, bottom, halfWidth >> 1); + top = (int) pointA.getY() - 1; + ResultPoint pointB = findCornerFromCenter(halfWidth, -deltaX, left, right, + halfHeight, 0, top, bottom, halfHeight >> 1); + left = (int) pointB.getX() - 1; + ResultPoint pointC = findCornerFromCenter(halfWidth, deltaX, left, right, + halfHeight, 0, top, bottom, halfHeight >> 1); + right = (int) pointC.getX() + 1; + ResultPoint pointD = findCornerFromCenter(halfWidth, 0, left, right, + halfHeight, deltaY, top, bottom, halfWidth >> 1); + bottom = (int) pointD.getY() + 1; + + // Go try to find point A again with better information -- might have been off at first. + pointA = findCornerFromCenter(halfWidth, 0, left, right, + halfHeight, -deltaY, top, bottom, halfWidth >> 2); + + return new ResultPoint[] { pointA, pointB, pointC, pointD }; + } + + /** + * Attempts to locate a corner of the barcode by scanning up, down, left or right from a center + * point which should be within the barcode. + * + * @param centerX center's x component (horizontal) + * @param deltaX same as deltaY but change in x per step instead + * @param left minimum value of x + * @param right maximum value of x + * @param centerY center's y component (vertical) + * @param deltaY change in y per step. If scanning up this is negative; down, positive; + * left or right, 0 + * @param top minimum value of y to search through (meaningless when di == 0) + * @param bottom maximum value of y + * @param maxWhiteRun maximum run of white pixels that can still be considered to be within + * the barcode + * @return a {@link com.google.zxing.ResultPoint} encapsulating the corner that was found + * @throws NotFoundException if such a point cannot be found + */ + private ResultPoint findCornerFromCenter(int centerX, int deltaX, int left, int right, + int centerY, int deltaY, int top, int bottom, int maxWhiteRun) throws NotFoundException { + int[] lastRange = null; + for (int y = centerY, x = centerX; + y < bottom && y >= top && x < right && x >= left; + y += deltaY, x += deltaX) { + int[] range; + if (deltaX == 0) { + // horizontal slices, up and down + range = blackWhiteRange(y, maxWhiteRun, left, right, true); + } else { + // vertical slices, left and right + range = blackWhiteRange(x, maxWhiteRun, top, bottom, false); + } + if (range == null) { + if (lastRange == null) { + throw NotFoundException.getNotFoundInstance(); + } + // lastRange was found + if (deltaX == 0) { + int lastY = y - deltaY; + if (lastRange[0] < centerX) { + if (lastRange[1] > centerX) { + // straddle, choose one or the other based on direction + return new ResultPoint(deltaY > 0 ? lastRange[0] : lastRange[1], lastY); + } + return new ResultPoint(lastRange[0], lastY); + } else { + return new ResultPoint(lastRange[1], lastY); + } + } else { + int lastX = x - deltaX; + if (lastRange[0] < centerY) { + if (lastRange[1] > centerY) { + return new ResultPoint(lastX, deltaX < 0 ? lastRange[0] : lastRange[1]); + } + return new ResultPoint(lastX, lastRange[0]); + } else { + return new ResultPoint(lastX, lastRange[1]); + } + } + } + lastRange = range; + } + throw NotFoundException.getNotFoundInstance(); + } + + /** + * Computes the start and end of a region of pixels, either horizontally or vertically, that could + * be part of a Data Matrix barcode. + * + * @param fixedDimension if scanning horizontally, this is the row (the fixed vertical location) + * where we are scanning. If scanning vertically it's the column, the fixed horizontal location + * @param maxWhiteRun largest run of white pixels that can still be considered part of the + * barcode region + * @param minDim minimum pixel location, horizontally or vertically, to consider + * @param maxDim maximum pixel location, horizontally or vertically, to consider + * @param horizontal if true, we're scanning left-right, instead of up-down + * @return int[] with start and end of found range, or null if no such range is found + * (e.g. only white was found) + */ + private int[] blackWhiteRange(int fixedDimension, int maxWhiteRun, int minDim, int maxDim, + boolean horizontal) { + + int center = (minDim + maxDim) >> 1; + + // Scan left/up first + int start = center; + while (start >= minDim) { + if (horizontal ? image.get(start, fixedDimension) : image.get(fixedDimension, start)) { + start--; + } else { + int whiteRunStart = start; + do { + start--; + } while (start >= minDim && !(horizontal ? image.get(start, fixedDimension) : + image.get(fixedDimension, start))); + int whiteRunSize = whiteRunStart - start; + if (start < minDim || whiteRunSize > maxWhiteRun) { + start = whiteRunStart; + break; + } + } + } + start++; + + // Then try right/down + int end = center; + while (end < maxDim) { + if (horizontal ? image.get(end, fixedDimension) : image.get(fixedDimension, end)) { + end++; + } else { + int whiteRunStart = end; + do { + end++; + } while (end < maxDim && !(horizontal ? image.get(end, fixedDimension) : + image.get(fixedDimension, end))); + int whiteRunSize = end - whiteRunStart; + if (end >= maxDim || whiteRunSize > maxWhiteRun) { + end = whiteRunStart; + break; + } + } + } + end--; + + return end > start ? new int[]{start, end} : null; + } + +}
\ No newline at end of file diff --git a/libraries/zxing/src/com/google/zxing/common/detector/WhiteRectangleDetector.java b/libraries/zxing/src/com/google/zxing/common/detector/WhiteRectangleDetector.java new file mode 100644 index 000000000..31d87e9d0 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/detector/WhiteRectangleDetector.java @@ -0,0 +1,347 @@ +/* + * Copyright 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.detector; + +import com.google.zxing.NotFoundException; +import com.google.zxing.ResultPoint; +import com.google.zxing.common.BitMatrix; + +/** + * <p> + * Detects a candidate barcode-like rectangular region within an image. It + * starts around the center of the image, increases the size of the candidate + * region until it finds a white rectangular region. By keeping track of the + * last black points it encountered, it determines the corners of the barcode. + * </p> + * + * @author David Olivier + */ +public final class WhiteRectangleDetector { + + private static final int INIT_SIZE = 30; + private static final int CORR = 1; + + private final BitMatrix image; + private final int height; + private final int width; + private final int leftInit; + private final int rightInit; + private final int downInit; + private final int upInit; + + /** + * @throws NotFoundException if image is too small + */ + public WhiteRectangleDetector(BitMatrix image) throws NotFoundException { + this.image = image; + height = image.getHeight(); + width = image.getWidth(); + leftInit = (width - INIT_SIZE) >> 1; + rightInit = (width + INIT_SIZE) >> 1; + upInit = (height - INIT_SIZE) >> 1; + downInit = (height + INIT_SIZE) >> 1; + if (upInit < 0 || leftInit < 0 || downInit >= height || rightInit >= width) { + throw NotFoundException.getNotFoundInstance(); + } + } + + /** + * @throws NotFoundException if image is too small + */ + public WhiteRectangleDetector(BitMatrix image, int initSize, int x, int y) throws NotFoundException { + this.image = image; + height = image.getHeight(); + width = image.getWidth(); + int halfsize = initSize >> 1; + leftInit = x - halfsize; + rightInit = x + halfsize; + upInit = y - halfsize; + downInit = y + halfsize; + if (upInit < 0 || leftInit < 0 || downInit >= height || rightInit >= width) { + throw NotFoundException.getNotFoundInstance(); + } + } + + /** + * <p> + * Detects a candidate barcode-like rectangular region within an image. It + * starts around the center of the image, increases the size of the candidate + * region until it finds a white rectangular region. + * </p> + * + * @return {@link ResultPoint[]} describing the corners of the rectangular + * region. The first and last points are opposed on the diagonal, as + * are the second and third. The first point will be the topmost + * point and the last, the bottommost. The second point will be + * leftmost and the third, the rightmost + * @throws NotFoundException if no Data Matrix Code can be found + */ + public ResultPoint[] detect() throws NotFoundException { + + int left = leftInit; + int right = rightInit; + int up = upInit; + int down = downInit; + boolean sizeExceeded = false; + boolean aBlackPointFoundOnBorder = true; + boolean atLeastOneBlackPointFoundOnBorder = false; + + while (aBlackPointFoundOnBorder) { + + aBlackPointFoundOnBorder = false; + + // ..... + // . | + // ..... + boolean rightBorderNotWhite = true; + while (rightBorderNotWhite && right < width) { + rightBorderNotWhite = containsBlackPoint(up, down, right, false); + if (rightBorderNotWhite) { + right++; + aBlackPointFoundOnBorder = true; + } + } + + if (right >= width) { + sizeExceeded = true; + break; + } + + // ..... + // . . + // .___. + boolean bottomBorderNotWhite = true; + while (bottomBorderNotWhite && down < height) { + bottomBorderNotWhite = containsBlackPoint(left, right, down, true); + if (bottomBorderNotWhite) { + down++; + aBlackPointFoundOnBorder = true; + } + } + + if (down >= height) { + sizeExceeded = true; + break; + } + + // ..... + // | . + // ..... + boolean leftBorderNotWhite = true; + while (leftBorderNotWhite && left >= 0) { + leftBorderNotWhite = containsBlackPoint(up, down, left, false); + if (leftBorderNotWhite) { + left--; + aBlackPointFoundOnBorder = true; + } + } + + if (left < 0) { + sizeExceeded = true; + break; + } + + // .___. + // . . + // ..... + boolean topBorderNotWhite = true; + while (topBorderNotWhite && up >= 0) { + topBorderNotWhite = containsBlackPoint(left, right, up, true); + if (topBorderNotWhite) { + up--; + aBlackPointFoundOnBorder = true; + } + } + + if (up < 0) { + sizeExceeded = true; + break; + } + + if (aBlackPointFoundOnBorder) { + atLeastOneBlackPointFoundOnBorder = true; + } + + } + + if (!sizeExceeded && atLeastOneBlackPointFoundOnBorder) { + + int maxSize = right - left; + + ResultPoint z = null; + for (int i = 1; i < maxSize; i++) { + z = getBlackPointOnSegment(left, down - i, left + i, down); + if (z != null) { + break; + } + } + + if (z == null) { + throw NotFoundException.getNotFoundInstance(); + } + + ResultPoint t = null; + //go down right + for (int i = 1; i < maxSize; i++) { + t = getBlackPointOnSegment(left, up + i, left + i, up); + if (t != null) { + break; + } + } + + if (t == null) { + throw NotFoundException.getNotFoundInstance(); + } + + ResultPoint x = null; + //go down left + for (int i = 1; i < maxSize; i++) { + x = getBlackPointOnSegment(right, up + i, right - i, up); + if (x != null) { + break; + } + } + + if (x == null) { + throw NotFoundException.getNotFoundInstance(); + } + + ResultPoint y = null; + //go up left + for (int i = 1; i < maxSize; i++) { + y = getBlackPointOnSegment(right, down - i, right - i, down); + if (y != null) { + break; + } + } + + if (y == null) { + throw NotFoundException.getNotFoundInstance(); + } + + return centerEdges(y, z, x, t); + + } else { + throw NotFoundException.getNotFoundInstance(); + } + } + + /** + * Ends up being a bit faster than Math.round(). This merely rounds its + * argument to the nearest int, where x.5 rounds up. + */ + private static int round(float d) { + return (int) (d + 0.5f); + } + + private ResultPoint getBlackPointOnSegment(float aX, float aY, float bX, float bY) { + int dist = distanceL2(aX, aY, bX, bY); + float xStep = (bX - aX) / dist; + float yStep = (bY - aY) / dist; + + for (int i = 0; i < dist; i++) { + int x = round(aX + i * xStep); + int y = round(aY + i * yStep); + if (image.get(x, y)) { + return new ResultPoint(x, y); + } + } + return null; + } + + private static int distanceL2(float aX, float aY, float bX, float bY) { + float xDiff = aX - bX; + float yDiff = aY - bY; + return round((float) Math.sqrt(xDiff * xDiff + yDiff * yDiff)); + } + + /** + * recenters the points of a constant distance towards the center + * + * @param y bottom most point + * @param z left most point + * @param x right most point + * @param t top most point + * @return {@link ResultPoint}[] describing the corners of the rectangular + * region. The first and last points are opposed on the diagonal, as + * are the second and third. The first point will be the topmost + * point and the last, the bottommost. The second point will be + * leftmost and the third, the rightmost + */ + private ResultPoint[] centerEdges(ResultPoint y, ResultPoint z, + ResultPoint x, ResultPoint t) { + + // + // t t + // z x + // x OR z + // y y + // + + float yi = y.getX(); + float yj = y.getY(); + float zi = z.getX(); + float zj = z.getY(); + float xi = x.getX(); + float xj = x.getY(); + float ti = t.getX(); + float tj = t.getY(); + + if (yi < width / 2) { + return new ResultPoint[]{ + new ResultPoint(ti - CORR, tj + CORR), + new ResultPoint(zi + CORR, zj + CORR), + new ResultPoint(xi - CORR, xj - CORR), + new ResultPoint(yi + CORR, yj - CORR)}; + } else { + return new ResultPoint[]{ + new ResultPoint(ti + CORR, tj + CORR), + new ResultPoint(zi + CORR, zj - CORR), + new ResultPoint(xi - CORR, xj + CORR), + new ResultPoint(yi - CORR, yj - CORR)}; + } + } + + /** + * Determines whether a segment contains a black point + * + * @param a min value of the scanned coordinate + * @param b max value of the scanned coordinate + * @param fixed value of fixed coordinate + * @param horizontal set to true if scan must be horizontal, false if vertical + * @return true if a black point has been found, else false. + */ + private boolean containsBlackPoint(int a, int b, int fixed, boolean horizontal) { + + if (horizontal) { + for (int x = a; x <= b; x++) { + if (image.get(x, fixed)) { + return true; + } + } + } else { + for (int y = a; y <= b; y++) { + if (image.get(fixed, y)) { + return true; + } + } + } + + return false; + } + +}
\ No newline at end of file diff --git a/libraries/zxing/src/com/google/zxing/common/reedsolomon/GenericGF.java b/libraries/zxing/src/com/google/zxing/common/reedsolomon/GenericGF.java new file mode 100644 index 000000000..859c379ee --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/reedsolomon/GenericGF.java @@ -0,0 +1,188 @@ +/* + * 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</code> + * 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); // x^12 + x^6 + x^5 + x^3 + 1 + public static final GenericGF AZTEC_DATA_10 = new GenericGF(0x409, 1024); // x^10 + x^3 + 1 + public static final GenericGF AZTEC_DATA_6 = new GenericGF(0x43, 64); // x^6 + x + 1 + public static final GenericGF AZTEC_PARAM = new GenericGF(0x13, 16); // x^4 + x + 1 + public static final GenericGF QR_CODE_FIELD_256 = new GenericGF(0x011D, 256); // x^8 + x^4 + x^3 + x^2 + 1 + public static final GenericGF DATA_MATRIX_FIELD_256 = new GenericGF(0x012D, 256); // x^8 + x^5 + x^3 + x^2 + 1 + public static final GenericGF AZTEC_DATA_8 = DATA_MATRIX_FIELD_256; + + 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 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 + */ + public GenericGF(int primitive, int size) { + this.primitive = primitive; + this.size = size; + + 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]; + } + + /** + * @param a + * @param b + * @return product of a and b in GF(size) + */ + int multiply(int a, int b) { + checkInit(); + + if (a == 0 || b == 0) { + return 0; + } + + if (a<0 || b <0 || a>=size || b >=size){ + a++; + } + + int logSum = logTable[a] + logTable[b]; + return expTable[(logSum % size) + logSum / size]; + } + + public int getSize(){ + return size; + } + +} diff --git a/libraries/zxing/src/com/google/zxing/common/reedsolomon/GenericGFPoly.java b/libraries/zxing/src/com/google/zxing/common/reedsolomon/GenericGFPoly.java new file mode 100644 index 000000000..056802287 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/reedsolomon/GenericGFPoly.java @@ -0,0 +1,263 @@ +/* + * 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 == null || 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 i = 0; i < size; i++) { + result = GenericGF.addOrSubtract(result, coefficients[i]); + } + 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 }; + } + + public String toString() { + StringBuffer result = new StringBuffer(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/libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java b/libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java new file mode 100644 index 000000000..b523fd34b --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java @@ -0,0 +1,194 @@ +/* + * 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 dataMatrix = field.equals(GenericGF.DATA_MATRIX_FIELD_256); + boolean noError = true; + for (int i = 0; i < twoS; i++) { + // Thanks to sanfordsquires for this fix: + int eval = poly.evaluateAt(field.exp(dataMatrix ? i + 1 : i)); + 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, dataMatrix); + 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 sLast = field.getOne(); + GenericGFPoly s = field.getZero(); + 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 sLastLast = sLast; + GenericGFPoly tLastLast = tLast; + rLast = r; + sLast = s; + 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)); + } + + s = q.multiply(sLast).addOrSubtract(sLastLast); + t = q.multiply(tLast).addOrSubtract(tLastLast); + } + + 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, boolean dataMatrix) { + // 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)); + // Thanks to sanfordsquires for this fix: + if (dataMatrix) { + result[i] = field.multiply(result[i], xiInverse); + } + } + return result; + } + +} diff --git a/libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java b/libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java new file mode 100644 index 000000000..05e2ae03a --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java @@ -0,0 +1,75 @@ +/* + * 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.reedsolomon; + +import java.util.Vector; + +/** + * <p>Implements Reed-Solomon enbcoding, as the name implies.</p> + * + * @author Sean Owen + * @author William Rucklidge + */ +public final class ReedSolomonEncoder { + + private final GenericGF field; + private final Vector cachedGenerators; + + public ReedSolomonEncoder(GenericGF field) { + if (!GenericGF.QR_CODE_FIELD_256.equals(field)) { + throw new IllegalArgumentException("Only QR Code is supported at this time"); + } + this.field = field; + this.cachedGenerators = new Vector(); + cachedGenerators.addElement(new GenericGFPoly(field, new int[] { 1 })); + } + + private GenericGFPoly buildGenerator(int degree) { + if (degree >= cachedGenerators.size()) { + GenericGFPoly lastGenerator = (GenericGFPoly) cachedGenerators.elementAt(cachedGenerators.size() - 1); + for (int d = cachedGenerators.size(); d <= degree; d++) { + GenericGFPoly nextGenerator = lastGenerator.multiply(new GenericGFPoly(field, new int[] { 1, field.exp(d - 1) })); + cachedGenerators.addElement(nextGenerator); + lastGenerator = nextGenerator; + } + } + return (GenericGFPoly) cachedGenerators.elementAt(degree); + } + + public void encode(int[] toEncode, int ecBytes) { + if (ecBytes == 0) { + throw new IllegalArgumentException("No error correction bytes"); + } + int dataBytes = toEncode.length - ecBytes; + if (dataBytes <= 0) { + throw new IllegalArgumentException("No data bytes provided"); + } + GenericGFPoly generator = buildGenerator(ecBytes); + int[] infoCoefficients = new int[dataBytes]; + System.arraycopy(toEncode, 0, infoCoefficients, 0, dataBytes); + GenericGFPoly info = new GenericGFPoly(field, infoCoefficients); + info = info.multiplyByMonomial(ecBytes, 1); + GenericGFPoly remainder = info.divide(generator)[1]; + int[] coefficients = remainder.getCoefficients(); + int numZeroCoefficients = ecBytes - coefficients.length; + for (int i = 0; i < numZeroCoefficients; i++) { + toEncode[dataBytes + i] = 0; + } + System.arraycopy(coefficients, 0, toEncode, dataBytes + numZeroCoefficients, coefficients.length); + } + +} diff --git a/libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java b/libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java new file mode 100644 index 000000000..d5b45a612 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java @@ -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/libraries/zxing/src/com/google/zxing/multi/ByQuadrantReader.java b/libraries/zxing/src/com/google/zxing/multi/ByQuadrantReader.java new file mode 100644 index 000000000..35904d364 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/multi/ByQuadrantReader.java @@ -0,0 +1,96 @@ +/* + * 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.multi; + +import com.google.zxing.BinaryBitmap; +import com.google.zxing.ChecksumException; +import com.google.zxing.FormatException; +import com.google.zxing.NotFoundException; +import com.google.zxing.Reader; +import com.google.zxing.Result; + +import java.util.Hashtable; + +/** + * This class attempts to decode a barcode from an image, not by scanning the whole image, + * but by scanning subsets of the image. This is important when there may be multiple barcodes in + * an image, and detecting a barcode may find parts of multiple barcode and fail to decode + * (e.g. QR Codes). Instead this scans the four quadrants of the image -- and also the center + * 'quadrant' to cover the case where a barcode is found in the center. + * + * @see GenericMultipleBarcodeReader + */ +public final class ByQuadrantReader implements Reader { + + private final Reader delegate; + + public ByQuadrantReader(Reader delegate) { + this.delegate = delegate; + } + + public Result decode(BinaryBitmap image) + throws NotFoundException, ChecksumException, FormatException { + return decode(image, null); + } + + public Result decode(BinaryBitmap image, Hashtable hints) + throws NotFoundException, ChecksumException, FormatException { + + int width = image.getWidth(); + int height = image.getHeight(); + int halfWidth = width / 2; + int halfHeight = height / 2; + + BinaryBitmap topLeft = image.crop(0, 0, halfWidth, halfHeight); + try { + return delegate.decode(topLeft, hints); + } catch (NotFoundException re) { + // continue + } + + BinaryBitmap topRight = image.crop(halfWidth, 0, halfWidth, halfHeight); + try { + return delegate.decode(topRight, hints); + } catch (NotFoundException re) { + // continue + } + + BinaryBitmap bottomLeft = image.crop(0, halfHeight, halfWidth, halfHeight); + try { + return delegate.decode(bottomLeft, hints); + } catch (NotFoundException re) { + // continue + } + + BinaryBitmap bottomRight = image.crop(halfWidth, halfHeight, halfWidth, halfHeight); + try { + return delegate.decode(bottomRight, hints); + } catch (NotFoundException re) { + // continue + } + + int quarterWidth = halfWidth / 2; + int quarterHeight = halfHeight / 2; + BinaryBitmap center = image.crop(quarterWidth, quarterHeight, halfWidth, halfHeight); + return delegate.decode(center, hints); + } + + public void reset() { + delegate.reset(); + } + +} diff --git a/libraries/zxing/src/com/google/zxing/multi/GenericMultipleBarcodeReader.java b/libraries/zxing/src/com/google/zxing/multi/GenericMultipleBarcodeReader.java new file mode 100644 index 000000000..70d454251 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/multi/GenericMultipleBarcodeReader.java @@ -0,0 +1,156 @@ +/* + * 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.multi; + +import com.google.zxing.BinaryBitmap; +import com.google.zxing.NotFoundException; +import com.google.zxing.Reader; +import com.google.zxing.ReaderException; +import com.google.zxing.Result; +import com.google.zxing.ResultPoint; + +import java.util.Hashtable; +import java.util.Vector; + +/** + * <p>Attempts to locate multiple barcodes in an image by repeatedly decoding portion of the image. + * After one barcode is found, the areas left, above, right and below the barcode's + * {@link com.google.zxing.ResultPoint}s are scanned, recursively.</p> + * + * <p>A caller may want to also employ {@link ByQuadrantReader} when attempting to find multiple + * 2D barcodes, like QR Codes, in an image, where the presence of multiple barcodes might prevent + * detecting any one of them.</p> + * + * <p>That is, instead of passing a {@link Reader} a caller might pass + * <code>new ByQuadrantReader(reader)</code>.</p> + * + * @author Sean Owen + */ +public final class GenericMultipleBarcodeReader implements MultipleBarcodeReader { + + private static final int MIN_DIMENSION_TO_RECUR = 100; + + private final Reader delegate; + + public GenericMultipleBarcodeReader(Reader delegate) { + this.delegate = delegate; + } + + public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException { + return decodeMultiple(image, null); + } + + public Result[] decodeMultiple(BinaryBitmap image, Hashtable hints) + throws NotFoundException { + Vector results = new Vector(); + doDecodeMultiple(image, hints, results, 0, 0); + if (results.isEmpty()) { + throw NotFoundException.getNotFoundInstance(); + } + int numResults = results.size(); + Result[] resultArray = new Result[numResults]; + for (int i = 0; i < numResults; i++) { + resultArray[i] = (Result) results.elementAt(i); + } + return resultArray; + } + + private void doDecodeMultiple(BinaryBitmap image, + Hashtable hints, + Vector results, + int xOffset, + int yOffset) { + Result result; + try { + result = delegate.decode(image, hints); + } catch (ReaderException re) { + return; + } + boolean alreadyFound = false; + for (int i = 0; i < results.size(); i++) { + Result existingResult = (Result) results.elementAt(i); + if (existingResult.getText().equals(result.getText())) { + alreadyFound = true; + break; + } + } + if (alreadyFound) { + return; + } + results.addElement(translateResultPoints(result, xOffset, yOffset)); + ResultPoint[] resultPoints = result.getResultPoints(); + if (resultPoints == null || resultPoints.length == 0) { + return; + } + int width = image.getWidth(); + int height = image.getHeight(); + float minX = width; + float minY = height; + float maxX = 0.0f; + float maxY = 0.0f; + for (int i = 0; i < resultPoints.length; i++) { + ResultPoint point = resultPoints[i]; + float x = point.getX(); + float y = point.getY(); + if (x < minX) { + minX = x; + } + if (y < minY) { + minY = y; + } + if (x > maxX) { + maxX = x; + } + if (y > maxY) { + maxY = y; + } + } + + // Decode left of barcode + if (minX > MIN_DIMENSION_TO_RECUR) { + doDecodeMultiple(image.crop(0, 0, (int) minX, height), + hints, results, xOffset, yOffset); + } + // Decode above barcode + if (minY > MIN_DIMENSION_TO_RECUR) { + doDecodeMultiple(image.crop(0, 0, width, (int) minY), + hints, results, xOffset, yOffset); + } + // Decode right of barcode + if (maxX < width - MIN_DIMENSION_TO_RECUR) { + doDecodeMultiple(image.crop((int) maxX, 0, width - (int) maxX, height), + hints, results, xOffset + (int) maxX, yOffset); + } + // Decode below barcode + if (maxY < height - MIN_DIMENSION_TO_RECUR) { + doDecodeMultiple(image.crop(0, (int) maxY, width, height - (int) maxY), + hints, results, xOffset, yOffset + (int) maxY); + } + } + + private static Result translateResultPoints(Result result, int xOffset, int yOffset) { + ResultPoint[] oldResultPoints = result.getResultPoints(); + ResultPoint[] newResultPoints = new ResultPoint[oldResultPoints.length]; + for (int i = 0; i < oldResultPoints.length; i++) { + ResultPoint oldPoint = oldResultPoints[i]; + newResultPoints[i] = new ResultPoint(oldPoint.getX() + xOffset, oldPoint.getY() + yOffset); + } + return new Result(result.getText(), result.getRawBytes(), newResultPoints, + result.getBarcodeFormat()); + } + +} diff --git a/libraries/zxing/src/com/google/zxing/multi/MultipleBarcodeReader.java b/libraries/zxing/src/com/google/zxing/multi/MultipleBarcodeReader.java new file mode 100644 index 000000000..5f0c7eb5d --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/multi/MultipleBarcodeReader.java @@ -0,0 +1,37 @@ +/* + * 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.multi; + +import com.google.zxing.BinaryBitmap; +import com.google.zxing.NotFoundException; +import com.google.zxing.Result; + +import java.util.Hashtable; + +/** + * Implementation of this interface attempt to read several barcodes from one image. + * + * @see com.google.zxing.Reader + * @author Sean Owen + */ +public interface MultipleBarcodeReader { + + Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException; + + Result[] decodeMultiple(BinaryBitmap image, Hashtable hints) throws NotFoundException; + +} diff --git a/libraries/zxing/src/com/google/zxing/multi/qrcode/detector/MultiDetector.java b/libraries/zxing/src/com/google/zxing/multi/qrcode/detector/MultiDetector.java new file mode 100644 index 000000000..584c41404 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/multi/qrcode/detector/MultiDetector.java @@ -0,0 +1,72 @@ +/* + * 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.multi.qrcode.detector; + +import com.google.zxing.NotFoundException; +import com.google.zxing.ReaderException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.common.DetectorResult; +import com.google.zxing.qrcode.detector.Detector; +import com.google.zxing.qrcode.detector.FinderPatternInfo; + +import java.util.Hashtable; +import java.util.Vector; + +/** + * <p>Encapsulates logic that can detect one or more QR Codes in an image, even if the QR Code + * is rotated or skewed, or partially obscured.</p> + * + * @author Sean Owen + * @author Hannes Erven + */ +public final class MultiDetector extends Detector { + + private static final DetectorResult[] EMPTY_DETECTOR_RESULTS = new DetectorResult[0]; + + public MultiDetector(BitMatrix image) { + super(image); + } + + public DetectorResult[] detectMulti(Hashtable hints) throws NotFoundException { + BitMatrix image = getImage(); + MultiFinderPatternFinder finder = new MultiFinderPatternFinder(image); + FinderPatternInfo[] info = finder.findMulti(hints); + + if (info == null || info.length == 0) { + throw NotFoundException.getNotFoundInstance(); + } + + Vector result = new Vector(); + for (int i = 0; i < info.length; i++) { + try { + result.addElement(processFinderPatternInfo(info[i])); + } catch (ReaderException e) { + // ignore + } + } + if (result.isEmpty()) { + return EMPTY_DETECTOR_RESULTS; + } else { + DetectorResult[] resultArray = new DetectorResult[result.size()]; + for (int i = 0; i < result.size(); i++) { + resultArray[i] = (DetectorResult) result.elementAt(i); + } + return resultArray; + } + } + +} diff --git a/libraries/zxing/src/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java b/libraries/zxing/src/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java new file mode 100644 index 000000000..1162324e2 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java @@ -0,0 +1,324 @@ +/*
+ * 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.multi.qrcode.detector;
+
+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;
+import com.google.zxing.common.Collections;
+import com.google.zxing.common.Comparator;
+import com.google.zxing.qrcode.detector.FinderPattern;
+import com.google.zxing.qrcode.detector.FinderPatternFinder;
+import com.google.zxing.qrcode.detector.FinderPatternInfo;
+
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * <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.
+ *
+ * <p>In contrast to {@link FinderPatternFinder}, this class will return an array of all possible
+ * QR code locations in the image.</p>
+ *
+ * <p>Use the TRY_HARDER hint to ask for a more thorough detection.</p>
+ *
+ * @author Sean Owen
+ * @author Hannes Erven
+ */
+final class MultiFinderPatternFinder extends FinderPatternFinder {
+
+ private static final FinderPatternInfo[] EMPTY_RESULT_ARRAY = new FinderPatternInfo[0];
+
+ // TODO MIN_MODULE_COUNT and MAX_MODULE_COUNT would be great hints to ask the user for
+ // since it limits the number of regions to decode
+
+ // max. legal count of modules per QR code edge (177)
+ private static final float MAX_MODULE_COUNT_PER_EDGE = 180;
+ // min. legal count per modules per QR code edge (11)
+ private static final float MIN_MODULE_COUNT_PER_EDGE = 9;
+
+ /**
+ * More or less arbitrary cutoff point for determining if two finder patterns might belong
+ * to the same code if they differ less than DIFF_MODSIZE_CUTOFF_PERCENT percent in their
+ * estimated modules sizes.
+ */
+ private static final float DIFF_MODSIZE_CUTOFF_PERCENT = 0.05f;
+
+ /**
+ * More or less arbitrary cutoff point for determining if two finder patterns might belong
+ * to the same code if they differ less than DIFF_MODSIZE_CUTOFF pixels/module in their
+ * estimated modules sizes.
+ */
+ private static final float DIFF_MODSIZE_CUTOFF = 0.5f;
+
+
+ /**
+ * A comparator that orders FinderPatterns by their estimated module size.
+ */
+ private static class ModuleSizeComparator implements Comparator {
+ public int compare(Object center1, Object center2) {
+ float value = ((FinderPattern) center2).getEstimatedModuleSize() -
+ ((FinderPattern) center1).getEstimatedModuleSize();
+ return value < 0.0 ? -1 : value > 0.0 ? 1 : 0;
+ }
+ }
+
+ /**
+ * <p>Creates a finder that will search the image for three finder patterns.</p>
+ *
+ * @param image image to search
+ */
+ MultiFinderPatternFinder(BitMatrix image) {
+ super(image);
+ }
+
+ MultiFinderPatternFinder(BitMatrix image, ResultPointCallback resultPointCallback) {
+ super(image, resultPointCallback);
+ }
+
+ /**
+ * @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 {
+ Vector possibleCenters = getPossibleCenters();
+ int size = possibleCenters.size();
+
+ if (size < 3) {
+ // Couldn't find enough finder patterns
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ /*
+ * Begin HE modifications to safely detect multiple codes of equal size
+ */
+ if (size == 3) {
+ return new FinderPattern[][]{
+ new FinderPattern[]{
+ (FinderPattern) possibleCenters.elementAt(0),
+ (FinderPattern) possibleCenters.elementAt(1),
+ (FinderPattern) possibleCenters.elementAt(2)
+ }
+ };
+ }
+
+ // Sort by estimated module size to speed up the upcoming checks
+ Collections.insertionSort(possibleCenters, new ModuleSizeComparator());
+
+ /*
+ * Now lets start: build a list of tuples of three finder locations that
+ * - feature similar module sizes
+ * - are placed in a distance so the estimated module count is within the QR specification
+ * - have similar distance between upper left/right and left top/bottom finder patterns
+ * - form a triangle with 90° angle (checked by comparing top right/bottom left distance
+ * with pythagoras)
+ *
+ * Note: we allow each point to be used for more than one code region: this might seem
+ * counterintuitive at first, but the performance penalty is not that big. At this point,
+ * we cannot make a good quality decision whether the three finders actually represent
+ * a QR code, or are just by chance layouted so it looks like there might be a QR code there.
+ * So, if the layout seems right, lets have the decoder try to decode.
+ */
+
+ Vector results = new Vector(); // holder for the results
+
+ for (int i1 = 0; i1 < (size - 2); i1++) {
+ FinderPattern p1 = (FinderPattern) possibleCenters.elementAt(i1);
+ if (p1 == null) {
+ continue;
+ }
+
+ for (int i2 = i1 + 1; i2 < (size - 1); i2++) {
+ FinderPattern p2 = (FinderPattern) possibleCenters.elementAt(i2);
+ if (p2 == null) {
+ continue;
+ }
+
+ // Compare the expected module sizes; if they are really off, skip
+ float vModSize12 = (p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize()) /
+ Math.min(p1.getEstimatedModuleSize(), p2.getEstimatedModuleSize());
+ float vModSize12A = Math.abs(p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize());
+ if (vModSize12A > DIFF_MODSIZE_CUTOFF && vModSize12 >= DIFF_MODSIZE_CUTOFF_PERCENT) {
+ // break, since elements are ordered by the module size deviation there cannot be
+ // any more interesting elements for the given p1.
+ break;
+ }
+
+ for (int i3 = i2 + 1; i3 < size; i3++) {
+ FinderPattern p3 = (FinderPattern) possibleCenters.elementAt(i3);
+ if (p3 == null) {
+ continue;
+ }
+
+ // Compare the expected module sizes; if they are really off, skip
+ float vModSize23 = (p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize()) /
+ Math.min(p2.getEstimatedModuleSize(), p3.getEstimatedModuleSize());
+ float vModSize23A = Math.abs(p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize());
+ if (vModSize23A > DIFF_MODSIZE_CUTOFF && vModSize23 >= DIFF_MODSIZE_CUTOFF_PERCENT) {
+ // break, since elements are ordered by the module size deviation there cannot be
+ // any more interesting elements for the given p1.
+ break;
+ }
+
+ FinderPattern[] test = {p1, p2, p3};
+ ResultPoint.orderBestPatterns(test);
+
+ // Calculate the distances: a = topleft-bottomleft, b=topleft-topright, c = diagonal
+ FinderPatternInfo info = new FinderPatternInfo(test);
+ float dA = ResultPoint.distance(info.getTopLeft(), info.getBottomLeft());
+ float dC = ResultPoint.distance(info.getTopRight(), info.getBottomLeft());
+ float dB = ResultPoint.distance(info.getTopLeft(), info.getTopRight());
+
+ // Check the sizes
+ float estimatedModuleCount = (dA + dB) / (p1.getEstimatedModuleSize() * 2.0f);
+ if (estimatedModuleCount > MAX_MODULE_COUNT_PER_EDGE ||
+ estimatedModuleCount < MIN_MODULE_COUNT_PER_EDGE) {
+ continue;
+ }
+
+ // Calculate the difference of the edge lengths in percent
+ float vABBC = Math.abs((dA - dB) / Math.min(dA, dB));
+ if (vABBC >= 0.1f) {
+ continue;
+ }
+
+ // Calculate the diagonal length by assuming a 90° angle at topleft
+ float dCpy = (float) Math.sqrt(dA * dA + dB * dB);
+ // Compare to the real distance in %
+ float vPyC = Math.abs((dC - dCpy) / Math.min(dC, dCpy));
+
+ if (vPyC >= 0.1f) {
+ continue;
+ }
+
+ // All tests passed!
+ results.addElement(test);
+ } // end iterate p3
+ } // end iterate p2
+ } // end iterate p1
+
+ if (!results.isEmpty()) {
+ FinderPattern[][] resultArray = new FinderPattern[results.size()][];
+ for (int i = 0; i < results.size(); i++) {
+ resultArray[i] = (FinderPattern[]) results.elementAt(i);
+ }
+ return resultArray;
+ }
+
+ // Nothing found!
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ public FinderPatternInfo[] findMulti(Hashtable hints) throws NotFoundException {
+ boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
+ BitMatrix image = getImage();
+ 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 = (int) (maxI / (MAX_MODULES * 4.0f) * 3);
+ if (iSkip < MIN_SKIP || tryHarder) {
+ iSkip = MIN_SKIP;
+ }
+
+ int[] stateCount = new int[5];
+ for (int i = iSkip - 1; i < maxI; 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) {
+ do { // Advance to next black pixel
+ j++;
+ } while (j < maxJ && !image.get(j, i));
+ j--; // back up to that last white pixel
+ }
+ // 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]++;
+ }
+ }
+ } // for j=...
+
+ if (foundPatternCross(stateCount)) {
+ handlePossibleCenter(stateCount, i, maxJ);
+ } // end if foundPatternCross
+ } // for i=iSkip-1 ...
+ FinderPattern[][] patternInfo = selectBestPatterns();
+ Vector result = new Vector();
+ for (int i = 0; i < patternInfo.length; i++) {
+ FinderPattern[] pattern = patternInfo[i];
+ ResultPoint.orderBestPatterns(pattern);
+ result.addElement(new FinderPatternInfo(pattern));
+ }
+
+ if (result.isEmpty()) {
+ return EMPTY_RESULT_ARRAY;
+ } else {
+ FinderPatternInfo[] resultArray = new FinderPatternInfo[result.size()];
+ for (int i = 0; i < result.size(); i++) {
+ resultArray[i] = (FinderPatternInfo) result.elementAt(i);
+ }
+ return resultArray;
+ }
+ }
+
+}
diff --git a/libraries/zxing/src/com/google/zxing/qrcode/QRCodeWriter.java b/libraries/zxing/src/com/google/zxing/qrcode/QRCodeWriter.java new file mode 100644 index 000000000..fff4f5d1e --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/QRCodeWriter.java @@ -0,0 +1,108 @@ +/* + * 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.qrcode; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.Writer; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.encoder.ByteMatrix; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.google.zxing.qrcode.encoder.Encoder; +import com.google.zxing.qrcode.encoder.QRCode; + +import java.util.Hashtable; + +/** + * This object renders a QR Code as a BitMatrix 2D array of greyscale values. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class QRCodeWriter implements Writer { + + private static final int QUIET_ZONE_SIZE = 0; // patched for Bitcoin Wallet + + public BitMatrix encode(String contents, BarcodeFormat format, int width, int height) + throws WriterException { + + return encode(contents, format, width, height, null); + } + + public BitMatrix encode(String contents, BarcodeFormat format, int width, int height, + Hashtable hints) throws WriterException { + + if (contents == null || contents.length() == 0) { + throw new IllegalArgumentException("Found empty contents"); + } + + if (format != BarcodeFormat.QR_CODE) { + throw new IllegalArgumentException("Can only encode QR_CODE, but got " + format); + } + + if (width < 0 || height < 0) { + throw new IllegalArgumentException("Requested dimensions are too small: " + width + 'x' + + height); + } + + ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L; + if (hints != null) { + ErrorCorrectionLevel requestedECLevel = (ErrorCorrectionLevel) hints.get(EncodeHintType.ERROR_CORRECTION); + if (requestedECLevel != null) { + errorCorrectionLevel = requestedECLevel; + } + } + + QRCode code = new QRCode(); + Encoder.encode(contents, errorCorrectionLevel, hints, code); + return renderResult(code, width, height); + } + + // Note that the input matrix uses 0 == white, 1 == black, while the output matrix uses + // 0 == black, 255 == white (i.e. an 8 bit greyscale bitmap). + private static BitMatrix renderResult(QRCode code, int width, int height) { + ByteMatrix input = code.getMatrix(); + int inputWidth = input.getWidth(); + int inputHeight = input.getHeight(); + int qrWidth = inputWidth + (QUIET_ZONE_SIZE << 1); + int qrHeight = inputHeight + (QUIET_ZONE_SIZE << 1); + int outputWidth = Math.max(width, qrWidth); + int outputHeight = Math.max(height, qrHeight); + + int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight); + // Padding includes both the quiet zone and the extra white pixels to accommodate the requested + // dimensions. For example, if input is 25x25 the QR will be 33x33 including the quiet zone. + // If the requested size is 200x160, the multiple will be 4, for a QR of 132x132. These will + // handle all the padding from 100x100 (the actual QR) up to 200x160. + int leftPadding = (outputWidth - (inputWidth * multiple)) / 2; + int topPadding = (outputHeight - (inputHeight * multiple)) / 2; + + BitMatrix output = new BitMatrix(outputWidth, outputHeight); + + for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) { + // Write the contents of this row of the barcode + for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) { + if (input.get(inputX, inputY) == 1) { + output.setRegion(outputX, outputY, multiple, multiple); + } + } + } + + return output; + } + +} diff --git a/libraries/zxing/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java new file mode 100644 index 000000000..9d131a554 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java @@ -0,0 +1,203 @@ +/*
+ * 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;
+
+ /**
+ * @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);
+ }
+ }
+
+ parsedVersion = Version.decodeVersionInformation(versionBits);
+ if (parsedVersion != null && parsedVersion.getDimensionForVersion() == dimension) {
+ return parsedVersion;
+ }
+
+ // 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);
+ }
+ }
+
+ parsedVersion = Version.decodeVersionInformation(versionBits);
+ if (parsedVersion != null && parsedVersion.getDimensionForVersion() == dimension) {
+ return parsedVersion;
+ }
+ throw FormatException.getFormatInstance();
+ }
+
+ private int copyBit(int i, int j, int versionBits) {
+ return bitMatrix.get(i, j) ? (versionBits << 1) | 0x1 : versionBits << 1;
+ }
+
+ /**
+ * <p>Reads the bits in the {@link BitMatrix} representing the finder pattern in the
+ * correct order in order to reconstitute 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((int) 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;
+ }
+
+}
\ No newline at end of file diff --git a/libraries/zxing/src/com/google/zxing/qrcode/decoder/DataBlock.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/DataBlock.java new file mode 100644 index 000000000..12959d9c1 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/decoder/DataBlock.java @@ -0,0 +1,123 @@ +/*
+ * 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 (int i = 0; i < ecBlockArray.length; i++) {
+ totalBlocks += ecBlockArray[i].getCount();
+ }
+
+ // Now establish DataBlocks of the appropriate size and number of data codewords
+ DataBlock[] result = new DataBlock[totalBlocks];
+ int numResultBlocks = 0;
+ for (int j = 0; j < ecBlockArray.length; j++) {
+ Version.ECB ecBlock = ecBlockArray[j];
+ 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/libraries/zxing/src/com/google/zxing/qrcode/decoder/DataMask.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/DataMask.java new file mode 100644 index 000000000..d29dbd47f --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/decoder/DataMask.java @@ -0,0 +1,155 @@ +/*
+ * 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 class DataMask000 extends DataMask {
+ boolean isMasked(int i, int j) {
+ return ((i + j) & 0x01) == 0;
+ }
+ }
+
+ /**
+ * 001: mask bits for which x mod 2 == 0
+ */
+ private static class DataMask001 extends DataMask {
+ boolean isMasked(int i, int j) {
+ return (i & 0x01) == 0;
+ }
+ }
+
+ /**
+ * 010: mask bits for which y mod 3 == 0
+ */
+ private static class DataMask010 extends DataMask {
+ boolean isMasked(int i, int j) {
+ return j % 3 == 0;
+ }
+ }
+
+ /**
+ * 011: mask bits for which (x + y) mod 3 == 0
+ */
+ private static class DataMask011 extends DataMask {
+ 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 class DataMask100 extends DataMask {
+ 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 class DataMask101 extends DataMask {
+ 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 class DataMask110 extends DataMask {
+ 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 class DataMask111 extends DataMask {
+ boolean isMasked(int i, int j) {
+ return ((((i + j) & 0x01) + ((i * j) % 3)) & 0x01) == 0;
+ }
+ }
+}
diff --git a/libraries/zxing/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java new file mode 100644 index 000000000..ff374ac50 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java @@ -0,0 +1,322 @@ +/* + * 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.BitSource; +import com.google.zxing.common.CharacterSetECI; +import com.google.zxing.common.DecoderResult; +import com.google.zxing.common.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.util.Hashtable; +import java.util.Vector; + +/** + * <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, Hashtable hints) + throws FormatException { + BitSource bits = new BitSource(bytes); + StringBuffer result = new StringBuffer(50); + CharacterSetECI currentCharacterSetECI = null; + boolean fc1InEffect = false; + Vector byteSegments = new Vector(1); + 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 { + try { + mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits + } catch (IllegalArgumentException iae) { + throw FormatException.getFormatInstance(); + } + } + if (!mode.equals(Mode.TERMINATOR)) { + if (mode.equals(Mode.FNC1_FIRST_POSITION) || mode.equals(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.equals(Mode.STRUCTURED_APPEND)) { + // 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.equals(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.equals(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.equals(Mode.NUMERIC)) { + decodeNumericSegment(bits, result, count); + } else if (mode.equals(Mode.ALPHANUMERIC)) { + decodeAlphanumericSegment(bits, result, count, fc1InEffect); + } else if (mode.equals(Mode.BYTE)) { + decodeByteSegment(bits, result, count, currentCharacterSetECI, byteSegments, hints); + } else if (mode.equals(Mode.KANJI)) { + decodeKanjiSegment(bits, result, count); + } else { + throw FormatException.getFormatInstance(); + } + } + } + } + } while (!mode.equals(Mode.TERMINATOR)); + + 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, + StringBuffer 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 uee) { + throw FormatException.getFormatInstance(); + } + } + + private static void decodeKanjiSegment(BitSource bits, + StringBuffer 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 uee) { + throw FormatException.getFormatInstance(); + } + } + + private static void decodeByteSegment(BitSource bits, + StringBuffer result, + int count, + CharacterSetECI currentCharacterSetECI, + Vector byteSegments, + Hashtable 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.getEncodingName(); + } + try { + result.append(new String(readBytes, encoding)); + } catch (UnsupportedEncodingException uce) { + throw FormatException.getFormatInstance(); + } + byteSegments.addElement(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, + StringBuffer result, + int count, + boolean fc1InEffect) throws FormatException { + // Read two characters at a time + int start = result.length(); + while (count > 1) { + 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 + 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, + StringBuffer result, + int count) throws FormatException { + // Read three digits at a time + while (count >= 3) { + // Each 10 bits encodes three digits + 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 + 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 + int digitBits = bits.readBits(4); + if (digitBits >= 10) { + throw FormatException.getFormatInstance(); + } + result.append(toAlphaNumericChar(digitBits)); + } + } + + private static int parseECIValue(BitSource bits) { + int firstByte = bits.readBits(8); + if ((firstByte & 0x80) == 0) { + // just one byte + return firstByte & 0x7F; + } else if ((firstByte & 0xC0) == 0x80) { + // two bytes + int secondByte = bits.readBits(8); + return ((firstByte & 0x3F) << 8) | secondByte; + } else if ((firstByte & 0xE0) == 0xC0) { + // three bytes + int secondThirdBytes = bits.readBits(16); + return ((firstByte & 0x1F) << 16) | secondThirdBytes; + } + throw new IllegalArgumentException("Bad ECI bits starting with byte " + firstByte); + } + +} diff --git a/libraries/zxing/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java new file mode 100644 index 000000000..e8d6c2589 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java @@ -0,0 +1,86 @@ +/* + * 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 final class ErrorCorrectionLevel { + + // No, we can't use an enum here. J2ME doesn't support it. + + /** + * L = ~7% correction + */ + public static final ErrorCorrectionLevel L = new ErrorCorrectionLevel(0, 0x01, "L"); + /** + * M = ~15% correction + */ + public static final ErrorCorrectionLevel M = new ErrorCorrectionLevel(1, 0x00, "M"); + /** + * Q = ~25% correction + */ + public static final ErrorCorrectionLevel Q = new ErrorCorrectionLevel(2, 0x03, "Q"); + /** + * H = ~30% correction + */ + public static final ErrorCorrectionLevel H = new ErrorCorrectionLevel(3, 0x02, "H"); + + private static final ErrorCorrectionLevel[] FOR_BITS = {M, L, H, Q}; + + private final int ordinal; + private final int bits; + private final String name; + + private ErrorCorrectionLevel(int ordinal, int bits, String name) { + this.ordinal = ordinal; + this.bits = bits; + this.name = name; + } + + public int ordinal() { + return ordinal; + } + + public int getBits() { + return bits; + } + + public String getName() { + return name; + } + + public String toString() { + return name; + } + + /** + * @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/libraries/zxing/src/com/google/zxing/qrcode/decoder/FormatInformation.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/FormatInformation.java new file mode 100644 index 000000000..1b76b0de5 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/decoder/FormatInformation.java @@ -0,0 +1,171 @@ +/* + * 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</code> + * 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 i = 0; i < FORMAT_INFO_DECODE_LOOKUP.length; i++) { + int[] decodeInfo = FORMAT_INFO_DECODE_LOOKUP[i]; + 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; + } + + public int hashCode() { + return (errorCorrectionLevel.ordinal() << 3) | (int) dataMask; + } + + 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/libraries/zxing/src/com/google/zxing/qrcode/decoder/Mode.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/Mode.java new file mode 100644 index 000000000..3c66217d3 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/decoder/Mode.java @@ -0,0 +1,117 @@ +/* + * 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 final class Mode { + + // No, we can't use an enum here. J2ME doesn't support it. + + public static final Mode TERMINATOR = new Mode(new int[]{0, 0, 0}, 0x00, "TERMINATOR"); // Not really a mode... + public static final Mode NUMERIC = new Mode(new int[]{10, 12, 14}, 0x01, "NUMERIC"); + public static final Mode ALPHANUMERIC = new Mode(new int[]{9, 11, 13}, 0x02, "ALPHANUMERIC"); + public static final Mode STRUCTURED_APPEND = new Mode(new int[]{0, 0, 0}, 0x03, "STRUCTURED_APPEND"); // Not supported + public static final Mode BYTE = new Mode(new int[]{8, 16, 16}, 0x04, "BYTE"); + public static final Mode ECI = new Mode(null, 0x07, "ECI"); // character counts don't apply + public static final Mode KANJI = new Mode(new int[]{8, 10, 12}, 0x08, "KANJI"); + public static final Mode FNC1_FIRST_POSITION = new Mode(null, 0x05, "FNC1_FIRST_POSITION"); + public static final Mode FNC1_SECOND_POSITION = new Mode(null, 0x09, "FNC1_SECOND_POSITION"); + /** See GBT 18284-2000; "Hanzi" is a transliteration of this mode name. */ + public static final Mode HANZI = new Mode(new int[]{8, 10, 12}, 0x0D, "HANZI"); + + private final int[] characterCountBitsForVersions; + private final int bits; + private final String name; + + private Mode(int[] characterCountBitsForVersions, int bits, String name) { + this.characterCountBitsForVersions = characterCountBitsForVersions; + this.bits = bits; + this.name = name; + } + + /** + * @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) { + if (characterCountBitsForVersions == null) { + throw new IllegalArgumentException("Character count doesn't apply to this mode"); + } + 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; + } + + public String getName() { + return name; + } + + public String toString() { + return name; + } + +} diff --git a/libraries/zxing/src/com/google/zxing/qrcode/decoder/Version.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/Version.java new file mode 100644 index 000000000..ba795de42 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/decoder/Version.java @@ -0,0 +1,586 @@ +/*
+ * 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 ecBlocks1,
+ ECBlocks ecBlocks2,
+ ECBlocks ecBlocks3,
+ ECBlocks ecBlocks4) {
+ this.versionNumber = versionNumber;
+ this.alignmentPatternCenters = alignmentPatternCenters;
+ this.ecBlocks = new ECBlocks[]{ecBlocks1, ecBlocks2, ecBlocks3, ecBlocks4};
+ int total = 0;
+ int ecCodewords = ecBlocks1.getECCodewordsPerBlock();
+ ECB[] ecbArray = ecBlocks1.getECBlocks();
+ for (int i = 0; i < ecbArray.length; i++) {
+ ECB ecBlock = ecbArray[i];
+ 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 iae) {
+ 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 = new ECB[]{ecBlocks};
+ }
+
+ ECBlocks(int ecCodewordsPerBlock, ECB ecBlocks1, ECB ecBlocks2) {
+ this.ecCodewordsPerBlock = ecCodewordsPerBlock;
+ this.ecBlocks = new ECB[]{ecBlocks1, ecBlocks2};
+ }
+
+ public int getECCodewordsPerBlock() {
+ return ecCodewordsPerBlock;
+ }
+
+ public int getNumBlocks() {
+ int total = 0;
+ for (int i = 0; i < ecBlocks.length; i++) {
+ total += ecBlocks[i].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;
+ }
+ }
+
+ 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/libraries/zxing/src/com/google/zxing/qrcode/detector/AlignmentPattern.java b/libraries/zxing/src/com/google/zxing/qrcode/detector/AlignmentPattern.java new file mode 100644 index 000000000..6fc1a2c88 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/detector/AlignmentPattern.java @@ -0,0 +1,48 @@ +/* + * 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 <= 1.0f; + } + return false; + } + +}
\ No newline at end of file diff --git a/libraries/zxing/src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java b/libraries/zxing/src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java new file mode 100644 index 000000000..3aadf284f --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java @@ -0,0 +1,279 @@ +/*
+ * 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.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Vector;
+
+/**
+ * <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.
+ *
+ * @author Sean Owen
+ */
+final class AlignmentPatternFinder {
+
+ private final BitMatrix image;
+ private final Vector 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 Vector(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 (AlignmentPattern) possibleCenters.elementAt(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 (float) (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 = (float) (stateCount[0] + stateCount[1] + stateCount[2]) / 3.0f;
+ int max = possibleCenters.size();
+ for (int index = 0; index < max; index++) {
+ AlignmentPattern center = (AlignmentPattern) possibleCenters.elementAt(index);
+ // Look for about the same center and module size:
+ if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
+ return new AlignmentPattern(centerJ, centerI, estimatedModuleSize);
+ }
+ }
+ // Hadn't found this before; save it
+ ResultPoint point = new AlignmentPattern(centerJ, centerI, estimatedModuleSize);
+ possibleCenters.addElement(point);
+ if (resultPointCallback != null) {
+ resultPointCallback.foundPossibleResultPoint(point);
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/libraries/zxing/src/com/google/zxing/qrcode/detector/Detector.java b/libraries/zxing/src/com/google/zxing/qrcode/detector/Detector.java new file mode 100644 index 000000000..724d39d59 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/detector/Detector.java @@ -0,0 +1,406 @@ +/* + * 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.qrcode.decoder.Version; + +import java.util.Hashtable; + +/** + * <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 BitMatrix getImage() { + return image; + } + + protected 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 DetectorResult detect(Hashtable 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 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); + } + + public 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 = sourceBottomRightY = dimMinusThree - 3.0f; + } 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 = 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> + */ + protected static int computeDimension(ResultPoint topLeft, + ResultPoint topRight, + ResultPoint bottomLeft, + float moduleSize) throws NotFoundException { + int tltrCentersDimension = round(ResultPoint.distance(topLeft, topRight) / moduleSize); + int tlblCentersDimension = 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 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() - fromX) / (float) (otherToX - fromX); + otherToX = image.getWidth(); + } + 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() - fromY) / (float) (otherToY - fromY); + otherToY = image.getHeight(); + } + otherToX = (int) (fromX + (otherToX - fromX) * scale); + + result += sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY); + return result; + } + + /** + * <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; + for (int x = fromX, y = fromY; x != toX; x += xstep) { + int realX = steep ? y : x; + int realY = steep ? x : y; + + // In white pixels, looking for black. + // FIXME(dswitkin): This method seems to assume square images, which can cause these calls to + // BitMatrix.get() to throw ArrayIndexOutOfBoundsException. + if (state == 1) { + if (image.get(realX, realY)) { + state++; + } + } else { + if (!image.get(realX, realY)) { + state++; + } + } + + // Found black, white, black, and stumbled back onto white, so we're done. + if (state == 3) { + int diffX = x - fromX; + int diffY = y - fromY; + if (xstep < 0) { + diffX++; + } + return (float) Math.sqrt((double) (diffX * diffX + diffY * diffY)); + } + error += dy; + if (error > 0) { + if (y == toY) { + break; + } + y += ystep; + error -= dx; + } + } + int diffX = toX - fromX; + int diffY = toY - fromY; + return (float) Math.sqrt((double) (diffX * diffX + diffY * diffY)); + } + + /** + * <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 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(); + } + + /** + * Ends up being a bit faster than Math.round(). This merely rounds its argument to the nearest int, + * where x.5 rounds up. + */ + private static int round(float d) { + return (int) (d + 0.5f); + } +} diff --git a/libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPattern.java b/libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPattern.java new file mode 100644 index 000000000..7a9914d76 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPattern.java @@ -0,0 +1,63 @@ +/* + * 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 int count; + + FinderPattern(float posX, float posY, float estimatedModuleSize) { + super(posX, posY); + this.estimatedModuleSize = estimatedModuleSize; + this.count = 1; + } + + 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 <= 1.0f; + } + return false; + } + +} diff --git a/libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPatternFinder.java b/libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPatternFinder.java new file mode 100644 index 000000000..01b3bde2a --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPatternFinder.java @@ -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 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;
+import com.google.zxing.common.Collections;
+import com.google.zxing.common.Comparator;
+
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * <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 Vector 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 Vector();
+ this.crossCheckStateCount = new int[5];
+ this.resultPointCallback = resultPointCallback;
+ }
+
+ protected BitMatrix getImage() {
+ return image;
+ }
+
+ protected Vector getPossibleCenters() {
+ return possibleCenters;
+ }
+
+ FinderPatternInfo find(Hashtable 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 (float) (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 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 = (float) stateCountTotal / 7.0f;
+ boolean found = false;
+ int max = possibleCenters.size();
+ for (int index = 0; index < max; index++) {
+ FinderPattern center = (FinderPattern) possibleCenters.elementAt(index);
+ // Look for about the same center and module size:
+ if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
+ center.incrementCount();
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ ResultPoint point = new FinderPattern(centerJ, centerI, estimatedModuleSize);
+ possibleCenters.addElement(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;
+ }
+ FinderPattern firstConfirmedCenter = null;
+ for (int i = 0; i < max; i++) {
+ FinderPattern center = (FinderPattern) possibleCenters.elementAt(i);
+ 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 (int i = 0; i < max; i++) {
+ FinderPattern pattern = (FinderPattern) possibleCenters.elementAt(i);
+ 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 / (float) max;
+ float totalDeviation = 0.0f;
+ for (int i = 0; i < max; i++) {
+ FinderPattern pattern = (FinderPattern) possibleCenters.elementAt(i);
+ 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 (int i = 0; i < startSize; i++) {
+ float size = ((FinderPattern) possibleCenters.elementAt(i)).getEstimatedModuleSize();
+ totalModuleSize += size;
+ square += size * size;
+ }
+ float average = totalModuleSize / (float) startSize;
+ float stdDev = (float) Math.sqrt(square / startSize - average * average);
+
+ Collections.insertionSort(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 = (FinderPattern) possibleCenters.elementAt(i);
+ if (Math.abs(pattern.getEstimatedModuleSize() - average) > limit) {
+ possibleCenters.removeElementAt(i);
+ i--;
+ }
+ }
+ }
+
+ if (possibleCenters.size() > 3) {
+ // Throw away all but those first size candidate points we found.
+
+ float totalModuleSize = 0.0f;
+ for (int i = 0; i < possibleCenters.size(); i++) {
+ totalModuleSize += ((FinderPattern) possibleCenters.elementAt(i)).getEstimatedModuleSize();
+ }
+
+ float average = totalModuleSize / (float) possibleCenters.size();
+
+ Collections.insertionSort(possibleCenters, new CenterComparator(average));
+
+ possibleCenters.setSize(3);
+ }
+
+ return new FinderPattern[]{
+ (FinderPattern) possibleCenters.elementAt(0),
+ (FinderPattern) possibleCenters.elementAt(1),
+ (FinderPattern) possibleCenters.elementAt(2)
+ };
+ }
+
+ /**
+ * <p>Orders by furthest from average</p>
+ */
+ private static class FurthestFromAverageComparator implements Comparator {
+ private final float average;
+ private FurthestFromAverageComparator(float f) {
+ average = f;
+ }
+ public int compare(Object center1, Object center2) {
+ float dA = Math.abs(((FinderPattern) center2).getEstimatedModuleSize() - average);
+ float dB = Math.abs(((FinderPattern) center1).getEstimatedModuleSize() - average);
+ return dA < dB ? -1 : dA == dB ? 0 : 1;
+ }
+ }
+
+ /**
+ * <p>Orders by {@link FinderPattern#getCount()}, descending.</p>
+ */
+ private static class CenterComparator implements Comparator {
+ private final float average;
+ private CenterComparator(float f) {
+ average = f;
+ }
+ public int compare(Object center1, Object center2) {
+ if (((FinderPattern) center2).getCount() == ((FinderPattern) center1).getCount()) {
+ float dA = Math.abs(((FinderPattern) center2).getEstimatedModuleSize() - average);
+ float dB = Math.abs(((FinderPattern) center1).getEstimatedModuleSize() - average);
+ return dA < dB ? 1 : dA == dB ? 0 : -1;
+ } else {
+ return ((FinderPattern) center2).getCount() - ((FinderPattern) center1).getCount();
+ }
+ }
+ }
+
+}
diff --git a/libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPatternInfo.java b/libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPatternInfo.java new file mode 100644 index 000000000..3c3401085 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPatternInfo.java @@ -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/libraries/zxing/src/com/google/zxing/qrcode/encoder/BlockPair.java b/libraries/zxing/src/com/google/zxing/qrcode/encoder/BlockPair.java new file mode 100644 index 000000000..5714d9c3a --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/encoder/BlockPair.java @@ -0,0 +1,37 @@ +/* + * 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.qrcode.encoder; + +final class BlockPair { + + private final byte[] dataBytes; + private final byte[] errorCorrectionBytes; + + BlockPair(byte[] data, byte[] errorCorrection) { + dataBytes = data; + errorCorrectionBytes = errorCorrection; + } + + public byte[] getDataBytes() { + return dataBytes; + } + + public byte[] getErrorCorrectionBytes() { + return errorCorrectionBytes; + } + +} diff --git a/libraries/zxing/src/com/google/zxing/qrcode/encoder/ByteMatrix.java b/libraries/zxing/src/com/google/zxing/qrcode/encoder/ByteMatrix.java new file mode 100644 index 000000000..eb248a26c --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/encoder/ByteMatrix.java @@ -0,0 +1,97 @@ +/* + * 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.qrcode.encoder; + +/** + * A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + * unsigned container, it's up to you to do byteValue & 0xff at each location. + * + * JAVAPORT: The original code was a 2D array of ints, but since it only ever gets assigned + * -1, 0, and 1, I'm going to use less memory and go with bytes. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class ByteMatrix { + + private final byte[][] bytes; + private final int width; + private final int height; + + public ByteMatrix(int width, int height) { + bytes = new byte[height][width]; + this.width = width; + this.height = height; + } + + public int getHeight() { + return height; + } + + public int getWidth() { + return width; + } + + public byte get(int x, int y) { + return bytes[y][x]; + } + + public byte[][] getArray() { + return bytes; + } + + public void set(int x, int y, byte value) { + bytes[y][x] = value; + } + + public void set(int x, int y, int value) { + bytes[y][x] = (byte) value; + } + + public void set(int x, int y, boolean value) { + bytes[y][x] = (byte) (value ? 1 : 0); + } + + public void clear(byte value) { + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + bytes[y][x] = value; + } + } + } + + public String toString() { + StringBuffer result = new StringBuffer(2 * width * height + 2); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + switch (bytes[y][x]) { + case 0: + result.append(" 0"); + break; + case 1: + result.append(" 1"); + break; + default: + result.append(" "); + break; + } + } + result.append('\n'); + } + return result.toString(); + } + +} diff --git a/libraries/zxing/src/com/google/zxing/qrcode/encoder/Encoder.java b/libraries/zxing/src/com/google/zxing/qrcode/encoder/Encoder.java new file mode 100644 index 000000000..8796511ab --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/encoder/Encoder.java @@ -0,0 +1,557 @@ +/* + * 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.qrcode.encoder; + +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitArray; +import com.google.zxing.common.CharacterSetECI; +import com.google.zxing.common.ECI; +import com.google.zxing.common.reedsolomon.GenericGF; +import com.google.zxing.common.reedsolomon.ReedSolomonEncoder; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.google.zxing.qrcode.decoder.Mode; +import com.google.zxing.qrcode.decoder.Version; + +import java.io.UnsupportedEncodingException; +import java.util.Hashtable; +import java.util.Vector; + +/** + * @author satorux@google.com (Satoru Takabayashi) - creator + * @author dswitkin@google.com (Daniel Switkin) - ported from C++ + */ +public final class Encoder { + + // The original table is defined in the table 5 of JISX0510:2004 (p.19). + private static final int[] ALPHANUMERIC_TABLE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f + 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f + }; + + static final String DEFAULT_BYTE_MODE_ENCODING = "ISO-8859-1"; + + private Encoder() { + } + + // The mask penalty calculation is complicated. See Table 21 of JISX0510:2004 (p.45) for details. + // Basically it applies four rules and summate all penalties. + private static int calculateMaskPenalty(ByteMatrix matrix) { + int penalty = 0; + penalty += MaskUtil.applyMaskPenaltyRule1(matrix); + penalty += MaskUtil.applyMaskPenaltyRule2(matrix); + penalty += MaskUtil.applyMaskPenaltyRule3(matrix); + penalty += MaskUtil.applyMaskPenaltyRule4(matrix); + return penalty; + } + + /** + * Encode "bytes" with the error correction level "ecLevel". The encoding mode will be chosen + * internally by chooseMode(). On success, store the result in "qrCode". + * + * We recommend you to use QRCode.EC_LEVEL_L (the lowest level) for + * "getECLevel" since our primary use is to show QR code on desktop screens. We don't need very + * strong error correction for this purpose. + * + * Note that there is no way to encode bytes in MODE_KANJI. We might want to add EncodeWithMode() + * with which clients can specify the encoding mode. For now, we don't need the functionality. + */ + public static void encode(String content, ErrorCorrectionLevel ecLevel, QRCode qrCode) + throws WriterException { + encode(content, ecLevel, null, qrCode); + } + + public static void encode(String content, ErrorCorrectionLevel ecLevel, Hashtable hints, + QRCode qrCode) throws WriterException { + + String encoding = hints == null ? null : (String) hints.get(EncodeHintType.CHARACTER_SET); + if (encoding == null) { + encoding = DEFAULT_BYTE_MODE_ENCODING; + } + + // Step 1: Choose the mode (encoding). + Mode mode = chooseMode(content, encoding); + + // Step 2: Append "bytes" into "dataBits" in appropriate encoding. + BitArray dataBits = new BitArray(); + appendBytes(content, mode, dataBits, encoding); + // Step 3: Initialize QR code that can contain "dataBits". + int numInputBytes = dataBits.getSizeInBytes(); + initQRCode(numInputBytes, ecLevel, mode, qrCode); + + // Step 4: Build another bit vector that contains header and data. + BitArray headerAndDataBits = new BitArray(); + + // Step 4.5: Append ECI message if applicable + if (mode == Mode.BYTE && !DEFAULT_BYTE_MODE_ENCODING.equals(encoding)) { + CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding); + if (eci != null) { + appendECI(eci, headerAndDataBits); + } + } + + appendModeInfo(mode, headerAndDataBits); + + int numLetters = mode.equals(Mode.BYTE) ? dataBits.getSizeInBytes() : content.length(); + appendLengthInfo(numLetters, qrCode.getVersion(), mode, headerAndDataBits); + headerAndDataBits.appendBitArray(dataBits); + + // Step 5: Terminate the bits properly. + terminateBits(qrCode.getNumDataBytes(), headerAndDataBits); + + // Step 6: Interleave data bits with error correction code. + BitArray finalBits = new BitArray(); + interleaveWithECBytes(headerAndDataBits, qrCode.getNumTotalBytes(), qrCode.getNumDataBytes(), + qrCode.getNumRSBlocks(), finalBits); + + // Step 7: Choose the mask pattern and set to "qrCode". + ByteMatrix matrix = new ByteMatrix(qrCode.getMatrixWidth(), qrCode.getMatrixWidth()); + qrCode.setMaskPattern(chooseMaskPattern(finalBits, qrCode.getECLevel(), qrCode.getVersion(), + matrix)); + + // Step 8. Build the matrix and set it to "qrCode". + MatrixUtil.buildMatrix(finalBits, qrCode.getECLevel(), qrCode.getVersion(), + qrCode.getMaskPattern(), matrix); + qrCode.setMatrix(matrix); + // Step 9. Make sure we have a valid QR Code. + if (!qrCode.isValid()) { + throw new WriterException("Invalid QR code: " + qrCode.toString()); + } + } + + /** + * @return the code point of the table used in alphanumeric mode or + * -1 if there is no corresponding code in the table. + */ + static int getAlphanumericCode(int code) { + if (code < ALPHANUMERIC_TABLE.length) { + return ALPHANUMERIC_TABLE[code]; + } + return -1; + } + + public static Mode chooseMode(String content) { + return chooseMode(content, null); + } + + /** + * Choose the best mode by examining the content. Note that 'encoding' is used as a hint; + * if it is Shift_JIS, and the input is only double-byte Kanji, then we return {@link Mode#KANJI}. + */ + public static Mode chooseMode(String content, String encoding) { + if ("Shift_JIS".equals(encoding)) { + // Choose Kanji mode if all input are double-byte characters + return isOnlyDoubleByteKanji(content) ? Mode.KANJI : Mode.BYTE; + } + boolean hasNumeric = false; + boolean hasAlphanumeric = false; + for (int i = 0; i < content.length(); ++i) { + char c = content.charAt(i); + if (c >= '0' && c <= '9') { + hasNumeric = true; + } else if (getAlphanumericCode(c) != -1) { + hasAlphanumeric = true; + } else { + return Mode.BYTE; + } + } + if (hasAlphanumeric) { + return Mode.ALPHANUMERIC; + } else if (hasNumeric) { + return Mode.NUMERIC; + } + return Mode.BYTE; + } + + private static boolean isOnlyDoubleByteKanji(String content) { + byte[] bytes; + try { + bytes = content.getBytes("Shift_JIS"); + } catch (UnsupportedEncodingException uee) { + return false; + } + int length = bytes.length; + if (length % 2 != 0) { + return false; + } + for (int i = 0; i < length; i += 2) { + int byte1 = bytes[i] & 0xFF; + if ((byte1 < 0x81 || byte1 > 0x9F) && (byte1 < 0xE0 || byte1 > 0xEB)) { + return false; + } + } + return true; + } + + private static int chooseMaskPattern(BitArray bits, ErrorCorrectionLevel ecLevel, int version, + ByteMatrix matrix) throws WriterException { + + int minPenalty = Integer.MAX_VALUE; // Lower penalty is better. + int bestMaskPattern = -1; + // We try all mask patterns to choose the best one. + for (int maskPattern = 0; maskPattern < QRCode.NUM_MASK_PATTERNS; maskPattern++) { + MatrixUtil.buildMatrix(bits, ecLevel, version, maskPattern, matrix); + int penalty = calculateMaskPenalty(matrix); + if (penalty < minPenalty) { + minPenalty = penalty; + bestMaskPattern = maskPattern; + } + } + return bestMaskPattern; + } + + /** + * Initialize "qrCode" according to "numInputBytes", "ecLevel", and "mode". On success, + * modify "qrCode". + */ + private static void initQRCode(int numInputBytes, ErrorCorrectionLevel ecLevel, Mode mode, + QRCode qrCode) throws WriterException { + qrCode.setECLevel(ecLevel); + qrCode.setMode(mode); + + // In the following comments, we use numbers of Version 7-H. + for (int versionNum = 1; versionNum <= 40; versionNum++) { + Version version = Version.getVersionForNumber(versionNum); + // numBytes = 196 + int numBytes = version.getTotalCodewords(); + // getNumECBytes = 130 + Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel); + int numEcBytes = ecBlocks.getTotalECCodewords(); + // getNumRSBlocks = 5 + int numRSBlocks = ecBlocks.getNumBlocks(); + // getNumDataBytes = 196 - 130 = 66 + int numDataBytes = numBytes - numEcBytes; + // We want to choose the smallest version which can contain data of "numInputBytes" + some + // extra bits for the header (mode info and length info). The header can be three bytes + // (precisely 4 + 16 bits) at most. Hence we do +3 here. + if (numDataBytes >= numInputBytes + 3) { + // Yay, we found the proper rs block info! + qrCode.setVersion(versionNum); + qrCode.setNumTotalBytes(numBytes); + qrCode.setNumDataBytes(numDataBytes); + qrCode.setNumRSBlocks(numRSBlocks); + // getNumECBytes = 196 - 66 = 130 + qrCode.setNumECBytes(numEcBytes); + // matrix width = 21 + 6 * 4 = 45 + qrCode.setMatrixWidth(version.getDimensionForVersion()); + return; + } + } + throw new WriterException("Cannot find proper rs block info (input data too big?)"); + } + + /** + * Terminate bits as described in 8.4.8 and 8.4.9 of JISX0510:2004 (p.24). + */ + static void terminateBits(int numDataBytes, BitArray bits) throws WriterException { + int capacity = numDataBytes << 3; + if (bits.getSize() > capacity) { + throw new WriterException("data bits cannot fit in the QR Code" + bits.getSize() + " > " + + capacity); + } + for (int i = 0; i < 4 && bits.getSize() < capacity; ++i) { + bits.appendBit(false); + } + // Append termination bits. See 8.4.8 of JISX0510:2004 (p.24) for details. + // If the last byte isn't 8-bit aligned, we'll add padding bits. + int numBitsInLastByte = bits.getSize() & 0x07; + if (numBitsInLastByte > 0) { + for (int i = numBitsInLastByte; i < 8; i++) { + bits.appendBit(false); + } + } + // If we have more space, we'll fill the space with padding patterns defined in 8.4.9 (p.24). + int numPaddingBytes = numDataBytes - bits.getSizeInBytes(); + for (int i = 0; i < numPaddingBytes; ++i) { + bits.appendBits((i & 0x01) == 0 ? 0xEC : 0x11, 8); + } + if (bits.getSize() != capacity) { + throw new WriterException("Bits size does not equal capacity"); + } + } + + /** + * Get number of data bytes and number of error correction bytes for block id "blockID". Store + * the result in "numDataBytesInBlock", and "numECBytesInBlock". See table 12 in 8.5.1 of + * JISX0510:2004 (p.30) + */ + static void getNumDataBytesAndNumECBytesForBlockID(int numTotalBytes, int numDataBytes, + int numRSBlocks, int blockID, int[] numDataBytesInBlock, + int[] numECBytesInBlock) throws WriterException { + if (blockID >= numRSBlocks) { + throw new WriterException("Block ID too large"); + } + // numRsBlocksInGroup2 = 196 % 5 = 1 + int numRsBlocksInGroup2 = numTotalBytes % numRSBlocks; + // numRsBlocksInGroup1 = 5 - 1 = 4 + int numRsBlocksInGroup1 = numRSBlocks - numRsBlocksInGroup2; + // numTotalBytesInGroup1 = 196 / 5 = 39 + int numTotalBytesInGroup1 = numTotalBytes / numRSBlocks; + // numTotalBytesInGroup2 = 39 + 1 = 40 + int numTotalBytesInGroup2 = numTotalBytesInGroup1 + 1; + // numDataBytesInGroup1 = 66 / 5 = 13 + int numDataBytesInGroup1 = numDataBytes / numRSBlocks; + // numDataBytesInGroup2 = 13 + 1 = 14 + int numDataBytesInGroup2 = numDataBytesInGroup1 + 1; + // numEcBytesInGroup1 = 39 - 13 = 26 + int numEcBytesInGroup1 = numTotalBytesInGroup1 - numDataBytesInGroup1; + // numEcBytesInGroup2 = 40 - 14 = 26 + int numEcBytesInGroup2 = numTotalBytesInGroup2 - numDataBytesInGroup2; + // Sanity checks. + // 26 = 26 + if (numEcBytesInGroup1 != numEcBytesInGroup2) { + throw new WriterException("EC bytes mismatch"); + } + // 5 = 4 + 1. + if (numRSBlocks != numRsBlocksInGroup1 + numRsBlocksInGroup2) { + throw new WriterException("RS blocks mismatch"); + } + // 196 = (13 + 26) * 4 + (14 + 26) * 1 + if (numTotalBytes != + ((numDataBytesInGroup1 + numEcBytesInGroup1) * + numRsBlocksInGroup1) + + ((numDataBytesInGroup2 + numEcBytesInGroup2) * + numRsBlocksInGroup2)) { + throw new WriterException("Total bytes mismatch"); + } + + if (blockID < numRsBlocksInGroup1) { + numDataBytesInBlock[0] = numDataBytesInGroup1; + numECBytesInBlock[0] = numEcBytesInGroup1; + } else { + numDataBytesInBlock[0] = numDataBytesInGroup2; + numECBytesInBlock[0] = numEcBytesInGroup2; + } + } + + /** + * Interleave "bits" with corresponding error correction bytes. On success, store the result in + * "result". The interleave rule is complicated. See 8.6 of JISX0510:2004 (p.37) for details. + */ + static void interleaveWithECBytes(BitArray bits, int numTotalBytes, + int numDataBytes, int numRSBlocks, BitArray result) throws WriterException { + + // "bits" must have "getNumDataBytes" bytes of data. + if (bits.getSizeInBytes() != numDataBytes) { + throw new WriterException("Number of bits and data bytes does not match"); + } + + // Step 1. Divide data bytes into blocks and generate error correction bytes for them. We'll + // store the divided data bytes blocks and error correction bytes blocks into "blocks". + int dataBytesOffset = 0; + int maxNumDataBytes = 0; + int maxNumEcBytes = 0; + + // Since, we know the number of reedsolmon blocks, we can initialize the vector with the number. + Vector blocks = new Vector(numRSBlocks); + + for (int i = 0; i < numRSBlocks; ++i) { + int[] numDataBytesInBlock = new int[1]; + int[] numEcBytesInBlock = new int[1]; + getNumDataBytesAndNumECBytesForBlockID( + numTotalBytes, numDataBytes, numRSBlocks, i, + numDataBytesInBlock, numEcBytesInBlock); + + int size = numDataBytesInBlock[0]; + byte[] dataBytes = new byte[size]; + bits.toBytes(8*dataBytesOffset, dataBytes, 0, size); + byte[] ecBytes = generateECBytes(dataBytes, numEcBytesInBlock[0]); + blocks.addElement(new BlockPair(dataBytes, ecBytes)); + + maxNumDataBytes = Math.max(maxNumDataBytes, size); + maxNumEcBytes = Math.max(maxNumEcBytes, ecBytes.length); + dataBytesOffset += numDataBytesInBlock[0]; + } + if (numDataBytes != dataBytesOffset) { + throw new WriterException("Data bytes does not match offset"); + } + + // First, place data blocks. + for (int i = 0; i < maxNumDataBytes; ++i) { + for (int j = 0; j < blocks.size(); ++j) { + byte[] dataBytes = ((BlockPair) blocks.elementAt(j)).getDataBytes(); + if (i < dataBytes.length) { + result.appendBits(dataBytes[i], 8); + } + } + } + // Then, place error correction blocks. + for (int i = 0; i < maxNumEcBytes; ++i) { + for (int j = 0; j < blocks.size(); ++j) { + byte[] ecBytes = ((BlockPair) blocks.elementAt(j)).getErrorCorrectionBytes(); + if (i < ecBytes.length) { + result.appendBits(ecBytes[i], 8); + } + } + } + if (numTotalBytes != result.getSizeInBytes()) { // Should be same. + throw new WriterException("Interleaving error: " + numTotalBytes + " and " + + result.getSizeInBytes() + " differ."); + } + } + + static byte[] generateECBytes(byte[] dataBytes, int numEcBytesInBlock) { + int numDataBytes = dataBytes.length; + int[] toEncode = new int[numDataBytes + numEcBytesInBlock]; + for (int i = 0; i < numDataBytes; i++) { + toEncode[i] = dataBytes[i] & 0xFF; + } + new ReedSolomonEncoder(GenericGF.QR_CODE_FIELD_256).encode(toEncode, numEcBytesInBlock); + + byte[] ecBytes = new byte[numEcBytesInBlock]; + for (int i = 0; i < numEcBytesInBlock; i++) { + ecBytes[i] = (byte) toEncode[numDataBytes + i]; + } + return ecBytes; + } + + /** + * Append mode info. On success, store the result in "bits". + */ + static void appendModeInfo(Mode mode, BitArray bits) { + bits.appendBits(mode.getBits(), 4); + } + + + /** + * Append length info. On success, store the result in "bits". + */ + static void appendLengthInfo(int numLetters, int version, Mode mode, BitArray bits) + throws WriterException { + int numBits = mode.getCharacterCountBits(Version.getVersionForNumber(version)); + if (numLetters > ((1 << numBits) - 1)) { + throw new WriterException(numLetters + "is bigger than" + ((1 << numBits) - 1)); + } + bits.appendBits(numLetters, numBits); + } + + /** + * Append "bytes" in "mode" mode (encoding) into "bits". On success, store the result in "bits". + */ + static void appendBytes(String content, Mode mode, BitArray bits, String encoding) + throws WriterException { + if (mode.equals(Mode.NUMERIC)) { + appendNumericBytes(content, bits); + } else if (mode.equals(Mode.ALPHANUMERIC)) { + appendAlphanumericBytes(content, bits); + } else if (mode.equals(Mode.BYTE)) { + append8BitBytes(content, bits, encoding); + } else if (mode.equals(Mode.KANJI)) { + appendKanjiBytes(content, bits); + } else { + throw new WriterException("Invalid mode: " + mode); + } + } + + static void appendNumericBytes(String content, BitArray bits) { + int length = content.length(); + int i = 0; + while (i < length) { + int num1 = content.charAt(i) - '0'; + if (i + 2 < length) { + // Encode three numeric letters in ten bits. + int num2 = content.charAt(i + 1) - '0'; + int num3 = content.charAt(i + 2) - '0'; + bits.appendBits(num1 * 100 + num2 * 10 + num3, 10); + i += 3; + } else if (i + 1 < length) { + // Encode two numeric letters in seven bits. + int num2 = content.charAt(i + 1) - '0'; + bits.appendBits(num1 * 10 + num2, 7); + i += 2; + } else { + // Encode one numeric letter in four bits. + bits.appendBits(num1, 4); + i++; + } + } + } + + static void appendAlphanumericBytes(String content, BitArray bits) throws WriterException { + int length = content.length(); + int i = 0; + while (i < length) { + int code1 = getAlphanumericCode(content.charAt(i)); + if (code1 == -1) { + throw new WriterException(); + } + if (i + 1 < length) { + int code2 = getAlphanumericCode(content.charAt(i + 1)); + if (code2 == -1) { + throw new WriterException(); + } + // Encode two alphanumeric letters in 11 bits. + bits.appendBits(code1 * 45 + code2, 11); + i += 2; + } else { + // Encode one alphanumeric letter in six bits. + bits.appendBits(code1, 6); + i++; + } + } + } + + static void append8BitBytes(String content, BitArray bits, String encoding) + throws WriterException { + byte[] bytes; + try { + bytes = content.getBytes(encoding); + } catch (UnsupportedEncodingException uee) { + throw new WriterException(uee.toString()); + } + for (int i = 0; i < bytes.length; ++i) { + bits.appendBits(bytes[i], 8); + } + } + + static void appendKanjiBytes(String content, BitArray bits) throws WriterException { + byte[] bytes; + try { + bytes = content.getBytes("Shift_JIS"); + } catch (UnsupportedEncodingException uee) { + throw new WriterException(uee.toString()); + } + int length = bytes.length; + for (int i = 0; i < length; i += 2) { + int byte1 = bytes[i] & 0xFF; + int byte2 = bytes[i + 1] & 0xFF; + int code = (byte1 << 8) | byte2; + int subtracted = -1; + if (code >= 0x8140 && code <= 0x9ffc) { + subtracted = code - 0x8140; + } else if (code >= 0xe040 && code <= 0xebbf) { + subtracted = code - 0xc140; + } + if (subtracted == -1) { + throw new WriterException("Invalid byte sequence"); + } + int encoded = ((subtracted >> 8) * 0xc0) + (subtracted & 0xff); + bits.appendBits(encoded, 13); + } + } + + private static void appendECI(ECI eci, BitArray bits) { + bits.appendBits(Mode.ECI.getBits(), 4); + // This is correct for values up to 127, which is all we need now. + bits.appendBits(eci.getValue(), 8); + } + +} diff --git a/libraries/zxing/src/com/google/zxing/qrcode/encoder/MaskUtil.java b/libraries/zxing/src/com/google/zxing/qrcode/encoder/MaskUtil.java new file mode 100644 index 000000000..61ccf48c1 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/encoder/MaskUtil.java @@ -0,0 +1,218 @@ +/* + * 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.qrcode.encoder; + +/** + * @author satorux@google.com (Satoru Takabayashi) - creator + * @author dswitkin@google.com (Daniel Switkin) - ported from C++ + */ +public final class MaskUtil { + + private MaskUtil() { + // do nothing + } + + // Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and + // give penalty to them. Example: 00000 or 11111. + public static int applyMaskPenaltyRule1(ByteMatrix matrix) { + return applyMaskPenaltyRule1Internal(matrix, true) + applyMaskPenaltyRule1Internal(matrix, false); + } + + // Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give + // penalty to them. + public static int applyMaskPenaltyRule2(ByteMatrix matrix) { + int penalty = 0; + byte[][] array = matrix.getArray(); + int width = matrix.getWidth(); + int height = matrix.getHeight(); + for (int y = 0; y < height - 1; ++y) { + for (int x = 0; x < width - 1; ++x) { + int value = array[y][x]; + if (value == array[y][x + 1] && value == array[y + 1][x] && value == array[y + 1][x + 1]) { + penalty += 3; + } + } + } + return penalty; + } + + // Apply mask penalty rule 3 and return the penalty. Find consecutive cells of 00001011101 or + // 10111010000, and give penalty to them. If we find patterns like 000010111010000, we give + // penalties twice (i.e. 40 * 2). + public static int applyMaskPenaltyRule3(ByteMatrix matrix) { + int penalty = 0; + byte[][] array = matrix.getArray(); + int width = matrix.getWidth(); + int height = matrix.getHeight(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + // Tried to simplify following conditions but failed. + if (x + 6 < width && + array[y][x] == 1 && + array[y][x + 1] == 0 && + array[y][x + 2] == 1 && + array[y][x + 3] == 1 && + array[y][x + 4] == 1 && + array[y][x + 5] == 0 && + array[y][x + 6] == 1 && + ((x + 10 < width && + array[y][x + 7] == 0 && + array[y][x + 8] == 0 && + array[y][x + 9] == 0 && + array[y][x + 10] == 0) || + (x - 4 >= 0 && + array[y][x - 1] == 0 && + array[y][x - 2] == 0 && + array[y][x - 3] == 0 && + array[y][x - 4] == 0))) { + penalty += 40; + } + if (y + 6 < height && + array[y][x] == 1 && + array[y + 1][x] == 0 && + array[y + 2][x] == 1 && + array[y + 3][x] == 1 && + array[y + 4][x] == 1 && + array[y + 5][x] == 0 && + array[y + 6][x] == 1 && + ((y + 10 < height && + array[y + 7][x] == 0 && + array[y + 8][x] == 0 && + array[y + 9][x] == 0 && + array[y + 10][x] == 0) || + (y - 4 >= 0 && + array[y - 1][x] == 0 && + array[y - 2][x] == 0 && + array[y - 3][x] == 0 && + array[y - 4][x] == 0))) { + penalty += 40; + } + } + } + return penalty; + } + + // Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give + // penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance. Examples: + // - 0% => 100 + // - 40% => 20 + // - 45% => 10 + // - 50% => 0 + // - 55% => 10 + // - 55% => 20 + // - 100% => 100 + public static int applyMaskPenaltyRule4(ByteMatrix matrix) { + int numDarkCells = 0; + byte[][] array = matrix.getArray(); + int width = matrix.getWidth(); + int height = matrix.getHeight(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (array[y][x] == 1) { + numDarkCells += 1; + } + } + } + int numTotalCells = matrix.getHeight() * matrix.getWidth(); + double darkRatio = (double) numDarkCells / numTotalCells; + return Math.abs((int) (darkRatio * 100 - 50)) / 5 * 10; + } + + // Return the mask bit for "getMaskPattern" at "x" and "y". See 8.8 of JISX0510:2004 for mask + // pattern conditions. + public static boolean getDataMaskBit(int maskPattern, int x, int y) { + if (!QRCode.isValidMaskPattern(maskPattern)) { + throw new IllegalArgumentException("Invalid mask pattern"); + } + int intermediate; + int temp; + switch (maskPattern) { + case 0: + intermediate = (y + x) & 0x1; + break; + case 1: + intermediate = y & 0x1; + break; + case 2: + intermediate = x % 3; + break; + case 3: + intermediate = (y + x) % 3; + break; + case 4: + intermediate = ((y >>> 1) + (x / 3)) & 0x1; + break; + case 5: + temp = y * x; + intermediate = (temp & 0x1) + (temp % 3); + break; + case 6: + temp = y * x; + intermediate = ((temp & 0x1) + (temp % 3)) & 0x1; + break; + case 7: + temp = y * x; + intermediate = ((temp % 3) + ((y + x) & 0x1)) & 0x1; + break; + default: + throw new IllegalArgumentException("Invalid mask pattern: " + maskPattern); + } + return intermediate == 0; + } + + // Helper function for applyMaskPenaltyRule1. We need this for doing this calculation in both + // vertical and horizontal orders respectively. + private static int applyMaskPenaltyRule1Internal(ByteMatrix matrix, boolean isHorizontal) { + int penalty = 0; + int numSameBitCells = 0; + int prevBit = -1; + // Horizontal mode: + // for (int i = 0; i < matrix.height(); ++i) { + // for (int j = 0; j < matrix.width(); ++j) { + // int bit = matrix.get(i, j); + // Vertical mode: + // for (int i = 0; i < matrix.width(); ++i) { + // for (int j = 0; j < matrix.height(); ++j) { + // int bit = matrix.get(j, i); + int iLimit = isHorizontal ? matrix.getHeight() : matrix.getWidth(); + int jLimit = isHorizontal ? matrix.getWidth() : matrix.getHeight(); + byte[][] array = matrix.getArray(); + for (int i = 0; i < iLimit; ++i) { + for (int j = 0; j < jLimit; ++j) { + int bit = isHorizontal ? array[i][j] : array[j][i]; + if (bit == prevBit) { + numSameBitCells += 1; + // Found five repetitive cells with the same color (bit). + // We'll give penalty of 3. + if (numSameBitCells == 5) { + penalty += 3; + } else if (numSameBitCells > 5) { + // After five repetitive cells, we'll add the penalty one + // by one. + penalty += 1; + } + } else { + numSameBitCells = 1; // Include the cell itself. + prevBit = bit; + } + } + numSameBitCells = 0; // Clear at each row/column. + } + return penalty; + } + +} diff --git a/libraries/zxing/src/com/google/zxing/qrcode/encoder/MatrixUtil.java b/libraries/zxing/src/com/google/zxing/qrcode/encoder/MatrixUtil.java new file mode 100644 index 000000000..3d434e675 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/encoder/MatrixUtil.java @@ -0,0 +1,524 @@ +/* + * 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.qrcode.encoder; + +import com.google.zxing.WriterException; +import com.google.zxing.common.BitArray; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + +/** + * @author satorux@google.com (Satoru Takabayashi) - creator + * @author dswitkin@google.com (Daniel Switkin) - ported from C++ + */ +public final class MatrixUtil { + + private MatrixUtil() { + // do nothing + } + + private static final int[][] POSITION_DETECTION_PATTERN = { + {1, 1, 1, 1, 1, 1, 1}, + {1, 0, 0, 0, 0, 0, 1}, + {1, 0, 1, 1, 1, 0, 1}, + {1, 0, 1, 1, 1, 0, 1}, + {1, 0, 1, 1, 1, 0, 1}, + {1, 0, 0, 0, 0, 0, 1}, + {1, 1, 1, 1, 1, 1, 1}, + }; + + private static final int[][] HORIZONTAL_SEPARATION_PATTERN = { + {0, 0, 0, 0, 0, 0, 0, 0}, + }; + + private static final int[][] VERTICAL_SEPARATION_PATTERN = { + {0}, {0}, {0}, {0}, {0}, {0}, {0}, + }; + + private static final int[][] POSITION_ADJUSTMENT_PATTERN = { + {1, 1, 1, 1, 1}, + {1, 0, 0, 0, 1}, + {1, 0, 1, 0, 1}, + {1, 0, 0, 0, 1}, + {1, 1, 1, 1, 1}, + }; + + // From Appendix E. Table 1, JIS0510X:2004 (p 71). The table was double-checked by komatsu. + private static final int[][] POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE = { + {-1, -1, -1, -1, -1, -1, -1}, // Version 1 + { 6, 18, -1, -1, -1, -1, -1}, // Version 2 + { 6, 22, -1, -1, -1, -1, -1}, // Version 3 + { 6, 26, -1, -1, -1, -1, -1}, // Version 4 + { 6, 30, -1, -1, -1, -1, -1}, // Version 5 + { 6, 34, -1, -1, -1, -1, -1}, // Version 6 + { 6, 22, 38, -1, -1, -1, -1}, // Version 7 + { 6, 24, 42, -1, -1, -1, -1}, // Version 8 + { 6, 26, 46, -1, -1, -1, -1}, // Version 9 + { 6, 28, 50, -1, -1, -1, -1}, // Version 10 + { 6, 30, 54, -1, -1, -1, -1}, // Version 11 + { 6, 32, 58, -1, -1, -1, -1}, // Version 12 + { 6, 34, 62, -1, -1, -1, -1}, // Version 13 + { 6, 26, 46, 66, -1, -1, -1}, // Version 14 + { 6, 26, 48, 70, -1, -1, -1}, // Version 15 + { 6, 26, 50, 74, -1, -1, -1}, // Version 16 + { 6, 30, 54, 78, -1, -1, -1}, // Version 17 + { 6, 30, 56, 82, -1, -1, -1}, // Version 18 + { 6, 30, 58, 86, -1, -1, -1}, // Version 19 + { 6, 34, 62, 90, -1, -1, -1}, // Version 20 + { 6, 28, 50, 72, 94, -1, -1}, // Version 21 + { 6, 26, 50, 74, 98, -1, -1}, // Version 22 + { 6, 30, 54, 78, 102, -1, -1}, // Version 23 + { 6, 28, 54, 80, 106, -1, -1}, // Version 24 + { 6, 32, 58, 84, 110, -1, -1}, // Version 25 + { 6, 30, 58, 86, 114, -1, -1}, // Version 26 + { 6, 34, 62, 90, 118, -1, -1}, // Version 27 + { 6, 26, 50, 74, 98, 122, -1}, // Version 28 + { 6, 30, 54, 78, 102, 126, -1}, // Version 29 + { 6, 26, 52, 78, 104, 130, -1}, // Version 30 + { 6, 30, 56, 82, 108, 134, -1}, // Version 31 + { 6, 34, 60, 86, 112, 138, -1}, // Version 32 + { 6, 30, 58, 86, 114, 142, -1}, // Version 33 + { 6, 34, 62, 90, 118, 146, -1}, // Version 34 + { 6, 30, 54, 78, 102, 126, 150}, // Version 35 + { 6, 24, 50, 76, 102, 128, 154}, // Version 36 + { 6, 28, 54, 80, 106, 132, 158}, // Version 37 + { 6, 32, 58, 84, 110, 136, 162}, // Version 38 + { 6, 26, 54, 82, 110, 138, 166}, // Version 39 + { 6, 30, 58, 86, 114, 142, 170}, // Version 40 + }; + + // Type info cells at the left top corner. + private static final int[][] TYPE_INFO_COORDINATES = { + {8, 0}, + {8, 1}, + {8, 2}, + {8, 3}, + {8, 4}, + {8, 5}, + {8, 7}, + {8, 8}, + {7, 8}, + {5, 8}, + {4, 8}, + {3, 8}, + {2, 8}, + {1, 8}, + {0, 8}, + }; + + // From Appendix D in JISX0510:2004 (p. 67) + private static final int VERSION_INFO_POLY = 0x1f25; // 1 1111 0010 0101 + + // From Appendix C in JISX0510:2004 (p.65). + private static final int TYPE_INFO_POLY = 0x537; + private static final int TYPE_INFO_MASK_PATTERN = 0x5412; + + // Set all cells to -1. -1 means that the cell is empty (not set yet). + // + // JAVAPORT: We shouldn't need to do this at all. The code should be rewritten to begin encoding + // with the ByteMatrix initialized all to zero. + public static void clearMatrix(ByteMatrix matrix) { + matrix.clear((byte) -1); + } + + // Build 2D matrix of QR Code from "dataBits" with "ecLevel", "version" and "getMaskPattern". On + // success, store the result in "matrix" and return true. + public static void buildMatrix(BitArray dataBits, ErrorCorrectionLevel ecLevel, int version, + int maskPattern, ByteMatrix matrix) throws WriterException { + clearMatrix(matrix); + embedBasicPatterns(version, matrix); + // Type information appear with any version. + embedTypeInfo(ecLevel, maskPattern, matrix); + // Version info appear if version >= 7. + maybeEmbedVersionInfo(version, matrix); + // Data should be embedded at end. + embedDataBits(dataBits, maskPattern, matrix); + } + + // Embed basic patterns. On success, modify the matrix and return true. + // The basic patterns are: + // - Position detection patterns + // - Timing patterns + // - Dark dot at the left bottom corner + // - Position adjustment patterns, if need be + public static void embedBasicPatterns(int version, ByteMatrix matrix) throws WriterException { + // Let's get started with embedding big squares at corners. + embedPositionDetectionPatternsAndSeparators(matrix); + // Then, embed the dark dot at the left bottom corner. + embedDarkDotAtLeftBottomCorner(matrix); + + // Position adjustment patterns appear if version >= 2. + maybeEmbedPositionAdjustmentPatterns(version, matrix); + // Timing patterns should be embedded after position adj. patterns. + embedTimingPatterns(matrix); + } + + // Embed type information. On success, modify the matrix. + public static void embedTypeInfo(ErrorCorrectionLevel ecLevel, int maskPattern, ByteMatrix matrix) + throws WriterException { + BitArray typeInfoBits = new BitArray(); + makeTypeInfoBits(ecLevel, maskPattern, typeInfoBits); + + for (int i = 0; i < typeInfoBits.getSize(); ++i) { + // Place bits in LSB to MSB order. LSB (least significant bit) is the last value in + // "typeInfoBits". + boolean bit = typeInfoBits.get(typeInfoBits.getSize() - 1 - i); + + // Type info bits at the left top corner. See 8.9 of JISX0510:2004 (p.46). + int x1 = TYPE_INFO_COORDINATES[i][0]; + int y1 = TYPE_INFO_COORDINATES[i][1]; + matrix.set(x1, y1, bit); + + if (i < 8) { + // Right top corner. + int x2 = matrix.getWidth() - i - 1; + int y2 = 8; + matrix.set(x2, y2, bit); + } else { + // Left bottom corner. + int x2 = 8; + int y2 = matrix.getHeight() - 7 + (i - 8); + matrix.set(x2, y2, bit); + } + } + } + + // Embed version information if need be. On success, modify the matrix and return true. + // See 8.10 of JISX0510:2004 (p.47) for how to embed version information. + public static void maybeEmbedVersionInfo(int version, ByteMatrix matrix) throws WriterException { + if (version < 7) { // Version info is necessary if version >= 7. + return; // Don't need version info. + } + BitArray versionInfoBits = new BitArray(); + makeVersionInfoBits(version, versionInfoBits); + + int bitIndex = 6 * 3 - 1; // It will decrease from 17 to 0. + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 3; ++j) { + // Place bits in LSB (least significant bit) to MSB order. + boolean bit = versionInfoBits.get(bitIndex); + bitIndex--; + // Left bottom corner. + matrix.set(i, matrix.getHeight() - 11 + j, bit); + // Right bottom corner. + matrix.set(matrix.getHeight() - 11 + j, i, bit); + } + } + } + + // Embed "dataBits" using "getMaskPattern". On success, modify the matrix and return true. + // For debugging purposes, it skips masking process if "getMaskPattern" is -1. + // See 8.7 of JISX0510:2004 (p.38) for how to embed data bits. + public static void embedDataBits(BitArray dataBits, int maskPattern, ByteMatrix matrix) + throws WriterException { + int bitIndex = 0; + int direction = -1; + // Start from the right bottom cell. + int x = matrix.getWidth() - 1; + int y = matrix.getHeight() - 1; + while (x > 0) { + // Skip the vertical timing pattern. + if (x == 6) { + x -= 1; + } + while (y >= 0 && y < matrix.getHeight()) { + for (int i = 0; i < 2; ++i) { + int xx = x - i; + // Skip the cell if it's not empty. + if (!isEmpty(matrix.get(xx, y))) { + continue; + } + boolean bit; + if (bitIndex < dataBits.getSize()) { + bit = dataBits.get(bitIndex); + ++bitIndex; + } else { + // Padding bit. If there is no bit left, we'll fill the left cells with 0, as described + // in 8.4.9 of JISX0510:2004 (p. 24). + bit = false; + } + + // Skip masking if mask_pattern is -1. + if (maskPattern != -1) { + if (MaskUtil.getDataMaskBit(maskPattern, xx, y)) { + bit = !bit; + } + } + matrix.set(xx, y, bit); + } + y += direction; + } + direction = -direction; // Reverse the direction. + y += direction; + x -= 2; // Move to the left. + } + // All bits should be consumed. + if (bitIndex != dataBits.getSize()) { + throw new WriterException("Not all bits consumed: " + bitIndex + '/' + dataBits.getSize()); + } + } + + // Return the position of the most significant bit set (to one) in the "value". The most + // significant bit is position 32. If there is no bit set, return 0. Examples: + // - findMSBSet(0) => 0 + // - findMSBSet(1) => 1 + // - findMSBSet(255) => 8 + public static int findMSBSet(int value) { + int numDigits = 0; + while (value != 0) { + value >>>= 1; + ++numDigits; + } + return numDigits; + } + + // Calculate BCH (Bose-Chaudhuri-Hocquenghem) code for "value" using polynomial "poly". The BCH + // code is used for encoding type information and version information. + // Example: Calculation of version information of 7. + // f(x) is created from 7. + // - 7 = 000111 in 6 bits + // - f(x) = x^2 + x^1 + x^0 + // g(x) is given by the standard (p. 67) + // - g(x) = x^12 + x^11 + x^10 + x^9 + x^8 + x^5 + x^2 + 1 + // Multiply f(x) by x^(18 - 6) + // - f'(x) = f(x) * x^(18 - 6) + // - f'(x) = x^14 + x^13 + x^12 + // Calculate the remainder of f'(x) / g(x) + // x^2 + // __________________________________________________ + // g(x) )x^14 + x^13 + x^12 + // x^14 + x^13 + x^12 + x^11 + x^10 + x^7 + x^4 + x^2 + // -------------------------------------------------- + // x^11 + x^10 + x^7 + x^4 + x^2 + // + // The remainder is x^11 + x^10 + x^7 + x^4 + x^2 + // Encode it in binary: 110010010100 + // The return value is 0xc94 (1100 1001 0100) + // + // Since all coefficients in the polynomials are 1 or 0, we can do the calculation by bit + // operations. We don't care if cofficients are positive or negative. + public static int calculateBCHCode(int value, int poly) { + // If poly is "1 1111 0010 0101" (version info poly), msbSetInPoly is 13. We'll subtract 1 + // from 13 to make it 12. + int msbSetInPoly = findMSBSet(poly); + value <<= msbSetInPoly - 1; + // Do the division business using exclusive-or operations. + while (findMSBSet(value) >= msbSetInPoly) { + value ^= poly << (findMSBSet(value) - msbSetInPoly); + } + // Now the "value" is the remainder (i.e. the BCH code) + return value; + } + + // Make bit vector of type information. On success, store the result in "bits" and return true. + // Encode error correction level and mask pattern. See 8.9 of + // JISX0510:2004 (p.45) for details. + public static void makeTypeInfoBits(ErrorCorrectionLevel ecLevel, int maskPattern, BitArray bits) + throws WriterException { + if (!QRCode.isValidMaskPattern(maskPattern)) { + throw new WriterException("Invalid mask pattern"); + } + int typeInfo = (ecLevel.getBits() << 3) | maskPattern; + bits.appendBits(typeInfo, 5); + + int bchCode = calculateBCHCode(typeInfo, TYPE_INFO_POLY); + bits.appendBits(bchCode, 10); + + BitArray maskBits = new BitArray(); + maskBits.appendBits(TYPE_INFO_MASK_PATTERN, 15); + bits.xor(maskBits); + + if (bits.getSize() != 15) { // Just in case. + throw new WriterException("should not happen but we got: " + bits.getSize()); + } + } + + // Make bit vector of version information. On success, store the result in "bits" and return true. + // See 8.10 of JISX0510:2004 (p.45) for details. + public static void makeVersionInfoBits(int version, BitArray bits) throws WriterException { + bits.appendBits(version, 6); + int bchCode = calculateBCHCode(version, VERSION_INFO_POLY); + bits.appendBits(bchCode, 12); + + if (bits.getSize() != 18) { // Just in case. + throw new WriterException("should not happen but we got: " + bits.getSize()); + } + } + + // Check if "value" is empty. + private static boolean isEmpty(int value) { + return value == -1; + } + + // Check if "value" is valid. + private static boolean isValidValue(int value) { + return value == -1 || // Empty. + value == 0 || // Light (white). + value == 1; // Dark (black). + } + + private static void embedTimingPatterns(ByteMatrix matrix) throws WriterException { + // -8 is for skipping position detection patterns (size 7), and two horizontal/vertical + // separation patterns (size 1). Thus, 8 = 7 + 1. + for (int i = 8; i < matrix.getWidth() - 8; ++i) { + int bit = (i + 1) % 2; + // Horizontal line. + if (!isValidValue(matrix.get(i, 6))) { + throw new WriterException(); + } + if (isEmpty(matrix.get(i, 6))) { + matrix.set(i, 6, bit); + } + // Vertical line. + if (!isValidValue(matrix.get(6, i))) { + throw new WriterException(); + } + if (isEmpty(matrix.get(6, i))) { + matrix.set(6, i, bit); + } + } + } + + // Embed the lonely dark dot at left bottom corner. JISX0510:2004 (p.46) + private static void embedDarkDotAtLeftBottomCorner(ByteMatrix matrix) throws WriterException { + if (matrix.get(8, matrix.getHeight() - 8) == 0) { + throw new WriterException(); + } + matrix.set(8, matrix.getHeight() - 8, 1); + } + + private static void embedHorizontalSeparationPattern(int xStart, int yStart, + ByteMatrix matrix) throws WriterException { + // We know the width and height. + if (HORIZONTAL_SEPARATION_PATTERN[0].length != 8 || HORIZONTAL_SEPARATION_PATTERN.length != 1) { + throw new WriterException("Bad horizontal separation pattern"); + } + for (int x = 0; x < 8; ++x) { + if (!isEmpty(matrix.get(xStart + x, yStart))) { + throw new WriterException(); + } + matrix.set(xStart + x, yStart, HORIZONTAL_SEPARATION_PATTERN[0][x]); + } + } + + private static void embedVerticalSeparationPattern(int xStart, int yStart, + ByteMatrix matrix) throws WriterException { + // We know the width and height. + if (VERTICAL_SEPARATION_PATTERN[0].length != 1 || VERTICAL_SEPARATION_PATTERN.length != 7) { + throw new WriterException("Bad vertical separation pattern"); + } + for (int y = 0; y < 7; ++y) { + if (!isEmpty(matrix.get(xStart, yStart + y))) { + throw new WriterException(); + } + matrix.set(xStart, yStart + y, VERTICAL_SEPARATION_PATTERN[y][0]); + } + } + + // Note that we cannot unify the function with embedPositionDetectionPattern() despite they are + // almost identical, since we cannot write a function that takes 2D arrays in different sizes in + // C/C++. We should live with the fact. + private static void embedPositionAdjustmentPattern(int xStart, int yStart, + ByteMatrix matrix) throws WriterException { + // We know the width and height. + if (POSITION_ADJUSTMENT_PATTERN[0].length != 5 || POSITION_ADJUSTMENT_PATTERN.length != 5) { + throw new WriterException("Bad position adjustment"); + } + for (int y = 0; y < 5; ++y) { + for (int x = 0; x < 5; ++x) { + if (!isEmpty(matrix.get(xStart + x, yStart + y))) { + throw new WriterException(); + } + matrix.set(xStart + x, yStart + y, POSITION_ADJUSTMENT_PATTERN[y][x]); + } + } + } + + private static void embedPositionDetectionPattern(int xStart, int yStart, + ByteMatrix matrix) throws WriterException { + // We know the width and height. + if (POSITION_DETECTION_PATTERN[0].length != 7 || POSITION_DETECTION_PATTERN.length != 7) { + throw new WriterException("Bad position detection pattern"); + } + for (int y = 0; y < 7; ++y) { + for (int x = 0; x < 7; ++x) { + if (!isEmpty(matrix.get(xStart + x, yStart + y))) { + throw new WriterException(); + } + matrix.set(xStart + x, yStart + y, POSITION_DETECTION_PATTERN[y][x]); + } + } + } + + // Embed position detection patterns and surrounding vertical/horizontal separators. + private static void embedPositionDetectionPatternsAndSeparators(ByteMatrix matrix) throws WriterException { + // Embed three big squares at corners. + int pdpWidth = POSITION_DETECTION_PATTERN[0].length; + // Left top corner. + embedPositionDetectionPattern(0, 0, matrix); + // Right top corner. + embedPositionDetectionPattern(matrix.getWidth() - pdpWidth, 0, matrix); + // Left bottom corner. + embedPositionDetectionPattern(0, matrix.getWidth() - pdpWidth, matrix); + + // Embed horizontal separation patterns around the squares. + int hspWidth = HORIZONTAL_SEPARATION_PATTERN[0].length; + // Left top corner. + embedHorizontalSeparationPattern(0, hspWidth - 1, matrix); + // Right top corner. + embedHorizontalSeparationPattern(matrix.getWidth() - hspWidth, + hspWidth - 1, matrix); + // Left bottom corner. + embedHorizontalSeparationPattern(0, matrix.getWidth() - hspWidth, matrix); + + // Embed vertical separation patterns around the squares. + int vspSize = VERTICAL_SEPARATION_PATTERN.length; + // Left top corner. + embedVerticalSeparationPattern(vspSize, 0, matrix); + // Right top corner. + embedVerticalSeparationPattern(matrix.getHeight() - vspSize - 1, 0, matrix); + // Left bottom corner. + embedVerticalSeparationPattern(vspSize, matrix.getHeight() - vspSize, + matrix); + } + + // Embed position adjustment patterns if need be. + private static void maybeEmbedPositionAdjustmentPatterns(int version, ByteMatrix matrix) + throws WriterException { + if (version < 2) { // The patterns appear if version >= 2 + return; + } + int index = version - 1; + int[] coordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index]; + int numCoordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index].length; + for (int i = 0; i < numCoordinates; ++i) { + for (int j = 0; j < numCoordinates; ++j) { + int y = coordinates[i]; + int x = coordinates[j]; + if (x == -1 || y == -1) { + continue; + } + // If the cell is unset, we embed the position adjustment pattern here. + if (isEmpty(matrix.get(x, y))) { + // -2 is necessary since the x/y coordinates point to the center of the pattern, not the + // left top corner. + embedPositionAdjustmentPattern(x - 2, y - 2, matrix); + } + } + } + } + +} diff --git a/libraries/zxing/src/com/google/zxing/qrcode/encoder/QRCode.java b/libraries/zxing/src/com/google/zxing/qrcode/encoder/QRCode.java new file mode 100644 index 000000000..05c818513 --- /dev/null +++ b/libraries/zxing/src/com/google/zxing/qrcode/encoder/QRCode.java @@ -0,0 +1,239 @@ +/* + * 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.qrcode.encoder; + +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.google.zxing.qrcode.decoder.Mode; + +/** + * @author satorux@google.com (Satoru Takabayashi) - creator + * @author dswitkin@google.com (Daniel Switkin) - ported from C++ + */ +public final class QRCode { + + public static final int NUM_MASK_PATTERNS = 8; + + private Mode mode; + private ErrorCorrectionLevel ecLevel; + private int version; + private int matrixWidth; + private int maskPattern; + private int numTotalBytes; + private int numDataBytes; + private int numECBytes; + private int numRSBlocks; + private ByteMatrix matrix; + + public QRCode() { + mode = null; + ecLevel = null; + version = -1; + matrixWidth = -1; + maskPattern = -1; + numTotalBytes = -1; + numDataBytes = -1; + numECBytes = -1; + numRSBlocks = -1; + matrix = null; + } + + // Mode of the QR Code. + public Mode getMode() { + return mode; + } + + // Error correction level of the QR Code. + public ErrorCorrectionLevel getECLevel() { + return ecLevel; + } + + // Version of the QR Code. The bigger size, the bigger version. + public int getVersion() { + return version; + } + + // ByteMatrix width of the QR Code. + public int getMatrixWidth() { + return matrixWidth; + } + + // Mask pattern of the QR Code. + public int getMaskPattern() { + return maskPattern; + } + + // Number of total bytes in the QR Code. + public int getNumTotalBytes() { + return numTotalBytes; + } + + // Number of data bytes in the QR Code. + public int getNumDataBytes() { + return numDataBytes; + } + + // Number of error correction bytes in the QR Code. + public int getNumECBytes() { + return numECBytes; + } + + // Number of Reedsolomon blocks in the QR Code. + public int getNumRSBlocks() { + return numRSBlocks; + } + + // ByteMatrix data of the QR Code. + public ByteMatrix getMatrix() { + return matrix; + } + + + // Return the value of the module (cell) pointed by "x" and "y" in the matrix of the QR Code. They + // call cells in the matrix "modules". 1 represents a black cell, and 0 represents a white cell. + public int at(int x, int y) { + // The value must be zero or one. + int value = matrix.get(x, y); + if (!(value == 0 || value == 1)) { + // this is really like an assert... not sure what better exception to use? + throw new RuntimeException("Bad value"); + } + return value; + } + + // Checks all the member variables are set properly. Returns true on success. Otherwise, returns + // false. + public boolean isValid() { + return + // First check if all version are not uninitialized. + mode != null && + ecLevel != null && + version != -1 && + matrixWidth != -1 && + maskPattern != -1 && + numTotalBytes != -1 && + numDataBytes != -1 && + numECBytes != -1 && + numRSBlocks != -1 && + // Then check them in other ways.. + isValidMaskPattern(maskPattern) && + numTotalBytes == numDataBytes + numECBytes && + // ByteMatrix stuff. + matrix != null && + matrixWidth == matrix.getWidth() && + // See 7.3.1 of JISX0510:2004 (p.5). + matrix.getWidth() == matrix.getHeight(); // Must be square. + } + + // Return debug String. + public String toString() { + StringBuffer result = new StringBuffer(200); + result.append("<<\n"); + result.append(" mode: "); + result.append(mode); + result.append("\n ecLevel: "); + result.append(ecLevel); + result.append("\n version: "); + result.append(version); + result.append("\n matrixWidth: "); + result.append(matrixWidth); + result.append("\n maskPattern: "); + result.append(maskPattern); + result.append("\n numTotalBytes: "); + result.append(numTotalBytes); + result.append("\n numDataBytes: "); + result.append(numDataBytes); + result.append("\n numECBytes: "); + result.append(numECBytes); + result.append("\n numRSBlocks: "); + result.append(numRSBlocks); + if (matrix == null) { + result.append("\n matrix: null\n"); + } else { + result.append("\n matrix:\n"); + result.append(matrix.toString()); + } + result.append(">>\n"); + return result.toString(); + } + + public void setMode(Mode value) { + mode = value; + } + + public void setECLevel(ErrorCorrectionLevel value) { + ecLevel = value; + } + + public void setVersion(int value) { + version = value; + } + + public void setMatrixWidth(int value) { + matrixWidth = value; + } + + public void setMaskPattern(int value) { + maskPattern = value; + } + + public void setNumTotalBytes(int value) { + numTotalBytes = value; + } + + public void setNumDataBytes(int value) { + numDataBytes = value; + } + + public void setNumECBytes(int value) { + numECBytes = value; + } + + public void setNumRSBlocks(int value) { + numRSBlocks = value; + } + + // This takes ownership of the 2D array. + public void setMatrix(ByteMatrix value) { + matrix = value; + } + + // Check if "mask_pattern" is valid. + public static boolean isValidMaskPattern(int maskPattern) { + return maskPattern >= 0 && maskPattern < NUM_MASK_PATTERNS; + } + + // Return true if the all values in the matrix are binary numbers. + // + // JAVAPORT: This is going to be super expensive and unnecessary, we should not call this in + // production. I'm leaving it because it may be useful for testing. It should be removed entirely + // if ByteMatrix is changed never to contain a -1. + /* + private static boolean EverythingIsBinary(final ByteMatrix matrix) { + for (int y = 0; y < matrix.height(); ++y) { + for (int x = 0; x < matrix.width(); ++x) { + int value = matrix.get(y, x); + if (!(value == 0 || value == 1)) { + // Found non zero/one value. + return false; + } + } + } + return true; + } + */ + +} |