git-svn-id: https://zxing.googlecode.com/svn/trunk@2 59b500cc-1b3d-0410-9834-0bbf25fbcc57

This commit is contained in:
srowen 2007-10-23 19:08:10 +00:00
parent 3df68d7105
commit 6eee886034
41 changed files with 4769 additions and 0 deletions

30
build.xml Normal file
View 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>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View file

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

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

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

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

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

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

View 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";
}
}

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

View file

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

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

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

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

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

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

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

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

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

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

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

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

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

View file

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

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

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