From 5273136c967ffa85331b6a625b85135b6522d670 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 28 Nov 2022 17:50:58 -0600 Subject: [PATCH] qrcode multi detector port --- src/multi/qrcode/detector/mod.rs | 2 +- src/multi/qrcode/detector/multi_detector.rs | 71 +-- .../detector/multi_finder_pattern_finder.rs | 496 ++++++++++-------- src/qrcode/detector/finder_pattern_finder.rs | 6 +- 4 files changed, 314 insertions(+), 261 deletions(-) diff --git a/src/multi/qrcode/detector/mod.rs b/src/multi/qrcode/detector/mod.rs index c371147..d88172e 100644 --- a/src/multi/qrcode/detector/mod.rs +++ b/src/multi/qrcode/detector/mod.rs @@ -2,4 +2,4 @@ mod multi_detector; pub use multi_detector::*; mod multi_finder_pattern_finder; -pub use multi_finder_pattern_finder::*; \ No newline at end of file +pub use multi_finder_pattern_finder::*; diff --git a/src/multi/qrcode/detector/multi_detector.rs b/src/multi/qrcode/detector/multi_detector.rs index a89ed6b..60aff22 100644 --- a/src/multi/qrcode/detector/multi_detector.rs +++ b/src/multi/qrcode/detector/multi_detector.rs @@ -14,7 +14,11 @@ * limitations under the License. */ -use crate::{qrcode::detector::{Detector, QRCodeDetectorResult}, common::{BitMatrix, DetectorRXingResult}, DecodingHintDictionary, Exceptions, DecodeHintType}; +use crate::{ + common::{BitMatrix, DetectorRXingResult}, + qrcode::detector::{Detector, QRCodeDetectorResult}, + DecodeHintType, DecodeHintValue, DecodingHintDictionary, Exceptions, +}; use super::MultiFinderPatternFinder; @@ -27,36 +31,43 @@ use super::MultiFinderPatternFinder; */ pub struct MultiDetector(Detector); impl MultiDetector { - pub fn new(image: BitMatrix) -> Self { - Self(Detector::new(image)) - } - - // private static final DetectorRXingResult[] EMPTY_DETECTOR_RESULTS = new DetectorRXingResult[0]; - - pub fn detectMulti(&self, hints:&DecodingHintDictionary) -> Result,Exceptions> { - let image = self.0.getImage(); - let resultPointCallback = - hints.get(&DecodeHintType::NEED_RESULT_POINT_CALLBACK); - let finder = MultiFinderPatternFinder::new(image, resultPointCallback); - let infos = finder.findMulti(hints)?; - - if infos.len() == 0 { - return Err(Exceptions::NotFoundException("".to_owned())) + pub fn new(image: BitMatrix) -> Self { + Self(Detector::new(image)) } - let result = Vec::new(); - for info in infos { - if let Ok(potential) = self.0.processFinderPatternInfo(info){ - result.push(potential); - } - // try { - // result.add(processFinderPatternInfo(info)); - // } catch (ReaderException e) { - // // ignore - // } - } - - Ok(result) - } + // private static final DetectorRXingResult[] EMPTY_DETECTOR_RESULTS = new DetectorRXingResult[0]; + pub fn detectMulti( + &self, + hints: &DecodingHintDictionary, + ) -> Result, Exceptions> { + let image = self.0.getImage(); + let resultPointCallback = if let Some(DecodeHintValue::NeedResultPointCallback(cb)) = + hints.get(&DecodeHintType::NEED_RESULT_POINT_CALLBACK) + { + Some(*cb) + } else { + None + }; + let mut finder = MultiFinderPatternFinder::new(image, resultPointCallback); + let infos = finder.findMulti(hints)?; + + if infos.len() == 0 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + let mut result = Vec::new(); + for info in infos { + if let Ok(potential) = self.0.processFinderPatternInfo(info) { + result.push(potential); + } + // try { + // result.add(processFinderPatternInfo(info)); + // } catch (ReaderException e) { + // // ignore + // } + } + + Ok(result) + } } diff --git a/src/multi/qrcode/detector/multi_finder_pattern_finder.rs b/src/multi/qrcode/detector/multi_finder_pattern_finder.rs index 5aad222..d93861c 100644 --- a/src/multi/qrcode/detector/multi_finder_pattern_finder.rs +++ b/src/multi/qrcode/detector/multi_finder_pattern_finder.rs @@ -14,27 +14,33 @@ * limitations under the License. */ -use crate::{qrcode::detector::{FinderPatternFinder, FinderPattern, FinderPatternInfo}, common::BitMatrix, RXingResultPointCallback, Exceptions, DecodingHintDictionary}; +use std::cmp::Ordering; + +use crate::{ + common::BitMatrix, + qrcode::detector::{FinderPattern, FinderPatternFinder, FinderPatternInfo}, + result_point_utils, DecodeHintType, DecodingHintDictionary, Exceptions, RXingResultPoint, + RXingResultPointCallback, +}; // max. legal count of modules per QR code edge (177) -const MAX_MODULE_COUNT_PER_EDGE : f32 = 180_f32; +const MAX_MODULE_COUNT_PER_EDGE: f32 = 180_f32; // min. legal count per modules per QR code edge (11) -const MIN_MODULE_COUNT_PER_EDGE : f32= 9_f32; +const MIN_MODULE_COUNT_PER_EDGE: f32 = 9_f32; +/** + * More or less arbitrary cutoff point for determining if two finder patterns might belong + * to the same code if they differ less than DIFF_MODSIZE_CUTOFF_PERCENT percent in their + * estimated modules sizes. + */ +const DIFF_MODSIZE_CUTOFF_PERCENT: f32 = 0.05_f32; - /** - * More or less arbitrary cutoff point for determining if two finder patterns might belong - * to the same code if they differ less than DIFF_MODSIZE_CUTOFF_PERCENT percent in their - * estimated modules sizes. - */ - const DIFF_MODSIZE_CUTOFF_PERCENT : f32= 0.05_f32; - - /** - * More or less arbitrary cutoff point for determining if two finder patterns might belong - * to the same code if they differ less than DIFF_MODSIZE_CUTOFF pixels/module in their - * estimated modules sizes. - */ - const DIFF_MODSIZE_CUTOFF :f32= 0.5_f32; +/** + * More or less arbitrary cutoff point for determining if two finder patterns might belong + * to the same code if they differ less than DIFF_MODSIZE_CUTOFF pixels/module in their + * estimated modules sizes. + */ +const DIFF_MODSIZE_CUTOFF: f32 = 0.5_f32; /** *

This class attempts to find finder patterns in a QR Code. Finder patterns are the square @@ -53,226 +59,262 @@ const MIN_MODULE_COUNT_PER_EDGE : f32= 9_f32; pub struct MultiFinderPatternFinder(FinderPatternFinder); impl MultiFinderPatternFinder { + // private static final FinderPatternInfo[] EMPTY_RESULT_ARRAY = new FinderPatternInfo[0]; + // private static final FinderPattern[] EMPTY_FP_ARRAY = new FinderPattern[0]; + // private static final FinderPattern[][] EMPTY_FP_2D_ARRAY = new FinderPattern[0][]; - // private static final FinderPatternInfo[] EMPTY_RESULT_ARRAY = new FinderPatternInfo[0]; - // private static final FinderPattern[] EMPTY_FP_ARRAY = new FinderPattern[0]; - // private static final FinderPattern[][] EMPTY_FP_2D_ARRAY = new FinderPattern[0][]; + // TODO MIN_MODULE_COUNT and MAX_MODULE_COUNT would be great hints to ask the user for + // since it limits the number of regions to decode - // TODO MIN_MODULE_COUNT and MAX_MODULE_COUNT would be great hints to ask the user for - // since it limits the number of regions to decode - - - - // /** - // * A comparator that orders FinderPatterns by their estimated module size. - // */ - // private static final class ModuleSizeComparator implements Comparator, Serializable { - // @Override - // public int compare(FinderPattern center1, FinderPattern center2) { - // float value = center2.getEstimatedModuleSize() - center1.getEstimatedModuleSize(); - // return value < 0.0 ? -1 : value > 0.0 ? 1 : 0; - // } - // } - - pub fn new( image:&BitMatrix, resultPointCallback:&RXingResultPointCallback) -> Self { - Self(FinderPatternFinder::with_callback(image, resultPointCallback)) - } - - /** - * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are - * those that have been detected at least 2 times, and whose module - * size differs from the average among those patterns the least - * @throws NotFoundException if 3 such finder patterns do not exist - */ - fn selectMultipleBestPatterns(&self) -> Result>,Exceptions> { - List possibleCenters = new ArrayList<>(); - for (FinderPattern fp : getPossibleCenters()) { - if (fp.getCount() >= 2) { - possibleCenters.add(fp); - } - } - int size = possibleCenters.size(); - - if (size < 3) { - // Couldn't find enough finder patterns - throw NotFoundException.getNotFoundInstance(); + pub fn new(image: &BitMatrix, resultPointCallback: Option) -> Self { + Self(FinderPatternFinder::with_callback( + image.clone(), + resultPointCallback, + )) } - /* - * Begin HE modifications to safely detect multiple codes of equal size + /** + * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are + * those that have been detected at least 2 times, and whose module + * size differs from the average among those patterns the least + * @throws NotFoundException if 3 such finder patterns do not exist */ - if (size == 3) { - return new FinderPattern[][] { possibleCenters.toArray(EMPTY_FP_ARRAY) }; - } - - // Sort by estimated module size to speed up the upcoming checks - Collections.sort(possibleCenters, new ModuleSizeComparator()); - - /* - * Now lets start: build a list of tuples of three finder locations that - * - feature similar module sizes - * - are placed in a distance so the estimated module count is within the QR specification - * - have similar distance between upper left/right and left top/bottom finder patterns - * - form a triangle with 90° angle (checked by comparing top right/bottom left distance - * with pythagoras) - * - * Note: we allow each point to be used for more than one code region: this might seem - * counterintuitive at first, but the performance penalty is not that big. At this point, - * we cannot make a good quality decision whether the three finders actually represent - * a QR code, or are just by chance laid out so it looks like there might be a QR code there. - * So, if the layout seems right, lets have the decoder try to decode. - */ - - List results = new ArrayList<>(); // holder for the results - - for (int i1 = 0; i1 < (size - 2); i1++) { - FinderPattern p1 = possibleCenters.get(i1); - if (p1 == null) { - continue; - } - - for (int i2 = i1 + 1; i2 < (size - 1); i2++) { - FinderPattern p2 = possibleCenters.get(i2); - if (p2 == null) { - continue; - } - - // Compare the expected module sizes; if they are really off, skip - float vModSize12 = (p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize()) / - Math.min(p1.getEstimatedModuleSize(), p2.getEstimatedModuleSize()); - float vModSize12A = Math.abs(p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize()); - if (vModSize12A > DIFF_MODSIZE_CUTOFF && vModSize12 >= DIFF_MODSIZE_CUTOFF_PERCENT) { - // break, since elements are ordered by the module size deviation there cannot be - // any more interesting elements for the given p1. - break; - } - - for (int i3 = i2 + 1; i3 < size; i3++) { - FinderPattern p3 = possibleCenters.get(i3); - if (p3 == null) { - continue; - } - - // Compare the expected module sizes; if they are really off, skip - float vModSize23 = (p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize()) / - Math.min(p2.getEstimatedModuleSize(), p3.getEstimatedModuleSize()); - float vModSize23A = Math.abs(p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize()); - if (vModSize23A > DIFF_MODSIZE_CUTOFF && vModSize23 >= DIFF_MODSIZE_CUTOFF_PERCENT) { - // break, since elements are ordered by the module size deviation there cannot be - // any more interesting elements for the given p1. - break; - } - - FinderPattern[] test = {p1, p2, p3}; - RXingResultPoint.orderBestPatterns(test); - - // Calculate the distances: a = topleft-bottomleft, b=topleft-topright, c = diagonal - FinderPatternInfo info = new FinderPatternInfo(test); - float dA = RXingResultPoint.distance(info.getTopLeft(), info.getBottomLeft()); - float dC = RXingResultPoint.distance(info.getTopRight(), info.getBottomLeft()); - float dB = RXingResultPoint.distance(info.getTopLeft(), info.getTopRight()); - - // Check the sizes - float estimatedModuleCount = (dA + dB) / (p1.getEstimatedModuleSize() * 2.0f); - if (estimatedModuleCount > MAX_MODULE_COUNT_PER_EDGE || - estimatedModuleCount < MIN_MODULE_COUNT_PER_EDGE) { - continue; - } - - // Calculate the difference of the edge lengths in percent - float vABBC = Math.abs((dA - dB) / Math.min(dA, dB)); - if (vABBC >= 0.1f) { - continue; - } - - // Calculate the diagonal length by assuming a 90° angle at topleft - float dCpy = (float) Math.sqrt((double) dA * dA + (double) dB * dB); - // Compare to the real distance in % - float vPyC = Math.abs((dC - dCpy) / Math.min(dC, dCpy)); - - if (vPyC >= 0.1f) { - continue; - } - - // All tests passed! - results.add(test); - } - } - } - - if (!results.isEmpty()) { - return results.toArray(EMPTY_FP_2D_ARRAY); - } - - // Nothing found! - throw NotFoundException.getNotFoundInstance(); - } - - pub fn findMulti(&self, hints:&DecodingHintDictionary) -> Result,Exceptions> { - boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER); - BitMatrix image = getImage(); - int maxI = image.getHeight(); - int maxJ = image.getWidth(); - // We are looking for black/white/black/white/black modules in - // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far - - // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the - // image, and then account for the center being 3 modules in size. This gives the smallest - // number of pixels the center could be, so skip this often. When trying harder, look for all - // QR versions regardless of how dense they are. - int iSkip = (3 * maxI) / (4 * MAX_MODULES); - if (iSkip < MIN_SKIP || tryHarder) { - iSkip = MIN_SKIP; - } - - int[] stateCount = new int[5]; - for (int i = iSkip - 1; i < maxI; i += iSkip) { - // Get a row of black/white values - doClearCounts(stateCount); - int currentState = 0; - for (int j = 0; j < maxJ; j++) { - if (image.get(j, i)) { - // Black pixel - if ((currentState & 1) == 1) { // Counting white pixels - currentState++; - } - stateCount[currentState]++; - } else { // White pixel - if ((currentState & 1) == 0) { // Counting black pixels - if (currentState == 4) { // A winner? - if (foundPatternCross(stateCount) && handlePossibleCenter(stateCount, i, j)) { // Yes - // Clear state to start looking again - currentState = 0; - doClearCounts(stateCount); - } else { // No, shift counts back by two - doShiftCounts2(stateCount); - currentState = 3; - } - } else { - stateCount[++currentState]++; + fn selectMultipleBestPatterns(&self) -> Result, Exceptions> { + let mut possibleCenters = Vec::new(); //new ArrayList<>(); + for fp in self.0.getPossibleCenters() { + if fp.getCount() >= 2 { + possibleCenters.push(*fp); } - } else { // Counting white pixels - stateCount[currentState]++; - } } - } // for j=... + let size = possibleCenters.len(); - if (foundPatternCross(stateCount)) { - handlePossibleCenter(stateCount, i, maxJ); - } - } // for i=iSkip-1 ... - FinderPattern[][] patternInfo = selectMultipleBestPatterns(); - List result = new ArrayList<>(); - for (FinderPattern[] pattern : patternInfo) { - RXingResultPoint.orderBestPatterns(pattern); - result.add(new FinderPatternInfo(pattern)); + if size < 3 { + // Couldn't find enough finder patterns + return Err(Exceptions::NotFoundException( + "Couldn't find enough finder patterns".to_owned(), + )); + } + + /* + * Begin HE modifications to safely detect multiple codes of equal size + */ + if size == 3 { + return Ok(vec![[ + possibleCenters[0], + possibleCenters[1], + possibleCenters[2], + ]]); + } + + // Sort by estimated module size to speed up the upcoming checks + possibleCenters.sort_by(compare_finder_patterns); + // Collections.sort(possibleCenters, new ModuleSizeComparator()); + + /* + * Now lets start: build a list of tuples of three finder locations that + * - feature similar module sizes + * - are placed in a distance so the estimated module count is within the QR specification + * - have similar distance between upper left/right and left top/bottom finder patterns + * - form a triangle with 90° angle (checked by comparing top right/bottom left distance + * with pythagoras) + * + * Note: we allow each point to be used for more than one code region: this might seem + * counterintuitive at first, but the performance penalty is not that big. At this point, + * we cannot make a good quality decision whether the three finders actually represent + * a QR code, or are just by chance laid out so it looks like there might be a QR code there. + * So, if the layout seems right, lets have the decoder try to decode. + */ + + let mut results = Vec::new(); //new ArrayList<>(); // holder for the results + + for i1 in 0..(size - 2) { + // for (int i1 = 0; i1 < (size - 2); i1++) { + let Some(p1) = possibleCenters.get(i1)else { + continue; + }; + + for i2 in (i1 + 1)..(size - 1) { + // for (int i2 = i1 + 1; i2 < (size - 1); i2++) { + let Some(p2) = possibleCenters.get(i2) else { + continue; + }; + + // Compare the expected module sizes; if they are really off, skip + let vModSize12 = (p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize()) + / p1.getEstimatedModuleSize().min(p2.getEstimatedModuleSize()); + let vModSize12A = (p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize()).abs(); + if vModSize12A > DIFF_MODSIZE_CUTOFF && vModSize12 >= DIFF_MODSIZE_CUTOFF_PERCENT { + // break, since elements are ordered by the module size deviation there cannot be + // any more interesting elements for the given p1. + break; + } + + for i3 in (i2 + 1)..size { + // for (int i3 = i2 + 1; i3 < size; i3++) { + let Some( p3) = possibleCenters.get(i3)else { + continue; + }; + + // Compare the expected module sizes; if they are really off, skip + let vModSize23 = (p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize()) + / p2.getEstimatedModuleSize().min(p3.getEstimatedModuleSize()); + let vModSize23A = + (p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize()).abs(); + if vModSize23A > DIFF_MODSIZE_CUTOFF + && vModSize23 >= DIFF_MODSIZE_CUTOFF_PERCENT + { + // break, since elements are ordered by the module size deviation there cannot be + // any more interesting elements for the given p1. + break; + } + + let mut test = [*p1, *p2, *p3]; + result_point_utils::orderBestPatterns(&mut test); + + // Calculate the distances: a = topleft-bottomleft, b=topleft-topright, c = diagonal + let info = FinderPatternInfo::new(test); + let dA = result_point_utils::distance(info.getTopLeft(), info.getBottomLeft()); + let dC = result_point_utils::distance(info.getTopRight(), info.getBottomLeft()); + let dB = result_point_utils::distance(info.getTopLeft(), info.getTopRight()); + + // Check the sizes + let estimatedModuleCount = (dA + dB) / (p1.getEstimatedModuleSize() * 2.0); + if estimatedModuleCount > MAX_MODULE_COUNT_PER_EDGE + || estimatedModuleCount < MIN_MODULE_COUNT_PER_EDGE + { + continue; + } + + // Calculate the difference of the edge lengths in percent + let vABBC = ((dA - dB) / dA.min(dB)).abs(); + if vABBC >= 0.1 { + continue; + } + + // Calculate the diagonal length by assuming a 90° angle at topleft + let dCpy = + ((dA as f64) * (dA as f64) + (dB as f64) * (dB as f64)).sqrt() as f32; + // Compare to the real distance in % + let vPyC = ((dC - dCpy) / dC.min(dCpy)).abs(); + + if vPyC >= 0.1 { + continue; + } + + // All tests passed! + results.push(test); + } + } + } + + if !results.is_empty() { + Ok(results) + } else { + Err(Exceptions::NotFoundException("no result".to_owned())) + } } - if (result.isEmpty()) { - return EMPTY_RESULT_ARRAY; - } else { - return result.toArray(EMPTY_RESULT_ARRAY); - } - } + pub fn findMulti( + &mut self, + hints: &DecodingHintDictionary, + ) -> Result, Exceptions> { + let tryHarder = hints.contains_key(&DecodeHintType::TRY_HARDER); + let image = self.0.getImage().clone(); + let maxI = image.getHeight(); + let maxJ = image.getWidth(); + // We are looking for black/white/black/white/black modules in + // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far + // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the + // image, and then account for the center being 3 modules in size. This gives the smallest + // number of pixels the center could be, so skip this often. When trying harder, look for all + // QR versions regardless of how dense they are. + let mut iSkip = (3 * maxI) / (4 * FinderPatternFinder::MAX_MODULES); + if iSkip < FinderPatternFinder::MIN_SKIP || tryHarder { + iSkip = FinderPatternFinder::MIN_SKIP; + } + + let mut stateCount = [0_u32; 5]; //new int[5]; + let mut i = iSkip - 1; + while i < maxI { + // for (int i = iSkip - 1; i < maxI; i += iSkip) { + // Get a row of black/white values + FinderPatternFinder::doClearCounts(&mut stateCount); + let mut currentState = 0; + for j in 0..maxJ { + // for (int j = 0; j < maxJ; j++) { + if image.get(j, i) { + // Black pixel + if (currentState & 1) == 1 { + // Counting white pixels + currentState += 1; + } + stateCount[currentState] += 1; + } else { + // White pixel + if (currentState & 1) == 0 { + // Counting black pixels + if currentState == 4 { + // A winner? + if FinderPatternFinder::foundPatternCross(&stateCount) + && self.0.handlePossibleCenter(&stateCount, i, j) + { + // Yes + // Clear state to start looking again + currentState = 0; + FinderPatternFinder::doClearCounts(&mut stateCount); + } else { + // No, shift counts back by two + FinderPatternFinder::doShiftCounts2(&mut stateCount); + currentState = 3; + } + } else { + currentState += 1; + stateCount[currentState] += 1; + } + } else { + // Counting white pixels + stateCount[currentState] += 1; + } + } + } // for j=... + + if FinderPatternFinder::foundPatternCross(&stateCount) { + self.0.handlePossibleCenter(&stateCount, i, maxJ); + } + + i += iSkip; + } // for i=iSkip-1 ... + let mut patternInfo = self.selectMultipleBestPatterns()?; + let mut result = Vec::new(); //new ArrayList<>(); + for pattern in patternInfo.iter_mut() { + result_point_utils::orderBestPatterns(pattern); + result.push(FinderPatternInfo::new(*pattern)); + } + + // if result.isEmpty() { + // return EMPTY_RESULT_ARRAY; + // } else { + // return result.toArray(EMPTY_RESULT_ARRAY); + // } + Ok(result) + } } + +/** + * A comparator that orders FinderPatterns by their estimated module size. + */ +// private static final class ModuleSizeComparator implements Comparator, Serializable { +// @Override +fn compare_finder_patterns(center1: &FinderPattern, center2: &FinderPattern) -> Ordering { + let value = center2.getEstimatedModuleSize() - center1.getEstimatedModuleSize(); + if value < 0.0 { + Ordering::Less + } else if value > 0.0 { + Ordering::Greater + } else { + Ordering::Equal + } + // return value < 0.0 ? -1 : value > 0.0 ? 1 : 0; +} +// } diff --git a/src/qrcode/detector/finder_pattern_finder.rs b/src/qrcode/detector/finder_pattern_finder.rs index 2762377..3bf23d1 100755 --- a/src/qrcode/detector/finder_pattern_finder.rs +++ b/src/qrcode/detector/finder_pattern_finder.rs @@ -37,10 +37,10 @@ pub struct FinderPatternFinder { resultPointCallback: Option, } impl FinderPatternFinder { - const CENTER_QUORUM: usize = 2; + pub const CENTER_QUORUM: usize = 2; // private static final EstimatedModuleComparator moduleComparator = new EstimatedModuleComparator(); - const MIN_SKIP: u32 = 3; // 1 pixel/module times 3 modules/center - const MAX_MODULES: u32 = 97; // support up to version 20 for mobile clients + pub const MIN_SKIP: u32 = 3; // 1 pixel/module times 3 modules/center + pub const MAX_MODULES: u32 = 97; // support up to version 20 for mobile clients /** *

Creates a finder that will search the image for three finder patterns.