Add ResultMetadataType.ERRORS_CORRECTED and ERASURES_CORRECTED (#1657)

* Add ResultMetadataType.ERRORS_CORRECTED and ERASURES_CORRECTED (2D barcodes)

* Maintain existing constructor on AztecDetectorResult

* Maintain existing ReedSolomonDecoder.decode method
This commit is contained in:
Daniel Gredler 2023-06-25 18:30:21 -04:00 committed by GitHub
parent 57ed027468
commit 282f5ba726
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 179 additions and 53 deletions

View file

@ -55,6 +55,18 @@ public enum ResultMetadataType {
*/ */
ERROR_CORRECTION_LEVEL, ERROR_CORRECTION_LEVEL,
/**
* The number of errors corrected. If applicable, maps to an {@link Integer} of value
* greater than or equal to zero.
*/
ERRORS_CORRECTED,
/**
* The number of erasures corrected. If applicable, maps to an {@link Integer} of value
* greater than or equal to zero.
*/
ERASURES_CORRECTED,
/** /**
* For some periodicals, indicates the issue number as an {@link Integer}. * For some periodicals, indicates the issue number as an {@link Integer}.
*/ */
@ -73,12 +85,12 @@ public enum ResultMetadataType {
POSSIBLE_COUNTRY, POSSIBLE_COUNTRY,
/** /**
* For some products, the extension text * For some products, the extension text.
*/ */
UPC_EAN_EXTENSION, UPC_EAN_EXTENSION,
/** /**
* PDF417-specific metadata * PDF417-specific metadata.
*/ */
PDF417_EXTRA_METADATA, PDF417_EXTRA_METADATA,

View file

@ -31,16 +31,27 @@ public final class AztecDetectorResult extends DetectorResult {
private final boolean compact; private final boolean compact;
private final int nbDatablocks; private final int nbDatablocks;
private final int nbLayers; private final int nbLayers;
private final int errorsCorrected;
public AztecDetectorResult(BitMatrix bits, public AztecDetectorResult(BitMatrix bits,
ResultPoint[] points, ResultPoint[] points,
boolean compact, boolean compact,
int nbDatablocks, int nbDatablocks,
int nbLayers) { int nbLayers) {
this(bits, points, compact, nbDatablocks, nbLayers, 0);
}
public AztecDetectorResult(BitMatrix bits,
ResultPoint[] points,
boolean compact,
int nbDatablocks,
int nbLayers,
int errorsCorrected) {
super(bits, points); super(bits, points);
this.compact = compact; this.compact = compact;
this.nbDatablocks = nbDatablocks; this.nbDatablocks = nbDatablocks;
this.nbLayers = nbLayers; this.nbLayers = nbLayers;
this.errorsCorrected = errorsCorrected;
} }
public int getNbLayers() { public int getNbLayers() {
@ -55,4 +66,7 @@ public final class AztecDetectorResult extends DetectorResult {
return compact; return compact;
} }
public int getErrorsCorrected() {
return errorsCorrected;
}
} }

View file

@ -61,9 +61,11 @@ public final class AztecReader implements Reader {
Detector detector = new Detector(image.getBlackMatrix()); Detector detector = new Detector(image.getBlackMatrix());
ResultPoint[] points = null; ResultPoint[] points = null;
DecoderResult decoderResult = null; DecoderResult decoderResult = null;
int errorsCorrected = 0;
try { try {
AztecDetectorResult detectorResult = detector.detect(false); AztecDetectorResult detectorResult = detector.detect(false);
points = detectorResult.getPoints(); points = detectorResult.getPoints();
errorsCorrected = detectorResult.getErrorsCorrected();
decoderResult = new Decoder().decode(detectorResult); decoderResult = new Decoder().decode(detectorResult);
} catch (NotFoundException e) { } catch (NotFoundException e) {
notFoundException = e; notFoundException = e;
@ -74,6 +76,7 @@ public final class AztecReader implements Reader {
try { try {
AztecDetectorResult detectorResult = detector.detect(true); AztecDetectorResult detectorResult = detector.detect(true);
points = detectorResult.getPoints(); points = detectorResult.getPoints();
errorsCorrected = detectorResult.getErrorsCorrected();
decoderResult = new Decoder().decode(detectorResult); decoderResult = new Decoder().decode(detectorResult);
} catch (NotFoundException | FormatException e) { } catch (NotFoundException | FormatException e) {
if (notFoundException != null) { if (notFoundException != null) {
@ -110,6 +113,8 @@ public final class AztecReader implements Reader {
if (ecLevel != null) { if (ecLevel != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel); result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
} }
errorsCorrected += decoderResult.getErrorsCorrected();
result.putMetadata(ResultMetadataType.ERRORS_CORRECTED, errorsCorrected);
result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]z" + decoderResult.getSymbologyModifier()); result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]z" + decoderResult.getSymbologyModifier());
return result; return result;

View file

@ -87,6 +87,7 @@ public final class Decoder {
DecoderResult decoderResult = DecoderResult decoderResult =
new DecoderResult(rawBytes, result, null, String.format("%d%%", correctedBits.ecLevel)); new DecoderResult(rawBytes, result, null, String.format("%d%%", correctedBits.ecLevel));
decoderResult.setNumBits(correctedBits.correctBits.length); decoderResult.setNumBits(correctedBits.correctBits.length);
decoderResult.setErrorsCorrected(correctedBits.errorsCorrected);
return decoderResult; return decoderResult;
} }
@ -264,10 +265,12 @@ public final class Decoder {
static final class CorrectedBitsResult { static final class CorrectedBitsResult {
private final boolean[] correctBits; private final boolean[] correctBits;
private final int errorsCorrected;
private final int ecLevel; private final int ecLevel;
CorrectedBitsResult(boolean[] correctBits, int ecLevel) { CorrectedBitsResult(boolean[] correctBits, int errorsCorrected, int ecLevel) {
this.correctBits = correctBits; this.correctBits = correctBits;
this.errorsCorrected = errorsCorrected;
this.ecLevel = ecLevel; this.ecLevel = ecLevel;
} }
} }
@ -308,9 +311,10 @@ public final class Decoder {
dataWords[i] = readCode(rawbits, offset, codewordSize); dataWords[i] = readCode(rawbits, offset, codewordSize);
} }
int errorsCorrected = 0;
try { try {
ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(gf); ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(gf);
rsDecoder.decode(dataWords, numCodewords - numDataCodewords); errorsCorrected = rsDecoder.decodeWithECCount(dataWords, numCodewords - numDataCodewords);
} catch (ReedSolomonException ex) { } catch (ReedSolomonException ex) {
throw FormatException.getFormatInstance(ex); throw FormatException.getFormatInstance(ex);
} }
@ -343,7 +347,8 @@ public final class Decoder {
} }
} }
return new CorrectedBitsResult(correctedBits, 100 * (numCodewords - numDataCodewords) / numCodewords); int ecLevel = 100 * (numCodewords - numDataCodewords) / numCodewords;
return new CorrectedBitsResult(correctedBits, errorsCorrected, ecLevel);
} }
/** /**

View file

@ -82,7 +82,7 @@ public final class Detector {
} }
// 3. Get the size of the matrix and other parameters from the bull's eye // 3. Get the size of the matrix and other parameters from the bull's eye
extractParameters(bullsEyeCorners); int errorsCorrected = extractParameters(bullsEyeCorners);
// 4. Sample the grid // 4. Sample the grid
BitMatrix bits = sampleGrid(image, BitMatrix bits = sampleGrid(image,
@ -94,16 +94,17 @@ public final class Detector {
// 5. Get the corners of the matrix. // 5. Get the corners of the matrix.
ResultPoint[] corners = getMatrixCornerPoints(bullsEyeCorners); ResultPoint[] corners = getMatrixCornerPoints(bullsEyeCorners);
return new AztecDetectorResult(bits, corners, compact, nbDataBlocks, nbLayers); return new AztecDetectorResult(bits, corners, compact, nbDataBlocks, nbLayers, errorsCorrected);
} }
/** /**
* Extracts the number of data layers and data blocks from the layer around the bull's eye. * Extracts the number of data layers and data blocks from the layer around the bull's eye.
* *
* @param bullsEyeCorners the array of bull's eye corners * @param bullsEyeCorners the array of bull's eye corners
* @return the number of errors corrected during parameter extraction
* @throws NotFoundException in case of too many errors or invalid parameters * @throws NotFoundException in case of too many errors or invalid parameters
*/ */
private void extractParameters(ResultPoint[] bullsEyeCorners) throws NotFoundException { private int extractParameters(ResultPoint[] bullsEyeCorners) throws NotFoundException {
if (!isValid(bullsEyeCorners[0]) || !isValid(bullsEyeCorners[1]) || if (!isValid(bullsEyeCorners[0]) || !isValid(bullsEyeCorners[1]) ||
!isValid(bullsEyeCorners[2]) || !isValid(bullsEyeCorners[3])) { !isValid(bullsEyeCorners[2]) || !isValid(bullsEyeCorners[3])) {
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
@ -140,7 +141,8 @@ public final class Detector {
// Corrects parameter data using RS. Returns just the data portion // Corrects parameter data using RS. Returns just the data portion
// without the error correction. // without the error correction.
int correctedData = getCorrectedParameterData(parameterData, compact); CorrectedParameter correctedParam = getCorrectedParameterData(parameterData, compact);
int correctedData = correctedParam.getData();
if (compact) { if (compact) {
// 8 bits: 2 bits layers and 6 bits data blocks // 8 bits: 2 bits layers and 6 bits data blocks
@ -151,6 +153,8 @@ public final class Detector {
nbLayers = (correctedData >> 11) + 1; nbLayers = (correctedData >> 11) + 1;
nbDataBlocks = (correctedData & 0x7FF) + 1; nbDataBlocks = (correctedData & 0x7FF) + 1;
} }
return correctedParam.getErrorsCorrected();
} }
private static int getRotation(int[] sides, int length) throws NotFoundException { private static int getRotation(int[] sides, int length) throws NotFoundException {
@ -189,9 +193,11 @@ public final class Detector {
* *
* @param parameterData parameter bits * @param parameterData parameter bits
* @param compact true if this is a compact Aztec code * @param compact true if this is a compact Aztec code
* @return the corrected parameter
* @throws NotFoundException if the array contains too many errors * @throws NotFoundException if the array contains too many errors
*/ */
private static int getCorrectedParameterData(long parameterData, boolean compact) throws NotFoundException { private static CorrectedParameter getCorrectedParameterData(long parameterData,
boolean compact) throws NotFoundException {
int numCodewords; int numCodewords;
int numDataCodewords; int numDataCodewords;
@ -209,18 +215,21 @@ public final class Detector {
parameterWords[i] = (int) parameterData & 0xF; parameterWords[i] = (int) parameterData & 0xF;
parameterData >>= 4; parameterData >>= 4;
} }
int errorsCorrected = 0;
try { try {
ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(GenericGF.AZTEC_PARAM); ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(GenericGF.AZTEC_PARAM);
rsDecoder.decode(parameterWords, numECCodewords); errorsCorrected = rsDecoder.decodeWithECCount(parameterWords, numECCodewords);
} catch (ReedSolomonException ignored) { } catch (ReedSolomonException ignored) {
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
// Toss the error correction. Just return the data as an integer // Toss the error correction. Just return the data as an integer
int result = 0; int result = 0;
for (int i = 0; i < numDataCodewords; i++) { for (int i = 0; i < numDataCodewords; i++) {
result = (result << 4) + parameterWords[i]; result = (result << 4) + parameterWords[i];
} }
return result; return new CorrectedParameter(result, errorsCorrected);
} }
/** /**
@ -600,4 +609,22 @@ public final class Detector {
return "<" + x + ' ' + y + '>'; return "<" + x + ' ' + y + '>';
} }
} }
static final class CorrectedParameter {
private final int data;
private final int errorsCorrected;
CorrectedParameter(int data, int errorsCorrected) {
this.data = data;
this.errorsCorrected = errorsCorrected;
}
int getData() {
return data;
}
int getErrorsCorrected() {
return errorsCorrected;
}
}
} }

View file

@ -56,6 +56,20 @@ public final class ReedSolomonDecoder {
* @throws ReedSolomonException if decoding fails for any reason * @throws ReedSolomonException if decoding fails for any reason
*/ */
public void decode(int[] received, int twoS) throws ReedSolomonException { public void decode(int[] received, int twoS) throws ReedSolomonException {
decodeWithECCount(received, twoS);
}
/**
* <p>Decodes given set of received codewords, which include both data and error-correction
* codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place,
* in the input.</p>
*
* @param received data and error-correction codewords
* @param twoS number of error-correction codewords available
* @return the number of errors corrected
* @throws ReedSolomonException if decoding fails for any reason
*/
public int decodeWithECCount(int[] received, int twoS) throws ReedSolomonException {
GenericGFPoly poly = new GenericGFPoly(field, received); GenericGFPoly poly = new GenericGFPoly(field, received);
int[] syndromeCoefficients = new int[twoS]; int[] syndromeCoefficients = new int[twoS];
boolean noError = true; boolean noError = true;
@ -67,7 +81,7 @@ public final class ReedSolomonDecoder {
} }
} }
if (noError) { if (noError) {
return; return 0;
} }
GenericGFPoly syndrome = new GenericGFPoly(field, syndromeCoefficients); GenericGFPoly syndrome = new GenericGFPoly(field, syndromeCoefficients);
GenericGFPoly[] sigmaOmega = GenericGFPoly[] sigmaOmega =
@ -83,6 +97,7 @@ public final class ReedSolomonDecoder {
} }
received[position] = GenericGF.addOrSubtract(received[position], errorMagnitudes[i]); received[position] = GenericGF.addOrSubtract(received[position], errorMagnitudes[i]);
} }
return errorLocations.length;
} }
private GenericGFPoly[] runEuclideanAlgorithm(GenericGFPoly a, GenericGFPoly b, int R) private GenericGFPoly[] runEuclideanAlgorithm(GenericGFPoly a, GenericGFPoly b, int R)

View file

@ -83,6 +83,7 @@ public final class DataMatrixReader implements Reader {
if (ecLevel != null) { if (ecLevel != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel); result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
} }
result.putMetadata(ResultMetadataType.ERRORS_CORRECTED, decoderResult.getErrorsCorrected());
result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]d" + decoderResult.getSymbologyModifier()); result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]d" + decoderResult.getSymbologyModifier());
return result; return result;
} }

View file

@ -78,13 +78,14 @@ public final class Decoder {
} }
byte[] resultBytes = new byte[totalBytes]; byte[] resultBytes = new byte[totalBytes];
int errorsCorrected = 0;
int dataBlocksCount = dataBlocks.length; int dataBlocksCount = dataBlocks.length;
// Error-correct and copy data blocks together into a stream of bytes // Error-correct and copy data blocks together into a stream of bytes
for (int j = 0; j < dataBlocksCount; j++) { for (int j = 0; j < dataBlocksCount; j++) {
DataBlock dataBlock = dataBlocks[j]; DataBlock dataBlock = dataBlocks[j];
byte[] codewordBytes = dataBlock.getCodewords(); byte[] codewordBytes = dataBlock.getCodewords();
int numDataCodewords = dataBlock.getNumDataCodewords(); int numDataCodewords = dataBlock.getNumDataCodewords();
correctErrors(codewordBytes, numDataCodewords); errorsCorrected += correctErrors(codewordBytes, numDataCodewords);
for (int i = 0; i < numDataCodewords; i++) { for (int i = 0; i < numDataCodewords; i++) {
// De-interlace data blocks. // De-interlace data blocks.
resultBytes[i * dataBlocksCount + j] = codewordBytes[i]; resultBytes[i * dataBlocksCount + j] = codewordBytes[i];
@ -92,7 +93,9 @@ public final class Decoder {
} }
// Decode the contents of that stream of bytes // Decode the contents of that stream of bytes
return DecodedBitStreamParser.decode(resultBytes); DecoderResult result = DecodedBitStreamParser.decode(resultBytes);
result.setErrorsCorrected(errorsCorrected);
return result;
} }
/** /**
@ -101,17 +104,19 @@ public final class Decoder {
* *
* @param codewordBytes data and error correction codewords * @param codewordBytes data and error correction codewords
* @param numDataCodewords number of codewords that are data bytes * @param numDataCodewords number of codewords that are data bytes
* @return the number of errors corrected
* @throws ChecksumException if error correction fails * @throws ChecksumException if error correction fails
*/ */
private void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException { private int correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException {
int numCodewords = codewordBytes.length; int numCodewords = codewordBytes.length;
// First read into an array of ints // First read into an array of ints
int[] codewordsInts = new int[numCodewords]; int[] codewordsInts = new int[numCodewords];
for (int i = 0; i < numCodewords; i++) { for (int i = 0; i < numCodewords; i++) {
codewordsInts[i] = codewordBytes[i] & 0xFF; codewordsInts[i] = codewordBytes[i] & 0xFF;
} }
int errorsCorrected = 0;
try { try {
rsDecoder.decode(codewordsInts, codewordBytes.length - numDataCodewords); errorsCorrected = rsDecoder.decodeWithECCount(codewordsInts, codewordBytes.length - numDataCodewords);
} catch (ReedSolomonException ignored) { } catch (ReedSolomonException ignored) {
throw ChecksumException.getChecksumInstance(); throw ChecksumException.getChecksumInstance();
} }
@ -120,6 +125,7 @@ public final class Decoder {
for (int i = 0; i < numDataCodewords; i++) { for (int i = 0; i < numDataCodewords; i++) {
codewordBytes[i] = (byte) codewordsInts[i]; codewordBytes[i] = (byte) codewordsInts[i];
} }
return errorsCorrected;
} }
} }

