Issue 1821: structured append support in QR code from mike06j

git-svn-id: https://zxing.googlecode.com/svn/trunk@2994 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen 2013-12-13 15:50:26 +00:00
parent 21d22400b9
commit 9a5391b653
5 changed files with 139 additions and 3 deletions

View file

@ -82,4 +82,16 @@ public enum ResultMetadataType {
*/
PDF417_EXTRA_METADATA,
/**
* If the code format supports structured append and the current scanned code is part of one then the
* sequence number is given with it.
*/
STRUCTURED_APPEND_SEQUENCE,
/**
* If the code format supports structured append and the current scanned code is part of one then the
* parity is given with it.
*/
STRUCTURED_APPEND_PARITY,
}

View file

@ -34,15 +34,28 @@ public final class DecoderResult {
private Integer errorsCorrected;
private Integer erasures;
private Object other;
private final int structuredAppendParity;
private final int structuredAppendSequenceNumber;
public DecoderResult(byte[] rawBytes,
String text,
List<byte[]> byteSegments,
String ecLevel) {
this(rawBytes, text, byteSegments, ecLevel, -1, -1);
}
public DecoderResult(byte[] rawBytes,
String text,
List<byte[]> byteSegments,
String ecLevel,
int saSequence,
int saParity) {
this.rawBytes = rawBytes;
this.text = text;
this.byteSegments = byteSegments;
this.ecLevel = ecLevel;
this.structuredAppendParity = saParity;
this.structuredAppendSequenceNumber = saSequence;
}
public byte[] getRawBytes() {
@ -85,4 +98,16 @@ public final class DecoderResult {
this.other = other;
}
public boolean hasStructuredAppend() {
return structuredAppendParity >= 0 && structuredAppendSequenceNumber >= 0;
}
public int getStructuredAppendParity() {
return structuredAppendParity;
}
public int getStructuredAppendSequenceNumber() {
return structuredAppendSequenceNumber;
}
}

View file

@ -32,8 +32,11 @@ import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Collections;
import java.util.Comparator;
/**
* This implementation can detect and decode multiple QR Codes in an image.
@ -44,6 +47,7 @@ import java.util.Map;
public final class QRCodeMultiReader extends QRCodeReader implements MultipleBarcodeReader {
private static final Result[] EMPTY_RESULT_ARRAY = new Result[0];
private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
@Override
public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException {
@ -72,6 +76,12 @@ public final class QRCodeMultiReader extends QRCodeReader implements MultipleBar
if (ecLevel != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
}
if (decoderResult.hasStructuredAppend()) {
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
decoderResult.getStructuredAppendSequenceNumber());
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
decoderResult.getStructuredAppendParity());
}
results.add(result);
} catch (ReaderException re) {
// ignore and continue
@ -80,8 +90,85 @@ public final class QRCodeMultiReader extends QRCodeReader implements MultipleBar
if (results.isEmpty()) {
return EMPTY_RESULT_ARRAY;
} else {
results = processStructuredAppend(results);
return results.toArray(new Result[results.size()]);
}
}
private static List<Result> processStructuredAppend(List<Result> results) {
boolean hasSA = false;
// first, check, if there is at least on SA result in the list
for (Result result : results) {
if (result.getResultMetadata().containsKey(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE)) {
hasSA = true;
break;
}
}
if (!hasSA) {
return results;
}
// it is, second, split the lists and built a new result list
List<Result> newResults = new ArrayList<>();
List<Result> saResults = new ArrayList<>();
for (Result result : results) {
newResults.add(result);
if (result.getResultMetadata().containsKey(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE)) {
saResults.add(result);
}
}
// sort and concatenate the SA list items
Collections.sort(saResults, new SAComparator());
StringBuilder concatedText = new StringBuilder();
int rawBytesLen = 0;
int byteSegmentLength = 0;
for (Result saResult : saResults) {
concatedText.append(saResult.getText());
rawBytesLen += saResult.getRawBytes().length;
if (saResult.getResultMetadata().containsKey(ResultMetadataType.BYTE_SEGMENTS)) {
for (byte[] segment : (Iterable<byte[]>) saResult.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS)) {
byteSegmentLength += segment.length;
}
}
}
byte[] newRawBytes = new byte[rawBytesLen];
byte[] newByteSegment = new byte[byteSegmentLength];
int newRawBytesIndex = 0;
int byteSegmentIndex = 0;
for (Result saResult : saResults) {
System.arraycopy(saResult.getRawBytes(), 0, newRawBytes, newRawBytesIndex, saResult.getRawBytes().length);
newRawBytesIndex += saResult.getRawBytes().length;
if (saResult.getResultMetadata().containsKey(ResultMetadataType.BYTE_SEGMENTS)) {
for (byte[] segment : (Iterable<byte[]>) saResult.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS)) {
System.arraycopy(segment, 0, newByteSegment, byteSegmentIndex, segment.length);
byteSegmentIndex += segment.length;
}
}
}
Result newResult = new Result(concatedText.toString(), newRawBytes, NO_POINTS, BarcodeFormat.QR_CODE);
if (byteSegmentLength > 0) {
Collection<byte[]> byteSegmentList = new ArrayList<>();
byteSegmentList.add(newByteSegment);
newResult.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegmentList);
}
newResults.add(newResult);
return newResults;
}
private static final class SAComparator implements Comparator<Result> {
@Override
public int compare(Result a, Result b) {
int aNumber = (int) (a.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE));
int bNumber = (int) (b.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE));
if (aNumber < bNumber) {
return -1;
}
if (aNumber > bNumber) {
return 1;
}
return 0;
}
}
}

View file

@ -93,6 +93,12 @@ public class QRCodeReader implements Reader {
if (ecLevel != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
}
if (decoderResult.hasStructuredAppend()) {
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
decoderResult.getStructuredAppendSequenceNumber());
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
decoderResult.getStructuredAppendParity());
}
return result;
}

View file

@ -60,6 +60,9 @@ final class DecodedBitStreamParser {
BitSource bits = new BitSource(bytes);
StringBuilder result = new StringBuilder(50);
List<byte[]> byteSegments = new ArrayList<>(1);
int symbolSequence = -1;
int parityData = -1;
try {
CharacterSetECI currentCharacterSetECI = null;
boolean fc1InEffect = false;
@ -80,9 +83,10 @@ final class DecodedBitStreamParser {
if (bits.available() < 16) {
throw FormatException.getFormatInstance();
}
// not really supported; all we do is ignore it
// sequence number and parity is added later to the result metadata
// Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
bits.readBits(16);
symbolSequence = bits.readBits(8);
parityData = bits.readBits(8);
} else if (mode == Mode.ECI) {
// Count doesn't apply to ECI
int value = parseECIValue(bits);
@ -126,7 +130,9 @@ final class DecodedBitStreamParser {
return new DecoderResult(bytes,
result.toString(),
byteSegments.isEmpty() ? null : byteSegments,
ecLevel == null ? null : ecLevel.toString());
ecLevel == null ? null : ecLevel.toString(),
symbolSequence,
parityData);
}
/**