mirror of
https://github.com/zxing/zxing.git
synced 2025-01-12 19:57:27 -08:00
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:
parent
21d22400b9
commit
9a5391b653
|
@ -82,4 +82,16 @@ public enum ResultMetadataType {
|
||||||
*/
|
*/
|
||||||
PDF417_EXTRA_METADATA,
|
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,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,15 +34,28 @@ public final class DecoderResult {
|
||||||
private Integer errorsCorrected;
|
private Integer errorsCorrected;
|
||||||
private Integer erasures;
|
private Integer erasures;
|
||||||
private Object other;
|
private Object other;
|
||||||
|
private final int structuredAppendParity;
|
||||||
|
private final int structuredAppendSequenceNumber;
|
||||||
|
|
||||||
public DecoderResult(byte[] rawBytes,
|
public DecoderResult(byte[] rawBytes,
|
||||||
String text,
|
String text,
|
||||||
List<byte[]> byteSegments,
|
List<byte[]> byteSegments,
|
||||||
String ecLevel) {
|
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.rawBytes = rawBytes;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.byteSegments = byteSegments;
|
this.byteSegments = byteSegments;
|
||||||
this.ecLevel = ecLevel;
|
this.ecLevel = ecLevel;
|
||||||
|
this.structuredAppendParity = saParity;
|
||||||
|
this.structuredAppendSequenceNumber = saSequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getRawBytes() {
|
public byte[] getRawBytes() {
|
||||||
|
@ -85,4 +98,16 @@ public final class DecoderResult {
|
||||||
this.other = other;
|
this.other = other;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasStructuredAppend() {
|
||||||
|
return structuredAppendParity >= 0 && structuredAppendSequenceNumber >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStructuredAppendParity() {
|
||||||
|
return structuredAppendParity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStructuredAppendSequenceNumber() {
|
||||||
|
return structuredAppendSequenceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -32,8 +32,11 @@ import com.google.zxing.qrcode.QRCodeReader;
|
||||||
import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData;
|
import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This implementation can detect and decode multiple QR Codes in an image.
|
* 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 {
|
public final class QRCodeMultiReader extends QRCodeReader implements MultipleBarcodeReader {
|
||||||
|
|
||||||
private static final Result[] EMPTY_RESULT_ARRAY = new Result[0];
|
private static final Result[] EMPTY_RESULT_ARRAY = new Result[0];
|
||||||
|
private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException {
|
public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException {
|
||||||
|
@ -72,6 +76,12 @@ public final class QRCodeMultiReader extends QRCodeReader implements MultipleBar
|
||||||
if (ecLevel != null) {
|
if (ecLevel != null) {
|
||||||
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
|
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);
|
results.add(result);
|
||||||
} catch (ReaderException re) {
|
} catch (ReaderException re) {
|
||||||
// ignore and continue
|
// ignore and continue
|
||||||
|
@ -80,8 +90,85 @@ public final class QRCodeMultiReader extends QRCodeReader implements MultipleBar
|
||||||
if (results.isEmpty()) {
|
if (results.isEmpty()) {
|
||||||
return EMPTY_RESULT_ARRAY;
|
return EMPTY_RESULT_ARRAY;
|
||||||
} else {
|
} else {
|
||||||
|
results = processStructuredAppend(results);
|
||||||
return results.toArray(new Result[results.size()]);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,12 @@ public class QRCodeReader implements Reader {
|
||||||
if (ecLevel != null) {
|
if (ecLevel != null) {
|
||||||
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,9 @@ final class DecodedBitStreamParser {
|
||||||
BitSource bits = new BitSource(bytes);
|
BitSource bits = new BitSource(bytes);
|
||||||
StringBuilder result = new StringBuilder(50);
|
StringBuilder result = new StringBuilder(50);
|
||||||
List<byte[]> byteSegments = new ArrayList<>(1);
|
List<byte[]> byteSegments = new ArrayList<>(1);
|
||||||
|
int symbolSequence = -1;
|
||||||
|
int parityData = -1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CharacterSetECI currentCharacterSetECI = null;
|
CharacterSetECI currentCharacterSetECI = null;
|
||||||
boolean fc1InEffect = false;
|
boolean fc1InEffect = false;
|
||||||
|
@ -80,9 +83,10 @@ final class DecodedBitStreamParser {
|
||||||
if (bits.available() < 16) {
|
if (bits.available() < 16) {
|
||||||
throw FormatException.getFormatInstance();
|
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
|
// 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) {
|
} else if (mode == Mode.ECI) {
|
||||||
// Count doesn't apply to ECI
|
// Count doesn't apply to ECI
|
||||||
int value = parseECIValue(bits);
|
int value = parseECIValue(bits);
|
||||||
|
@ -126,7 +130,9 @@ final class DecodedBitStreamParser {
|
||||||
return new DecoderResult(bytes,
|
return new DecoderResult(bytes,
|
||||||
result.toString(),
|
result.toString(),
|
||||||
byteSegments.isEmpty() ? null : byteSegments,
|
byteSegments.isEmpty() ? null : byteSegments,
|
||||||
ecLevel == null ? null : ecLevel.toString());
|
ecLevel == null ? null : ecLevel.toString(),
|
||||||
|
symbolSequence,
|
||||||
|
parityData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue