qr_code_multi_reader ported

This commit is contained in:
Henry Schimke
2022-12-01 11:17:23 -06:00
parent 5273136c96
commit 63a10db413
9 changed files with 223 additions and 173 deletions

View File

@@ -18,7 +18,7 @@
// import java.util.List; // import java.util.List;
use std::any::Any; use std::{any::Any, rc::Rc};
/** /**
* <p>Encapsulates the result of decoding a matrix of bits. This typically * <p>Encapsulates the result of decoding a matrix of bits. This typically
@@ -35,7 +35,7 @@ pub struct DecoderRXingResult {
ecLevel: String, ecLevel: String,
errorsCorrected: u64, errorsCorrected: u64,
erasures: u64, erasures: u64,
other: Box<dyn Any>, other: Rc<dyn Any>,
structuredAppendParity: i32, structuredAppendParity: i32,
structuredAppendSequenceNumber: i32, structuredAppendSequenceNumber: i32,
symbologyModifier: u32, symbologyModifier: u32,
@@ -106,7 +106,7 @@ impl DecoderRXingResult {
ecLevel, ecLevel,
errorsCorrected: 0, errorsCorrected: 0,
erasures: 0, erasures: 0,
other: Box::new(false), other: Rc::new(false),
structuredAppendParity: saParity, structuredAppendParity: saParity,
structuredAppendSequenceNumber: saSequence, structuredAppendSequenceNumber: saSequence,
symbologyModifier, symbologyModifier,
@@ -182,11 +182,11 @@ impl DecoderRXingResult {
/** /**
* @return arbitrary additional metadata * @return arbitrary additional metadata
*/ */
pub fn getOther(&self) -> &Box<dyn Any> { pub fn getOther(&self) -> Rc<dyn Any> {
&self.other self.other.clone()
} }
pub fn setOther(&mut self, other: Box<dyn Any>) { pub fn setOther(&mut self, other: Rc<dyn Any>) {
self.other = other self.other = other
} }

View File

@@ -1 +1,3 @@
mod multiple_barcode_reader;
pub mod qrcode; pub mod qrcode;
pub use multiple_barcode_reader::*;

View File

@@ -14,14 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.zxing.multi; use crate::{BinaryBitmap, DecodingHintDictionary, Exceptions, RXingResult};
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.NotFoundException;
import com.google.zxing.RXingResult;
import java.util.Map;
/** /**
* Implementation of this interface attempt to read several barcodes from one image. * Implementation of this interface attempt to read several barcodes from one image.
@@ -29,11 +22,12 @@ import java.util.Map;
* @see com.google.zxing.Reader * @see com.google.zxing.Reader
* @author Sean Owen * @author Sean Owen
*/ */
public interface MultipleBarcodeReader { pub trait MultipleBarcodeReader {
fn decodeMultiple(&self, image: &BinaryBitmap) -> Result<Vec<RXingResult>, Exceptions>;
RXingResult[] decodeMultiple(BinaryBitmap image) throws NotFoundException;
RXingResult[] decodeMultiple(BinaryBitmap image,
Map<DecodeHintType,?> hints) throws NotFoundException;
fn decodeMultipleWithHints(
&self,
image: &BinaryBitmap,
hints: &DecodingHintDictionary,
) -> Result<Vec<RXingResult>, Exceptions>;
} }

View File

