f86525e6573aaf352116d929d784b012c9e1b43f
[zxing.git] / core / src / com / google / zxing / common / GlobalHistogramBinarizer.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 import com.google.zxing.ReaderException;
22
23 /**
24  * This Binarizer implementation uses the old ZXing global histogram approach. It is suitable
25  * for low-end mobile devices which don't have enough CPU or memory to use a local thresholding
26  * algorithm. However, because it picks a global black point, it cannot handle difficult shadows
27  * and gradients.
28  *
29  * @author dswitkin@google.com (Daniel Switkin)
30  * @author Sean Owen
31  */
32 public final class GlobalHistogramBinarizer extends Binarizer {
33
34   private static final int LUMINANCE_BITS = 5;
35   private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;
36   private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;
37
38   private byte[] luminances = null;
39   private int[] buckets = null;
40
41   public GlobalHistogramBinarizer(LuminanceSource source) {
42     super(source);
43   }
44
45   // Applies simple sharpening to the row data to improve performance of the 1D Readers.
46   public BitArray getBlackRow(int y, BitArray row) throws ReaderException {
47     LuminanceSource source = getLuminanceSource();
48     int width = source.getWidth();
49     if (row == null || row.getSize() < width) {
50       row = new BitArray(width);
51     } else {
52       row.clear();
53     }
54
55     initArrays(width);
56     byte[] localLuminances = source.getRow(y, luminances);
57     int[] localBuckets = buckets;
58     for (int x = 0; x < width; x++) {
59       int pixel = localLuminances[x] & 0xff;
60       localBuckets[pixel >> LUMINANCE_SHIFT]++;
61     }
62     int blackPoint = estimateBlackPoint(localBuckets);
63
64     int left = localLuminances[0] & 0xff;
65     int center = localLuminances[1] & 0xff;
66     for (int x = 1; x < width - 1; x++) {
67       int right = localLuminances[x + 1] & 0xff;
68       // A simple -1 4 -1 box filter with a weight of 2.
69       int luminance = ((center << 2) - left - right) >> 1;
70       if (luminance < blackPoint) {
71         row.set(x);
72       }
73       left = center;
74       center = right;
75     }
76     return row;
77   }
78
79   // Does not sharpen the data, as this call is intended to only be used by 2D Readers.
80   public BitMatrix getBlackMatrix() throws ReaderException {
81     LuminanceSource source = getLuminanceSource();
82     int width = source.getWidth();
83     int height = source.getHeight();
84     BitMatrix matrix = new BitMatrix(width, height);
85
86     // Quickly calculates the histogram by sampling four rows from the image. This proved to be
87     // more robust on the blackbox tests than sampling a diagonal as we used to do.
88     initArrays(width);
89     int[] localBuckets = buckets;
90     for (int y = 1; y < 5; y++) {
91       int row = height * y / 5;
92       byte[] localLuminances = source.getRow(row, luminances);
93       int right = (width << 2) / 5;
94       for (int x = width / 5; x < right; x++) {
95         int pixel = localLuminances[x] & 0xff;
96         localBuckets[pixel >> LUMINANCE_SHIFT]++;
97       }
98     }
99     int blackPoint = estimateBlackPoint(localBuckets);
100
101     // We delay reading the entire image luminance until the black point estimation succeeds.
102     // Although we end up reading four rows twice, it is consistent with our motto of
103     // "fail quickly" which is necessary for continuous scanning.
104     byte[] localLuminances = source.getMatrix();
105     for (int y = 0; y < height; y++) {
106       int offset = y * width;
107       for (int x = 0; x< width; x++) {
108         int pixel = localLuminances[offset + x] & 0xff; 
109         if (pixel < blackPoint) {
110           matrix.set(x, y);
111         }
112       }
113     }
114
115     return matrix;
116   }
117
118   public Binarizer createBinarizer(LuminanceSource source) {
119     return new GlobalHistogramBinarizer(source);
120   }
121
122   private void initArrays(int luminanceSize) {
123     if (luminances == null || luminances.length < luminanceSize) {
124       luminances = new byte[luminanceSize];
125     }
126     if (buckets == null) {
127       buckets = new int[LUMINANCE_BUCKETS];
128     } else {
129       for (int x = 0; x < LUMINANCE_BUCKETS; x++) {
130         buckets[x] = 0;
131       }
132     }
133   }
134
135   private static int estimateBlackPoint(int[] buckets) throws ReaderException {
136     // Find the tallest peak in the histogram.
137     int numBuckets = buckets.length;
138     int maxBucketCount = 0;
139     int firstPeak = 0;
140     int firstPeakSize = 0;
141     for (int x = 0; x < numBuckets; x++) {
142       if (buckets[x] > firstPeakSize) {
143         firstPeak = x;
144         firstPeakSize = buckets[x];
145       }
146       if (buckets[x] > maxBucketCount) {
147         maxBucketCount = buckets[x];
148       }
149     }
150
151     // Find the second-tallest peak which is somewhat far from the tallest peak.
152     int secondPeak = 0;
153     int secondPeakScore = 0;
154     for (int x = 0; x < numBuckets; x++) {
155       int distanceToBiggest = x - firstPeak;
156       // Encourage more distant second peaks by multiplying by square of distance.
157       int score = buckets[x] * distanceToBiggest * distanceToBiggest;
158       if (score > secondPeakScore) {
159         secondPeak = x;
160         secondPeakScore = score;
161       }
162     }
163
164     // Make sure firstPeak corresponds to the black peak.
165     if (firstPeak > secondPeak) {
166       int temp = firstPeak;
167       firstPeak = secondPeak;
168       secondPeak = temp;
169     }
170
171     // If there is too little contrast in the image to pick a meaningful black point, throw rather
172     // than waste time trying to decode the image, and risk false positives.
173     // TODO: It might be worth comparing the brightest and darkest pixels seen, rather than the
174     // two peaks, to determine the contrast.
175     if (secondPeak - firstPeak <= numBuckets >> 4) {
176       throw ReaderException.getInstance();
177     }
178
179     // Find a valley between them that is low and closer to the white peak.
180     int bestValley = secondPeak - 1;
181     int bestValleyScore = -1;
182     for (int x = secondPeak - 1; x > firstPeak; x--) {
183       int fromFirst = x - firstPeak;
184       int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]);
185       if (score > bestValleyScore) {
186         bestValley = x;
187         bestValleyScore = score;
188       }
189     }
190
191     return bestValley << LUMINANCE_SHIFT;
192   }
193
194 }