Refactored the MonochromeBitmapSource class hierarchy into LuminanceSource, Binarizer...
[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.ReaderException;
21 import com.google.zxing.LuminanceSource;
22
23 /**
24  * This class implements a local thresholding algorithm, which while slower than the
25  * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for
26  * high frequency images of barcodes with black data on white backgrounds. For this application,
27  * it does a much better job than a global blackpoint with severe shadows and gradients.
28  * However it tends to produce artifacts on lower frequency images and is therefore not
29  * a good general purpose binarizer for uses outside ZXing.
30  *
31  * @author dswitkin@google.com (Daniel Switkin)
32  */
33 public final class LocalBlockBinarizer extends Binarizer {
34
35   private BitMatrix matrix = null;
36
37   public LocalBlockBinarizer(LuminanceSource source) {
38     super(source);
39   }
40
41   public BitArray getBlackRow(int y, BitArray row) throws ReaderException {
42     binarizeEntireImage();
43     return matrix.getRow(y, row);
44   }
45
46   public BitMatrix getBlackMatrix() throws ReaderException {
47     binarizeEntireImage();
48     return matrix;
49   }
50
51   public Binarizer createBinarizer(LuminanceSource source) {
52     return new LocalBlockBinarizer(source);
53   }
54
55   // Calculates the final BitMatrix once for all requests. This could be called once from the
56   // constructor instead, but there are some advantages to doing it lazily, such as making
57   // profiling easier, and not doing heavy lifting when callers don't expect it.
58   private void binarizeEntireImage() {
59     if (matrix == null) {
60       LuminanceSource source = getLuminanceSource();
61       byte[] luminances = source.getMatrix();
62       int width = source.getWidth();
63       int height = source.getHeight();
64       sharpenRow(luminances, width, height);
65
66       int subWidth = width >> 3;
67       int subHeight = height >> 3;
68       int[][] blackPoints = calculateBlackPoints(luminances, subWidth, subHeight, width);
69
70       matrix = new BitMatrix(width, height);
71       calculateThresholdForBlock(luminances, subWidth, subHeight, width, blackPoints, matrix);
72     }
73   }
74
75   // For each 8x8 block in the image, calculate the average black point using a 5x5 grid
76   // of the blocks around it. Also handles the corner cases, but will ignore up to 7 pixels
77   // on the right edge and 7 pixels at the bottom of the image if the overall dimsions are not
78   // multiples of eight. In practice, leaving those pixels white does not seem to be a problem.
79   private static void calculateThresholdForBlock(byte[] luminances, int subWidth, int subHeight,
80       int stride, int[][] blackPoints, BitMatrix matrix) {
81     for (int y = 0; y < subHeight; y++) {
82       for (int x = 0; x < subWidth; x++) {
83         int sum = 0;
84         int left = (x > 1) ? x : 2;
85         left = (left < subWidth - 2) ? left : subWidth - 3;
86         int top = (y > 1) ? y : 2;
87         top = (top < subHeight - 2) ? top : subHeight - 3;
88         for (int z = -2; z <= 2; z++) {
89           sum += blackPoints[top + z][left - 2];
90           sum += blackPoints[top + z][left - 1];
91           sum += blackPoints[top + z][left];
92           sum += blackPoints[top + z][left + 1];
93           sum += blackPoints[top + z][left + 2];
94         }
95         int average = sum / 25;
96         threshold8x8Block(luminances, x * 8, y * 8, average, stride, matrix);
97       }
98     }
99   }
100
101   // Applies a single threshold to an 8x8 block of pixels.
102   private static void threshold8x8Block(byte[] luminances, int xoffset, int yoffset, int threshold,
103       int stride, BitMatrix matrix) {
104     for (int y = 0; y < 8; y++) {
105       int offset = (yoffset + y) * stride + xoffset;
106       for (int x = 0; x < 8; x++) {
107         int pixel = luminances[offset + x] & 0xff;
108         if (pixel < threshold) {
109           matrix.set(xoffset + x, yoffset + y);
110         }
111       }
112     }
113   }
114
115   // Calculates a single black point for each 8x8 block of pixels and saves it away.
116   private static int[][] calculateBlackPoints(byte[] luminances, int subWidth, int subHeight,
117       int stride) {
118     int[][] blackPoints = new int[subHeight][subWidth];
119     for (int y = 0; y < subHeight; y++) {
120       for (int x = 0; x < subWidth; x++) {
121         int sum = 0;
122         int min = 255;
123         int max = 0;
124         for (int yy = 0; yy < 8; yy++) {
125           int offset = (y * 8 + yy) * stride + (x * 8);
126           for (int xx = 0; xx < 8; xx++) {
127             int pixel = luminances[offset + xx] & 0xff;
128             sum += pixel;
129             if (pixel < min) {
130               min = pixel;
131             }
132             if (pixel > max) {
133               max = pixel;
134             }
135           }
136         }
137
138         // If the contrast is inadequate, use half the minimum, so that this block will be
139         // treated as part of the white background, but won't drag down neighboring blocks
140         // too much.
141         int average = (max - min > 24) ? (sum >> 6) : (min >> 1);
142         blackPoints[y][x] = average;
143       }
144     }
145     return blackPoints;
146   }
147
148   // Applies a simple -1 4 -1 box filter with a weight of 2 to each row.
149   private static void sharpenRow(byte[] luminances, int width, int height) {
150     for (int y = 0; y < height; y++) {
151       int offset = y * width;
152       int left = luminances[offset] & 0xff;
153       int center = luminances[offset + 1] & 0xff;
154       for (int x = 1; x < width - 1; x++) {
155         int right = luminances[offset + x + 1] & 0xff;
156         luminances[x] = (byte)(((center << 2) - left - right) >> 1);
157         left = center;
158         center = right;
159       }
160     }
161   }
162
163 }