View file

@ -64,7 +64,7 @@ public final class MaxiCodeReader implements Reader {
BitMatrix bits = extractPureBits(image.getBlackMatrix()); BitMatrix bits = extractPureBits(image.getBlackMatrix());
DecoderResult decoderResult = decoder.decode(bits, hints); DecoderResult decoderResult = decoder.decode(bits, hints);
Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), NO_POINTS, BarcodeFormat.MAXICODE); Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), NO_POINTS, BarcodeFormat.MAXICODE);
result.putMetadata(ResultMetadataType.ERRORS_CORRECTED, decoderResult.getErrorsCorrected());
String ecLevel = decoderResult.getECLevel(); String ecLevel = decoderResult.getECLevel();
if (ecLevel != null) { if (ecLevel != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel); result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);

View file

@ -54,20 +54,20 @@ public final class Decoder {
BitMatrixParser parser = new BitMatrixParser(bits); BitMatrixParser parser = new BitMatrixParser(bits);
byte[] codewords = parser.readCodewords(); byte[] codewords = parser.readCodewords();
correctErrors(codewords, 0, 10, 10, ALL); int errorsCorrected = correctErrors(codewords, 0, 10, 10, ALL);
int mode = codewords[0] & 0x0F; int mode = codewords[0] & 0x0F;
byte[] datawords; byte[] datawords;
switch (mode) { switch (mode) {
case 2: case 2:
case 3: case 3:
case 4: case 4:
correctErrors(codewords, 20, 84, 40, EVEN); errorsCorrected += correctErrors(codewords, 20, 84, 40, EVEN);
correctErrors(codewords, 20, 84, 40, ODD); errorsCorrected += correctErrors(codewords, 20, 84, 40, ODD);
datawords = new byte[94]; datawords = new byte[94];
break; break;
case 5: case 5:
correctErrors(codewords, 20, 68, 56, EVEN); errorsCorrected += correctErrors(codewords, 20, 68, 56, EVEN);
correctErrors(codewords, 20, 68, 56, ODD); errorsCorrected += correctErrors(codewords, 20, 68, 56, ODD);
datawords = new byte[78]; datawords = new byte[78];
break; break;
default: default:
@ -77,10 +77,12 @@ public final class Decoder {
System.arraycopy(codewords, 0, datawords, 0, 10); System.arraycopy(codewords, 0, datawords, 0, 10);
System.arraycopy(codewords, 20, datawords, 10, datawords.length - 10); System.arraycopy(codewords, 20, datawords, 10, datawords.length - 10);
return DecodedBitStreamParser.decode(datawords, mode); DecoderResult result = DecodedBitStreamParser.decode(datawords, mode);
result.setErrorsCorrected(errorsCorrected);
return result;
} }
private void correctErrors(byte[] codewordBytes, private int correctErrors(byte[] codewordBytes,
int start, int start,
int dataCodewords, int dataCodewords,
int ecCodewords, int ecCodewords,
@ -97,8 +99,9 @@ public final class Decoder {
codewordsInts[i / divisor] = codewordBytes[i + start] & 0xFF; codewordsInts[i / divisor] = codewordBytes[i + start] & 0xFF;
} }
} }
int errorsCorrected = 0;
try { try {
rsDecoder.decode(codewordsInts, ecCodewords / divisor); errorsCorrected = rsDecoder.decodeWithECCount(codewordsInts, ecCodewords / divisor);
} catch (ReedSolomonException ignored) { } catch (ReedSolomonException ignored) {
throw ChecksumException.getChecksumInstance(); throw ChecksumException.getChecksumInstance();
} }
@ -109,6 +112,7 @@ public final class Decoder {
codewordBytes[i + start] = (byte) codewordsInts[i / divisor]; codewordBytes[i + start] = (byte) codewordsInts[i / divisor];
} }
} }
return errorsCorrected;
} }
} }