@@ -1,149 +0,0 @@
/*
* 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.RXingResult;
import com.google.zxing.RXingResultMetadataType;
import com.google.zxing.RXingResultPoint;
import com.google.zxing.common.DecoderRXingResult;
import com.google.zxing.common.DetectorRXingResult;
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.ByteArrayOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
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 RXingResult[] EMPTY_RESULT_ARRAY = new RXingResult[0];
private static final RXingResultPoint[] NO_POINTS = new RXingResultPoint[0];
@Override
public RXingResult[] decodeMultiple(BinaryBitmap image) throws NotFoundException {
return decodeMultiple(image, null);
}
@Override
public RXingResult[] decodeMultiple(BinaryBitmap image, Map<DecodeHintType,?> hints) throws NotFoundException {
List<RXingResult> results = new ArrayList<>();
DetectorRXingResult[] detectorRXingResults = new MultiDetector(image.getBlackMatrix()).detectMulti(hints);
for (DetectorRXingResult detectorRXingResult : detectorRXingResults) {
try {
DecoderRXingResult decoderRXingResult = getDecoder().decode(detectorRXingResult.getBits(), hints);
RXingResultPoint[] points = detectorRXingResult.getPoints();
// If the code was mirrored: swap the bottom-left and the top-right points.
if (decoderRXingResult.getOther() instanceof QRCodeDecoderMetaData) {
((QRCodeDecoderMetaData) decoderRXingResult.getOther()).applyMirroredCorrection(points);
}
RXingResult result = new RXingResult(decoderRXingResult.getText(), decoderRXingResult.getRawBytes(), points,
BarcodeFormat.QR_CODE);
List<byte[]> byteSegments = decoderRXingResult.getByteSegments();
if (byteSegments != null) {
result.putMetadata(RXingResultMetadataType.BYTE_SEGMENTS, byteSegments);
}
String ecLevel = decoderRXingResult.getECLevel();
if (ecLevel != null) {
result.putMetadata(RXingResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
}
if (decoderRXingResult.hasStructuredAppend()) {
result.putMetadata(RXingResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
decoderRXingResult.getStructuredAppendSequenceNumber());
result.putMetadata(RXingResultMetadataType.STRUCTURED_APPEND_PARITY,
decoderRXingResult.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);
}
}
static List<RXingResult> processStructuredAppend(List<RXingResult> results) {
List<RXingResult> newRXingResults = new ArrayList<>();
List<RXingResult> saRXingResults = new ArrayList<>();
for (RXingResult result : results) {
if (result.getRXingResultMetadata().containsKey(RXingResultMetadataType.STRUCTURED_APPEND_SEQUENCE)) {
saRXingResults.add(result);
} else {
newRXingResults.add(result);
}
}
if (saRXingResults.isEmpty()) {
return results;
}
// sort and concatenate the SA list items
Collections.sort(saRXingResults, new SAComparator());
StringBuilder newText = new StringBuilder();
ByteArrayOutputStream newRawBytes = new ByteArrayOutputStream();
ByteArrayOutputStream newByteSegment = new ByteArrayOutputStream();
for (RXingResult saRXingResult : saRXingResults) {
newText.append(saRXingResult.getText());
byte[] saBytes = saRXingResult.getRawBytes();
newRawBytes.write(saBytes, 0, saBytes.length);
@SuppressWarnings("unchecked")
Iterable<byte[]> byteSegments =
(Iterable<byte[]>) saRXingResult.getRXingResultMetadata().get(RXingResultMetadataType.BYTE_SEGMENTS);
if (byteSegments != null) {
for (byte[] segment : byteSegments) {
newByteSegment.write(segment, 0, segment.length);
}
}
}
RXingResult newRXingResult = new RXingResult(newText.toString(), newRawBytes.toByteArray(), NO_POINTS, BarcodeFormat.QR_CODE);
if (newByteSegment.size() > 0) {
newRXingResult.putMetadata(RXingResultMetadataType.BYTE_SEGMENTS, Collections.singletonList(newByteSegment.toByteArray()));
}
newRXingResults.add(newRXingResult);
return newRXingResults;
}
private static final class SAComparator implements Comparator<RXingResult>, Serializable {
@Override
public int compare(RXingResult a, RXingResult b) {
int aNumber = (int) a.getRXingResultMetadata().get(RXingResultMetadataType.STRUCTURED_APPEND_SEQUENCE);
int bNumber = (int) b.getRXingResultMetadata().get(RXingResultMetadataType.STRUCTURED_APPEND_SEQUENCE);
return Integer.compare(aNumber, bNumber);
}
}
}

View File

@@ -1 +1,3 @@
pub mod detector; pub mod detector;
mod qr_code_multi_reader;
pub use qr_code_multi_reader::*;

View File

@@ -0,0 +1,200 @@
/*
* 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.
*/
use std::{cmp::Ordering, collections::HashMap};
use crate::{
common::DetectorRXingResult,
multi::MultipleBarcodeReader,
qrcode::{
decoder::{self, QRCodeDecoderMetaData},
QRCodeReader,
},
BarcodeFormat, Exceptions, RXingResult, RXingResultMetadataType, RXingResultMetadataValue,
};
use super::detector::MultiDetector;
/**
* This implementation can detect and decode multiple QR Codes in an image.
*
* @author Sean Owen
* @author Hannes Erven
*/
pub struct QRCodeMultiReader(QRCodeReader);
impl MultipleBarcodeReader for QRCodeMultiReader {
fn decodeMultiple(
&self,
image: &crate::BinaryBitmap,
) -> Result<Vec<crate::RXingResult>, crate::Exceptions> {
self.decodeMultipleWithHints(image, &HashMap::new())
}
fn decodeMultipleWithHints(
&self,
image: &crate::BinaryBitmap,
hints: &crate::DecodingHintDictionary,
) -> Result<Vec<crate::RXingResult>, crate::Exceptions> {
let mut results = Vec::new();
let detectorRXingResults =
MultiDetector::new(image.getBlackMatrix().clone()).detectMulti(hints)?;
for detectorRXingResult in detectorRXingResults {
let mut proc = || -> Result<(), Exceptions> {
let decoderRXingResult = decoder::decoder::decode_bitmatrix_with_hints(
detectorRXingResult.getBits(),
hints,
)?;
let mut points = detectorRXingResult.getPoints().clone();
// If the code was mirrored: swap the bottom-left and the top-right points.
let other = decoderRXingResult.getOther();
if other.is::<QRCodeDecoderMetaData>() {
(other
.downcast::<QRCodeDecoderMetaData>()
.expect("must downcast to QRCodeDecoderMetaData"))
.applyMirroredCorrection(&mut points);
}
// if (decoderRXingResult.getOther() instanceof QRCodeDecoderMetaData) {
// ((QRCodeDecoderMetaData) decoderRXingResult.getOther()).applyMirroredCorrection(points);
// }
let mut result = RXingResult::new(
decoderRXingResult.getText(),
decoderRXingResult.getRawBytes().clone(),
points,
BarcodeFormat::QR_CODE,
);
let byteSegments = decoderRXingResult.getByteSegments();
// if (byteSegments != null) {
result.putMetadata(
RXingResultMetadataType::BYTE_SEGMENTS,
RXingResultMetadataValue::ByteSegments(byteSegments.clone()),
);
// }
let ecLevel = decoderRXingResult.getECLevel();
// if (ecLevel != null) {
result.putMetadata(
RXingResultMetadataType::ERROR_CORRECTION_LEVEL,
RXingResultMetadataValue::ErrorCorrectionLevel(ecLevel.to_owned()),
);
// }
if decoderRXingResult.hasStructuredAppend() {
result.putMetadata(
RXingResultMetadataType::STRUCTURED_APPEND_SEQUENCE,
RXingResultMetadataValue::StructuredAppendSequence(
decoderRXingResult.getStructuredAppendSequenceNumber(),
),
);
result.putMetadata(
RXingResultMetadataType::STRUCTURED_APPEND_PARITY,
RXingResultMetadataValue::StructuredAppendParity(
decoderRXingResult.getStructuredAppendParity(),
),
);
}
results.push(result);
Ok(())
};
let output = proc();
if output.is_ok() {
continue;
} else if let Err(Exceptions::ReaderException(_)) = output {
// ignore and continue
continue;
} else {
return Err(output.err().unwrap());
}
}
results = Self::processStructuredAppend(results)?;
Ok(results)
}
}
impl QRCodeMultiReader {
fn processStructuredAppend(results: Vec<RXingResult>) -> Result<Vec<RXingResult>, Exceptions> {
let mut newRXingResults = Vec::new();
let mut saRXingResults = Vec::new();
for result in &results {
if result
.getRXingResultMetadata()
.contains_key(&RXingResultMetadataType::STRUCTURED_APPEND_SEQUENCE)
{
saRXingResults.push(result.clone());
} else {
newRXingResults.push(result.clone());
}
}
if saRXingResults.is_empty() {
return Ok(results);
}
// sort and concatenate the SA list items
saRXingResults.sort_by(compareRXingResult);
let mut newText = String::new();
let mut newRawBytes = Vec::new(); //new ByteArrayOutputStream();
let mut newByteSegment = Vec::new(); //new ByteArrayOutputStream();
for saRXingResult in saRXingResults {
newText.push_str(saRXingResult.getText());
let saBytes = saRXingResult.getRawBytes();
newRawBytes.extend_from_slice(saBytes);
// newRawBytes.write(saBytes, 0, saBytes.len());
if let Some(RXingResultMetadataValue::ByteSegments(byteSegments)) = saRXingResult
.getRXingResultMetadata()
.get(&RXingResultMetadataType::BYTE_SEGMENTS)
{
for segment in byteSegments {
// newByteSegment.write(segment, 0, segment.len());
newByteSegment.extend_from_slice(segment);
}
};
}
let mut newRXingResult =
RXingResult::new(&newText, newRawBytes, Vec::new(), BarcodeFormat::QR_CODE);
if newByteSegment.len() > 0 {
newRXingResult.putMetadata(
RXingResultMetadataType::BYTE_SEGMENTS,
RXingResultMetadataValue::ByteSegments(vec![newByteSegment]),
);
}
newRXingResults.push(newRXingResult);
Ok(newRXingResults)
}
}
fn compareRXingResult(a: &RXingResult, b: &RXingResult) -> Ordering {
let aNumber = if let Some(RXingResultMetadataValue::StructuredAppendSequence(v)) = a
.getRXingResultMetadata()
.get(&RXingResultMetadataType::STRUCTURED_APPEND_SEQUENCE)
{
v
} else {
&-1
};
let bNumber = if let Some(RXingResultMetadataValue::StructuredAppendSequence(v)) = b
.getRXingResultMetadata()
.get(&RXingResultMetadataType::STRUCTURED_APPEND_SEQUENCE)
{
v
} else {
&-1
};
aNumber.cmp(bNumber)
}

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use std::collections::HashMap; use std::{collections::HashMap, rc::Rc};
/** /**
* <p>The main class which implements QR Code decoding -- as opposed to locating and extracting * <p>The main class which implements QR Code decoding -- as opposed to locating and extracting
@@ -115,7 +115,7 @@ pub fn decode_bitmatrix_with_hints(
let mut result = decode_bitmatrix_parser_with_hints(&mut parser, hints)?; let mut result = decode_bitmatrix_parser_with_hints(&mut parser, hints)?;
// Success! Notify the caller that the code was mirrored. // Success! Notify the caller that the code was mirrored.
result.setOther(Box::new(QRCodeDecoderMetaData::new(true))); result.setOther(Rc::new(QRCodeDecoderMetaData::new(true)));
Ok(result) Ok(result)
}; };

View File

@@ -32,6 +32,7 @@ use crate::{BarcodeFormat, RXingResultMetadataType, RXingResultMetadataValue, RX
* *
* @author Sean Owen * @author Sean Owen
*/ */
#[derive(Clone)]
pub struct RXingResult { pub struct RXingResult {
text: String, text: String,
rawBytes: Vec<u8>, rawBytes: Vec<u8>,

View File

@@ -22,7 +22,7 @@
* *
* @author Sean Owen * @author Sean Owen
*/ */
#[derive(Eq, PartialEq, Hash, Debug)] #[derive(Eq, PartialEq, Hash, Debug, Clone)]
pub enum RXingResultMetadataType { pub enum RXingResultMetadataType {
/** /**
* Unspecified, application-specific metadata. Maps to an unspecified {@link Object}. * Unspecified, application-specific metadata. Maps to an unspecified {@link Object}.
@@ -122,7 +122,7 @@ impl From<String> for RXingResultMetadataType {
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum RXingResultMetadataValue { pub enum RXingResultMetadataValue {
/** /**
* Unspecified, application-specific metadata. Maps to an unspecified {@link Object}. * Unspecified, application-specific metadata. Maps to an unspecified {@link Object}.