blob: fb8f79d0bfe48e399436a068d46db2671943f6ee [file] [log] [blame]
/*
* 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.multi.qrcode;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.NotFoundException;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultMetadataType;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.DecoderResult;
import com.google.zxing.common.DetectorResult;
import com.google.zxing.multi.MultipleBarcodeReader;
import com.google.zxing.multi.qrcode.detector.MultiDetector;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData;
import java.io.Serializable;
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.
*
* @author Sean Owen
* @author Hannes Erven
*/
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 {
return decodeMultiple(image, null);
}
@Override
public Result[] decodeMultiple(BinaryBitmap image, Map<DecodeHintType,?> hints) throws NotFoundException {
List<Result> results = new ArrayList<>();
DetectorResult[] detectorResults = new MultiDetector(image.getBlackMatrix()).detectMulti(hints);
for (DetectorResult detectorResult : detectorResults) {
try {
DecoderResult decoderResult = getDecoder().decode(detectorResult.getBits(), hints);
ResultPoint[] points = detectorResult.getPoints();
// If the code was mirrored: swap the bottom-left and the top-right points.
if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
}
Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points,
BarcodeFormat.QR_CODE);
List<byte[]> byteSegments = decoderResult.getByteSegments();
if (byteSegments != null) {
result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
}
String ecLevel = decoderResult.getECLevel();
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
}
}
if (results.isEmpty()) {
return EMPTY_RESULT_ARRAY;
} else {
results = processStructuredAppend(results);
return results.toArray(EMPTY_RESULT_ARRAY);
}
}
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)) {
@SuppressWarnings("unchecked")
Iterable<byte[]> byteSegments =
(Iterable<byte[]>) saResult.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS);
for (byte[] segment : byteSegments) {
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)) {
@SuppressWarnings("unchecked")
Iterable<byte[]> byteSegments =
(Iterable<byte[]>) saResult.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS);
for (byte[] segment : byteSegments) {
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>, Serializable {
@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);
return Integer.compare(aNumber, bNumber);
}
}
}