View file

@ -90,6 +90,8 @@ public final class PDF417Reader implements Reader, MultipleBarcodeReader {
points[6], points[7], getMinCodewordWidth(points), getMaxCodewordWidth(points)); points[6], points[7], getMinCodewordWidth(points), getMaxCodewordWidth(points));
Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.PDF_417); Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.PDF_417);
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, decoderResult.getECLevel()); result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, decoderResult.getECLevel());
result.putMetadata(ResultMetadataType.ERRORS_CORRECTED, decoderResult.getErrorsCorrected());
result.putMetadata(ResultMetadataType.ERASURES_CORRECTED, decoderResult.getErasures());
PDF417ResultMetadata pdf417ResultMetadata = (PDF417ResultMetadata) decoderResult.getOther(); PDF417ResultMetadata pdf417ResultMetadata = (PDF417ResultMetadata) decoderResult.getOther();
if (pdf417ResultMetadata != null) { if (pdf417ResultMetadata != null) {
result.putMetadata(ResultMetadataType.PDF417_EXTRA_METADATA, pdf417ResultMetadata); result.putMetadata(ResultMetadataType.PDF417_EXTRA_METADATA, pdf417ResultMetadata);

View file

@ -99,6 +99,7 @@ public class QRCodeReader implements Reader {
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY, result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
decoderResult.getStructuredAppendParity()); decoderResult.getStructuredAppendParity());
} }
result.putMetadata(ResultMetadataType.ERRORS_CORRECTED, decoderResult.getErrorsCorrected());
result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]Q" + decoderResult.getSymbologyModifier()); result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]Q" + decoderResult.getSymbologyModifier());
return result; return result;
} }

