2 * Copyright 2009 ZXing authors
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
17 using Binarizer = com.google.zxing.Binarizer;
\r
18 using LuminanceSource = com.google.zxing.LuminanceSource;
\r
19 using ReaderException = com.google.zxing.ReaderException;
\r
20 namespace com.google.zxing.common
\r
23 /// <summary> This Binarizer implementation uses the old ZXing global histogram approach. It is suitable
\r
24 /// for low-end mobile devices which don't have enough CPU or memory to use a local thresholding
\r
25 /// algorithm. However, because it picks a global black point, it cannot handle difficult shadows
\r
28 /// Faster mobile devices and all desktop applications should probably use HybridBinarizer instead.
\r
31 /// <author> dswitkin@google.com (Daniel Switkin)
\r
33 /// <author> Sean Owen
\r
35 /// <author>www.Redivivus.in (suraj.supekar@redivivus.in) - Ported from ZXING Java Source
\r
37 public class GlobalHistogramBinarizer:Binarizer
\r
39 override public BitMatrix BlackMatrix
\r
41 // Does not sharpen the data, as this call is intended to only be used by 2D Readers.
\r
45 LuminanceSource source = LuminanceSource;
\r
46 // Redivivus.in Java to c# Porting update
\r
50 sbyte[] localLuminances;
\r
53 int width = source.Width;
\r
54 int height = source.Height;
\r
55 BitMatrix matrix = new BitMatrix(width, height);
\r
57 // Quickly calculates the histogram by sampling four rows from the image. This proved to be
\r
58 // more robust on the blackbox tests than sampling a diagonal as we used to do.
\r
60 int[] localBuckets = buckets;
\r
61 for (int y = 1; y < 5; y++)
\r
63 int row = height * y / 5;
\r
64 // Redivivus.in Java to c# Porting update
\r
66 // Commented & Added
\r
68 //sbyte[] localLuminances = source.getRow(row, luminances);
\r
69 localLuminances = source.getRow(row, luminances);
\r
71 int right = (width << 2) / 5;
\r
72 for (int x = width / 5; x < right; x++)
\r
74 int pixel = localLuminances[x] & 0xff;
\r
75 localBuckets[pixel >> LUMINANCE_SHIFT]++;
\r
78 int blackPoint = estimateBlackPoint(localBuckets);
\r
80 // We delay reading the entire image luminance until the black point estimation succeeds.
\r
81 // Although we end up reading four rows twice, it is consistent with our motto of
\r
82 // "fail quickly" which is necessary for continuous scanning.
\r
84 localLuminances = source.Matrix; // Govinda : Removed sbyte []
\r
85 for (int y = 0; y < height; y++)
\r
87 int offset = y * width;
\r
88 for (int x = 0; x < width; x++)
\r
90 int pixel = localLuminances[offset + x] & 0xff;
\r
91 if (pixel < blackPoint)
\r
93 matrix.set_Renamed(x, y);
\r
103 private const int LUMINANCE_BITS = 5;
\r
104 //UPGRADE_NOTE: Final was removed from the declaration of 'LUMINANCE_SHIFT '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
\r
105 private static readonly int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;
\r
106 //UPGRADE_NOTE: Final was removed from the declaration of 'LUMINANCE_BUCKETS '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
\r
107 private static readonly int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;
\r
109 private sbyte[] luminances = null;
\r
110 private int[] buckets = null;
\r
112 public GlobalHistogramBinarizer(LuminanceSource source):base(source)
\r
116 // Applies simple sharpening to the row data to improve performance of the 1D Readers.
\r
117 public override BitArray getBlackRow(int y, BitArray row)
\r
119 LuminanceSource source = LuminanceSource;
\r
120 int width = source.Width;
\r
121 if (row == null || row.Size < width)
\r
123 row = new BitArray(width);
\r
131 sbyte[] localLuminances = source.getRow(y, luminances);
\r
132 int[] localBuckets = buckets;
\r
133 for (int x = 0; x < width; x++)
\r
135 int pixel = localLuminances[x] & 0xff;
\r
136 localBuckets[pixel >> LUMINANCE_SHIFT]++;
\r
138 int blackPoint = estimateBlackPoint(localBuckets);
\r
140 int left = localLuminances[0] & 0xff;
\r
141 int center = localLuminances[1] & 0xff;
\r
142 for (int x = 1; x < width - 1; x++)
\r
144 int right = localLuminances[x + 1] & 0xff;
\r
145 // A simple -1 4 -1 box filter with a weight of 2.
\r
146 int luminance = ((center << 2) - left - right) >> 1;
\r
147 if (luminance < blackPoint)
\r
149 row.set_Renamed(x);
\r
157 public override Binarizer createBinarizer(LuminanceSource source)
\r
159 return new GlobalHistogramBinarizer(source);
\r
162 private void initArrays(int luminanceSize)
\r
164 if (luminances == null || luminances.Length < luminanceSize)
\r
166 luminances = new sbyte[luminanceSize];
\r
168 if (buckets == null)
\r
170 buckets = new int[LUMINANCE_BUCKETS];
\r
174 for (int x = 0; x < LUMINANCE_BUCKETS; x++)
\r
181 private static int estimateBlackPoint(int[] buckets)
\r
183 // Find the tallest peak in the histogram.
\r
184 int numBuckets = buckets.Length;
\r
185 int maxBucketCount = 0;
\r
187 int firstPeakSize = 0;
\r
188 for (int x = 0; x < numBuckets; x++)
\r
190 if (buckets[x] > firstPeakSize)
\r
193 firstPeakSize = buckets[x];
\r
195 if (buckets[x] > maxBucketCount)
\r
197 maxBucketCount = buckets[x];
\r
201 // Find the second-tallest peak which is somewhat far from the tallest peak.
\r
202 int secondPeak = 0;
\r
203 int secondPeakScore = 0;
\r
204 for (int x = 0; x < numBuckets; x++)
\r
206 int distanceToBiggest = x - firstPeak;
\r
207 // Encourage more distant second peaks by multiplying by square of distance.
\r
208 int score = buckets[x] * distanceToBiggest * distanceToBiggest;
\r
209 if (score > secondPeakScore)
\r
212 secondPeakScore = score;
\r
216 // Make sure firstPeak corresponds to the black peak.
\r
217 if (firstPeak > secondPeak)
\r
219 int temp = firstPeak;
\r
220 firstPeak = secondPeak;
\r
224 // If there is too little contrast in the image to pick a meaningful black point, throw rather
\r
225 // than waste time trying to decode the image, and risk false positives.
\r
226 // TODO: It might be worth comparing the brightest and darkest pixels seen, rather than the
\r
227 // two peaks, to determine the contrast.
\r
228 if (secondPeak - firstPeak <= numBuckets >> 4)
\r
230 throw ReaderException.Instance;
\r
233 // Find a valley between them that is low and closer to the white peak.
\r
234 int bestValley = secondPeak - 1;
\r
235 int bestValleyScore = - 1;
\r
236 for (int x = secondPeak - 1; x > firstPeak; x--)
\r
238 int fromFirst = x - firstPeak;
\r
239 int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]);
\r
240 if (score > bestValleyScore)
\r
243 bestValleyScore = score;
\r
247 return bestValley << LUMINANCE_SHIFT;
\r