| /* |
| * Copyright 2013 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; |
| |
| import com.google.zxing.ChecksumException; |
| import com.google.zxing.FormatException; |
| import com.google.zxing.NotFoundException; |
| import com.google.zxing.ResultPoint; |
| import com.google.zxing.common.BitMatrix; |
| import com.google.zxing.common.DecoderResult; |
| import com.google.zxing.common.detector.MathUtils; |
| import com.google.zxing.pdf417.PDF417Common; |
| import com.google.zxing.pdf417.decoder.ec.ErrorCorrection; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Formatter; |
| import java.util.List; |
| |
| /** |
| * @author Guenther Grau |
| */ |
| public final class PDF417ScanningDecoder { |
| |
| private static final int CODEWORD_SKEW_SIZE = 2; |
| |
| private static final int MAX_ERRORS = 3; |
| private static final int MAX_EC_CODEWORDS = 512; |
| private static final ErrorCorrection errorCorrection = new ErrorCorrection(); |
| |
| private PDF417ScanningDecoder() { |
| } |
| |
| // TODO don't pass in minCodewordWidth and maxCodewordWidth, pass in barcode columns for start and stop pattern |
| // columns. That way width can be deducted from the pattern column. |
| // This approach also allows to detect more details about the barcode, e.g. if a bar type (white or black) is wider |
| // than it should be. This can happen if the scanner used a bad blackpoint. |
| public static DecoderResult decode(BitMatrix image, |
| ResultPoint imageTopLeft, |
| ResultPoint imageBottomLeft, |
| ResultPoint imageTopRight, |
| ResultPoint imageBottomRight, |
| int minCodewordWidth, |
| int maxCodewordWidth) |
| throws NotFoundException, FormatException, ChecksumException { |
| BoundingBox boundingBox = new BoundingBox(image, imageTopLeft, imageBottomLeft, imageTopRight, imageBottomRight); |
| DetectionResultRowIndicatorColumn leftRowIndicatorColumn = null; |
| DetectionResultRowIndicatorColumn rightRowIndicatorColumn = null; |
| DetectionResult detectionResult; |
| for (boolean firstPass = true; ; firstPass = false) { |
| if (imageTopLeft != null) { |
| leftRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopLeft, true, minCodewordWidth, |
| maxCodewordWidth); |
| } |
| if (imageTopRight != null) { |
| rightRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopRight, false, minCodewordWidth, |
| maxCodewordWidth); |
| } |
| detectionResult = merge(leftRowIndicatorColumn, rightRowIndicatorColumn); |
| if (detectionResult == null) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| BoundingBox resultBox = detectionResult.getBoundingBox(); |
| if (firstPass && resultBox != null && |
| (resultBox.getMinY() < boundingBox.getMinY() || resultBox.getMaxY() > boundingBox.getMaxY())) { |
| boundingBox = resultBox; |
| } else { |
| break; |
| } |
| } |
| detectionResult.setBoundingBox(boundingBox); |
| int maxBarcodeColumn = detectionResult.getBarcodeColumnCount() + 1; |
| detectionResult.setDetectionResultColumn(0, leftRowIndicatorColumn); |
| detectionResult.setDetectionResultColumn(maxBarcodeColumn, rightRowIndicatorColumn); |
| |
| boolean leftToRight = leftRowIndicatorColumn != null; |
| for (int barcodeColumnCount = 1; barcodeColumnCount <= maxBarcodeColumn; barcodeColumnCount++) { |
| int barcodeColumn = leftToRight ? barcodeColumnCount : maxBarcodeColumn - barcodeColumnCount; |
| if (detectionResult.getDetectionResultColumn(barcodeColumn) != null) { |
| // This will be the case for the opposite row indicator column, which doesn't need to be decoded again. |
| continue; |
| } |
| DetectionResultColumn detectionResultColumn; |
| if (barcodeColumn == 0 || barcodeColumn == maxBarcodeColumn) { |
| detectionResultColumn = new DetectionResultRowIndicatorColumn(boundingBox, barcodeColumn == 0); |
| } else { |
| detectionResultColumn = new DetectionResultColumn(boundingBox); |
| } |
| detectionResult.setDetectionResultColumn(barcodeColumn, detectionResultColumn); |
| int startColumn = -1; |
| int previousStartColumn = startColumn; |
| // TODO start at a row for which we know the start position, then detect upwards and downwards from there. |
| for (int imageRow = boundingBox.getMinY(); imageRow <= boundingBox.getMaxY(); imageRow++) { |
| startColumn = getStartColumn(detectionResult, barcodeColumn, imageRow, leftToRight); |
| if (startColumn < 0 || startColumn > boundingBox.getMaxX()) { |
| if (previousStartColumn == -1) { |
| continue; |
| } |
| startColumn = previousStartColumn; |
| } |
| Codeword codeword = detectCodeword(image, boundingBox.getMinX(), boundingBox.getMaxX(), leftToRight, |
| startColumn, imageRow, minCodewordWidth, maxCodewordWidth); |
| if (codeword != null) { |
| detectionResultColumn.setCodeword(imageRow, codeword); |
| previousStartColumn = startColumn; |
| minCodewordWidth = Math.min(minCodewordWidth, codeword.getWidth()); |
| maxCodewordWidth = Math.max(maxCodewordWidth, codeword.getWidth()); |
| } |
| } |
| } |
| return createDecoderResult(detectionResult); |
| } |
| |
| private static DetectionResult merge(DetectionResultRowIndicatorColumn leftRowIndicatorColumn, |
| DetectionResultRowIndicatorColumn rightRowIndicatorColumn) |
| throws NotFoundException { |
| if (leftRowIndicatorColumn == null && rightRowIndicatorColumn == null) { |
| return null; |
| } |
| BarcodeMetadata barcodeMetadata = getBarcodeMetadata(leftRowIndicatorColumn, rightRowIndicatorColumn); |
| if (barcodeMetadata == null) { |
| return null; |
| } |
| BoundingBox boundingBox = BoundingBox.merge(adjustBoundingBox(leftRowIndicatorColumn), |
| adjustBoundingBox(rightRowIndicatorColumn)); |
| return new DetectionResult(barcodeMetadata, boundingBox); |
| } |
| |
| private static BoundingBox adjustBoundingBox(DetectionResultRowIndicatorColumn rowIndicatorColumn) |
| throws NotFoundException { |
| if (rowIndicatorColumn == null) { |
| return null; |
| } |
| int[] rowHeights = rowIndicatorColumn.getRowHeights(); |
| if (rowHeights == null) { |
| return null; |
| } |
| int maxRowHeight = getMax(rowHeights); |
| int missingStartRows = 0; |
| for (int rowHeight : rowHeights) { |
| missingStartRows += maxRowHeight - rowHeight; |
| if (rowHeight > 0) { |
| break; |
| } |
| } |
| Codeword[] codewords = rowIndicatorColumn.getCodewords(); |
| for (int row = 0; missingStartRows > 0 && codewords[row] == null; row++) { |
| missingStartRows--; |
| } |
| int missingEndRows = 0; |
| for (int row = rowHeights.length - 1; row >= 0; row--) { |
| missingEndRows += maxRowHeight - rowHeights[row]; |
| if (rowHeights[row] > 0) { |
| break; |
| } |
| } |
| for (int row = codewords.length - 1; missingEndRows > 0 && codewords[row] == null; row--) { |
| missingEndRows--; |
| } |
| return rowIndicatorColumn.getBoundingBox().addMissingRows(missingStartRows, missingEndRows, |
| rowIndicatorColumn.isLeft()); |
| } |
| |
| private static int getMax(int[] values) { |
| int maxValue = -1; |
| for (int value : values) { |
| maxValue = Math.max(maxValue, value); |
| } |
| return maxValue; |
| } |
| |
| private static BarcodeMetadata getBarcodeMetadata(DetectionResultRowIndicatorColumn leftRowIndicatorColumn, |
| DetectionResultRowIndicatorColumn rightRowIndicatorColumn) { |
| BarcodeMetadata leftBarcodeMetadata; |
| if (leftRowIndicatorColumn == null || |
| (leftBarcodeMetadata = leftRowIndicatorColumn.getBarcodeMetadata()) == null) { |
| return rightRowIndicatorColumn == null ? null : rightRowIndicatorColumn.getBarcodeMetadata(); |
| } |
| BarcodeMetadata rightBarcodeMetadata; |
| if (rightRowIndicatorColumn == null || |
| (rightBarcodeMetadata = rightRowIndicatorColumn.getBarcodeMetadata()) == null) { |
| return leftBarcodeMetadata; |
| } |
| |
| if (leftBarcodeMetadata.getColumnCount() != rightBarcodeMetadata.getColumnCount() && |
| leftBarcodeMetadata.getErrorCorrectionLevel() != rightBarcodeMetadata.getErrorCorrectionLevel() && |
| leftBarcodeMetadata.getRowCount() != rightBarcodeMetadata.getRowCount()) { |
| return null; |
| } |
| return leftBarcodeMetadata; |
| } |
| |
| private static DetectionResultRowIndicatorColumn getRowIndicatorColumn(BitMatrix image, |
| BoundingBox boundingBox, |
| ResultPoint startPoint, |
| boolean leftToRight, |
| int minCodewordWidth, |
| int maxCodewordWidth) { |
| DetectionResultRowIndicatorColumn rowIndicatorColumn = new DetectionResultRowIndicatorColumn(boundingBox, |
| leftToRight); |
| for (int i = 0; i < 2; i++) { |
| int increment = i == 0 ? 1 : -1; |
| int startColumn = (int) startPoint.getX(); |
| for (int imageRow = (int) startPoint.getY(); imageRow <= boundingBox.getMaxY() && |
| imageRow >= boundingBox.getMinY(); imageRow += increment) { |
| Codeword codeword = detectCodeword(image, 0, image.getWidth(), leftToRight, startColumn, imageRow, |
| minCodewordWidth, maxCodewordWidth); |
| if (codeword != null) { |
| rowIndicatorColumn.setCodeword(imageRow, codeword); |
| if (leftToRight) { |
| startColumn = codeword.getStartX(); |
| } else { |
| startColumn = codeword.getEndX(); |
| } |
| } |
| } |
| } |
| return rowIndicatorColumn; |
| } |
| |
| private static void adjustCodewordCount(DetectionResult detectionResult, BarcodeValue[][] barcodeMatrix) |
| throws NotFoundException { |
| BarcodeValue barcodeMatrix01 = barcodeMatrix[0][1]; |
| int[] numberOfCodewords = barcodeMatrix01.getValue(); |
| int calculatedNumberOfCodewords = detectionResult.getBarcodeColumnCount() * |
| detectionResult.getBarcodeRowCount() - |
| getNumberOfECCodeWords(detectionResult.getBarcodeECLevel()); |
| if (numberOfCodewords.length == 0) { |
| if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > PDF417Common.MAX_CODEWORDS_IN_BARCODE) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| barcodeMatrix01.setValue(calculatedNumberOfCodewords); |
| } else if (numberOfCodewords[0] != calculatedNumberOfCodewords) { |
| if (calculatedNumberOfCodewords >= 1 && calculatedNumberOfCodewords <= PDF417Common.MAX_CODEWORDS_IN_BARCODE) { |
| // The calculated one is more reliable as it is derived from the row indicator columns |
| barcodeMatrix01.setValue(calculatedNumberOfCodewords); |
| } |
| } |
| } |
| |
| private static DecoderResult createDecoderResult(DetectionResult detectionResult) throws FormatException, |
| ChecksumException, NotFoundException { |
| BarcodeValue[][] barcodeMatrix = createBarcodeMatrix(detectionResult); |
| adjustCodewordCount(detectionResult, barcodeMatrix); |
| Collection<Integer> erasures = new ArrayList<>(); |
| int[] codewords = new int[detectionResult.getBarcodeRowCount() * detectionResult.getBarcodeColumnCount()]; |
| List<int[]> ambiguousIndexValuesList = new ArrayList<>(); |
| Collection<Integer> ambiguousIndexesList = new ArrayList<>(); |
| for (int row = 0; row < detectionResult.getBarcodeRowCount(); row++) { |
| for (int column = 0; column < detectionResult.getBarcodeColumnCount(); column++) { |
| int[] values = barcodeMatrix[row][column + 1].getValue(); |
| int codewordIndex = row * detectionResult.getBarcodeColumnCount() + column; |
| if (values.length == 0) { |
| erasures.add(codewordIndex); |
| } else if (values.length == 1) { |
| codewords[codewordIndex] = values[0]; |
| } else { |
| ambiguousIndexesList.add(codewordIndex); |
| ambiguousIndexValuesList.add(values); |
| } |
| } |
| } |
| int[][] ambiguousIndexValues = new int[ambiguousIndexValuesList.size()][]; |
| for (int i = 0; i < ambiguousIndexValues.length; i++) { |
| ambiguousIndexValues[i] = ambiguousIndexValuesList.get(i); |
| } |
| return createDecoderResultFromAmbiguousValues(detectionResult.getBarcodeECLevel(), codewords, |
| PDF417Common.toIntArray(erasures), PDF417Common.toIntArray(ambiguousIndexesList), ambiguousIndexValues); |
| } |
| |
| /** |
| * This method deals with the fact, that the decoding process doesn't always yield a single most likely value. The |
| * current error correction implementation doesn't deal with erasures very well, so it's better to provide a value |
| * for these ambiguous codewords instead of treating it as an erasure. The problem is that we don't know which of |
| * the ambiguous values to choose. We try decode using the first value, and if that fails, we use another of the |
| * ambiguous values and try to decode again. This usually only happens on very hard to read and decode barcodes, |
| * so decoding the normal barcodes is not affected by this. |
| * |
| * @param erasureArray contains the indexes of erasures |
| * @param ambiguousIndexes array with the indexes that have more than one most likely value |
| * @param ambiguousIndexValues two dimensional array that contains the ambiguous values. The first dimension must |
| * be the same length as the ambiguousIndexes array |
| */ |
| private static DecoderResult createDecoderResultFromAmbiguousValues(int ecLevel, |
| int[] codewords, |
| int[] erasureArray, |
| int[] ambiguousIndexes, |
| int[][] ambiguousIndexValues) |
| throws FormatException, ChecksumException { |
| int[] ambiguousIndexCount = new int[ambiguousIndexes.length]; |
| |
| int tries = 100; |
| while (tries-- > 0) { |
| for (int i = 0; i < ambiguousIndexCount.length; i++) { |
| codewords[ambiguousIndexes[i]] = ambiguousIndexValues[i][ambiguousIndexCount[i]]; |
| } |
| try { |
| return decodeCodewords(codewords, ecLevel, erasureArray); |
| } catch (ChecksumException ignored) { |
| // |
| } |
| if (ambiguousIndexCount.length == 0) { |
| throw ChecksumException.getChecksumInstance(); |
| } |
| for (int i = 0; i < ambiguousIndexCount.length; i++) { |
| if (ambiguousIndexCount[i] < ambiguousIndexValues[i].length - 1) { |
| ambiguousIndexCount[i]++; |
| break; |
| } else { |
| ambiguousIndexCount[i] = 0; |
| if (i == ambiguousIndexCount.length - 1) { |
| throw ChecksumException.getChecksumInstance(); |
| } |
| } |
| } |
| } |
| throw ChecksumException.getChecksumInstance(); |
| } |
| |
| private static BarcodeValue[][] createBarcodeMatrix(DetectionResult detectionResult) { |
| BarcodeValue[][] barcodeMatrix = |
| new BarcodeValue[detectionResult.getBarcodeRowCount()][detectionResult.getBarcodeColumnCount() + 2]; |
| for (int row = 0; row < barcodeMatrix.length; row++) { |
| for (int column = 0; column < barcodeMatrix[row].length; column++) { |
| barcodeMatrix[row][column] = new BarcodeValue(); |
| } |
| } |
| |
| int column = 0; |
| for (DetectionResultColumn detectionResultColumn : detectionResult.getDetectionResultColumns()) { |
| if (detectionResultColumn != null) { |
| for (Codeword codeword : detectionResultColumn.getCodewords()) { |
| if (codeword != null) { |
| int rowNumber = codeword.getRowNumber(); |
| if (rowNumber >= 0) { |
| if (rowNumber >= barcodeMatrix.length) { |
| // We have more rows than the barcode metadata allows for, ignore them. |
| continue; |
| } |
| barcodeMatrix[rowNumber][column].setValue(codeword.getValue()); |
| } |
| } |
| } |
| } |
| column++; |
| } |
| return barcodeMatrix; |
| } |
| |
| private static boolean isValidBarcodeColumn(DetectionResult detectionResult, int barcodeColumn) { |
| return barcodeColumn >= 0 && barcodeColumn <= detectionResult.getBarcodeColumnCount() + 1; |
| } |
| |
| private static int getStartColumn(DetectionResult detectionResult, |
| int barcodeColumn, |
| int imageRow, |
| boolean leftToRight) { |
| int offset = leftToRight ? 1 : -1; |
| Codeword codeword = null; |
| if (isValidBarcodeColumn(detectionResult, barcodeColumn - offset)) { |
| codeword = detectionResult.getDetectionResultColumn(barcodeColumn - offset).getCodeword(imageRow); |
| } |
| if (codeword != null) { |
| return leftToRight ? codeword.getEndX() : codeword.getStartX(); |
| } |
| codeword = detectionResult.getDetectionResultColumn(barcodeColumn).getCodewordNearby(imageRow); |
| if (codeword != null) { |
| return leftToRight ? codeword.getStartX() : codeword.getEndX(); |
| } |
| if (isValidBarcodeColumn(detectionResult, barcodeColumn - offset)) { |
| codeword = detectionResult.getDetectionResultColumn(barcodeColumn - offset).getCodewordNearby(imageRow); |
| } |
| if (codeword != null) { |
| return leftToRight ? codeword.getEndX() : codeword.getStartX(); |
| } |
| int skippedColumns = 0; |
| |
| while (isValidBarcodeColumn(detectionResult, barcodeColumn - offset)) { |
| barcodeColumn -= offset; |
| for (Codeword previousRowCodeword : detectionResult.getDetectionResultColumn(barcodeColumn).getCodewords()) { |
| if (previousRowCodeword != null) { |
| return (leftToRight ? previousRowCodeword.getEndX() : previousRowCodeword.getStartX()) + |
| offset * |
| skippedColumns * |
| (previousRowCodeword.getEndX() - previousRowCodeword.getStartX()); |
| } |
| } |
| skippedColumns++; |
| } |
| return leftToRight ? detectionResult.getBoundingBox().getMinX() : detectionResult.getBoundingBox().getMaxX(); |
| } |
| |
| private static Codeword detectCodeword(BitMatrix image, |
| int minColumn, |
| int maxColumn, |
| boolean leftToRight, |
| int startColumn, |
| int imageRow, |
| int minCodewordWidth, |
| int maxCodewordWidth) { |
| startColumn = adjustCodewordStartColumn(image, minColumn, maxColumn, leftToRight, startColumn, imageRow); |
| // we usually know fairly exact now how long a codeword is. We should provide minimum and maximum expected length |
| // and try to adjust the read pixels, e.g. remove single pixel errors or try to cut off exceeding pixels. |
| // min and maxCodewordWidth should not be used as they are calculated for the whole barcode an can be inaccurate |
| // for the current position |
| int[] moduleBitCount = getModuleBitCount(image, minColumn, maxColumn, leftToRight, startColumn, imageRow); |
| if (moduleBitCount == null) { |
| return null; |
| } |
| int endColumn; |
| int codewordBitCount = MathUtils.sum(moduleBitCount); |
| if (leftToRight) { |
| endColumn = startColumn + codewordBitCount; |
| } else { |
| for (int i = 0; i < moduleBitCount.length / 2; i++) { |
| int tmpCount = moduleBitCount[i]; |
| moduleBitCount[i] = moduleBitCount[moduleBitCount.length - 1 - i]; |
| moduleBitCount[moduleBitCount.length - 1 - i] = tmpCount; |
| } |
| endColumn = startColumn; |
| startColumn = endColumn - codewordBitCount; |
| } |
| // TODO implement check for width and correction of black and white bars |
| // use start (and maybe stop pattern) to determine if black bars are wider than white bars. If so, adjust. |
| // should probably done only for codewords with a lot more than 17 bits. |
| // The following fixes 10-1.png, which has wide black bars and small white bars |
| // for (int i = 0; i < moduleBitCount.length; i++) { |
| // if (i % 2 == 0) { |
| // moduleBitCount[i]--; |
| // } else { |
| // moduleBitCount[i]++; |
| // } |
| // } |
| |
| // We could also use the width of surrounding codewords for more accurate results, but this seems |
| // sufficient for now |
| if (!checkCodewordSkew(codewordBitCount, minCodewordWidth, maxCodewordWidth)) { |
| // We could try to use the startX and endX position of the codeword in the same column in the previous row, |
| // create the bit count from it and normalize it to 8. This would help with single pixel errors. |
| return null; |
| } |
| |
| int decodedValue = PDF417CodewordDecoder.getDecodedValue(moduleBitCount); |
| int codeword = PDF417Common.getCodeword(decodedValue); |
| if (codeword == -1) { |
| return null; |
| } |
| return new Codeword(startColumn, endColumn, getCodewordBucketNumber(decodedValue), codeword); |
| } |
| |
| private static int[] getModuleBitCount(BitMatrix image, |
| int minColumn, |
| int maxColumn, |
| boolean leftToRight, |
| int startColumn, |
| int imageRow) { |
| int imageColumn = startColumn; |
| int[] moduleBitCount = new int[8]; |
| int moduleNumber = 0; |
| int increment = leftToRight ? 1 : -1; |
| boolean previousPixelValue = leftToRight; |
| while ((leftToRight ? imageColumn < maxColumn : imageColumn >= minColumn) && |
| moduleNumber < moduleBitCount.length) { |
| if (image.get(imageColumn, imageRow) == previousPixelValue) { |
| moduleBitCount[moduleNumber]++; |
| imageColumn += increment; |
| } else { |
| moduleNumber++; |
| previousPixelValue = !previousPixelValue; |
| } |
| } |
| if (moduleNumber == moduleBitCount.length || |
| ((imageColumn == (leftToRight ? maxColumn : minColumn)) && |
| moduleNumber == moduleBitCount.length - 1)) { |
| return moduleBitCount; |
| } |
| return null; |
| } |
| |
| private static int getNumberOfECCodeWords(int barcodeECLevel) { |
| return 2 << barcodeECLevel; |
| } |
| |
| private static int adjustCodewordStartColumn(BitMatrix image, |
| int minColumn, |
| int maxColumn, |
| boolean leftToRight, |
| int codewordStartColumn, |
| int imageRow) { |
| int correctedStartColumn = codewordStartColumn; |
| int increment = leftToRight ? -1 : 1; |
| // there should be no black pixels before the start column. If there are, then we need to start earlier. |
| for (int i = 0; i < 2; i++) { |
| while ((leftToRight ? correctedStartColumn >= minColumn : correctedStartColumn < maxColumn) && |
| leftToRight == image.get(correctedStartColumn, imageRow)) { |
| if (Math.abs(codewordStartColumn - correctedStartColumn) > CODEWORD_SKEW_SIZE) { |
| return codewordStartColumn; |
| } |
| correctedStartColumn += increment; |
| } |
| increment = -increment; |
| leftToRight = !leftToRight; |
| } |
| return correctedStartColumn; |
| } |
| |
| private static boolean checkCodewordSkew(int codewordSize, int minCodewordWidth, int maxCodewordWidth) { |
| return minCodewordWidth - CODEWORD_SKEW_SIZE <= codewordSize && |
| codewordSize <= maxCodewordWidth + CODEWORD_SKEW_SIZE; |
| } |
| |
| private static DecoderResult decodeCodewords(int[] codewords, int ecLevel, int[] erasures) throws FormatException, |
| ChecksumException { |
| if (codewords.length == 0) { |
| throw FormatException.getFormatInstance(); |
| } |
| |
| int numECCodewords = 1 << (ecLevel + 1); |
| int correctedErrorsCount = correctErrors(codewords, erasures, numECCodewords); |
| verifyCodewordCount(codewords, numECCodewords); |
| |
| // Decode the codewords |
| DecoderResult decoderResult = DecodedBitStreamParser.decode(codewords, String.valueOf(ecLevel)); |
| decoderResult.setErrorsCorrected(correctedErrorsCount); |
| decoderResult.setErasures(erasures.length); |
| return decoderResult; |
| } |
| |
| /** |
| * <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to |
| * correct the errors in-place.</p> |
| * |
| * @param codewords data and error correction codewords |
| * @param erasures positions of any known erasures |
| * @param numECCodewords number of error correction codewords that are available in codewords |
| * @throws ChecksumException if error correction fails |
| */ |
| private static int correctErrors(int[] codewords, int[] erasures, int numECCodewords) throws ChecksumException { |
| if (erasures != null && |
| erasures.length > numECCodewords / 2 + MAX_ERRORS || |
| numECCodewords < 0 || |
| numECCodewords > MAX_EC_CODEWORDS) { |
| // Too many errors or EC Codewords is corrupted |
| throw ChecksumException.getChecksumInstance(); |
| } |
| return errorCorrection.decode(codewords, numECCodewords, erasures); |
| } |
| |
| /** |
| * Verify that all is OK with the codeword array. |
| */ |
| private static void verifyCodewordCount(int[] codewords, int numECCodewords) throws FormatException { |
| if (codewords.length < 4) { |
| // Codeword array size should be at least 4 allowing for |
| // Count CW, At least one Data CW, Error Correction CW, Error Correction CW |
| throw FormatException.getFormatInstance(); |
| } |
| // The first codeword, the Symbol Length Descriptor, shall always encode the total number of data |
| // codewords in the symbol, including the Symbol Length Descriptor itself, data codewords and pad |
| // codewords, but excluding the number of error correction codewords. |
| int numberOfCodewords = codewords[0]; |
| if (numberOfCodewords > codewords.length) { |
| throw FormatException.getFormatInstance(); |
| } |
| if (numberOfCodewords == 0) { |
| // Reset to the length of the array - 8 (Allow for at least level 3 Error Correction (8 Error Codewords) |
| if (numECCodewords < codewords.length) { |
| codewords[0] = codewords.length - numECCodewords; |
| } else { |
| throw FormatException.getFormatInstance(); |
| } |
| } |
| } |
| |
| private static int[] getBitCountForCodeword(int codeword) { |
| int[] result = new int[8]; |
| int previousValue = 0; |
| int i = result.length - 1; |
| while (true) { |
| if ((codeword & 0x1) != previousValue) { |
| previousValue = codeword & 0x1; |
| i--; |
| if (i < 0) { |
| break; |
| } |
| } |
| result[i]++; |
| codeword >>= 1; |
| } |
| return result; |
| } |
| |
| private static int getCodewordBucketNumber(int codeword) { |
| return getCodewordBucketNumber(getBitCountForCodeword(codeword)); |
| } |
| |
| private static int getCodewordBucketNumber(int[] moduleBitCount) { |
| return (moduleBitCount[0] - moduleBitCount[2] + moduleBitCount[4] - moduleBitCount[6] + 9) % 9; |
| } |
| |
| public static String toString(BarcodeValue[][] barcodeMatrix) { |
| try (Formatter formatter = new Formatter()) { |
| for (int row = 0; row < barcodeMatrix.length; row++) { |
| formatter.format("Row %2d: ", row); |
| for (int column = 0; column < barcodeMatrix[row].length; column++) { |
| BarcodeValue barcodeValue = barcodeMatrix[row][column]; |
| if (barcodeValue.getValue().length == 0) { |
| formatter.format(" ", (Object[]) null); |
| } else { |
| formatter.format("%4d(%2d)", barcodeValue.getValue()[0], |
| barcodeValue.getConfidence(barcodeValue.getValue()[0])); |
| } |
| } |
| formatter.format("%n"); |
| } |
| return formatter.toString(); |
| } |
| } |
| |
| } |