View file

@ -146,17 +146,20 @@ public final class Decoder {
int resultOffset = 0; int resultOffset = 0;
// Error-correct and copy data blocks together into a stream of bytes // Error-correct and copy data blocks together into a stream of bytes
int errorsCorrected = 0;
for (DataBlock dataBlock : dataBlocks) { for (DataBlock dataBlock : dataBlocks) {
byte[] codewordBytes = dataBlock.getCodewords(); byte[] codewordBytes = dataBlock.getCodewords();
int numDataCodewords = dataBlock.getNumDataCodewords(); int numDataCodewords = dataBlock.getNumDataCodewords();
correctErrors(codewordBytes, numDataCodewords); errorsCorrected += correctErrors(codewordBytes, numDataCodewords);
for (int i = 0; i < numDataCodewords; i++) { for (int i = 0; i < numDataCodewords; i++) {
resultBytes[resultOffset++] = codewordBytes[i]; resultBytes[resultOffset++] = codewordBytes[i];
} }
} }
// Decode the contents of that stream of bytes // Decode the contents of that stream of bytes
return DecodedBitStreamParser.decode(resultBytes, version, ecLevel, hints); DecoderResult result = DecodedBitStreamParser.decode(resultBytes, version, ecLevel, hints);
result.setErrorsCorrected(errorsCorrected);
return result;
} }
/** /**
@ -165,17 +168,19 @@ public final class Decoder {
* *
* @param codewordBytes data and error correction codewords * @param codewordBytes data and error correction codewords
* @param numDataCodewords number of codewords that are data bytes * @param numDataCodewords number of codewords that are data bytes
* @return the number of errors corrected
* @throws ChecksumException if error correction fails * @throws ChecksumException if error correction fails
*/ */
private void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException { private int correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException {
int numCodewords = codewordBytes.length; int numCodewords = codewordBytes.length;
// First read into an array of ints // First read into an array of ints
int[] codewordsInts = new int[numCodewords]; int[] codewordsInts = new int[numCodewords];
for (int i = 0; i < numCodewords; i++) { for (int i = 0; i < numCodewords; i++) {
codewordsInts[i] = codewordBytes[i] & 0xFF; codewordsInts[i] = codewordBytes[i] & 0xFF;
} }
int errorsCorrected = 0;
try { try {
rsDecoder.decode(codewordsInts, codewordBytes.length - numDataCodewords); errorsCorrected = rsDecoder.decodeWithECCount(codewordsInts, codewordBytes.length - numDataCodewords);
} catch (ReedSolomonException ignored) { } catch (ReedSolomonException ignored) {
throw ChecksumException.getChecksumInstance(); throw ChecksumException.getChecksumInstance();
} }
@ -184,6 +189,7 @@ public final class Decoder {
for (int i = 0; i < numDataCodewords; i++) { for (int i = 0; i < numDataCodewords; i++) {
codewordBytes[i] = (byte) codewordsInts[i]; codewordBytes[i] = (byte) codewordsInts[i];
} }
return errorsCorrected;
} }
} }

