Added a --products_only flag to the CommandLineRunner.
[zxing.git] / core / src / com / google / zxing / common / LocalBlockBinarizer.java
1 /*
2  * Copyright 2009 ZXing authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.google.zxing.common;
18
19 import com.google.zxing.Binarizer;
20 import com.google.zxing.LuminanceSource;
21
22 /**
23  * This class implements a local thresholding algorithm, which while slower than the
24  * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for
25  * high frequency images of barcodes with black data on white backgrounds. For this application,
26  * it does a much better job than a global blackpoint with severe shadows and gradients.
27  * However it tends to produce artifacts on lower frequency images and is therefore not
28  * a good general purpose binarizer for uses outside ZXing.
29  *
30  * NOTE: This class is still experimental and may not be ready for prime time yet.
31  *
32  * @author dswitkin@google.com (Daniel Switkin)
33  */
34 public final class LocalBlockBinarizer extends Binarizer {
35
36   private BitMatrix matrix = null;
37
38   public LocalBlockBinarizer(LuminanceSource source) {
39     super(source);
40   }
41
42   // TODO: Consider a different strategy for 1D Readers.
43   public BitArray getBlackRow(int y, BitArray row) {
44     binarizeEntireImage();
45     return matrix.getRow(y, row);
46   }
47
48   // TODO: If getBlackRow() calculates its own values, removing sharpening here.
49   public BitMatrix getBlackMatrix() {
50     binarizeEntireImage();
51     return matrix;
52   }
53
54   public Binarizer createBinarizer(LuminanceSource source) {
55     return new LocalBlockBinarizer(source);
56   }
57
58   // Calculates the final BitMatrix once for all requests. This could be called once from the
59   // constructor instead, but there are some advantages to doing it lazily, such as making
60   // profiling easier, and not doing heavy lifting when callers don't expect it.
61   private void binarizeEntireImage() {
62     if (matrix == null) {
63       LuminanceSource source = getLuminanceSource();
64       byte[] luminances = source.getMatrix();
65       int width = source.getWidth();
66       int height = source.getHeight();
67       sharpenRow(luminances, width, height);
68
69       int subWidth = width >> 3;
70       int subHeight = height >> 3;
71       int[][] blackPoints = calculateBlackPoints(luminances, subWidth, subHeight, width);
72
73       matrix = new BitMatrix(width, height);
74       calculateThresholdForBlock(luminances, subWidth, subHeight, width, blackPoints, matrix);
75     }
76   }
77
78   // For each 8x8 block in the image, calculate the average black point using a 5x5 grid
79   // of the blocks around it. Also handles the corner cases, but will ignore up to 7 pixels
80   // on the right edge and 7 pixels at the bottom of the image if the overall dimensions are not
81   // multiples of eight. In practice, leaving those pixels white does not seem to be a problem.
82   private static void calculateThresholdForBlock(byte[] luminances, int subWidth, int subHeight,
83       int stride, int[][] blackPoints, BitMatrix matrix) {
84     for (int y = 0; y < subHeight; y++) {
85       for (int x = 0; x < subWidth; x++) {
86         int left = (x > 1) ? x : 2;
87         left = (left < subWidth - 2) ? left : subWidth - 3;
88         int top = (y > 1) ? y : 2;
89         top = (top < subHeight - 2) ? top : subHeight - 3;
90         int sum = 0;
91         for (int z = -2; z <= 2; z++) {
92           sum += blackPoints[top + z][left - 2];
93           sum += blackPoints[top + z][left - 1];
94           sum += blackPoints[top + z][left];
95           sum += blackPoints[top + z][left + 1];
96           sum += blackPoints[top + z][left + 2];
97         }
98         int average = sum / 25;
99         threshold8x8Block(luminances, x << 3, y << 3, average, stride, matrix);
100       }
101     }
102   }
103
104   // Applies a single threshold to an 8x8 block of pixels.
105   private static void threshold8x8Block(byte[] luminances, int xoffset, int yoffset, int threshold,
106       int stride, BitMatrix matrix) {
107     for (int y = 0; y < 8; y++) {
108       int offset = (yoffset + y) * stride + xoffset;
109       for (int x = 0; x < 8; x++) {
110         int pixel = luminances[offset + x] & 0xff;
111         if (pixel < threshold) {
112           matrix.set(xoffset + x, yoffset + y);
113         }
114       }
115     }
116   }
117
118   // Calculates a single black point for each 8x8 block of pixels and saves it away.
119   private static int[][] calculateBlackPoints(byte[] luminances, int subWidth, int subHeight,
120       int stride) {
121     int[][] blackPoints = new int[subHeight][subWidth];
122     for (int y = 0; y < subHeight; y++) {
123       for (int x = 0; x < subWidth; x++) {
124         int sum = 0;
125         int min = 255;
126         int max = 0;
127         for (int yy = 0; yy < 8; yy++) {
128           int offset = ((y << 3) + yy) * stride + (x << 3);
129           for (int xx = 0; xx < 8; xx++) {
130             int pixel = luminances[offset + xx] & 0xff;
131             sum += pixel;
132             if (pixel < min) {
133               min = pixel;
134             }
135             if (pixel > max) {
136               max = pixel;
137             }
138           }
139         }
140
141         // If the contrast is inadequate, use half the minimum, so that this block will be
142         // treated as part of the white background, but won't drag down neighboring blocks
143         // too much.
144         int average = (max - min > 24) ? (sum >> 6) : (min >> 1);
145         blackPoints[y][x] = average;
146       }
147     }
148     return blackPoints;
149   }
150
151   // Applies a simple -1 4 -1 box filter with a weight of 2 to each row.
152   private static void sharpenRow(byte[] luminances, int width, int height) {
153     for (int y = 0; y < height; y++) {
154       int offset = y * width;
155       int left = luminances[offset] & 0xff;
156       int center = luminances[offset + 1] & 0xff;
157       for (int x = 1; x < width - 1; x++) {
158         int right = luminances[offset + x + 1] & 0xff;
159         int pixel = ((center << 2) - left - right) >> 1;
160         // Must clamp values to 0..255 so they will fit in a byte.
161         if (pixel > 255) {
162           pixel = 255;
163         } else if (pixel < 0) {
164           pixel = 0;
165         }
166         luminances[offset + x] = (byte)pixel;
167         left = center;
168         center = right;
169       }
170     }
171   }
172
173 }