summaryrefslogtreecommitdiff
path: root/core/src/main/java
diff options
context:
space:
mode:
authorAndrew Szeto <github@jabagawee.com>2026-04-08 06:40:49 +0300
committerGitHub <noreply@github.com>2026-04-08 06:40:49 +0300
commit282319c8134c132fddfc0c942981c41eea3311cb (patch)
tree74eb01eb9b8d8931fc6d4537fd055eb3d7aadbc1 /core/src/main/java
parent3007faae3a91e98765305dd00cfd080887c41997 (diff)
downloadzxing-282319c8134c132fddfc0c942981c41eea3311cb.tar.xz
Fix QR decode failure for small/native-size images (#2061)
* Fix QR decode failure for small/native-size images (#1976) QRCodeWriter.encode(..., 0, 0) produces a 33x33 native-size QR that ZXing cannot decode. The CENTER_QUORUM filter in selectBestPatterns() (added in 110ef9e to fix #1567) removes finder patterns with count < 2. For small images, the row-skip interval (iSkip=3, MIN_SKIP) means the scanner only crosses the top two finder pattern centers on a single row each, giving them count=1. The bottom-left pattern gets count=2 because it's scanned after iSkip drops to 2. The filter then removes the two count=1 patterns, leaving 1 of the 3 needed, and throws NotFoundException. The fix: only apply the CENTER_QUORUM filter when either (a) at least 3 confirmed patterns would survive, or (b) the total candidate list exceeds a size threshold. This preserves #1567 protection while allowing small images through. Why a size threshold: the filter was added to prevent O(n^3) blowup in the triangle-selection loop when noisy images produce thousands of spurious candidates. When the candidate list is small (<= 25 entries, yielding <= ~2300 loop iterations), the cubic cost is negligible regardless of whether the patterns are real or spurious. A clean single-QR image produces ~3 candidates; even a noisy image below the threshold is computationally trivial. Above the threshold, the filter applies unconditionally, matching today's behavior. Alternatives considered: - Removing the filter entirely: all 561 existing tests pass, but re-opens #1567 for pathological images with thousands of spurious count=1 candidates. - Reducing MIN_SKIP from 3 to 1 for small images: addresses the symptom (more scan rows = higher counts) but doesn't fix the fundamental interaction between iSkip and the quorum filter, and changes scan behavior for all small images. - Fallback (try with filter, retry without on failure): re-opens #1567 whenever fewer than 3 confirmed patterns exist, which is exactly the scenario that produces thousands of unconfirmed candidates in noisy images. * Increase noisy-image test timeout from 5s to 30s GitHub shared runners timed out at 5s. Local measurement shows ~1.2s, implying at least 4x slowdown on CI. 30s gives a comfortable margin while still catching the O(n^3) regression from #1567.
Diffstat (limited to 'core/src/main/java')
-rwxr-xr-xcore/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java15
1 files changed, 12 insertions, 3 deletions
diff --git a/core/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java b/core/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java
index 951bd197f..2eeef5c8d 100755
--- a/core/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java
+++ b/core/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java
@@ -42,6 +42,7 @@ import java.util.Map;
public class FinderPatternFinder {
private static final int CENTER_QUORUM = 2;
+ private static final int MAX_FINDER_PATTERN_CANDIDATES = 25;
private static final EstimatedModuleComparator moduleComparator = new EstimatedModuleComparator();
protected static final int MIN_SKIP = 3; // 1 pixel/module times 3 modules/center
protected static final int MAX_MODULES = 97; // support up to version 20 for mobile clients
@@ -623,9 +624,17 @@ public class FinderPatternFinder {
throw NotFoundException.getNotFoundInstance();
}
- for (Iterator<FinderPattern> it = possibleCenters.iterator(); it.hasNext();) {
- if (it.next().getCount() < CENTER_QUORUM) {
- it.remove();
+ int confirmedCount = 0;
+ for (FinderPattern pattern : possibleCenters) {
+ if (pattern.getCount() >= CENTER_QUORUM) {
+ confirmedCount++;
+ }
+ }
+ if (confirmedCount >= 3 || startSize > MAX_FINDER_PATTERN_CANDIDATES) {
+ for (Iterator<FinderPattern> it = possibleCenters.iterator(); it.hasNext();) {
+ if (it.next().getCount() < CENTER_QUORUM) {
+ it.remove();
+ }
}
}