View file

@ -26,10 +26,10 @@ public final class AztecBlackBox1TestCase extends AbstractBlackBoxTestCase {
public AztecBlackBox1TestCase() { public AztecBlackBox1TestCase() {
super("src/test/resources/blackbox/aztec-1", new AztecReader(), BarcodeFormat.AZTEC); super("src/test/resources/blackbox/aztec-1", new AztecReader(), BarcodeFormat.AZTEC);
addTest(14, 14, 0.0f); addTest(15, 15, 0.0f);
addTest(14, 14, 90.0f); addTest(15, 15, 90.0f);
addTest(14, 14, 180.0f); addTest(15, 15, 180.0f);
addTest(14, 14, 270.0f); addTest(15, 15, 270.0f);
} }
} }

View file

@ -493,7 +493,7 @@ public final class EncoderTest extends Assert {
assertEquals("Unexpected nr. of layers", layers, aztec.getLayers()); assertEquals("Unexpected nr. of layers", layers, aztec.getLayers());
BitMatrix matrix = aztec.getMatrix(); BitMatrix matrix = aztec.getMatrix();
AztecDetectorResult r = AztecDetectorResult r =
new AztecDetectorResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers()); new AztecDetectorResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers(), 0);
DecoderResult res = new Decoder().decode(r); DecoderResult res = new Decoder().decode(r);
assertEquals(data, res.getText()); assertEquals(data, res.getText());
// Check error correction by introducing a few minor errors // Check error correction by introducing a few minor errors
@ -502,7 +502,7 @@ public final class EncoderTest extends Assert {
matrix.flip(random.nextInt(matrix.getWidth()), matrix.getHeight() - 2 + random.nextInt(2)); matrix.flip(random.nextInt(matrix.getWidth()), matrix.getHeight() - 2 + random.nextInt(2));
matrix.flip(random.nextInt(2), random.nextInt(matrix.getHeight())); matrix.flip(random.nextInt(2), random.nextInt(matrix.getHeight()));
matrix.flip(matrix.getWidth() - 2 + random.nextInt(2), random.nextInt(matrix.getHeight())); matrix.flip(matrix.getWidth() - 2 + random.nextInt(2), random.nextInt(matrix.getHeight()));
r = new AztecDetectorResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers()); r = new AztecDetectorResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers(), 0);
res = new Decoder().decode(r); res = new Decoder().decode(r);
assertEquals(data, res.getText()); assertEquals(data, res.getText());
} }
@ -527,7 +527,7 @@ public final class EncoderTest extends Assert {
BitMatrix matrix2 = aztec.getMatrix(); BitMatrix matrix2 = aztec.getMatrix();
assertEquals(matrix, matrix2); assertEquals(matrix, matrix2);
AztecDetectorResult r = AztecDetectorResult r =
new AztecDetectorResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers()); new AztecDetectorResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers(), 0);
DecoderResult res = new Decoder().decode(r); DecoderResult res = new Decoder().decode(r);
assertEquals(data, res.getText()); assertEquals(data, res.getText());
// Check error correction by introducing up to eccPercent/2 errors // Check error correction by introducing up to eccPercent/2 errors
@ -543,7 +543,7 @@ public final class EncoderTest extends Assert {
: matrix.getHeight() - 1 - random.nextInt(aztec.getLayers() * 2); : matrix.getHeight() - 1 - random.nextInt(aztec.getLayers() * 2);
matrix.flip(x, y); matrix.flip(x, y);
} }
r = new AztecDetectorResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers()); r = new AztecDetectorResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers(), 0);
res = new Decoder().decode(r); res = new Decoder().decode(r);
assertEquals(data, res.getText()); assertEquals(data, res.getText());
} }

