mirror of
https://github.com/zxing/zxing.git
synced 2025-03-05 20:48:51 -08:00
Added PDF417 to list of readers. Will be used only if its specified in a Hint. git-svn-id: https://zxing.googlecode.com/svn/trunk@982 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
parent
36db720dac
commit
db23e7c3b1
127
core/src/com/google/zxing/pdf417/PDF417Reader.java
Normal file
127
core/src/com/google/zxing/pdf417/PDF417Reader.java
Normal file
|
@ -0,0 +1,127 @@
|
|||
package com.google.zxing.pdf417;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.MonochromeBitmapSource;
|
||||
import com.google.zxing.Reader;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.common.DecoderResult;
|
||||
import com.google.zxing.common.DetectorResult;
|
||||
import com.google.zxing.pdf417.decoder.Decoder;
|
||||
import com.google.zxing.pdf417.detector.Detector;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* This implementation can detect and decode PDF417 codes in an image.
|
||||
*
|
||||
* @author SITA Lab (kevin.osullivan@sita.aero)
|
||||
*/
|
||||
public final class PDF417Reader implements Reader {
|
||||
private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
|
||||
|
||||
private final Decoder decoder = new Decoder();
|
||||
|
||||
/**
|
||||
* Locates and decodes a PDF417 code in an image.
|
||||
*
|
||||
* @return a String representing the content encoded by the PDF417 code
|
||||
* @throws ReaderException if a PDF417 code cannot be found, or cannot be decoded
|
||||
*/
|
||||
public Result decode(MonochromeBitmapSource image) throws ReaderException {
|
||||
return decode(image, null);
|
||||
}
|
||||
|
||||
public Result decode(MonochromeBitmapSource image, Hashtable hints)
|
||||
throws ReaderException {
|
||||
DecoderResult decoderResult;
|
||||
ResultPoint[] points;
|
||||
if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
|
||||
BitMatrix bits = extractPureBits(image);
|
||||
decoderResult = decoder.decode(bits);
|
||||
points = NO_POINTS;
|
||||
} else {
|
||||
DetectorResult detectorResult = new Detector(image).detect();
|
||||
decoderResult = decoder.decode(detectorResult.getBits());
|
||||
points = detectorResult.getPoints();
|
||||
}
|
||||
Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.PDF417);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method detects a barcode in a "pure" image -- that is, pure monochrome image
|
||||
* which contains only an unrotated, unskewed, image of a barcode, with some white border
|
||||
* around it. This is a specialized method that works exceptionally fast in this special
|
||||
* case.
|
||||
*/
|
||||
private static BitMatrix extractPureBits(MonochromeBitmapSource image) throws ReaderException {
|
||||
// Now need to determine module size in pixels
|
||||
|
||||
int height = image.getHeight();
|
||||
int width = image.getWidth();
|
||||
int minDimension = Math.min(height, width);
|
||||
|
||||
// First, skip white border by tracking diagonally from the top left down and to the right:
|
||||
int borderWidth = 0;
|
||||
while (borderWidth < minDimension && !image.isBlack(borderWidth, borderWidth)) {
|
||||
borderWidth++;
|
||||
}
|
||||
if (borderWidth == minDimension) {
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
|
||||
// And then keep tracking across the top-left black module to determine module size
|
||||
int moduleEnd = borderWidth;
|
||||
while (moduleEnd < minDimension && image.isBlack(moduleEnd, moduleEnd)) {
|
||||
moduleEnd++;
|
||||
}
|
||||
if (moduleEnd == minDimension) {
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
|
||||
int moduleSize = moduleEnd - borderWidth;
|
||||
|
||||
// And now find where the rightmost black module on the first row ends
|
||||
int rowEndOfSymbol = width - 1;
|
||||
while (rowEndOfSymbol >= 0 && !image.isBlack(rowEndOfSymbol, borderWidth)) {
|
||||
rowEndOfSymbol--;
|
||||
}
|
||||
if (rowEndOfSymbol < 0) {
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
rowEndOfSymbol++;
|
||||
|
||||
// Make sure width of barcode is a multiple of module size
|
||||
if ((rowEndOfSymbol - borderWidth) % moduleSize != 0) {
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
int dimension = (rowEndOfSymbol - borderWidth) / moduleSize;
|
||||
|
||||
// Push in the "border" by half the module width so that we start
|
||||
// sampling in the middle of the module. Just in case the image is a
|
||||
// little off, this will help recover.
|
||||
borderWidth += moduleSize >> 1;
|
||||
|
||||
int sampleDimension = borderWidth + (dimension - 1) * moduleSize;
|
||||
if (sampleDimension >= width || sampleDimension >= height) {
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
|
||||
// Now just read off the bits
|
||||
BitMatrix bits = new BitMatrix(dimension);
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
int iOffset = borderWidth + i * moduleSize;
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
if (image.isBlack(borderWidth + j * moduleSize, iOffset)) {
|
||||
bits.set(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
}
|
||||
|
1211
core/src/com/google/zxing/pdf417/decoder/BitMatrixParser.java
Normal file
1211
core/src/com/google/zxing/pdf417/decoder/BitMatrixParser.java
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,630 @@
|
|||
/*
|
||||
* Copyright 2007 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.ReaderException;
|
||||
import com.google.zxing.common.DecoderResult;
|
||||
|
||||
/**
|
||||
* <p>This class contains the methods for decoding the PDF417 codewords.</p>
|
||||
*
|
||||
* @author SITA Lab (kevin.osullivan@sita.aero)
|
||||
*/
|
||||
final class DecodedBitStreamParser {
|
||||
public static final boolean debug = true;
|
||||
|
||||
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 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 ALPHA = 0;
|
||||
private static final int LOWER = 1;
|
||||
private static final int MIXED = 2;
|
||||
private static final int PUNCT = 3;
|
||||
private static final int PUNCT_SHIFT = 4;
|
||||
|
||||
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[] = {';', '<', '>', '@', '[', 92, '}', '_', 96, '~', '!',
|
||||
13, 9, ',', ':', 10, '-', '.', '$', '/', 34, '|', '*',
|
||||
'(', ')', '?', '{', '}', 39};
|
||||
|
||||
private static final char mixed_chars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '&',
|
||||
13, 9, ',', ':', '#', '-', '.', '$', '/', '+', '%', '*',
|
||||
'=', '^'};
|
||||
|
||||
// Table containing values for the exponent of 900.
|
||||
// This is used in the numeric compaction decode algorithm.
|
||||
private static final String exp900[] =
|
||||
{"000000000000000000000000000000000000000000001",
|
||||
"000000000000000000000000000000000000000000900",
|
||||
"000000000000000000000000000000000000000810000",
|
||||
"000000000000000000000000000000000000729000000",
|
||||
"000000000000000000000000000000000656100000000",
|
||||
"000000000000000000000000000000590490000000000",
|
||||
"000000000000000000000000000531441000000000000",
|
||||
"000000000000000000000000478296900000000000000",
|
||||
"000000000000000000000430467210000000000000000",
|
||||
"000000000000000000387420489000000000000000000",
|
||||
"000000000000000348678440100000000000000000000",
|
||||
"000000000000313810596090000000000000000000000",
|
||||
"000000000282429536481000000000000000000000000",
|
||||
"000000254186582832900000000000000000000000000",
|
||||
"000228767924549610000000000000000000000000000",
|
||||
"205891132094649000000000000000000000000000000"};
|
||||
|
||||
private DecodedBitStreamParser() {
|
||||
}
|
||||
|
||||
static DecoderResult decode(int codewords[]) throws ReaderException {
|
||||
StringBuffer result = new StringBuffer(100);
|
||||
// Get compaction mode
|
||||
int codeIndex = 1;
|
||||
int code = codewords[codeIndex++];
|
||||
while (codeIndex < codewords[0]) {
|
||||
switch(code) {
|
||||
case TEXT_COMPACTION_MODE_LATCH: {
|
||||
codeIndex = textCompaction(codewords, codeIndex, result);
|
||||
break;
|
||||
}
|
||||
case BYTE_COMPACTION_MODE_LATCH: {
|
||||
codeIndex = byteCompaction(code, codewords, codeIndex, result);
|
||||
break;
|
||||
}
|
||||
case NUMERIC_COMPACTION_MODE_LATCH: {
|
||||
codeIndex = numericCompaction(codewords, codeIndex, result);
|
||||
break;
|
||||
}
|
||||
case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: {
|
||||
codeIndex = byteCompaction(code, codewords, codeIndex, result);
|
||||
break;
|
||||
}
|
||||
case BYTE_COMPACTION_MODE_LATCH_6: {
|
||||
codeIndex = byteCompaction(code, codewords, codeIndex, result);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Default to text compaction. During testing numberous 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 (codeIndex < codewords.length) {
|
||||
code = codewords[codeIndex++];
|
||||
} else {
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
}
|
||||
if (debug) System.out.println(result);
|
||||
return new DecoderResult(null, result.toString(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, StringBuffer result) {
|
||||
// 2 character per codeword
|
||||
int textCompactionData[] = new int[codewords[0] * 2];
|
||||
// Used to hold the byte compaction value if there is a mode shift
|
||||
int byteCompactionData[] = new int[codewords[0] * 2];
|
||||
|
||||
int index = 0;
|
||||
int code = 0;
|
||||
boolean end = false;
|
||||
while((codeIndex < codewords[0]) && !end) {
|
||||
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: {
|
||||
codeIndex--;
|
||||
end = true;
|
||||
break;
|
||||
}
|
||||
case BYTE_COMPACTION_MODE_LATCH: {
|
||||
codeIndex--;
|
||||
end = true;
|
||||
break;
|
||||
}
|
||||
case NUMERIC_COMPACTION_MODE_LATCH: {
|
||||
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;
|
||||
byteCompactionData[index] = code; //Integer.toHexString(code);
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case BYTE_COMPACTION_MODE_LATCH_6: {
|
||||
codeIndex--;
|
||||
end = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
decodeTextCompaction(textCompactionData, byteCompactionData, index, result);
|
||||
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.
|
||||
*/
|
||||
private static void decodeTextCompaction(int textCompactionData[],
|
||||
int byteCompactionData[],
|
||||
int length,
|
||||
StringBuffer result) {
|
||||
// Beginning from an initial state of the Alpha sub-mode
|
||||
// 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.
|
||||
int subMode = ALPHA;
|
||||
int priorToShiftMode = ALPHA;
|
||||
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 {
|
||||
if (subModeCh == 26) {
|
||||
ch = ' ';
|
||||
} else if (subModeCh == LL) {
|
||||
subMode = LOWER;
|
||||
} else if (subModeCh == ML) {
|
||||
subMode = MIXED;
|
||||
} else if (subModeCh == PS) {
|
||||
// Shift to punctuation
|
||||
priorToShiftMode = subMode;
|
||||
subMode = PUNCT_SHIFT;
|
||||
} else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) {
|
||||
result.append((char)byteCompactionData[i]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LOWER :
|
||||
// Lower (lowercase alphabetic)
|
||||
if (subModeCh < 26) {
|
||||
ch = (char)('a' + subModeCh);
|
||||
} else {
|
||||
if (subModeCh == 26) {
|
||||
ch = ' ';
|
||||
} else if (subModeCh == AL) {
|
||||
subMode = ALPHA;
|
||||
} else if (subModeCh == ML) {
|
||||
subMode = MIXED;
|
||||
} else if (subModeCh == PS) {
|
||||
// Shift to punctuation
|
||||
priorToShiftMode = subMode;
|
||||
subMode = PUNCT_SHIFT;
|
||||
} else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) {
|
||||
result.append((char)byteCompactionData[i]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MIXED :
|
||||
// Mixed (numeric and some punctuation)
|
||||
if ( subModeCh < PL ) {
|
||||
ch = mixed_chars[subModeCh];
|
||||
} else {
|
||||
if (subModeCh == PL) {
|
||||
subMode = PUNCT;
|
||||
} else if (subModeCh == 26) {
|
||||
ch = ' ';
|
||||
} else if (subModeCh == AS) {
|
||||
//mode_change = true;
|
||||
} else if (subModeCh == AL) {
|
||||
subMode = ALPHA;
|
||||
} else if (subModeCh == PS) {
|
||||
// Shift to punctuation
|
||||
priorToShiftMode = subMode;
|
||||
subMode = PUNCT_SHIFT;
|
||||
} else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) {
|
||||
result.append((char)byteCompactionData[i]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PUNCT :
|
||||
// Punctuation
|
||||
if (subModeCh < PS) {
|
||||
ch = punct_chars[subModeCh];
|
||||
} else {
|
||||
if (subModeCh == PAL) {
|
||||
subMode = ALPHA;
|
||||
} else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) {
|
||||
result.append((char)byteCompactionData[i]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PUNCT_SHIFT :
|
||||
// Restore sub-mode
|
||||
subMode = priorToShiftMode;
|
||||
if (subModeCh < PS) {
|
||||
ch = punct_chars[subModeCh];
|
||||
} else {
|
||||
if (subModeCh == PAL) {
|
||||
subMode = ALPHA;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (ch !=0) {
|
||||
// Append decoded character to result
|
||||
result.append(ch);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, StringBuffer result) {
|
||||
if (mode == BYTE_COMPACTION_MODE_LATCH) {
|
||||
// Total number of Byte Compaction characters to be encoded
|
||||
// is not a multiple of 6
|
||||
int count = 0;
|
||||
long value = 0;
|
||||
char decodedData[] = new char[6];
|
||||
int byteCompactedCodewords[] = new int[6];
|
||||
int code = 0;
|
||||
boolean end = false;
|
||||
while(( codeIndex < codewords[0]) && !end) {
|
||||
code = codewords[codeIndex++];
|
||||
if (code < TEXT_COMPACTION_MODE_LATCH) {
|
||||
byteCompactedCodewords[count] = code;
|
||||
count++;
|
||||
// Base 900
|
||||
value *= 900;
|
||||
value += code;
|
||||
} else {
|
||||
if ((code == TEXT_COMPACTION_MODE_LATCH) ||
|
||||
(code == BYTE_COMPACTION_MODE_LATCH) ||
|
||||
(code == NUMERIC_COMPACTION_MODE_LATCH) ||
|
||||
(code == BYTE_COMPACTION_MODE_LATCH_6) ||
|
||||
(code == BEGIN_MACRO_PDF417_CONTROL_BLOCK) ||
|
||||
(code == BEGIN_MACRO_PDF417_OPTIONAL_FIELD) ||
|
||||
(code == MACRO_PDF417_TERMINATOR)) {
|
||||
}
|
||||
codeIndex--;
|
||||
end = true;
|
||||
}
|
||||
if ((count % 5 == 0) && (count > 0)) {
|
||||
// Decode every 5 codewords
|
||||
// Convert to Base 256
|
||||
for (int j = 0; j < 6; ++j) {
|
||||
decodedData[5 - j] = (char) (value % 256);
|
||||
value >>= 8;
|
||||
}
|
||||
result.append(decodedData);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
// If Byte Compaction mode is invoked with codeword 901,
|
||||
// the final group of codewords is interpreted directly
|
||||
// as one byte per codeword, without compaction.
|
||||
for (int i = (( count / 5) * 5); i < count; i++) {
|
||||
result.append((char)byteCompactedCodewords[i]);
|
||||
}
|
||||
|
||||
} else if (mode == BYTE_COMPACTION_MODE_LATCH_6) {
|
||||
// Total number of Byte Compaction characters to be encoded
|
||||
// is an integer multiple of 6
|
||||
int count = 0;
|
||||
long value = 0;
|
||||
int code = 0;
|
||||
boolean end = false;
|
||||
while(( codeIndex < codewords[0]) && !end) {
|
||||
code = codewords[codeIndex++];
|
||||
char decodedData[] = new char[6];
|
||||
if (code < TEXT_COMPACTION_MODE_LATCH) {
|
||||
count += 1;
|
||||
// Base 900
|
||||
value *= 900;
|
||||
value += code;
|
||||
} else {
|
||||
if ((code == TEXT_COMPACTION_MODE_LATCH) ||
|
||||
(code == BYTE_COMPACTION_MODE_LATCH) ||
|
||||
(code == NUMERIC_COMPACTION_MODE_LATCH) ||
|
||||
(code == BYTE_COMPACTION_MODE_LATCH_6) ||
|
||||
(code == BEGIN_MACRO_PDF417_CONTROL_BLOCK) ||
|
||||
(code == BEGIN_MACRO_PDF417_OPTIONAL_FIELD) ||
|
||||
(code == MACRO_PDF417_TERMINATOR)) {
|
||||
}
|
||||
codeIndex--;
|
||||
end = true;
|
||||
}
|
||||
if ((count % 5 == 0) && ( count > 0)) {
|
||||
// Decode every 5 codewords
|
||||
// Convert to Base 256
|
||||
for (int j = 0; j < 6; ++j) {
|
||||
decodedData[5 - j] = (char) (value % 256);
|
||||
value >>= 8;
|
||||
}
|
||||
result.append(decodedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
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, StringBuffer result) {
|
||||
int code;
|
||||
int count = 0;
|
||||
boolean end = false;
|
||||
|
||||
int numericCodewords[] = new int[MAX_NUMERIC_CODEWORDS];
|
||||
|
||||
while ((codeIndex < codewords.length) && !end) {
|
||||
code = codewords[codeIndex++];
|
||||
if (code < TEXT_COMPACTION_MODE_LATCH) {
|
||||
numericCodewords[count] = code;
|
||||
count++;
|
||||
} else {
|
||||
if ((code == TEXT_COMPACTION_MODE_LATCH) ||
|
||||
(code == BYTE_COMPACTION_MODE_LATCH) ||
|
||||
(code == BYTE_COMPACTION_MODE_LATCH_6) ||
|
||||
(code == BEGIN_MACRO_PDF417_CONTROL_BLOCK) ||
|
||||
(code == BEGIN_MACRO_PDF417_OPTIONAL_FIELD) ||
|
||||
(code == MACRO_PDF417_TERMINATOR)) {
|
||||
}
|
||||
codeIndex--;
|
||||
end = true;
|
||||
}
|
||||
if ((count % MAX_NUMERIC_CODEWORDS) == 0 ||
|
||||
code == NUMERIC_COMPACTION_MODE_LATCH) {
|
||||
// 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.
|
||||
String s = decodeBase900toBase10(numericCodewords, count);
|
||||
result.append(s);
|
||||
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
|
||||
|
||||
As there are huge numbers involved here we must use fake out the maths using string
|
||||
tokens for the numbers.
|
||||
BigDecimal is not supported by J2ME.
|
||||
*/
|
||||
private static String decodeBase900toBase10(int codewords[], int count) {
|
||||
StringBuffer accum = null;
|
||||
StringBuffer value = null;
|
||||
for (int i = 0; i < count; i++) {
|
||||
value = multiply(exp900[count - i - 1], codewords[i]);
|
||||
if (accum == null) {
|
||||
// First time in accum=0
|
||||
accum = value;
|
||||
} else {
|
||||
accum = add(accum.toString(), value.toString());
|
||||
}
|
||||
}
|
||||
String result = null;
|
||||
// Remove leading '1' which was inserted to preserce
|
||||
// leading zeros
|
||||
for (int i = 0; i < accum.length(); i++) {
|
||||
if (accum.charAt(i) == '1') {
|
||||
//result = accum.substring(i + 1);
|
||||
result = accum.toString().substring(i+1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
// No leading 1 => just write the converted number.
|
||||
result = accum.toString();
|
||||
}
|
||||
if (debug) System.out.println("Big Integer=" + result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies two String numbers
|
||||
* @param value1 Any number represented as a string.
|
||||
* @param value2 A number <= 999.
|
||||
* @return the result of value1 * value2.
|
||||
*/
|
||||
private static StringBuffer multiply(String value1, int value2) {
|
||||
StringBuffer result = new StringBuffer(value1.length());
|
||||
for (int i = 0; i < value1.length(); i++) {
|
||||
// Put zeros into the result.
|
||||
result.append('0');
|
||||
}
|
||||
int hundreds = value2 / 100;
|
||||
int tens = (value2 / 10) % 10;
|
||||
int ones = value2 % 10;
|
||||
// Multiply by ones
|
||||
for (int j = 0; j < ones ; j++) {
|
||||
result = add(result.toString(), value1);
|
||||
}
|
||||
// Multiply by tens
|
||||
for (int j = 0; j < tens ; j++) {
|
||||
result = add(result.toString(), (value1 + "0").substring(1));
|
||||
}
|
||||
// Multiply by hundreds
|
||||
for (int j = 0; j < hundreds ; j++) {
|
||||
result = add(result.toString(), (value1 + "00").substring(2));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add two numbers which are represented as strings.
|
||||
* @param value1
|
||||
* @param value2
|
||||
* @return the result of value1 + value2
|
||||
*/
|
||||
private static StringBuffer add(String value1, String value2) {
|
||||
StringBuffer temp1 = new StringBuffer(5);
|
||||
StringBuffer temp2 = new StringBuffer(5);
|
||||
StringBuffer result = new StringBuffer(value1.length());
|
||||
for (int i = 0; i < value1.length(); i++) {
|
||||
// Put zeros into the result.
|
||||
result.append('0');
|
||||
}
|
||||
int carry = 0;
|
||||
for (int i = value1.length()-3; i > -1 ; i-=3) {
|
||||
|
||||
temp1.setLength(0);
|
||||
temp1.append(value1.charAt(i));
|
||||
temp1.append(value1.charAt(i + 1));
|
||||
temp1.append(value1.charAt(i + 2));
|
||||
|
||||
temp2.setLength(0);
|
||||
temp2.append(value2.charAt(i));
|
||||
temp2.append(value2.charAt(i + 1));
|
||||
temp2.append(value2.charAt(i + 2));
|
||||
|
||||
int intValue1 = Integer.parseInt(temp1.toString());
|
||||
int intValue2 = Integer.parseInt(temp2.toString());
|
||||
|
||||
int sumval = ( intValue1 + intValue2 + carry) % 1000;
|
||||
carry = (intValue1 + intValue2 + carry) / 1000;
|
||||
|
||||
result.setCharAt(i + 2, (char)((sumval % 10) + '0'));
|
||||
result.setCharAt(i + 1, (char)(((sumval / 10) % 10) + '0'));
|
||||
result.setCharAt(i, (char) (( sumval / 100) + '0'));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
private static String decodeBase900toBase10(int codewords[], int count) {
|
||||
BigInteger accum = BigInteger.valueOf(0);
|
||||
BigInteger value = null;
|
||||
for (int i = 0; i < count; i++) {
|
||||
value = BigInteger.valueOf(900).pow(count - i - 1);
|
||||
value = value.multiply(BigInteger.valueOf(codewords[i]));
|
||||
accum = accum.add(value);
|
||||
}
|
||||
if (debug) System.out.println("Big Integer " + accum);
|
||||
String result = accum.toString().substring(1);
|
||||
return result;
|
||||
}
|
||||
*/
|
||||
}
|
172
core/src/com/google/zxing/pdf417/decoder/Decoder.java
Normal file
172
core/src/com/google/zxing/pdf417/decoder/Decoder.java
Normal file
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright 2007 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.ReaderException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.common.DecoderResult;
|
||||
//import com.google.zxing.pdf417.reedsolomon.ReedSolomonDecoder;
|
||||
|
||||
/**
|
||||
* <p>The main class which implements PDF417 Code decoding -- as
|
||||
* opposed to locating and extracting the PDF417 Code from an image.</p>
|
||||
*
|
||||
* @author SITA Lab (kevin.osullivan@sita.aero)
|
||||
*/
|
||||
public final class Decoder {
|
||||
public static final boolean debug = false;
|
||||
private static final int MAX_ERRORS = 3;
|
||||
private static final int MAX_EC_CODEWORDS = 512;
|
||||
//private final ReedSolomonDecoder rsDecoder;
|
||||
|
||||
public Decoder() {
|
||||
// TODO MGMG
|
||||
//rsDecoder = new ReedSolomonDecoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Convenience method that can decode a PDF417 Code represented as a 2D array of booleans.
|
||||
* "true" is taken to mean a black module.</p>
|
||||
*
|
||||
* @param image booleans representing white/black PDF417 modules
|
||||
* @return text and bytes encoded within the PDF417 Code
|
||||
* @throws ReaderException if the PDF417 Code cannot be decoded
|
||||
*/
|
||||
public DecoderResult decode(boolean[][] image) throws ReaderException {
|
||||
int dimension = image.length;
|
||||
BitMatrix bits = new BitMatrix(dimension);
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
for (int j = 0; j < dimension; j++) {
|
||||
if (image[i][j]) {
|
||||
bits.set(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
return decode(bits);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Decodes a PDF417 Code represented as a {@link BitMatrix}.
|
||||
* A 1 or "true" is taken to mean a black module.</p>
|
||||
*
|
||||
* @param bits booleans representing white/black PDF417 Code modules
|
||||
* @return text and bytes encoded within the PDF417 Code
|
||||
* @throws ReaderException if the PDF417 Code cannot be decoded
|
||||
*/
|
||||
public DecoderResult decode(BitMatrix bits) throws ReaderException {
|
||||
// Construct a parser to read the data codewords and error-correction level
|
||||
long t2;
|
||||
long t1 = System.currentTimeMillis();
|
||||
BitMatrixParser parser = new BitMatrixParser(bits);
|
||||
int[] codewords = parser.readCodewords();
|
||||
if (codewords == null || codewords.length == 0) {
|
||||
if (debug) System.out.println("No codewords read");
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
if (debug) {
|
||||
t2 = System.currentTimeMillis();
|
||||
System.out.println("Elapsed time in ms - BitMatrixParser " +(t2-t1));
|
||||
}
|
||||
|
||||
int ecLevel = parser.getECLevel();
|
||||
int numECCodewords = 1 << (ecLevel+1);
|
||||
int erasures[] = parser.getErasures();
|
||||
|
||||
t1 = System.currentTimeMillis();
|
||||
correctErrors(codewords, erasures, numECCodewords);
|
||||
if (debug) {
|
||||
t2 = System.currentTimeMillis();
|
||||
System.out.println("Elapsed time in ms - Reed Solomon " +(t2-t1));
|
||||
}
|
||||
verifyCodewordCount(codewords, numECCodewords);
|
||||
|
||||
t1 = System.currentTimeMillis();
|
||||
DecoderResult dr = DecodedBitStreamParser.decode(codewords);
|
||||
if (debug) {
|
||||
t2 = System.currentTimeMillis();
|
||||
System.out.println("Elapsed time in ms - DecodeBitStreamParser " +(t2-t1));
|
||||
}
|
||||
// Decode the codewords
|
||||
return dr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that all is OK with the codeword array.
|
||||
* @param codewords
|
||||
* @return an index to the first data codeword.
|
||||
* @throws ReaderException
|
||||
*/
|
||||
private int verifyCodewordCount(int codewords[], int numECCodewords) throws ReaderException {
|
||||
int numberOfCodewords = 0;
|
||||
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
|
||||
if (debug) System.out.println("Not enough codewords " + codewords.length);
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
// 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.
|
||||
numberOfCodewords = codewords[0];
|
||||
if (numberOfCodewords > codewords.length) {
|
||||
if (debug) System.out.println("Invalid number of codewords[0]=" + numberOfCodewords + " codewords.length=" + codewords.length);
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
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;
|
||||
if (debug) System.out.println("Codewords[0] is zero, resetting to " +(codewords[0]));
|
||||
} else {
|
||||
if (debug) System.out.println("Codewords[0] is zero, no data codewords");
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
}
|
||||
return 1; // Index to first data codeword
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
|
||||
* correct the errors in-place using Reed-Solomon error correction.</p>
|
||||
*
|
||||
* @param codewordBytes data and error correction codewords
|
||||
* @param numDataCodewords number of codewords that are data bytes
|
||||
* @throws ReaderException if error correction fails
|
||||
*/
|
||||
private int correctErrors(int[] codewords, int[] erasures, int numECCodewords) throws ReaderException {
|
||||
if ((erasures != null && erasures.length > numECCodewords/2 + MAX_ERRORS) ||
|
||||
(numECCodewords < 0 || numECCodewords > MAX_EC_CODEWORDS)) {
|
||||
// Too many errors or EC Codewords is corrupted
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
// Try to correct the errors
|
||||
int result = 0; // rsDecoder.correctErrors(codewords, numECCodewords);
|
||||
if (debug) {
|
||||
System.out.println("Corrected errors: " + result);
|
||||
}
|
||||
if (erasures != null) {
|
||||
int numErasures = erasures.length;
|
||||
if (result > 0) {
|
||||
numErasures = numErasures - result;
|
||||
}
|
||||
if (numErasures > MAX_ERRORS) {
|
||||
// Still too many errors
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
553
core/src/com/google/zxing/pdf417/detector/Detector.java
Normal file
553
core/src/com/google/zxing/pdf417/detector/Detector.java
Normal file
|
@ -0,0 +1,553 @@
|
|||
/*
|
||||
* Copyright 2007 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.detector;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
import com.google.zxing.BlackPointEstimationMethod;
|
||||
import com.google.zxing.MonochromeBitmapSource;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitArray;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.common.DetectorResult;
|
||||
import com.google.zxing.common.GridSampler;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Encapsulates logic that can detect a PDF417 Code in an image, even if the
|
||||
* PDF417 Code is rotated or skewed, or partially obscured.
|
||||
* </p>
|
||||
*
|
||||
* @author SITA Lab (kevin.osullivan@sita.aero)
|
||||
*/
|
||||
public final class Detector {
|
||||
public static final boolean debug = false;
|
||||
public static final int MAX_AVG_VARIANCE = (int) ((1 << 8) * 0.42f);
|
||||
public static final int MAX_INDIVIDUAL_VARIANCE = (int) ((1 << 8) * 0.8f);
|
||||
// B S B S B S B S Bar/Space pattern
|
||||
private static final int[] START_PATTERN = { 8, 1, 1, 1, 1, 1, 1, 3 }; // 11111111
|
||||
// 0 1
|
||||
// 0 1
|
||||
// 0 1
|
||||
// 000
|
||||
|
||||
// B S B S B S B S B Bar/Space pattern
|
||||
private static final int[] STOP_PATTERN_REVERSE = { 1, 2, 1, 1, 1, 3, 1, 1,
|
||||
7 }; // 1111111 0 1 000 1 0 1 00 1
|
||||
|
||||
private final MonochromeBitmapSource image;
|
||||
|
||||
public Detector(MonochromeBitmapSource image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Detects a PDF417 Code in an image, simply.
|
||||
* </p>
|
||||
*
|
||||
* @return {@link DetectorResult} encapsulating results of detecting a PDF417
|
||||
* Code
|
||||
* @throws ReaderException
|
||||
* if no QR Code can be found
|
||||
*/
|
||||
public DetectorResult detect() throws ReaderException {
|
||||
return detect(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Detects a PDF417 Code in an image, simply.
|
||||
* </p>
|
||||
*
|
||||
* @param hints
|
||||
* optional hints to detector
|
||||
* @return {@link DetectorResult} encapsulating results of detecting a PDF417
|
||||
* Code
|
||||
* @throws ReaderException
|
||||
* if no PDF417 Code can be found
|
||||
*/
|
||||
public DetectorResult detect(Hashtable hints) throws ReaderException {
|
||||
if (!BlackPointEstimationMethod.TWO_D_SAMPLING.equals(image
|
||||
.getLastEstimationMethod())) {
|
||||
image.estimateBlackPoint(BlackPointEstimationMethod.TWO_D_SAMPLING, 0);
|
||||
}
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
if (debug)
|
||||
System.out.println("Width= " + width);
|
||||
if (debug)
|
||||
System.out.println("Height= " + height);
|
||||
|
||||
long t2;
|
||||
long t1 = System.currentTimeMillis();
|
||||
ResultPoint[] vertices = findVertices(image);
|
||||
if (vertices == null) { // Couldn't find the vertices
|
||||
// Maybe the image is rotated 180 degrees?
|
||||
vertices = findVertices180(image);
|
||||
/*
|
||||
* // Don't need this because the PDF417 code won't fit into // the
|
||||
* camera view finder when it is rotated. if (vertices == null) { //
|
||||
* Couldn't find the vertices // Maybe the image is rotated 90 degrees?
|
||||
* vertices = findVertices90(image); if (vertices == null) { //
|
||||
* Couldn't find the vertices // Maybe the image is rotated 270
|
||||
* degrees? vertices = findVertices270(image); } }
|
||||
*/
|
||||
}
|
||||
if (vertices != null) {
|
||||
if (debug) {
|
||||
t2 = System.currentTimeMillis();
|
||||
System.out.println("Elapsed time in ms - Find Vertices "
|
||||
+ (t2 - t1));
|
||||
}
|
||||
|
||||
float moduleWidth = computeModuleWidth(vertices);
|
||||
if (moduleWidth < 1.0f) {
|
||||
if (debug)
|
||||
System.out.println("Module Size is less than 1.0f="
|
||||
+ moduleWidth);
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
if (debug)
|
||||
System.out.println("Module Size=" + moduleWidth);
|
||||
|
||||
int dimension = computeDimension(vertices[4], vertices[6],
|
||||
vertices[5], vertices[7], moduleWidth);
|
||||
if (debug)
|
||||
System.out.println("Dimension=" + dimension);
|
||||
|
||||
// Deskew and sample image
|
||||
t1 = System.currentTimeMillis();
|
||||
BitMatrix bits = sampleGrid(image, vertices[4], vertices[5],
|
||||
vertices[6], vertices[7], dimension);
|
||||
//bits.setModuleWidth(moduleWidth);
|
||||
if (debug) {
|
||||
t2 = System.currentTimeMillis();
|
||||
System.out
|
||||
.println("Elapsed time in ms - Sampled Grid " + (t2 - t1));
|
||||
}
|
||||
return new DetectorResult(bits, new ResultPoint[] { vertices[4],
|
||||
vertices[5], vertices[6], vertices[7] });
|
||||
} else {
|
||||
if (debug) {
|
||||
System.out.println("Unable to locate vertices");
|
||||
}
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the vertices and the codewords area of a black blob using the Start
|
||||
* and Stop patterns as locators.
|
||||
*
|
||||
* @param image
|
||||
* the scanned barcode image.
|
||||
* @return the an array containing the vertices. vertices[0] x, y top left
|
||||
* barcode vertices[1] x, y bottom left barcode vertices[2] x, y top
|
||||
* right barcode vertices[3] x, y bottom right barcode vertices[4] x,
|
||||
* y top left codeword area vertices[5] x, y bottom left codeword
|
||||
* area vertices[6] x, y top right codeword area vertices[7] x, y
|
||||
* bottom right codeword area
|
||||
*/
|
||||
private ResultPoint[] findVertices(MonochromeBitmapSource image) {
|
||||
int height = image.getHeight();
|
||||
int width = image.getWidth();
|
||||
|
||||
ResultPoint[] result = new ResultPoint[8];
|
||||
BitArray row = null;
|
||||
boolean found = false;
|
||||
|
||||
int[] loc = null;
|
||||
// Top Left
|
||||
for (int i = 0; i < height; i++) {
|
||||
row = image.getBlackRow(i, null, 0, width / 4);
|
||||
loc = findGuardPattern(row, 0, START_PATTERN);
|
||||
if (loc != null) {
|
||||
if (debug)
|
||||
System.out.println("Found START pattern at X=" + loc[0]
|
||||
+ " End X=" + loc[1] + ", Y=" + i);
|
||||
result[0] = new ResultPoint(loc[0], i);
|
||||
result[4] = new ResultPoint(loc[1], i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Bottom left
|
||||
if (found) { // Found the Top Left vertex
|
||||
found = false;
|
||||
for (int i = height - 1; i > 0; i--) {
|
||||
row = image.getBlackRow(i, null, 0, width / 4);
|
||||
loc = findGuardPattern(row, 0, START_PATTERN);
|
||||
if (loc != null) {
|
||||
if (debug)
|
||||
System.out.println("Found START pattern at X=" + loc[0]
|
||||
+ " End X=" + loc[1] + ", Y=" + i);
|
||||
result[1] = new ResultPoint(loc[0], i);
|
||||
result[5] = new ResultPoint(loc[1], i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Top right
|
||||
if (found) { // Found the Bottom Left vertex
|
||||
found = false;
|
||||
for (int i = 0; i < height; i++) {
|
||||
row = image.getBlackRow(i, null, (width * 3) / 4, width / 4);
|
||||
row.reverse();
|
||||
loc = findGuardPattern(row, 0, STOP_PATTERN_REVERSE);
|
||||
if (loc != null) {
|
||||
if (debug)
|
||||
System.out.println("Found STOP pattern at X="
|
||||
+ (width - loc[0]) + " End X=" + (width - loc[1])
|
||||
+ ", Y=" + i);
|
||||
result[2] = new ResultPoint(width - loc[0], i);
|
||||
result[6] = new ResultPoint(width - loc[1], i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Bottom right
|
||||
if (found) { // Found the Top right vertex
|
||||
found = false;
|
||||
for (int i = height - 1; i > 0; i--) {
|
||||
row = image.getBlackRow(i, null, (width * 3) / 4, width / 4);
|
||||
row.reverse();
|
||||
loc = findGuardPattern(row, 0, STOP_PATTERN_REVERSE);
|
||||
if (loc != null) {
|
||||
if (debug)
|
||||
System.out.println("Found STOP pattern at X="
|
||||
+ (width - loc[0]) + " End X=" + (width - loc[1])
|
||||
+ ", Y=" + i);
|
||||
result[3] = new ResultPoint(width - loc[0], i);
|
||||
result[7] = new ResultPoint(width - loc[1], i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the vertices and the codewords area of a black blob using the Start
|
||||
* and Stop patterns as locators. This assumes that the image is rotated 180
|
||||
* degrees and if it locates the start and stop patterns at it will re-map
|
||||
* the vertices for a 0 degree rotation.
|
||||
*
|
||||
* @param image
|
||||
* the scanned barcode image.
|
||||
* @return the an array containing the vertices. vertices[0] x, y top left
|
||||
* barcode vertices[1] x, y bottom left barcode vertices[2] x, y top
|
||||
* right barcode vertices[3] x, y bottom right barcode vertices[4] x,
|
||||
* y top left codeword area vertices[5] x, y bottom left codeword
|
||||
* area vertices[6] x, y top right codeword area vertices[7] x, y
|
||||
* bottom right codeword area
|
||||
*/
|
||||
private ResultPoint[] findVertices180(MonochromeBitmapSource image) {
|
||||
int height = image.getHeight();
|
||||
int width = image.getWidth();
|
||||
|
||||
ResultPoint[] result = new ResultPoint[8];
|
||||
BitArray row = null;
|
||||
boolean found = false;
|
||||
|
||||
int[] loc = null;
|
||||
// Top Left
|
||||
for (int i = height - 1; i > 0; i--) {
|
||||
row = image.getBlackRow(i, null, 0, width / 4);
|
||||
row.reverse();
|
||||
loc = findGuardPattern(row, 0, START_PATTERN);
|
||||
if (loc != null) {
|
||||
if (debug)
|
||||
System.out.println("Found START pattern at X="
|
||||
+ (width - loc[0]) + " End X=" + (width - loc[1]) + ", Y="
|
||||
+ i);
|
||||
result[0] = new ResultPoint(width - loc[0], i);
|
||||
result[4] = new ResultPoint(width - loc[1], i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Bottom Left
|
||||
if (found) { // Found the Top Left vertex
|
||||
found = false;
|
||||
for (int i = 0; i < height; i++) {
|
||||
row = image.getBlackRow(i, null, 0, width / 4);
|
||||
row.reverse();
|
||||
loc = findGuardPattern(row, 0, START_PATTERN);
|
||||
if (loc != null) {
|
||||
if (debug)
|
||||
System.out.println("Found START pattern at X="
|
||||
+ (width - loc[0]) + " End X=" + (width - loc[1])
|
||||
+ ", Y=" + i);
|
||||
result[1] = new ResultPoint(width - loc[0], i);
|
||||
result[5] = new ResultPoint(width - loc[1], i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Top Right
|
||||
if (found) { // Found the Bottom Left vertex
|
||||
found = false;
|
||||
for (int i = height - 1; i > 0; i--) {
|
||||
row = image.getBlackRow(i, null, (width * 3) / 4, width / 4);
|
||||
loc = findGuardPattern(row, 0, STOP_PATTERN_REVERSE);
|
||||
if (loc != null) {
|
||||
if (debug)
|
||||
System.out.println("Found STOP pattern at X=" + loc[0]
|
||||
+ " End X=" + loc[1] + ", Y=" + i);
|
||||
result[2] = new ResultPoint(loc[0], i);
|
||||
result[6] = new ResultPoint(loc[1], i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Bottom Right
|
||||
if (found) { // Found the Top Right vertex
|
||||
found = false;
|
||||
for (int i = 0; i < height; i++) {
|
||||
row = image.getBlackRow(i, null, (width * 3) / 4, width / 4);
|
||||
loc = findGuardPattern(row, 0, STOP_PATTERN_REVERSE);
|
||||
if (loc != null) {
|
||||
if (debug)
|
||||
System.out.println("Found STOP pattern at X=" + loc[0]
|
||||
+ " End X=" + loc[1] + ", Y=" + i);
|
||||
result[3] = new ResultPoint(loc[0], i);
|
||||
result[7] = new ResultPoint(loc[1], i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Estimates module size (pixels in a module) based on the Start and End
|
||||
* finder patterns -- it uses
|
||||
* {@link #sizeOfBlackWhiteBlackRun(int, int, int, int)} to figure the width
|
||||
* of each.
|
||||
*
|
||||
* @param vertices
|
||||
* [] vertices[0] x, y top left barcode vertices[1] x, y bottom
|
||||
* left barcode vertices[2] x, y top right barcode vertices[3] x, y
|
||||
* bottom right barcode vertices[4] x, y top left Codeword area
|
||||
* vertices[5] x, y bottom left Codeword area vertices[6] x, y top
|
||||
* right Codeword area vertices[7] x, y bottom right Codeword area
|
||||
* @return the module size.
|
||||
*/
|
||||
private float computeModuleWidth(ResultPoint[] vertices) {
|
||||
float pixels1 = ResultPoint.distance(vertices[0], vertices[4]);
|
||||
float pixels2 = ResultPoint.distance(vertices[1], vertices[5]);
|
||||
float moduleWidth1 = (pixels1 + pixels2) / (17 * 2.0f);
|
||||
float pixels3 = ResultPoint.distance(vertices[6], vertices[2]);
|
||||
float pixels4 = ResultPoint.distance(vertices[7], vertices[3]);
|
||||
float moduleWidth2 = (pixels3 + pixels4) / (18 * 2.0f);
|
||||
return (moduleWidth1 + moduleWidth2) / 2.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Computes the dimension (number of modules in a row) of the PDF417 Code
|
||||
* based on vertices of the codeword area and estimated module size.
|
||||
*
|
||||
* @param topLeft
|
||||
* of codeword area
|
||||
* @param topRight
|
||||
* of codeword area
|
||||
* @param bottomLeft
|
||||
* of codeword area
|
||||
* @param bottomRight
|
||||
* of codeword are
|
||||
* @param moduleWidth
|
||||
* estimated module size
|
||||
* @return the number of modules in a row.
|
||||
*/
|
||||
private static int computeDimension(ResultPoint topLeft,
|
||||
ResultPoint topRight, ResultPoint bottomLeft, ResultPoint bottomRight,
|
||||
float moduleWidth) {
|
||||
|
||||
int topRowDimension = round(ResultPoint
|
||||
.distance(topLeft, topRight)
|
||||
/ moduleWidth);
|
||||
int bottomRowDimension = round(ResultPoint.distance(bottomLeft,
|
||||
bottomRight)
|
||||
/ moduleWidth);
|
||||
int dimension = ((((topRowDimension + bottomRowDimension) >> 1) + 8) / 17) * 17;
|
||||
return dimension;
|
||||
/*
|
||||
* int topRowDimension = round(ResultPoint.distance(topLeft,
|
||||
* topRight)); //moduleWidth); int bottomRowDimension =
|
||||
* round(ResultPoint.distance(bottomLeft, bottomRight)); //
|
||||
* moduleWidth); int dimension = ((topRowDimension + bottomRowDimension)
|
||||
* >> 1); // Round up to nearest 17 modules i.e. there are 17 modules per
|
||||
* codeword //int dimension = ((((topRowDimension + bottomRowDimension) >>
|
||||
* 1) + 8) / 17) * 17; return dimension;
|
||||
*/
|
||||
}
|
||||
|
||||
private static BitMatrix sampleGrid(MonochromeBitmapSource image,
|
||||
ResultPoint topLeft, ResultPoint bottomLeft, ResultPoint topRight,
|
||||
ResultPoint bottomRight, int dimension) throws ReaderException {
|
||||
|
||||
// Note that unlike in the QR Code sampler, we didn't find the center of
|
||||
// modules, but the
|
||||
// very corners. So there is no 0.5f here; 0.0f is right.
|
||||
GridSampler sampler = GridSampler.getInstance();
|
||||
return sampler.sampleGrid(image, dimension, 0.0f, // p1ToX
|
||||
0.0f, // p1ToY
|
||||
dimension, // p2ToX
|
||||
0.0f, // p2ToY
|
||||
dimension, // p3ToX
|
||||
dimension, // p3ToY
|
||||
0.0f, // p4ToX
|
||||
dimension, // p4ToY
|
||||
topLeft.getX(), // p1FromX
|
||||
topLeft.getY(), // p1FromY
|
||||
topRight.getX(), // p2FromX
|
||||
topRight.getY(), // p2FromY
|
||||
bottomRight.getX(), // p3FromX
|
||||
bottomRight.getY(), // p3FromY
|
||||
bottomLeft.getX(), // p4FromX
|
||||
bottomLeft.getY()); // p4FromY
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends up being a bit faster than Math.round(). This merely rounds its
|
||||
* argument to the nearest int, where x.5 rounds up.
|
||||
*/
|
||||
private static int round(float d) {
|
||||
return (int) (d + 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param row
|
||||
* row of black/white values to search
|
||||
* @param rowOffset
|
||||
* position to start search
|
||||
* @param pattern
|
||||
* pattern of counts of number of black and white pixels that are
|
||||
* being searched for as a pattern
|
||||
* @return start/end horizontal offset of guard pattern, as an array of two
|
||||
* ints.
|
||||
*/
|
||||
int[] findGuardPattern(BitArray row, int rowOffset, int[] pattern) {
|
||||
int patternLength = pattern.length;
|
||||
int[] counters = new int[patternLength];
|
||||
int width = row.getSize();
|
||||
boolean isWhite = false;
|
||||
|
||||
int counterPosition = 0;
|
||||
int patternStart = rowOffset;
|
||||
for (int x = rowOffset; x < width; x++) {
|
||||
boolean pixel = row.get(x);
|
||||
if ((!pixel && isWhite) || (pixel && !isWhite)) {
|
||||
counters[counterPosition]++;
|
||||
} else {
|
||||
if (counterPosition == patternLength - 1) {
|
||||
if (patternMatchVariance(counters, pattern,
|
||||
MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
|
||||
return new int[] { patternStart, x };
|
||||
}
|
||||
patternStart += counters[0] + counters[1];
|
||||
for (int y = 2; y < patternLength; y++) {
|
||||
counters[y - 2] = counters[y];
|
||||
}
|
||||
counters[patternLength - 2] = 0;
|
||||
counters[patternLength - 1] = 0;
|
||||
counterPosition--;
|
||||
} else {
|
||||
counterPosition++;
|
||||
}
|
||||
counters[counterPosition] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how closely a set of observed counts of runs of black/white
|
||||
* values matches a given target pattern. This is reported as the ratio of
|
||||
* the total variance from the expected pattern proportions across all
|
||||
* pattern elements, to the length of the pattern.
|
||||
*
|
||||
* @param counters
|
||||
* observed counters
|
||||
* @param pattern
|
||||
* expected pattern
|
||||
* @param maxIndividualVariance
|
||||
* The most any counter can differ before we give up
|
||||
* @return ratio of total variance between counters and pattern compared to
|
||||
* total pattern size, where the ratio has been multiplied by 256.
|
||||
* So, 0 means no variance (perfect match); 256 means the total
|
||||
* variance between counters and patterns equals the pattern length,
|
||||
* higher values mean even more variance
|
||||
*/
|
||||
public static int patternMatchVariance(int[] counters, int[] pattern,
|
||||
int maxIndividualVariance) {
|
||||
int numCounters = counters.length;
|
||||
int total = 0;
|
||||
int patternLength = 0;
|
||||
for (int i = 0; i < numCounters; i++) {
|
||||
total += counters[i];
|
||||
patternLength += pattern[i];
|
||||
}
|
||||
if (total < patternLength) {
|
||||
// If we don't even have one pixel per unit of bar width, assume this
|
||||
// is too small
|
||||
// to reliably match, so fail:
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
// We're going to fake floating-point math in integers. We just need to
|
||||
// use more bits.
|
||||
// Scale up patternLength so that intermediate values below like
|
||||
// scaledCounter will have
|
||||
// more "significant digits"
|
||||
int unitBarWidth = (total << 8) / patternLength;
|
||||
maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> 8;
|
||||
|
||||
int totalVariance = 0;
|
||||
for (int x = 0; x < numCounters; x++) {
|
||||
int counter = counters[x] << 8;
|
||||
int scaledPattern = pattern[x] * unitBarWidth;
|
||||
int variance = counter > scaledPattern ? counter - scaledPattern
|
||||
: scaledPattern - counter;
|
||||
if (variance > maxIndividualVariance) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
totalVariance += variance;
|
||||
}
|
||||
return totalVariance / total;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue