Add parts of zxing library to generate qr codes

This commit is contained in:
Dominik Schürmann 2013-10-05 20:43:42 +02:00
parent bef6977aad
commit 05cc2023da
67 changed files with 10307 additions and 1 deletions

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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() {
}
}

View File

@ -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() {
}
}

View File

@ -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;
}
}

View File

@ -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.");
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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];
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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)))
};
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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;
}
*/
}

View File

@ -25,6 +25,7 @@ import org.sufficientlysecure.keychain.R;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentIntegratorSupportV4;
import android.content.Intent;
import android.os.Bundle;
@ -70,7 +71,8 @@ public class ShareActivity extends SherlockFragmentActivity {
getResources().getText(R.string.shareKeyringWith)));
} else if (ACTION_SHARE_KEYRING_WITH_QR_CODE.equals(action)) {
// use barcode scanner integration library
new IntentIntegrator(this).shareText(keyringArmored.get(0));
// new IntentIntegrator(this).shareText(keyringArmored.get(0));
// new IntentIntegratorSupportV4(this).shareText(activity, text);
}
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2011 Andreas Schildbach
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.util;
import java.util.Hashtable;
import org.sufficientlysecure.keychain.Constants;
import android.graphics.Bitmap;
import android.graphics.Color;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
public class QrCodeUtils {
public final static QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
/**
* Generate Bitmap with QR Code based on input.
*
* @param input
* @param size
* @return QR Code as Bitmap
*/
public static Bitmap getQRCodeBitmap(final String input, final int size) {
try {
final Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size,
size, hints);
final int width = result.getWidth();
final int height = result.getHeight();
final int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
final int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
}
}
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
} catch (final WriterException e) {
Log.e(Constants.TAG, "QrCodeUtils", e);
return null;
}
}
/**
* Displays QrCode in Dialog
*/
// public static void showQrCode(Activity activity, Bitmap qrCodeBitmap) {
// final Dialog dialog = new Dialog(activity);
// dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
// dialog.setContentView(R.layout.qr_code_dialog);
// final ImageView imageView = (ImageView) dialog.findViewById(R.id.qr_dialog_view);
// imageView.setImageBitmap(qrCodeBitmap);
// dialog.setCanceledOnTouchOutside(true);
// dialog.show();
// imageView.setOnClickListener(new OnClickListener() {
// public void onClick(final View v) {
// dialog.dismiss();
// }
// });
// }
/**
* Starts Scanning of Barcode with Barcode Scanner App, if Barcode Scanner is not installed
* requests install of it, done by using IntentIntegrator from Barcode Scanner
*
* @param activity
*/
// public static void scanQrCode(Activity activity) {
// IntentIntegrator.initiateScan(activity, R.string.no_barcode_scanner_title,
// R.string.no_barcode_scanner, R.string.button_yes, R.string.button_no);
// }
//
// /**
// * Return scanned QR Code as String, must be used in Activities onActivityResult(), done by
// * using IntentIntegrator from Barcode Scanner
// *
// * @param requestCode
// * @param resultCode
// * @param intent
// * @return QR Code content as String
// */
// public static String returnQrCodeOnActivityResult(int requestCode, int resultCode, Intent
// intent) {
// IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode,
// intent);
//
// if (scanResult != null) {
// return scanResult.getContents();
// } else {
// return null;
// }
// }
}