From 63a10db4137782dcfb1b95df8826c4af473b7aa2 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 1 Dec 2022 11:17:23 -0600 Subject: [PATCH] qr_code_multi_reader ported --- src/common/decoder_rxing_result.rs | 12 +- src/multi/mod.rs | 2 + ...Reader.java => multiple_barcode_reader.rs} | 22 +- src/multi/qrcode/QRCodeMultiReader.java | 149 ------------- src/multi/qrcode/mod.rs | 2 + src/multi/qrcode/qr_code_multi_reader.rs | 200 ++++++++++++++++++ src/qrcode/decoder/decoder.rs | 4 +- src/rxing_result.rs | 1 + src/rxing_result_metadata.rs | 4 +- 9 files changed, 223 insertions(+), 173 deletions(-) rename src/multi/{MultipleBarcodeReader.java => multiple_barcode_reader.rs} (61%) delete mode 100644 src/multi/qrcode/QRCodeMultiReader.java create mode 100644 src/multi/qrcode/qr_code_multi_reader.rs diff --git a/src/common/decoder_rxing_result.rs b/src/common/decoder_rxing_result.rs index a8e2a4e..e688a76 100644 --- a/src/common/decoder_rxing_result.rs +++ b/src/common/decoder_rxing_result.rs @@ -18,7 +18,7 @@ // import java.util.List; -use std::any::Any; +use std::{any::Any, rc::Rc}; /** *

Encapsulates the result of decoding a matrix of bits. This typically @@ -35,7 +35,7 @@ pub struct DecoderRXingResult { ecLevel: String, errorsCorrected: u64, erasures: u64, - other: Box, + other: Rc, structuredAppendParity: i32, structuredAppendSequenceNumber: i32, symbologyModifier: u32, @@ -106,7 +106,7 @@ impl DecoderRXingResult { ecLevel, errorsCorrected: 0, erasures: 0, - other: Box::new(false), + other: Rc::new(false), structuredAppendParity: saParity, structuredAppendSequenceNumber: saSequence, symbologyModifier, @@ -182,11 +182,11 @@ impl DecoderRXingResult { /** * @return arbitrary additional metadata */ - pub fn getOther(&self) -> &Box { - &self.other + pub fn getOther(&self) -> Rc { + self.other.clone() } - pub fn setOther(&mut self, other: Box) { + pub fn setOther(&mut self, other: Rc) { self.other = other } diff --git a/src/multi/mod.rs b/src/multi/mod.rs index b0d645a..a55c715 100644 --- a/src/multi/mod.rs +++ b/src/multi/mod.rs @@ -1 +1,3 @@ +mod multiple_barcode_reader; pub mod qrcode; +pub use multiple_barcode_reader::*; diff --git a/src/multi/MultipleBarcodeReader.java b/src/multi/multiple_barcode_reader.rs similarity index 61% rename from src/multi/MultipleBarcodeReader.java rename to src/multi/multiple_barcode_reader.rs index 65c89a8..441e930 100644 --- a/src/multi/MultipleBarcodeReader.java +++ b/src/multi/multiple_barcode_reader.rs @@ -14,14 +14,7 @@ * limitations under the License. */ -package com.google.zxing.multi; - -import com.google.zxing.BinaryBitmap; -import com.google.zxing.DecodeHintType; -import com.google.zxing.NotFoundException; -import com.google.zxing.RXingResult; - -import java.util.Map; +use crate::{BinaryBitmap, DecodingHintDictionary, Exceptions, RXingResult}; /** * 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 * @author Sean Owen */ -public interface MultipleBarcodeReader { - - RXingResult[] decodeMultiple(BinaryBitmap image) throws NotFoundException; - - RXingResult[] decodeMultiple(BinaryBitmap image, - Map hints) throws NotFoundException; +pub trait MultipleBarcodeReader { + fn decodeMultiple(&self, image: &BinaryBitmap) -> Result, Exceptions>; + fn decodeMultipleWithHints( + &self, + image: &BinaryBitmap, + hints: &DecodingHintDictionary, + ) -> Result, Exceptions>; } diff --git a/src/multi/qrcode/QRCodeMultiReader.java b/src/multi/qrcode/QRCodeMultiReader.java deleted file mode 100644 index cd760fe..0000000 --- a/src/multi/qrcode/QRCodeMultiReader.java +++ /dev/null @@ -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 hints) throws NotFoundException { - List 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 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 processStructuredAppend(List results) { - List newRXingResults = new ArrayList<>(); - List 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 byteSegments = - (Iterable) 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, 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); - } - } - -} diff --git a/src/multi/qrcode/mod.rs b/src/multi/qrcode/mod.rs index 4c98ab1..fba716a 100644 --- a/src/multi/qrcode/mod.rs +++ b/src/multi/qrcode/mod.rs @@ -1 +1,3 @@ pub mod detector; +mod qr_code_multi_reader; +pub use qr_code_multi_reader::*; diff --git a/src/multi/qrcode/qr_code_multi_reader.rs b/src/multi/qrcode/qr_code_multi_reader.rs new file mode 100644 index 0000000..9230baf --- /dev/null +++ b/src/multi/qrcode/qr_code_multi_reader.rs @@ -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, crate::Exceptions> { + self.decodeMultipleWithHints(image, &HashMap::new()) + } + + fn decodeMultipleWithHints( + &self, + image: &crate::BinaryBitmap, + hints: &crate::DecodingHintDictionary, + ) -> Result, 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::() { + (other + .downcast::() + .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) -> Result, 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) +} diff --git a/src/qrcode/decoder/decoder.rs b/src/qrcode/decoder/decoder.rs index 61eec89..2e4691f 100644 --- a/src/qrcode/decoder/decoder.rs +++ b/src/qrcode/decoder/decoder.rs @@ -14,7 +14,7 @@ * limitations under the License. */ -use std::collections::HashMap; +use std::{collections::HashMap, rc::Rc}; /** *

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)?; // 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) }; diff --git a/src/rxing_result.rs b/src/rxing_result.rs index 16e6e61..f611397 100644 --- a/src/rxing_result.rs +++ b/src/rxing_result.rs @@ -32,6 +32,7 @@ use crate::{BarcodeFormat, RXingResultMetadataType, RXingResultMetadataValue, RX * * @author Sean Owen */ +#[derive(Clone)] pub struct RXingResult { text: String, rawBytes: Vec, diff --git a/src/rxing_result_metadata.rs b/src/rxing_result_metadata.rs index 6d7dc7b..5c36a3f 100644 --- a/src/rxing_result_metadata.rs +++ b/src/rxing_result_metadata.rs @@ -22,7 +22,7 @@ * * @author Sean Owen */ -#[derive(Eq, PartialEq, Hash, Debug)] +#[derive(Eq, PartialEq, Hash, Debug, Clone)] pub enum RXingResultMetadataType { /** * Unspecified, application-specific metadata. Maps to an unspecified {@link Object}. @@ -122,7 +122,7 @@ impl From for RXingResultMetadataType { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum RXingResultMetadataValue { /** * Unspecified, application-specific metadata. Maps to an unspecified {@link Object}.