View file

@ -165,6 +165,8 @@ public abstract class AbstractBlackBoxTestCase extends Assert {
try (BufferedReader reader = Files.newBufferedReader(expectedMetadataFile, StandardCharsets.UTF_8)) { try (BufferedReader reader = Files.newBufferedReader(expectedMetadataFile, StandardCharsets.UTF_8)) {
expectedMetadata.load(reader); expectedMetadata.load(reader);
} }
correctInteger(expectedMetadata, ResultMetadataType.ERRORS_CORRECTED);
correctInteger(expectedMetadata, ResultMetadataType.ERASURES_CORRECTED);
} }
for (int x = 0; x < testCount; x++) { for (int x = 0; x < testCount; x++) {
@ -249,6 +251,15 @@ public abstract class AbstractBlackBoxTestCase extends Assert {
} }
} }
private static void correctInteger(Properties metadata, ResultMetadataType key) {
String skey = key.toString();
if (metadata.containsKey(skey)) {
String sval = metadata.getProperty(skey);
Integer ival = Integer.parseInt(sval);
metadata.put(skey, ival);
}
}
private boolean decode(BinaryBitmap source, private boolean decode(BinaryBitmap source,
float rotation, float rotation,
String expectedText, String expectedText,

View file

@ -27,10 +27,10 @@ public final class DataMatrixBlackBox1TestCase extends AbstractBlackBoxTestCase
public DataMatrixBlackBox1TestCase() { public DataMatrixBlackBox1TestCase() {
super("src/test/resources/blackbox/datamatrix-1", new MultiFormatReader(), BarcodeFormat.DATA_MATRIX); super("src/test/resources/blackbox/datamatrix-1", new MultiFormatReader(), BarcodeFormat.DATA_MATRIX);
addTest(21, 21, 0.0f); addTest(22, 22, 0.0f);
addTest(21, 21, 90.0f); addTest(22, 22, 90.0f);
addTest(21, 21, 180.0f); addTest(22, 22, 180.0f);
addTest(21, 21, 270.0f); addTest(22, 22, 270.0f);
} }
} }

