diff --git a/src/qrcode/decoder/BitMatrixParser.java b/src/qrcode/decoder/BitMatrixParser.java deleted file mode 100644 index 2298695..0000000 --- a/src/qrcode/decoder/BitMatrixParser.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2007 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.qrcode.decoder; - -import com.google.zxing.FormatException; -import com.google.zxing.common.BitMatrix; - -/** - * @author Sean Owen - */ -final class BitMatrixParser { - - private final BitMatrix bitMatrix; - private Version parsedVersion; - private FormatInformation parsedFormatInfo; - private boolean mirror; - - /** - * @param bitMatrix {@link BitMatrix} to parse - * @throws FormatException if dimension is not >= 21 and 1 mod 4 - */ - BitMatrixParser(BitMatrix bitMatrix) throws FormatException { - int dimension = bitMatrix.getHeight(); - if (dimension < 21 || (dimension & 0x03) != 1) { - throw FormatException.getFormatInstance(); - } - this.bitMatrix = bitMatrix; - } - - /** - *

Reads format information from one of its two locations within the QR Code.

- * - * @return {@link FormatInformation} encapsulating the QR Code's format info - * @throws FormatException if both format information locations cannot be parsed as - * the valid encoding of format information - */ - FormatInformation readFormatInformation() throws FormatException { - - if (parsedFormatInfo != null) { - return parsedFormatInfo; - } - - // Read top-left format info bits - int formatInfoBits1 = 0; - for (int i = 0; i < 6; i++) { - formatInfoBits1 = copyBit(i, 8, formatInfoBits1); - } - // .. and skip a bit in the timing pattern ... - formatInfoBits1 = copyBit(7, 8, formatInfoBits1); - formatInfoBits1 = copyBit(8, 8, formatInfoBits1); - formatInfoBits1 = copyBit(8, 7, formatInfoBits1); - // .. and skip a bit in the timing pattern ... - for (int j = 5; j >= 0; j--) { - formatInfoBits1 = copyBit(8, j, formatInfoBits1); - } - - // Read the top-right/bottom-left pattern too - int dimension = bitMatrix.getHeight(); - int formatInfoBits2 = 0; - int jMin = dimension - 7; - for (int j = dimension - 1; j >= jMin; j--) { - formatInfoBits2 = copyBit(8, j, formatInfoBits2); - } - for (int i = dimension - 8; i < dimension; i++) { - formatInfoBits2 = copyBit(i, 8, formatInfoBits2); - } - - parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits1, formatInfoBits2); - if (parsedFormatInfo != null) { - return parsedFormatInfo; - } - throw FormatException.getFormatInstance(); - } - - /** - *

Reads version information from one of its two locations within the QR Code.

- * - * @return {@link Version} encapsulating the QR Code's version - * @throws FormatException if both version information locations cannot be parsed as - * the valid encoding of version information - */ - Version readVersion() throws FormatException { - - if (parsedVersion != null) { - return parsedVersion; - } - - int dimension = bitMatrix.getHeight(); - - int provisionalVersion = (dimension - 17) / 4; - if (provisionalVersion <= 6) { - return Version.getVersionForNumber(provisionalVersion); - } - - // Read top-right version info: 3 wide by 6 tall - int versionBits = 0; - int ijMin = dimension - 11; - for (int j = 5; j >= 0; j--) { - for (int i = dimension - 9; i >= ijMin; i--) { - versionBits = copyBit(i, j, versionBits); - } - } - - Version theParsedVersion = Version.decodeVersionInformation(versionBits); - if (theParsedVersion != null && theParsedVersion.getDimensionForVersion() == dimension) { - parsedVersion = theParsedVersion; - return theParsedVersion; - } - - // Hmm, failed. Try bottom left: 6 wide by 3 tall - versionBits = 0; - for (int i = 5; i >= 0; i--) { - for (int j = dimension - 9; j >= ijMin; j--) { - versionBits = copyBit(i, j, versionBits); - } - } - - theParsedVersion = Version.decodeVersionInformation(versionBits); - if (theParsedVersion != null && theParsedVersion.getDimensionForVersion() == dimension) { - parsedVersion = theParsedVersion; - return theParsedVersion; - } - throw FormatException.getFormatInstance(); - } - - private int copyBit(int i, int j, int versionBits) { - boolean bit = mirror ? bitMatrix.get(j, i) : bitMatrix.get(i, j); - return bit ? (versionBits << 1) | 0x1 : versionBits << 1; - } - - /** - *

Reads the bits in the {@link BitMatrix} representing the finder pattern in the - * correct order in order to reconstruct the codewords bytes contained within the - * QR Code.

- * - * @return bytes encoded within the QR Code - * @throws FormatException if the exact number of bytes expected is not read - */ - byte[] readCodewords() throws FormatException { - - FormatInformation formatInfo = readFormatInformation(); - Version version = readVersion(); - - // Get the data mask for the format used in this QR Code. This will exclude - // some bits from reading as we wind through the bit matrix. - DataMask dataMask = DataMask.values()[formatInfo.getDataMask()]; - int dimension = bitMatrix.getHeight(); - dataMask.unmaskBitMatrix(bitMatrix, dimension); - - BitMatrix functionPattern = version.buildFunctionPattern(); - - boolean readingUp = true; - byte[] result = new byte[version.getTotalCodewords()]; - int resultOffset = 0; - int currentByte = 0; - int bitsRead = 0; - // Read columns in pairs, from right to left - for (int j = dimension - 1; j > 0; j -= 2) { - if (j == 6) { - // Skip whole column with vertical alignment pattern; - // saves time and makes the other code proceed more cleanly - j--; - } - // Read alternatingly from bottom to top then top to bottom - for (int count = 0; count < dimension; count++) { - int i = readingUp ? dimension - 1 - count : count; - for (int col = 0; col < 2; col++) { - // Ignore bits covered by the function pattern - if (!functionPattern.get(j - col, i)) { - // Read a bit - bitsRead++; - currentByte <<= 1; - if (bitMatrix.get(j - col, i)) { - currentByte |= 1; - } - // If we've made a whole byte, save it off - if (bitsRead == 8) { - result[resultOffset++] = (byte) currentByte; - bitsRead = 0; - currentByte = 0; - } - } - } - } - readingUp ^= true; // readingUp = !readingUp; // switch directions - } - if (resultOffset != version.getTotalCodewords()) { - throw FormatException.getFormatInstance(); - } - return result; - } - - /** - * Revert the mask removal done while reading the code words. The bit matrix should revert to its original state. - */ - void remask() { - if (parsedFormatInfo == null) { - return; // We have no format information, and have no data mask - } - DataMask dataMask = DataMask.values()[parsedFormatInfo.getDataMask()]; - int dimension = bitMatrix.getHeight(); - dataMask.unmaskBitMatrix(bitMatrix, dimension); - } - - /** - * Prepare the parser for a mirrored operation. - * This flag has effect only on the {@link #readFormatInformation()} and the - * {@link #readVersion()}. Before proceeding with {@link #readCodewords()} the - * {@link #mirror()} method should be called. - * - * @param mirror Whether to read version and format information mirrored. - */ - void setMirror(boolean mirror) { - parsedVersion = null; - parsedFormatInfo = null; - this.mirror = mirror; - } - - /** Mirror the bit matrix in order to attempt a second reading. */ - void mirror() { - for (int x = 0; x < bitMatrix.getWidth(); x++) { - for (int y = x + 1; y < bitMatrix.getHeight(); y++) { - if (bitMatrix.get(x, y) != bitMatrix.get(y, x)) { - bitMatrix.flip(y, x); - bitMatrix.flip(x, y); - } - } - } - } - -} diff --git a/src/qrcode/decoder/DataMaskTestCase.rs b/src/qrcode/decoder/DataMaskTestCase.rs index b18e2df..3e5eaf1 100644 --- a/src/qrcode/decoder/DataMaskTestCase.rs +++ b/src/qrcode/decoder/DataMaskTestCase.rs @@ -27,26 +27,34 @@ type MaskCondition = fn(u32, u32) -> bool; #[test] fn testMask0() { testMaskAcrossDimensions(DataMask::DATA_MASK_000, |i, j| ((i + j) % 2 == 0)); + testMaskAcrossDimensionsU8(0, |i, j| ((i + j) % 2 == 0)); + } #[test] fn testMask1() { testMaskAcrossDimensions(DataMask::DATA_MASK_001, |i, j| i % 2 == 0); + testMaskAcrossDimensionsU8(1, |i, j| i % 2 == 0); + } #[test] fn testMask2() { testMaskAcrossDimensions(DataMask::DATA_MASK_010, |i, j| j % 3 == 0); + testMaskAcrossDimensionsU8(2, |i, j| j % 3 == 0); + } #[test] fn testMask3() { testMaskAcrossDimensions(DataMask::DATA_MASK_011, |i, j| (i + j) % 3 == 0); + testMaskAcrossDimensionsU8(3, |i, j| (i + j) % 3 == 0); } #[test] fn testMask4() { testMaskAcrossDimensions(DataMask::DATA_MASK_100, |i, j| (i / 2 + j / 3) % 2 == 0); + testMaskAcrossDimensionsU8(4, |i, j| (i / 2 + j / 3) % 2 == 0); } #[test] @@ -54,6 +62,9 @@ fn testMask5() { testMaskAcrossDimensions(DataMask::DATA_MASK_101, |i, j| { (i * j) % 2 + (i * j) % 3 == 0 }); + testMaskAcrossDimensionsU8(5, |i, j| { + (i * j) % 2 + (i * j) % 3 == 0 + }); } #[test] @@ -61,6 +72,9 @@ fn testMask6() { testMaskAcrossDimensions(DataMask::DATA_MASK_110, |i, j| { ((i * j) % 2 + (i * j) % 3) % 2 == 0 }); + testMaskAcrossDimensionsU8(6, |i, j| { + ((i * j) % 2 + (i * j) % 3) % 2 == 0 + }); } #[test] @@ -68,6 +82,13 @@ fn testMask7() { testMaskAcrossDimensions(DataMask::DATA_MASK_111, |i, j| { ((i + j) % 2 + (i * j) % 3) % 2 == 0 }); + testMaskAcrossDimensionsU8(7, |i, j| { + ((i + j) % 2 + (i * j) % 3) % 2 == 0 + }); +} + +fn testMaskAcrossDimensionsU8(mask: u8, condition: MaskCondition) { + testMaskAcrossDimensions(mask.try_into().unwrap(), condition) } fn testMaskAcrossDimensions(mask: DataMask, condition: MaskCondition) { diff --git a/src/qrcode/decoder/bit_matrix_parser.rs b/src/qrcode/decoder/bit_matrix_parser.rs new file mode 100644 index 0000000..15bdcb6 --- /dev/null +++ b/src/qrcode/decoder/bit_matrix_parser.rs @@ -0,0 +1,272 @@ +/* + * Copyright 2007 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 crate::{Exceptions, common::BitMatrix}; + +use super::{Version, FormatInformation, VersionRef, DataMask}; + + +/** + * @author Sean Owen + */ +pub struct BitMatrixParser { + + bitMatrix:BitMatrix, + parsedVersion:Option, + parsedFormatInfo:Option, + mirror:bool, +} + +impl BitMatrixParser { + + /** + * @param bitMatrix {@link BitMatrix} to parse + * @throws FormatException if dimension is not >= 21 and 1 mod 4 + */ + pub fn new( bit_matrix:BitMatrix) -> Result { + let dimension = bit_matrix.getHeight(); + if dimension < 21 || (dimension & 0x03) != 1 { + Err(Exceptions::FormatException(format!("{} < 21 || ({} % 0x03) != 1", dimension,dimension))) + }else { + Ok(Self{ + bitMatrix: bit_matrix, + parsedVersion: None, + parsedFormatInfo: None, + mirror: false + }) + } + } + + /** + *

Reads format information from one of its two locations within the QR Code.

+ * + * @return {@link FormatInformation} encapsulating the QR Code's format info + * @throws FormatException if both format information locations cannot be parsed as + * the valid encoding of format information + */ + pub fn readFormatInformation(&self) -> Result<&FormatInformation,Exceptions>{ + + if let Some(pfi) = self.parsedFormatInfo { + return Ok(&pfi) + } + + // Read top-left format info bits + let formatInfoBits1 = 0; + for i in 0..6 { + // for (int i = 0; i < 6; i++) { + formatInfoBits1 = self.copyBit(i, 8, formatInfoBits1); + } + // .. and skip a bit in the timing pattern ... + formatInfoBits1 = self.copyBit(7, 8, formatInfoBits1); + formatInfoBits1 = self.copyBit(8, 8, formatInfoBits1); + formatInfoBits1 = self.copyBit(8, 7, formatInfoBits1); + // .. and skip a bit in the timing pattern ... + for j in (0..=5).rev() { + // for (int j = 5; j >= 0; j--) { + formatInfoBits1 = self.copyBit(8, j, formatInfoBits1); + } + + // Read the top-right/bottom-left pattern too + let dimension = self.bitMatrix.getHeight(); + let formatInfoBits2 = 0; + let jMin = dimension - 7; + for j in (jMin..=dimension).rev() { + // for (int j = dimension - 1; j >= jMin; j--) { + formatInfoBits2 = self.copyBit(8, j, formatInfoBits2); + } + for i in (dimension -8)..dimension { + // for (int i = dimension - 8; i < dimension; i++) { + formatInfoBits2 = self.copyBit(i, 8, formatInfoBits2); + } + + self.parsedFormatInfo = FormatInformation::decodeFormatInformation(formatInfoBits1, formatInfoBits2); + if let Some(pfi) = self.parsedFormatInfo { + return Ok(&pfi) + } + Err(Exceptions::FormatException("".to_owned())) + } + + /** + *

Reads version information from one of its two locations within the QR Code.

+ * + * @return {@link Version} encapsulating the QR Code's version + * @throws FormatException if both version information locations cannot be parsed as + * the valid encoding of version information + */ + pub fn readVersion(&self) -> Result { + + if let Some(pv) = self.parsedVersion { + return Ok(&pv) + } + + let dimension = self.bitMatrix.getHeight(); + + let provisionalVersion = (dimension - 17) / 4; + if provisionalVersion <= 6 { + return Version::getVersionForNumber(provisionalVersion) + } + + // Read top-right version info: 3 wide by 6 tall + let versionBits = 0; + let ijMin = dimension - 11; + for j in (0..=5).rev() { + // for (int j = 5; j >= 0; j--) { + for i in (ijMin..(dimension-8)).rev(){ + // for (int i = dimension - 9; i >= ijMin; i--) { + versionBits = self.copyBit(i, j, versionBits); + } + } + + if let Ok(theParsedVersion) = Version::decodeVersionInformation(versionBits){ + if theParsedVersion.getDimensionForVersion() == dimension { + self.parsedVersion = Some(theParsedVersion); + return Ok(theParsedVersion) + }} + + // Hmm, failed. Try bottom left: 6 wide by 3 tall + versionBits = 0; + for i in (0..=5 ).rev() { + // for (int i = 5; i >= 0; i--) { + for j in (ijMin..(dimension-5)).rev() { + // for (int j = dimension - 9; j >= ijMin; j--) { + versionBits = self.copyBit(i, j, versionBits); + } + } + + if let Ok(theParsedVersion) = Version::decodeVersionInformation(versionBits){ + if theParsedVersion.getDimensionForVersion() == dimension { + self.parsedVersion = Some(theParsedVersion); + return Ok(theParsedVersion) + }} + Err(Exceptions::FormatException("".to_owned())) + } + + fn copyBit(&self, i:u32, j:u32, versionBits:u32) -> u32{ + let bit = if self.mirror {self.bitMatrix.get(j, i)} else {self.bitMatrix.get(i, j)}; + if bit {(versionBits << 1) | 0x1} else {versionBits << 1} + } + + /** + *

Reads the bits in the {@link BitMatrix} representing the finder pattern in the + * correct order in order to reconstruct the codewords bytes contained within the + * QR Code.

+ * + * @return bytes encoded within the QR Code + * @throws FormatException if the exact number of bytes expected is not read + */ + pub fn readCodewords(&self) -> Result,Exceptions> { + + let formatInfo = self.readFormatInformation()?; + let version = self.readVersion()?; + + // Get the data mask for the format used in this QR Code. This will exclude + // some bits from reading as we wind through the bit matrix. + let dataMask :DataMask= formatInfo.getDataMask().try_into()?;//DataMask.values()[formatInfo.getDataMask()]; + let dimension = self.bitMatrix.getHeight(); + dataMask.unmaskBitMatrix(self.bitMatrix, dimension); + + let functionPattern = version.buildFunctionPattern(); + + let readingUp = true; + let result = vec![0u8;version.getTotalCodewords() as usize]; + let resultOffset = 0; + let currentByte = 0; + let bitsRead = 0; + // Read columns in pairs, from right to left + let mut j = dimension - 1; + while j > 0 { + // for (int j = dimension - 1; j > 0; j -= 2) { + if j == 6 { + // Skip whole column with vertical alignment pattern; + // saves time and makes the other code proceed more cleanly + j-=1; + } + // Read alternatingly from bottom to top then top to bottom + for count in 0..dimension { + // for (int count = 0; count < dimension; count++) { + let i = if readingUp {dimension - 1 - count} else {count}; + for col in 0..2 { + // for (int col = 0; col < 2; col++) { + // Ignore bits covered by the function pattern + if (!functionPattern.get(j - col, i)) { + // Read a bit + bitsRead+=1; + currentByte <<= 1; + if self.bitMatrix.get(j - col, i) { + currentByte |= 1; + } + // If we've made a whole byte, save it off + if (bitsRead == 8) { + result[resultOffset] = currentByte; + resultOffset+=1; + bitsRead = 0; + currentByte = 0; + } + } + } + } + readingUp ^= true; // readingUp = !readingUp; // switch directions + + j -= 2; + } + + if (resultOffset != version.getTotalCodewords()) { + throw FormatException.getFormatInstance(); + } + return result; + } + + /** + * Revert the mask removal done while reading the code words. The bit matrix should revert to its original state. + */ + pub fn remask(&self) { + if self.parsedFormatInfo.is_none() { + return; // We have no format information, and have no data mask + } + let dataMask = DataMask.values()[self.parsedFormatInfo.getDataMask()]; + let dimension = self.bitMatrix.getHeight(); + dataMask.unmaskBitMatrix(self.bitMatrix, dimension); + } + + /** + * Prepare the parser for a mirrored operation. + * This flag has effect only on the {@link #readFormatInformation()} and the + * {@link #readVersion()}. Before proceeding with {@link #readCodewords()} the + * {@link #mirror()} method should be called. + * + * @param mirror Whether to read version and format information mirrored. + */ + pub fn setMirror(&self, mirror:bool) { + self.parsedVersion = None; + self.parsedFormatInfo = None; + self.mirror = mirror; + } + + /** Mirror the bit matrix in order to attempt a second reading. */ + pub fn mirror(&self) { + for x in 0..self.bitMatrix.getWidth() { + // for (int x = 0; x < bitMatrix.getWidth(); x++) { + for y in (x+1)..self.bitMatrix.getHeight() { + // for (int y = x + 1; y < bitMatrix.getHeight(); y++) { + if (self.bitMatrix.get(x, y) != self.bitMatrix.get(y, x)) { + self.bitMatrix.flip_coords(y, x); + self.bitMatrix.flip_coords(x, y); + } + } + } + } + +} diff --git a/src/qrcode/decoder/data_mask.rs b/src/qrcode/decoder/data_mask.rs index 70a6c96..6e88498 100755 --- a/src/qrcode/decoder/data_mask.rs +++ b/src/qrcode/decoder/data_mask.rs @@ -14,7 +14,7 @@ * limitations under the License. */ -use crate::common::BitMatrix; +use crate::{common::BitMatrix, Exceptions}; /** *

Encapsulates data masks for the data bits in a QR code, per ISO 18004:2006 6.8. Implementations @@ -216,3 +216,21 @@ impl DataMask { abstract boolean isMasked(int i, int j); */ + +impl TryFrom for DataMask { + type Error = Exceptions; + + fn try_from(value: u8) -> Result { + match value { + 0=>Ok(DataMask::DATA_MASK_000), + 1=>Ok(DataMask::DATA_MASK_001), + 2=>Ok(DataMask::DATA_MASK_010), + 3=>Ok(DataMask::DATA_MASK_011), + 4=>Ok(DataMask::DATA_MASK_100), + 5=>Ok(DataMask::DATA_MASK_101), + 6=>Ok(DataMask::DATA_MASK_110), + 7=>Ok(DataMask::DATA_MASK_111), + _=>Err(Exceptions::IllegalArgumentException(format!("{} is not between 0 and 7",value))), + } + } +} \ No newline at end of file diff --git a/src/qrcode/decoder/mod.rs b/src/qrcode/decoder/mod.rs index 2f7f35b..172a2c1 100644 --- a/src/qrcode/decoder/mod.rs +++ b/src/qrcode/decoder/mod.rs @@ -3,8 +3,9 @@ mod mode; mod error_correction_level; mod format_information; mod data_block; -mod qr_code_decoder_metaData; +mod qr_code_decoder_meta_data; mod data_mask; +mod bit_matrix_parser; #[cfg(test)] mod ErrorCorrectionLevelTestCase; @@ -22,5 +23,6 @@ pub use mode::*; pub use error_correction_level::*; pub use format_information::*; pub use data_block::*; -pub use qr_code_decoder_metaData::*; -pub use data_mask::*; \ No newline at end of file +pub use qr_code_decoder_meta_data::*; +pub use data_mask::*; +pub use bit_matrix_parser::*; \ No newline at end of file diff --git a/src/qrcode/decoder/qr_code_decoder_metaData.rs b/src/qrcode/decoder/qr_code_decoder_meta_data.rs similarity index 100% rename from src/qrcode/decoder/qr_code_decoder_metaData.rs rename to src/qrcode/decoder/qr_code_decoder_meta_data.rs