diff --git a/core/src/com/google/zxing/common/HybridBinarizer.java b/core/src/com/google/zxing/common/HybridBinarizer.java index 3fcce238f..f4f723f45 100644 --- a/core/src/com/google/zxing/common/HybridBinarizer.java +++ b/core/src/com/google/zxing/common/HybridBinarizer.java @@ -1,3 +1,4 @@ +// -*- mode:java; tab-width:2; indent-tabs-mode:nil; c-basic-offset:2 -*- /* * Copyright 2009 ZXing authors * @@ -132,13 +133,21 @@ public final class HybridBinarizer extends GlobalHistogramBinarizer { BitMatrix matrix) { for (int y = 0, offset = yoffset * stride + xoffset; y < BLOCK_SIZE; y++, offset += stride) { for (int x = 0; x < BLOCK_SIZE; x++) { - if ((luminances[offset + x] & 0xFF) < threshold) { + // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0 + if ((luminances[offset + x] & 0xFF) <= threshold) { matrix.set(xoffset + x, yoffset + y); } } } } + // Esimates blackPoint from previously calculated neighbor esitmates + private static int getBlackPointFromNeighbors(int[][] blackPoints, int x, int y) { + return (blackPoints[y-1][x] + + 2*blackPoints[y][x-1] + + blackPoints[y-1][x-1]) >> 2; + } + // Calculates a single black point for each 8x8 block of pixels and saves it away. private static int[][] calculateBlackPoints(byte[] luminances, int subWidth, @@ -172,15 +181,38 @@ public final class HybridBinarizer extends GlobalHistogramBinarizer { } } - // If the contrast is inadequate, use half the minimum, so that this block will be - // treated as part of the white background, but won't drag down neighboring blocks - // too much. - int average; - if (max - min > 24) { - average = sum >> 6; - } else { - // When min == max == 0, let average be 1 so all is black - average = max == 0 ? 1 : min >> 1; + // See + // http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 + + // The default estimate is the average of the values in the block + int average = sum >> 6; + + if (max - min <= 24) { + // If variation wihthin the block is low, assume this is a + // block with only light or only dark pixels. + + // The default assumption is that the block is light/background. + // Since no estimate for the level of dark pixels + // exists locally, use half the min for the block. + average = min >> 1; + + if (y > 0 && x > 0) { + // Correct the "white/background" assumption for blocks + // that have neighbors by comparing the pixels in this + // block to the previously calculated blackpoints. This is + // based on the fact that dark barcode symbology is always + // surrounded by some amount of light background for which + // reasonable blackpoint esimates were made. The bp estimated + // at the bondaries is used for the interior. + + // The (min < bp) seems pretty arbitrary but works better than + // other heurstics that were tried. + + int bp = getBlackPointFromNeighbors(blackPoints, x, y); + if (min < bp) { + average = bp; + } + } } blackPoints[y][x] = average; } diff --git a/core/test/data/blackbox/qrcode-6/15.jpg b/core/test/data/blackbox/qrcode-6/15.jpg new file mode 100644 index 000000000..5c76007df Binary files /dev/null and b/core/test/data/blackbox/qrcode-6/15.jpg differ diff --git a/core/test/data/blackbox/qrcode-6/15.txt b/core/test/data/blackbox/qrcode-6/15.txt new file mode 100644 index 000000000..3b1246497 --- /dev/null +++ b/core/test/data/blackbox/qrcode-6/15.txt @@ -0,0 +1 @@ +TEST \ No newline at end of file diff --git a/core/test/src/com/google/zxing/aztec/AztecBlackBox2TestCase.java b/core/test/src/com/google/zxing/aztec/AztecBlackBox2TestCase.java index fc1d9b92e..f6adf3684 100644 --- a/core/test/src/com/google/zxing/aztec/AztecBlackBox2TestCase.java +++ b/core/test/src/com/google/zxing/aztec/AztecBlackBox2TestCase.java @@ -29,7 +29,7 @@ public final class AztecBlackBox2TestCase extends AbstractBlackBoxTestCase { public AztecBlackBox2TestCase() { super("test/data/blackbox/aztec-2", new AztecReader(), BarcodeFormat.AZTEC); addTest(2, 2, 0.0f); - addTest(2, 2, 90.0f); + addTest(1, 1, 90.0f); addTest(3, 3, 180.0f); addTest(1, 1, 270.0f); } diff --git a/core/test/src/com/google/zxing/datamatrix/DataMatrixBlackBox2TestCase.java b/core/test/src/com/google/zxing/datamatrix/DataMatrixBlackBox2TestCase.java index 34752df5d..d83cd48e8 100644 --- a/core/test/src/com/google/zxing/datamatrix/DataMatrixBlackBox2TestCase.java +++ b/core/test/src/com/google/zxing/datamatrix/DataMatrixBlackBox2TestCase.java @@ -27,10 +27,10 @@ public final class DataMatrixBlackBox2TestCase extends AbstractBlackBoxTestCase public DataMatrixBlackBox2TestCase() { super("test/data/blackbox/datamatrix-2", new MultiFormatReader(), BarcodeFormat.DATA_MATRIX); - addTest(10, 10, 0.0f); - addTest(13, 13, 90.0f); - addTest(16, 16, 180.0f); - addTest(13, 13, 270.0f); + addTest(8, 8, 0.0f); + addTest(14, 14, 90.0f); + addTest(14, 14, 180.0f); + addTest(12, 12, 270.0f); } } diff --git a/core/test/src/com/google/zxing/pdf417/PDF417BlackBox2TestCase.java b/core/test/src/com/google/zxing/pdf417/PDF417BlackBox2TestCase.java index 08bbc53e1..1efc5d0f9 100644 --- a/core/test/src/com/google/zxing/pdf417/PDF417BlackBox2TestCase.java +++ b/core/test/src/com/google/zxing/pdf417/PDF417BlackBox2TestCase.java @@ -33,8 +33,8 @@ public final class PDF417BlackBox2TestCase extends AbstractBlackBoxTestCase { public PDF417BlackBox2TestCase() { super("test/data/blackbox/pdf417-2", new MultiFormatReader(), BarcodeFormat.PDF_417); - addTest(13, 13, 0.0f); - addTest(13, 13, 180.0f); + addTest(15, 15, 0.0f); + addTest(14, 14, 180.0f); } @Override diff --git a/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox1TestCase.java b/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox1TestCase.java index e789ce978..d9a3d6770 100644 --- a/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox1TestCase.java +++ b/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox1TestCase.java @@ -28,8 +28,8 @@ public final class QRCodeBlackBox1TestCase extends AbstractBlackBoxTestCase { public QRCodeBlackBox1TestCase() { super("test/data/blackbox/qrcode-1", new MultiFormatReader(), BarcodeFormat.QR_CODE); addTest(17, 17, 0.0f); - addTest(13, 13, 90.0f); - addTest(16, 16, 180.0f); + addTest(14, 14, 90.0f); + addTest(17, 17, 180.0f); addTest(14, 14, 270.0f); } diff --git a/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox2TestCase.java b/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox2TestCase.java index 8c9ebf781..dc1bed53b 100644 --- a/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox2TestCase.java +++ b/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox2TestCase.java @@ -27,10 +27,10 @@ public final class QRCodeBlackBox2TestCase extends AbstractBlackBoxTestCase { public QRCodeBlackBox2TestCase() { super("test/data/blackbox/qrcode-2", new MultiFormatReader(), BarcodeFormat.QR_CODE); - addTest(29, 29, 0.0f); - addTest(29, 29, 90.0f); - addTest(29, 29, 180.0f); - addTest(28, 28, 270.0f); + addTest(30, 30, 0.0f); + addTest(30, 30, 90.0f); + addTest(30, 30, 180.0f); + addTest(29, 29, 270.0f); } } diff --git a/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox3TestCase.java b/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox3TestCase.java index 0894020e7..d561a7956 100644 --- a/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox3TestCase.java +++ b/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox3TestCase.java @@ -30,7 +30,7 @@ public final class QRCodeBlackBox3TestCase extends AbstractBlackBoxTestCase { addTest(38, 38, 0.0f); addTest(38, 38, 90.0f); addTest(36, 36, 180.0f); - addTest(38, 38, 270.0f); + addTest(39, 39, 270.0f); } } diff --git a/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox4TestCase.java b/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox4TestCase.java index 359137d02..bcddd5de6 100644 --- a/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox4TestCase.java +++ b/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox4TestCase.java @@ -32,7 +32,7 @@ public final class QRCodeBlackBox4TestCase extends AbstractBlackBoxTestCase { addTest(36, 36, 0.0f); addTest(35, 35, 90.0f); addTest(35, 35, 180.0f); - addTest(34, 34, 270.0f); + addTest(35, 35, 270.0f); } } diff --git a/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox5TestCase.java b/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox5TestCase.java index c2562c7a0..8ca49732e 100644 --- a/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox5TestCase.java +++ b/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox5TestCase.java @@ -31,7 +31,7 @@ public final class QRCodeBlackBox5TestCase extends AbstractBlackBoxTestCase { public QRCodeBlackBox5TestCase() { super("test/data/blackbox/qrcode-5", new MultiFormatReader(), BarcodeFormat.QR_CODE); - addTest(18, 18, 0.0f); + addTest(19, 19, 0.0f); addTest(19, 19, 90.0f); addTest(19, 19, 180.0f); addTest(18, 18, 270.0f); diff --git a/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox6TestCase.java b/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox6TestCase.java index b15098881..a99780e7d 100644 --- a/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox6TestCase.java +++ b/core/test/src/com/google/zxing/qrcode/QRCodeBlackBox6TestCase.java @@ -28,10 +28,10 @@ public final class QRCodeBlackBox6TestCase extends AbstractBlackBoxTestCase { public QRCodeBlackBox6TestCase() { super("test/data/blackbox/qrcode-6", new MultiFormatReader(), BarcodeFormat.QR_CODE); - addTest(14, 14, 0.0f); - addTest(13, 13, 90.0f); - addTest(11, 12, 180.0f); - addTest(13, 13, 270.0f); + addTest(15, 15, 0.0f); + addTest(14, 14, 90.0f); + addTest(12, 13, 180.0f); + addTest(14, 14, 270.0f); } } diff --git a/cpp/core/src/zxing/common/HybridBinarizer.cpp b/cpp/core/src/zxing/common/HybridBinarizer.cpp index 0a290f5a0..5678bb3dc 100644 --- a/cpp/core/src/zxing/common/HybridBinarizer.cpp +++ b/cpp/core/src/zxing/common/HybridBinarizer.cpp @@ -38,7 +38,6 @@ namespace { HybridBinarizer::HybridBinarizer(Ref source) : GlobalHistogramBinarizer(source), matrix_(NULL), cached_row_(NULL), cached_row_num_(-1) { - } HybridBinarizer::~HybridBinarizer() { @@ -125,13 +124,21 @@ void HybridBinarizer::threshold8x8Block(unsigned char* luminances, int xoffset, y++, offset += stride) { for (int x = 0; x < BLOCK_SIZE; x++) { int pixel = luminances[offset + x] & 0xff; - if (pixel < threshold) { + if (pixel <= threshold) { matrix->set(xoffset + x, yoffset + y); } } } } +namespace { + inline int getBlackPointFromNeighbors(int* blackPoints, int subWidth, int x, int y) { + return (blackPoints[(y-1)*subWidth+x] + + 2*blackPoints[y*subWidth+x-1] + + blackPoints[(y-1)*subWidth+x-1]) >> 2; + } +} + int* HybridBinarizer::calculateBlackPoints(unsigned char* luminances, int subWidth, int subHeight, int width, int height) { int *blackPoints = new int[subHeight * subWidth]; @@ -162,15 +169,18 @@ int* HybridBinarizer::calculateBlackPoints(unsigned char* luminances, int subWid } } } - - // If the contrast is inadequate, use half the minimum, so that this block will be - // treated as part of the white background, but won't drag down neighboring blocks - // too much. - int average; - if (max - min > 24) { - average = (sum >> 6); - } else { - average = max == 0 ? 1 : (min >> 1); + + // See + // http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 + int average = sum >> 6; + if (max - min <= 24) { + average = min >> 1; + if (y > 0 && x > 0) { + int bp = getBlackPointFromNeighbors(blackPoints, subWidth, x, y); + if (min < bp) { + average = bp; + } + } } blackPoints[y * subWidth + x] = average; }