summaryrefslogtreecommitdiff
path: root/core/src/test/java/com
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/test/java/com
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/test/java/com')
-rw-r--r--core/src/test/java/com/google/zxing/qrcode/FinderPatternFinderPerformanceTestCase.java66
1 files changed, 66 insertions, 0 deletions
diff --git a/core/src/test/java/com/google/zxing/qrcode/FinderPatternFinderPerformanceTestCase.java b/core/src/test/java/com/google/zxing/qrcode/FinderPatternFinderPerformanceTestCase.java
new file mode 100644
index 000000000..60f6e9cb5
--- /dev/null
+++ b/core/src/test/java/com/google/zxing/qrcode/FinderPatternFinderPerformanceTestCase.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2026 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;
+
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.BufferedImageLuminanceSource;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.HybridBinarizer;
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Regression test for #1567: a noisy image with many spurious finder
+ * pattern candidates must not cause the decoder to hang.
+ */
+public final class FinderPatternFinderPerformanceTestCase
+ extends Assert {
+
+ private static final String IMAGE_PATH =
+ "src/test/resources/blackbox/triggeringImage1567.jpg";
+
+ @Test(timeout = 30000)
+ public void testNoisyImageDoesNotHang() throws Exception {
+ Path file = Paths.get(IMAGE_PATH);
+ if (!Files.exists(file)) {
+ file = Paths.get("core/").resolve(IMAGE_PATH);
+ }
+ assertTrue("Test image not found", Files.exists(file));
+
+ BufferedImage image = ImageIO.read(file.toFile());
+ assertNotNull("Failed to load test image", image);
+
+ BinaryBitmap bitmap = new BinaryBitmap(
+ new HybridBinarizer(
+ new BufferedImageLuminanceSource(image)));
+
+ MultiFormatReader reader = new MultiFormatReader();
+ try {
+ reader.decode(bitmap);
+ } catch (NotFoundException e) {
+ // Expected: no QR code in the image
+ }
+ // If we get here without timeout, the test passes
+ }
+}