View file

@ -32,7 +32,7 @@ public final class MaxiCodeBlackBox1TestCase extends AbstractBlackBoxTestCase {
public MaxiCodeBlackBox1TestCase() { public MaxiCodeBlackBox1TestCase() {
super("src/test/resources/blackbox/maxicode-1", new MultiFormatReader(), BarcodeFormat.MAXICODE); super("src/test/resources/blackbox/maxicode-1", new MultiFormatReader(), BarcodeFormat.MAXICODE);
addHint(DecodeHintType.PURE_BARCODE); addHint(DecodeHintType.PURE_BARCODE);
addTest(7, 7, 0.0f); addTest(8, 8, 0.0f);
} }
} }

View file

@ -27,7 +27,7 @@ public final class Maxicode1TestCase extends AbstractBlackBoxTestCase {
public Maxicode1TestCase() { public Maxicode1TestCase() {
super("src/test/resources/blackbox/maxicode-1", new MultiFormatReader(), BarcodeFormat.MAXICODE); super("src/test/resources/blackbox/maxicode-1", new MultiFormatReader(), BarcodeFormat.MAXICODE);
addTest(7, 7, 0.0f); addTest(8, 8, 0.0f);
} }
} }

View file

@ -29,10 +29,10 @@ public final class PDF417BlackBox1TestCase extends AbstractBlackBoxTestCase {
public PDF417BlackBox1TestCase() { public PDF417BlackBox1TestCase() {
super("src/test/resources/blackbox/pdf417-1", new MultiFormatReader(), BarcodeFormat.PDF_417); super("src/test/resources/blackbox/pdf417-1", new MultiFormatReader(), BarcodeFormat.PDF_417);
addTest(12, 12, 0.0f); addTest(13, 13, 0.0f);
addTest(12, 12, 90.0f); addTest(13, 13, 90.0f);
addTest(12, 12, 180.0f); addTest(13, 13, 180.0f);
addTest(12, 12, 270.0f); addTest(13, 13, 270.0f);
} }
} }

