From 50a675c693412f37c73845725d31d95abfec6027 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sat, 8 Oct 2022 10:48:16 -0500 Subject: [PATCH] detector ported, no tests --- src/lib.rs | 15 +- src/qrcode/detector/Detector.java | 405 --------------- src/qrcode/detector/alignment_pattern.rs | 4 + src/qrcode/detector/detector.rs | 488 ++++++++++++++++++ src/qrcode/detector/finder_pattern.rs | 8 +- src/qrcode/detector/mod.rs | 6 +- src/qrcode/detector/qrcode_detector_result.rs | 28 + 7 files changed, 539 insertions(+), 415 deletions(-) delete mode 100644 src/qrcode/detector/Detector.java create mode 100644 src/qrcode/detector/detector.rs create mode 100644 src/qrcode/detector/qrcode_detector_result.rs diff --git a/src/lib.rs b/src/lib.rs index 15b8bb3..d237686 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1176,6 +1176,7 @@ use crate::common::detector::MathUtils; pub trait ResultPoint { fn getX(&self) -> f32; fn getY(&self) -> f32; + fn into_rxing_result_point(self) -> RXingResultPoint; } pub mod result_point_utils { @@ -1187,7 +1188,7 @@ pub mod result_point_utils { * * @param patterns array of three {@code RXingResultPoint} to order */ - pub fn orderBestPatterns(patterns: &mut [T; 3]) { + pub fn orderBestPatterns(patterns: &mut [T; 3]) { // Find distances between pattern centers let zeroOneDistance = MathUtils::distance_float( patterns[0].getX(), @@ -1250,7 +1251,7 @@ pub mod result_point_utils { * @param pattern2 second pattern * @return distance between two points */ - pub fn distance(pattern1: T, pattern2: T) -> f32 { + pub fn distance(pattern1: &T, pattern2: &T) -> f32 { return MathUtils::distance_float( pattern1.getX(), pattern1.getY(), @@ -1262,11 +1263,7 @@ pub mod result_point_utils { /** * Returns the z component of the cross product between vectors BC and BA. */ - pub fn crossProductZ( - pointA: T, - pointB: T, - pointC: T, - ) -> f32 { + pub fn crossProductZ(pointA: T, pointB: T, pointC: T) -> f32 { let bX = pointB.getX(); let bY = pointB.getY(); return ((pointC.getX() - bX) * (pointA.getY() - bY)) @@ -1311,6 +1308,10 @@ impl ResultPoint for RXingResultPoint { fn getY(&self) -> f32 { return self.y; } + + fn into_rxing_result_point(self) -> RXingResultPoint { + self + } } impl fmt::Display for RXingResultPoint { diff --git a/src/qrcode/detector/Detector.java b/src/qrcode/detector/Detector.java deleted file mode 100644 index 0ad67ea..0000000 --- a/src/qrcode/detector/Detector.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.qrcode.detector; - -import com.google.zxing.DecodeHintType; -import com.google.zxing.FormatException; -import com.google.zxing.NotFoundException; -import com.google.zxing.RXingResultPoint; -import com.google.zxing.RXingResultPointCallback; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.common.DetectorRXingResult; -import com.google.zxing.common.GridSampler; -import com.google.zxing.common.PerspectiveTransform; -import com.google.zxing.common.detector.MathUtils; -import com.google.zxing.qrcode.decoder.Version; - -import java.util.Map; - -/** - *

Encapsulates logic that can detect a QR Code in an image, even if the QR Code - * is rotated or skewed, or partially obscured.

- * - * @author Sean Owen - */ -public class Detector { - - private final BitMatrix image; - private RXingResultPointCallback resultPointCallback; - - public Detector(BitMatrix image) { - this.image = image; - } - - protected final BitMatrix getImage() { - return image; - } - - protected final RXingResultPointCallback getRXingResultPointCallback() { - return resultPointCallback; - } - - /** - *

Detects a QR Code in an image.

- * - * @return {@link DetectorRXingResult} encapsulating results of detecting a QR Code - * @throws NotFoundException if QR Code cannot be found - * @throws FormatException if a QR Code cannot be decoded - */ - public DetectorRXingResult detect() throws NotFoundException, FormatException { - return detect(null); - } - - /** - *

Detects a QR Code in an image.

- * - * @param hints optional hints to detector - * @return {@link DetectorRXingResult} encapsulating results of detecting a QR Code - * @throws NotFoundException if QR Code cannot be found - * @throws FormatException if a QR Code cannot be decoded - */ - public final DetectorRXingResult detect(Map hints) throws NotFoundException, FormatException { - - resultPointCallback = hints == null ? null : - (RXingResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); - - FinderPatternFinder finder = new FinderPatternFinder(image, resultPointCallback); - FinderPatternInfo info = finder.find(hints); - - return processFinderPatternInfo(info); - } - - protected final DetectorRXingResult processFinderPatternInfo(FinderPatternInfo info) - throws NotFoundException, FormatException { - - FinderPattern topLeft = info.getTopLeft(); - FinderPattern topRight = info.getTopRight(); - FinderPattern bottomLeft = info.getBottomLeft(); - - float moduleSize = calculateModuleSize(topLeft, topRight, bottomLeft); - if (moduleSize < 1.0f) { - throw NotFoundException.getNotFoundInstance(); - } - int dimension = computeDimension(topLeft, topRight, bottomLeft, moduleSize); - Version provisionalVersion = Version.getProvisionalVersionForDimension(dimension); - int modulesBetweenFPCenters = provisionalVersion.getDimensionForVersion() - 7; - - AlignmentPattern alignmentPattern = null; - // Anything above version 1 has an alignment pattern - if (provisionalVersion.getAlignmentPatternCenters().length > 0) { - - // Guess where a "bottom right" finder pattern would have been - float bottomRightX = topRight.getX() - topLeft.getX() + bottomLeft.getX(); - float bottomRightY = topRight.getY() - topLeft.getY() + bottomLeft.getY(); - - // Estimate that alignment pattern is closer by 3 modules - // from "bottom right" to known top left location - float correctionToTopLeft = 1.0f - 3.0f / modulesBetweenFPCenters; - int estAlignmentX = (int) (topLeft.getX() + correctionToTopLeft * (bottomRightX - topLeft.getX())); - int estAlignmentY = (int) (topLeft.getY() + correctionToTopLeft * (bottomRightY - topLeft.getY())); - - // Kind of arbitrary -- expand search radius before giving up - for (int i = 4; i <= 16; i <<= 1) { - try { - alignmentPattern = findAlignmentInRegion(moduleSize, - estAlignmentX, - estAlignmentY, - i); - break; - } catch (NotFoundException re) { - // try next round - } - } - // If we didn't find alignment pattern... well try anyway without it - } - - PerspectiveTransform transform = - createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension); - - BitMatrix bits = sampleGrid(image, transform, dimension); - - RXingResultPoint[] points; - if (alignmentPattern == null) { - points = new RXingResultPoint[]{bottomLeft, topLeft, topRight}; - } else { - points = new RXingResultPoint[]{bottomLeft, topLeft, topRight, alignmentPattern}; - } - return new DetectorRXingResult(bits, points); - } - - private static PerspectiveTransform createTransform(RXingResultPoint topLeft, - RXingResultPoint topRight, - RXingResultPoint bottomLeft, - RXingResultPoint alignmentPattern, - int dimension) { - float dimMinusThree = dimension - 3.5f; - float bottomRightX; - float bottomRightY; - float sourceBottomRightX; - float sourceBottomRightY; - if (alignmentPattern != null) { - bottomRightX = alignmentPattern.getX(); - bottomRightY = alignmentPattern.getY(); - sourceBottomRightX = dimMinusThree - 3.0f; - sourceBottomRightY = sourceBottomRightX; - } else { - // Don't have an alignment pattern, just make up the bottom-right point - bottomRightX = (topRight.getX() - topLeft.getX()) + bottomLeft.getX(); - bottomRightY = (topRight.getY() - topLeft.getY()) + bottomLeft.getY(); - sourceBottomRightX = dimMinusThree; - sourceBottomRightY = dimMinusThree; - } - - return PerspectiveTransform.quadrilateralToQuadrilateral( - 3.5f, - 3.5f, - dimMinusThree, - 3.5f, - sourceBottomRightX, - sourceBottomRightY, - 3.5f, - dimMinusThree, - topLeft.getX(), - topLeft.getY(), - topRight.getX(), - topRight.getY(), - bottomRightX, - bottomRightY, - bottomLeft.getX(), - bottomLeft.getY()); - } - - private static BitMatrix sampleGrid(BitMatrix image, - PerspectiveTransform transform, - int dimension) throws NotFoundException { - - GridSampler sampler = GridSampler.getInstance(); - return sampler.sampleGrid(image, dimension, dimension, transform); - } - - /** - *

Computes the dimension (number of modules on a size) of the QR Code based on the position - * of the finder patterns and estimated module size.

- */ - private static int computeDimension(RXingResultPoint topLeft, - RXingResultPoint topRight, - RXingResultPoint bottomLeft, - float moduleSize) throws NotFoundException { - int tltrCentersDimension = MathUtils.round(RXingResultPoint.distance(topLeft, topRight) / moduleSize); - int tlblCentersDimension = MathUtils.round(RXingResultPoint.distance(topLeft, bottomLeft) / moduleSize); - int dimension = ((tltrCentersDimension + tlblCentersDimension) / 2) + 7; - switch (dimension & 0x03) { // mod 4 - case 0: - dimension++; - break; - // 1? do nothing - case 2: - dimension--; - break; - case 3: - throw NotFoundException.getNotFoundInstance(); - } - return dimension; - } - - /** - *

Computes an average estimated module size based on estimated derived from the positions - * of the three finder patterns.

- * - * @param topLeft detected top-left finder pattern center - * @param topRight detected top-right finder pattern center - * @param bottomLeft detected bottom-left finder pattern center - * @return estimated module size - */ - protected final float calculateModuleSize(RXingResultPoint topLeft, - RXingResultPoint topRight, - RXingResultPoint bottomLeft) { - // Take the average - return (calculateModuleSizeOneWay(topLeft, topRight) + - calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0f; - } - - /** - *

Estimates module size based on two finder patterns -- it uses - * {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the - * width of each, measuring along the axis between their centers.

- */ - private float calculateModuleSizeOneWay(RXingResultPoint pattern, RXingResultPoint otherPattern) { - float moduleSizeEst1 = sizeOfBlackWhiteBlackRunBothWays((int) pattern.getX(), - (int) pattern.getY(), - (int) otherPattern.getX(), - (int) otherPattern.getY()); - float moduleSizeEst2 = sizeOfBlackWhiteBlackRunBothWays((int) otherPattern.getX(), - (int) otherPattern.getY(), - (int) pattern.getX(), - (int) pattern.getY()); - if (Float.isNaN(moduleSizeEst1)) { - return moduleSizeEst2 / 7.0f; - } - if (Float.isNaN(moduleSizeEst2)) { - return moduleSizeEst1 / 7.0f; - } - // Average them, and divide by 7 since we've counted the width of 3 black modules, - // and 1 white and 1 black module on either side. Ergo, divide sum by 14. - return (moduleSizeEst1 + moduleSizeEst2) / 14.0f; - } - - /** - * See {@link #sizeOfBlackWhiteBlackRun(int, int, int, int)}; computes the total width of - * a finder pattern by looking for a black-white-black run from the center in the direction - * of another point (another finder pattern center), and in the opposite direction too. - */ - private float sizeOfBlackWhiteBlackRunBothWays(int fromX, int fromY, int toX, int toY) { - - float result = sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY); - - // Now count other way -- don't run off image though of course - float scale = 1.0f; - int otherToX = fromX - (toX - fromX); - if (otherToX < 0) { - scale = fromX / (float) (fromX - otherToX); - otherToX = 0; - } else if (otherToX >= image.getWidth()) { - scale = (image.getWidth() - 1 - fromX) / (float) (otherToX - fromX); - otherToX = image.getWidth() - 1; - } - int otherToY = (int) (fromY - (toY - fromY) * scale); - - scale = 1.0f; - if (otherToY < 0) { - scale = fromY / (float) (fromY - otherToY); - otherToY = 0; - } else if (otherToY >= image.getHeight()) { - scale = (image.getHeight() - 1 - fromY) / (float) (otherToY - fromY); - otherToY = image.getHeight() - 1; - } - otherToX = (int) (fromX + (otherToX - fromX) * scale); - - result += sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY); - - // Middle pixel is double-counted this way; subtract 1 - return result - 1.0f; - } - - /** - *

This method traces a line from a point in the image, in the direction towards another point. - * It begins in a black region, and keeps going until it finds white, then black, then white again. - * It reports the distance from the start to this point.

- * - *

This is used when figuring out how wide a finder pattern is, when the finder pattern - * may be skewed or rotated.

- */ - private float sizeOfBlackWhiteBlackRun(int fromX, int fromY, int toX, int toY) { - // Mild variant of Bresenham's algorithm; - // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm - boolean steep = Math.abs(toY - fromY) > Math.abs(toX - fromX); - if (steep) { - int temp = fromX; - fromX = fromY; - fromY = temp; - temp = toX; - toX = toY; - toY = temp; - } - - int dx = Math.abs(toX - fromX); - int dy = Math.abs(toY - fromY); - int error = -dx / 2; - int xstep = fromX < toX ? 1 : -1; - int ystep = fromY < toY ? 1 : -1; - - // In black pixels, looking for white, first or second time. - int state = 0; - // Loop up until x == toX, but not beyond - int xLimit = toX + xstep; - for (int x = fromX, y = fromY; x != xLimit; x += xstep) { - int realX = steep ? y : x; - int realY = steep ? x : y; - - // Does current pixel mean we have moved white to black or vice versa? - // Scanning black in state 0,2 and white in state 1, so if we find the wrong - // color, advance to next state or end if we are in state 2 already - if ((state == 1) == image.get(realX, realY)) { - if (state == 2) { - return MathUtils.distance(x, y, fromX, fromY); - } - state++; - } - - error += dy; - if (error > 0) { - if (y == toY) { - break; - } - y += ystep; - error -= dx; - } - } - // Found black-white-black; give the benefit of the doubt that the next pixel outside the image - // is "white" so this last point at (toX+xStep,toY) is the right ending. This is really a - // small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this. - if (state == 2) { - return MathUtils.distance(toX + xstep, toY, fromX, fromY); - } - // else we didn't find even black-white-black; no estimate is really possible - return Float.NaN; - } - - /** - *

Attempts to locate an alignment pattern in a limited region of the image, which is - * guessed to contain it. This method uses {@link AlignmentPattern}.

- * - * @param overallEstModuleSize estimated module size so far - * @param estAlignmentX x coordinate of center of area probably containing alignment pattern - * @param estAlignmentY y coordinate of above - * @param allowanceFactor number of pixels in all directions to search from the center - * @return {@link AlignmentPattern} if found, or null otherwise - * @throws NotFoundException if an unexpected error occurs during detection - */ - protected final AlignmentPattern findAlignmentInRegion(float overallEstModuleSize, - int estAlignmentX, - int estAlignmentY, - float allowanceFactor) - throws NotFoundException { - // Look for an alignment pattern (3 modules in size) around where it - // should be - int allowance = (int) (allowanceFactor * overallEstModuleSize); - int alignmentAreaLeftX = Math.max(0, estAlignmentX - allowance); - int alignmentAreaRightX = Math.min(image.getWidth() - 1, estAlignmentX + allowance); - if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) { - throw NotFoundException.getNotFoundInstance(); - } - - int alignmentAreaTopY = Math.max(0, estAlignmentY - allowance); - int alignmentAreaBottomY = Math.min(image.getHeight() - 1, estAlignmentY + allowance); - if (alignmentAreaBottomY - alignmentAreaTopY < overallEstModuleSize * 3) { - throw NotFoundException.getNotFoundInstance(); - } - - AlignmentPatternFinder alignmentFinder = - new AlignmentPatternFinder( - image, - alignmentAreaLeftX, - alignmentAreaTopY, - alignmentAreaRightX - alignmentAreaLeftX, - alignmentAreaBottomY - alignmentAreaTopY, - overallEstModuleSize, - resultPointCallback); - return alignmentFinder.find(); - } - -} diff --git a/src/qrcode/detector/alignment_pattern.rs b/src/qrcode/detector/alignment_pattern.rs index 8dc7ca8..24f8d41 100644 --- a/src/qrcode/detector/alignment_pattern.rs +++ b/src/qrcode/detector/alignment_pattern.rs @@ -38,6 +38,10 @@ impl ResultPoint for AlignmentPattern { fn getY(&self) -> f32 { self.point.1 } + + fn into_rxing_result_point(self) -> RXingResultPoint { + RXingResultPoint { x: self.point.0, y: self.point.1 } + } } impl AlignmentPattern { diff --git a/src/qrcode/detector/detector.rs b/src/qrcode/detector/detector.rs new file mode 100644 index 0000000..6df5d54 --- /dev/null +++ b/src/qrcode/detector/detector.rs @@ -0,0 +1,488 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::collections::HashMap; + +use crate::{ + common::{ + detector::MathUtils, BitMatrix, DefaultGridSampler, DetectorRXingResult, GridSampler, + PerspectiveTransform, + }, + qrcode::decoder::Version, + result_point_utils, DecodeHintType, DecodeHintValue, DecodingHintDictionary, Exceptions, + RXingResultPoint, RXingResultPointCallback, ResultPoint, +}; + +use super::{ + AlignmentPattern, AlignmentPatternFinder, FinderPatternFinder, FinderPatternInfo, + QRCodeDetectorResult, +}; + +/** + *

Encapsulates logic that can detect a QR Code in an image, even if the QR Code + * is rotated or skewed, or partially obscured.

+ * + * @author Sean Owen + */ +pub struct Detector { + image: BitMatrix, + resultPointCallback: Option, +} + +impl Detector { + pub fn new(image: BitMatrix) -> Self { + Self { + image, + resultPointCallback: None, + } + } + + pub fn getImage(&self) -> &BitMatrix { + &self.image + } + + pub fn getRXingResultPointCallback(&self) -> &Option { + &self.resultPointCallback + } + + /** + *

Detects a QR Code in an image.

+ * + * @return {@link DetectorRXingResult} encapsulating results of detecting a QR Code + * @throws NotFoundException if QR Code cannot be found + * @throws FormatException if a QR Code cannot be decoded + */ + pub fn detect(&mut self) -> Result { + self.detect_with_hints(&HashMap::new()) + } + + /** + *

Detects a QR Code in an image.

+ * + * @param hints optional hints to detector + * @return {@link DetectorRXingResult} encapsulating results of detecting a QR Code + * @throws NotFoundException if QR Code cannot be found + * @throws FormatException if a QR Code cannot be decoded + */ + pub fn detect_with_hints( + &mut self, + hints: &DecodingHintDictionary, + ) -> Result { + self.resultPointCallback = + if let Some(nrpc) = hints.get(&DecodeHintType::NEED_RESULT_POINT_CALLBACK) { + if let DecodeHintValue::NeedResultPointCallback(cb) = nrpc { + Some(*cb) + } else { + None + } + } else { + None + }; + + // self.resultPointCallback = hints.get(&DecodeHintType::NEED_RESULT_POINT_CALLBACK); + // resultPointCallback = hints == null ? null : + // (RXingResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); + + let mut finder = + FinderPatternFinder::with_callback(self.image.clone(), self.resultPointCallback); + let info = finder.find(hints)?; + + self.processFinderPatternInfo(info) + } + + pub fn processFinderPatternInfo( + &self, + info: FinderPatternInfo, + ) -> Result { + let topLeft = info.getTopLeft(); + let topRight = info.getTopRight(); + let bottomLeft = info.getBottomLeft(); + + let moduleSize = self.calculateModuleSize(topLeft, topRight, bottomLeft); + if moduleSize < 1.0 { + return Err(Exceptions::NotFoundException("not found".to_owned())); + } + let dimension = Self::computeDimension(topLeft, topRight, bottomLeft, moduleSize)?; + let provisionalVersion = Version::getProvisionalVersionForDimension(dimension)?; + let modulesBetweenFPCenters = provisionalVersion.getDimensionForVersion() - 7; + + let mut alignmentPattern = None; + // Anything above version 1 has an alignment pattern + if provisionalVersion.getAlignmentPatternCenters().len() > 0 { + // Guess where a "bottom right" finder pattern would have been + let bottomRightX = topRight.getX() - topLeft.getX() + bottomLeft.getX(); + let bottomRightY = topRight.getY() - topLeft.getY() + bottomLeft.getY(); + + // Estimate that alignment pattern is closer by 3 modules + // from "bottom right" to known top left location + let correctionToTopLeft = 1.0 - (3.0 / modulesBetweenFPCenters as f32); + let estAlignmentX = + (topLeft.getX() + correctionToTopLeft * (bottomRightX - topLeft.getX())) as u32; + let estAlignmentY = + (topLeft.getY() + correctionToTopLeft * (bottomRightY - topLeft.getY())) as u32; + + // Kind of arbitrary -- expand search radius before giving up + let mut i = 4; + while i <= 16 { + // for (int i = 4; i <= 16; i <<= 1) { + if let Ok(ap) = + self.findAlignmentInRegion(moduleSize, estAlignmentX, estAlignmentY, i as f32) + { + alignmentPattern = Some(ap); + break; + } + // try { + // alignmentPattern = findAlignmentInRegion(moduleSize, + // estAlignmentX, + // estAlignmentY, + // i); + // break; + // } catch (NotFoundException re) { + // // try next round + // } + i <<= 1; + } + // If we didn't find alignment pattern... well try anyway without it + } + + let ap_ref = if alignmentPattern.is_some() { + Some(alignmentPattern.as_ref().unwrap()) + } else { + None + }; + + let transform = Self::createTransform(topLeft, topRight, bottomLeft, ap_ref, dimension); + + let bits = Detector::sampleGrid(&self.image, &transform, dimension)?; + + let points = if alignmentPattern.is_none() { + vec![ + bottomLeft.into_rxing_result_point(), + topLeft.into_rxing_result_point(), + topRight.into_rxing_result_point(), + ] + } else { + vec![ + bottomLeft.into_rxing_result_point(), + topLeft.into_rxing_result_point(), + topRight.into_rxing_result_point(), + alignmentPattern.unwrap().into_rxing_result_point(), + ] + }; + + Ok(QRCodeDetectorResult::new(bits, points)) + } + + fn createTransform( + topLeft: &T, + topRight: &T, + bottomLeft: &T, + alignmentPattern: Option<&X>, + dimension: u32, + ) -> PerspectiveTransform { + let dimMinusThree = dimension as f32 - 3.5; + let bottomRightX: f32; + let bottomRightY: f32; + let sourceBottomRightX: f32; + let sourceBottomRightY: f32; + if (alignmentPattern.is_some()) { + let alignmentPattern = alignmentPattern.as_ref().unwrap(); + bottomRightX = alignmentPattern.getX(); + bottomRightY = alignmentPattern.getY(); + sourceBottomRightX = dimMinusThree - 3.0; + sourceBottomRightY = sourceBottomRightX; + } else { + // Don't have an alignment pattern, just make up the bottom-right point + bottomRightX = (topRight.getX() - topLeft.getX()) + bottomLeft.getX(); + bottomRightY = (topRight.getY() - topLeft.getY()) + bottomLeft.getY(); + sourceBottomRightX = dimMinusThree; + sourceBottomRightY = dimMinusThree; + } + + return PerspectiveTransform::quadrilateralToQuadrilateral( + 3.5, + 3.5, + dimMinusThree, + 3.5, + sourceBottomRightX, + sourceBottomRightY, + 3.5, + dimMinusThree, + topLeft.getX(), + topLeft.getY(), + topRight.getX(), + topRight.getY(), + bottomRightX, + bottomRightY, + bottomLeft.getX(), + bottomLeft.getY(), + ); + } + + fn sampleGrid( + image: &BitMatrix, + transform: &PerspectiveTransform, + dimension: u32, + ) -> Result { + let sampler = DefaultGridSampler {}; + return sampler.sample_grid(&image, dimension, dimension, transform); + } + + /** + *

Computes the dimension (number of modules on a size) of the QR Code based on the position + * of the finder patterns and estimated module size.

+ */ + fn computeDimension( + topLeft: &T, + topRight: &T, + bottomLeft: &T, + moduleSize: f32, + ) -> Result { + let tltrCentersDimension = + MathUtils::round(result_point_utils::distance(topLeft, topRight) / moduleSize); + let tlblCentersDimension = + MathUtils::round(result_point_utils::distance(topLeft, bottomLeft) / moduleSize); + let mut dimension = ((tltrCentersDimension + tlblCentersDimension) / 2) + 7; + match dimension & 0x03 { + 0 => dimension += 1, + 2 => dimension -= 1, + 3 => return Err(Exceptions::NotFoundException("not found".to_owned())), + _ => {} + } + // switch (dimension & 0x03) { // mod 4 + // case 0: + // dimension++; + // break; + // // 1? do nothing + // case 2: + // dimension--; + // break; + // case 3: + // return Err(Exceptions::NotFoundException("not found".to_owned())) + // } + Ok(dimension as u32) + } + + /** + *

Computes an average estimated module size based on estimated derived from the positions + * of the three finder patterns.

+ * + * @param topLeft detected top-left finder pattern center + * @param topRight detected top-right finder pattern center + * @param bottomLeft detected bottom-left finder pattern center + * @return estimated module size + */ + pub fn calculateModuleSize( + &self, + topLeft: &T, + topRight: &T, + bottomLeft: &T, + ) -> f32 { + // Take the average + return (self.calculateModuleSizeOneWay(topLeft, topRight) + + self.calculateModuleSizeOneWay(topLeft, bottomLeft)) + / 2.0; + } + + /** + *

Estimates module size based on two finder patterns -- it uses + * {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the + * width of each, measuring along the axis between their centers.

+ */ + fn calculateModuleSizeOneWay(&self, pattern: &T, otherPattern: &T) -> f32 { + let moduleSizeEst1 = self.sizeOfBlackWhiteBlackRunBothWays( + pattern.getX() as u32, + pattern.getY() as u32, + otherPattern.getX() as u32, + otherPattern.getY() as u32, + ); + let moduleSizeEst2 = self.sizeOfBlackWhiteBlackRunBothWays( + otherPattern.getX() as u32, + otherPattern.getY() as u32, + pattern.getX() as u32, + pattern.getY() as u32, + ); + if moduleSizeEst1.is_nan() { + return moduleSizeEst2 / 7.0; + } + if moduleSizeEst2.is_nan() { + return moduleSizeEst1 / 7.0; + } + // Average them, and divide by 7 since we've counted the width of 3 black modules, + // and 1 white and 1 black module on either side. Ergo, divide sum by 14. + return (moduleSizeEst1 + moduleSizeEst2) / 14.0; + } + + /** + * See {@link #sizeOfBlackWhiteBlackRun(int, int, int, int)}; computes the total width of + * a finder pattern by looking for a black-white-black run from the center in the direction + * of another point (another finder pattern center), and in the opposite direction too. + */ + fn sizeOfBlackWhiteBlackRunBothWays(&self, fromX: u32, fromY: u32, toX: u32, toY: u32) -> f32 { + let mut result = self.sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY); + + // Now count other way -- don't run off image though of course + let mut scale = 1.0; + let mut otherToX = fromX - (toX - fromX); + if (otherToX < 0) { + scale = fromX as f32 / (fromX - otherToX) as f32; + otherToX = 0; + } else if (otherToX >= self.image.getWidth()) { + scale = (self.image.getWidth() - 1 - fromX) as f32 / (otherToX - fromX) as f32; + otherToX = self.image.getWidth() - 1; + } + let mut otherToY = (fromY - (toY - fromY) * scale as u32) as u32; + + scale = 1.0; + if (otherToY < 0) { + scale = fromY as f32 / (fromY - otherToY) as f32; + otherToY = 0; + } else if (otherToY >= self.image.getHeight()) { + scale = (self.image.getHeight() - 1 - fromY) as f32 / (otherToY - fromY) as f32; + otherToY = self.image.getHeight() - 1; + } + otherToX = (fromX + (otherToX - fromX) * scale as u32) as u32; + + result += self.sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY); + + // Middle pixel is double-counted this way; subtract 1 + return result - 1.0; + } + + /** + *

This method traces a line from a point in the image, in the direction towards another point. + * It begins in a black region, and keeps going until it finds white, then black, then white again. + * It reports the distance from the start to this point.

+ * + *

This is used when figuring out how wide a finder pattern is, when the finder pattern + * may be skewed or rotated.

+ */ + fn sizeOfBlackWhiteBlackRun(&self, fromX: u32, fromY: u32, toX: u32, toY: u32) -> f32 { + let mut fromX = fromX; + let mut fromY = fromY; + let mut toX = toX; + let mut toY = toY; + // Mild variant of Bresenham's algorithm; + // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm + let steep = (toY - fromY) > (toX - fromX); + if (steep) { + let mut temp = fromX; + fromX = fromY; + fromY = temp; + temp = toX; + toX = toY; + toY = temp; + } + + let dx: i32 = (toX - fromX) as i32; + let dy: i32 = (toY - fromY) as i32; + let mut error = -dx / 2; + let xstep: i32 = if fromX < toX { 1 } else { -1 }; + let ystep: i32 = if fromY < toY { 1 } else { -1 }; + + // In black pixels, looking for white, first or second time. + let mut state = 0; + // Loop up until x == toX, but not beyond + let xLimit = toX as i32 + xstep; + + let mut x: i32 = fromX as i32; + let mut y: i32 = fromY as i32; + while x != xLimit { + // for (int x = fromX, y = fromY; x != xLimit; x += xstep) { + let realX = if steep { y } else { x }; + let realY = if steep { x } else { y }; + + // Does current pixel mean we have moved white to black or vice versa? + // Scanning black in state 0,2 and white in state 1, so if we find the wrong + // color, advance to next state or end if we are in state 2 already + if ((state == 1) == self.image.get(realX as u32, realY as u32)) { + if (state == 2) { + return MathUtils::distance_int(x, y, fromX as i32, fromY as i32); + } + state += 1; + } + + error += dy; + if (error > 0) { + if (y == toY as i32) { + break; + } + y += ystep; + error -= dx; + } + + x += xstep; + } + // Found black-white-black; give the benefit of the doubt that the next pixel outside the image + // is "white" so this last point at (toX+xStep,toY) is the right ending. This is really a + // small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this. + if (state == 2) { + return MathUtils::distance_int( + toX as i32 + xstep as i32, + toY as i32, + fromX as i32, + fromY as i32, + ); + } + // else we didn't find even black-white-black; no estimate is really possible + return f32::NAN; + } + + /** + *

Attempts to locate an alignment pattern in a limited region of the image, which is + * guessed to contain it. This method uses {@link AlignmentPattern}.

+ * + * @param overallEstModuleSize estimated module size so far + * @param estAlignmentX x coordinate of center of area probably containing alignment pattern + * @param estAlignmentY y coordinate of above + * @param allowanceFactor number of pixels in all directions to search from the center + * @return {@link AlignmentPattern} if found, or null otherwise + * @throws NotFoundException if an unexpected error occurs during detection + */ + pub fn findAlignmentInRegion( + &self, + overallEstModuleSize: f32, + estAlignmentX: u32, + estAlignmentY: u32, + allowanceFactor: f32, + ) -> Result { + // Look for an alignment pattern (3 modules in size) around where it + // should be + let allowance = (allowanceFactor * overallEstModuleSize) as u32; + let alignmentAreaLeftX = 0.max(estAlignmentX - allowance); + let alignmentAreaRightX = (self.image.getWidth() - 1).min(estAlignmentX + allowance); + if ((alignmentAreaRightX - alignmentAreaLeftX) as f32) < overallEstModuleSize * 3.0 { + return Err(Exceptions::NotFoundException("not found".to_owned())); + } + + let alignmentAreaTopY = 0.max(estAlignmentY - allowance); + let alignmentAreaBottomY = (self.image.getHeight() - 1).min(estAlignmentY + allowance); + if alignmentAreaBottomY - alignmentAreaTopY < overallEstModuleSize as u32 * 3 { + return Err(Exceptions::NotFoundException("not found".to_owned())); + } + + let mut alignmentFinder = AlignmentPatternFinder::new( + self.image.clone(), + alignmentAreaLeftX, + alignmentAreaTopY, + alignmentAreaRightX - alignmentAreaLeftX, + alignmentAreaBottomY - alignmentAreaTopY, + overallEstModuleSize, + self.resultPointCallback, + ); + alignmentFinder.find() + } +} diff --git a/src/qrcode/detector/finder_pattern.rs b/src/qrcode/detector/finder_pattern.rs index 57bbd8b..a79c2ad 100644 --- a/src/qrcode/detector/finder_pattern.rs +++ b/src/qrcode/detector/finder_pattern.rs @@ -23,8 +23,8 @@ use crate::{RXingResultPoint, ResultPoint}; * * @author Sean Owen */ -#[derive(Debug,Clone, Copy,PartialEq)] - pub struct FinderPattern { +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct FinderPattern { estimatedModuleSize: f32, count: usize, point: (f32, f32), @@ -38,6 +38,10 @@ impl ResultPoint for FinderPattern { fn getY(&self) -> f32 { self.point.1 } + + fn into_rxing_result_point(self) -> RXingResultPoint { + RXingResultPoint { x: self.point.0, y: self.point.1 } + } } impl FinderPattern { diff --git a/src/qrcode/detector/mod.rs b/src/qrcode/detector/mod.rs index fc05eb9..f1cffd5 100644 --- a/src/qrcode/detector/mod.rs +++ b/src/qrcode/detector/mod.rs @@ -3,9 +3,13 @@ mod finder_pattern; mod alignment_pattern; mod alignment_pattern_finder; mod finder_pattern_finder; +mod detector; +mod qrcode_detector_result; pub use finder_pattern_info::*; pub use finder_pattern::*; pub use alignment_pattern::*; pub use alignment_pattern_finder::*; -pub use finder_pattern_finder::*; \ No newline at end of file +pub use finder_pattern_finder::*; +pub use detector::*; +pub use qrcode_detector_result::*; \ No newline at end of file diff --git a/src/qrcode/detector/qrcode_detector_result.rs b/src/qrcode/detector/qrcode_detector_result.rs new file mode 100644 index 0000000..63b0864 --- /dev/null +++ b/src/qrcode/detector/qrcode_detector_result.rs @@ -0,0 +1,28 @@ +use crate::{ + common::{BitMatrix, DetectorRXingResult}, + RXingResultPoint, ResultPoint, +}; + +pub struct QRCodeDetectorResult { + bit_source: BitMatrix, + result_points: Vec, +} + +impl QRCodeDetectorResult { + pub fn new(bit_source: BitMatrix, result_points: Vec) -> Self { + Self { + bit_source, + result_points, + } + } +} + +impl DetectorRXingResult for QRCodeDetectorResult { + fn getBits(&self) -> &crate::common::BitMatrix { + &self.bit_source + } + + fn getPoints(&self) -> &Vec { + &self.result_points + } +}