blob: becaf3787d2a6ca81a2e3d2f7038a511703c8765 [file] [log] [blame]
/*
* 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();
}
}