mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-23 17:22:16 -05:00
Add parts of zxing library to generate qr codes
This commit is contained in:
parent
bef6977aad
commit
05cc2023da
106
OpenPGP-Keychain/src/com/google/zxing/BarcodeFormat.java
Normal file
106
OpenPGP-Keychain/src/com/google/zxing/BarcodeFormat.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
80
OpenPGP-Keychain/src/com/google/zxing/Binarizer.java
Normal file
80
OpenPGP-Keychain/src/com/google/zxing/Binarizer.java
Normal 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);
|
||||
|
||||
}
|
128
OpenPGP-Keychain/src/com/google/zxing/BinaryBitmap.java
Normal file
128
OpenPGP-Keychain/src/com/google/zxing/BinaryBitmap.java
Normal 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));
|
||||
}
|
||||
|
||||
}
|
37
OpenPGP-Keychain/src/com/google/zxing/ChecksumException.java
Normal file
37
OpenPGP-Keychain/src/com/google/zxing/ChecksumException.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
79
OpenPGP-Keychain/src/com/google/zxing/DecodeHintType.java
Normal file
79
OpenPGP-Keychain/src/com/google/zxing/DecodeHintType.java
Normal 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() {
|
||||
}
|
||||
|
||||
}
|
39
OpenPGP-Keychain/src/com/google/zxing/EncodeHintType.java
Normal file
39
OpenPGP-Keychain/src/com/google/zxing/EncodeHintType.java
Normal 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() {
|
||||
}
|
||||
|
||||
}
|
38
OpenPGP-Keychain/src/com/google/zxing/FormatException.java
Normal file
38
OpenPGP-Keychain/src/com/google/zxing/FormatException.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
113
OpenPGP-Keychain/src/com/google/zxing/LuminanceSource.java
Normal file
113
OpenPGP-Keychain/src/com/google/zxing/LuminanceSource.java
Normal 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.");
|
||||
}
|
||||
|
||||
}
|
37
OpenPGP-Keychain/src/com/google/zxing/NotFoundException.java
Normal file
37
OpenPGP-Keychain/src/com/google/zxing/NotFoundException.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
64
OpenPGP-Keychain/src/com/google/zxing/Reader.java
Normal file
64
OpenPGP-Keychain/src/com/google/zxing/Reader.java
Normal 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();
|
||||
|
||||
}
|
98
OpenPGP-Keychain/src/com/google/zxing/ReaderException.java
Normal file
98
OpenPGP-Keychain/src/com/google/zxing/ReaderException.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
143
OpenPGP-Keychain/src/com/google/zxing/Result.java
Normal file
143
OpenPGP-Keychain/src/com/google/zxing/Result.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
109
OpenPGP-Keychain/src/com/google/zxing/ResultMetadataType.java
Normal file
109
OpenPGP-Keychain/src/com/google/zxing/ResultMetadataType.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
129
OpenPGP-Keychain/src/com/google/zxing/ResultPoint.java
Normal file
129
OpenPGP-Keychain/src/com/google/zxing/ResultPoint.java
Normal 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));
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
54
OpenPGP-Keychain/src/com/google/zxing/Writer.java
Normal file
54
OpenPGP-Keychain/src/com/google/zxing/Writer.java
Normal 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;
|
||||
|
||||
}
|
35
OpenPGP-Keychain/src/com/google/zxing/WriterException.java
Normal file
35
OpenPGP-Keychain/src/com/google/zxing/WriterException.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
246
OpenPGP-Keychain/src/com/google/zxing/common/BitArray.java
Normal file
246
OpenPGP-Keychain/src/com/google/zxing/common/BitArray.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
247
OpenPGP-Keychain/src/com/google/zxing/common/BitMatrix.java
Normal file
247
OpenPGP-Keychain/src/com/google/zxing/common/BitMatrix.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
97
OpenPGP-Keychain/src/com/google/zxing/common/BitSource.java
Normal file
97
OpenPGP-Keychain/src/com/google/zxing/common/BitSource.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
27
OpenPGP-Keychain/src/com/google/zxing/common/Comparator.java
Normal file
27
OpenPGP-Keychain/src/com/google/zxing/common/Comparator.java
Normal 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);
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
52
OpenPGP-Keychain/src/com/google/zxing/common/ECI.java
Normal file
52
OpenPGP-Keychain/src/com/google/zxing/common/ECI.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
156
OpenPGP-Keychain/src/com/google/zxing/common/GridSampler.java
Normal file
156
OpenPGP-Keychain/src/com/google/zxing/common/GridSampler.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
192
OpenPGP-Keychain/src/com/google/zxing/common/StringUtils.java
Normal file
192
OpenPGP-Keychain/src/com/google/zxing/common/StringUtils.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
108
OpenPGP-Keychain/src/com/google/zxing/qrcode/QRCodeWriter.java
Normal file
108
OpenPGP-Keychain/src/com/google/zxing/qrcode/QRCodeWriter.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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];
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
117
OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/Mode.java
Normal file
117
OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/Mode.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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)))
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
239
OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/QRCode.java
Normal file
239
OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/QRCode.java
Normal 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;
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
// }
|
||||
// }
|
||||
}
|
Loading…
Reference in New Issue
Block a user