View file

@ -27,10 +27,10 @@ public final class QRCodeBlackBox2TestCase extends AbstractBlackBoxTestCase {
public QRCodeBlackBox2TestCase() { public QRCodeBlackBox2TestCase() {
super("src/test/resources/blackbox/qrcode-2", new MultiFormatReader(), BarcodeFormat.QR_CODE); super("src/test/resources/blackbox/qrcode-2", new MultiFormatReader(), BarcodeFormat.QR_CODE);
addTest(31, 31, 0.0f); addTest(32, 32, 0.0f);
addTest(29, 29, 90.0f); addTest(30, 30, 90.0f);
addTest(30, 30, 180.0f); addTest(31, 31, 180.0f);
addTest(30, 30, 270.0f); addTest(31, 31, 270.0f);
} }
} }

View file

@ -0,0 +1 @@
ERRORS_CORRECTED=3

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

@ -0,0 +1 @@
hello

View file

@ -0,0 +1 @@
ERRORS_CORRECTED=0

View file

@ -1 +1,2 @@
SYMBOLOGY_IDENTIFIER=]d1 SYMBOLOGY_IDENTIFIER=]d1
ERRORS_CORRECTED=0

View file

@ -0,0 +1 @@
ERRORS_CORRECTED=2

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*(),./\

View file

@ -0,0 +1 @@
ERRORS_CORRECTED=0

View file

@ -0,0 +1 @@
ERRORS_CORRECTED=3

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -0,0 +1 @@
ABCDEFGHIJKLMNOPQRSTUVWXYZ "#$%&'()*+,-./01234:56789

View file

@ -1 +1,3 @@
SYMBOLOGY_IDENTIFIER=]L0 SYMBOLOGY_IDENTIFIER=]L0
ERRORS_CORRECTED=0
ERASURES_CORRECTED=0

View file

@ -0,0 +1,2 @@
ERRORS_CORRECTED=4
ERASURES_CORRECTED=3

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
This is PDF417

View file

@ -1 +1,2 @@
SYMBOLOGY_IDENTIFIER=]Q1 SYMBOLOGY_IDENTIFIER=]Q1
ERRORS_CORRECTED=0

View file

@ -0,0 +1 @@
ERRORS_CORRECTED=2

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

View file

@ -0,0 +1 @@
Morden