mirror of
https://github.com/zxing/zxing.git
synced 2024-11-09 20:44:03 -08:00
git-svn-id: https://zxing.googlecode.com/svn/trunk@2 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
parent
3df68d7105
commit
6eee886034
30
build.xml
Normal file
30
build.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="core" default="build">
|
||||
|
||||
<property name="WTK-home" value="/usr/local/WTK2.5.2"/>
|
||||
<property name="JDK1.4-home" value="/usr/lib/jvm/j2sdk1.4.2_16"/>
|
||||
|
||||
<target name="init">
|
||||
<tstamp/>
|
||||
</target>
|
||||
|
||||
<target name="build" depends="init">
|
||||
<mkdir dir="build"/>
|
||||
<javac srcdir="src"
|
||||
destdir="build"
|
||||
source="1.4"
|
||||
target="1.4"
|
||||
bootclasspath="${JDK1.4-home}/jre/lib/rt.jar"
|
||||
optimize="true"
|
||||
debug="true"
|
||||
deprecation="true"
|
||||
fork="true"/>
|
||||
<jar jarfile="core.jar" basedir="build"/>
|
||||
</target>
|
||||
|
||||
<target name="clean">
|
||||
<delete dir="build"/>
|
||||
<delete file="core.jar"/>
|
||||
</target>
|
||||
|
||||
</project>
|
32
src/com/google/zxing/BarcodeFormat.java
Normal file
32
src/com/google/zxing/BarcodeFormat.java
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing;
|
||||
|
||||
/**
|
||||
* Enumerates barcode formats known to this package.
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class BarcodeFormat {
|
||||
|
||||
// No, we can't use an enum here. J2ME doesn't support it.
|
||||
|
||||
public static final BarcodeFormat UPC = new BarcodeFormat();
|
||||
public static final BarcodeFormat QR_CODE = new BarcodeFormat();
|
||||
public static final BarcodeFormat DATAMATRIX = new BarcodeFormat();
|
||||
|
||||
}
|
43
src/com/google/zxing/DecodeHintType.java
Normal file
43
src/com/google/zxing/DecodeHintType.java
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing;
|
||||
|
||||
/**
|
||||
* 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 srowen@google.com (Sean Owen), dswitkin@google.com (Daniel Switkin)
|
||||
* @see Reader#decode(MonochromeBitmapSource, java.util.Map)
|
||||
*/
|
||||
public final class DecodeHintType {
|
||||
|
||||
// No, we can't use an enum here. J2ME doesn't support it.
|
||||
|
||||
/** Unspecified, application-specific hint. */
|
||||
public static final DecodeHintType OTHER = new DecodeHintType();
|
||||
/** Image is a pure monochrome image of a barcode. */
|
||||
public static final DecodeHintType PURE_BARCODE = new DecodeHintType();
|
||||
/**
|
||||
* Image is known to be of one of a few possible formats.
|
||||
* Maps to {@link java.util.Collection} of {@link BarcodeFormat}s.
|
||||
*/
|
||||
public static final DecodeHintType POSSIBLE_FORMATS = new DecodeHintType();
|
||||
|
||||
private DecodeHintType() {}
|
||||
|
||||
}
|
61
src/com/google/zxing/MonochromeBitmapSource.java
Normal file
61
src/com/google/zxing/MonochromeBitmapSource.java
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing;
|
||||
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* Encapsulates a generic black-and-white bitmap -- a collection of pixels in two dimensions.
|
||||
* This unifies many possible representations, like AWT's <code>BufferedImage</code>.
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public interface MonochromeBitmapSource {
|
||||
|
||||
/**
|
||||
* @param x horizontal offset, from left, of the pixel
|
||||
* @param y vertical offset, from top, of the pixel
|
||||
* @return true iff the pixel at (x,y) is black
|
||||
*/
|
||||
boolean isBlack(int x, int y);
|
||||
|
||||
/**
|
||||
* Returns an entire row of black/white pixels as an array of bits, where "true" means "black".
|
||||
* This is a sort of "bulk get" operation intended to enable efficient access in
|
||||
* certain situations.
|
||||
*
|
||||
* @param y vertical offset, from top, of the row of pixels
|
||||
* @param row if not null, {@link BitArray} to write pixels into. If null, a new {@link BitArray}
|
||||
* is allocated and returned.
|
||||
* @param startX horizontal offset, from left, from which to start getting pixels
|
||||
* @param getWidth number of pixels to get from the row
|
||||
* @return {@link BitArray} representing the (subset of the) row of pixels. If row parameter
|
||||
* was not null, it is returned.
|
||||
*/
|
||||
BitArray getBlackRow(int y, BitArray row, int startX, int getWidth);
|
||||
|
||||
/**
|
||||
* @return height of underlying image
|
||||
*/
|
||||
int getHeight();
|
||||
|
||||
/**
|
||||
* @return width of underlying image
|
||||
*/
|
||||
int getWidth();
|
||||
|
||||
}
|
45
src/com/google/zxing/MultiFormatReader.java
Normal file
45
src/com/google/zxing/MultiFormatReader.java
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing;
|
||||
|
||||
import com.google.zxing.qrcode.QRCodeReader;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* For now, only delegates to {@link QRCodeReader}.
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen), dswitkin@google.com (Daniel Switkin)
|
||||
*/
|
||||
public final class MultiFormatReader implements Reader {
|
||||
|
||||
public Result decode(MonochromeBitmapSource image) throws ReaderException {
|
||||
return decode(image, null);
|
||||
}
|
||||
|
||||
public Result decode(MonochromeBitmapSource image, Hashtable hints)
|
||||
throws ReaderException {
|
||||
Hashtable possibleFormats =
|
||||
hints == null ? null : (Hashtable) hints.get(DecodeHintType.POSSIBLE_FORMATS);
|
||||
if (possibleFormats == null || possibleFormats.contains(BarcodeFormat.QR_CODE)) {
|
||||
return new QRCodeReader().decode(image, hints);
|
||||
} else {
|
||||
throw new ReaderException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
56
src/com/google/zxing/Reader.java
Normal file
56
src/com/google/zxing/Reader.java
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing;
|
||||
|
||||
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 srowen@google.com (Sean Owen), 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 ReaderException if the barcode cannot be located or decoded for any reason
|
||||
*/
|
||||
Result decode(MonochromeBitmapSource image) throws ReaderException;
|
||||
|
||||
/**
|
||||
* 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 Hashtable} from {@link DecodeHintType} to aribtrary 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 ReaderException if the barcode cannot be located or decoded for any reason
|
||||
*/
|
||||
Result decode(MonochromeBitmapSource image, Hashtable hints) throws ReaderException;
|
||||
|
||||
}
|
35
src/com/google/zxing/ReaderException.java
Normal file
35
src/com/google/zxing/ReaderException.java
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing;
|
||||
|
||||
/**
|
||||
* 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 srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class ReaderException extends Exception {
|
||||
|
||||
public ReaderException() {
|
||||
}
|
||||
|
||||
public ReaderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
50
src/com/google/zxing/Result.java
Normal file
50
src/com/google/zxing/Result.java
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing;
|
||||
|
||||
/**
|
||||
* <p>Encapsulates the result of decoding a barcode within an image.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class Result {
|
||||
|
||||
private final String text;
|
||||
private final ResultPoint[] resultPoints;
|
||||
|
||||
public Result(String text, ResultPoint[] resultPoints) {
|
||||
this.text = text;
|
||||
this.resultPoints = resultPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return raw text encoded by the barcode, if any
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
30
src/com/google/zxing/ResultPoint.java
Normal file
30
src/com/google/zxing/ResultPoint.java
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing;
|
||||
|
||||
/**
|
||||
* <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 srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public interface ResultPoint {
|
||||
|
||||
float getX();
|
||||
float getY();
|
||||
|
||||
}
|
72
src/com/google/zxing/common/BitArray.java
Normal file
72
src/com/google/zxing/common/BitArray.java
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.common;
|
||||
|
||||
/**
|
||||
* <p>A simple, fast array of bits, represented compactly by an array of ints internally.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class BitArray {
|
||||
|
||||
private final int[] bits;
|
||||
|
||||
public BitArray(int size) {
|
||||
int arraySize = size >> 5;
|
||||
if ((size & 0x1F) != 0) {
|
||||
arraySize++;
|
||||
}
|
||||
bits = new int[arraySize];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true iff bit i is set
|
||||
*/
|
||||
public boolean get(int i) {
|
||||
return (bits[i >> 5] & (1 << (i & 0x1F))) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bit i.
|
||||
*/
|
||||
public void set(int i) {
|
||||
bits[i >> 5] |= 1 << (i & 0x1F);
|
||||
}
|
||||
|
||||
public void setBulk(int i, int newBits) {
|
||||
bits[i >> 5] = newBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all bits.
|
||||
*/
|
||||
public void clear() {
|
||||
int max = bits.length;
|
||||
for (int i = 0; i < max; i++) {
|
||||
bits[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
106
src/com/google/zxing/common/BitMatrix.java
Executable file
106
src/com/google/zxing/common/BitMatrix.java
Executable file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.common;
|
||||
|
||||
/**
|
||||
* <p>Represnts a square matrix of bits. In function arguments below, i is the row position
|
||||
* and j the column position of a bit. The top left bit corresponds to i = 0 and j = 0.</p>
|
||||
*
|
||||
* <p>Internally the bits are represented in a compact 1-D array of 32-bit ints. The
|
||||
* ordering of bits is column-major; that is the bits in this array correspond to
|
||||
* j=0 and i=0..dimension-1 first, then j=1 and i=0..dimension-1, etc.</p>
|
||||
*
|
||||
* <p>Within each int, less-signficant bits correspond to lower values of i and higher rows.
|
||||
* That is, the top-left bit is the least significant bit of the first int.</p>
|
||||
*
|
||||
* <p>This class is a convenient wrapper around this representation, but also exposes the internal
|
||||
* array for efficient access and manipulation.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class BitMatrix {
|
||||
|
||||
private final int dimension;
|
||||
private final int[] bits;
|
||||
|
||||
public BitMatrix(int dimension) {
|
||||
if (dimension < 1) {
|
||||
throw new IllegalArgumentException("dimension must be at least 1");
|
||||
}
|
||||
this.dimension = dimension;
|
||||
int numBits = dimension * dimension;
|
||||
int arraySize = numBits >> 5; // one int per 32 bits
|
||||
if ((numBits & 0x1F) != 0) { // plus one more if there are leftovers
|
||||
arraySize++;
|
||||
}
|
||||
bits = new int[arraySize];
|
||||
}
|
||||
|
||||
public boolean get(int i, int j) {
|
||||
int offset = i + dimension * j;
|
||||
return ((bits[offset >> 5] >>> (offset & 0x1F)) & 0x01) != 0;
|
||||
}
|
||||
|
||||
public void set(int i, int j) {
|
||||
int offset = i + dimension * j;
|
||||
bits[offset >> 5] |= 1 << (offset & 0x1F);
|
||||
}
|
||||
|
||||
public void setRegion(int topI, int leftJ, int height, int width) {
|
||||
if (topI < 0 || leftJ < 0) {
|
||||
throw new IllegalArgumentException("topI and leftJ must be nonnegative");
|
||||
}
|
||||
if (height < 1 || width < 1) {
|
||||
throw new IllegalArgumentException("height and width must be at least 1");
|
||||
}
|
||||
int maxJ = leftJ + width;
|
||||
int maxI = topI + height;
|
||||
if (maxI > dimension || maxJ > dimension) {
|
||||
throw new IllegalArgumentException(
|
||||
"topI + height and leftJ + width must be <= matrix dimension");
|
||||
}
|
||||
for (int j = leftJ; j < maxJ; j++) {
|
||||
int jOffset = dimension * j;
|
||||
for (int i = topI; i < maxI; i++) {
|
||||
int offset = i + jOffset;
|
||||
bits[offset >> 5] |= 1 << (offset & 0x1F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getDimension() {
|
||||
return dimension;
|
||||
}
|
||||
|
||||
public int[] getBits() {
|
||||
return bits;
|
||||
}
|
||||
|
||||
/*
|
||||
public BufferedImage toBufferedImage() {
|
||||
BufferedImage image =
|
||||
new BufferedImage(dimension, dimension, BufferedImage.TYPE_BYTE_BINARY);
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
image.setRGB(j, i, get(i, j) ? 0x00000000 : 0x00FFFFFF);
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
93
src/com/google/zxing/common/BlackPointEstimator.java
Normal file
93
src/com/google/zxing/common/BlackPointEstimator.java
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.common;
|
||||
|
||||
/**
|
||||
* <p>Encapsulates logic that estimates the optimal "black point", the luminance value
|
||||
* which is the best line between "white" and "black" in a grayscale image.</p>
|
||||
*
|
||||
* <p>TODO: Include reference to paper with similar ideas</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class BlackPointEstimator {
|
||||
|
||||
private BlackPointEstimator() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Given an array of <em>counts</em> of luminance values (i.e. a histogram), this method
|
||||
* decides which bucket of values corresponds to the black point -- which bucket contains the
|
||||
* count of the brightest luminance values that should be considered "black".</p>
|
||||
*
|
||||
* @param luminanceBuckets an array of <em>counts</em> of luminance values
|
||||
* @return index within argument of bucket corresponding to brightest values which should be
|
||||
* considered "black"
|
||||
*/
|
||||
public static int estimate(int[] luminanceBuckets) {
|
||||
|
||||
int numBuckets = luminanceBuckets.length;
|
||||
|
||||
// Find tallest peak in histogram
|
||||
int firstPeak = 0;
|
||||
int firstPeakSize = 0;
|
||||
for (int i = 0; i < numBuckets; i++) {
|
||||
if (luminanceBuckets[i] > firstPeakSize) {
|
||||
firstPeak = i;
|
||||
firstPeakSize = luminanceBuckets[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Find second-tallest peak -- well, another peak that is tall and not
|
||||
// so close to the first one
|
||||
int secondPeak = 0;
|
||||
int secondPeakScore = 0;
|
||||
for (int i = 0; i < numBuckets; i++) {
|
||||
int distanceToBiggest = i - firstPeak;
|
||||
// Encourage more distant second peaks by multiplying by square
|
||||
// of distance
|
||||
int score = luminanceBuckets[i] * distanceToBiggest * distanceToBiggest;
|
||||
if (score > secondPeakScore) {
|
||||
secondPeak = i;
|
||||
secondPeakScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
// Put firstPeak first
|
||||
if (firstPeak > secondPeak) {
|
||||
int temp = firstPeak;
|
||||
firstPeak = secondPeak;
|
||||
secondPeak = temp;
|
||||
}
|
||||
|
||||
// Find a valley between them that is low and close to the midpoint of the two peaks
|
||||
int bestValley = firstPeak;
|
||||
int bestValleyScore = 0;
|
||||
for (int i = firstPeak + 1; i < secondPeak; i++) {
|
||||
// Encourage low valleys near the mid point between peaks
|
||||
int score = (firstPeakSize - luminanceBuckets[i]) *
|
||||
(i - firstPeak) * (secondPeak - i);
|
||||
if (score > bestValleyScore) {
|
||||
bestValley = i;
|
||||
bestValleyScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
return bestValley;
|
||||
}
|
||||
|
||||
}
|
50
src/com/google/zxing/common/Collections.java
Normal file
50
src/com/google/zxing/common/Collections.java
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.common;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* This is basically a substitute for <code>java.util.Collections</code>.
|
||||
*/
|
||||
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
|
||||
* @param comparator
|
||||
*/
|
||||
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
src/com/google/zxing/common/Comparator.java
Normal file
27
src/com/google/zxing/common/Comparator.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.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);
|
||||
|
||||
}
|
94
src/com/google/zxing/common/reedsolomon/GF256.java
Normal file
94
src/com/google/zxing/common/reedsolomon/GF256.java
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.common.reedsolomon;
|
||||
|
||||
/**
|
||||
* <p>This class contains utility methods for performing mathematical operations over
|
||||
* the Galois Field GF(256). Operations use the primitive polynomial
|
||||
* x^8 + x^4 + x^3 + x^2 + 1 in calculations.</p>
|
||||
*
|
||||
* <p>Throughout this package, elements of GF(256) are represented as an <code>int</code>
|
||||
* for convenience and speed (but at the cost of memory).
|
||||
* Only the bottom 8 bits are really used.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
final class GF256 {
|
||||
|
||||
private static final int PRIMITIVE = 0x011D;
|
||||
private static final int[] exp = new int[256];
|
||||
private static final int[] log = new int[256];
|
||||
static {
|
||||
int x = 1;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
exp[i] = x;
|
||||
x <<= 1; // x = x * 2; we're assuming the generator alpha is 2
|
||||
if (x >= 0x100) {
|
||||
x ^= PRIMITIVE;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 255; i++) {
|
||||
log[exp[i]] = i;
|
||||
}
|
||||
// log[0] == 0 but this should never be used
|
||||
}
|
||||
|
||||
private GF256() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Addition and subtraction are the same in GF(256).
|
||||
*/
|
||||
static int addOrSubtract(int a, int b) {
|
||||
return a ^ b;
|
||||
}
|
||||
|
||||
static int exp(int a) {
|
||||
return exp[a];
|
||||
}
|
||||
|
||||
static int log(int a) {
|
||||
if (a == 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return log[a];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return multiplicative inverse of a
|
||||
*/
|
||||
static int inverse(int a) {
|
||||
if (a == 0) {
|
||||
throw new ArithmeticException();
|
||||
}
|
||||
return exp[255 - log[a]];
|
||||
}
|
||||
|
||||
static int multiply(int a, int b) {
|
||||
if (a == 0 || b == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (a == 1) {
|
||||
return b;
|
||||
}
|
||||
if (b == 1) {
|
||||
return a;
|
||||
}
|
||||
return exp[(log[a] + log[b]) % 255];
|
||||
}
|
||||
|
||||
}
|
224
src/com/google/zxing/common/reedsolomon/GF256Poly.java
Normal file
224
src/com/google/zxing/common/reedsolomon/GF256Poly.java
Normal file
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.common.reedsolomon;
|
||||
|
||||
/**
|
||||
* <p>Represents a polynomial whose coefficients are elements of GF(256).
|
||||
* 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 srowen@google.com (Sean Owen)
|
||||
*/
|
||||
final class GF256Poly {
|
||||
|
||||
/** Polynimal representing the monomial 0. */
|
||||
static final GF256Poly ZERO = new GF256Poly(new int[] { 0 });
|
||||
/** Polynimal representing the monomial 1. */
|
||||
static final GF256Poly ONE = new GF256Poly(new int[] { 1 });
|
||||
|
||||
private final int[] coefficients;
|
||||
|
||||
/**
|
||||
* @param coefficients coefficients as ints representing elements of GF(256), 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")
|
||||
*/
|
||||
GF256Poly(int[] coefficients) {
|
||||
if (coefficients == null || coefficients.length == 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (coefficients.length > 1 && coefficients[0] == 0) {
|
||||
// Leading term must be non-zero for anything except the constant polynomial "0"
|
||||
int firstNonZero = 1;
|
||||
while (firstNonZero < coefficients.length && coefficients[firstNonZero] == 0) {
|
||||
firstNonZero++;
|
||||
}
|
||||
if (firstNonZero == coefficients.length) {
|
||||
this.coefficients = ZERO.coefficients;
|
||||
} else {
|
||||
this.coefficients = new int[coefficients.length - firstNonZero];
|
||||
System.arraycopy(coefficients,
|
||||
firstNonZero,
|
||||
this.coefficients,
|
||||
0,
|
||||
this.coefficients.length);
|
||||
}
|
||||
} else {
|
||||
this.coefficients = 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 the monomial representing coefficient * x^degree
|
||||
*/
|
||||
static GF256Poly buildMonomial(int degree, int coefficient) {
|
||||
if (degree < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (coefficient == 0) {
|
||||
return ZERO;
|
||||
}
|
||||
int[] coefficients = new int[degree + 1];
|
||||
coefficients[0] = coefficient;
|
||||
return new GF256Poly(coefficients);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
final int size = coefficients.length;
|
||||
if (a == 1) {
|
||||
// Just the sum of the coefficients
|
||||
int result = 0;
|
||||
for (int i = 0; i < size; i++) {
|
||||
result = GF256.addOrSubtract(result, coefficients[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
int result = coefficients[0];
|
||||
for (int i = 1; i < size; i++) {
|
||||
result = GF256.addOrSubtract(GF256.multiply(a, result), coefficients[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int evaluateFormatDerivativeAt(int a) {
|
||||
int degree = getDegree();
|
||||
if (degree == 0) {
|
||||
// Derivative of a constant is zero.
|
||||
return 0;
|
||||
}
|
||||
|
||||
int aToTheI = 1;
|
||||
int sum = getCoefficient(1);
|
||||
int aSquared = GF256.multiply(a, a);
|
||||
for (int i = 2; i < degree; i += 2) {
|
||||
aToTheI = GF256.multiply(aSquared, aToTheI);
|
||||
sum = GF256.addOrSubtract(sum, GF256.multiply(aToTheI, getCoefficient(i + 1)));
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
GF256Poly addOrSubtract(GF256Poly other) {
|
||||
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] = GF256.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
|
||||
}
|
||||
|
||||
return new GF256Poly(sumDiff);
|
||||
}
|
||||
|
||||
GF256Poly multiply(GF256Poly other) {
|
||||
if (isZero() || other.isZero()) {
|
||||
return ZERO;
|
||||
}
|
||||
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] = GF256.addOrSubtract(product[i + j],
|
||||
GF256.multiply(aCoeff, bCoefficients[j]));
|
||||
}
|
||||
}
|
||||
return new GF256Poly(product);
|
||||
}
|
||||
|
||||
GF256Poly multiply(int scalar) {
|
||||
if (scalar == 0) {
|
||||
return ZERO;
|
||||
}
|
||||
if (scalar == 1) {
|
||||
return this;
|
||||
}
|
||||
int size = coefficients.length;
|
||||
int[] product = new int[size];
|
||||
System.arraycopy(coefficients, 0, product, 0, size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
product[i] = GF256.multiply(product[i], scalar);
|
||||
}
|
||||
return new GF256Poly(product);
|
||||
}
|
||||
|
||||
GF256Poly multiplyByMonomial(int degree, int coefficient) {
|
||||
if (degree < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (coefficient == 0) {
|
||||
return ZERO;
|
||||
}
|
||||
int size = coefficients.length;
|
||||
int[] product = new int[size + degree];
|
||||
System.arraycopy(coefficients, 0, product, 0, size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
product[i] = GF256.multiply(product[i], coefficient);
|
||||
}
|
||||
return new GF256Poly(product);
|
||||
}
|
||||
|
||||
}
|
160
src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java
Normal file
160
src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java
Normal file
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.common.reedsolomon;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* <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 srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class ReedSolomonDecoder {
|
||||
|
||||
private ReedSolomonDecoder() {
|
||||
}
|
||||
|
||||
public static void decode(int[] received, int twoS) throws ReedSolomonException {
|
||||
GF256Poly poly = new GF256Poly(received);
|
||||
int[] syndromeCoefficients = new int[twoS];
|
||||
for (int i = 0; i < twoS; i++) {
|
||||
syndromeCoefficients[syndromeCoefficients.length - 1 - i] = poly.evaluateAt(GF256.exp(i));
|
||||
}
|
||||
GF256Poly syndrome = new GF256Poly(syndromeCoefficients);
|
||||
if (!syndrome.isZero()) { // Error
|
||||
GF256Poly[] sigmaOmega =
|
||||
runEuclideanAlgorithm(GF256Poly.buildMonomial(twoS, 1), syndrome, twoS);
|
||||
int[] errorLocations = findErrorLocations(sigmaOmega[0]);
|
||||
int[] errorMagnitudes = findErrorMagnitudes(sigmaOmega[1], errorLocations);
|
||||
for (int i = 0; i < errorLocations.length; i++) {
|
||||
int position = received.length - 1 - GF256.log(errorLocations[i]);
|
||||
received[position] = GF256.addOrSubtract(received[position], errorMagnitudes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static GF256Poly[] runEuclideanAlgorithm(GF256Poly a, GF256Poly b, int R)
|
||||
throws ReedSolomonException {
|
||||
// Assume a's degree is >= b's
|
||||
if (a.getDegree() < b.getDegree()) {
|
||||
GF256Poly temp = a;
|
||||
a = b;
|
||||
b = temp;
|
||||
}
|
||||
|
||||
GF256Poly rLast = a;
|
||||
GF256Poly r = b;
|
||||
GF256Poly sLast = GF256Poly.ONE;
|
||||
GF256Poly s = GF256Poly.ZERO;
|
||||
GF256Poly tLast = GF256Poly.ZERO;
|
||||
GF256Poly t = GF256Poly.ONE;
|
||||
|
||||
// Run Euclidean algorithm until r's degree is less than R/2
|
||||
while (r.getDegree() >= R / 2) {
|
||||
GF256Poly rLastLast = rLast;
|
||||
GF256Poly sLastLast = sLast;
|
||||
GF256Poly 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;
|
||||
GF256Poly q = GF256Poly.ZERO;
|
||||
int denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree());
|
||||
int dltInverse = GF256.inverse(denominatorLeadingTerm);
|
||||
while (r.getDegree() >= rLast.getDegree() && !r.isZero()) {
|
||||
int degreeDiff = r.getDegree() - rLast.getDegree();
|
||||
int scale = GF256.multiply(r.getCoefficient(r.getDegree()), dltInverse);
|
||||
q = q.addOrSubtract(GF256Poly.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 = GF256.inverse(sigmaTildeAtZero);
|
||||
GF256Poly sigma = t.multiply(inverse);
|
||||
GF256Poly omega = r.multiply(inverse);
|
||||
return new GF256Poly[] { sigma, omega };
|
||||
}
|
||||
|
||||
private static int[] findErrorLocations(GF256Poly errorLocator)
|
||||
throws ReedSolomonException {
|
||||
// This is a direct application of Chien's search
|
||||
Vector errorLocations = new Vector(3);
|
||||
for (int i = 1; i < 256; i++) {
|
||||
if (errorLocator.evaluateAt(i) == 0) {
|
||||
errorLocations.addElement(new Integer(GF256.inverse(i)));
|
||||
}
|
||||
}
|
||||
if (errorLocations.size() != errorLocator.getDegree()) {
|
||||
throw new ReedSolomonException("Error locator degree does not match number of roots");
|
||||
}
|
||||
int[] result = new int[errorLocations.size()]; // Can't use toArray() here
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = ((Integer) errorLocations.elementAt(i)).intValue();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int[] findErrorMagnitudes(GF256Poly errorEvaluator,
|
||||
int[] errorLocations) {
|
||||
// This is directly applying Forney's Formula
|
||||
int s = errorLocations.length;
|
||||
int[] result = new int[s];
|
||||
for (int i = 0; i < errorLocations.length; i++) {
|
||||
int xiInverse = GF256.inverse(errorLocations[i]);
|
||||
int denominator = 1;
|
||||
for (int j = 0; j < s; j++) {
|
||||
if (i != j) {
|
||||
denominator = GF256.multiply(denominator,
|
||||
GF256.addOrSubtract(1, GF256.multiply(errorLocations[j], xiInverse)));
|
||||
}
|
||||
}
|
||||
result[i] = GF256.multiply(errorEvaluator.evaluateAt(xiInverse),
|
||||
GF256.inverse(denominator));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.common.reedsolomon;
|
||||
|
||||
/**
|
||||
* <p>Thrown when an exception occurs during Reed-Solomon decoding, such as when
|
||||
* there are too many errors to correct.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class ReedSolomonException extends Exception {
|
||||
|
||||
public ReedSolomonException() {
|
||||
}
|
||||
|
||||
public ReedSolomonException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
106
src/com/google/zxing/qrcode/QRCodeReader.java
Normal file
106
src/com/google/zxing/qrcode/QRCodeReader.java
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode;
|
||||
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.MonochromeBitmapSource;
|
||||
import com.google.zxing.Reader;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.decoder.Decoder;
|
||||
import com.google.zxing.qrcode.detector.Detector;
|
||||
import com.google.zxing.qrcode.detector.DetectorResult;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class QRCodeReader implements Reader {
|
||||
|
||||
/**
|
||||
* Locates and decodes a QR code in an image.
|
||||
*
|
||||
* @return a String representing the content encoded by the QR code
|
||||
* @throws ReaderException if a QR code cannot be found, or cannot be decoded
|
||||
*/
|
||||
public Result decode(MonochromeBitmapSource image) throws ReaderException {
|
||||
return decode(image, null);
|
||||
}
|
||||
|
||||
public Result decode(MonochromeBitmapSource image, Hashtable hints)
|
||||
throws ReaderException {
|
||||
String text;
|
||||
ResultPoint[] points;
|
||||
if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
|
||||
BitMatrix bits = extractPureBits(image);
|
||||
text = Decoder.decode(bits);
|
||||
points = new ResultPoint[0];
|
||||
} else {
|
||||
DetectorResult result = new Detector(image).detect();
|
||||
text = Decoder.decode(result.getBits());
|
||||
points = result.getPoints();
|
||||
}
|
||||
return new Result(text, points);
|
||||
}
|
||||
|
||||
private static BitMatrix extractPureBits(MonochromeBitmapSource image)
|
||||
throws ReaderException {
|
||||
// Now need to determine module size in pixels
|
||||
// First, skip white border
|
||||
int borderWidth = 0;
|
||||
while (!image.isBlack(borderWidth, borderWidth)) {
|
||||
borderWidth++;
|
||||
}
|
||||
int moduleEnd = borderWidth;
|
||||
while (image.isBlack(moduleEnd, moduleEnd)) {
|
||||
moduleEnd++;
|
||||
}
|
||||
int moduleSize = moduleEnd - borderWidth;
|
||||
|
||||
int rowEndOfSymbol = image.getWidth() - 1;
|
||||
while (!image.isBlack(rowEndOfSymbol, borderWidth)) {
|
||||
rowEndOfSymbol--;
|
||||
}
|
||||
rowEndOfSymbol++;
|
||||
|
||||
if ((rowEndOfSymbol - borderWidth) % moduleSize != 0) {
|
||||
throw new ReaderException("Bad module size / width: " + moduleSize +
|
||||
" / " + (rowEndOfSymbol - borderWidth));
|
||||
}
|
||||
int dimension = (rowEndOfSymbol - borderWidth) / moduleSize;
|
||||
|
||||
// Push in the "border" by half the module width so that we start
|
||||
// sampling in the middle of the module. Just in case the image is a
|
||||
// little off, this will help recover.
|
||||
borderWidth += moduleSize >> 1;
|
||||
|
||||
BitMatrix bits = new BitMatrix(dimension);
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
int iOffset = borderWidth + i * moduleSize;
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
if (image.isBlack(borderWidth + j * moduleSize, iOffset)) {
|
||||
bits.set(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
}
|
180
src/com/google/zxing/qrcode/decoder/BitMatrixParser.java
Normal file
180
src/com/google/zxing/qrcode/decoder/BitMatrixParser.java
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.decoder;
|
||||
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
/**
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
final class BitMatrixParser {
|
||||
|
||||
private final BitMatrix bitMatrix;
|
||||
private Version parsedVersion;
|
||||
private FormatInformation parsedFormatInfo;
|
||||
|
||||
/**
|
||||
* @throws com.google.zxing.ReaderException
|
||||
* if dimension is not >= 21 and 1 mod 4
|
||||
*/
|
||||
BitMatrixParser(BitMatrix bitMatrix) throws ReaderException {
|
||||
int dimension = bitMatrix.getDimension();
|
||||
if (dimension < 21 || (dimension & 0x03) != 1) {
|
||||
throw new ReaderException("Dimension must be 1 mod 4 and >= 21");
|
||||
}
|
||||
this.bitMatrix = bitMatrix;
|
||||
}
|
||||
|
||||
FormatInformation readFormatInformation() throws ReaderException {
|
||||
|
||||
if (parsedFormatInfo != null) {
|
||||
return parsedFormatInfo;
|
||||
}
|
||||
|
||||
// Read top-left format info bits
|
||||
int formatInfoBits = 0;
|
||||
for (int j = 0; j < 6; j++) {
|
||||
formatInfoBits = copyBit(8, j, formatInfoBits);
|
||||
}
|
||||
// .. and skip a bit in the timing pattern ...
|
||||
formatInfoBits = copyBit(8, 7, formatInfoBits);
|
||||
formatInfoBits = copyBit(8, 8, formatInfoBits);
|
||||
formatInfoBits = copyBit(7, 8, formatInfoBits);
|
||||
// .. and skip a bit in the timing pattern ...
|
||||
for (int i = 5; i >= 0; i--) {
|
||||
formatInfoBits = copyBit(i, 8, formatInfoBits);
|
||||
}
|
||||
|
||||
parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits);
|
||||
if (parsedFormatInfo != null) {
|
||||
return parsedFormatInfo;
|
||||
}
|
||||
|
||||
// Hmm, failed. Try the top-right/bottom-left pattern
|
||||
int dimension = bitMatrix.getDimension();
|
||||
formatInfoBits = 0;
|
||||
int iMin = dimension - 8;
|
||||
for (int i = dimension - 1; i >= iMin; i--) {
|
||||
formatInfoBits = copyBit(i, 8, formatInfoBits);
|
||||
}
|
||||
for (int j = dimension - 7; j < dimension; j++) {
|
||||
formatInfoBits = copyBit(8, j, formatInfoBits);
|
||||
}
|
||||
|
||||
parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits);
|
||||
if (parsedFormatInfo != null) {
|
||||
return parsedFormatInfo;
|
||||
}
|
||||
throw new ReaderException("Could not decode format information");
|
||||
}
|
||||
|
||||
Version readVersion() throws ReaderException {
|
||||
|
||||
if (parsedVersion != null) {
|
||||
return parsedVersion;
|
||||
}
|
||||
|
||||
int dimension = bitMatrix.getDimension();
|
||||
|
||||
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;
|
||||
for (int i = 5; i >= 0; i--) {
|
||||
int jMin = dimension - 11;
|
||||
for (int j = dimension - 9; j >= jMin; j--) {
|
||||
versionBits = copyBit(i, j, versionBits);
|
||||
}
|
||||
}
|
||||
|
||||
parsedVersion = Version.decodeVersionInformation(versionBits);
|
||||
if (parsedVersion != null) {
|
||||
return parsedVersion;
|
||||
}
|
||||
|
||||
// Hmm, failed. Try bottom left: 6 wide by 3 tall
|
||||
versionBits = 0;
|
||||
for (int j = 5; j >= 0; j--) {
|
||||
int iMin = dimension - 11;
|
||||
for (int i = dimension - 11; i >= iMin; i--) {
|
||||
versionBits = copyBit(i, j, versionBits);
|
||||
}
|
||||
}
|
||||
|
||||
parsedVersion = Version.decodeVersionInformation(versionBits);
|
||||
if (parsedVersion != null) {
|
||||
return parsedVersion;
|
||||
}
|
||||
throw new ReaderException("Could not decode version");
|
||||
}
|
||||
|
||||
private int copyBit(int i, int j, int versionBits) {
|
||||
return bitMatrix.get(i, j) ? (versionBits << 1) | 0x1 : versionBits << 1;
|
||||
}
|
||||
|
||||
byte[] readCodewords() throws ReaderException {
|
||||
|
||||
FormatInformation formatInfo = readFormatInformation();
|
||||
Version version = readVersion();
|
||||
|
||||
DataMask dataMask = DataMask.forReference((int) formatInfo.getDataMask());
|
||||
int dimension = bitMatrix.getDimension();
|
||||
dataMask.unmaskBitMatrix(bitMatrix.getBits(), dimension);
|
||||
|
||||
BitMatrix functionPattern = version.buildFunctionPattern();
|
||||
|
||||
boolean readingUp = true;
|
||||
byte[] result = new byte[version.getTotalCodewords()];
|
||||
int resultOffset = 0;
|
||||
int currentByte = 0;
|
||||
int bitsRead = 0;
|
||||
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--;
|
||||
}
|
||||
for (int count = 0; count < dimension; count++) {
|
||||
int i = readingUp ? dimension - 1 - count : count;
|
||||
for (int col = 0; col < 2; col++) {
|
||||
if (!functionPattern.get(i, j - col)) {
|
||||
bitsRead++;
|
||||
currentByte <<= 1;
|
||||
if (bitMatrix.get(i, j - col)) {
|
||||
currentByte |= 1;
|
||||
}
|
||||
if (bitsRead == 8) {
|
||||
result[resultOffset++] = (byte) currentByte;
|
||||
bitsRead = 0;
|
||||
currentByte = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
readingUp = !readingUp; // switch directions
|
||||
}
|
||||
if (resultOffset != version.getTotalCodewords()) {
|
||||
throw new ReaderException("Did not read all codewords");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
84
src/com/google/zxing/qrcode/decoder/BitSource.java
Executable file
84
src/com/google/zxing/qrcode/decoder/BitSource.java
Executable file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.decoder;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
final class BitSource {
|
||||
|
||||
private final byte[] bytes;
|
||||
private int byteOffset;
|
||||
private int bitOffset;
|
||||
|
||||
BitSource(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if numBits isn't in [1,32]
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
int available() {
|
||||
return 8 * (bytes.length - byteOffset) - bitOffset;
|
||||
}
|
||||
|
||||
}
|
110
src/com/google/zxing/qrcode/decoder/DataBlock.java
Executable file
110
src/com/google/zxing/qrcode/decoder/DataBlock.java
Executable file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.decoder;
|
||||
|
||||
/**
|
||||
* @author srowen@google.com (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;
|
||||
}
|
||||
|
||||
static DataBlock[] getDataBlocks(byte[] rawCodewords,
|
||||
Version version,
|
||||
ErrorCorrectionLevel ecLevel) {
|
||||
Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
|
||||
int totalBlocks = 0;
|
||||
Version.ECB[] ecBlockArray = ecBlocks.getECBlocks();
|
||||
for (int i = 0; i < ecBlockArray.length; i++) {
|
||||
totalBlocks += ecBlockArray[i].getCount();
|
||||
}
|
||||
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.getECCodewords() + 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;
|
||||
}
|
||||
if (numCodewords != shorterBlocksTotalCodewords + 1) {
|
||||
throw new IllegalStateException(
|
||||
"Data block sizes differ by more than 1");
|
||||
}
|
||||
longerBlocksStartAt--;
|
||||
}
|
||||
longerBlocksStartAt++;
|
||||
|
||||
int shorterBlocksNumDataCodewords =
|
||||
shorterBlocksTotalCodewords - ecBlocks.getECCodewords();
|
||||
// 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++];
|
||||
}
|
||||
}
|
||||
|
||||
if (rawCodewordsOffset != rawCodewords.length) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int getNumDataCodewords() {
|
||||
return numDataCodewords;
|
||||
}
|
||||
|
||||
byte[] getCodewords() {
|
||||
return codewords;
|
||||
}
|
||||
|
||||
}
|
245
src/com/google/zxing/qrcode/decoder/DataMask.java
Executable file
245
src/com/google/zxing/qrcode/decoder/DataMask.java
Executable file
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.decoder;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
abstract class DataMask {
|
||||
|
||||
/**
|
||||
* See ISO 18004:2006 6.8.1
|
||||
*/
|
||||
private static final DataMask[] DATA_MASKS = new DataMask[]{
|
||||
new DataMask000(),
|
||||
new DataMask001(),
|
||||
new DataMask010(),
|
||||
new DataMask011(),
|
||||
new DataMask100(),
|
||||
new DataMask101(),
|
||||
new DataMask110(),
|
||||
new DataMask111(),
|
||||
};
|
||||
|
||||
private DataMask() {
|
||||
}
|
||||
|
||||
abstract void unmaskBitMatrix(int[] bits, int dimension);
|
||||
|
||||
static DataMask forReference(int reference) {
|
||||
if (reference < 0 || reference > 7) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return DATA_MASKS[reference];
|
||||
}
|
||||
|
||||
/**
|
||||
* 000: mask bits for which (i + j) mod 2 == 0
|
||||
*/
|
||||
private static class DataMask000 extends DataMask {
|
||||
private static final int BITMASK = 0x55555555; // = 010101...
|
||||
|
||||
void unmaskBitMatrix(int[] bits, int dimension) {
|
||||
// This one's easy. Because the dimension of BitMatrix is always odd,
|
||||
// we can merely flip every other bit
|
||||
int max = bits.length;
|
||||
for (int i = 0; i < max; i++) {
|
||||
bits[i] ^= BITMASK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 001: mask bits for which j mod 2 == 0
|
||||
*/
|
||||
private static class DataMask001 extends DataMask {
|
||||
void unmaskBitMatrix(int[] bits, int dimension) {
|
||||
int bitMask = 0;
|
||||
int count = 0;
|
||||
int offset = 0;
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
if ((i & 0x01) == 0) {
|
||||
bitMask |= 1 << count;
|
||||
}
|
||||
if (++count == 32) {
|
||||
bits[offset++] ^= bitMask;
|
||||
count = 0;
|
||||
bitMask = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
bits[offset] ^= bitMask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 010: mask bits for which j mod 3 == 0
|
||||
*/
|
||||
private static class DataMask010 extends DataMask {
|
||||
void unmaskBitMatrix(int[] bits, int dimension) {
|
||||
int bitMask = 0;
|
||||
int count = 0;
|
||||
int offset = 0;
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
boolean columnMasked = j % 3 == 0;
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
if (columnMasked) {
|
||||
bitMask |= 1 << count;
|
||||
}
|
||||
if (++count == 32) {
|
||||
bits[offset++] ^= bitMask;
|
||||
count = 0;
|
||||
bitMask = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
bits[offset] ^= bitMask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 011: mask bits for which (i + j) mod 3 == 0
|
||||
*/
|
||||
private static class DataMask011 extends DataMask {
|
||||
void unmaskBitMatrix(int[] bits, int dimension) {
|
||||
int bitMask = 0;
|
||||
int count = 0;
|
||||
int offset = 0;
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
if ((i + j) % 3 == 0) {
|
||||
bitMask |= 1 << count;
|
||||
}
|
||||
if (++count == 32) {
|
||||
bits[offset++] ^= bitMask;
|
||||
count = 0;
|
||||
bitMask = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
bits[offset] ^= bitMask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 100: mask bits for which (i/2 + j/3) mod 2 == 0
|
||||
*/
|
||||
private static class DataMask100 extends DataMask {
|
||||
void unmaskBitMatrix(int[] bits, int dimension) {
|
||||
int bitMask = 0;
|
||||
int count = 0;
|
||||
int offset = 0;
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
int jComponent = j / 3;
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
if (((i >> 1 + jComponent) & 0x01) == 0) {
|
||||
bitMask |= 1 << count;
|
||||
}
|
||||
if (++count == 32) {
|
||||
bits[offset++] ^= bitMask;
|
||||
count = 0;
|
||||
bitMask = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
bits[offset] ^= bitMask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 101: mask bits for which ij mod 2 + ij mod 3 == 0
|
||||
*/
|
||||
private static class DataMask101 extends DataMask {
|
||||
void unmaskBitMatrix(int[] bits, int dimension) {
|
||||
int bitMask = 0;
|
||||
int count = 0;
|
||||
int offset = 0;
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
int product = i * j;
|
||||
if (((product & 0x01) == 0) && product % 3 == 0) {
|
||||
bitMask |= 1 << count;
|
||||
}
|
||||
if (++count == 32) {
|
||||
bits[offset++] ^= bitMask;
|
||||
count = 0;
|
||||
bitMask = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
bits[offset] ^= bitMask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 110: mask bits for which (ij mod 2 + ij mod 3) mod 2 == 0
|
||||
*/
|
||||
private static class DataMask110 extends DataMask {
|
||||
void unmaskBitMatrix(int[] bits, int dimension) {
|
||||
int bitMask = 0;
|
||||
int count = 0;
|
||||
int offset = 0;
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
int product = i * j;
|
||||
if ((((product & 0x01) + product % 3) & 0x01) == 0) {
|
||||
bitMask |= 1 << count;
|
||||
}
|
||||
if (++count == 32) {
|
||||
bits[offset++] ^= bitMask;
|
||||
count = 0;
|
||||
bitMask = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
bits[offset] ^= bitMask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 111: mask bits for which ((i+j)mod 2 + ij mod 3) mod 2 == 0
|
||||
*/
|
||||
private static class DataMask111 extends DataMask {
|
||||
void unmaskBitMatrix(int[] bits, int dimension) {
|
||||
int bitMask = 0;
|
||||
int count = 0;
|
||||
int offset = 0;
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
if (((((i + j) & 0x01) + (i * j) % 3) & 0x01) == 0) {
|
||||
bitMask |= 1 << count;
|
||||
}
|
||||
if (++count == 32) {
|
||||
bits[offset++] ^= bitMask;
|
||||
count = 0;
|
||||
bitMask = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
bits[offset] ^= bitMask;
|
||||
}
|
||||
}
|
||||
}
|
213
src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java
Normal file
213
src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java
Normal file
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.decoder;
|
||||
|
||||
import com.google.zxing.ReaderException;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* See ISO 18004:2006, 6.4.3 - 6.4.7
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
final class DecodedBitStreamParser {
|
||||
|
||||
/**
|
||||
* See ISO 18004:2006, 6.4.4 Table 5
|
||||
*/
|
||||
private static final char[] ALPHANUMERIC_CHARS = new char[]{
|
||||
'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 String SHIFT_JIS = "Shift_JIS";
|
||||
private static final boolean ASSUME_SHIFT_JIS;
|
||||
|
||||
static {
|
||||
String platformDefault = System.getProperty("file.encoding");
|
||||
ASSUME_SHIFT_JIS = SHIFT_JIS.equalsIgnoreCase(platformDefault) ||
|
||||
"EUC-JP".equalsIgnoreCase(platformDefault);
|
||||
}
|
||||
|
||||
private DecodedBitStreamParser() {
|
||||
}
|
||||
|
||||
static String decode(byte[] bytes, Version version) throws ReaderException {
|
||||
BitSource bits = new BitSource(bytes);
|
||||
StringBuffer result = new StringBuffer();
|
||||
Mode mode;
|
||||
do {
|
||||
// While still another segment to read...
|
||||
mode = Mode.forBits(bits.readBits(4));
|
||||
if (!mode.equals(Mode.TERMINATOR)) {
|
||||
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);
|
||||
} else if (mode.equals(Mode.BYTE)) {
|
||||
decodeByteSegment(bits, result, count);
|
||||
} else if (mode.equals(Mode.KANJI)) {
|
||||
decodeKanjiSegment(bits, result, count);
|
||||
} else {
|
||||
throw new ReaderException("Unsupported mode indicator: " + mode);
|
||||
}
|
||||
}
|
||||
} while (!mode.equals(Mode.TERMINATOR));
|
||||
|
||||
/*
|
||||
int bitsLeft = bits.available();
|
||||
if (bitsLeft > 0) {
|
||||
if (bitsLeft > 6 || bits.readBits(bitsLeft) != 0) {
|
||||
throw new ReaderException("Excess bits or non-zero bits after terminator mode indicator");
|
||||
}
|
||||
}
|
||||
*/
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static void decodeKanjiSegment(BitSource bits,
|
||||
StringBuffer result,
|
||||
int count) throws ReaderException {
|
||||
byte[] buffer = new byte[2 * count];
|
||||
int offset = 0;
|
||||
while (count > 0) {
|
||||
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, "Shift_JIS"));
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new ReaderException("Can't decode SHIFT_JIS string: " + uee);
|
||||
}
|
||||
}
|
||||
|
||||
private static void decodeByteSegment(BitSource bits,
|
||||
StringBuffer result,
|
||||
int count) throws ReaderException {
|
||||
byte[] readBytes = new byte[count];
|
||||
if (count << 3 > bits.available()) {
|
||||
throw new ReaderException("Count too large: " + count);
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
readBytes[i] = (byte) bits.readBits(8);
|
||||
}
|
||||
// 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.
|
||||
String encoding = guessEncoding(readBytes);
|
||||
try {
|
||||
result.append(new String(readBytes, encoding));
|
||||
} catch (UnsupportedEncodingException uce) {
|
||||
throw new ReaderException(uce.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void decodeAlphanumericSegment(BitSource bits,
|
||||
StringBuffer result,
|
||||
int count) {
|
||||
// Read two characters at a time
|
||||
while (count > 1) {
|
||||
int nextTwoCharsBits = bits.readBits(11);
|
||||
result.append(ALPHANUMERIC_CHARS[nextTwoCharsBits / 45]);
|
||||
result.append(ALPHANUMERIC_CHARS[nextTwoCharsBits % 45]);
|
||||
count -= 2;
|
||||
}
|
||||
if (count == 1) {
|
||||
// special case on char left
|
||||
result.append(ALPHANUMERIC_CHARS[bits.readBits(6)]);
|
||||
}
|
||||
}
|
||||
|
||||
private static void decodeNumericSegment(BitSource bits,
|
||||
StringBuffer result,
|
||||
int count) throws ReaderException {
|
||||
while (count >= 3) {
|
||||
int threeDigitsBits = bits.readBits(10);
|
||||
if (threeDigitsBits >= 1000) {
|
||||
throw new ReaderException("Illegal value for 3-digit unit: " + threeDigitsBits);
|
||||
}
|
||||
result.append(ALPHANUMERIC_CHARS[threeDigitsBits / 100]);
|
||||
result.append(ALPHANUMERIC_CHARS[(threeDigitsBits / 10) % 10]);
|
||||
result.append(ALPHANUMERIC_CHARS[threeDigitsBits % 10]);
|
||||
count -= 3;
|
||||
}
|
||||
if (count == 2) {
|
||||
int twoDigitsBits = bits.readBits(7);
|
||||
if (twoDigitsBits >= 100) {
|
||||
throw new ReaderException("Illegal value for 2-digit unit: " + twoDigitsBits);
|
||||
}
|
||||
result.append(ALPHANUMERIC_CHARS[twoDigitsBits / 10]);
|
||||
result.append(ALPHANUMERIC_CHARS[twoDigitsBits % 10]);
|
||||
} else if (count == 1) {
|
||||
int digitBits = bits.readBits(4);
|
||||
if (digitBits >= 10) {
|
||||
throw new ReaderException("Illegal value for digit unit: " + digitBits);
|
||||
}
|
||||
result.append(ALPHANUMERIC_CHARS[digitBits]);
|
||||
}
|
||||
}
|
||||
|
||||
private static String guessEncoding(byte[] bytes) {
|
||||
if (ASSUME_SHIFT_JIS) {
|
||||
return SHIFT_JIS;
|
||||
}
|
||||
// For now, merely tries to distinguish ISO-8859-1 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.
|
||||
int length = bytes.length;
|
||||
for (int i = 0; i < length; i++) {
|
||||
int value = bytes[i] & 0xFF;
|
||||
if (value >= 0x80 && value <= 0x9F && i < length - 1) {
|
||||
// ISO-8859-1 shouldn't use this, but before we decide it is Shift_JIS,
|
||||
// just double check that it is followed by a byte that's valid in
|
||||
// the Shift_JIS encoding
|
||||
int nextValue = bytes[i + 1] & 0xFF;
|
||||
if ((value & 0x1) == 0) {
|
||||
// if even,
|
||||
if (nextValue >= 0x40 && nextValue <= 0x9E) {
|
||||
return SHIFT_JIS;
|
||||
}
|
||||
} else {
|
||||
if (nextValue >= 0x9F && nextValue <= 0x7C) {
|
||||
return SHIFT_JIS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "ISO-8859-1";
|
||||
}
|
||||
|
||||
}
|
88
src/com/google/zxing/qrcode/decoder/Decoder.java
Normal file
88
src/com/google/zxing/qrcode/decoder/Decoder.java
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.decoder;
|
||||
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
|
||||
import com.google.zxing.common.reedsolomon.ReedSolomonException;
|
||||
|
||||
/**
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class Decoder {
|
||||
|
||||
private Decoder() {
|
||||
}
|
||||
|
||||
public static String decode(boolean[][] image) throws ReaderException {
|
||||
int dimension = image.length;
|
||||
BitMatrix bits = new BitMatrix(dimension);
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
if (image[i][j]) {
|
||||
bits.set(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
return decode(bits);
|
||||
}
|
||||
|
||||
public static String decode(BitMatrix bits) throws ReaderException {
|
||||
BitMatrixParser parser = new BitMatrixParser(bits);
|
||||
Version version = parser.readVersion();
|
||||
ErrorCorrectionLevel ecLevel = parser.readFormatInformation().getErrorCorrectionLevel();
|
||||
byte[] codewords = parser.readCodewords();
|
||||
DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel);
|
||||
int totalBytes = 0;
|
||||
for (int i = 0; i < dataBlocks.length; i++) {
|
||||
totalBytes += dataBlocks[i].getNumDataCodewords();
|
||||
}
|
||||
byte[] resultBytes = new byte[totalBytes];
|
||||
int resultOffset = 0;
|
||||
for (int j = 0; j < dataBlocks.length; j++) {
|
||||
DataBlock dataBlock = dataBlocks[j];
|
||||
byte[] codewordBytes = dataBlock.getCodewords();
|
||||
int numDataCodewords = dataBlock.getNumDataCodewords();
|
||||
correctErrors(codewordBytes, numDataCodewords);
|
||||
for (int i = 0; i < numDataCodewords; i++) {
|
||||
resultBytes[resultOffset++] = codewordBytes[i];
|
||||
}
|
||||
}
|
||||
|
||||
return DecodedBitStreamParser.decode(resultBytes, version);
|
||||
}
|
||||
|
||||
private static void correctErrors(byte[] codewordBytes, int numDataCodewords)
|
||||
throws ReaderException {
|
||||
int numCodewords = codewordBytes.length;
|
||||
int[] codewordsInts = new int[numCodewords];
|
||||
for (int i = 0; i < numCodewords; i++) {
|
||||
codewordsInts[i] = codewordBytes[i] & 0xFF;
|
||||
}
|
||||
int numECCodewords = codewordBytes.length - numDataCodewords;
|
||||
try {
|
||||
ReedSolomonDecoder.decode(codewordsInts, numECCodewords);
|
||||
} catch (ReedSolomonException rse) {
|
||||
throw new ReaderException(rse.toString());
|
||||
}
|
||||
for (int i = 0; i < numDataCodewords; i++) {
|
||||
codewordBytes[i] = (byte) codewordsInts[i];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.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 srowen@google.com (Sean Owen)
|
||||
*/
|
||||
final class ErrorCorrectionLevel {
|
||||
|
||||
// No, we can't use an enum here. J2ME doesn't support it.
|
||||
|
||||
/** L = ~7% correction */
|
||||
static final ErrorCorrectionLevel L = new ErrorCorrectionLevel(0);
|
||||
/** M = ~15% correction */
|
||||
static final ErrorCorrectionLevel M = new ErrorCorrectionLevel(1);
|
||||
/** Q = ~25% correction */
|
||||
static final ErrorCorrectionLevel Q = new ErrorCorrectionLevel(2);
|
||||
/** H = ~30% correction */
|
||||
static final ErrorCorrectionLevel H = new ErrorCorrectionLevel(3);
|
||||
|
||||
private static final ErrorCorrectionLevel[] FOR_BITS = new ErrorCorrectionLevel[] { M, L, H, Q };
|
||||
|
||||
private final int ordinal;
|
||||
|
||||
private ErrorCorrectionLevel(final int ordinal) {
|
||||
this.ordinal = ordinal;
|
||||
}
|
||||
|
||||
int ordinal() {
|
||||
return ordinal;
|
||||
}
|
||||
|
||||
static ErrorCorrectionLevel forBits(int bits) {
|
||||
return FOR_BITS[bits];
|
||||
}
|
||||
|
||||
|
||||
}
|
147
src/com/google/zxing/qrcode/decoder/FormatInformation.java
Normal file
147
src/com/google/zxing/qrcode/decoder/FormatInformation.java
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.decoder;
|
||||
|
||||
/**
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
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 = new int[][]{
|
||||
{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},
|
||||
};
|
||||
|
||||
private static final int[] BITS_SET_IN_HALF_BYTE =
|
||||
new int[]{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;
|
||||
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)];
|
||||
}
|
||||
|
||||
static FormatInformation decodeFormatInformation(int rawFormatInfo) {
|
||||
FormatInformation formatInfo = doDecodeFormatInformation(rawFormatInfo);
|
||||
if (formatInfo != null) {
|
||||
return formatInfo;
|
||||
}
|
||||
// Should return null, but, some QR codes apparently
|
||||
// do not mask this info. Try again, first masking the raw bits so
|
||||
// the function will unmask
|
||||
return doDecodeFormatInformation(rawFormatInfo ^ FORMAT_INFO_MASK_QR);
|
||||
}
|
||||
|
||||
private static FormatInformation doDecodeFormatInformation(
|
||||
int rawFormatInfo) {
|
||||
// Unmask:
|
||||
int unmaskedFormatInfo = rawFormatInfo ^ FORMAT_INFO_MASK_QR;
|
||||
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 == unmaskedFormatInfo) {
|
||||
return new FormatInformation(decodeInfo[1]);
|
||||
}
|
||||
int bitsDifference = numBitsDiffering(unmaskedFormatInfo, targetInfo);
|
||||
if (bitsDifference < bestDifference) {
|
||||
bestFormatInfo = decodeInfo[1];
|
||||
bestDifference = bitsDifference;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
73
src/com/google/zxing/qrcode/decoder/Mode.java
Normal file
73
src/com/google/zxing/qrcode/decoder/Mode.java
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.decoder;
|
||||
|
||||
import com.google.zxing.ReaderException;
|
||||
|
||||
/**
|
||||
* <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 srowen@google.com (Sean Owen)
|
||||
*/
|
||||
final class Mode {
|
||||
|
||||
// No, we can't use an enum here. J2ME doesn't support it.
|
||||
|
||||
static final Mode TERMINATOR = new Mode(new int[] {0, 0, 0}); // Not really a mode...
|
||||
static final Mode NUMERIC = new Mode(new int[] {10, 12, 14});
|
||||
static final Mode ALPHANUMERIC = new Mode(new int[] {9, 11, 13});
|
||||
static final Mode BYTE = new Mode(new int[] {8, 16, 16});
|
||||
static final Mode KANJI = new Mode(new int[] {8, 10, 12});
|
||||
|
||||
private int[] characterCountBitsForVersions;
|
||||
|
||||
private Mode(int[] characterCountBitsForVersions) {
|
||||
this.characterCountBitsForVersions = characterCountBitsForVersions;
|
||||
}
|
||||
|
||||
static Mode forBits(int bits) throws ReaderException {
|
||||
switch (bits) {
|
||||
case 0x0:
|
||||
return TERMINATOR;
|
||||
case 0x1:
|
||||
return NUMERIC;
|
||||
case 0x2:
|
||||
return ALPHANUMERIC;
|
||||
case 0x4:
|
||||
return BYTE;
|
||||
case 0x8:
|
||||
return KANJI;
|
||||
default:
|
||||
throw new ReaderException("Illegal mode bits: " + bits);
|
||||
}
|
||||
}
|
||||
|
||||
int getCharacterCountBits(Version version) {
|
||||
int number = version.getVersionNumber();
|
||||
int offset;
|
||||
if (number <= 9) {
|
||||
offset = 0;
|
||||
} else if (number <= 26) {
|
||||
offset = 1;
|
||||
} else {
|
||||
offset = 2;
|
||||
}
|
||||
return characterCountBitsForVersions[offset];
|
||||
}
|
||||
|
||||
}
|
562
src/com/google/zxing/qrcode/decoder/Version.java
Executable file
562
src/com/google/zxing/qrcode/decoder/Version.java
Executable file
|
@ -0,0 +1,562 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.decoder;
|
||||
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
/**
|
||||
* See ISO 18004:2006 Annex D
|
||||
*
|
||||
* @author srowen@google.com (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 = new int[]{
|
||||
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;
|
||||
ECBlocks levelLECBlocks = ecBlocks1; // L,M,Q,H -- all the same total
|
||||
int ecCodewords = levelLECBlocks.ecCodewords;
|
||||
ECB[] ecbArray = levelLECBlocks.ecBlocks;
|
||||
for (int i = 0; i < ecbArray.length; i++) {
|
||||
ECB ecBlock = ecbArray[i];
|
||||
total += ecBlock.count * (ecBlock.dataCodewords + 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;
|
||||
}
|
||||
|
||||
ECBlocks getECBlocksForLevel(ErrorCorrectionLevel ecLevel) {
|
||||
return ecBlocks[ecLevel.ordinal()];
|
||||
}
|
||||
|
||||
public static Version getProvisionalVersionForDimension(int dimension)
|
||||
throws ReaderException {
|
||||
if (dimension % 4 != 1) {
|
||||
throw new ReaderException("Dimension must be 1 mod 4");
|
||||
}
|
||||
return getVersionForNumber((dimension - 17) >> 2);
|
||||
}
|
||||
|
||||
public static Version getVersionForNumber(int versionNumber)
|
||||
throws ReaderException {
|
||||
if (versionNumber < 1 || versionNumber > 40) {
|
||||
throw new ReaderException(
|
||||
"versionNumber must be between 1 and 40");
|
||||
}
|
||||
return VERSIONS[versionNumber - 1];
|
||||
}
|
||||
|
||||
static Version decodeVersionInformation(int versionBits)
|
||||
throws ReaderException {
|
||||
int bestDifference = Integer.MAX_VALUE;
|
||||
int bestVersion = 0;
|
||||
for (int i = 0; i < VERSION_DECODE_INFO.length; i++) {
|
||||
int targetVersion = VERSION_DECODE_INFO[i];
|
||||
if (targetVersion == versionBits) {
|
||||
return getVersionForNumber(i + 7);
|
||||
}
|
||||
int bitsDifference =
|
||||
FormatInformation.numBitsDiffering(versionBits, targetVersion);
|
||||
if (bitsDifference < bestDifference) {
|
||||
bestVersion = i + 7;
|
||||
}
|
||||
}
|
||||
if (bestDifference <= 3) {
|
||||
return getVersionForNumber(bestVersion);
|
||||
}
|
||||
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(0, dimension - 8, 9, 8);
|
||||
// Bottom left finder pattern + separator + format
|
||||
bitMatrix.setRegion(dimension - 8, 0, 8, 9);
|
||||
|
||||
// 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(i, alignmentPatternCenters[y] - 2, 5, 5);
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical timing pattern
|
||||
bitMatrix.setRegion(9, 6, dimension - 17, 1);
|
||||
// Horizontal timing pattern
|
||||
bitMatrix.setRegion(6, 9, 1, dimension - 17);
|
||||
|
||||
if (versionNumber > 6) {
|
||||
// Version info, top right
|
||||
bitMatrix.setRegion(0, dimension - 11, 6, 3);
|
||||
// Version info, bottom left
|
||||
bitMatrix.setRegion(dimension - 11, 0, 3, 6);
|
||||
}
|
||||
|
||||
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>
|
||||
*/
|
||||
static final class ECBlocks {
|
||||
private int ecCodewords;
|
||||
private ECB[] ecBlocks;
|
||||
|
||||
private ECBlocks(int ecCodewords, ECB ecBlocks) {
|
||||
this.ecCodewords = ecCodewords;
|
||||
this.ecBlocks = new ECB[] { ecBlocks };
|
||||
}
|
||||
|
||||
private ECBlocks(int ecCodewords, ECB ecBlocks1, ECB ecBlocks2) {
|
||||
this.ecCodewords = ecCodewords;
|
||||
this.ecBlocks = new ECB[] { ecBlocks1, ecBlocks2 };
|
||||
}
|
||||
|
||||
int getECCodewords() {
|
||||
return ecCodewords;
|
||||
}
|
||||
|
||||
ECB[] getECBlocks() {
|
||||
return ecBlocks;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Encapsualtes the parameters for one error-correction block in one symbol version.
|
||||
* This includes the number of data codewords, and the number of times a block with these
|
||||
* parameters is used consecutively in the QR code version's format.</p>
|
||||
*/
|
||||
static final class ECB {
|
||||
private int count;
|
||||
private int dataCodewords;
|
||||
|
||||
private ECB(int count, int dataCodewords) {
|
||||
this.count = count;
|
||||
this.dataCodewords = dataCodewords;
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
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, 74, 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, 1061),
|
||||
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)))
|
||||
};
|
||||
}
|
||||
|
||||
}
|
56
src/com/google/zxing/qrcode/detector/AlignmentPattern.java
Normal file
56
src/com/google/zxing/qrcode/detector/AlignmentPattern.java
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.detector;
|
||||
|
||||
import com.google.zxing.ResultPoint;
|
||||
|
||||
/**
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class AlignmentPattern implements ResultPoint {
|
||||
|
||||
private final float posX;
|
||||
private final float posY;
|
||||
private final float estimatedModuleSize;
|
||||
|
||||
AlignmentPattern(float posX, float posY, float estimatedModuleSize) {
|
||||
this.posX = posX;
|
||||
this.posY = posY;
|
||||
this.estimatedModuleSize = estimatedModuleSize;
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
return posX;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return posY;
|
||||
}
|
||||
|
||||
float getEstimatedModuleSize() {
|
||||
return estimatedModuleSize;
|
||||
}
|
||||
|
||||
boolean aboutEquals(float moduleSize, float i, float j) {
|
||||
return
|
||||
Math.abs(i - posY) <= moduleSize &&
|
||||
Math.abs(j - posX) <= moduleSize &&
|
||||
(Math.abs(moduleSize - estimatedModuleSize) <= 1.0f ||
|
||||
Math.abs(moduleSize - estimatedModuleSize) / estimatedModuleSize <= 0.1f);
|
||||
}
|
||||
|
||||
}
|
210
src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java
Normal file
210
src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java
Normal file
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.detector;
|
||||
|
||||
import com.google.zxing.MonochromeBitmapSource;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* <p>At the moment this only looks for the bottom-right alignment pattern.</p>
|
||||
*
|
||||
* <p>This class is not thread-safe.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
final class AlignmentPatternFinder {
|
||||
|
||||
private final MonochromeBitmapSource 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;
|
||||
|
||||
AlignmentPatternFinder(MonochromeBitmapSource image,
|
||||
int startX,
|
||||
int startY,
|
||||
int width,
|
||||
int height,
|
||||
float moduleSize) {
|
||||
this.image = image;
|
||||
this.possibleCenters = new Vector(5);
|
||||
this.startX = startX;
|
||||
this.startY = startY;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.moduleSize = moduleSize;
|
||||
}
|
||||
|
||||
AlignmentPattern find() throws ReaderException {
|
||||
int startX = this.startX;
|
||||
int height = this.height;
|
||||
int maxJ = startX + width;
|
||||
int middleI = startY + (height >> 1);
|
||||
BitArray luminanceRow = new BitArray(width);
|
||||
int[] stateCount = new int[3]; // looking for 1 1 1
|
||||
for (int iGen = 0; iGen < height; iGen++) {
|
||||
// Search from middle outwards
|
||||
int i = middleI +
|
||||
((iGen & 0x01) == 0 ? ((iGen + 1) >> 1) : -((iGen + 1) >> 1));
|
||||
image.getBlackRow(i, luminanceRow, startX, width);
|
||||
stateCount[0] = 0;
|
||||
stateCount[1] = 0;
|
||||
stateCount[2] = 0;
|
||||
int currentState = 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 (!luminanceRow.get(j - startX) && j < maxJ) {
|
||||
j++;
|
||||
}
|
||||
while (j < maxJ) {
|
||||
if (luminanceRow.get(j - startX)) {
|
||||
// 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 new ReaderException("Could not find alignment pattern");
|
||||
}
|
||||
|
||||
private static float centerFromEnd(int[] stateCount, int end) {
|
||||
return (float) (end - stateCount[2]) - stateCount[1] / 2.0f;
|
||||
}
|
||||
|
||||
private boolean foundPatternCross(int[] stateCount) {
|
||||
float moduleSize = this.moduleSize;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (2.0f * Math.abs(moduleSize - stateCount[i]) >= moduleSize) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private float crossCheckVertical(int startI, int centerJ, int maxCount) {
|
||||
MonochromeBitmapSource image = this.image;
|
||||
|
||||
int maxI = image.getHeight();
|
||||
int[] stateCount = new int[3];
|
||||
int i = startI;
|
||||
while (i >= 0 && image.isBlack(centerJ, i) && stateCount[1] <= maxCount) {
|
||||
stateCount[1]++;
|
||||
i--;
|
||||
}
|
||||
if (i < 0 || stateCount[1] > maxCount) {
|
||||
return Float.NaN;
|
||||
}
|
||||
while (i >= 0 && !image.isBlack(centerJ, i) && stateCount[0] <= maxCount) {
|
||||
stateCount[0]++;
|
||||
i--;
|
||||
}
|
||||
if (stateCount[0] > maxCount) {
|
||||
return Float.NaN;
|
||||
}
|
||||
|
||||
i = startI + 1;
|
||||
while (i < maxI && image.isBlack(centerJ, i) &&
|
||||
stateCount[1] <= maxCount) {
|
||||
stateCount[1]++;
|
||||
i++;
|
||||
}
|
||||
if (i == maxI || stateCount[1] > maxCount) {
|
||||
return Float.NaN;
|
||||
}
|
||||
while (i < maxI && !image.isBlack(centerJ, i) &&
|
||||
stateCount[2] <= maxCount) {
|
||||
stateCount[2]++;
|
||||
i++;
|
||||
}
|
||||
if (stateCount[2] > maxCount) {
|
||||
return Float.NaN;
|
||||
}
|
||||
|
||||
return
|
||||
foundPatternCross(stateCount) ?
|
||||
centerFromEnd(stateCount, i) :
|
||||
Float.NaN;
|
||||
}
|
||||
|
||||
private AlignmentPattern handlePossibleCenter(int[] stateCount,
|
||||
int i,
|
||||
int j) {
|
||||
float centerJ = centerFromEnd(stateCount, j);
|
||||
float centerI = crossCheckVertical(i, (int) centerJ, 2 * stateCount[1]);
|
||||
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
|
||||
possibleCenters.addElement(new AlignmentPattern(centerJ, centerI, estimatedModuleSize));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
88
src/com/google/zxing/qrcode/detector/DefaultGridSampler.java
Normal file
88
src/com/google/zxing/qrcode/detector/DefaultGridSampler.java
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.detector;
|
||||
|
||||
import com.google.zxing.MonochromeBitmapSource;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
/**
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class DefaultGridSampler extends GridSampler {
|
||||
|
||||
protected BitMatrix sampleGrid(MonochromeBitmapSource image,
|
||||
FinderPattern topLeft,
|
||||
FinderPattern topRight,
|
||||
FinderPattern bottomLeft,
|
||||
AlignmentPattern alignmentPattern,
|
||||
int dimension) throws ReaderException {
|
||||
float bottomRightX;
|
||||
float bottomRightY;
|
||||
if (alignmentPattern != null) {
|
||||
bottomRightX = alignmentPattern.getX();
|
||||
bottomRightY = alignmentPattern.getY();
|
||||
} 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();
|
||||
}
|
||||
|
||||
float dimMinusThree = (float) dimension - 3.5f;
|
||||
JAIPerspectiveTransform transform = JAIPerspectiveTransform.getQuadToQuad(
|
||||
3.5f,
|
||||
3.5f,
|
||||
dimMinusThree,
|
||||
3.5f,
|
||||
3.5f,
|
||||
dimMinusThree,
|
||||
dimMinusThree - 3.0f,
|
||||
dimMinusThree - 3.0f,
|
||||
topLeft.getX(),
|
||||
topLeft.getY(),
|
||||
topRight.getX(),
|
||||
topRight.getY(),
|
||||
bottomLeft.getX(),
|
||||
bottomLeft.getY(),
|
||||
bottomRightX,
|
||||
bottomRightY);
|
||||
|
||||
BitMatrix bits = new BitMatrix(dimension);
|
||||
float[] points = new float[dimension << 1];
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
int max = points.length;
|
||||
float iValue = (float) i + 0.5f;
|
||||
for (int j = 0; j < max; j += 2) {
|
||||
points[j] = (float) j + 0.5f;
|
||||
points[j + 1] = iValue;
|
||||
}
|
||||
transform.transform(points);
|
||||
// Quick check to see if points transformed to something inside the image;
|
||||
// sufficent to check the endpoints
|
||||
checkEndpoint(image, points);
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
int offset = j << 1;
|
||||
if (image.isBlack((int) points[offset], (int) points[offset + 1])) {
|
||||
// Black(-ish) pixel
|
||||
bits.set(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
}
|
263
src/com/google/zxing/qrcode/detector/Detector.java
Normal file
263
src/com/google/zxing/qrcode/detector/Detector.java
Normal file
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.detector;
|
||||
|
||||
import com.google.zxing.MonochromeBitmapSource;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.decoder.Version;
|
||||
|
||||
/**
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class Detector {
|
||||
|
||||
private final MonochromeBitmapSource image;
|
||||
|
||||
public Detector(MonochromeBitmapSource image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public DetectorResult detect() throws ReaderException {
|
||||
|
||||
MonochromeBitmapSource image = this.image;
|
||||
|
||||
FinderPatternFinder finder = new FinderPatternFinder(image);
|
||||
FinderPatternInfo info = finder.find();
|
||||
|
||||
FinderPattern topLeft = info.getTopLeft();
|
||||
FinderPattern topRight = info.getTopRight();
|
||||
FinderPattern bottomLeft = info.getBottomLeft();
|
||||
|
||||
float moduleSize = calculateModuleSize(topLeft, topRight, bottomLeft);
|
||||
int dimension = computeDimension(topLeft, topRight, bottomLeft, moduleSize);
|
||||
Version provisionalVersion = Version.getProvisionalVersionForDimension(dimension);
|
||||
int modulesBetweenFPCenters = provisionalVersion.getDimensionForVersion() - 7;
|
||||
|
||||
// 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();
|
||||
|
||||
AlignmentPattern alignmentPattern = null;
|
||||
// Anything above version 1 has an alignment pattern
|
||||
if (provisionalVersion.getAlignmentPatternCenters().length > 0) {
|
||||
|
||||
// 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 (ReaderException de) {
|
||||
// try next round
|
||||
}
|
||||
}
|
||||
if (alignmentPattern == null) {
|
||||
throw new ReaderException("Could not find alignment pattern");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GridSampler sampler = GridSampler.getInstance();
|
||||
BitMatrix bits = sampler.sampleGrid(image,
|
||||
topLeft,
|
||||
topRight,
|
||||
bottomLeft,
|
||||
alignmentPattern,
|
||||
dimension);
|
||||
|
||||
/*
|
||||
try {
|
||||
BufferedImage outImage =
|
||||
new BufferedImage(dimension,
|
||||
dimension,
|
||||
BufferedImage.TYPE_BYTE_BINARY);
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
if (bits.get(i, j)) {
|
||||
outImage.setRGB(j, i, 0xFF000000);
|
||||
} else {
|
||||
outImage.setRGB(j, i, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImageIO.write(outImage, "PNG",
|
||||
new File("/home/srowen/out.png"));
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
*/
|
||||
|
||||
ResultPoint[] points;
|
||||
if (alignmentPattern == null) {
|
||||
points = new ResultPoint[] { bottomLeft, topLeft, topRight };
|
||||
} else {
|
||||
points = new ResultPoint[] { bottomLeft, topLeft, topRight, alignmentPattern };
|
||||
}
|
||||
return new DetectorResult(bits, points);
|
||||
}
|
||||
|
||||
private static int computeDimension(ResultPoint topLeft,
|
||||
ResultPoint topRight,
|
||||
ResultPoint bottomLeft,
|
||||
float moduleSize)
|
||||
throws ReaderException {
|
||||
int tltrCentersDimension =
|
||||
round(FinderPatternFinder.distance(topLeft, topRight) / moduleSize);
|
||||
int tlblCentersDimension =
|
||||
round(FinderPatternFinder.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 new ReaderException("Bad dimension: " + dimension);
|
||||
}
|
||||
return dimension;
|
||||
}
|
||||
|
||||
private float calculateModuleSize(ResultPoint topLeft,
|
||||
ResultPoint topRight,
|
||||
ResultPoint bottomLeft) {
|
||||
// Take the average
|
||||
return (calculateModuleSizeOneWay(topLeft, topRight) +
|
||||
calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0f;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (Float.isNaN(moduleSizeEst2)) {
|
||||
return moduleSizeEst1;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
private float sizeOfBlackWhiteBlackRunBothWays(int fromX, int fromY, int toX, int toY) {
|
||||
float result = sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY);
|
||||
result += sizeOfBlackWhiteBlackRun(fromX, fromY, fromX - (toX - fromX), fromY - (toY - fromY));
|
||||
return result - 1.0f; // -1 because we counted the middle pixel twice
|
||||
}
|
||||
|
||||
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 ystep = fromY < toY ? 1 : -1;
|
||||
int xstep = fromX < toX ? 1 : -1;
|
||||
int state = 0; // In black pixels, looking for white, first or second time
|
||||
for (int x = fromX, y = fromY; x != toX; x += xstep) {
|
||||
|
||||
int realX = steep ? y : x;
|
||||
int realY = steep ? x : y;
|
||||
if (state == 1) { // In white pixels, looking for black
|
||||
if (image.isBlack(realX, realY)) {
|
||||
state++;
|
||||
}
|
||||
} else {
|
||||
if (!image.isBlack(realX, realY)) {
|
||||
state++;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == 3) { // Found black, white, black, and stumbled back onto white; done
|
||||
int diffX = x - fromX;
|
||||
int diffY = y - fromY;
|
||||
return (float) Math.sqrt((double) (diffX * diffX + diffY * diffY));
|
||||
}
|
||||
error += dy;
|
||||
if (error > 0) {
|
||||
y += ystep;
|
||||
error -= dx;
|
||||
}
|
||||
}
|
||||
// Hmm, couldn't find all of what we wanted -- don't know
|
||||
return Float.NaN;
|
||||
}
|
||||
|
||||
private AlignmentPattern findAlignmentInRegion(float overallEstModuleSize,
|
||||
int estAlignmentX,
|
||||
int estAlignmentY,
|
||||
float allowanceFactor)
|
||||
throws ReaderException {
|
||||
// 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);
|
||||
int alignmentAreaTopY = Math.max(0, estAlignmentY - allowance);
|
||||
int alignmentAreaBottomY = Math.min(image.getHeight() - 1,
|
||||
estAlignmentY + allowance);
|
||||
|
||||
AlignmentPatternFinder alignmentFinder =
|
||||
new AlignmentPatternFinder(
|
||||
image,
|
||||
alignmentAreaLeftX,
|
||||
alignmentAreaTopY,
|
||||
alignmentAreaRightX - alignmentAreaLeftX,
|
||||
alignmentAreaBottomY - alignmentAreaTopY,
|
||||
overallEstModuleSize);
|
||||
return alignmentFinder.find();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends up being a bit faster than Math.round()
|
||||
*/
|
||||
private static int round(float d) {
|
||||
return (int) (d + 0.5f);
|
||||
}
|
||||
|
||||
}
|
47
src/com/google/zxing/qrcode/detector/DetectorResult.java
Normal file
47
src/com/google/zxing/qrcode/detector/DetectorResult.java
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.detector;
|
||||
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
/**
|
||||
* <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 srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class DetectorResult {
|
||||
|
||||
private final BitMatrix bits;
|
||||
private final ResultPoint[] points;
|
||||
|
||||
DetectorResult(BitMatrix bits, ResultPoint[] points) {
|
||||
this.bits = bits;
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
public BitMatrix getBits() {
|
||||
return bits;
|
||||
}
|
||||
|
||||
public ResultPoint[] getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
}
|
65
src/com/google/zxing/qrcode/detector/FinderPattern.java
Normal file
65
src/com/google/zxing/qrcode/detector/FinderPattern.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.detector;
|
||||
|
||||
import com.google.zxing.ResultPoint;
|
||||
|
||||
/**
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class FinderPattern implements ResultPoint {
|
||||
|
||||
private final float posX;
|
||||
private final float posY;
|
||||
private final float estimatedModuleSize;
|
||||
private int count;
|
||||
|
||||
FinderPattern(float posX, float posY, float estimatedModuleSize) {
|
||||
this.posX = posX;
|
||||
this.posY = posY;
|
||||
this.estimatedModuleSize = estimatedModuleSize;
|
||||
this.count = 1;
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
return posX;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return posY;
|
||||
}
|
||||
|
||||
float getEstimatedModuleSize() {
|
||||
return estimatedModuleSize;
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
void incrementCount() {
|
||||
this.count++;
|
||||
}
|
||||
|
||||
boolean aboutEquals(float moduleSize, float i, float j) {
|
||||
return Math.abs(i - posY) <= moduleSize &&
|
||||
Math.abs(j - posX) <= moduleSize &&
|
||||
(Math.abs(moduleSize - estimatedModuleSize) <= 1.0f ||
|
||||
Math.abs(moduleSize - estimatedModuleSize) / estimatedModuleSize <= 0.1f);
|
||||
}
|
||||
|
||||
}
|
474
src/com/google/zxing/qrcode/detector/FinderPatternFinder.java
Executable file
474
src/com/google/zxing/qrcode/detector/FinderPatternFinder.java
Executable file
|
@ -0,0 +1,474 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.detector;
|
||||
|
||||
import com.google.zxing.MonochromeBitmapSource;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitArray;
|
||||
import com.google.zxing.common.Collections;
|
||||
import com.google.zxing.common.Comparator;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* <p>This class is not thread-safe and should not be reused.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
final class FinderPatternFinder {
|
||||
|
||||
private static final int CENTER_QUORUM = 2;
|
||||
private static final int BIG_SKIP = 3;
|
||||
|
||||
private final MonochromeBitmapSource image;
|
||||
private final Vector possibleCenters;
|
||||
private boolean hasSkipped;
|
||||
|
||||
FinderPatternFinder(MonochromeBitmapSource image) {
|
||||
this.image = image;
|
||||
this.possibleCenters = new Vector(5);
|
||||
}
|
||||
|
||||
FinderPatternInfo find() throws ReaderException {
|
||||
int maxI = image.getHeight();
|
||||
int maxJ = image.getWidth();
|
||||
int[] stateCount = new int[5]; // looking for 1 1 3 1 1
|
||||
boolean done = false;
|
||||
// We can afford to examine every few lines until we've started finding
|
||||
// the patterns
|
||||
int iSkip = BIG_SKIP;
|
||||
for (int i = iSkip - 1; i < maxI && !done; i += iSkip) {
|
||||
BitArray luminanceRow = image.getBlackRow(i, null, 0, maxJ);
|
||||
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 (luminanceRow.get(j)) {
|
||||
// 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) {
|
||||
iSkip = 1; // Go back to examining each line
|
||||
if (hasSkipped) {
|
||||
done = haveMulitplyConfirmedCenters();
|
||||
} 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 {
|
||||
// Advance to next black pixel
|
||||
do {
|
||||
j++;
|
||||
} while (j < maxJ && !luminanceRow.get(j));
|
||||
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]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (foundPatternCross(stateCount)) {
|
||||
boolean confirmed = handlePossibleCenter(stateCount, i, maxJ);
|
||||
if (confirmed) {
|
||||
iSkip = stateCount[0];
|
||||
if (hasSkipped) {
|
||||
// Found a third one
|
||||
done = haveMulitplyConfirmedCenters();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FinderPattern[] patternInfo = selectBestPatterns();
|
||||
patternInfo = orderBestPatterns(patternInfo);
|
||||
float totalModuleSize = 0.0f;
|
||||
for (int i = 0; i < patternInfo.length; i++) {
|
||||
totalModuleSize += patternInfo[i].getEstimatedModuleSize();
|
||||
}
|
||||
|
||||
return new FinderPatternInfo(totalModuleSize / (float) patternInfo.length,
|
||||
patternInfo);
|
||||
}
|
||||
|
||||
private static float centerFromEnd(int[] stateCount, int end) {
|
||||
return (float) (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0f;
|
||||
}
|
||||
|
||||
private static boolean foundPatternCross(int[] stateCount) {
|
||||
int totalModuleSize = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (stateCount[i] == 0) {
|
||||
return false;
|
||||
}
|
||||
totalModuleSize += stateCount[i];
|
||||
}
|
||||
if (totalModuleSize < 7) {
|
||||
return false;
|
||||
}
|
||||
int moduleSize = totalModuleSize / 7;
|
||||
// Allow less than 50% deviance from 1-1-3-1-1 pattern
|
||||
return
|
||||
Math.abs(moduleSize - stateCount[0]) << 1 <= moduleSize &&
|
||||
Math.abs(moduleSize - stateCount[1]) << 1 <= moduleSize &&
|
||||
Math.abs(3 * moduleSize - stateCount[2]) << 1 <= 3 * moduleSize &&
|
||||
Math.abs(moduleSize - stateCount[3]) << 1 <= moduleSize &&
|
||||
Math.abs(moduleSize - stateCount[4]) << 1 <= moduleSize;
|
||||
}
|
||||
|
||||
private float crossCheckVertical(int startI, int centerJ, int maxCount) {
|
||||
MonochromeBitmapSource image = this.image;
|
||||
|
||||
int maxI = image.getHeight();
|
||||
int[] stateCount = new int[5];
|
||||
|
||||
int i = startI;
|
||||
while (i >= 0 && image.isBlack(centerJ, i)) {
|
||||
stateCount[2]++;
|
||||
i--;
|
||||
}
|
||||
if (i < 0) {
|
||||
return Float.NaN;
|
||||
}
|
||||
while (i >= 0 && !image.isBlack(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.isBlack(centerJ, i) && stateCount[0] <= maxCount) {
|
||||
stateCount[0]++;
|
||||
i--;
|
||||
}
|
||||
if (i < 0 || stateCount[0] > maxCount) {
|
||||
return Float.NaN;
|
||||
}
|
||||
|
||||
i = startI + 1;
|
||||
while (i < maxI && image.isBlack(centerJ, i)) {
|
||||
stateCount[2]++;
|
||||
i++;
|
||||
}
|
||||
if (i == maxI) {
|
||||
return Float.NaN;
|
||||
}
|
||||
while (i < maxI && !image.isBlack(centerJ, i) && stateCount[3] < maxCount) {
|
||||
stateCount[3]++;
|
||||
i++;
|
||||
}
|
||||
if (i == maxI || stateCount[3] >= maxCount) {
|
||||
return Float.NaN;
|
||||
}
|
||||
while (i < maxI && image.isBlack(centerJ, i) && stateCount[4] < maxCount) {
|
||||
stateCount[4]++;
|
||||
i++;
|
||||
}
|
||||
if (stateCount[4] >= maxCount) {
|
||||
return Float.NaN;
|
||||
}
|
||||
|
||||
return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : Float.NaN;
|
||||
}
|
||||
|
||||
private float crossCheckHorizontal(int startJ, int centerI, int maxCount) {
|
||||
MonochromeBitmapSource image = this.image;
|
||||
|
||||
int maxJ = image.getWidth();
|
||||
int[] stateCount = new int[5];
|
||||
|
||||
int j = startJ;
|
||||
while (j >= 0 && image.isBlack(j, centerI)) {
|
||||
stateCount[2]++;
|
||||
j--;
|
||||
}
|
||||
if (j < 0) {
|
||||
return Float.NaN;
|
||||
}
|
||||
while (j >= 0 && !image.isBlack(j, centerI) && stateCount[1] <= maxCount) {
|
||||
stateCount[1]++;
|
||||
j--;
|
||||
}
|
||||
// If already too many modules in this state or ran off the edge:
|
||||
if (j < 0 || stateCount[1] > maxCount) {
|
||||
return Float.NaN;
|
||||
}
|
||||
while (j >= 0 && image.isBlack(j, centerI) && stateCount[0] <= maxCount) {
|
||||
stateCount[0]++;
|
||||
j--;
|
||||
}
|
||||
if (j < 0 || stateCount[0] > maxCount) {
|
||||
return Float.NaN;
|
||||
}
|
||||
|
||||
j = startJ + 1;
|
||||
while (j < maxJ && image.isBlack(j, centerI)) {
|
||||
stateCount[2]++;
|
||||
j++;
|
||||
}
|
||||
if (j == maxJ) {
|
||||
return Float.NaN;
|
||||
}
|
||||
while (j < maxJ && !image.isBlack(j, centerI) && stateCount[3] < maxCount) {
|
||||
stateCount[3]++;
|
||||
j++;
|
||||
}
|
||||
if (j == maxJ || stateCount[3] >= maxCount) {
|
||||
return Float.NaN;
|
||||
}
|
||||
while (j < maxJ && image.isBlack(j, centerI) && stateCount[4] < maxCount) {
|
||||
stateCount[4]++;
|
||||
j++;
|
||||
}
|
||||
if (stateCount[4] >= maxCount) {
|
||||
return Float.NaN;
|
||||
}
|
||||
|
||||
return foundPatternCross(stateCount) ? centerFromEnd(stateCount, j) : Float.NaN;
|
||||
}
|
||||
|
||||
private boolean handlePossibleCenter(int[] stateCount,
|
||||
int i,
|
||||
int j) {
|
||||
float centerJ = centerFromEnd(stateCount, j);
|
||||
float centerI = crossCheckVertical(i, (int) centerJ, stateCount[2]);
|
||||
if (!Float.isNaN(centerI)) {
|
||||
// Re-cross check
|
||||
centerJ = crossCheckHorizontal((int) centerJ, (int) centerI, stateCount[2]);
|
||||
if (!Float.isNaN(centerJ)) {
|
||||
float estimatedModuleSize = (float) (stateCount[0] +
|
||||
stateCount[1] +
|
||||
stateCount[2] +
|
||||
stateCount[3] +
|
||||
stateCount[4]) / 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) {
|
||||
possibleCenters.addElement(
|
||||
new FinderPattern(centerJ, centerI, estimatedModuleSize));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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 first. Draw it out.
|
||||
hasSkipped = true;
|
||||
return (int) Math.abs(Math.abs(firstConfirmedCenter.getX() - center.getX()) -
|
||||
Math.abs(firstConfirmedCenter.getY() - center.getY()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private boolean haveMulitplyConfirmedCenters() {
|
||||
int count = 0;
|
||||
int max = possibleCenters.size();
|
||||
for (int i = 0; i < max; i++) {
|
||||
if (((FinderPattern) possibleCenters.elementAt(i)).getCount() >= CENTER_QUORUM) {
|
||||
if (++count == 3) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private FinderPattern[] selectBestPatterns() throws ReaderException {
|
||||
Collections.insertionSort(possibleCenters, new CenterComparator());
|
||||
int size = 0;
|
||||
int max = possibleCenters.size();
|
||||
while (size < max) {
|
||||
if (((FinderPattern) possibleCenters.elementAt(size)).getCount() < CENTER_QUORUM) {
|
||||
break;
|
||||
}
|
||||
size++;
|
||||
}
|
||||
|
||||
if (size < 3) {
|
||||
// Couldn't find enough finder patterns
|
||||
throw new ReaderException("Could not find three finder patterns");
|
||||
}
|
||||
|
||||
if (size == 3) {
|
||||
// Found just enough -- hope these are good!
|
||||
// toArray() is not available
|
||||
FinderPattern[] result = new FinderPattern[possibleCenters.size()];
|
||||
for (int i = 0; i < possibleCenters.size(); i++) {
|
||||
result[i] = (FinderPattern) possibleCenters.elementAt(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
possibleCenters.setSize(size);
|
||||
|
||||
// Hmm, multiple found. We need to pick the best three. Find the most
|
||||
// popular ones whose module size is nearest the average
|
||||
|
||||
float averageModuleSize = 0.0f;
|
||||
for (int i = 0; i < size; i++) {
|
||||
averageModuleSize += ((FinderPattern) possibleCenters.elementAt(i)).getEstimatedModuleSize();
|
||||
}
|
||||
averageModuleSize /= (float) size;
|
||||
|
||||
Collections.insertionSort(
|
||||
possibleCenters,
|
||||
new ClosestToAverageComparator(averageModuleSize));
|
||||
|
||||
//return confirmedCenters.subList(0, 3).toArray(new FinderPattern[3]);
|
||||
FinderPattern[] result = new FinderPattern[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
result[i] = (FinderPattern) possibleCenters.elementAt(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static FinderPattern[] orderBestPatterns(FinderPattern[] patterns) {
|
||||
|
||||
// Find distances between pattern centers
|
||||
float abDistance = distance(patterns[0], patterns[1]);
|
||||
float bcDistance = distance(patterns[1], patterns[2]);
|
||||
float acDistance = distance(patterns[0], patterns[2]);
|
||||
|
||||
FinderPattern topLeft;
|
||||
FinderPattern topRight;
|
||||
FinderPattern bottomLeft;
|
||||
// Assume one closest to other two is top left
|
||||
if (bcDistance >= abDistance && bcDistance >= acDistance) {
|
||||
topLeft = patterns[0];
|
||||
topRight = patterns[1]; // These two are guesses at the moment
|
||||
bottomLeft = patterns[2];
|
||||
} else if (acDistance >= bcDistance && acDistance >= abDistance) {
|
||||
topLeft = patterns[1];
|
||||
topRight = patterns[0];
|
||||
bottomLeft = patterns[2];
|
||||
} else {
|
||||
topLeft = patterns[2];
|
||||
topRight = patterns[0];
|
||||
bottomLeft = patterns[1];
|
||||
}
|
||||
|
||||
// Use cross product to figure out which of other1/2 is the bottom left
|
||||
// pattern. The vector "top-left -> bottom-left" x "top-left -> top-right"
|
||||
// should yield a vector with positive z component
|
||||
if ((bottomLeft.getY() - topLeft.getY()) * (topRight.getX() - topLeft.getX()) <
|
||||
(bottomLeft.getX() - topLeft.getX()) * (topRight.getY() - topLeft.getY())) {
|
||||
FinderPattern temp = topRight;
|
||||
topRight = bottomLeft;
|
||||
bottomLeft = temp;
|
||||
}
|
||||
|
||||
return new FinderPattern[]{bottomLeft, topLeft, topRight};
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
private static class CenterComparator implements Comparator {
|
||||
public int compare(Object center1, Object center2) {
|
||||
return ((FinderPattern) center2).getCount() - ((FinderPattern) center1).getCount();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClosestToAverageComparator implements Comparator {
|
||||
private float averageModuleSize;
|
||||
|
||||
private ClosestToAverageComparator(float averageModuleSize) {
|
||||
this.averageModuleSize = averageModuleSize;
|
||||
}
|
||||
|
||||
public int compare(Object center1, Object center2) {
|
||||
return
|
||||
Math.abs(((FinderPattern) center1).getEstimatedModuleSize() - averageModuleSize) <
|
||||
Math.abs(((FinderPattern) center2).getEstimatedModuleSize() - averageModuleSize) ?
|
||||
-1 :
|
||||
1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
53
src/com/google/zxing/qrcode/detector/FinderPatternInfo.java
Normal file
53
src/com/google/zxing/qrcode/detector/FinderPatternInfo.java
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.detector;
|
||||
|
||||
/**
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
final class FinderPatternInfo {
|
||||
|
||||
private final float rawEstimatedModuleSize;
|
||||
private final FinderPattern bottomLeft;
|
||||
private final FinderPattern topLeft;
|
||||
private final FinderPattern topRight;
|
||||
|
||||
FinderPatternInfo(float rawEstimatedModuleSize,
|
||||
FinderPattern[] patternCenters) {
|
||||
this.rawEstimatedModuleSize = rawEstimatedModuleSize;
|
||||
this.bottomLeft = patternCenters[0];
|
||||
this.topLeft = patternCenters[1];
|
||||
this.topRight = patternCenters[2];
|
||||
}
|
||||
|
||||
float getRawEstimatedModuleSize() {
|
||||
return rawEstimatedModuleSize;
|
||||
}
|
||||
|
||||
FinderPattern getBottomLeft() {
|
||||
return bottomLeft;
|
||||
}
|
||||
|
||||
FinderPattern getTopLeft() {
|
||||
return topLeft;
|
||||
}
|
||||
|
||||
FinderPattern getTopRight() {
|
||||
return topRight;
|
||||
}
|
||||
|
||||
}
|
88
src/com/google/zxing/qrcode/detector/GridSampler.java
Normal file
88
src/com/google/zxing/qrcode/detector/GridSampler.java
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.detector;
|
||||
|
||||
import com.google.zxing.MonochromeBitmapSource;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
/**
|
||||
* 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 #setGridSamplerClassName(String)}
|
||||
* with the name of a class which implements this interface.
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public abstract class GridSampler {
|
||||
|
||||
private static final String DEFAULT_IMPL_CLASS =
|
||||
"com.google.zxing.qrcode.detector.DefaultGridSampler";
|
||||
|
||||
private static String gridSamplerClassName = DEFAULT_IMPL_CLASS;
|
||||
private static GridSampler gridSampler;
|
||||
|
||||
public static void setGridSamplerClassName(String className) {
|
||||
if (className == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
gridSamplerClassName = className;
|
||||
}
|
||||
|
||||
public static GridSampler getInstance() {
|
||||
if (gridSampler == null) {
|
||||
try {
|
||||
Class gridSamplerClass = Class.forName(gridSamplerClassName);
|
||||
gridSampler = (GridSampler) gridSamplerClass.newInstance();
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
throw new RuntimeException(cnfe.toString());
|
||||
} catch (IllegalAccessException iae) {
|
||||
throw new RuntimeException(iae.toString());
|
||||
} catch (InstantiationException ie) {
|
||||
throw new RuntimeException(ie.toString());
|
||||
}
|
||||
}
|
||||
return gridSampler;
|
||||
}
|
||||
|
||||
protected abstract BitMatrix sampleGrid(MonochromeBitmapSource image,
|
||||
FinderPattern topLeft,
|
||||
FinderPattern topRight,
|
||||
FinderPattern bottomLeft,
|
||||
AlignmentPattern alignmentPattern,
|
||||
int dimension) throws ReaderException;
|
||||
|
||||
protected static void checkEndpoint(MonochromeBitmapSource image, float[] points)
|
||||
throws ReaderException {
|
||||
int x = (int) points[0];
|
||||
int y = (int) points[1];
|
||||
if (x < 0 || x >= image.getWidth() || y < 0 || y >= image.getHeight()) {
|
||||
throw new ReaderException("Transformed point out of bounds at " + x + ',' + y);
|
||||
}
|
||||
x = (int) points[points.length - 2];
|
||||
y = (int) points[points.length - 1];
|
||||
if (x < 0 || x >= image.getWidth() || y < 0 || y >= image.getHeight()) {
|
||||
throw new ReaderException("Transformed point out of bounds at " + x + ',' + y);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
package com.google.zxing.qrcode.detector;
|
||||
|
||||
/**
|
||||
* TODO need to reimplement this from scratch. This is derived from jai-core from Sun
|
||||
* and it is not clear we can redistribute this modification.
|
||||
*/
|
||||
final class JAIPerspectiveTransform {
|
||||
|
||||
private float m00, m01, m02, m10, m11, m12, m20, m21, m22;
|
||||
|
||||
JAIPerspectiveTransform() {
|
||||
m00 = m11 = m22 = 1.0f;
|
||||
m01 = m02 = m10 = m12 = m20 = m21 = 0.0f;
|
||||
}
|
||||
|
||||
private void makeAdjoint() {
|
||||
float m00p = m11 * m22 - m12 * m21;
|
||||
float m01p = m12 * m20 - m10 * m22; // flipped sign
|
||||
float m02p = m10 * m21 - m11 * m20;
|
||||
float m10p = m02 * m21 - m01 * m22; // flipped sign
|
||||
float m11p = m00 * m22 - m02 * m20;
|
||||
float m12p = m01 * m20 - m00 * m21; // flipped sign
|
||||
float m20p = m01 * m12 - m02 * m11;
|
||||
float m21p = m02 * m10 - m00 * m12; // flipped sign
|
||||
float m22p = m00 * m11 - m01 * m10;
|
||||
// Transpose and copy sub-determinants
|
||||
m00 = m00p;
|
||||
m01 = m10p;
|
||||
m02 = m20p;
|
||||
m10 = m01p;
|
||||
m11 = m11p;
|
||||
m12 = m21p;
|
||||
m20 = m02p;
|
||||
m21 = m12p;
|
||||
m22 = m22p;
|
||||
}
|
||||
|
||||
private static void getSquareToQuad(float x0, float y0,
|
||||
float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3,
|
||||
JAIPerspectiveTransform tx) {
|
||||
float dx3 = x0 - x1 + x2 - x3;
|
||||
float dy3 = y0 - y1 + y2 - y3;
|
||||
tx.m22 = 1.0f;
|
||||
if ((dx3 == 0.0f) && (dy3 == 0.0f)) { // to do: use tolerance
|
||||
tx.m00 = x1 - x0;
|
||||
tx.m01 = x2 - x1;
|
||||
tx.m02 = x0;
|
||||
tx.m10 = y1 - y0;
|
||||
tx.m11 = y2 - y1;
|
||||
tx.m12 = y0;
|
||||
tx.m20 = 0.0f;
|
||||
tx.m21 = 0.0f;
|
||||
} else {
|
||||
float dx1 = x1 - x2;
|
||||
float dy1 = y1 - y2;
|
||||
float dx2 = x3 - x2;
|
||||
float dy2 = y3 - y2;
|
||||
float invdet = 1.0f / (dx1 * dy2 - dx2 * dy1);
|
||||
tx.m20 = (dx3 * dy2 - dx2 * dy3) * invdet;
|
||||
tx.m21 = (dx1 * dy3 - dx3 * dy1) * invdet;
|
||||
tx.m00 = x1 - x0 + tx.m20 * x1;
|
||||
tx.m01 = x3 - x0 + tx.m21 * x3;
|
||||
tx.m02 = x0;
|
||||
tx.m10 = y1 - y0 + tx.m20 * y1;
|
||||
tx.m11 = y3 - y0 + tx.m21 * y3;
|
||||
tx.m12 = y0;
|
||||
}
|
||||
}
|
||||
|
||||
private static JAIPerspectiveTransform getSquareToQuad(float x0, float y0,
|
||||
float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3) {
|
||||
JAIPerspectiveTransform tx = new JAIPerspectiveTransform();
|
||||
getSquareToQuad(x0, y0, x1, y1, x2, y2, x3, y3, tx);
|
||||
return tx;
|
||||
}
|
||||
|
||||
private static JAIPerspectiveTransform getQuadToSquare(float x0, float y0,
|
||||
float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3) {
|
||||
JAIPerspectiveTransform tx = new JAIPerspectiveTransform();
|
||||
getSquareToQuad(x0, y0, x1, y1, x2, y2, x3, y3, tx);
|
||||
tx.makeAdjoint();
|
||||
return tx;
|
||||
}
|
||||
|
||||
static JAIPerspectiveTransform getQuadToQuad(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) {
|
||||
JAIPerspectiveTransform tx1 = getQuadToSquare(x0, y0, x1, y1, x2, y2, x3, y3);
|
||||
JAIPerspectiveTransform tx2 = getSquareToQuad(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);
|
||||
tx1.concatenate(tx2);
|
||||
return tx1;
|
||||
}
|
||||
|
||||
private void concatenate(JAIPerspectiveTransform Tx) {
|
||||
float m00p = m00 * Tx.m00 + m10 * Tx.m01 + m20 * Tx.m02;
|
||||
float m10p = m00 * Tx.m10 + m10 * Tx.m11 + m20 * Tx.m12;
|
||||
float m20p = m00 * Tx.m20 + m10 * Tx.m21 + m20 * Tx.m22;
|
||||
float m01p = m01 * Tx.m00 + m11 * Tx.m01 + m21 * Tx.m02;
|
||||
float m11p = m01 * Tx.m10 + m11 * Tx.m11 + m21 * Tx.m12;
|
||||
float m21p = m01 * Tx.m20 + m11 * Tx.m21 + m21 * Tx.m22;
|
||||
float m02p = m02 * Tx.m00 + m12 * Tx.m01 + m22 * Tx.m02;
|
||||
float m12p = m02 * Tx.m10 + m12 * Tx.m11 + m22 * Tx.m12;
|
||||
float m22p = m02 * Tx.m20 + m12 * Tx.m21 + m22 * Tx.m22;
|
||||
m00 = m00p;
|
||||
m10 = m10p;
|
||||
m20 = m20p;
|
||||
m01 = m01p;
|
||||
m11 = m11p;
|
||||
m21 = m21p;
|
||||
m02 = m02p;
|
||||
m12 = m12p;
|
||||
m22 = m22p;
|
||||
}
|
||||
|
||||
void transform(float[] points) {
|
||||
int max = points.length;
|
||||
for (int offset = 0; offset < max; offset += 2) {
|
||||
float x = points[offset];
|
||||
float y = points[offset + 1];
|
||||
float w = m20 * x + m21 * y + m22;
|
||||
if (w == 0.0f) {
|
||||
points[offset] = x;
|
||||
points[offset + 1] = y;
|
||||
} else {
|
||||
float oneOverW = 1.0f / w;
|
||||
points[offset] = (m00 * x + m01 * y + m02) * oneOverW;
|
||||
points[offset + 1] = (m10 * x + m11 * y + m12) * oneOverW;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
122
test/src/com/google/zxing/qrcode/decoder/DataMaskTestCase.java
Normal file
122
test/src/com/google/zxing/qrcode/decoder/DataMaskTestCase.java
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.decoder;
|
||||
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import junit.framework.TestCase;
|
||||
import junit.textui.TestRunner;
|
||||
|
||||
/**
|
||||
* @author Sean Owen
|
||||
*/
|
||||
public final class DataMaskTestCase extends TestCase {
|
||||
|
||||
public void testMask0() {
|
||||
testMaskAcrossDimensions(0, new MaskCondition() {
|
||||
public boolean isMasked(int i, int j) {
|
||||
return (i + j) % 2 == 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testMask1() {
|
||||
testMaskAcrossDimensions(1, new MaskCondition() {
|
||||
public boolean isMasked(int i, int j) {
|
||||
return i % 2 == 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testMask2() {
|
||||
testMaskAcrossDimensions(2, new MaskCondition() {
|
||||
public boolean isMasked(int i, int j) {
|
||||
return j % 3 == 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testMask3() {
|
||||
testMaskAcrossDimensions(3, new MaskCondition() {
|
||||
public boolean isMasked(int i, int j) {
|
||||
return (i + j) % 3 == 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testMask4() {
|
||||
testMaskAcrossDimensions(4, new MaskCondition() {
|
||||
public boolean isMasked(int i, int j) {
|
||||
return (i/2 + j/3) % 2 == 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testMask5() {
|
||||
testMaskAcrossDimensions(5, new MaskCondition() {
|
||||
public boolean isMasked(int i, int j) {
|
||||
return (i*j) % 2 + (i*j) % 3 == 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testMask6() {
|
||||
testMaskAcrossDimensions(6, new MaskCondition() {
|
||||
public boolean isMasked(int i, int j) {
|
||||
return ((i*j) % 2 + (i*j) % 3) % 2 == 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testMask7() {
|
||||
testMaskAcrossDimensions(7, new MaskCondition() {
|
||||
public boolean isMasked(int i, int j) {
|
||||
return ((i+j) % 2 + (i*j) % 3) % 2 == 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void testMaskAcrossDimensions(int reference,
|
||||
MaskCondition condition) {
|
||||
DataMask mask = DataMask.forReference(reference);
|
||||
for (int version = 1; version <= 40; version++) {
|
||||
int dimension = 17 + 4*version;
|
||||
testMask(mask, dimension, condition);
|
||||
}
|
||||
}
|
||||
|
||||
private void testMask(DataMask mask, int dimension, MaskCondition condition) {
|
||||
BitMatrix bits = new BitMatrix(dimension);
|
||||
mask.unmaskBitMatrix(bits.getBits(), dimension);
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
assertEquals(
|
||||
"(" + i + ',' + j + ')',
|
||||
condition.isMasked(i, j),
|
||||
bits.get(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static interface MaskCondition {
|
||||
boolean isMasked(int i, int j);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestRunner.run(new DataMaskTestCase());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.qrcode.decoder;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import junit.textui.TestRunner;
|
||||
|
||||
/**
|
||||
* @author Sean Owen
|
||||
*/
|
||||
public final class FormatInformationTestCase extends TestCase {
|
||||
|
||||
public void testBitsDiffering() {
|
||||
assertEquals(0, FormatInformation.numBitsDiffering(1, 1));
|
||||
assertEquals(1, FormatInformation.numBitsDiffering(0, 2));
|
||||
assertEquals(2, FormatInformation.numBitsDiffering(1, 2));
|
||||
assertEquals(32, FormatInformation.numBitsDiffering(-1,0));
|
||||
}
|
||||
|
||||
public void testDecode() {
|
||||
// Normal case
|
||||
FormatInformation expected =
|
||||
FormatInformation.decodeFormatInformation(0x2BED ^ 0x5412);
|
||||
assertEquals((byte) 0x07, expected.getDataMask());
|
||||
assertEquals(ErrorCorrectionLevel.Q, expected.getErrorCorrectionLevel());
|
||||
// where the code forgot the mask!
|
||||
assertEquals(expected, FormatInformation.decodeFormatInformation(0x2BED));
|
||||
// 1,2,3,4 bits difference
|
||||
assertEquals(expected, FormatInformation.decodeFormatInformation(
|
||||
0x2BEF ^ 0x5412));
|
||||
assertEquals(expected, FormatInformation.decodeFormatInformation(
|
||||
0x2BEE ^ 0x5412));
|
||||
assertEquals(expected, FormatInformation.decodeFormatInformation(
|
||||
0x2BEA ^ 0x5412));
|
||||
assertNull(FormatInformation.decodeFormatInformation(0x2BE2 ^ 0x5412));
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestRunner.run(new DataMaskTestCase());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue