mirror of
https://github.com/zxing/zxing.git
synced 2024-11-10 04:54:04 -08:00
Issue 1169 PDF417 error correction ground-work. Now actually computes the syndrome to detect errors but doesn't correct. This fixes misreads but takes out 1-2 images that accidentally passed as the erasure was inconsequential.
git-svn-id: https://zxing.googlecode.com/svn/trunk@2190 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
parent
8dce85e03c
commit
3dbad7baf6
|
@ -18,6 +18,7 @@ package com.google.zxing.pdf417;
|
|||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.ChecksumException;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.FormatException;
|
||||
import com.google.zxing.NotFoundException;
|
||||
|
@ -51,13 +52,13 @@ public final class PDF417Reader implements Reader {
|
|||
* @throws FormatException if a PDF417 cannot be decoded
|
||||
*/
|
||||
@Override
|
||||
public Result decode(BinaryBitmap image) throws NotFoundException, FormatException {
|
||||
public Result decode(BinaryBitmap image) throws NotFoundException, FormatException, ChecksumException {
|
||||
return decode(image, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result decode(BinaryBitmap image,
|
||||
Map<DecodeHintType,?> hints) throws NotFoundException, FormatException {
|
||||
public Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
|
||||
throws NotFoundException, FormatException, ChecksumException {
|
||||
DecoderResult decoderResult;
|
||||
ResultPoint[] points;
|
||||
if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
|
||||
|
|
|
@ -18,9 +18,9 @@ package com.google.zxing.pdf417.decoder;
|
|||
|
||||
import com.google.zxing.ChecksumException;
|
||||
import com.google.zxing.FormatException;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.common.DecoderResult;
|
||||
import com.google.zxing.pdf417.decoder.ec.ErrorCorrection;
|
||||
|
||||
/**
|
||||
* <p>The main class which implements PDF417 Code decoding -- as
|
||||
|
@ -32,11 +32,10 @@ public final class Decoder {
|
|||
|
||||
private static final int MAX_ERRORS = 3;
|
||||
private static final int MAX_EC_CODEWORDS = 512;
|
||||
//private final ReedSolomonDecoder rsDecoder;
|
||||
private final ErrorCorrection errorCorrection;
|
||||
|
||||
public Decoder() {
|
||||
// TODO MGMG
|
||||
//rsDecoder = new ReedSolomonDecoder();
|
||||
errorCorrection = new ErrorCorrection();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,9 +44,8 @@ public final class Decoder {
|
|||
*
|
||||
* @param image booleans representing white/black PDF417 modules
|
||||
* @return text and bytes encoded within the PDF417 Code
|
||||
* @throws NotFoundException if the PDF417 Code cannot be decoded
|
||||
*/
|
||||
public DecoderResult decode(boolean[][] image) throws FormatException {
|
||||
public DecoderResult decode(boolean[][] image) throws FormatException, ChecksumException {
|
||||
int dimension = image.length;
|
||||
BitMatrix bits = new BitMatrix(dimension);
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
|
@ -68,7 +66,7 @@ public final class Decoder {
|
|||
* @return text and bytes encoded within the PDF417 Code
|
||||
* @throws FormatException if the PDF417 Code cannot be decoded
|
||||
*/
|
||||
public DecoderResult decode(BitMatrix bits) throws FormatException {
|
||||
public DecoderResult decode(BitMatrix bits) throws FormatException, ChecksumException {
|
||||
// Construct a parser to read the data codewords and error-correction level
|
||||
BitMatrixParser parser = new BitMatrixParser(bits);
|
||||
int[] codewords = parser.readCodewords();
|
||||
|
@ -80,7 +78,7 @@ public final class Decoder {
|
|||
int numECCodewords = 1 << (ecLevel + 1);
|
||||
int[] erasures = parser.getErasures();
|
||||
|
||||
correctErrors(codewords, erasures, numECCodewords);
|
||||
correctErrors(codewords, erasures.length, numECCodewords);
|
||||
verifyCodewordCount(codewords, numECCodewords);
|
||||
|
||||
// Decode the codewords
|
||||
|
@ -92,7 +90,6 @@ public final class Decoder {
|
|||
*
|
||||
* @param codewords
|
||||
* @return an index to the first data codeword.
|
||||
* @throws FormatException
|
||||
*/
|
||||
private static void verifyCodewordCount(int[] codewords, int numECCodewords) throws FormatException {
|
||||
if (codewords.length < 4) {
|
||||
|
@ -119,31 +116,20 @@ public final class Decoder {
|
|||
|
||||
/**
|
||||
* <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
|
||||
* correct the errors in-place using Reed-Solomon error correction.</p>
|
||||
* correct the errors in-place.</p>
|
||||
*
|
||||
* @param codewords data and error correction codewords
|
||||
* @throws ChecksumException if error correction fails
|
||||
*/
|
||||
private static int correctErrors(int[] codewords,
|
||||
int[] erasures,
|
||||
int numECCodewords) throws FormatException {
|
||||
if (erasures.length > numECCodewords / 2 + MAX_ERRORS ||
|
||||
private void correctErrors(int[] codewords,
|
||||
int numErasures,
|
||||
int numECCodewords) throws ChecksumException {
|
||||
if (numErasures > numECCodewords / 2 + MAX_ERRORS ||
|
||||
numECCodewords < 0 || numECCodewords > MAX_EC_CODEWORDS) {
|
||||
// Too many errors or EC Codewords is corrupted
|
||||
throw FormatException.getFormatInstance();
|
||||
throw ChecksumException.getChecksumInstance();
|
||||
}
|
||||
// Try to correct the errors
|
||||
// TODO enable error correction
|
||||
int result = 0; // rsDecoder.correctErrors(codewords, numECCodewords);
|
||||
int numErasures = erasures.length;
|
||||
if (result > 0) {
|
||||
numErasures -= result;
|
||||
}
|
||||
if (numErasures > MAX_ERRORS) {
|
||||
// Still too many errors
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
return result;
|
||||
errorCorrection.decode(codewords, numECCodewords);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2012 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.pdf417.decoder.ec;
|
||||
|
||||
import com.google.zxing.ChecksumException;
|
||||
|
||||
/**
|
||||
* <p>Incomplete implementation of PDF417 error correction. For now, only detects errors.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @see com.google.zxing.common.reedsolomon.ReedSolomonDecoder
|
||||
*/
|
||||
public final class ErrorCorrection {
|
||||
|
||||
private final ModulusGF field;
|
||||
|
||||
public ErrorCorrection() {
|
||||
this.field = ModulusGF.PDF417_GF;
|
||||
}
|
||||
|
||||
public void decode(int[] received, int numECCodewords) throws ChecksumException {
|
||||
ModulusPoly poly = new ModulusPoly(field, received);
|
||||
int[] syndromeCoefficients = new int[numECCodewords];
|
||||
boolean noError = true;
|
||||
for (int i = 0; i < numECCodewords; i++) {
|
||||
int eval = poly.evaluateAt(field.exp(i + 1));
|
||||
syndromeCoefficients[syndromeCoefficients.length - 1 - i] = eval;
|
||||
if (eval != 0) {
|
||||
noError = false;
|
||||
}
|
||||
}
|
||||
if (!noError) {
|
||||
throw ChecksumException.getChecksumInstance();
|
||||
}
|
||||
// TODO actually correct errors!
|
||||
}
|
||||
|
||||
}
|
110
core/src/com/google/zxing/pdf417/decoder/ec/ModulusGF.java
Normal file
110
core/src/com/google/zxing/pdf417/decoder/ec/ModulusGF.java
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright 2012 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.pdf417.decoder.ec;
|
||||
|
||||
/**
|
||||
* <p>A field based on powers of a generator integer, modulo some modulus.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @see com.google.zxing.common.reedsolomon.GenericGF
|
||||
*/
|
||||
public final class ModulusGF {
|
||||
|
||||
public static final ModulusGF PDF417_GF = new ModulusGF(929, 3);
|
||||
|
||||
private final int[] expTable;
|
||||
private final int[] logTable;
|
||||
private final ModulusPoly zero;
|
||||
private final ModulusPoly one;
|
||||
private final int modulus;
|
||||
|
||||
public ModulusGF(int modulus, int generator) {
|
||||
this.modulus = modulus;
|
||||
expTable = new int[modulus];
|
||||
logTable = new int[modulus];
|
||||
int x = 1;
|
||||
for (int i = 0; i < modulus; i++) {
|
||||
expTable[i] = x;
|
||||
x = (x * generator) % modulus;
|
||||
}
|
||||
for (int i = 0; i < modulus-1; i++) {
|
||||
logTable[expTable[i]] = i;
|
||||
}
|
||||
// logTable[0] == 0 but this should never be used
|
||||
zero = new ModulusPoly(this, new int[]{0});
|
||||
one = new ModulusPoly(this, new int[]{1});
|
||||
}
|
||||
|
||||
|
||||
ModulusPoly getZero() {
|
||||
return zero;
|
||||
}
|
||||
|
||||
ModulusPoly getOne() {
|
||||
return one;
|
||||
}
|
||||
|
||||
ModulusPoly 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 ModulusPoly(this, coefficients);
|
||||
}
|
||||
|
||||
int add(int a, int b) {
|
||||
return (a + b) % modulus;
|
||||
}
|
||||
|
||||
int subtract(int a, int b) {
|
||||
return (modulus + a - b) % modulus;
|
||||
}
|
||||
|
||||
int exp(int a) {
|
||||
return expTable[a];
|
||||
}
|
||||
|
||||
int log(int a) {
|
||||
if (a == 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return logTable[a];
|
||||
}
|
||||
|
||||
int inverse(int a) {
|
||||
if (a == 0) {
|
||||
throw new ArithmeticException();
|
||||
}
|
||||
return expTable[modulus - logTable[a] - 1];
|
||||
}
|
||||
|
||||
int multiply(int a, int b) {
|
||||
if (a == 0 || b == 0) {
|
||||
return 0;
|
||||
}
|
||||
return expTable[(logTable[a] + logTable[b]) % (modulus - 1)];
|
||||
}
|
||||
|
||||
int getSize() {
|
||||
return modulus;
|
||||
}
|
||||
|
||||
}
|
260
core/src/com/google/zxing/pdf417/decoder/ec/ModulusPoly.java
Normal file
260
core/src/com/google/zxing/pdf417/decoder/ec/ModulusPoly.java
Normal file
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* Copyright 2012 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.pdf417.decoder.ec;
|
||||
|
||||
/**
|
||||
* @author Sean Owen
|
||||
* @see com.google.zxing.common.reedsolomon.GenericGFPoly
|
||||
*/
|
||||
final class ModulusPoly {
|
||||
|
||||
private final ModulusGF field;
|
||||
private final int[] coefficients;
|
||||
|
||||
ModulusPoly(ModulusGF field, int[] coefficients) {
|
||||
if (coefficients.length == 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.field = field;
|
||||
int coefficientsLength = coefficients.length;
|
||||
if (coefficientsLength > 1 && coefficients[0] == 0) {
|
||||
// Leading term must be non-zero for anything except the constant polynomial "0"
|
||||
int firstNonZero = 1;
|
||||
while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0) {
|
||||
firstNonZero++;
|
||||
}
|
||||
if (firstNonZero == coefficientsLength) {
|
||||
this.coefficients = field.getZero().coefficients;
|
||||
} else {
|
||||
this.coefficients = new int[coefficientsLength - firstNonZero];
|
||||
System.arraycopy(coefficients,
|
||||
firstNonZero,
|
||||
this.coefficients,
|
||||
0,
|
||||
this.coefficients.length);
|
||||
}
|
||||
} else {
|
||||
this.coefficients = coefficients;
|
||||
}
|
||||
}
|
||||
|
||||
int[] getCoefficients() {
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return degree of this polynomial
|
||||
*/
|
||||
int getDegree() {
|
||||
return coefficients.length - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true iff this polynomial is the monomial "0"
|
||||
*/
|
||||
boolean isZero() {
|
||||
return coefficients[0] == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return coefficient of x^degree term in this polynomial
|
||||
*/
|
||||
int getCoefficient(int degree) {
|
||||
return coefficients[coefficients.length - 1 - degree];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return evaluation of this polynomial at a given point
|
||||
*/
|
||||
int evaluateAt(int a) {
|
||||
if (a == 0) {
|
||||
// Just return the x^0 coefficient
|
||||
return getCoefficient(0);
|
||||
}
|
||||
int size = coefficients.length;
|
||||
if (a == 1) {
|
||||
// Just the sum of the coefficients
|
||||
int result = 0;
|
||||
for (int coefficient : coefficients) {
|
||||
result = field.add(result, coefficient);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
int result = coefficients[0];
|
||||
for (int i = 1; i < size; i++) {
|
||||
result = field.add(field.multiply(a, result), coefficients[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ModulusPoly add(ModulusPoly other) {
|
||||
if (!field.equals(other.field)) {
|
||||
throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field");
|
||||
}
|
||||
if (isZero()) {
|
||||
return other;
|
||||
}
|
||||
if (other.isZero()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
int[] smallerCoefficients = this.coefficients;
|
||||
int[] largerCoefficients = other.coefficients;
|
||||
if (smallerCoefficients.length > largerCoefficients.length) {
|
||||
int[] temp = smallerCoefficients;
|
||||
smallerCoefficients = largerCoefficients;
|
||||
largerCoefficients = temp;
|
||||
}
|
||||
int[] sumDiff = new int[largerCoefficients.length];
|
||||
int lengthDiff = largerCoefficients.length - smallerCoefficients.length;
|
||||
// Copy high-order terms only found in higher-degree polynomial's coefficients
|
||||
System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff);
|
||||
|
||||
for (int i = lengthDiff; i < largerCoefficients.length; i++) {
|
||||
sumDiff[i] = field.add(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
|
||||
}
|
||||
|
||||
return new ModulusPoly(field, sumDiff);
|
||||
}
|
||||
|
||||
ModulusPoly subtract(ModulusPoly other) {
|
||||
if (!field.equals(other.field)) {
|
||||
throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field");
|
||||
}
|
||||
if (other.isZero()) {
|
||||
return this;
|
||||
}
|
||||
return add(other.negative());
|
||||
}
|
||||
|
||||
ModulusPoly multiply(ModulusPoly other) {
|
||||
if (!field.equals(other.field)) {
|
||||
throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field");
|
||||
}
|
||||
if (isZero() || other.isZero()) {
|
||||
return field.getZero();
|
||||
}
|
||||
int[] aCoefficients = this.coefficients;
|
||||
int aLength = aCoefficients.length;
|
||||
int[] bCoefficients = other.coefficients;
|
||||
int bLength = bCoefficients.length;
|
||||
int[] product = new int[aLength + bLength - 1];
|
||||
for (int i = 0; i < aLength; i++) {
|
||||
int aCoeff = aCoefficients[i];
|
||||
for (int j = 0; j < bLength; j++) {
|
||||
product[i + j] = field.add(product[i + j], field.multiply(aCoeff, bCoefficients[j]));
|
||||
}
|
||||
}
|
||||
return new ModulusPoly(field, product);
|
||||
}
|
||||
|
||||
ModulusPoly negative() {
|
||||
int size = coefficients.length;
|
||||
int[] negativeCoefficients = new int[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
negativeCoefficients[i] = field.subtract(0, coefficients[i]);
|
||||
}
|
||||
return new ModulusPoly(field, negativeCoefficients);
|
||||
}
|
||||
|
||||
ModulusPoly multiply(int scalar) {
|
||||
if (scalar == 0) {
|
||||
return field.getZero();
|
||||
}
|
||||
if (scalar == 1) {
|
||||
return this;
|
||||
}
|
||||
int size = coefficients.length;
|
||||
int[] product = new int[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
product[i] = field.multiply(coefficients[i], scalar);
|
||||
}
|
||||
return new ModulusPoly(field, product);
|
||||
}
|
||||
|
||||
ModulusPoly multiplyByMonomial(int degree, int coefficient) {
|
||||
if (degree < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (coefficient == 0) {
|
||||
return field.getZero();
|
||||
}
|
||||
int size = coefficients.length;
|
||||
int[] product = new int[size + degree];
|
||||
for (int i = 0; i < size; i++) {
|
||||
product[i] = field.multiply(coefficients[i], coefficient);
|
||||
}
|
||||
return new ModulusPoly(field, product);
|
||||
}
|
||||
|
||||
ModulusPoly[] divide(ModulusPoly other) {
|
||||
if (!field.equals(other.field)) {
|
||||
throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field");
|
||||
}
|
||||
if (other.isZero()) {
|
||||
throw new IllegalArgumentException("Divide by 0");
|
||||
}
|
||||
|
||||
ModulusPoly quotient = field.getZero();
|
||||
ModulusPoly remainder = this;
|
||||
|
||||
int denominatorLeadingTerm = other.getCoefficient(other.getDegree());
|
||||
int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm);
|
||||
|
||||
while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) {
|
||||
int degreeDifference = remainder.getDegree() - other.getDegree();
|
||||
int scale = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm);
|
||||
ModulusPoly term = other.multiplyByMonomial(degreeDifference, scale);
|
||||
ModulusPoly iterationQuotient = field.buildMonomial(degreeDifference, scale);
|
||||
quotient = quotient.add(iterationQuotient);
|
||||
remainder = remainder.subtract(term);
|
||||
}
|
||||
|
||||
return new ModulusPoly[] { quotient, remainder };
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder(8 * getDegree());
|
||||
for (int degree = getDegree(); degree >= 0; degree--) {
|
||||
int coefficient = getCoefficient(degree);
|
||||
if (coefficient != 0) {
|
||||
if (coefficient < 0) {
|
||||
result.append(" - ");
|
||||
coefficient = -coefficient;
|
||||
} else {
|
||||
if (result.length() > 0) {
|
||||
result.append(" + ");
|
||||
}
|
||||
}
|
||||
if (degree == 0 || coefficient != 1) {
|
||||
result.append(coefficient);
|
||||
}
|
||||
if (degree != 0) {
|
||||
if (degree == 1) {
|
||||
result.append('x');
|
||||
} else {
|
||||
result.append("x^");
|
||||
result.append(degree);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -29,8 +29,8 @@ public final class PDF417BlackBox2TestCase extends AbstractBlackBoxTestCase {
|
|||
|
||||
public PDF417BlackBox2TestCase() {
|
||||
super("test/data/blackbox/pdf417-2", new MultiFormatReader(), BarcodeFormat.PDF_417);
|
||||
addTest(15, 15, 3, 3, 0.0f);
|
||||
addTest(14, 14, 1, 1, 180.0f);
|
||||
addTest(11, 11, 0, 0, 0.0f);
|
||||
addTest(13, 13, 0, 0, 180.0f);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue