mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-26 12:22:34 +00:00
moved java files for pre-convert
This commit is contained in:
58
src/pdf417/decoder/BarcodeMetadata.java
Normal file
58
src/pdf417/decoder/BarcodeMetadata.java
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Guenther Grau
|
||||
*/
|
||||
final class BarcodeMetadata {
|
||||
|
||||
private final int columnCount;
|
||||
private final int errorCorrectionLevel;
|
||||
private final int rowCountUpperPart;
|
||||
private final int rowCountLowerPart;
|
||||
private final int rowCount;
|
||||
|
||||
BarcodeMetadata(int columnCount, int rowCountUpperPart, int rowCountLowerPart, int errorCorrectionLevel) {
|
||||
this.columnCount = columnCount;
|
||||
this.errorCorrectionLevel = errorCorrectionLevel;
|
||||
this.rowCountUpperPart = rowCountUpperPart;
|
||||
this.rowCountLowerPart = rowCountLowerPart;
|
||||
this.rowCount = rowCountUpperPart + rowCountLowerPart;
|
||||
}
|
||||
|
||||
int getColumnCount() {
|
||||
return columnCount;
|
||||
}
|
||||
|
||||
int getErrorCorrectionLevel() {
|
||||
return errorCorrectionLevel;
|
||||
}
|
||||
|
||||
int getRowCount() {
|
||||
return rowCount;
|
||||
}
|
||||
|
||||
int getRowCountUpperPart() {
|
||||
return rowCountUpperPart;
|
||||
}
|
||||
|
||||
int getRowCountLowerPart() {
|
||||
return rowCountLowerPart;
|
||||
}
|
||||
|
||||
}
|
||||
68
src/pdf417/decoder/BarcodeValue.java
Normal file
68
src/pdf417/decoder/BarcodeValue.java
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.pdf417.PDF417Common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* @author Guenther Grau
|
||||
*/
|
||||
final class BarcodeValue {
|
||||
private final Map<Integer,Integer> values = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Add an occurrence of a value
|
||||
*/
|
||||
void setValue(int value) {
|
||||
Integer confidence = values.get(value);
|
||||
if (confidence == null) {
|
||||
confidence = 0;
|
||||
}
|
||||
confidence++;
|
||||
values.put(value, confidence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the maximum occurrence of a set value and returns all values which were set with this occurrence.
|
||||
* @return an array of int, containing the values with the highest occurrence, or null, if no value was set
|
||||
*/
|
||||
int[] getValue() {
|
||||
int maxConfidence = -1;
|
||||
Collection<Integer> result = new ArrayList<>();
|
||||
for (Entry<Integer,Integer> entry : values.entrySet()) {
|
||||
if (entry.getValue() > maxConfidence) {
|
||||
maxConfidence = entry.getValue();
|
||||
result.clear();
|
||||
result.add(entry.getKey());
|
||||
} else if (entry.getValue() == maxConfidence) {
|
||||
result.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
return PDF417Common.toIntArray(result);
|
||||
}
|
||||
|
||||
Integer getConfidence(int value) {
|
||||
return values.get(value);
|
||||
}
|
||||
|
||||
}
|
||||
157
src/pdf417/decoder/BoundingBox.java
Normal file
157
src/pdf417/decoder/BoundingBox.java
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.NotFoundException;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
/**
|
||||
* @author Guenther Grau
|
||||
*/
|
||||
final class BoundingBox {
|
||||
|
||||
private final BitMatrix image;
|
||||
private final ResultPoint topLeft;
|
||||
private final ResultPoint bottomLeft;
|
||||
private final ResultPoint topRight;
|
||||
private final ResultPoint bottomRight;
|
||||
private final int minX;
|
||||
private final int maxX;
|
||||
private final int minY;
|
||||
private final int maxY;
|
||||
|
||||
BoundingBox(BitMatrix image,
|
||||
ResultPoint topLeft,
|
||||
ResultPoint bottomLeft,
|
||||
ResultPoint topRight,
|
||||
ResultPoint bottomRight) throws NotFoundException {
|
||||
boolean leftUnspecified = topLeft == null || bottomLeft == null;
|
||||
boolean rightUnspecified = topRight == null || bottomRight == null;
|
||||
if (leftUnspecified && rightUnspecified) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
if (leftUnspecified) {
|
||||
topLeft = new ResultPoint(0, topRight.getY());
|
||||
bottomLeft = new ResultPoint(0, bottomRight.getY());
|
||||
} else if (rightUnspecified) {
|
||||
topRight = new ResultPoint(image.getWidth() - 1, topLeft.getY());
|
||||
bottomRight = new ResultPoint(image.getWidth() - 1, bottomLeft.getY());
|
||||
}
|
||||
this.image = image;
|
||||
this.topLeft = topLeft;
|
||||
this.bottomLeft = bottomLeft;
|
||||
this.topRight = topRight;
|
||||
this.bottomRight = bottomRight;
|
||||
this.minX = (int) Math.min(topLeft.getX(), bottomLeft.getX());
|
||||
this.maxX = (int) Math.max(topRight.getX(), bottomRight.getX());
|
||||
this.minY = (int) Math.min(topLeft.getY(), topRight.getY());
|
||||
this.maxY = (int) Math.max(bottomLeft.getY(), bottomRight.getY());
|
||||
}
|
||||
|
||||
BoundingBox(BoundingBox boundingBox) {
|
||||
this.image = boundingBox.image;
|
||||
this.topLeft = boundingBox.topLeft;
|
||||
this.bottomLeft = boundingBox.bottomLeft;
|
||||
this.topRight = boundingBox.topRight;
|
||||
this.bottomRight = boundingBox.bottomRight;
|
||||
this.minX = boundingBox.minX;
|
||||
this.maxX = boundingBox.maxX;
|
||||
this.minY = boundingBox.minY;
|
||||
this.maxY = boundingBox.maxY;
|
||||
}
|
||||
|
||||
static BoundingBox merge(BoundingBox leftBox, BoundingBox rightBox) throws NotFoundException {
|
||||
if (leftBox == null) {
|
||||
return rightBox;
|
||||
}
|
||||
if (rightBox == null) {
|
||||
return leftBox;
|
||||
}
|
||||
return new BoundingBox(leftBox.image, leftBox.topLeft, leftBox.bottomLeft, rightBox.topRight, rightBox.bottomRight);
|
||||
}
|
||||
|
||||
BoundingBox addMissingRows(int missingStartRows, int missingEndRows, boolean isLeft) throws NotFoundException {
|
||||
ResultPoint newTopLeft = topLeft;
|
||||
ResultPoint newBottomLeft = bottomLeft;
|
||||
ResultPoint newTopRight = topRight;
|
||||
ResultPoint newBottomRight = bottomRight;
|
||||
|
||||
if (missingStartRows > 0) {
|
||||
ResultPoint top = isLeft ? topLeft : topRight;
|
||||
int newMinY = (int) top.getY() - missingStartRows;
|
||||
if (newMinY < 0) {
|
||||
newMinY = 0;
|
||||
}
|
||||
ResultPoint newTop = new ResultPoint(top.getX(), newMinY);
|
||||
if (isLeft) {
|
||||
newTopLeft = newTop;
|
||||
} else {
|
||||
newTopRight = newTop;
|
||||
}
|
||||
}
|
||||
|
||||
if (missingEndRows > 0) {
|
||||
ResultPoint bottom = isLeft ? bottomLeft : bottomRight;
|
||||
int newMaxY = (int) bottom.getY() + missingEndRows;
|
||||
if (newMaxY >= image.getHeight()) {
|
||||
newMaxY = image.getHeight() - 1;
|
||||
}
|
||||
ResultPoint newBottom = new ResultPoint(bottom.getX(), newMaxY);
|
||||
if (isLeft) {
|
||||
newBottomLeft = newBottom;
|
||||
} else {
|
||||
newBottomRight = newBottom;
|
||||
}
|
||||
}
|
||||
|
||||
return new BoundingBox(image, newTopLeft, newBottomLeft, newTopRight, newBottomRight);
|
||||
}
|
||||
|
||||
int getMinX() {
|
||||
return minX;
|
||||
}
|
||||
|
||||
int getMaxX() {
|
||||
return maxX;
|
||||
}
|
||||
|
||||
int getMinY() {
|
||||
return minY;
|
||||
}
|
||||
|
||||
int getMaxY() {
|
||||
return maxY;
|
||||
}
|
||||
|
||||
ResultPoint getTopLeft() {
|
||||
return topLeft;
|
||||
}
|
||||
|
||||
ResultPoint getTopRight() {
|
||||
return topRight;
|
||||
}
|
||||
|
||||
ResultPoint getBottomLeft() {
|
||||
return bottomLeft;
|
||||
}
|
||||
|
||||
ResultPoint getBottomRight() {
|
||||
return bottomRight;
|
||||
}
|
||||
|
||||
}
|
||||
84
src/pdf417/decoder/Codeword.java
Normal file
84
src/pdf417/decoder/Codeword.java
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Guenther Grau
|
||||
*/
|
||||
final class Codeword {
|
||||
|
||||
private static final int BARCODE_ROW_UNKNOWN = -1;
|
||||
|
||||
private final int startX;
|
||||
private final int endX;
|
||||
private final int bucket;
|
||||
private final int value;
|
||||
private int rowNumber = BARCODE_ROW_UNKNOWN;
|
||||
|
||||
Codeword(int startX, int endX, int bucket, int value) {
|
||||
this.startX = startX;
|
||||
this.endX = endX;
|
||||
this.bucket = bucket;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
boolean hasValidRowNumber() {
|
||||
return isValidRowNumber(rowNumber);
|
||||
}
|
||||
|
||||
boolean isValidRowNumber(int rowNumber) {
|
||||
return rowNumber != BARCODE_ROW_UNKNOWN && bucket == (rowNumber % 3) * 3;
|
||||
}
|
||||
|
||||
void setRowNumberAsRowIndicatorColumn() {
|
||||
rowNumber = (value / 30) * 3 + bucket / 3;
|
||||
}
|
||||
|
||||
int getWidth() {
|
||||
return endX - startX;
|
||||
}
|
||||
|
||||
int getStartX() {
|
||||
return startX;
|
||||
}
|
||||
|
||||
int getEndX() {
|
||||
return endX;
|
||||
}
|
||||
|
||||
int getBucket() {
|
||||
return bucket;
|
||||
}
|
||||
|
||||
int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
int getRowNumber() {
|
||||
return rowNumber;
|
||||
}
|
||||
|
||||
void setRowNumber(int rowNumber) {
|
||||
this.rowNumber = rowNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return rowNumber + "|" + value;
|
||||
}
|
||||
|
||||
}
|
||||
698
src/pdf417/decoder/DecodedBitStreamParser.java
Normal file
698
src/pdf417/decoder/DecodedBitStreamParser.java
Normal file
@@ -0,0 +1,698 @@
|
||||
/*
|
||||
* Copyright 2009 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.FormatException;
|
||||
import com.google.zxing.common.ECIStringBuilder;
|
||||
import com.google.zxing.common.DecoderResult;
|
||||
import com.google.zxing.pdf417.PDF417ResultMetadata;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* <p>This class contains the methods for decoding the PDF417 codewords.</p>
|
||||
*
|
||||
* @author SITA Lab (kevin.osullivan@sita.aero)
|
||||
* @author Guenther Grau
|
||||
*/
|
||||
final class DecodedBitStreamParser {
|
||||
|
||||
private enum Mode {
|
||||
ALPHA,
|
||||
LOWER,
|
||||
MIXED,
|
||||
PUNCT,
|
||||
ALPHA_SHIFT,
|
||||
PUNCT_SHIFT
|
||||
}
|
||||
|
||||
private static final int TEXT_COMPACTION_MODE_LATCH = 900;
|
||||
private static final int BYTE_COMPACTION_MODE_LATCH = 901;
|
||||
private static final int NUMERIC_COMPACTION_MODE_LATCH = 902;
|
||||
private static final int BYTE_COMPACTION_MODE_LATCH_6 = 924;
|
||||
private static final int ECI_USER_DEFINED = 925;
|
||||
private static final int ECI_GENERAL_PURPOSE = 926;
|
||||
private static final int ECI_CHARSET = 927;
|
||||
private static final int BEGIN_MACRO_PDF417_CONTROL_BLOCK = 928;
|
||||
private static final int BEGIN_MACRO_PDF417_OPTIONAL_FIELD = 923;
|
||||
private static final int MACRO_PDF417_TERMINATOR = 922;
|
||||
private static final int MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913;
|
||||
private static final int MAX_NUMERIC_CODEWORDS = 15;
|
||||
|
||||
private static final int MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME = 0;
|
||||
private static final int MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT = 1;
|
||||
private static final int MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP = 2;
|
||||
private static final int MACRO_PDF417_OPTIONAL_FIELD_SENDER = 3;
|
||||
private static final int MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE = 4;
|
||||
private static final int MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE = 5;
|
||||
private static final int MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM = 6;
|
||||
|
||||
private static final int PL = 25;
|
||||
private static final int LL = 27;
|
||||
private static final int AS = 27;
|
||||
private static final int ML = 28;
|
||||
private static final int AL = 28;
|
||||
private static final int PS = 29;
|
||||
private static final int PAL = 29;
|
||||
|
||||
private static final char[] PUNCT_CHARS =
|
||||
";<>@[\\]_`~!\r\t,:\n-.$/\"|*()?{}'".toCharArray();
|
||||
|
||||
private static final char[] MIXED_CHARS =
|
||||
"0123456789&\r\t,:#-.$/+%*=^".toCharArray();
|
||||
|
||||
/**
|
||||
* Table containing values for the exponent of 900.
|
||||
* This is used in the numeric compaction decode algorithm.
|
||||
*/
|
||||
private static final BigInteger[] EXP900;
|
||||
|
||||
static {
|
||||
EXP900 = new BigInteger[16];
|
||||
EXP900[0] = BigInteger.ONE;
|
||||
BigInteger nineHundred = BigInteger.valueOf(900);
|
||||
EXP900[1] = nineHundred;
|
||||
for (int i = 2; i < EXP900.length; i++) {
|
||||
EXP900[i] = EXP900[i - 1].multiply(nineHundred);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int NUMBER_OF_SEQUENCE_CODEWORDS = 2;
|
||||
|
||||
private DecodedBitStreamParser() {
|
||||
}
|
||||
|
||||
static DecoderResult decode(int[] codewords, String ecLevel) throws FormatException {
|
||||
ECIStringBuilder result = new ECIStringBuilder(codewords.length * 2);
|
||||
int codeIndex = textCompaction(codewords, 1, result);
|
||||
PDF417ResultMetadata resultMetadata = new PDF417ResultMetadata();
|
||||
while (codeIndex < codewords[0]) {
|
||||
int code = codewords[codeIndex++];
|
||||
switch (code) {
|
||||
case TEXT_COMPACTION_MODE_LATCH:
|
||||
codeIndex = textCompaction(codewords, codeIndex, result);
|
||||
break;
|
||||
case BYTE_COMPACTION_MODE_LATCH:
|
||||
case BYTE_COMPACTION_MODE_LATCH_6:
|
||||
codeIndex = byteCompaction(code, codewords, codeIndex, result);
|
||||
break;
|
||||
case MODE_SHIFT_TO_BYTE_COMPACTION_MODE:
|
||||
result.append((char) codewords[codeIndex++]);
|
||||
break;
|
||||
case NUMERIC_COMPACTION_MODE_LATCH:
|
||||
codeIndex = numericCompaction(codewords, codeIndex, result);
|
||||
break;
|
||||
case ECI_CHARSET:
|
||||
result.appendECI(codewords[codeIndex++]);
|
||||
break;
|
||||
case ECI_GENERAL_PURPOSE:
|
||||
// Can't do anything with generic ECI; skip its 2 characters
|
||||
codeIndex += 2;
|
||||
break;
|
||||
case ECI_USER_DEFINED:
|
||||
// Can't do anything with user ECI; skip its 1 character
|
||||
codeIndex++;
|
||||
break;
|
||||
case BEGIN_MACRO_PDF417_CONTROL_BLOCK:
|
||||
codeIndex = decodeMacroBlock(codewords, codeIndex, resultMetadata);
|
||||
break;
|
||||
case BEGIN_MACRO_PDF417_OPTIONAL_FIELD:
|
||||
case MACRO_PDF417_TERMINATOR:
|
||||
// Should not see these outside a macro block
|
||||
throw FormatException.getFormatInstance();
|
||||
default:
|
||||
// Default to text compaction. During testing numerous barcodes
|
||||
// appeared to be missing the starting mode. In these cases defaulting
|
||||
// to text compaction seems to work.
|
||||
codeIndex--;
|
||||
codeIndex = textCompaction(codewords, codeIndex, result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result.isEmpty() && resultMetadata.getFileId() == null) {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
DecoderResult decoderResult = new DecoderResult(null, result.toString(), null, ecLevel);
|
||||
decoderResult.setOther(resultMetadata);
|
||||
return decoderResult;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
static int decodeMacroBlock(int[] codewords, int codeIndex, PDF417ResultMetadata resultMetadata)
|
||||
throws FormatException {
|
||||
if (codeIndex + NUMBER_OF_SEQUENCE_CODEWORDS > codewords[0]) {
|
||||
// we must have at least two bytes left for the segment index
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
int[] segmentIndexArray = new int[NUMBER_OF_SEQUENCE_CODEWORDS];
|
||||
for (int i = 0; i < NUMBER_OF_SEQUENCE_CODEWORDS; i++, codeIndex++) {
|
||||
segmentIndexArray[i] = codewords[codeIndex];
|
||||
}
|
||||
String segmentIndexString = decodeBase900toBase10(segmentIndexArray, NUMBER_OF_SEQUENCE_CODEWORDS);
|
||||
if (segmentIndexString.isEmpty()) {
|
||||
resultMetadata.setSegmentIndex(0);
|
||||
} else {
|
||||
try {
|
||||
resultMetadata.setSegmentIndex(Integer.parseInt(segmentIndexString));
|
||||
} catch (NumberFormatException nfe) {
|
||||
// too large; bad input?
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
}
|
||||
|
||||
// Decoding the fileId codewords as 0-899 numbers, each 0-filled to width 3. This follows the spec
|
||||
// (See ISO/IEC 15438:2015 Annex H.6) and preserves all info, but some generators (e.g. TEC-IT) write
|
||||
// the fileId using text compaction, so in those cases the fileId will appear mangled.
|
||||
StringBuilder fileId = new StringBuilder();
|
||||
while (codeIndex < codewords[0] &&
|
||||
codeIndex < codewords.length &&
|
||||
codewords[codeIndex] != MACRO_PDF417_TERMINATOR &&
|
||||
codewords[codeIndex] != BEGIN_MACRO_PDF417_OPTIONAL_FIELD) {
|
||||
fileId.append(String.format("%03d", codewords[codeIndex]));
|
||||
codeIndex++;
|
||||
}
|
||||
if (fileId.length() == 0) {
|
||||
// at least one fileId codeword is required (Annex H.2)
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
resultMetadata.setFileId(fileId.toString());
|
||||
|
||||
int optionalFieldsStart = -1;
|
||||
if (codewords[codeIndex] == BEGIN_MACRO_PDF417_OPTIONAL_FIELD) {
|
||||
optionalFieldsStart = codeIndex + 1;
|
||||
}
|
||||
|
||||
while (codeIndex < codewords[0]) {
|
||||
switch (codewords[codeIndex]) {
|
||||
case BEGIN_MACRO_PDF417_OPTIONAL_FIELD:
|
||||
codeIndex++;
|
||||
switch (codewords[codeIndex]) {
|
||||
case MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME:
|
||||
ECIStringBuilder fileName = new ECIStringBuilder();
|
||||
codeIndex = textCompaction(codewords, codeIndex + 1, fileName);
|
||||
resultMetadata.setFileName(fileName.toString());
|
||||
break;
|
||||
case MACRO_PDF417_OPTIONAL_FIELD_SENDER:
|
||||
ECIStringBuilder sender = new ECIStringBuilder();
|
||||
codeIndex = textCompaction(codewords, codeIndex + 1, sender);
|
||||
resultMetadata.setSender(sender.toString());
|
||||
break;
|
||||
case MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE:
|
||||
ECIStringBuilder addressee = new ECIStringBuilder();
|
||||
codeIndex = textCompaction(codewords, codeIndex + 1, addressee);
|
||||
resultMetadata.setAddressee(addressee.toString());
|
||||
break;
|
||||
case MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT:
|
||||
ECIStringBuilder segmentCount = new ECIStringBuilder();
|
||||
codeIndex = numericCompaction(codewords, codeIndex + 1, segmentCount);
|
||||
resultMetadata.setSegmentCount(Integer.parseInt(segmentCount.toString()));
|
||||
break;
|
||||
case MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP:
|
||||
ECIStringBuilder timestamp = new ECIStringBuilder();
|
||||
codeIndex = numericCompaction(codewords, codeIndex + 1, timestamp);
|
||||
resultMetadata.setTimestamp(Long.parseLong(timestamp.toString()));
|
||||
break;
|
||||
case MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM:
|
||||
ECIStringBuilder checksum = new ECIStringBuilder();
|
||||
codeIndex = numericCompaction(codewords, codeIndex + 1, checksum);
|
||||
resultMetadata.setChecksum(Integer.parseInt(checksum.toString()));
|
||||
break;
|
||||
case MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE:
|
||||
ECIStringBuilder fileSize = new ECIStringBuilder();
|
||||
codeIndex = numericCompaction(codewords, codeIndex + 1, fileSize);
|
||||
resultMetadata.setFileSize(Long.parseLong(fileSize.toString()));
|
||||
break;
|
||||
default:
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
break;
|
||||
case MACRO_PDF417_TERMINATOR:
|
||||
codeIndex++;
|
||||
resultMetadata.setLastSegment(true);
|
||||
break;
|
||||
default:
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
}
|
||||
|
||||
// copy optional fields to additional options
|
||||
if (optionalFieldsStart != -1) {
|
||||
int optionalFieldsLength = codeIndex - optionalFieldsStart;
|
||||
if (resultMetadata.isLastSegment()) {
|
||||
// do not include terminator
|
||||
optionalFieldsLength--;
|
||||
}
|
||||
resultMetadata.setOptionalData(
|
||||
Arrays.copyOfRange(codewords, optionalFieldsStart, optionalFieldsStart + optionalFieldsLength));
|
||||
}
|
||||
|
||||
return codeIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Text Compaction mode (see 5.4.1.5) permits all printable ASCII characters to be
|
||||
* encoded, i.e. values 32 - 126 inclusive in accordance with ISO/IEC 646 (IRV), as
|
||||
* well as selected control characters.
|
||||
*
|
||||
* @param codewords The array of codewords (data + error)
|
||||
* @param codeIndex The current index into the codeword array.
|
||||
* @param result The decoded data is appended to the result.
|
||||
* @return The next index into the codeword array.
|
||||
*/
|
||||
private static int textCompaction(int[] codewords, int codeIndex, ECIStringBuilder result) throws FormatException {
|
||||
// 2 character per codeword
|
||||
int[] textCompactionData = new int[(codewords[0] - codeIndex) * 2];
|
||||
// Used to hold the byte compaction value if there is a mode shift
|
||||
int[] byteCompactionData = new int[(codewords[0] - codeIndex) * 2];
|
||||
|
||||
int index = 0;
|
||||
boolean end = false;
|
||||
Mode subMode = Mode.ALPHA;
|
||||
while ((codeIndex < codewords[0]) && !end) {
|
||||
int code = codewords[codeIndex++];
|
||||
if (code < TEXT_COMPACTION_MODE_LATCH) {
|
||||
textCompactionData[index] = code / 30;
|
||||
textCompactionData[index + 1] = code % 30;
|
||||
index += 2;
|
||||
} else {
|
||||
switch (code) {
|
||||
case TEXT_COMPACTION_MODE_LATCH:
|
||||
// reinitialize text compaction mode to alpha sub mode
|
||||
textCompactionData[index++] = TEXT_COMPACTION_MODE_LATCH;
|
||||
break;
|
||||
case BYTE_COMPACTION_MODE_LATCH:
|
||||
case BYTE_COMPACTION_MODE_LATCH_6:
|
||||
case NUMERIC_COMPACTION_MODE_LATCH:
|
||||
case BEGIN_MACRO_PDF417_CONTROL_BLOCK:
|
||||
case BEGIN_MACRO_PDF417_OPTIONAL_FIELD:
|
||||
case MACRO_PDF417_TERMINATOR:
|
||||
codeIndex--;
|
||||
end = true;
|
||||
break;
|
||||
case MODE_SHIFT_TO_BYTE_COMPACTION_MODE:
|
||||
// The Mode Shift codeword 913 shall cause a temporary
|
||||
// switch from Text Compaction mode to Byte Compaction mode.
|
||||
// This switch shall be in effect for only the next codeword,
|
||||
// after which the mode shall revert to the prevailing sub-mode
|
||||
// of the Text Compaction mode. Codeword 913 is only available
|
||||
// in Text Compaction mode; its use is described in 5.4.2.4.
|
||||
textCompactionData[index] = MODE_SHIFT_TO_BYTE_COMPACTION_MODE;
|
||||
code = codewords[codeIndex++];
|
||||
byteCompactionData[index] = code;
|
||||
index++;
|
||||
break;
|
||||
case ECI_CHARSET:
|
||||
subMode = decodeTextCompaction(textCompactionData, byteCompactionData, index, result, subMode);
|
||||
result.appendECI(codewords[codeIndex++]);
|
||||
textCompactionData = new int[(codewords[0] - codeIndex) * 2];
|
||||
byteCompactionData = new int[(codewords[0] - codeIndex) * 2];
|
||||
index = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
decodeTextCompaction(textCompactionData, byteCompactionData, index, result, subMode);
|
||||
return codeIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Text Compaction mode includes all the printable ASCII characters
|
||||
* (i.e. values from 32 to 126) and three ASCII control characters: HT or tab
|
||||
* (ASCII value 9), LF or line feed (ASCII value 10), and CR or carriage
|
||||
* return (ASCII value 13). The Text Compaction mode also includes various latch
|
||||
* and shift characters which are used exclusively within the mode. The Text
|
||||
* Compaction mode encodes up to 2 characters per codeword. The compaction rules
|
||||
* for converting data into PDF417 codewords are defined in 5.4.2.2. The sub-mode
|
||||
* switches are defined in 5.4.2.3.
|
||||
*
|
||||
* @param textCompactionData The text compaction data.
|
||||
* @param byteCompactionData The byte compaction data if there
|
||||
* was a mode shift.
|
||||
* @param length The size of the text compaction and byte compaction data.
|
||||
* @param result The decoded data is appended to the result.
|
||||
* @param startMode The mode in which decoding starts
|
||||
* @return The mode in which decoding ended
|
||||
*/
|
||||
private static Mode decodeTextCompaction(int[] textCompactionData,
|
||||
int[] byteCompactionData,
|
||||
int length,
|
||||
ECIStringBuilder result,
|
||||
Mode startMode) {
|
||||
// Beginning from an initial state
|
||||
// The default compaction mode for PDF417 in effect at the start of each symbol shall always be Text
|
||||
// Compaction mode Alpha sub-mode (uppercase alphabetic). A latch codeword from another mode to the Text
|
||||
// Compaction mode shall always switch to the Text Compaction Alpha sub-mode.
|
||||
Mode subMode = startMode;
|
||||
Mode priorToShiftMode = startMode;
|
||||
Mode latchedMode = startMode;
|
||||
int i = 0;
|
||||
while (i < length) {
|
||||
int subModeCh = textCompactionData[i];
|
||||
char ch = 0;
|
||||
switch (subMode) {
|
||||
case ALPHA:
|
||||
// Alpha (uppercase alphabetic)
|
||||
if (subModeCh < 26) {
|
||||
// Upper case Alpha Character
|
||||
ch = (char) ('A' + subModeCh);
|
||||
} else {
|
||||
switch (subModeCh) {
|
||||
case 26:
|
||||
ch = ' ';
|
||||
break;
|
||||
case LL:
|
||||
subMode = Mode.LOWER;
|
||||
latchedMode = subMode;
|
||||
break;
|
||||
case ML:
|
||||
subMode = Mode.MIXED;
|
||||
latchedMode = subMode;
|
||||
break;
|
||||
case PS:
|
||||
// Shift to punctuation
|
||||
priorToShiftMode = subMode;
|
||||
subMode = Mode.PUNCT_SHIFT;
|
||||
break;
|
||||
case MODE_SHIFT_TO_BYTE_COMPACTION_MODE:
|
||||
result.append((char) byteCompactionData[i]);
|
||||
break;
|
||||
case TEXT_COMPACTION_MODE_LATCH:
|
||||
subMode = Mode.ALPHA;
|
||||
latchedMode = subMode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LOWER:
|
||||
// Lower (lowercase alphabetic)
|
||||
if (subModeCh < 26) {
|
||||
ch = (char) ('a' + subModeCh);
|
||||
} else {
|
||||
switch (subModeCh) {
|
||||
case 26:
|
||||
ch = ' ';
|
||||
break;
|
||||
case AS:
|
||||
// Shift to alpha
|
||||
priorToShiftMode = subMode;
|
||||
subMode = Mode.ALPHA_SHIFT;
|
||||
break;
|
||||
case ML:
|
||||
subMode = Mode.MIXED;
|
||||
latchedMode = subMode;
|
||||
break;
|
||||
case PS:
|
||||
// Shift to punctuation
|
||||
priorToShiftMode = subMode;
|
||||
subMode = Mode.PUNCT_SHIFT;
|
||||
break;
|
||||
case MODE_SHIFT_TO_BYTE_COMPACTION_MODE:
|
||||
result.append((char) byteCompactionData[i]);
|
||||
break;
|
||||
case TEXT_COMPACTION_MODE_LATCH:
|
||||
subMode = Mode.ALPHA;
|
||||
latchedMode = subMode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MIXED:
|
||||
// Mixed (numeric and some punctuation)
|
||||
if (subModeCh < PL) {
|
||||
ch = MIXED_CHARS[subModeCh];
|
||||
} else {
|
||||
switch (subModeCh) {
|
||||
case PL:
|
||||
subMode = Mode.PUNCT;
|
||||
latchedMode = subMode;
|
||||
break;
|
||||
case 26:
|
||||
ch = ' ';
|
||||
break;
|
||||
case LL:
|
||||
subMode = Mode.LOWER;
|
||||
latchedMode = subMode;
|
||||
break;
|
||||
case AL:
|
||||
case TEXT_COMPACTION_MODE_LATCH:
|
||||
subMode = Mode.ALPHA;
|
||||
latchedMode = subMode;
|
||||
break;
|
||||
case PS:
|
||||
// Shift to punctuation
|
||||
priorToShiftMode = subMode;
|
||||
subMode = Mode.PUNCT_SHIFT;
|
||||
break;
|
||||
case MODE_SHIFT_TO_BYTE_COMPACTION_MODE:
|
||||
result.append((char) byteCompactionData[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PUNCT:
|
||||
// Punctuation
|
||||
if (subModeCh < PAL) {
|
||||
ch = PUNCT_CHARS[subModeCh];
|
||||
} else {
|
||||
switch (subModeCh) {
|
||||
case PAL:
|
||||
case TEXT_COMPACTION_MODE_LATCH:
|
||||
subMode = Mode.ALPHA;
|
||||
latchedMode = subMode;
|
||||
break;
|
||||
case MODE_SHIFT_TO_BYTE_COMPACTION_MODE:
|
||||
result.append((char) byteCompactionData[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ALPHA_SHIFT:
|
||||
// Restore sub-mode
|
||||
subMode = priorToShiftMode;
|
||||
if (subModeCh < 26) {
|
||||
ch = (char) ('A' + subModeCh);
|
||||
} else {
|
||||
switch (subModeCh) {
|
||||
case 26:
|
||||
ch = ' ';
|
||||
break;
|
||||
case TEXT_COMPACTION_MODE_LATCH:
|
||||
subMode = Mode.ALPHA;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PUNCT_SHIFT:
|
||||
// Restore sub-mode
|
||||
subMode = priorToShiftMode;
|
||||
if (subModeCh < PAL) {
|
||||
ch = PUNCT_CHARS[subModeCh];
|
||||
} else {
|
||||
switch (subModeCh) {
|
||||
case PAL:
|
||||
case TEXT_COMPACTION_MODE_LATCH:
|
||||
subMode = Mode.ALPHA;
|
||||
break;
|
||||
case MODE_SHIFT_TO_BYTE_COMPACTION_MODE:
|
||||
// PS before Shift-to-Byte is used as a padding character,
|
||||
// see 5.4.2.4 of the specification
|
||||
result.append((char) byteCompactionData[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (ch != 0) {
|
||||
// Append decoded character to result
|
||||
result.append(ch);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return latchedMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Byte Compaction mode (see 5.4.3) permits all 256 possible 8-bit byte values to be encoded.
|
||||
* This includes all ASCII characters value 0 to 127 inclusive and provides for international
|
||||
* character set support.
|
||||
*
|
||||
* @param mode The byte compaction mode i.e. 901 or 924
|
||||
* @param codewords The array of codewords (data + error)
|
||||
* @param codeIndex The current index into the codeword array.
|
||||
* @param result The decoded data is appended to the result.
|
||||
* @return The next index into the codeword array.
|
||||
*/
|
||||
private static int byteCompaction(int mode,
|
||||
int[] codewords,
|
||||
int codeIndex,
|
||||
ECIStringBuilder result) throws FormatException {
|
||||
boolean end = false;
|
||||
|
||||
while (codeIndex < codewords[0] && !end) {
|
||||
//handle leading ECIs
|
||||
while (codeIndex < codewords[0] && codewords[codeIndex] == ECI_CHARSET) {
|
||||
result.appendECI(codewords[++codeIndex]);
|
||||
codeIndex++;
|
||||
}
|
||||
|
||||
if (codeIndex >= codewords[0] || codewords[codeIndex] >= TEXT_COMPACTION_MODE_LATCH) {
|
||||
end = true;
|
||||
} else {
|
||||
//decode one block of 5 codewords to 6 bytes
|
||||
long value = 0;
|
||||
int count = 0;
|
||||
do {
|
||||
value = 900 * value + codewords[codeIndex++];
|
||||
count++;
|
||||
} while (count < 5 &&
|
||||
codeIndex < codewords[0] &&
|
||||
codewords[codeIndex] < TEXT_COMPACTION_MODE_LATCH);
|
||||
if (count == 5 && (mode == BYTE_COMPACTION_MODE_LATCH_6 ||
|
||||
codeIndex < codewords[0] &&
|
||||
codewords[codeIndex] < TEXT_COMPACTION_MODE_LATCH)) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
result.append((byte) (value >> (8 * (5 - i))));
|
||||
}
|
||||
} else {
|
||||
codeIndex -= count;
|
||||
while ((codeIndex < codewords[0]) && !end) {
|
||||
int code = codewords[codeIndex++];
|
||||
if (code < TEXT_COMPACTION_MODE_LATCH) {
|
||||
result.append((byte) code);
|
||||
} else if (code == ECI_CHARSET) {
|
||||
result.appendECI(codewords[codeIndex++]);
|
||||
} else {
|
||||
codeIndex--;
|
||||
end = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return codeIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Numeric Compaction mode (see 5.4.4) permits efficient encoding of numeric data strings.
|
||||
*
|
||||
* @param codewords The array of codewords (data + error)
|
||||
* @param codeIndex The current index into the codeword array.
|
||||
* @param result The decoded data is appended to the result.
|
||||
* @return The next index into the codeword array.
|
||||
*/
|
||||
private static int numericCompaction(int[] codewords, int codeIndex, ECIStringBuilder result) throws FormatException {
|
||||
int count = 0;
|
||||
boolean end = false;
|
||||
|
||||
int[] numericCodewords = new int[MAX_NUMERIC_CODEWORDS];
|
||||
|
||||
while (codeIndex < codewords[0] && !end) {
|
||||
int code = codewords[codeIndex++];
|
||||
if (codeIndex == codewords[0]) {
|
||||
end = true;
|
||||
}
|
||||
if (code < TEXT_COMPACTION_MODE_LATCH) {
|
||||
numericCodewords[count] = code;
|
||||
count++;
|
||||
} else {
|
||||
switch (code) {
|
||||
case TEXT_COMPACTION_MODE_LATCH:
|
||||
case BYTE_COMPACTION_MODE_LATCH:
|
||||
case BYTE_COMPACTION_MODE_LATCH_6:
|
||||
case BEGIN_MACRO_PDF417_CONTROL_BLOCK:
|
||||
case BEGIN_MACRO_PDF417_OPTIONAL_FIELD:
|
||||
case MACRO_PDF417_TERMINATOR:
|
||||
case ECI_CHARSET:
|
||||
codeIndex--;
|
||||
end = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((count % MAX_NUMERIC_CODEWORDS == 0 || code == NUMERIC_COMPACTION_MODE_LATCH || end) && count > 0) {
|
||||
// Re-invoking Numeric Compaction mode (by using codeword 902
|
||||
// while in Numeric Compaction mode) serves to terminate the
|
||||
// current Numeric Compaction mode grouping as described in 5.4.4.2,
|
||||
// and then to start a new one grouping.
|
||||
result.append(decodeBase900toBase10(numericCodewords, count));
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
return codeIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list of Numeric Compacted codewords from Base 900 to Base 10.
|
||||
*
|
||||
* @param codewords The array of codewords
|
||||
* @param count The number of codewords
|
||||
* @return The decoded string representing the Numeric data.
|
||||
*/
|
||||
/*
|
||||
EXAMPLE
|
||||
Encode the fifteen digit numeric string 000213298174000
|
||||
Prefix the numeric string with a 1 and set the initial value of
|
||||
t = 1 000 213 298 174 000
|
||||
Calculate codeword 0
|
||||
d0 = 1 000 213 298 174 000 mod 900 = 200
|
||||
|
||||
t = 1 000 213 298 174 000 div 900 = 1 111 348 109 082
|
||||
Calculate codeword 1
|
||||
d1 = 1 111 348 109 082 mod 900 = 282
|
||||
|
||||
t = 1 111 348 109 082 div 900 = 1 234 831 232
|
||||
Calculate codeword 2
|
||||
d2 = 1 234 831 232 mod 900 = 632
|
||||
|
||||
t = 1 234 831 232 div 900 = 1 372 034
|
||||
Calculate codeword 3
|
||||
d3 = 1 372 034 mod 900 = 434
|
||||
|
||||
t = 1 372 034 div 900 = 1 524
|
||||
Calculate codeword 4
|
||||
d4 = 1 524 mod 900 = 624
|
||||
|
||||
t = 1 524 div 900 = 1
|
||||
Calculate codeword 5
|
||||
d5 = 1 mod 900 = 1
|
||||
t = 1 div 900 = 0
|
||||
Codeword sequence is: 1, 624, 434, 632, 282, 200
|
||||
|
||||
Decode the above codewords involves
|
||||
1 x 900 power of 5 + 624 x 900 power of 4 + 434 x 900 power of 3 +
|
||||
632 x 900 power of 2 + 282 x 900 power of 1 + 200 x 900 power of 0 = 1000213298174000
|
||||
|
||||
Remove leading 1 => Result is 000213298174000
|
||||
*/
|
||||
private static String decodeBase900toBase10(int[] codewords, int count) throws FormatException {
|
||||
BigInteger result = BigInteger.ZERO;
|
||||
for (int i = 0; i < count; i++) {
|
||||
result = result.add(EXP900[count - i - 1].multiply(BigInteger.valueOf(codewords[i])));
|
||||
}
|
||||
String resultString = result.toString();
|
||||
if (resultString.charAt(0) != '1') {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
return resultString.substring(1);
|
||||
}
|
||||
|
||||
}
|
||||
299
src/pdf417/decoder/DetectionResult.java
Normal file
299
src/pdf417/decoder/DetectionResult.java
Normal file
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* 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.pdf417.PDF417Common;
|
||||
|
||||
import java.util.Formatter;
|
||||
|
||||
/**
|
||||
* @author Guenther Grau
|
||||
*/
|
||||
final class DetectionResult {
|
||||
|
||||
private static final int ADJUST_ROW_NUMBER_SKIP = 2;
|
||||
|
||||
private final BarcodeMetadata barcodeMetadata;
|
||||
private final DetectionResultColumn[] detectionResultColumns;
|
||||
private BoundingBox boundingBox;
|
||||
private final int barcodeColumnCount;
|
||||
|
||||
DetectionResult(BarcodeMetadata barcodeMetadata, BoundingBox boundingBox) {
|
||||
this.barcodeMetadata = barcodeMetadata;
|
||||
this.barcodeColumnCount = barcodeMetadata.getColumnCount();
|
||||
this.boundingBox = boundingBox;
|
||||
detectionResultColumns = new DetectionResultColumn[barcodeColumnCount + 2];
|
||||
}
|
||||
|
||||
DetectionResultColumn[] getDetectionResultColumns() {
|
||||
adjustIndicatorColumnRowNumbers(detectionResultColumns[0]);
|
||||
adjustIndicatorColumnRowNumbers(detectionResultColumns[barcodeColumnCount + 1]);
|
||||
int unadjustedCodewordCount = PDF417Common.MAX_CODEWORDS_IN_BARCODE;
|
||||
int previousUnadjustedCount;
|
||||
do {
|
||||
previousUnadjustedCount = unadjustedCodewordCount;
|
||||
unadjustedCodewordCount = adjustRowNumbers();
|
||||
} while (unadjustedCodewordCount > 0 && unadjustedCodewordCount < previousUnadjustedCount);
|
||||
return detectionResultColumns;
|
||||
}
|
||||
|
||||
private void adjustIndicatorColumnRowNumbers(DetectionResultColumn detectionResultColumn) {
|
||||
if (detectionResultColumn != null) {
|
||||
((DetectionResultRowIndicatorColumn) detectionResultColumn)
|
||||
.adjustCompleteIndicatorColumnRowNumbers(barcodeMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO ensure that no detected codewords with unknown row number are left
|
||||
// we should be able to estimate the row height and use it as a hint for the row number
|
||||
// we should also fill the rows top to bottom and bottom to top
|
||||
/**
|
||||
* @return number of codewords which don't have a valid row number. Note that the count is not accurate as codewords
|
||||
* will be counted several times. It just serves as an indicator to see when we can stop adjusting row numbers
|
||||
*/
|
||||
private int adjustRowNumbers() {
|
||||
int unadjustedCount = adjustRowNumbersByRow();
|
||||
if (unadjustedCount == 0) {
|
||||
return 0;
|
||||
}
|
||||
for (int barcodeColumn = 1; barcodeColumn < barcodeColumnCount + 1; barcodeColumn++) {
|
||||
Codeword[] codewords = detectionResultColumns[barcodeColumn].getCodewords();
|
||||
for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) {
|
||||
if (codewords[codewordsRow] == null) {
|
||||
continue;
|
||||
}
|
||||
if (!codewords[codewordsRow].hasValidRowNumber()) {
|
||||
adjustRowNumbers(barcodeColumn, codewordsRow, codewords);
|
||||
}
|
||||
}
|
||||
}
|
||||
return unadjustedCount;
|
||||
}
|
||||
|
||||
private int adjustRowNumbersByRow() {
|
||||
adjustRowNumbersFromBothRI();
|
||||
// TODO we should only do full row adjustments if row numbers of left and right row indicator column match.
|
||||
// Maybe it's even better to calculated the height (in codeword rows) and divide it by the number of barcode
|
||||
// rows. This, together with the LRI and RRI row numbers should allow us to get a good estimate where a row
|
||||
// number starts and ends.
|
||||
int unadjustedCount = adjustRowNumbersFromLRI();
|
||||
return unadjustedCount + adjustRowNumbersFromRRI();
|
||||
}
|
||||
|
||||
private void adjustRowNumbersFromBothRI() {
|
||||
if (detectionResultColumns[0] == null || detectionResultColumns[barcodeColumnCount + 1] == null) {
|
||||
return;
|
||||
}
|
||||
Codeword[] LRIcodewords = detectionResultColumns[0].getCodewords();
|
||||
Codeword[] RRIcodewords = detectionResultColumns[barcodeColumnCount + 1].getCodewords();
|
||||
for (int codewordsRow = 0; codewordsRow < LRIcodewords.length; codewordsRow++) {
|
||||
if (LRIcodewords[codewordsRow] != null &&
|
||||
RRIcodewords[codewordsRow] != null &&
|
||||
LRIcodewords[codewordsRow].getRowNumber() == RRIcodewords[codewordsRow].getRowNumber()) {
|
||||
for (int barcodeColumn = 1; barcodeColumn <= barcodeColumnCount; barcodeColumn++) {
|
||||
Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow];
|
||||
if (codeword == null) {
|
||||
continue;
|
||||
}
|
||||
codeword.setRowNumber(LRIcodewords[codewordsRow].getRowNumber());
|
||||
if (!codeword.hasValidRowNumber()) {
|
||||
detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int adjustRowNumbersFromRRI() {
|
||||
if (detectionResultColumns[barcodeColumnCount + 1] == null) {
|
||||
return 0;
|
||||
}
|
||||
int unadjustedCount = 0;
|
||||
Codeword[] codewords = detectionResultColumns[barcodeColumnCount + 1].getCodewords();
|
||||
for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) {
|
||||
if (codewords[codewordsRow] == null) {
|
||||
continue;
|
||||
}
|
||||
int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber();
|
||||
int invalidRowCounts = 0;
|
||||
for (int barcodeColumn = barcodeColumnCount + 1;
|
||||
barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP;
|
||||
barcodeColumn--) {
|
||||
Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow];
|
||||
if (codeword != null) {
|
||||
invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword);
|
||||
if (!codeword.hasValidRowNumber()) {
|
||||
unadjustedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return unadjustedCount;
|
||||
}
|
||||
|
||||
private int adjustRowNumbersFromLRI() {
|
||||
if (detectionResultColumns[0] == null) {
|
||||
return 0;
|
||||
}
|
||||
int unadjustedCount = 0;
|
||||
Codeword[] codewords = detectionResultColumns[0].getCodewords();
|
||||
for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) {
|
||||
if (codewords[codewordsRow] == null) {
|
||||
continue;
|
||||
}
|
||||
int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber();
|
||||
int invalidRowCounts = 0;
|
||||
for (int barcodeColumn = 1;
|
||||
barcodeColumn < barcodeColumnCount + 1 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP;
|
||||
barcodeColumn++) {
|
||||
Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow];
|
||||
if (codeword != null) {
|
||||
invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword);
|
||||
if (!codeword.hasValidRowNumber()) {
|
||||
unadjustedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return unadjustedCount;
|
||||
}
|
||||
|
||||
private static int adjustRowNumberIfValid(int rowIndicatorRowNumber, int invalidRowCounts, Codeword codeword) {
|
||||
if (codeword == null) {
|
||||
return invalidRowCounts;
|
||||
}
|
||||
if (!codeword.hasValidRowNumber()) {
|
||||
if (codeword.isValidRowNumber(rowIndicatorRowNumber)) {
|
||||
codeword.setRowNumber(rowIndicatorRowNumber);
|
||||
invalidRowCounts = 0;
|
||||
} else {
|
||||
++invalidRowCounts;
|
||||
}
|
||||
}
|
||||
return invalidRowCounts;
|
||||
}
|
||||
|
||||
private void adjustRowNumbers(int barcodeColumn, int codewordsRow, Codeword[] codewords) {
|
||||
Codeword codeword = codewords[codewordsRow];
|
||||
Codeword[] previousColumnCodewords = detectionResultColumns[barcodeColumn - 1].getCodewords();
|
||||
Codeword[] nextColumnCodewords = previousColumnCodewords;
|
||||
if (detectionResultColumns[barcodeColumn + 1] != null) {
|
||||
nextColumnCodewords = detectionResultColumns[barcodeColumn + 1].getCodewords();
|
||||
}
|
||||
|
||||
Codeword[] otherCodewords = new Codeword[14];
|
||||
|
||||
otherCodewords[2] = previousColumnCodewords[codewordsRow];
|
||||
otherCodewords[3] = nextColumnCodewords[codewordsRow];
|
||||
|
||||
if (codewordsRow > 0) {
|
||||
otherCodewords[0] = codewords[codewordsRow - 1];
|
||||
otherCodewords[4] = previousColumnCodewords[codewordsRow - 1];
|
||||
otherCodewords[5] = nextColumnCodewords[codewordsRow - 1];
|
||||
}
|
||||
if (codewordsRow > 1) {
|
||||
otherCodewords[8] = codewords[codewordsRow - 2];
|
||||
otherCodewords[10] = previousColumnCodewords[codewordsRow - 2];
|
||||
otherCodewords[11] = nextColumnCodewords[codewordsRow - 2];
|
||||
}
|
||||
if (codewordsRow < codewords.length - 1) {
|
||||
otherCodewords[1] = codewords[codewordsRow + 1];
|
||||
otherCodewords[6] = previousColumnCodewords[codewordsRow + 1];
|
||||
otherCodewords[7] = nextColumnCodewords[codewordsRow + 1];
|
||||
}
|
||||
if (codewordsRow < codewords.length - 2) {
|
||||
otherCodewords[9] = codewords[codewordsRow + 2];
|
||||
otherCodewords[12] = previousColumnCodewords[codewordsRow + 2];
|
||||
otherCodewords[13] = nextColumnCodewords[codewordsRow + 2];
|
||||
}
|
||||
for (Codeword otherCodeword : otherCodewords) {
|
||||
if (adjustRowNumber(codeword, otherCodeword)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true, if row number was adjusted, false otherwise
|
||||
*/
|
||||
private static boolean adjustRowNumber(Codeword codeword, Codeword otherCodeword) {
|
||||
if (otherCodeword == null) {
|
||||
return false;
|
||||
}
|
||||
if (otherCodeword.hasValidRowNumber() && otherCodeword.getBucket() == codeword.getBucket()) {
|
||||
codeword.setRowNumber(otherCodeword.getRowNumber());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int getBarcodeColumnCount() {
|
||||
return barcodeColumnCount;
|
||||
}
|
||||
|
||||
int getBarcodeRowCount() {
|
||||
return barcodeMetadata.getRowCount();
|
||||
}
|
||||
|
||||
int getBarcodeECLevel() {
|
||||
return barcodeMetadata.getErrorCorrectionLevel();
|
||||
}
|
||||
|
||||
void setBoundingBox(BoundingBox boundingBox) {
|
||||
this.boundingBox = boundingBox;
|
||||
}
|
||||
|
||||
BoundingBox getBoundingBox() {
|
||||
return boundingBox;
|
||||
}
|
||||
|
||||
void setDetectionResultColumn(int barcodeColumn, DetectionResultColumn detectionResultColumn) {
|
||||
detectionResultColumns[barcodeColumn] = detectionResultColumn;
|
||||
}
|
||||
|
||||
DetectionResultColumn getDetectionResultColumn(int barcodeColumn) {
|
||||
return detectionResultColumns[barcodeColumn];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
DetectionResultColumn rowIndicatorColumn = detectionResultColumns[0];
|
||||
if (rowIndicatorColumn == null) {
|
||||
rowIndicatorColumn = detectionResultColumns[barcodeColumnCount + 1];
|
||||
}
|
||||
try (Formatter formatter = new Formatter()) {
|
||||
for (int codewordsRow = 0; codewordsRow < rowIndicatorColumn.getCodewords().length; codewordsRow++) {
|
||||
formatter.format("CW %3d:", codewordsRow);
|
||||
for (int barcodeColumn = 0; barcodeColumn < barcodeColumnCount + 2; barcodeColumn++) {
|
||||
if (detectionResultColumns[barcodeColumn] == null) {
|
||||
formatter.format(" | ");
|
||||
continue;
|
||||
}
|
||||
Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow];
|
||||
if (codeword == null) {
|
||||
formatter.format(" | ");
|
||||
continue;
|
||||
}
|
||||
formatter.format(" %3d|%3d", codeword.getRowNumber(), codeword.getValue());
|
||||
}
|
||||
formatter.format("%n");
|
||||
}
|
||||
return formatter.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
95
src/pdf417/decoder/DetectionResultColumn.java
Normal file
95
src/pdf417/decoder/DetectionResultColumn.java
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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 java.util.Formatter;
|
||||
|
||||
/**
|
||||
* @author Guenther Grau
|
||||
*/
|
||||
class DetectionResultColumn {
|
||||
|
||||
private static final int MAX_NEARBY_DISTANCE = 5;
|
||||
|
||||
private final BoundingBox boundingBox;
|
||||
private final Codeword[] codewords;
|
||||
|
||||
DetectionResultColumn(BoundingBox boundingBox) {
|
||||
this.boundingBox = new BoundingBox(boundingBox);
|
||||
codewords = new Codeword[boundingBox.getMaxY() - boundingBox.getMinY() + 1];
|
||||
}
|
||||
|
||||
final Codeword getCodewordNearby(int imageRow) {
|
||||
Codeword codeword = getCodeword(imageRow);
|
||||
if (codeword != null) {
|
||||
return codeword;
|
||||
}
|
||||
for (int i = 1; i < MAX_NEARBY_DISTANCE; i++) {
|
||||
int nearImageRow = imageRowToCodewordIndex(imageRow) - i;
|
||||
if (nearImageRow >= 0) {
|
||||
codeword = codewords[nearImageRow];
|
||||
if (codeword != null) {
|
||||
return codeword;
|
||||
}
|
||||
}
|
||||
nearImageRow = imageRowToCodewordIndex(imageRow) + i;
|
||||
if (nearImageRow < codewords.length) {
|
||||
codeword = codewords[nearImageRow];
|
||||
if (codeword != null) {
|
||||
return codeword;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final int imageRowToCodewordIndex(int imageRow) {
|
||||
return imageRow - boundingBox.getMinY();
|
||||
}
|
||||
|
||||
final void setCodeword(int imageRow, Codeword codeword) {
|
||||
codewords[imageRowToCodewordIndex(imageRow)] = codeword;
|
||||
}
|
||||
|
||||
final Codeword getCodeword(int imageRow) {
|
||||
return codewords[imageRowToCodewordIndex(imageRow)];
|
||||
}
|
||||
|
||||
final BoundingBox getBoundingBox() {
|
||||
return boundingBox;
|
||||
}
|
||||
|
||||
final Codeword[] getCodewords() {
|
||||
return codewords;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try (Formatter formatter = new Formatter()) {
|
||||
int row = 0;
|
||||
for (Codeword codeword : codewords) {
|
||||
if (codeword == null) {
|
||||
formatter.format("%3d: | %n", row++);
|
||||
continue;
|
||||
}
|
||||
formatter.format("%3d: %3d|%3d%n", row++, codeword.getRowNumber(), codeword.getValue());
|
||||
}
|
||||
return formatter.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
261
src/pdf417/decoder/DetectionResultRowIndicatorColumn.java
Normal file
261
src/pdf417/decoder/DetectionResultRowIndicatorColumn.java
Normal file
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* 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.ResultPoint;
|
||||
import com.google.zxing.pdf417.PDF417Common;
|
||||
|
||||
/**
|
||||
* @author Guenther Grau
|
||||
*/
|
||||
final class DetectionResultRowIndicatorColumn extends DetectionResultColumn {
|
||||
|
||||
private final boolean isLeft;
|
||||
|
||||
DetectionResultRowIndicatorColumn(BoundingBox boundingBox, boolean isLeft) {
|
||||
super(boundingBox);
|
||||
this.isLeft = isLeft;
|
||||
}
|
||||
|
||||
private void setRowNumbers() {
|
||||
for (Codeword codeword : getCodewords()) {
|
||||
if (codeword != null) {
|
||||
codeword.setRowNumberAsRowIndicatorColumn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO implement properly
|
||||
// TODO maybe we should add missing codewords to store the correct row number to make
|
||||
// finding row numbers for other columns easier
|
||||
// use row height count to make detection of invalid row numbers more reliable
|
||||
void adjustCompleteIndicatorColumnRowNumbers(BarcodeMetadata barcodeMetadata) {
|
||||
Codeword[] codewords = getCodewords();
|
||||
setRowNumbers();
|
||||
removeIncorrectCodewords(codewords, barcodeMetadata);
|
||||
BoundingBox boundingBox = getBoundingBox();
|
||||
ResultPoint top = isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight();
|
||||
ResultPoint bottom = isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight();
|
||||
int firstRow = imageRowToCodewordIndex((int) top.getY());
|
||||
int lastRow = imageRowToCodewordIndex((int) bottom.getY());
|
||||
// We need to be careful using the average row height. Barcode could be skewed so that we have smaller and
|
||||
// taller rows
|
||||
//float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount();
|
||||
int barcodeRow = -1;
|
||||
int maxRowHeight = 1;
|
||||
int currentRowHeight = 0;
|
||||
for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) {
|
||||
if (codewords[codewordsRow] == null) {
|
||||
continue;
|
||||
}
|
||||
Codeword codeword = codewords[codewordsRow];
|
||||
|
||||
int rowDifference = codeword.getRowNumber() - barcodeRow;
|
||||
|
||||
// TODO improve handling with case where first row indicator doesn't start with 0
|
||||
|
||||
if (rowDifference == 0) {
|
||||
currentRowHeight++;
|
||||
} else if (rowDifference == 1) {
|
||||
maxRowHeight = Math.max(maxRowHeight, currentRowHeight);
|
||||
currentRowHeight = 1;
|
||||
barcodeRow = codeword.getRowNumber();
|
||||
} else if (rowDifference < 0 ||
|
||||
codeword.getRowNumber() >= barcodeMetadata.getRowCount() ||
|
||||
rowDifference > codewordsRow) {
|
||||
codewords[codewordsRow] = null;
|
||||
} else {
|
||||
int checkedRows;
|
||||
if (maxRowHeight > 2) {
|
||||
checkedRows = (maxRowHeight - 2) * rowDifference;
|
||||
} else {
|
||||
checkedRows = rowDifference;
|
||||
}
|
||||
boolean closePreviousCodewordFound = checkedRows >= codewordsRow;
|
||||
for (int i = 1; i <= checkedRows && !closePreviousCodewordFound; i++) {
|
||||
// there must be (height * rowDifference) number of codewords missing. For now we assume height = 1.
|
||||
// This should hopefully get rid of most problems already.
|
||||
closePreviousCodewordFound = codewords[codewordsRow - i] != null;
|
||||
}
|
||||
if (closePreviousCodewordFound) {
|
||||
codewords[codewordsRow] = null;
|
||||
} else {
|
||||
barcodeRow = codeword.getRowNumber();
|
||||
currentRowHeight = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
//return (int) (averageRowHeight + 0.5);
|
||||
}
|
||||
|
||||
int[] getRowHeights() {
|
||||
BarcodeMetadata barcodeMetadata = getBarcodeMetadata();
|
||||
if (barcodeMetadata == null) {
|
||||
return null;
|
||||
}
|
||||
adjustIncompleteIndicatorColumnRowNumbers(barcodeMetadata);
|
||||
int[] result = new int[barcodeMetadata.getRowCount()];
|
||||
for (Codeword codeword : getCodewords()) {
|
||||
if (codeword != null) {
|
||||
int rowNumber = codeword.getRowNumber();
|
||||
if (rowNumber >= result.length) {
|
||||
// We have more rows than the barcode metadata allows for, ignore them.
|
||||
continue;
|
||||
}
|
||||
result[rowNumber]++;
|
||||
} // else throw exception?
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO maybe we should add missing codewords to store the correct row number to make
|
||||
// finding row numbers for other columns easier
|
||||
// use row height count to make detection of invalid row numbers more reliable
|
||||
private void adjustIncompleteIndicatorColumnRowNumbers(BarcodeMetadata barcodeMetadata) {
|
||||
BoundingBox boundingBox = getBoundingBox();
|
||||
ResultPoint top = isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight();
|
||||
ResultPoint bottom = isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight();
|
||||
int firstRow = imageRowToCodewordIndex((int) top.getY());
|
||||
int lastRow = imageRowToCodewordIndex((int) bottom.getY());
|
||||
//float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount();
|
||||
Codeword[] codewords = getCodewords();
|
||||
int barcodeRow = -1;
|
||||
int maxRowHeight = 1;
|
||||
int currentRowHeight = 0;
|
||||
for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) {
|
||||
if (codewords[codewordsRow] == null) {
|
||||
continue;
|
||||
}
|
||||
Codeword codeword = codewords[codewordsRow];
|
||||
|
||||
codeword.setRowNumberAsRowIndicatorColumn();
|
||||
|
||||
int rowDifference = codeword.getRowNumber() - barcodeRow;
|
||||
|
||||
// TODO improve handling with case where first row indicator doesn't start with 0
|
||||
|
||||
if (rowDifference == 0) {
|
||||
currentRowHeight++;
|
||||
} else if (rowDifference == 1) {
|
||||
maxRowHeight = Math.max(maxRowHeight, currentRowHeight);
|
||||
currentRowHeight = 1;
|
||||
barcodeRow = codeword.getRowNumber();
|
||||
} else if (codeword.getRowNumber() >= barcodeMetadata.getRowCount()) {
|
||||
codewords[codewordsRow] = null;
|
||||
} else {
|
||||
barcodeRow = codeword.getRowNumber();
|
||||
currentRowHeight = 1;
|
||||
}
|
||||
}
|
||||
//return (int) (averageRowHeight + 0.5);
|
||||
}
|
||||
|
||||
BarcodeMetadata getBarcodeMetadata() {
|
||||
Codeword[] codewords = getCodewords();
|
||||
BarcodeValue barcodeColumnCount = new BarcodeValue();
|
||||
BarcodeValue barcodeRowCountUpperPart = new BarcodeValue();
|
||||
BarcodeValue barcodeRowCountLowerPart = new BarcodeValue();
|
||||
BarcodeValue barcodeECLevel = new BarcodeValue();
|
||||
for (Codeword codeword : codewords) {
|
||||
if (codeword == null) {
|
||||
continue;
|
||||
}
|
||||
codeword.setRowNumberAsRowIndicatorColumn();
|
||||
int rowIndicatorValue = codeword.getValue() % 30;
|
||||
int codewordRowNumber = codeword.getRowNumber();
|
||||
if (!isLeft) {
|
||||
codewordRowNumber += 2;
|
||||
}
|
||||
switch (codewordRowNumber % 3) {
|
||||
case 0:
|
||||
barcodeRowCountUpperPart.setValue(rowIndicatorValue * 3 + 1);
|
||||
break;
|
||||
case 1:
|
||||
barcodeECLevel.setValue(rowIndicatorValue / 3);
|
||||
barcodeRowCountLowerPart.setValue(rowIndicatorValue % 3);
|
||||
break;
|
||||
case 2:
|
||||
barcodeColumnCount.setValue(rowIndicatorValue + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Maybe we should check if we have ambiguous values?
|
||||
if ((barcodeColumnCount.getValue().length == 0) ||
|
||||
(barcodeRowCountUpperPart.getValue().length == 0) ||
|
||||
(barcodeRowCountLowerPart.getValue().length == 0) ||
|
||||
(barcodeECLevel.getValue().length == 0) ||
|
||||
barcodeColumnCount.getValue()[0] < 1 ||
|
||||
barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] <
|
||||
PDF417Common.MIN_ROWS_IN_BARCODE ||
|
||||
barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] >
|
||||
PDF417Common.MAX_ROWS_IN_BARCODE) {
|
||||
return null;
|
||||
}
|
||||
BarcodeMetadata barcodeMetadata = new BarcodeMetadata(barcodeColumnCount.getValue()[0],
|
||||
barcodeRowCountUpperPart.getValue()[0], barcodeRowCountLowerPart.getValue()[0], barcodeECLevel.getValue()[0]);
|
||||
removeIncorrectCodewords(codewords, barcodeMetadata);
|
||||
return barcodeMetadata;
|
||||
}
|
||||
|
||||
private void removeIncorrectCodewords(Codeword[] codewords, BarcodeMetadata barcodeMetadata) {
|
||||
// Remove codewords which do not match the metadata
|
||||
// TODO Maybe we should keep the incorrect codewords for the start and end positions?
|
||||
for (int codewordRow = 0; codewordRow < codewords.length; codewordRow++) {
|
||||
Codeword codeword = codewords[codewordRow];
|
||||
if (codewords[codewordRow] == null) {
|
||||
continue;
|
||||
}
|
||||
int rowIndicatorValue = codeword.getValue() % 30;
|
||||
int codewordRowNumber = codeword.getRowNumber();
|
||||
if (codewordRowNumber > barcodeMetadata.getRowCount()) {
|
||||
codewords[codewordRow] = null;
|
||||
continue;
|
||||
}
|
||||
if (!isLeft) {
|
||||
codewordRowNumber += 2;
|
||||
}
|
||||
switch (codewordRowNumber % 3) {
|
||||
case 0:
|
||||
if (rowIndicatorValue * 3 + 1 != barcodeMetadata.getRowCountUpperPart()) {
|
||||
codewords[codewordRow] = null;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (rowIndicatorValue / 3 != barcodeMetadata.getErrorCorrectionLevel() ||
|
||||
rowIndicatorValue % 3 != barcodeMetadata.getRowCountLowerPart()) {
|
||||
codewords[codewordRow] = null;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (rowIndicatorValue + 1 != barcodeMetadata.getColumnCount()) {
|
||||
codewords[codewordRow] = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isLeft() {
|
||||
return isLeft;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IsLeft: " + isLeft + '\n' + super.toString();
|
||||
}
|
||||
|
||||
}
|
||||
120
src/pdf417/decoder/PDF417CodewordDecoder.java
Normal file
120
src/pdf417/decoder/PDF417CodewordDecoder.java
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.common.detector.MathUtils;
|
||||
import com.google.zxing.pdf417.PDF417Common;
|
||||
|
||||
/**
|
||||
* @author Guenther Grau
|
||||
* @author creatale GmbH (christoph.schulz@creatale.de)
|
||||
*/
|
||||
final class PDF417CodewordDecoder {
|
||||
|
||||
private static final float[][] RATIOS_TABLE =
|
||||
new float[PDF417Common.SYMBOL_TABLE.length][PDF417Common.BARS_IN_MODULE];
|
||||
|
||||
static {
|
||||
// Pre-computes the symbol ratio table.
|
||||
for (int i = 0; i < PDF417Common.SYMBOL_TABLE.length; i++) {
|
||||
int currentSymbol = PDF417Common.SYMBOL_TABLE[i];
|
||||
int currentBit = currentSymbol & 0x1;
|
||||
for (int j = 0; j < PDF417Common.BARS_IN_MODULE; j++) {
|
||||
float size = 0.0f;
|
||||
while ((currentSymbol & 0x1) == currentBit) {
|
||||
size += 1.0f;
|
||||
currentSymbol >>= 1;
|
||||
}
|
||||
currentBit = currentSymbol & 0x1;
|
||||
RATIOS_TABLE[i][PDF417Common.BARS_IN_MODULE - j - 1] = size / PDF417Common.MODULES_IN_CODEWORD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PDF417CodewordDecoder() {
|
||||
}
|
||||
|
||||
static int getDecodedValue(int[] moduleBitCount) {
|
||||
int decodedValue = getDecodedCodewordValue(sampleBitCounts(moduleBitCount));
|
||||
if (decodedValue != -1) {
|
||||
return decodedValue;
|
||||
}
|
||||
return getClosestDecodedValue(moduleBitCount);
|
||||
}
|
||||
|
||||
private static int[] sampleBitCounts(int[] moduleBitCount) {
|
||||
float bitCountSum = MathUtils.sum(moduleBitCount);
|
||||
int[] result = new int[PDF417Common.BARS_IN_MODULE];
|
||||
int bitCountIndex = 0;
|
||||
int sumPreviousBits = 0;
|
||||
for (int i = 0; i < PDF417Common.MODULES_IN_CODEWORD; i++) {
|
||||
float sampleIndex =
|
||||
bitCountSum / (2 * PDF417Common.MODULES_IN_CODEWORD) +
|
||||
(i * bitCountSum) / PDF417Common.MODULES_IN_CODEWORD;
|
||||
if (sumPreviousBits + moduleBitCount[bitCountIndex] <= sampleIndex) {
|
||||
sumPreviousBits += moduleBitCount[bitCountIndex];
|
||||
bitCountIndex++;
|
||||
}
|
||||
result[bitCountIndex]++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int getDecodedCodewordValue(int[] moduleBitCount) {
|
||||
int decodedValue = getBitValue(moduleBitCount);
|
||||
return PDF417Common.getCodeword(decodedValue) == -1 ? -1 : decodedValue;
|
||||
}
|
||||
|
||||
private static int getBitValue(int[] moduleBitCount) {
|
||||
long result = 0;
|
||||
for (int i = 0; i < moduleBitCount.length; i++) {
|
||||
for (int bit = 0; bit < moduleBitCount[i]; bit++) {
|
||||
result = (result << 1) | (i % 2 == 0 ? 1 : 0);
|
||||
}
|
||||
}
|
||||
return (int) result;
|
||||
}
|
||||
|
||||
private static int getClosestDecodedValue(int[] moduleBitCount) {
|
||||
int bitCountSum = MathUtils.sum(moduleBitCount);
|
||||
float[] bitCountRatios = new float[PDF417Common.BARS_IN_MODULE];
|
||||
if (bitCountSum > 1) {
|
||||
for (int i = 0; i < bitCountRatios.length; i++) {
|
||||
bitCountRatios[i] = moduleBitCount[i] / (float) bitCountSum;
|
||||
}
|
||||
}
|
||||
float bestMatchError = Float.MAX_VALUE;
|
||||
int bestMatch = -1;
|
||||
for (int j = 0; j < RATIOS_TABLE.length; j++) {
|
||||
float error = 0.0f;
|
||||
float[] ratioTableRow = RATIOS_TABLE[j];
|
||||
for (int k = 0; k < PDF417Common.BARS_IN_MODULE; k++) {
|
||||
float diff = ratioTableRow[k] - bitCountRatios[k];
|
||||
error += diff * diff;
|
||||
if (error >= bestMatchError) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (error < bestMatchError) {
|
||||
bestMatchError = error;
|
||||
bestMatch = PDF417Common.SYMBOL_TABLE[j];
|
||||
}
|
||||
}
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
}
|
||||
634
src/pdf417/decoder/PDF417ScanningDecoder.java
Normal file
634
src/pdf417/decoder/PDF417ScanningDecoder.java
Normal file
@@ -0,0 +1,634 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
190
src/pdf417/decoder/ec/ErrorCorrection.java
Normal file
190
src/pdf417/decoder/ec/ErrorCorrection.java
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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>PDF417 error correction implementation.</p>
|
||||
*
|
||||
* <p>This <a href="http://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction#Example">example</a>
|
||||
* is quite useful in understanding the algorithm.</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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param received received codewords
|
||||
* @param numECCodewords number of those codewords used for EC
|
||||
* @param erasures location of erasures
|
||||
* @return number of errors
|
||||
* @throws ChecksumException if errors cannot be corrected, maybe because of too many errors
|
||||
*/
|
||||
public int decode(int[] received,
|
||||
int numECCodewords,
|
||||
int[] erasures) throws ChecksumException {
|
||||
|
||||
ModulusPoly poly = new ModulusPoly(field, received);
|
||||
int[] S = new int[numECCodewords];
|
||||
boolean error = false;
|
||||
for (int i = numECCodewords; i > 0; i--) {
|
||||
int eval = poly.evaluateAt(field.exp(i));
|
||||
S[numECCodewords - i] = eval;
|
||||
if (eval != 0) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ModulusPoly knownErrors = field.getOne();
|
||||
if (erasures != null) {
|
||||
for (int erasure : erasures) {
|
||||
int b = field.exp(received.length - 1 - erasure);
|
||||
// Add (1 - bx) term:
|
||||
ModulusPoly term = new ModulusPoly(field, new int[]{field.subtract(0, b), 1});
|
||||
knownErrors = knownErrors.multiply(term);
|
||||
}
|
||||
}
|
||||
|
||||
ModulusPoly syndrome = new ModulusPoly(field, S);
|
||||
//syndrome = syndrome.multiply(knownErrors);
|
||||
|
||||
ModulusPoly[] sigmaOmega =
|
||||
runEuclideanAlgorithm(field.buildMonomial(numECCodewords, 1), syndrome, numECCodewords);
|
||||
ModulusPoly sigma = sigmaOmega[0];
|
||||
ModulusPoly omega = sigmaOmega[1];
|
||||
|
||||
//sigma = sigma.multiply(knownErrors);
|
||||
|
||||
int[] errorLocations = findErrorLocations(sigma);
|
||||
int[] errorMagnitudes = findErrorMagnitudes(omega, sigma, errorLocations);
|
||||
|
||||
for (int i = 0; i < errorLocations.length; i++) {
|
||||
int position = received.length - 1 - field.log(errorLocations[i]);
|
||||
if (position < 0) {
|
||||
throw ChecksumException.getChecksumInstance();
|
||||
}
|
||||
received[position] = field.subtract(received[position], errorMagnitudes[i]);
|
||||
}
|
||||
return errorLocations.length;
|
||||
}
|
||||
|
||||
private ModulusPoly[] runEuclideanAlgorithm(ModulusPoly a, ModulusPoly b, int R)
|
||||
throws ChecksumException {
|
||||
// Assume a's degree is >= b's
|
||||
if (a.getDegree() < b.getDegree()) {
|
||||
ModulusPoly temp = a;
|
||||
a = b;
|
||||
b = temp;
|
||||
}
|
||||
|
||||
ModulusPoly rLast = a;
|
||||
ModulusPoly r = b;
|
||||
ModulusPoly tLast = field.getZero();
|
||||
ModulusPoly t = field.getOne();
|
||||
|
||||
// Run Euclidean algorithm until r's degree is less than R/2
|
||||
while (r.getDegree() >= R / 2) {
|
||||
ModulusPoly rLastLast = rLast;
|
||||
ModulusPoly tLastLast = tLast;
|
||||
rLast = r;
|
||||
tLast = t;
|
||||
|
||||
// Divide rLastLast by rLast, with quotient in q and remainder in r
|
||||
if (rLast.isZero()) {
|
||||
// Oops, Euclidean algorithm already terminated?
|
||||
throw ChecksumException.getChecksumInstance();
|
||||
}
|
||||
r = rLastLast;
|
||||
ModulusPoly q = field.getZero();
|
||||
int denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree());
|
||||
int dltInverse = field.inverse(denominatorLeadingTerm);
|
||||
while (r.getDegree() >= rLast.getDegree() && !r.isZero()) {
|
||||
int degreeDiff = r.getDegree() - rLast.getDegree();
|
||||
int scale = field.multiply(r.getCoefficient(r.getDegree()), dltInverse);
|
||||
q = q.add(field.buildMonomial(degreeDiff, scale));
|
||||
r = r.subtract(rLast.multiplyByMonomial(degreeDiff, scale));
|
||||
}
|
||||
|
||||
t = q.multiply(tLast).subtract(tLastLast).negative();
|
||||
}
|
||||
|
||||
int sigmaTildeAtZero = t.getCoefficient(0);
|
||||
if (sigmaTildeAtZero == 0) {
|
||||
throw ChecksumException.getChecksumInstance();
|
||||
}
|
||||
|
||||
int inverse = field.inverse(sigmaTildeAtZero);
|
||||
ModulusPoly sigma = t.multiply(inverse);
|
||||
ModulusPoly omega = r.multiply(inverse);
|
||||
return new ModulusPoly[]{sigma, omega};
|
||||
}
|
||||
|
||||
private int[] findErrorLocations(ModulusPoly errorLocator) throws ChecksumException {
|
||||
// This is a direct application of Chien's search
|
||||
int numErrors = errorLocator.getDegree();
|
||||
int[] result = new int[numErrors];
|
||||
int e = 0;
|
||||
for (int i = 1; i < field.getSize() && e < numErrors; i++) {
|
||||
if (errorLocator.evaluateAt(i) == 0) {
|
||||
result[e] = field.inverse(i);
|
||||
e++;
|
||||
}
|
||||
}
|
||||
if (e != numErrors) {
|
||||
throw ChecksumException.getChecksumInstance();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int[] findErrorMagnitudes(ModulusPoly errorEvaluator,
|
||||
ModulusPoly errorLocator,
|
||||
int[] errorLocations) {
|
||||
int errorLocatorDegree = errorLocator.getDegree();
|
||||
if (errorLocatorDegree < 1) {
|
||||
return new int[0];
|
||||
}
|
||||
int[] formalDerivativeCoefficients = new int[errorLocatorDegree];
|
||||
for (int i = 1; i <= errorLocatorDegree; i++) {
|
||||
formalDerivativeCoefficients[errorLocatorDegree - i] =
|
||||
field.multiply(i, errorLocator.getCoefficient(i));
|
||||
}
|
||||
ModulusPoly formalDerivative = new ModulusPoly(field, formalDerivativeCoefficients);
|
||||
|
||||
// This is directly applying Forney's Formula
|
||||
int s = errorLocations.length;
|
||||
int[] result = new int[s];
|
||||
for (int i = 0; i < s; i++) {
|
||||
int xiInverse = field.inverse(errorLocations[i]);
|
||||
int numerator = field.subtract(0, errorEvaluator.evaluateAt(xiInverse));
|
||||
int denominator = field.inverse(formalDerivative.evaluateAt(xiInverse));
|
||||
result[i] = field.multiply(numerator, denominator);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
112
src/pdf417/decoder/ec/ModulusGF.java
Normal file
112
src/pdf417/decoder/ec/ModulusGF.java
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.pdf417.PDF417Common;
|
||||
|
||||
/**
|
||||
* <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(PDF417Common.NUMBER_OF_CODEWORDS, 3);
|
||||
|
||||
private final int[] expTable;
|
||||
private final int[] logTable;
|
||||
private final ModulusPoly zero;
|
||||
private final ModulusPoly one;
|
||||
private final int modulus;
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
}
|
||||
233
src/pdf417/decoder/ec/ModulusPoly.java
Normal file
233
src/pdf417/decoder/ec/ModulusPoly.java
Normal file
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
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 = new int[]{0};
|
||||
} 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);
|
||||
}
|
||||
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];
|
||||
int size = coefficients.length;
|
||||
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);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user