rqrr-0.9.3/.cargo_vcs_info.json0000644000000001360000000000100120330ustar { "git": { "sha1": "e91047ba2fc927ccc8195d27576aacb2bdb23fb6" }, "path_in_vcs": "" }rqrr-0.9.3/Cargo.toml0000644000000027010000000000100100310ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.64.0" name = "rqrr" version = "0.9.3" authors = ["WanzenBug "] build = false include = [ "/README.md", "/LICENSE-*", "/src/**/*.rs", "/src/**/*.png", ] autobins = false autoexamples = false autotests = false autobenches = false description = """ Detect and read QR codes from any image source """ documentation = "https://docs.rs/rqrr/" readme = "README.md" keywords = [ "qrcode", "detection", ] categories = [ "graphics", "encoding", ] license = "(MIT OR Apache-2.0) AND ISC" repository = "https://github.com/WanzenBug/rqrr" [lib] name = "rqrr" path = "src/lib.rs" [dependencies.g2p] version = "1.0" [dependencies.image] version = ">= 0.24, <= 0.25" optional = true default-features = false [dependencies.lru] version = "0.12" [dev-dependencies.image] version = ">= 0.24, <= 0.25" features = [ "png", "jpeg", "gif", ] default-features = false [features] default = ["img"] img = ["image"] rqrr-0.9.3/Cargo.toml.orig000064400000000000000000000016701046102023000135160ustar 00000000000000[package] name = "rqrr" edition = "2021" rust-version = "1.64.0" version = "0.9.3" authors = ["WanzenBug "] license = "(MIT OR Apache-2.0) AND ISC" documentation = "https://docs.rs/rqrr/" repository = "https://github.com/WanzenBug/rqrr" readme = "./README.md" keywords = ["qrcode", "detection"] description = """ Detect and read QR codes from any image source """ categories = [ "graphics", "encoding", ] include = [ "/README.md", "/LICENSE-*", "/src/**/*.rs", "/src/**/*.png", ] [features] img = ["image"] default = ["img"] [[bench]] name = "bench_cap_find" path = "benches/bench_cap_find.rs" required-features = ["img"] harness = false [dev-dependencies] #criterion = "0.5" image = { version = ">= 0.24, <= 0.25", default-features = false, features = ["png", "jpeg", "gif"] } [dependencies] g2p = "1.0" lru = "0.12" image = { version = ">= 0.24, <= 0.25", optional = true, default-features = false } rqrr-0.9.3/LICENSE-APACHE000064400000000000000000000251611046102023000125540ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright (c) 2019 Moritz Wanzenböck 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. rqrr-0.9.3/LICENSE-ISC000064400000000000000000000014571046102023000122530ustar 00000000000000quirc -- QR-code recognition library Copyright (C) 2010-2012 Daniel Beer ISC License =========== Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. rqrr-0.9.3/LICENSE-MIT000064400000000000000000000020711046102023000122570ustar 00000000000000Copyright 2019 Moritz Wanzenböck Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rqrr-0.9.3/README.md000064400000000000000000000020571046102023000121060ustar 00000000000000# rust-qr-reader - Find and read QR-Codes [![documentation](https://docs.rs/rqrr/badge.svg)](https://docs.rs/rqrr/) [![Build Status](https://github.com/WanzenBug/rqrr/actions/workflows/CI.yaml/badge.svg?branch=master)](https://github.com/WanzenBug/rqrr/actions/workflows/CI.yaml) This crates exports functions and types that can be used to search for QR-Codes in images and decode them. ## Usage The most basic usage is shown below: ```rust use image; use rqrr; let img = image::open("tests/data/github.gif")?.to_luma(); // Prepare for detection let mut img = rqrr::PreparedImage::prepare(img); // Search for grids, without decoding let grids = img.detect_grids(); assert_eq!(grids.len(), 1); // Decode the grid let (meta, content) = grids[0].decode()?; assert_eq!(meta.ecc_level, 0); assert_eq!(content, "https://github.com/WanzenBug/rqrr"); ``` For more information visit [docs.rs](https://docs.rs/rqrr/) ## License Either [APACHE](LICENSE-APACHE) or [MIT](LICENSE-MIT) ## Attribution This library was made on the base of [quirc](https://github.com/dlbeer/quirc) rqrr-0.9.3/src/decode.rs000064400000000000000000000555071046102023000132170ustar 00000000000000use std::io::Write; use std::mem; use g2p::{g2p, GaloisField}; use crate::version_db::{RSParameters, VERSION_DATA_BASE}; use crate::{BitGrid, DeQRError, DeQRResult}; g2p!(GF16, 4, modulus: 0b1_0011); g2p!(GF256, 8, modulus: 0b1_0001_1101); pub const MAX_PAYLOAD_SIZE: usize = 8896; /// Version of a QR Code which determines its size #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct Version(pub usize); impl Version { /// Given the grid size, determine the likely grid size pub fn from_size(b: usize) -> DeQRResult { let computed_version = b.saturating_sub(17) / 4; if computed_version > 0 && computed_version <= 40 { Ok(Version(computed_version)) } else { Err(DeQRError::InvalidVersion) } } /// Return the size of a grid of the given version pub fn to_size(&self) -> usize { self.0 * 4 + 17 } } /// MetaData for a QR grid /// /// Stores information about the size/version of given grid. Also contains /// information about the error correction level and bit mask used. #[derive(Debug, Clone, Copy)] pub struct MetaData { /// The version/size of the grid pub version: Version, /// the error correction leven, between 0 and 3 pub ecc_level: u16, /// The mask that was used, value between 0 and 7 pub mask: u16, } /// The bit stream contained in the QR Code #[derive(Clone)] pub struct RawData { /// The bits of the QR Code pub data: [u8; MAX_PAYLOAD_SIZE], /// Length of the bit stream in bits. pub len: usize, } impl RawData { /// Push a new bit into the bit stream pub fn push(&mut self, bit: bool) { assert!((self.len >> 8) < MAX_PAYLOAD_SIZE); let bitpos = (self.len & 7) as u8; let bytepos = self.len >> 3; if bit { self.data[bytepos] |= 0x80_u8 >> bitpos; } self.len += 1; } } #[derive(Clone)] pub struct CorrectedDataStream { data: [u8; MAX_PAYLOAD_SIZE], ptr: usize, bit_len: usize, } impl CorrectedDataStream { pub fn bits_remaining(&self) -> usize { assert!(self.bit_len >= self.ptr); self.bit_len - self.ptr } pub fn take_bits(&mut self, nbits: usize) -> usize { let mut ret = 0; let max_len = ::std::cmp::min(self.bits_remaining(), nbits); assert!(max_len <= mem::size_of::() * 8); for _ in 0..max_len { let b = self.data[self.ptr >> 3]; let bitpos = self.ptr & 7; ret <<= 1; if 0 != (b << bitpos) & 0x80 { ret |= 1 } self.ptr += 1; } ret } } /// Given a grid try to decode and write it to the output writer /// /// This tries to read the bit patterns from a [Grid](trait.Grid.html), correct /// errors and/or missing bits and write the result to the output. If successful /// also returns [MetaData](struct.MetaData.html) of the read grid. pub fn decode(code: &dyn BitGrid, writer: W) -> DeQRResult where W: Write, { let meta = read_format(code)?; let raw = read_data(code, &meta, true); let stream = codestream_ecc(&meta, raw)?; decode_payload(&meta, stream, writer)?; Ok(meta) } /// Return extracted metadata and the raw, uncorrected bit stream pub fn get_raw(code: &dyn BitGrid) -> DeQRResult<(MetaData, RawData)> { let meta = read_format(code)?; let raw = read_data(code, &meta, false); Ok((meta, raw)) } fn decode_payload(meta: &MetaData, mut ds: CorrectedDataStream, mut writer: W) -> DeQRResult<()> where W: Write, { while ds.bits_remaining() >= 4 { let ty = ds.take_bits(4); match ty { 0 => break, 1 => decode_numeric(meta, &mut ds, &mut writer), 2 => decode_alpha(meta, &mut ds, &mut writer), 4 => decode_byte(meta, &mut ds, &mut writer), 8 => decode_kanji(meta, &mut ds, &mut writer), 7 => decode_eci(meta, &mut ds, &mut writer), _ => Err(DeQRError::UnknownDataType)?, }?; } Ok(()) } fn decode_eci(_meta: &MetaData, ds: &mut CorrectedDataStream, mut _writer: W) -> DeQRResult<()> where W: Write, { if ds.bits_remaining() < 8 { Err(DeQRError::DataUnderflow)? } let mut _eci = ds.take_bits(8) as u32; if _eci & 0xc0 == 0x80 { if ds.bits_remaining() < 8 { Err(DeQRError::DataUnderflow)? } _eci = (_eci << 8) | (ds.take_bits(8) as u32) } else if _eci & 0xe0 == 0xc0 { if ds.bits_remaining() < 16 { Err(DeQRError::DataUnderflow)? } _eci = (_eci << 16) | (ds.take_bits(16) as u32) } Ok(()) } fn decode_kanji(meta: &MetaData, ds: &mut CorrectedDataStream, mut writer: W) -> DeQRResult<()> where W: Write, { let nbits = match meta.version { Version(0..=9) => 8, Version(10..=26) => 10, _ => 12, }; let count = ds.take_bits(nbits); if ds.bits_remaining() < count * 13 { Err(DeQRError::DataUnderflow)? } for _ in 0..count { let d = ds.take_bits(13); let ms_b = d / 0xc0; let ls_b = d % 0xc0; let intermediate = (ms_b << 8) | ls_b; let sjw = if intermediate + 0x8140 <= 0x9ffc { /* bytes are in the range 0x8140 to 0x9FFC */ (intermediate + 0x8140) as u16 } else { (intermediate + 0xc140) as u16 }; writer .write_all(&[(sjw >> 8) as u8, (sjw & 0xff) as u8]) .map_err(|_| DeQRError::IoError)?; } Ok(()) } fn decode_byte(meta: &MetaData, ds: &mut CorrectedDataStream, mut writer: W) -> DeQRResult<()> where W: Write, { let nbits = match meta.version { Version(0..=9) => 8, _ => 16, }; let count = ds.take_bits(nbits); if ds.bits_remaining() < count * 8 { Err(DeQRError::DataUnderflow)?; } for _ in 0..count { let buf = &[ds.take_bits(8) as u8]; writer.write_all(buf).map_err(|_| DeQRError::IoError)?; } Ok(()) } fn decode_alpha(meta: &MetaData, ds: &mut CorrectedDataStream, mut writer: W) -> DeQRResult<()> where W: Write, { let nbits = match meta.version { Version(0..=9) => 9, Version(10..=26) => 11, _ => 13, }; let mut count = ds.take_bits(nbits); let mut buf = [0; 2]; while count >= 2 { alpha_tuple(&mut buf, ds, 11, 2)?; writer.write_all(&buf[..]).map_err(|_| DeQRError::IoError)?; count -= 2; } if count == 1 { alpha_tuple(&mut buf, ds, 6, 1)?; writer .write_all(&buf[..1]) .map_err(|_| DeQRError::IoError)?; } Ok(()) } fn alpha_tuple( buf: &mut [u8; 2], ds: &mut CorrectedDataStream, nbits: usize, digits: usize, ) -> DeQRResult<()> { if ds.bits_remaining() < nbits { Err(DeQRError::DataUnderflow) } else { let mut tuple = ds.take_bits(nbits); for i in (0..digits).rev() { const ALPHA_MAP: &[u8; 46] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:\x00"; buf[i] = ALPHA_MAP[tuple % 45]; tuple /= 45; } Ok(()) } } fn decode_numeric(meta: &MetaData, ds: &mut CorrectedDataStream, mut writer: W) -> DeQRResult<()> where W: Write, { let nbits = match meta.version { Version(0..=9) => 10, Version(10..=26) => 12, _ => 14, }; let mut count = ds.take_bits(nbits); let mut buf = [0; 3]; while count >= 3 { numeric_tuple(&mut buf, ds, 10, 3)?; writer.write_all(&buf[..]).map_err(|_| DeQRError::IoError)?; count -= 3; } if count == 2 { numeric_tuple(&mut buf, ds, 7, 2)?; writer .write_all(&buf[..2]) .map_err(|_| DeQRError::IoError)?; count -= 2; } if count == 1 { numeric_tuple(&mut buf, ds, 4, 1)?; writer .write_all(&buf[..1]) .map_err(|_| DeQRError::IoError)?; } Ok(()) } fn numeric_tuple( buf: &mut [u8; 3], ds: &mut CorrectedDataStream, nbits: usize, digits: usize, ) -> DeQRResult<()> { if ds.bits_remaining() < nbits { Err(DeQRError::DataUnderflow) } else { let mut tuple = ds.take_bits(nbits); for i in (0..digits).rev() { buf[i] = (tuple % 10) as u8 + b'0'; tuple /= 10; } Ok(()) } } fn codestream_ecc(meta: &MetaData, ds: RawData) -> DeQRResult { let mut out = CorrectedDataStream { data: [0; MAX_PAYLOAD_SIZE], ptr: 0, bit_len: 0, }; let ver = &VERSION_DATA_BASE[meta.version.0]; let sb_ecc = &ver.ecc[meta.ecc_level as usize]; let lb_ecc = RSParameters { bs: sb_ecc.bs + 1, dw: sb_ecc.dw + 1, ns: sb_ecc.ns, }; let lb_count = (ver.data_bytes - sb_ecc.bs * sb_ecc.ns) / (sb_ecc.bs + 1); let bc = lb_count + sb_ecc.ns; let ecc_offset = sb_ecc.dw * bc + lb_count; let mut dst_offset = 0; for i in 0..bc { let ecc = if i < sb_ecc.ns { sb_ecc } else { &lb_ecc }; let dst = &mut out.data[dst_offset..(dst_offset + ecc.bs)]; let num_ec = ecc.bs - ecc.dw; #[allow(clippy::needless_range_loop)] for j in 0..ecc.dw { dst[j] = ds.data[j * bc + i]; } for j in 0..num_ec { dst[ecc.dw + j] = ds.data[ecc_offset + j * bc + i]; } correct_block(dst, ecc)?; dst_offset += ecc.dw; } out.bit_len = dst_offset * 8; Ok(out) } fn correct_block(block: &mut [u8], ecc: &RSParameters) -> DeQRResult<()> { assert!(ecc.bs > ecc.dw); let npar = ecc.bs - ecc.dw; let mut sigma_deriv = [GF256::ZERO; 64]; // Calculate syndromes. If all 0 there is nothing to do. let s = match block_syndromes(&block[..ecc.bs], npar) { Ok(_) => return Ok(()), Err(s) => s, }; let sigma = berlekamp_massey(&s, npar); /* Compute derivative of sigma */ for i in (1..64).step_by(2) { sigma_deriv[i - 1] = sigma[i]; } /* Compute error evaluator polynomial */ let omega = eloc_poly(&s, &sigma, npar - 1); /* Find error locations and magnitudes */ for i in 0..ecc.bs { let xinv = GF256::GENERATOR.pow(255 - i); if poly_eval(&sigma, xinv) == GF256::ZERO { let sd_x = poly_eval(&sigma_deriv, xinv); let omega_x = poly_eval(&omega, xinv); if sd_x == GF256::ZERO { return Err(DeQRError::DataEcc); } let error = omega_x / sd_x; block[ecc.bs - i - 1] = (GF256(block[ecc.bs - i - 1]) + error).0; } } match block_syndromes(&block[..ecc.bs], npar) { Ok(_) => Ok(()), Err(_) => Err(DeQRError::DataEcc), } } /* *********************************************************************** * Code stream error correction * * Generator polynomial for GF(2^8) is x^8 + x^4 + x^3 + x^2 + 1 */ fn block_syndromes(block: &[u8], npar: usize) -> Result<[GF256; 64], [GF256; 64]> { let mut nonzero: bool = false; let mut s = [GF256::ZERO; 64]; #[allow(clippy::needless_range_loop)] for i in 0..npar { for j in 0..block.len() { let c = GF256(block[block.len() - 1 - j]); s[i] += c * GF256::GENERATOR.pow(i * j); } if s[i] != GF256::ZERO { nonzero = true; } } if nonzero { Err(s) } else { Ok(s) } } fn poly_eval(s: &[G; 64], x: G) -> G where G: GaloisField, { let mut sum = G::ZERO; let mut x_pow = G::ONE; #[allow(clippy::needless_range_loop)] for i in 0..64 { sum += s[i] * x_pow; x_pow *= x; } sum } fn eloc_poly(s: &[GF256; 64], sigma: &[GF256; 64], npar: usize) -> [GF256; 64] { let mut omega = [GF256::ZERO; 64]; for i in 0..npar { let a = sigma[i]; for j in 0..(npar - i) { let b = s[j + 1]; omega[i + j] += a * b; } } omega } /* *********************************************************************** * Berlekamp-Massey algorithm for finding error locator polynomials. */ fn berlekamp_massey(s: &[G; 64], n: usize) -> [G; 64] where G: GaloisField, { let mut ts: [G; 64] = [G::ZERO; 64]; let mut cs: [G; 64] = [G::ZERO; 64]; let mut bs: [G; 64] = [G::ZERO; 64]; let mut l: usize = 0; let mut m: usize = 1; let mut b = G::ONE; bs[0] = G::ONE; cs[0] = G::ONE; for n in 0..n { let mut d = s[n]; // Calculate in GF(p): // d = s[n] + \Sum_{i=1}^{l} c[i] * s[n - i] for i in 1..=l { d += cs[i] * s[n - i]; } // Pre-calculate d * b^-1 in GF(p) let mult = d / b; if d == G::ZERO { m += 1 } else if l * 2 <= n { ts.copy_from_slice(&cs); poly_add(&mut cs, &bs, mult, m); bs.copy_from_slice(&ts); l = n + 1 - l; b = d; m = 1 } else { poly_add(&mut cs, &bs, mult, m); m += 1 } } cs } /* *********************************************************************** * Polynomial operations */ fn poly_add(dst: &mut [G; 64], src: &[G; 64], c: G, shift: usize) where G: GaloisField, { if c == G::ZERO { return; } #[allow(clippy::needless_range_loop)] for i in 0..64 { let p = i + shift; if p >= 64 { break; } let v = src[i]; dst[p] += v * c; } } /// Reads the code in the "zigzag" pattern, optionally removing the mask fn read_data(code: &dyn BitGrid, meta: &MetaData, remove_mask: bool) -> RawData { let mut ds = RawData { data: [0; MAX_PAYLOAD_SIZE], len: 0, }; let mut y = code.size() - 1; let mut x = code.size() - 1; let mut neg_dir = true; while x > 0 { if x == 6 { x -= 1; } if !reserved_cell(meta.version, y, x) { ds.push(read_bit(code, meta, y, x, remove_mask)); } if !reserved_cell(meta.version, y, x - 1) { ds.push(read_bit(code, meta, y, x - 1, remove_mask)); } let (new_y, new_neg_dir) = match (y, neg_dir) { (0, true) => { x = x.saturating_sub(2); (0, false) } (y, false) if y == code.size() - 1 => { x = x.saturating_sub(2); (code.size() - 1, true) } (y, true) => (y - 1, true), (y, false) => (y + 1, false), }; y = new_y; neg_dir = new_neg_dir; } ds } // The read_bit() function can optionally consider the mask. // This allows bits to be read as they appear "physically" in the QR code or with the mask removed, reflecting the actual code. fn read_bit(code: &dyn BitGrid, meta: &MetaData, y: usize, x: usize, remove_mask: bool) -> bool { let mut v = code.bit(y, x) as u8; if remove_mask && mask_bit(meta.mask, y, x) { v ^= 1 } v != 0 } fn mask_bit(mask: u16, y: usize, x: usize) -> bool { match mask { 0 => 0 == (y + x) % 2, 1 => 0 == y % 2, 2 => 0 == x % 3, 3 => 0 == (y + x) % 3, 4 => 0 == ((y / 2) + (x / 3)) % 2, 5 => 0 == ((y * x) % 2 + (y * x) % 3), 6 => 0 == ((y * x) % 2 + (y * x) % 3) % 2, 7 => 0 == ((y * x) % 3 + (y + x) % 2) % 2, _ => panic!("Unknown mask value"), } } fn reserved_cell(version: Version, i: usize, j: usize) -> bool { let ver = &VERSION_DATA_BASE[version.0]; let size = version.0 * 4 + 17; /* Finder + format: top left */ if i < 9 && j < 9 { return true; } /* Finder + format: bottom left */ if i + 8 >= size && j < 9 { return true; } /* Finder + format: top right */ if i < 9 && j + 8 >= size { return true; } /* Exclude timing patterns */ if i == 6 || j == 6 { return true; } /* Exclude version info, if it exists. Version info sits adjacent to * the top-right and bottom-left finders in three rows, bounded by * the timing pattern. */ if version.0 >= 7 { #[allow(clippy::if_same_then_else)] if i < 6 && j + 11 >= size { return true; } else if i + 11 >= size && j < 6 { return true; } } /* Exclude alignment patterns */ let mut ai = None; let mut aj = None; fn abs_diff(x: usize, y: usize) -> usize { if x < y { y - x } else { x - y } } let mut len = 0; for (a, &pattern) in ver.apat.iter().take_while(|&&x| x != 0).enumerate() { len = a; if abs_diff(pattern, i) < 3 { ai = Some(a) } if abs_diff(pattern, j) < 3 { aj = Some(a) } } match (ai, aj) { (Some(x), Some(y)) if x == len && y == len => true, (Some(x), Some(_)) if 0 < x && x < len => true, (Some(_), Some(x)) if 0 < x && x < len => true, _ => false, } } fn correct_format(mut word: u16) -> DeQRResult { /* Evaluate U (received codeword) at each of alpha_1 .. alpha_6 * to get S_1 .. S_6 (but we index them from 0). */ if let Err(s) = format_syndromes(word) { let sigma = berlekamp_massey(&s, 6); /* Now, find the roots of the polynomial */ for i in 0..15 { if poly_eval(&sigma, GF16::GENERATOR.pow(15 - i)) == GF16::ZERO { word ^= 1 << i; } } // Double CHECK syndromes format_syndromes(word).map_err(|_| DeQRError::FormatEcc)?; } Ok(word) } fn read_format(code: &dyn BitGrid) -> DeQRResult { let mut format = 0; // Try first location const XS: [usize; 15] = [8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0]; const YS: [usize; 15] = [0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8]; for i in (0..15).rev() { format = (format << 1) | code.bit(YS[i], XS[i]) as u16; } format ^= 0x5412; // Check format, try other location if needed let verified_format = correct_format(format).or_else(|_| { let mut format = 0; for i in 0..7 { format = (format << 1) | code.bit(code.size() - 1 - i, 8) as u16; } for i in 0..8 { format = (format << 1) | code.bit(8, code.size() - 8 + i) as u16; } format ^= 0x5412; correct_format(format) })?; let fdata = verified_format >> 10; let ecc_level = fdata >> 3; let mask = fdata & 7; let version = Version::from_size(code.size())?; Ok(MetaData { version, ecc_level, mask, }) } /* *********************************************************************** * Format value error correction * * Generator polynomial for GF(2^4) is x^4 + x + 1 */ fn format_syndromes(u: u16) -> Result<[GF16; 64], [GF16; 64]> { let mut result = [GF16(0); 64]; let mut nonzero = false; #[allow(clippy::needless_range_loop)] for i in 0..6 { for j in 0..15 { if u & (1 << j) != 0 { result[i] += GF16::GENERATOR.pow((i + 1) * j); } } if result[i].0 != 0 { nonzero = true; } } if nonzero { Err(result) } else { Ok(result) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_mask_0() { let test = [ [1, 0, 1, 0, 1, 0, 1], [0, 1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 1, 0, 1], [0, 1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 1, 0, 1], [0, 1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 1, 0, 1], ]; for x in 0..7 { for y in 0..7 { assert_eq!(test[y][x] != 0, mask_bit(0, y, x)); } } } #[test] fn test_mask_1() { let test = [ [1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1], ]; for x in 0..7 { for y in 0..7 { assert_eq!(test[y][x] != 0, mask_bit(1, y, x)); } } } #[test] fn test_mask_2() { let test = [ [1, 0, 0, 1, 0, 0, 1], [1, 0, 0, 1, 0, 0, 1], [1, 0, 0, 1, 0, 0, 1], [1, 0, 0, 1, 0, 0, 1], [1, 0, 0, 1, 0, 0, 1], [1, 0, 0, 1, 0, 0, 1], [1, 0, 0, 1, 0, 0, 1], ]; for x in 0..7 { for y in 0..7 { assert_eq!(test[y][x] != 0, mask_bit(2, y, x)); } } } #[test] fn test_mask_3() { let test = [ [1, 0, 0, 1, 0, 0, 1], [0, 0, 1, 0, 0, 1, 0], [0, 1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0, 1], [0, 0, 1, 0, 0, 1, 0], [0, 1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0, 1], ]; for x in 0..7 { for y in 0..7 { assert_eq!(test[y][x] != 0, mask_bit(3, y, x)); } } } #[test] fn test_mask_4() { let test = [ [1, 1, 1, 0, 0, 0, 1], [1, 1, 1, 0, 0, 0, 1], [0, 0, 0, 1, 1, 1, 0], [0, 0, 0, 1, 1, 1, 0], [1, 1, 1, 0, 0, 0, 1], [1, 1, 1, 0, 0, 0, 1], [0, 0, 0, 1, 1, 1, 0], ]; for x in 0..7 { for y in 0..7 { assert_eq!(test[y][x] != 0, mask_bit(4, y, x)); } } } #[test] fn test_mask_5() { let test = [ [1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0, 1], [1, 0, 0, 1, 0, 0, 1], [1, 0, 1, 0, 1, 0, 1], [1, 0, 0, 1, 0, 0, 1], [1, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1], ]; for x in 0..7 { for y in 0..7 { assert_eq!(test[y][x] != 0, mask_bit(5, y, x)); } } } #[test] fn test_mask_6() { let test = [ [1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 0, 0, 0, 1], [1, 1, 0, 1, 1, 0, 1], [1, 0, 1, 0, 1, 0, 1], [1, 0, 1, 1, 0, 1, 1], [1, 0, 0, 0, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1], ]; for x in 0..7 { for y in 0..7 { assert_eq!(test[y][x] != 0, mask_bit(6, y, x)); } } } #[test] fn test_mask_7() { let test = [ [1, 0, 1, 0, 1, 0, 1], [0, 0, 0, 1, 1, 1, 0], [1, 0, 0, 0, 1, 1, 1], [0, 1, 0, 1, 0, 1, 0], [1, 1, 1, 0, 0, 0, 1], [0, 1, 1, 1, 0, 0, 0], [1, 0, 1, 0, 1, 0, 1], ]; for x in 0..7 { for y in 0..7 { assert_eq!(test[y][x] != 0, mask_bit(7, y, x)); } } } } rqrr-0.9.3/src/detect.rs000064400000000000000000000520271046102023000132360ustar 00000000000000use crate::prepare::{AreaFiller, ImageBuffer, PixelColor}; use crate::{ geometry::Perspective, identify::Point, prepare::{ColoredRegion, PreparedImage, Row}, }; /// A locator pattern of a QR grid /// /// One of 3 corner patterns of a QR code. Can be found using a distinctive /// 1:1:3:1:1 pattern of black-white zones. /// /// Stores information about the corners of the capstone (NOT the grid), the /// center point and the local `perspective` i.e. in which direction the grid is /// likely skewed. #[derive(Debug, Clone)] pub struct CapStone { /// The 4 corners of the capstone pub corners: [Point; 4], /// The center point of the capstone pub center: Point, /// The local perspective of the capstone, i.e. in which direction(s) the /// capstone is skewed. pub c: Perspective, } /// Find all 'capstones' in a given image. /// /// A Capstones is the locator pattern of a QR code. Every QR code has 3 of /// these in 3 corners. This function finds these patterns by scanning the image /// line by line for a distinctive 1:1:3:1:1 pattern of /// black-white-black-white-black zones. /// /// Returns a vector of [CapStones](struct.CapStone.html) pub fn capstones_from_image(img: &mut PreparedImage) -> Vec where S: ImageBuffer, { let mut res = Vec::new(); for y in 0..img.height() { let mut finder = LineScanner::new(img.get_pixel_at(0, y)); for x in 1..img.width() { let linepos = match finder.advance(img.get_pixel_at(x, y)) { Some(l) => l, None => continue, }; if !is_capstone(img, &linepos, y) { continue; } let cap = match create_capstone(img, &linepos, y) { Some(c) => c, None => continue, }; res.push(cap); } // Insert a virtual white pixel at the end to trigger a re-check. Necessary when // the capstone lies right on the corner of an image if let Some(linepos) = finder.advance(PixelColor::White) { if !is_capstone(img, &linepos, y) { continue; } let cap = match create_capstone(img, &linepos, y) { Some(c) => c, None => continue, }; res.push(cap); } } res } #[derive(Debug, Clone, Copy, Eq, PartialEq)] struct LinePosition { left: usize, stone: usize, right: usize, } /// Find a possible capstone based on black/white transitions /// /// ```bash /// ####### /// # # /// --> # ### # <-- /// # ### # /// # ### # /// # # /// ####### /// ``` /// A capstone has a distinctive pattern of 1:1:3:1:1 of black-white /// transitions. So a run of black is followed by a run of white of equal /// length, followed by black with 3 times the length and so on. /// /// This struct is meant to operate on a single line, with the first value in /// the line given to `CapStoneFinder::new` and any following values given to /// `CapStoneFinder::advance` #[derive(Debug, Clone, Copy, Eq, PartialEq)] struct LineScanner { lookbehind_buf: [usize; 5], last_color: PixelColor, run_length: usize, color_changes: usize, current_position: usize, } impl LineScanner { /// Initialize a new CapStoneFinder with the value of the first pixel in a /// line fn new(initial_col: PixelColor) -> Self { LineScanner { lookbehind_buf: [0; 5], last_color: initial_col, run_length: 1, color_changes: 0, current_position: 0, } } /// Advance the position of the finder with the given color. /// /// This will return `None` if no pattern matching a CapStone was recently /// observed. It will return `Some(position)` if the last added pixel /// completes a 1:1:3:1:1 pattern of black/white runs. This is a /// candidate for capstones. fn advance(&mut self, color: PixelColor) -> Option { self.current_position += 1; // If we did not observe a color change, we have not reached the boundary of a // capstone if self.last_color == color { self.run_length += 1; return None; } self.last_color = color; self.lookbehind_buf.rotate_left(1); self.lookbehind_buf[4] = self.run_length; self.run_length = 1; self.color_changes += 1; if self.test_for_capstone() { Some(LinePosition { left: self.current_position - self.lookbehind_buf.iter().sum::(), stone: self.current_position - self.lookbehind_buf[2..].iter().sum::(), right: self.current_position - self.lookbehind_buf[4], }) } else { None } } /// Test if the observed pattern matches that of a capstone. /// /// Capstones have a distinct pattern of 1:1:3:1:1 of /// black->white->black->white->black transitions. fn test_for_capstone(&self) -> bool { // A capstone should look like > x xxx x < so we have to check after 5 color // changes and only if the newly observed color is white if PixelColor::White == self.last_color && self.color_changes >= 5 { const CHECK: [usize; 5] = [1, 1, 3, 1, 1]; let avg = (self.lookbehind_buf[0] + self.lookbehind_buf[1] + self.lookbehind_buf[3] + self.lookbehind_buf[4]) / 4; let err = avg * 3 / 4; #[allow(clippy::needless_range_loop)] for i in 0..5 { if self.lookbehind_buf[i] < CHECK[i] * avg - err || self.lookbehind_buf[i] > CHECK[i] * avg + err { return false; } } true } else { false } } } /// Determine if the given position is an unobserved capstone /// /// This checks for a few things: /// * The `left` and `right` positions are connected /// * The `stone` position is **not** connected to the others /// * The positions are unclaimed, i.e. not used for other capstones etc. /// * The ratio between the size of `stone` position and the outer `ring` /// position is roughly 37.5% /// /// Returns `true` if all of the above are true, `false` otherwise fn is_capstone(img: &mut PreparedImage, linepos: &LinePosition, y: usize) -> bool where S: ImageBuffer, { let ring_reg = img.get_region((linepos.right, y)); let stone_reg = img.get_region((linepos.stone, y)); if img.get_pixel_at(linepos.left, y) != img.get_pixel_at(linepos.right, y) { return false; } match (ring_reg, stone_reg) { ( ColoredRegion::Unclaimed { color: ring_color, pixel_count: ring_count, .. }, ColoredRegion::Unclaimed { color: stone_color, pixel_count: stone_count, .. }, ) => { let ratio = stone_count * 100 / ring_count; // Verify that left is connected to right, and that stone is not connected // Also that the pixel counts roughly repsect the 37.5% ratio ring_color != stone_color && 10 < ratio && ratio < 70 } _ => false, } } /// Create a capstone at the given position /// /// * This determines the extend and perspective of the capstone at the given /// position /// * It marks the `ring` and `stone` of the capstone as reserved so that it may /// not be detected again /// /// Returns the `CapStone` at the given position fn create_capstone( img: &mut PreparedImage, linepos: &LinePosition, y: usize, ) -> Option where S: ImageBuffer, { /* Find the corners of the ring */ let start_point = Point { x: linepos.right as i32, y: y as i32, }; let first_corner_finder = FirstCornerFinder::new(start_point); let first_corner_finder = img.repaint_and_apply((linepos.right, y), PixelColor::Tmp1, first_corner_finder); let all_corner_finder = AllCornerFinder::new(start_point, first_corner_finder.best()); let all_corner_finder = img.repaint_and_apply((linepos.right, y), PixelColor::CapStone, all_corner_finder); let corners = all_corner_finder.best(); /* Set up the perspective transform and find the center */ let c = Perspective::create(&corners, 7.0, 7.0)?; let center = c.map(3.5, 3.5); Some(CapStone { c, corners, center }) } /// Find the a corner of a sheared rectangle. /// /// This finds the point that is the farthest from a given reference point on /// the rectangle. This point must be one corner #[derive(Debug, Eq, PartialEq, Clone)] struct FirstCornerFinder { initial: Point, best: Point, score: i32, } impl FirstCornerFinder { pub fn new(initial: Point) -> Self { FirstCornerFinder { initial, best: Default::default(), score: -1, } } pub fn best(self) -> Point { self.best } } impl AreaFiller for FirstCornerFinder { fn update(&mut self, row: Row) { let dy = (row.y as i32) - self.initial.y; let l_dx = (row.left as i32) - self.initial.x; let r_dx = (row.right as i32) - self.initial.x; let l_dist = l_dx * l_dx + dy * dy; let r_dist = r_dx * r_dx + dy * dy; if l_dist > self.score { self.score = l_dist; self.best = Point { x: row.left as i32, y: row.y as i32, } } if r_dist > self.score { self.score = r_dist; self.best = Point { x: row.right as i32, y: row.y as i32, } } } } /// Find the 4 corners of a rectangle /// /// Expects an initial point in the rectangle as well as a known corner /// /// The other corners are found by searching extreme points based on the line /// between initial and corner point. The opposite corner must one of the points /// that lie the farthest in the opposite direction. The 2 other corners are /// those that are the farthest left and right from the reference line. #[derive(Debug, Eq, PartialEq, Clone)] struct AllCornerFinder { baseline: Point, best: [Point; 4], scores: [i32; 4], } impl AllCornerFinder { pub fn new(initial: Point, corner: Point) -> Self { let baseline = Point { x: corner.x - initial.x, y: corner.y - initial.y, }; let parallel_score = initial.x * baseline.x + initial.y * baseline.y; let orthogonal_score = -initial.x * baseline.y + initial.y * baseline.x; AllCornerFinder { baseline, best: [initial; 4], scores: [ parallel_score, orthogonal_score, -parallel_score, -orthogonal_score, ], } } pub fn best(self) -> [Point; 4] { self.best } } impl AreaFiller for AllCornerFinder { fn update(&mut self, row: Row) { let l_par_score = (row.left as i32) * self.baseline.x + (row.y as i32) * self.baseline.y; let l_ort_score = -(row.left as i32) * self.baseline.y + (row.y as i32) * self.baseline.x; let l_scores = [l_par_score, l_ort_score, -l_par_score, -l_ort_score]; let r_par_score = (row.right as i32) * self.baseline.x + (row.y as i32) * self.baseline.y; let r_ort_score = -(row.right as i32) * self.baseline.y + (row.y as i32) * self.baseline.x; let r_scores = [r_par_score, r_ort_score, -r_par_score, -r_ort_score]; for j in 0..4 { if l_scores[j] > self.scores[j] { self.scores[j] = l_scores[j]; self.best[j] = Point { x: row.left as i32, y: row.y as i32, } } if r_scores[j] > self.scores[j] { self.scores[j] = r_scores[j]; self.best[j] = Point { x: row.right as i32, y: row.y as i32, } } } } } #[cfg(test)] mod tests { use super::*; use crate::prepare::BasicImageBuffer; #[test] fn test_capstone_finder_small() { let mut line = [1, 0, 1, 1, 1, 0, 1, 0].iter(); let mut finder = LineScanner::new(PixelColor::White); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( Some(LinePosition { left: 1, stone: 3, right: 7, }), finder.advance(PixelColor::from(*line.next().unwrap())) ); } #[test] fn test_capstone_finder_start() { let mut line = [0, 1, 1, 1, 0, 1, 0].iter(); let mut finder = LineScanner::new(PixelColor::Black); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( Some(LinePosition { left: 0, stone: 2, right: 6, }), finder.advance(PixelColor::from(*line.next().unwrap())) ); } #[test] fn test_capstone_finder_multiple() { let mut line = [0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0].iter(); let mut finder = LineScanner::new(PixelColor::Black); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( Some(LinePosition { left: 0, stone: 2, right: 6, }), finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( Some(LinePosition { left: 6, stone: 8, right: 12, }), finder.advance(PixelColor::from(*line.next().unwrap())) ); } #[test] fn test_capstone_finder_variance() { let mut line = [1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0].iter(); let mut finder = LineScanner::new(PixelColor::White); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( None, finder.advance(PixelColor::from(*line.next().unwrap())) ); assert_eq!( Some(LinePosition { left: 1, stone: 6, right: 13, }), finder.advance(PixelColor::from(*line.next().unwrap())) ); } fn img_from_array(array: [[u8; 3]; 3]) -> PreparedImage { crate::PreparedImage::prepare_from_bitmap(3, 3, |x, y| array[y][x] == 1) } #[test] fn test_one_corner_finder() { let mut test_u = img_from_array([[1, 0, 1], [1, 0, 1], [1, 1, 1]]); let finder = FirstCornerFinder::new(Point { x: 0, y: 0 }); let res = test_u.repaint_and_apply((0, 0), PixelColor::Tmp1, finder); assert_eq!(Point { x: 2, y: 2 }, res.best()); } #[test] fn test_all_corner_finder() { let mut test_u = img_from_array([[1, 0, 1], [1, 0, 1], [1, 1, 1]]); let initial = Point { x: 0, y: 0 }; let one_corner = Point { x: 2, y: 2 }; let finder = AllCornerFinder::new(initial, one_corner); let res = test_u.repaint_and_apply((0, 0), PixelColor::Tmp1, finder); let corners = res.best(); assert_eq!(Point { x: 2, y: 2 }, corners[0]); assert_eq!(Point { x: 0, y: 2 }, corners[1]); assert_eq!(Point { x: 0, y: 0 }, corners[2]); assert_eq!(Point { x: 2, y: 0 }, corners[3]); } #[test] fn test_minimal_capstone() { let array = [ [1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0, 1], [1, 0, 1, 1, 1, 0, 1], [1, 0, 1, 1, 1, 0, 1], [1, 0, 1, 1, 1, 0, 1], [1, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1], ]; let mut prep_image = crate::PreparedImage::prepare_from_bitmap(7, 7, |x, y| array[y][x] == 1); let caps = crate::capstones_from_image(&mut prep_image); assert_eq!(1, caps.len()); assert_eq!(Point { x: 3, y: 3 }, caps[0].center) } fn load_and_find(img: &[u8]) -> Vec { let img = image::load_from_memory(img).unwrap().to_luma8(); let w = img.width() as usize; let h = img.height() as usize; let mut img = crate::PreparedImage::prepare_from_greyscale(w, h, |x, y| { img.get_pixel(x as u32, y as u32).0[0] }); crate::capstones_from_image(&mut img) } #[test] fn test_cap() { let caps = load_and_find(include_bytes!("test_data/cap.png")); assert_eq!(1, caps.len()); } #[test] fn test_cap_connected() { let caps = load_and_find(include_bytes!("test_data/cap_connect.png")); assert_eq!(0, caps.len()); } #[test] fn test_cap_disconnected() { let caps = load_and_find(include_bytes!("test_data/cap_disconnect.png")); assert_eq!(0, caps.len()); } #[test] fn test_cap_size() { let caps = load_and_find(include_bytes!("test_data/cap_size.png")); assert_eq!(0, caps.len()); } } rqrr-0.9.3/src/geometry.rs000064400000000000000000000224211046102023000136140ustar 00000000000000use crate::identify::Point; #[derive(Debug, PartialEq, Clone)] pub struct Perspective(pub [f64; 8]); impl Perspective { pub fn create(rect: &[Point; 4], w: f64, h: f64) -> Option { let mut c = [0.0; 8]; let x0 = rect[0].x as f64; let y0 = rect[0].y as f64; let x1 = rect[1].x as f64; let y1 = rect[1].y as f64; let x2 = rect[2].x as f64; let y2 = rect[2].y as f64; let x3 = rect[3].x as f64; let y3 = rect[3].y as f64; let wden = w * (x2 * y3 - x3 * y2 + (x3 - x2) * y1 + x1 * (y2 - y3)); let hden = h * (x2 * y3 + x1 * (y2 - y3) - x3 * y2 + (x3 - x2) * y1); if wden < f64::EPSILON || hden < f64::EPSILON { return None; } c[0] = (x1 * (x2 * y3 - x3 * y2) + x0 * (-x2 * y3 + x3 * y2 + (x2 - x3) * y1) + x1 * (x3 - x2) * y0) / wden; c[1] = -(x0 * (x2 * y3 + x1 * (y2 - y3) - x2 * y1) - x1 * x3 * y2 + x2 * x3 * y1 + (x1 * x3 - x2 * x3) * y0) / hden; c[2] = x0; c[3] = (y0 * (x1 * (y3 - y2) - x2 * y3 + x3 * y2) + y1 * (x2 * y3 - x3 * y2) + x0 * y1 * (y2 - y3)) / wden; c[4] = (x0 * (y1 * y3 - y2 * y3) + x1 * y2 * y3 - x2 * y1 * y3 + y0 * (x3 * y2 - x1 * y2 + (x2 - x3) * y1)) / hden; c[5] = y0; c[6] = (x1 * (y3 - y2) + x0 * (y2 - y3) + (x2 - x3) * y1 + (x3 - x2) * y0) / wden; c[7] = (-x2 * y3 + x1 * y3 + x3 * y2 + x0 * (y1 - y2) - x3 * y1 + (x2 - x1) * y0) / hden; Some(Perspective(c)) } pub fn map(&self, u: f64, v: f64) -> Point { let den = self.0[6] * u + self.0[7] * v + 1.0f64; let x = (self.0[0] * u + self.0[1] * v + self.0[2]) / den; let y = (self.0[3] * u + self.0[4] * v + self.0[5]) / den; let x = x.round(); let y = y.round(); assert!(x <= i32::MAX as f64); assert!(x >= i32::MIN as f64); assert!(y <= i32::MAX as f64); assert!(y >= i32::MIN as f64); Point { x: x as i32, y: y as i32, } } pub fn unmap(&self, p: &Point) -> (f64, f64) { let x = p.x as f64; let y = p.y as f64; let den = -self.0[0] * self.0[7] * y + self.0[1] * self.0[6] * y + (self.0[3] * self.0[7] - self.0[4] * self.0[6]) * x + self.0[0] * self.0[4] - self.0[1] * self.0[3]; let u = -(self.0[1] * (y - self.0[5]) - self.0[2] * self.0[7] * y + (self.0[5] * self.0[7] - self.0[4]) * x + self.0[2] * self.0[4]) / den; let v = (self.0[0] * (y - self.0[5]) - self.0[2] * self.0[6] * y + (self.0[5] * self.0[6] - self.0[3]) * x + self.0[2] * self.0[3]) / den; (u, v) } } pub fn line_intersect(p0: &Point, p1: &Point, q0: &Point, q1: &Point) -> Option { /* (a, b) is perpendicular to line p */ let a = -(p1.y - p0.y); let b = p1.x - p0.x; /* (c, d) is perpendicular to line q */ let c = -(q1.y - q0.y); let d = q1.x - q0.x; /* e and f are dot products of the respective vectors with p and q */ let e = a * p1.x + b * p1.y; let f = c * q1.x + d * q1.y; /* Now we need to solve: * [a b] [rx] [e] * [c d] [ry] = [f] * * We do this by inverting the matrix and applying it to (e, f): * [ d -b] [e] [rx] * 1/det [-c a] [f] = [ry] */ let det = a * d - b * c; if det == 0 { None } else { Some(Point { x: (d * e - b * f) / det, y: (-c * e + a * f) / det, }) } } #[derive(Debug, Clone)] pub struct BresenhamScan { x: i32, y: i32, dom_step: i32, non_step: i32, i: i32, d: i32, n: i32, a: i32, x_dom: bool, } impl BresenhamScan { pub fn new(from: &Point, to: &Point) -> Self { let n = to.x - from.x; let d = to.y - from.y; let x = from.x; let y = from.y; let (x_dom, n, d) = if n.abs() > d.abs() { (true, d, n) } else { (false, n, d) }; let (n, non_step) = if n < 0 { (-n, -1) } else { (n, 1) }; let (d, dom_step) = if d < 0 { (-d, -1) } else { (d, 1) }; BresenhamScan { x, y, dom_step, non_step, i: 0, d, n, a: n, x_dom, } } } impl Iterator for BresenhamScan { type Item = Point; fn next(&mut self) -> Option<::Item> { if self.i > self.d { return None; } let ret = Point { x: self.x, y: self.y, }; self.a += self.n; let (dom, non) = match (self.x_dom, &mut self.x, &mut self.y) { (true, x, y) => (x, y), (false, x, y) => (y, x), }; *dom += self.dom_step; if self.a >= self.d { *non += self.non_step; self.a -= self.d; } self.i += 1; Some(ret) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_bresenham_straight() { let middle = Point { x: 100, y: 100 }; let up = Point { x: 100, y: 200 }; let right = Point { x: 300, y: 100 }; let scan_up = BresenhamScan::new(&middle, &up); for (i, p) in scan_up.enumerate() { assert_eq!(100 + i as i32, p.y); assert_eq!(100, p.x); } let scan_down = BresenhamScan::new(&up, &middle); for (i, p) in scan_down.enumerate() { assert_eq!(200 - i as i32, p.y); assert_eq!(100, p.x); } let scan_right = BresenhamScan::new(&middle, &right); for (i, p) in scan_right.enumerate() { assert_eq!(100, p.y); assert_eq!(100 + i as i32, p.x); } let scan_right = BresenhamScan::new(&right, &middle); for (i, p) in scan_right.enumerate() { assert_eq!(100, p.y); assert_eq!(300 - i as i32, p.x); } } #[test] fn test_bresenham_zero() { let start = Point { x: 37, y: 45 }; let mut scan = BresenhamScan::new(&start, &start); assert_eq!(scan.next(), Some(Point { x: 37, y: 45 })); assert_eq!(scan.next(), None) } #[test] fn test_bresenham_major_x() { // Taken from https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm let start = Point { x: 1, y: 1 }; let end = Point { x: 11, y: 5 }; let mut scan = BresenhamScan::new(&start, &end); assert_eq!(scan.next(), Some(Point { x: 1, y: 1 })); assert_eq!(scan.next(), Some(Point { x: 2, y: 1 })); assert_eq!(scan.next(), Some(Point { x: 3, y: 2 })); assert_eq!(scan.next(), Some(Point { x: 4, y: 2 })); assert_eq!(scan.next(), Some(Point { x: 5, y: 3 })); assert_eq!(scan.next(), Some(Point { x: 6, y: 3 })); assert_eq!(scan.next(), Some(Point { x: 7, y: 3 })); assert_eq!(scan.next(), Some(Point { x: 8, y: 4 })); assert_eq!(scan.next(), Some(Point { x: 9, y: 4 })); assert_eq!(scan.next(), Some(Point { x: 10, y: 5 })); assert_eq!(scan.next(), Some(Point { x: 11, y: 5 })); assert_eq!(scan.next(), None); } #[test] fn test_bresenham_major_y() { // Taken from https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm let start = Point { x: 5, y: 11 }; let end = Point { x: 1, y: 1 }; let mut scan = BresenhamScan::new(&start, &end); assert_eq!(scan.next(), Some(Point { x: 5, y: 11 })); assert_eq!(scan.next(), Some(Point { x: 5, y: 10 })); assert_eq!(scan.next(), Some(Point { x: 4, y: 9 })); assert_eq!(scan.next(), Some(Point { x: 4, y: 8 })); assert_eq!(scan.next(), Some(Point { x: 3, y: 7 })); assert_eq!(scan.next(), Some(Point { x: 3, y: 6 })); assert_eq!(scan.next(), Some(Point { x: 3, y: 5 })); assert_eq!(scan.next(), Some(Point { x: 2, y: 4 })); assert_eq!(scan.next(), Some(Point { x: 2, y: 3 })); assert_eq!(scan.next(), Some(Point { x: 1, y: 2 })); assert_eq!(scan.next(), Some(Point { x: 1, y: 1 })); assert_eq!(scan.next(), None); } #[test] fn test_line_intersect_parallel() { let p0 = Point { x: 0, y: 0 }; let p1 = Point { x: 0, y: 10 }; let q0 = Point { x: 1, y: 1 }; let q1 = Point { x: 1, y: -9 }; assert_eq!(line_intersect(&p0, &p1, &q0, &q1), None) } #[test] fn test_line_intersect_values() { let p0 = Point { x: 0, y: 0 }; let p1 = Point { x: 0, y: 10 }; let q0 = Point { x: 1, y: 1 }; let q1 = Point { x: 10, y: -9 }; // Check that all permutations produce same result assert_eq!( line_intersect(&p0, &p1, &q0, &q1), Some(Point { x: 0, y: 2 }) ); assert_eq!( line_intersect(&p0, &p1, &q1, &q0), Some(Point { x: 0, y: 2 }) ); assert_eq!( line_intersect(&p1, &p0, &q0, &q1), Some(Point { x: 0, y: 2 }) ); assert_eq!( line_intersect(&p1, &p0, &q1, &q0), Some(Point { x: 0, y: 2 }) ); } } rqrr-0.9.3/src/identify/grid.rs000064400000000000000000000336221046102023000145260ustar 00000000000000use std::{cmp, mem}; use crate::{ geometry, identify::match_capstones::CapStoneGroup, prepare::PreparedImage, prepare::{AreaFiller, ColoredRegion, ImageBuffer, PixelColor, Row}, version_db::VERSION_DATA_BASE, BitGrid, CapStone, Point, }; /// Location of a skewed square in an image /// /// A skewed square formed by 3 [CapStones](struct.CapStone.html) and possibly /// an alignment pattern located in the 4th corner. This can be converted into a /// normal [Grid](trait.Grid.html) that can be decoded. #[derive(Debug, Clone)] pub struct SkewedGridLocation { pub grid_size: usize, pub c: geometry::Perspective, } impl SkewedGridLocation { /// Create a SkewedGridLocation from corners /// /// Given 3 corners of a grid, this tries to match the expected features of /// a QR code such that the SkewedGridLocation maps onto those features. /// /// For all grids this includes searching for timing patterns between /// capstones to determine the grid size. /// /// For bigger grids this includes searching for an alignment pattern in the /// 4 corner. /// /// If no sufficient match could be produces, return `None` instead. pub fn from_group(img: &mut PreparedImage, mut group: CapStoneGroup) -> Option where S: ImageBuffer, { /* Construct the hypotenuse line from A to C. B should be to * the left of this line. */ let h0 = group.0.center; let mut hd = Point { x: group.2.center.x - group.0.center.x, y: group.2.center.y - group.0.center.y, }; /* Make sure A-B-C is clockwise */ if (group.1.center.x - h0.x) * -hd.y + (group.1.center.y - h0.y) * hd.x > 0 { mem::swap(&mut group.0, &mut group.2); hd.x = -hd.x; hd.y = -hd.y } /* Rotate each capstone so that corner 0 is top-left with respect * to the grid. */ rotate_capstone(&mut group.0, &h0, &hd); rotate_capstone(&mut group.1, &h0, &hd); rotate_capstone(&mut group.2, &h0, &hd); /* Check the timing pattern. This doesn't require a perspective * transform. */ let grid_size = measure_timing_pattern(img, &group); /* Make an estimate based for the alignment pattern based on extending * lines from capstones A and C. */ let mut align = geometry::line_intersect( &group.0.corners[0], &group.0.corners[1], &group.2.corners[0], &group.2.corners[3], )?; /* On V2+ grids, we should use the alignment pattern. */ if grid_size > 21 { /* Try to find the actual location of the alignment pattern. */ align = find_alignment_pattern(img, align, &group.0, &group.2)?; let score = -hd.y * align.x + hd.x * align.y; let finder = LeftMostFinder { line_p: hd, best: align, score, }; let found = img.repaint_and_apply( (align.x as usize, align.y as usize), PixelColor::Alignment, finder, ); align = found.best; } if version_from_grid_size(grid_size) >= VERSION_DATA_BASE.len() { return None; } let c = setup_perspective(img, &group, align, grid_size)?; Some(SkewedGridLocation { grid_size, c }) } /// Convert into a grid referencing the underlying image as source pub fn into_grid_image(self, img: &PreparedImage) -> RefGridImage { RefGridImage { grid: self, img } } } /// Get the version for a given grid size. /// /// The returned version can be used to fetch the VersionInfo for the given grid /// size. fn version_from_grid_size(grid_size: usize) -> usize { (grid_size - 17) / 4 } /// A Grid that references a bigger image /// /// Given a grid location and an image, implement the [Grid /// trait](trait.Grid.html) so that it may be decoded by /// [decode](fn.decode.html) pub struct RefGridImage<'a, S> { grid: SkewedGridLocation, img: &'a PreparedImage, } impl BitGrid for RefGridImage<'_, S> where S: ImageBuffer, { fn size(&self) -> usize { self.grid.grid_size } fn bit(&self, y: usize, x: usize) -> bool { let p = self.grid.c.map(x as f64 + 0.5, y as f64 + 0.5); PixelColor::White != self.img.get_pixel_at_point(p) } } fn setup_perspective( img: &PreparedImage, caps: &CapStoneGroup, align: Point, grid_size: usize, ) -> Option where S: ImageBuffer, { let inital = geometry::Perspective::create( &[ caps.1.corners[0], caps.2.corners[0], align, caps.0.corners[0], ], (grid_size - 7) as f64, (grid_size - 7) as f64, )?; Some(jiggle_perspective(img, inital, grid_size)) } fn rotate_capstone(cap: &mut CapStone, h0: &Point, hd: &Point) { let (best_idx, _) = cap .corners .iter() .enumerate() .min_by_key(|(_, a)| (a.x - h0.x) * (-hd.y) + (a.y - h0.y) * hd.x) .expect("corners cannot be empty"); /* Rotate the capstone */ cap.corners.rotate_left(best_idx); cap.c = geometry::Perspective::create(&cap.corners, 7.0, 7.0) .expect("rotated perspective can't fail"); } //* Try the measure the timing pattern for a given QR code. This does // * not require the global perspective to have been set up, but it // * does require that the capstone corners have been set to their // * canonical rotation. // * // * For each capstone, we find a point in the middle of the ring band // * which is nearest the centre of the code. Using these points, we do // * a horizontal and a vertical timing scan. // */ fn measure_timing_pattern(img: &PreparedImage, caps: &CapStoneGroup) -> usize where S: ImageBuffer, { const US: [f64; 3] = [6.5f64, 6.5f64, 0.5f64]; const VS: [f64; 3] = [0.5f64, 6.5f64, 6.5f64]; let tpet0 = caps.0.c.map(US[0], VS[0]); let tpet1 = caps.1.c.map(US[1], VS[1]); let tpet2 = caps.2.c.map(US[2], VS[2]); let hscan = timing_scan(img, &tpet1, &tpet2); let vscan = timing_scan(img, &tpet1, &tpet0); let scan = cmp::max(hscan, vscan); /* Choose the nearest allowable grid size */ assert!(scan >= 1); let size = scan * 2 + 13; let ver = (size as f64 - 15.0).floor() as usize / 4; ver * 4 + 17 } fn timing_scan(img: &PreparedImage, p0: &Point, p1: &Point) -> usize where S: ImageBuffer, { let mut run_length = 0; let mut count = 0; for p in geometry::BresenhamScan::new(p0, p1) { let pixel = img.get_pixel_at_point(p); if PixelColor::White != pixel { if run_length >= 2 { count += 1 } run_length = 0; } else { run_length += 1; } } count } fn find_alignment_pattern( img: &mut PreparedImage, mut align_seed: Point, c0: &CapStone, c2: &CapStone, ) -> Option where S: ImageBuffer, { /* Guess another two corners of the alignment pattern so that we * can estimate its size. */ let (u, v) = c0.c.unmap(&align_seed); let a = c0.c.map(u, v + 1.0); let (u, v) = c2.c.unmap(&align_seed); let c = c2.c.map(u + 1.0, v); let size_estimate = ((a.x - align_seed.x) * -(c.y - align_seed.y) + (a.y - align_seed.y) * (c.x - align_seed.x)) .unsigned_abs() as usize; /* Spiral outwards from the estimate point until we find something * roughly the right size. Don't look too far from the estimate * point. */ let mut dir = 0; let mut step_size = 1; while step_size * step_size < size_estimate * 100 { const DX_MAP: [i32; 4] = [1, 0, -1, 0]; const DY_MAP: [i32; 4] = [0, -1, 0, 1]; for _pass in 0..step_size { let x = align_seed.x as usize; let y = align_seed.y as usize; // Alignment pattern should not be white if x < img.width() && y < img.height() && PixelColor::White != img.get_pixel_at(x, y) { let region = img.get_region((x, y)); let count = match region { ColoredRegion::Unclaimed { pixel_count, .. } => pixel_count, _ => continue, }; // Matches expected size of alignment pattern if count >= size_estimate / 2 && count <= size_estimate * 2 { return Some(align_seed); } } align_seed.x += DX_MAP[dir]; align_seed.y += DY_MAP[dir]; } // Cycle directions dir = (dir + 1) % 4; if dir & 1 == 0 { step_size += 1 } } None } struct LeftMostFinder { line_p: Point, best: Point, score: i32, } impl AreaFiller for LeftMostFinder { fn update(&mut self, row: Row) { let left_d = -self.line_p.y * (row.left as i32) + self.line_p.x * row.y as i32; let right_d = -self.line_p.y * (row.right as i32) + self.line_p.x * row.y as i32; if left_d < self.score { self.score = left_d; self.best.x = row.left as i32; self.best.y = row.y as i32; } if right_d < self.score { self.score = right_d; self.best.x = row.right as i32; self.best.y = row.y as i32; } } } fn jiggle_perspective( img: &PreparedImage, mut perspective: geometry::Perspective, grid_size: usize, ) -> geometry::Perspective where S: ImageBuffer, { let mut best = fitness_all(img, &perspective, grid_size); let mut adjustments: [f64; 8] = [ perspective.0[0] * 0.02f64, perspective.0[1] * 0.02f64, perspective.0[2] * 0.02f64, perspective.0[3] * 0.02f64, perspective.0[4] * 0.02f64, perspective.0[5] * 0.02f64, perspective.0[6] * 0.02f64, perspective.0[7] * 0.02f64, ]; for _pass in 0..5 { for i in 0..16 { let j = i >> 1; let old = perspective.0[j]; let step = adjustments[j]; let new = if i & 1 != 0 { old + step } else { old - step }; perspective.0[j] = new; let test = fitness_all(img, &perspective, grid_size); if test > best { best = test } else { perspective.0[j] = old } } #[allow(clippy::needless_range_loop)] for i in 0..8 { adjustments[i] *= 0.5f64; } } perspective } /* Compute a fitness score for the currently configured perspective * transform, using the features we expect to find by scanning the * grid. */ fn fitness_all( img: &PreparedImage, perspective: &geometry::Perspective, grid_size: usize, ) -> i32 where S: ImageBuffer, { let version = version_from_grid_size(grid_size); let info = &VERSION_DATA_BASE[version]; let mut score = 0; /* Check the timing pattern */ for i in 0..(grid_size as i32 - 14) { let expect = if 0 != i & 1 { 1 } else { -1 }; score += fitness_cell(img, perspective, i + 7, 6) * expect; score += fitness_cell(img, perspective, 6, i + 7) * expect; } /* Check capstones */ score += fitness_capstone(img, perspective, 0, 0); score += fitness_capstone(img, perspective, grid_size as i32 - 7, 0); score += fitness_capstone(img, perspective, 0, grid_size as i32 - 7); /* Check alignment patterns */ let mut ap_count = 0; while ap_count < 7 && info.apat[ap_count] != 0 { ap_count += 1 } for i in 1..(ap_count.saturating_sub(1)) { score += fitness_apat(img, perspective, 6, info.apat[i] as i32); score += fitness_apat(img, perspective, info.apat[i] as i32, 6); } for i in 1..ap_count { for j in 1..ap_count { score += fitness_apat(img, perspective, info.apat[i] as i32, info.apat[j] as i32); } } score } fn fitness_apat( img: &PreparedImage, perspective: &geometry::Perspective, cx: i32, cy: i32, ) -> i32 where S: ImageBuffer, { fitness_cell(img, perspective, cx, cy) - fitness_ring(img, perspective, cx, cy, 1) + fitness_ring(img, perspective, cx, cy, 2) } fn fitness_ring( img: &PreparedImage, perspective: &geometry::Perspective, cx: i32, cy: i32, radius: i32, ) -> i32 where S: ImageBuffer, { let mut score = 0; for i in 0..(radius * 2) { score += fitness_cell(img, perspective, cx - radius + i, cy - radius); score += fitness_cell(img, perspective, cx - radius, cy + radius - i); score += fitness_cell(img, perspective, cx + radius, cy - radius + i); score += fitness_cell(img, perspective, cx + radius - i, cy + radius); } score } fn fitness_cell( img: &PreparedImage, perspective: &geometry::Perspective, x: i32, y: i32, ) -> i32 where S: ImageBuffer, { const OFFSETS: [f64; 3] = [0.3f64, 0.5f64, 0.7f64]; let mut score = 0; #[allow(clippy::needless_range_loop)] for v in 0..3 { for u in 0..3 { let p = perspective.map(x as f64 + OFFSETS[u], y as f64 + OFFSETS[v]); if !(p.y < 0 || p.y as usize >= img.height() || p.x < 0 || p.x as usize >= img.width()) { if PixelColor::White != img.get_pixel_at_point(p) { score += 1 } else { score -= 1 } } } } score } fn fitness_capstone( img: &PreparedImage, perspective: &geometry::Perspective, x: i32, y: i32, ) -> i32 where S: ImageBuffer, { fitness_cell(img, perspective, x + 3, y + 3) + fitness_ring(img, perspective, x + 3, y + 3, 1) - fitness_ring(img, perspective, x + 3, y + 3, 2) + fitness_ring(img, perspective, x + 3, y + 3, 3) } rqrr-0.9.3/src/identify/match_capstones.rs000064400000000000000000000046401046102023000167520ustar 00000000000000use crate::CapStone; #[derive(Debug, Clone)] pub struct CapStoneGroup(pub CapStone, pub CapStone, pub CapStone); #[derive(Clone, Copy, Debug, PartialEq)] struct Neighbor { index: usize, distance: f64, } /// Return each pair Capstone indexes that are likely to be from a QR code /// Ordered from most symmetric to least symmetric pub fn find_and_rank_possible_neighbors(capstones: &[CapStone], idx: usize) -> Vec<(usize, usize)> { const VIABILITY_THRESHOLD: f64 = 0.25; let (hlist, vlist) = find_possible_neighbors(capstones, idx); let mut res = Vec::new(); struct NeighborSet { score: f64, h_index: usize, v_index: usize, } /* Test each possible grouping */ for hn in hlist { for vn in vlist.iter() { let score = { if hn.distance < vn.distance { (1.0f64 - hn.distance / vn.distance).abs() } else { (1.0f64 - vn.distance / hn.distance).abs() } }; if score < VIABILITY_THRESHOLD { res.push(NeighborSet { score, h_index: hn.index, v_index: vn.index, }); } } } res.sort_unstable_by(|a, b| { (a.score) .partial_cmp(&(b.score)) .expect("Neighbor distance should never cause a div by 0") }); res.iter().map(|n| (n.h_index, n.v_index)).collect() } fn find_possible_neighbors(capstones: &[CapStone], idx: usize) -> (Vec, Vec) { let cap = &capstones[idx]; let mut hlist = Vec::new(); let mut vlist = Vec::new(); /* Look for potential neighbours by examining the relative gradients * from this capstone to others. */ #[allow(clippy::needless_range_loop)] for others_idx in 0..capstones.len() { if others_idx == idx { continue; } let cmp_cap = &capstones[others_idx]; let (mut u, mut v) = cap.c.unmap(&cmp_cap.center); u = (u - 3.5f64).abs(); v = (v - 3.5f64).abs(); if u < 0.2f64 * v { hlist.push(Neighbor { index: others_idx, distance: v, }); } if v < 0.2f64 * u { vlist.push(Neighbor { index: others_idx, distance: u, }); } } (hlist, vlist) } rqrr-0.9.3/src/identify/mod.rs000064400000000000000000000004401046102023000143500ustar 00000000000000pub use self::grid::SkewedGridLocation; pub use self::match_capstones::find_and_rank_possible_neighbors; pub mod grid; pub mod match_capstones; /// A simple point in (some) space #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] pub struct Point { pub x: i32, pub y: i32, } rqrr-0.9.3/src/lib.rs000064400000000000000000000535661046102023000125450ustar 00000000000000//! Find and read QR-Codes //! //! This crates exports functions and types that can be used to search for //! QR-Codes in images and decode them. //! //! #![cfg_attr( feature = "img", doc = r##" # Usage The most basic usage is shown below: ```rust use image; # fn main() -> Result<(), Box> { // Load on image to search, convert it to grayscale let img = image::open("tests/data/github.gif")?.to_luma8(); // Prepare for detection let mut img = rqrr::PreparedImage::prepare(img); // Search for grids, without decoding let grids = img.detect_grids(); assert_eq!(grids.len(), 1); // Decode the grid let (meta, content) = grids[0].decode()?; assert_eq!(meta.ecc_level, 0); assert_eq!(content, "https://github.com/WanzenBug/rqrr"); # Ok(()) # } ``` If you have some other form of picture storage, you can use [`PreparedImage::prepare_from_*`](struct.PreparedImage.html). This allows you to define your own source for images. "## )] pub use self::decode::{MetaData, RawData, Version, MAX_PAYLOAD_SIZE}; pub(crate) use self::detect::{capstones_from_image, CapStone}; pub use self::identify::Point; pub(crate) use self::identify::SkewedGridLocation; pub use self::prepare::PreparedImage; use std::error::Error; use std::io::Write; mod decode; mod detect; pub(crate) mod geometry; mod identify; mod prepare; mod version_db; /// Wrapper around any grid that can be interpreted as a QR code #[derive(Debug, Clone)] pub struct Grid { /// The backing binary square pub grid: G, /// The bounds of the square, in underlying coordinates. /// /// The points are listed in the following order: /// [top-left, top-right, bottom-right, bottom-left] /// /// If this grid references for example an underlying image, these values /// will be set to coordinates in that image. pub bounds: [Point; 4], } impl Grid where G: BitGrid, { /// Create a new grid from a BitGrid. /// /// This just initialises the bounds to 0. pub fn new(grid: G) -> Self { Grid { grid, bounds: [ Point { x: 0, y: 0 }, Point { x: 0, y: 0 }, Point { x: 0, y: 0 }, Point { x: 0, y: 0 }, ], } } /// Try to decode the grid. /// /// If successful returns the decoded string as well as metadata about the /// code. pub fn decode(&self) -> DeQRResult<(MetaData, String)> { let mut out = Vec::new(); let meta = self.decode_to(&mut out)?; let out = String::from_utf8(out)?; Ok((meta, out)) } /// Try to read metadata, and return the raw, uncorrected bit stream. /// /// If successful, returns the metadata along with the raw bit pattern. /// The raw data is still masked, so bits appear as in the source image. pub fn get_raw_data(&self) -> DeQRResult<(MetaData, RawData)> { crate::decode::get_raw(&self.grid) } /// Try to decode the grid. /// /// Instead of returning a String, this methode writes the decoded result to /// the given writer /// /// **Warning**: This may lead to half decoded content to be written to the /// writer. pub fn decode_to(&self, writer: W) -> DeQRResult where W: Write, { crate::decode::decode(&self.grid, writer) } } /// A grid that contains exactly one QR code square. /// /// The common trait for everything that can be decoded as a QR code. Given a /// normal image, we first need to find the QR grids in it. /// /// This trait can be implemented when some object is known to be exactly the /// bit-pattern of a QR code. pub trait BitGrid { /// Return the size of the grid. /// /// Since QR codes are always squares, the grid is assumed to be size * /// size. fn size(&self) -> usize; /// Return the value of the bit at the given location. /// /// `true` means 'black', `false` means 'white' fn bit(&self, y: usize, x: usize) -> bool; #[cfg(feature = "img")] fn write_grid_to(&self, p: &str) { let mut dyn_img = image::GrayImage::new(self.size() as u32, self.size() as u32); for y in 0..self.size() { for x in 0..self.size() { let color = match self.bit(x, y) { true => 0, false => 255, }; dyn_img.get_pixel_mut(x as u32, y as u32).0[0] = color; } } dyn_img.save(p).unwrap(); } } /// A basic GridImage that can be generated from a given function. /// /// # Example /// /// ```rust /// # fn main() -> Result<(), rqrr::DeQRError> { /// let grid = [ /// [ /// 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, /// ], /// [ /// 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, /// ], /// [ /// 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, /// ], /// [ /// 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, /// ], /// [ /// 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, /// ], /// [ /// 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, /// ], /// [ /// 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, /// ], /// [ /// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /// ], /// [ /// 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, /// ], /// [ /// 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, /// ], /// [ /// 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, /// ], /// [ /// 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, /// ], /// [ /// 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, /// ], /// [ /// 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, /// ], /// [ /// 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, /// ], /// [ /// 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, /// ], /// [ /// 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, /// ], /// [ /// 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, /// ], /// [ /// 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, /// ], /// [ /// 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, /// ], /// [ /// 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, /// ], /// ]; /// /// let simple = rqrr::SimpleGrid::from_func(21, |x, y| grid[y][x] == 1); /// let grid = rqrr::Grid::new(simple); /// let (_meta, content) = grid.decode()?; /// assert_eq!(content, "rqrr"); /// # Ok(()) /// # } /// ``` #[derive(Debug, Clone)] pub struct SimpleGrid { cell_bitmap: Vec, size: usize, } impl SimpleGrid { pub fn from_func(size: usize, fill_func: F) -> Self where F: Fn(usize, usize) -> bool, { let mut cell_bitmap = vec![0; (size * size + 7) / 8]; let mut c = 0; for y in 0..size { for x in 0..size { if fill_func(x, y) { cell_bitmap[c >> 3] |= 1 << (c & 7) as u8; } c += 1; } } SimpleGrid { cell_bitmap, size } } } impl BitGrid for SimpleGrid { fn size(&self) -> usize { self.size } fn bit(&self, y: usize, x: usize) -> bool { let c = y * self.size + x; self.cell_bitmap[c >> 3] & (1 << (c & 7) as u8) != 0 } } /// Possible errors that can happen during decoding #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum DeQRError { /// Could not write the output to the output stream/string IoError, /// Expected more bits to decode DataUnderflow, /// Expected less bits to decode DataOverflow, /// Unknown data type in encoding UnknownDataType, /// Could not correct errors / code corrupt DataEcc, /// Could not read format information from both locations FormatEcc, /// Unsupported / non-existent version read InvalidVersion, /// Unsupported / non-existent grid size read InvalidGridSize, /// Output was not encoded in expected UTF8 EncodingError, } type DeQRResult = Result; impl Error for DeQRError {} impl From<::std::string::FromUtf8Error> for DeQRError { fn from(_: ::std::string::FromUtf8Error) -> Self { DeQRError::EncodingError } } impl ::std::fmt::Display for DeQRError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let msg = match self { DeQRError::IoError => "IoError(Could not write to output)", DeQRError::DataUnderflow => "DataUnderflow(Expected more bits to decode)", DeQRError::DataOverflow => "DataOverflow(Expected less bits to decode)", DeQRError::UnknownDataType => "UnknownDataType(DataType not known or not implemented)", DeQRError::DataEcc => "Ecc(Too many errors to correct)", DeQRError::FormatEcc => "Ecc(Version information corrupt)", DeQRError::InvalidVersion => "InvalidVersion(Invalid version or corrupt)", DeQRError::InvalidGridSize => "InvalidGridSize(Invalid version or corrupt)", DeQRError::EncodingError => "Encoding(Not UTF8)", }; write!(f, "{}", msg) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_rqrr() { let grid = [ [ 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, ], [ 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, ], [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, ], [ 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, ], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], [ 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, ], [ 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, ], [ 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, ], [ 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, ], [ 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, ], [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, ], [ 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, ], [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, ], [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, ], [ 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, ], ]; let img = crate::SimpleGrid::from_func(21, |x, y| grid[y][x] == 1); let mut buf = vec![0; img.size() * img.size() / 8 + 1]; for y in 0..img.size() { for x in 0..img.size() { let i = y * img.size() + x; if img.bit(y, x) { buf[i >> 3] |= 1 << ((i & 7) as u8); } } } let mut vec = Vec::new(); crate::decode::decode(&img, &mut vec).unwrap(); assert_eq!(b"rqrr".as_ref(), &vec[..]) } #[test] fn test_github() { let grid = [ [ 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, ], [ 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, ], [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, ], [ 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, ], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], [ 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, ], [ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, ], [ 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, ], [ 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, ], [ 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, ], [ 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, ], [ 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, ], [ 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, ], [ 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, ], [ 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, ], [ 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, ], [ 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, ], [ 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, ], [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, ], [ 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, ], [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, ], [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, ], [ 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, ], ]; let img = crate::SimpleGrid::from_func(29, |x, y| grid[y][x] == 1); let mut buf = vec![0; img.size() * img.size() / 8 + 1]; for y in 0..img.size() { for x in 0..img.size() { let i = y * img.size() + x; if img.bit(y, x) { buf[i >> 3] |= 1 << ((i & 7) as u8); } } } let mut vec = Vec::new(); crate::decode::decode(&img, &mut vec).unwrap(); assert_eq!(b"https://github.com/WanzenBug/rqrr".as_ref(), &vec[..]) } #[test] fn test_number() { let grid = [ [ 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, ], [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, ], [ 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, ], [ 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, ], [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, ], [ 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, ], [ 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, ], [ 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, ], [ 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ], [ 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, ], [ 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, ], [ 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, ], [ 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, ], [ 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, ], [ 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, ], [ 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, ], [ 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, ], [ 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, ], [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, ], [ 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, ], [ 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, ], [ 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, ], [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, ], [ 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, ], ]; let img = crate::SimpleGrid::from_func(29, |x, y| grid[y][x] == 1); let mut buf = vec![0; img.size() * img.size() / 8 + 1]; for y in 0..img.size() { for x in 0..img.size() { let i = y * img.size() + x; if img.bit(y, x) { buf[i >> 3] |= 1 << ((i & 7) as u8); } } } let mut vec = Vec::new(); crate::decode::decode(&img, &mut vec).unwrap(); assert_eq!(b"1234567891011121314151617181920".as_ref(), &vec[..]) } } rqrr-0.9.3/src/prepare.rs000064400000000000000000000471671046102023000134350ustar 00000000000000use std::{cmp, num::NonZeroUsize}; use crate::identify::match_capstones::CapStoneGroup; use crate::identify::Point; use lru::LruCache; /// An black-and-white image that can be mutated on search for QR codes /// /// During search for QR codes, some black zones will be recolored in /// 'different' shades of black. This is done to speed up the search and /// mitigate the impact of a huge zones. pub struct PreparedImage { buffer: S, cache: LruCache, } impl Clone for PreparedImage where S: Clone, { fn clone(&self) -> Self { let mut cache = LruCache::new(self.cache.cap()); for (k, v) in self.cache.iter() { cache.put(*k, *v); } PreparedImage { buffer: self.buffer.clone(), cache, } } } pub trait ImageBuffer { fn width(&self) -> usize; fn height(&self) -> usize; fn get_pixel(&self, x: usize, y: usize) -> u8; fn set_pixel(&mut self, x: usize, y: usize, val: u8); } #[cfg(feature = "img")] impl< T: image::GenericImage> + image::GenericImageView>, > ImageBuffer for T { fn width(&self) -> usize { self.width() as usize } fn height(&self) -> usize { self.height() as usize } fn get_pixel(&self, x: usize, y: usize) -> u8 { self.get_pixel(x as u32, y as u32).0[0] } fn set_pixel(&mut self, x: usize, y: usize, val: u8) { let pixel = image::Luma::::from([val; 1]); self.put_pixel(x as u32, y as u32, pixel); } } #[derive(Clone, Debug)] pub struct BasicImageBuffer { w: usize, h: usize, pixels: Box<[u8]>, } impl ImageBuffer for BasicImageBuffer { fn width(&self) -> usize { self.w } fn height(&self) -> usize { self.h } fn get_pixel(&self, x: usize, y: usize) -> u8 { let w = self.width(); self.pixels[(y * w) + x] } fn set_pixel(&mut self, x: usize, y: usize, val: u8) { let w = self.width(); self.pixels[(y * w) + x] = val } } #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct Row { pub left: usize, pub right: usize, pub y: usize, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum PixelColor { White, Black, CapStone, Alignment, Tmp1, Discarded(u8), } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ColoredRegion { Unclaimed { color: PixelColor, src_x: usize, src_y: usize, pixel_count: usize, }, CapStone, Alignment, Tmp1, } impl From for PixelColor { fn from(x: u8) -> Self { match x { 0 => PixelColor::White, 1 => PixelColor::Black, 2 => PixelColor::CapStone, 3 => PixelColor::Alignment, 4 => PixelColor::Tmp1, x => PixelColor::Discarded(x - 5), } } } impl From for u8 { fn from(c: PixelColor) -> Self { match c { PixelColor::White => 0, PixelColor::Black => 1, PixelColor::CapStone => 2, PixelColor::Alignment => 3, PixelColor::Tmp1 => 4, PixelColor::Discarded(x) => x + 5, } } } impl PartialEq for PixelColor { fn eq(&self, other: &u8) -> bool { let rep: u8 = (*self).into(); rep == *other } } pub trait AreaFiller { fn update(&mut self, row: Row); } impl AreaFiller for F where F: FnMut(Row), { fn update(&mut self, row: Row) { self(row) } } struct AreaCounter(usize); impl AreaFiller for AreaCounter { fn update(&mut self, row: Row) { self.0 += row.right - row.left + 1; } } impl PreparedImage where S: ImageBuffer, { pub fn prepare(mut buf: S) -> Self { let w = buf.width(); let h = buf.height(); let mut row_average = vec![0; w]; let mut avg_v = 0; let mut avg_u = 0; let threshold_s = cmp::max(w / 8, 1); for y in 0..h { for r in &mut row_average { *r = 0; } for x in 0..w { let (v, u) = if y % 2 == 0 { (w - 1 - x, x) } else { (x, w - 1 - x) }; avg_v = avg_v * (threshold_s - 1) / threshold_s + buf.get_pixel(v, y) as usize; avg_u = avg_u * (threshold_s - 1) / threshold_s + buf.get_pixel(u, y) as usize; row_average[v] += avg_v; row_average[u] += avg_u; } #[allow(clippy::needless_range_loop)] for x in 0..w { let fill = if (buf.get_pixel(x, y) as usize) < row_average[x] * (100 - 5) / (200 * threshold_s) { PixelColor::Black } else { PixelColor::White }; buf.set_pixel(x, y, fill.into()); } } PreparedImage { buffer: buf, cache: LruCache::new(NonZeroUsize::new(251).unwrap()), } } /// Group [CapStones](struct.CapStone.html) into [Grids](struct.Grid.html) /// that are likely QR codes /// /// Return a vector of Grids pub fn detect_grids(&mut self) -> Vec>> where S: Clone, { let mut res = Vec::new(); let stones = crate::capstones_from_image(self); let groups = self.find_groupings(stones); let locations: Vec<_> = groups .into_iter() .filter_map(|v| crate::SkewedGridLocation::from_group(self, v)) .collect(); for grid_location in locations { let bounds = [ grid_location.c.map(0.0, 0.0), grid_location .c .map(grid_location.grid_size as f64 + 1.0, 0.0), grid_location.c.map( grid_location.grid_size as f64 + 1.0, grid_location.grid_size as f64 + 1.0, ), grid_location .c .map(0.0, grid_location.grid_size as f64 + 1.0), ]; let grid = grid_location.into_grid_image(self); res.push(crate::Grid { grid, bounds }); } res } /// Find CapStones that form a grid /// /// By trying to match up the relative perspective of 3 /// [CapStones](struct.CapStone.html) along with other criteria we can find the /// CapStones that corner the same QR code. fn find_groupings(&mut self, capstones: Vec) -> Vec where S: Clone, { let mut used_capstones = Vec::new(); let mut groups = Vec::new(); for idx in 0..capstones.len() { if used_capstones.contains(&idx) { continue; } let pairs = crate::identify::find_and_rank_possible_neighbors(&capstones, idx); for pair in pairs { if used_capstones.contains(&pair.0) || used_capstones.contains(&pair.1) { continue; } let group_under_test = CapStoneGroup( capstones[pair.0].clone(), capstones[idx].clone(), capstones[pair.1].clone(), ); // Confirm that this group has the other requirements of a QR code. // A copy of the input image is used to not contaminate the // original on an incorrect set of CapStones let mut image_copy = self.clone(); if crate::SkewedGridLocation::from_group(&mut image_copy, group_under_test.clone()) .is_none() { continue; } // This is a viable set, save this grouping groups.push(group_under_test); used_capstones.push(pair.0); used_capstones.push(idx); used_capstones.push(pair.1); } } groups } pub fn without_preparation(buf: S) -> Self { for y in 0..buf.height() { for x in 0..buf.width() { assert!(buf.get_pixel(x, y) < 2); } } PreparedImage { buffer: buf, cache: LruCache::new(NonZeroUsize::new(251).unwrap()), } } /// Return the width of the image pub fn width(&self) -> usize { self.buffer.width() } /// Return the height of the image pub fn height(&self) -> usize { self.buffer.height() } pub(crate) fn get_region(&mut self, (x, y): (usize, usize)) -> ColoredRegion { let color: PixelColor = self.buffer.get_pixel(x, y).into(); match color { PixelColor::Discarded(r) => *self.cache.get(&r).unwrap(), PixelColor::Black => { let cache_fill = self.cache.len(); let reg_idx = if cache_fill == self.cache.cap().get() { let (c, reg) = self.cache.pop_lru().expect("fill is at capacity (251)"); #[allow(clippy::single_match)] match reg { ColoredRegion::Unclaimed { src_x, src_y, color, .. } => { let _ = self.flood_fill( src_x, src_y, color.into(), PixelColor::Black.into(), |_| (), ); } _ => (), } c } else { cache_fill as u8 }; let next_reg_color = PixelColor::Discarded(reg_idx); let counter = self.repaint_and_apply((x, y), next_reg_color, AreaCounter(0)); let new_reg = ColoredRegion::Unclaimed { color: next_reg_color, src_x: x, src_y: y, pixel_count: counter.0, }; self.cache.put(reg_idx, new_reg); new_reg } PixelColor::Tmp1 => ColoredRegion::Tmp1, PixelColor::Alignment => ColoredRegion::Alignment, PixelColor::CapStone => ColoredRegion::CapStone, PixelColor::White => panic!("Tried to color white patch"), } } pub(crate) fn repaint_and_apply( &mut self, (x, y): (usize, usize), target_color: PixelColor, fill: F, ) -> F where F: AreaFiller, { let src = self.buffer.get_pixel(x, y); if PixelColor::White == src || target_color == src { panic!("Cannot repaint with white or same color"); } self.flood_fill(x, y, src, target_color.into(), fill) } pub fn get_pixel_at_point(&self, p: Point) -> PixelColor { let x = cmp::max(0, cmp::min((self.width() - 1) as i32, p.x)); let y = cmp::max(0, cmp::min((self.height() - 1) as i32, p.y)); self.buffer.get_pixel(x as usize, y as usize).into() } pub fn get_pixel_at(&self, x: usize, y: usize) -> PixelColor { self.buffer.get_pixel(x, y).into() } #[cfg(feature = "img")] pub fn write_state_to(&self, p: &str) { let mut dyn_img = image::RgbImage::new(self.width() as u32, self.height() as u32); const COLORS: [[u8; 3]; 8] = [ [255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 0], [255, 0, 255], [0, 255, 255], [128, 128, 128], [128, 0, 128], ]; for y in 0..self.height() { for x in 0..self.width() { let px = self.buffer.get_pixel(x, y); dyn_img.get_pixel_mut(x as u32, y as u32).0 = if px == 0 { [255, 255, 255] } else if px == 1 { [0, 0, 0] } else { let i = self.buffer.get_pixel(x, y) - 2; COLORS[(i % 8) as usize] } } } dyn_img.save(p).unwrap(); } fn flood_fill(&mut self, x: usize, y: usize, from: u8, to: u8, mut fill: F) -> F where F: AreaFiller, { assert_ne!(from, to); let w = self.width(); let mut queue = Vec::new(); queue.push((x, y)); while let Some((x, y)) = queue.pop() { // Bail early in case there is nothing to fill if self.buffer.get_pixel(x, y) == to || self.buffer.get_pixel(x, y) != from { continue; } let mut left = x; let mut right = x; while left > 0 && self.buffer.get_pixel(left - 1, y) == from { left -= 1; } while right < w - 1 && self.buffer.get_pixel(right + 1, y) == from { right += 1 } /* Fill the extent */ for idx in left..=right { self.buffer.set_pixel(idx, y, to); } fill.update(Row { left, right, y }); /* Seed new flood-fills */ if y > 0 { let mut seeded_previous = false; for x in left..=right { let p = self.buffer.get_pixel(x, y - 1); if p == from { if !seeded_previous { queue.push((x, y - 1)); } seeded_previous = true; } else { seeded_previous = false; } } } if y < self.height() - 1 { let mut seeded_previous = false; for x in left..=right { let p = self.buffer.get_pixel(x, y + 1); if p == from { if !seeded_previous { queue.push((x, y + 1)); } seeded_previous = true; } else { seeded_previous = false; } } } } fill } } impl PreparedImage { /// Given a function with binary output, generate a searchable image /// /// If the given function returns `true` the matching pixel will be 'black'. pub fn prepare_from_bitmap(w: usize, h: usize, mut fill: F) -> Self where F: FnMut(usize, usize) -> bool, { let capacity = w.checked_mul(h).expect("Image dimensions caused overflow"); let mut pixels = Vec::with_capacity(capacity); for y in 0..h { for x in 0..w { let col = if fill(x, y) { PixelColor::Black } else { PixelColor::White }; pixels.push(col.into()) } } let pixels = pixels.into_boxed_slice(); let buffer = BasicImageBuffer { w, h, pixels }; PreparedImage::without_preparation(buffer) } /// Given a byte valued function, generate a searchable image /// /// The values returned by the function are interpreted as luminance. i.e. a /// value of 0 is black, 255 is white. pub fn prepare_from_greyscale(w: usize, h: usize, mut fill: F) -> Self where F: FnMut(usize, usize) -> u8, { let capacity = w.checked_mul(h).expect("Image dimensions caused overflow"); let mut data = Vec::with_capacity(capacity); for y in 0..h { for x in 0..w { data.push(fill(x, y)); } } let pixels = data.into_boxed_slice(); let buffer = BasicImageBuffer { w, h, pixels }; PreparedImage::prepare(buffer) } } #[cfg(test)] mod tests { use super::*; fn img_from_array(array: [[u8; 3]; 3]) -> PreparedImage { let mut pixels = Vec::new(); for col in array.iter() { for item in col.iter() { if *item == 0 { pixels.push(0) } else { pixels.push(1) } } } let buffer = BasicImageBuffer { w: 3, h: 3, pixels: pixels.into_boxed_slice(), }; PreparedImage { buffer, cache: LruCache::new(NonZeroUsize::new(251).unwrap()), } } #[test] fn test_flood_fill_full() { let mut test_full = img_from_array([[1, 1, 1], [1, 1, 1], [1, 1, 1]]); test_full.flood_fill(0, 0, 1, 2, &mut |_| ()); for x in 0..3 { for y in 0..3 { assert_eq!(test_full.get_pixel_at(x, y), 2); } } } #[test] fn test_flood_fill_single() { let mut test_single = img_from_array([[1, 0, 1], [0, 1, 0], [1, 0, 1]]); test_single.flood_fill(1, 1, 1, 2, &mut |_| ()); for x in 0..3 { for y in 0..3 { if x == 1 && y == 1 { assert_eq!(test_single.get_pixel_at(x, y), 2); } else { let col = if (x + y) % 2 == 0 { 1 } else { 0 }; assert_eq!(test_single.get_pixel_at(x, y), col); } } } } #[test] fn test_flood_fill_ring() { let mut test_ring = img_from_array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]); test_ring.flood_fill(0, 0, 1, 2, &mut |_| ()); for x in 0..3 { for y in 0..3 { if x == 1 && y == 1 { assert_eq!(test_ring.get_pixel_at(x, y), 0); } else { assert_eq!(test_ring.get_pixel_at(x, y), 2); } } } } #[test] fn test_flood_fill_u() { let mut test_u = img_from_array([[1, 0, 1], [1, 0, 1], [1, 1, 1]]); test_u.flood_fill(0, 0, 1, 2, &mut |_| ()); for x in 0..3 { for y in 0..3 { if x == 1 && (y == 0 || y == 1) { assert_eq!(test_u.get_pixel_at(x, y), 0); } else { assert_eq!(test_u.get_pixel_at(x, y), 2); } } } } #[test] fn test_flood_fill_empty() { let mut test_empty = img_from_array([[0, 0, 0], [0, 0, 0], [0, 0, 0]]); test_empty.flood_fill(1, 1, 1, 2, &mut |_| ()); for x in 0..3 { for y in 0..3 { assert_eq!(test_empty.get_pixel_at(x, y), 0) } } } #[test] fn test_get_region() { let mut test_u = img_from_array([[1, 0, 1], [1, 0, 1], [1, 1, 1]]); let reg = test_u.get_region((0, 0)); let (color, src_x, src_y, pixel_count) = match reg { ColoredRegion::Unclaimed { color, src_x, src_y, pixel_count, } => (color, src_x, src_y, pixel_count), x => panic!("Expected Region::Unclaimed, got {:?}", x), }; assert_eq!(0, src_x); assert_eq!(0, src_y); assert_eq!(7, pixel_count); for x in 0..3 { for y in 0..3 { if x == 1 && (y == 0 || y == 1) { assert_eq!(PixelColor::White, test_u.get_pixel_at(x, y)); } else { assert_eq!(color, test_u.get_pixel_at(x, y)); } } } } } rqrr-0.9.3/src/test_data/cap.png000064400000000000000000000003101046102023000146250ustar 00000000000000PNG  IHDR٬gAMA a pHYsodjIDAT8O͑K 0Dcg+(_C} qy"ϖ#"Mv)4EE%$RMsبFfOa\̍u76hkK IENDB`rqrr-0.9.3/src/test_data/cap_connect.png000064400000000000000000000003041046102023000163410ustar 00000000000000PNG  IHDR٬gAMA a pHYsodfIDAT8OՒQ Ca33Ttc-*y,bӘY";ۈk1ṕ.$4tJlLR gV[-=- U9J6ZĸIENDB`rqrr-0.9.3/src/test_data/cap_disconnect.png000064400000000000000000000003231046102023000170420ustar 00000000000000PNG  IHDR٬gAMA a pHYsoduIDAT8O͓Q CekҎL@-U6\1vStn(@ڍJL)d0(W4D+,΍ݼ> l$/,mBy~0IENDB`rqrr-0.9.3/src/test_data/cap_size.png000064400000000000000000000003211046102023000156610ustar 00000000000000PNG  IHDRVΎWgAMA a pHYs(JsIDAT8Oݔ 0E畑!Ꝏz`:k<k}̝ύNȓCk>) OJ+9eU$+Lj"JXl=jJ@HuҿfxvvPJ%IENDB`rqrr-0.9.3/src/version_db.rs000064400000000000000000000561321046102023000141210ustar 00000000000000/* *********************************************************************** * QR-code version information database */ #[derive(Copy, Clone, Debug)] pub struct RSParameters { /// Small block size pub bs: usize, /// Small data words pub dw: usize, /// Number of small blocks pub ns: usize, } #[derive(Copy, Clone, Debug)] pub struct VersionInfo { pub data_bytes: usize, /// Alignment pattern information pub apat: [usize; 7], pub ecc: [RSParameters; 4], } pub const VERSION_DATA_BASE: [VersionInfo; 41] = [ VersionInfo { data_bytes: 0, apat: [0; 7], ecc: [RSParameters { bs: 0, dw: 0, ns: 0, }; 4], }, VersionInfo { data_bytes: 26, apat: [0, 0, 0, 0, 0, 0, 0], ecc: [ RSParameters { bs: 26, dw: 16, ns: 1, }, RSParameters { bs: 26, dw: 19, ns: 1, }, RSParameters { bs: 26, dw: 9, ns: 1, }, RSParameters { bs: 26, dw: 13, ns: 1, }, ], }, VersionInfo { data_bytes: 44, apat: [6, 18, 0, 0, 0, 0, 0], ecc: [ RSParameters { bs: 44, dw: 28, ns: 1, }, RSParameters { bs: 44, dw: 34, ns: 1, }, RSParameters { bs: 44, dw: 16, ns: 1, }, RSParameters { bs: 44, dw: 22, ns: 1, }, ], }, VersionInfo { data_bytes: 70, apat: [6, 22, 0, 0, 0, 0, 0], ecc: [ RSParameters { bs: 70, dw: 44, ns: 1, }, RSParameters { bs: 70, dw: 55, ns: 1, }, RSParameters { bs: 35, dw: 13, ns: 2, }, RSParameters { bs: 35, dw: 17, ns: 2, }, ], }, VersionInfo { data_bytes: 100, apat: [6, 26, 0, 0, 0, 0, 0], ecc: [ RSParameters { bs: 50, dw: 32, ns: 2, }, RSParameters { bs: 100, dw: 80, ns: 1, }, RSParameters { bs: 25, dw: 9, ns: 4, }, RSParameters { bs: 50, dw: 24, ns: 2, }, ], }, VersionInfo { data_bytes: 134, apat: [6, 30, 0, 0, 0, 0, 0], ecc: [ RSParameters { bs: 67, dw: 43, ns: 2, }, RSParameters { bs: 134, dw: 108, ns: 1, }, RSParameters { bs: 33, dw: 11, ns: 2, }, RSParameters { bs: 33, dw: 15, ns: 2, }, ], }, VersionInfo { data_bytes: 172, apat: [6, 34, 0, 0, 0, 0, 0], ecc: [ RSParameters { bs: 43, dw: 27, ns: 4, }, RSParameters { bs: 86, dw: 68, ns: 2, }, RSParameters { bs: 43, dw: 15, ns: 4, }, RSParameters { bs: 43, dw: 19, ns: 4, }, ], }, VersionInfo { data_bytes: 196, apat: [6, 22, 38, 0, 0, 0, 0], ecc: [ RSParameters { bs: 49, dw: 31, ns: 4, }, RSParameters { bs: 98, dw: 78, ns: 2, }, RSParameters { bs: 39, dw: 13, ns: 4, }, RSParameters { bs: 32, dw: 14, ns: 2, }, ], }, VersionInfo { data_bytes: 242, apat: [6, 24, 42, 0, 0, 0, 0], ecc: [ RSParameters { bs: 60, dw: 38, ns: 2, }, RSParameters { bs: 121, dw: 97, ns: 2, }, RSParameters { bs: 40, dw: 14, ns: 4, }, RSParameters { bs: 40, dw: 18, ns: 4, }, ], }, VersionInfo { data_bytes: 292, apat: [6, 26, 46, 0, 0, 0, 0], ecc: [ RSParameters { bs: 58, dw: 36, ns: 3, }, RSParameters { bs: 146, dw: 116, ns: 2, }, RSParameters { bs: 36, dw: 12, ns: 4, }, RSParameters { bs: 36, dw: 16, ns: 4, }, ], }, VersionInfo { data_bytes: 346, apat: [6, 28, 50, 0, 0, 0, 0], ecc: [ RSParameters { bs: 69, dw: 43, ns: 4, }, RSParameters { bs: 86, dw: 68, ns: 2, }, RSParameters { bs: 43, dw: 15, ns: 6, }, RSParameters { bs: 43, dw: 19, ns: 6, }, ], }, VersionInfo { data_bytes: 404, apat: [6, 30, 54, 0, 0, 0, 0], ecc: [ RSParameters { bs: 80, dw: 50, ns: 1, }, RSParameters { bs: 101, dw: 81, ns: 4, }, RSParameters { bs: 36, dw: 12, ns: 3, }, RSParameters { bs: 50, dw: 22, ns: 4, }, ], }, VersionInfo { data_bytes: 466, apat: [6, 32, 58, 0, 0, 0, 0], ecc: [ RSParameters { bs: 58, dw: 36, ns: 6, }, RSParameters { bs: 116, dw: 92, ns: 2, }, RSParameters { bs: 42, dw: 14, ns: 7, }, RSParameters { bs: 46, dw: 20, ns: 4, }, ], }, VersionInfo { data_bytes: 532, apat: [6, 34, 62, 0, 0, 0, 0], ecc: [ RSParameters { bs: 59, dw: 37, ns: 8, }, RSParameters { bs: 133, dw: 107, ns: 4, }, RSParameters { bs: 33, dw: 11, ns: 12, }, RSParameters { bs: 44, dw: 20, ns: 8, }, ], }, VersionInfo { data_bytes: 581, apat: [6, 26, 46, 66, 0, 0, 0], ecc: [ RSParameters { bs: 64, dw: 40, ns: 4, }, RSParameters { bs: 145, dw: 115, ns: 3, }, RSParameters { bs: 36, dw: 12, ns: 11, }, RSParameters { bs: 36, dw: 16, ns: 11, }, ], }, VersionInfo { data_bytes: 655, apat: [6, 26, 48, 70, 0, 0, 0], ecc: [ RSParameters { bs: 65, dw: 41, ns: 5, }, RSParameters { bs: 109, dw: 87, ns: 5, }, RSParameters { bs: 36, dw: 12, ns: 11, }, RSParameters { bs: 54, dw: 24, ns: 5, }, ], }, VersionInfo { data_bytes: 733, apat: [6, 26, 50, 74, 0, 0, 0], ecc: [ RSParameters { bs: 73, dw: 45, ns: 7, }, RSParameters { bs: 122, dw: 98, ns: 5, }, RSParameters { bs: 45, dw: 15, ns: 3, }, RSParameters { bs: 43, dw: 19, ns: 15, }, ], }, VersionInfo { data_bytes: 815, apat: [6, 30, 54, 78, 0, 0, 0], ecc: [ RSParameters { bs: 74, dw: 46, ns: 10, }, RSParameters { bs: 135, dw: 107, ns: 1, }, RSParameters { bs: 42, dw: 14, ns: 2, }, RSParameters { bs: 50, dw: 22, ns: 1, }, ], }, VersionInfo { data_bytes: 901, apat: [6, 30, 56, 82, 0, 0, 0], ecc: [ RSParameters { bs: 69, dw: 43, ns: 9, }, RSParameters { bs: 150, dw: 120, ns: 5, }, RSParameters { bs: 42, dw: 14, ns: 2, }, RSParameters { bs: 50, dw: 22, ns: 17, }, ], }, VersionInfo { data_bytes: 991, apat: [6, 30, 58, 86, 0, 0, 0], ecc: [ RSParameters { bs: 70, dw: 44, ns: 3, }, RSParameters { bs: 141, dw: 113, ns: 3, }, RSParameters { bs: 39, dw: 13, ns: 9, }, RSParameters { bs: 47, dw: 21, ns: 17, }, ], }, VersionInfo { data_bytes: 1085, apat: [6, 34, 62, 90, 0, 0, 0], ecc: [ RSParameters { bs: 67, dw: 41, ns: 3, }, RSParameters { bs: 135, dw: 107, ns: 3, }, RSParameters { bs: 43, dw: 15, ns: 15, }, RSParameters { bs: 54, dw: 24, ns: 15, }, ], }, VersionInfo { data_bytes: 1156, apat: [6, 28, 50, 72, 92, 0, 0], ecc: [ RSParameters { bs: 68, dw: 42, ns: 17, }, RSParameters { bs: 144, dw: 116, ns: 4, }, RSParameters { bs: 46, dw: 16, ns: 19, }, RSParameters { bs: 50, dw: 22, ns: 17, }, ], }, VersionInfo { data_bytes: 1258, apat: [6, 26, 50, 74, 98, 0, 0], ecc: [ RSParameters { bs: 74, dw: 46, ns: 17, }, RSParameters { bs: 139, dw: 111, ns: 2, }, RSParameters { bs: 37, dw: 13, ns: 34, }, RSParameters { bs: 54, dw: 24, ns: 7, }, ], }, VersionInfo { data_bytes: 1364, apat: [6, 30, 54, 78, 102, 0, 0], ecc: [ RSParameters { bs: 75, dw: 47, ns: 4, }, RSParameters { bs: 151, dw: 121, ns: 4, }, RSParameters { bs: 45, dw: 15, ns: 16, }, RSParameters { bs: 54, dw: 24, ns: 11, }, ], }, VersionInfo { data_bytes: 1474, apat: [6, 28, 54, 80, 106, 0, 0], ecc: [ RSParameters { bs: 73, dw: 45, ns: 6, }, RSParameters { bs: 147, dw: 117, ns: 6, }, RSParameters { bs: 46, dw: 16, ns: 30, }, RSParameters { bs: 54, dw: 24, ns: 11, }, ], }, VersionInfo { data_bytes: 1588, apat: [6, 32, 58, 84, 110, 0, 0], ecc: [ RSParameters { bs: 75, dw: 47, ns: 8, }, RSParameters { bs: 132, dw: 106, ns: 8, }, RSParameters { bs: 45, dw: 15, ns: 22, }, RSParameters { bs: 54, dw: 24, ns: 7, }, ], }, VersionInfo { data_bytes: 1706, apat: [6, 30, 58, 86, 114, 0, 0], ecc: [ RSParameters { bs: 74, dw: 46, ns: 19, }, RSParameters { bs: 142, dw: 114, ns: 10, }, RSParameters { bs: 46, dw: 16, ns: 33, }, RSParameters { bs: 50, dw: 22, ns: 28, }, ], }, VersionInfo { data_bytes: 1828, apat: [6, 34, 62, 90, 118, 0, 0], ecc: [ RSParameters { bs: 73, dw: 45, ns: 22, }, RSParameters { bs: 152, dw: 122, ns: 8, }, RSParameters { bs: 45, dw: 15, ns: 12, }, RSParameters { bs: 53, dw: 23, ns: 8, }, ], }, VersionInfo { data_bytes: 1921, apat: [6, 26, 50, 74, 98, 122, 0], ecc: [ RSParameters { bs: 73, dw: 45, ns: 3, }, RSParameters { bs: 147, dw: 117, ns: 3, }, RSParameters { bs: 45, dw: 15, ns: 11, }, RSParameters { bs: 54, dw: 24, ns: 4, }, ], }, VersionInfo { data_bytes: 2051, apat: [6, 30, 54, 78, 102, 126, 0], ecc: [ RSParameters { bs: 73, dw: 45, ns: 21, }, RSParameters { bs: 146, dw: 116, ns: 7, }, RSParameters { bs: 45, dw: 15, ns: 19, }, RSParameters { bs: 53, dw: 23, ns: 1, }, ], }, VersionInfo { data_bytes: 2185, apat: [6, 26, 52, 78, 104, 130, 0], ecc: [ RSParameters { bs: 75, dw: 47, ns: 19, }, RSParameters { bs: 145, dw: 115, ns: 5, }, RSParameters { bs: 45, dw: 15, ns: 23, }, RSParameters { bs: 54, dw: 24, ns: 15, }, ], }, VersionInfo { data_bytes: 2323, apat: [6, 30, 56, 82, 108, 134, 0], ecc: [ RSParameters { bs: 74, dw: 46, ns: 2, }, RSParameters { bs: 145, dw: 115, ns: 13, }, RSParameters { bs: 45, dw: 15, ns: 23, }, RSParameters { bs: 54, dw: 24, ns: 42, }, ], }, VersionInfo { data_bytes: 2465, apat: [6, 34, 60, 86, 112, 138, 0], ecc: [ RSParameters { bs: 74, dw: 46, ns: 10, }, RSParameters { bs: 145, dw: 115, ns: 17, }, RSParameters { bs: 45, dw: 15, ns: 19, }, RSParameters { bs: 54, dw: 24, ns: 10, }, ], }, VersionInfo { data_bytes: 2611, apat: [6, 30, 58, 86, 114, 142, 0], ecc: [ RSParameters { bs: 74, dw: 46, ns: 14, }, RSParameters { bs: 145, dw: 115, ns: 17, }, RSParameters { bs: 45, dw: 15, ns: 11, }, RSParameters { bs: 54, dw: 24, ns: 29, }, ], }, VersionInfo { data_bytes: 2761, apat: [6, 34, 62, 90, 118, 146, 0], ecc: [ RSParameters { bs: 74, dw: 46, ns: 14, }, RSParameters { bs: 145, dw: 115, ns: 13, }, RSParameters { bs: 46, dw: 16, ns: 59, }, RSParameters { bs: 54, dw: 24, ns: 44, }, ], }, VersionInfo { data_bytes: 2876, apat: [6, 30, 54, 78, 102, 126, 150], ecc: [ RSParameters { bs: 75, dw: 47, ns: 12, }, RSParameters { bs: 151, dw: 121, ns: 12, }, RSParameters { bs: 45, dw: 15, ns: 22, }, RSParameters { bs: 54, dw: 24, ns: 39, }, ], }, VersionInfo { data_bytes: 3034, apat: [6, 24, 50, 76, 102, 128, 154], ecc: [ RSParameters { bs: 75, dw: 47, ns: 6, }, RSParameters { bs: 151, dw: 121, ns: 6, }, RSParameters { bs: 45, dw: 15, ns: 2, }, RSParameters { bs: 54, dw: 24, ns: 46, }, ], }, VersionInfo { data_bytes: 3196, apat: [6, 28, 54, 80, 106, 132, 158], ecc: [ RSParameters { bs: 74, dw: 46, ns: 29, }, RSParameters { bs: 152, dw: 122, ns: 17, }, RSParameters { bs: 45, dw: 15, ns: 24, }, RSParameters { bs: 54, dw: 24, ns: 49, }, ], }, VersionInfo { data_bytes: 3362, apat: [6, 32, 58, 84, 110, 136, 162], ecc: [ RSParameters { bs: 74, dw: 46, ns: 13, }, RSParameters { bs: 152, dw: 122, ns: 4, }, RSParameters { bs: 45, dw: 15, ns: 42, }, RSParameters { bs: 54, dw: 24, ns: 48, }, ], }, VersionInfo { data_bytes: 3532, apat: [6, 26, 54, 82, 110, 138, 166], ecc: [ RSParameters { bs: 75, dw: 47, ns: 40, }, RSParameters { bs: 147, dw: 117, ns: 20, }, RSParameters { bs: 45, dw: 15, ns: 10, }, RSParameters { bs: 54, dw: 24, ns: 43, }, ], }, VersionInfo { data_bytes: 3706, apat: [6, 30, 58, 86, 114, 142, 170], ecc: [ RSParameters { bs: 75, dw: 47, ns: 18, }, RSParameters { bs: 148, dw: 118, ns: 19, }, RSParameters { bs: 45, dw: 15, ns: 20, }, RSParameters { bs: 54, dw: 24, ns: 34, }, ], }, ];