Morgan's cosmetic improvement to a translation, and equivalent for other translations...
[zxing.git] / csharp / common / GlobalHistogramBinarizer.cs
1 /*\r
2 * Copyright 2009 ZXing authors\r
3 *\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
7 *\r
8 *      http://www.apache.org/licenses/LICENSE-2.0\r
9 *\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
15 */\r
16 using System;\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
21 {\r
22         \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
26         /// and gradients.\r
27         /// \r
28         /// Faster mobile devices and all desktop applications should probably use HybridBinarizer instead.\r
29         /// \r
30         /// </summary>\r
31         /// <author>  dswitkin@google.com (Daniel Switkin)\r
32         /// </author>\r
33         /// <author>  Sean Owen\r
34         /// </author>\r
35         /// <author>www.Redivivus.in (suraj.supekar@redivivus.in) - Ported from ZXING Java Source \r
36         /// </author>\r
37         public class GlobalHistogramBinarizer:Binarizer\r
38         {\r
39                 override public BitMatrix BlackMatrix\r
40                 {\r
41                         // Does not sharpen the data, as this call is intended to only be used by 2D Readers.\r
42                         \r
43                         get\r
44                         {\r
45                                 LuminanceSource source = LuminanceSource;\r
46                 // Redivivus.in Java to c# Porting update\r
47                 // 30/01/2010 \r
48                 // Added\r
49                 // START\r
50                 sbyte[] localLuminances;\r
51                                 //END\r
52                 \r
53                 int width = source.Width;\r
54                                 int height = source.Height;\r
55                                 BitMatrix matrix = new BitMatrix(width, height);\r
56                                 \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
59                                 initArrays(width);\r
60                                 int[] localBuckets = buckets;\r
61                                 for (int y = 1; y < 5; y++)\r
62                                 {\r
63                                         int row = height * y / 5;\r
64                     // Redivivus.in Java to c# Porting update\r
65                     // 30/01/2010 \r
66                     // Commented & Added\r
67                     // START\r
68                     //sbyte[] localLuminances = source.getRow(row, luminances);\r
69                     localLuminances = source.getRow(row, luminances);\r
70                     // END\r
71                                         int right = (width << 2) / 5;\r
72                                         for (int x = width / 5; x < right; x++)\r
73                                         {\r
74                                                 int pixel = localLuminances[x] & 0xff;\r
75                                                 localBuckets[pixel >> LUMINANCE_SHIFT]++;\r
76                                         }\r
77                                 }\r
78                                 int blackPoint = estimateBlackPoint(localBuckets);\r
79                                 \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
83 \r
84                                 localLuminances = source.Matrix; // Govinda : Removed sbyte []\r
85                                 for (int y = 0; y < height; y++)\r
86                                 {\r
87                                         int offset = y * width;\r
88                                         for (int x = 0; x < width; x++)\r
89                                         {\r
90                                                 int pixel = localLuminances[offset + x] & 0xff;\r
91                                                 if (pixel < blackPoint)\r
92                                                 {\r
93                                                         matrix.set_Renamed(x, y);\r
94                                                 }\r
95                                         }\r
96                                 }\r
97                                 \r
98                                 return matrix;\r
99                         }\r
100                         \r
101                 }\r
102                 \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
108                 \r
109                 private sbyte[] luminances = null;\r
110                 private int[] buckets = null;\r
111                 \r
112                 public GlobalHistogramBinarizer(LuminanceSource source):base(source)\r
113                 {\r
114                 }\r
115                 \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
118                 {\r
119                         LuminanceSource source = LuminanceSource;\r
120                         int width = source.Width;\r
121                         if (row == null || row.Size < width)\r
122                         {\r
123                                 row = new BitArray(width);\r
124                         }\r
125                         else\r
126                         {\r
127                                 row.clear();\r
128                         }\r
129                         \r
130                         initArrays(width);\r
131                         sbyte[] localLuminances = source.getRow(y, luminances);\r
132                         int[] localBuckets = buckets;\r
133                         for (int x = 0; x < width; x++)\r
134                         {\r
135                                 int pixel = localLuminances[x] & 0xff;\r
136                                 localBuckets[pixel >> LUMINANCE_SHIFT]++;\r
137                         }\r
138                         int blackPoint = estimateBlackPoint(localBuckets);\r
139                         \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
143                         {\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
148                                 {\r
149                                         row.set_Renamed(x);\r
150                                 }\r
151                                 left = center;\r
152                                 center = right;\r
153                         }\r
154                         return row;\r
155                 }\r
156                 \r
157                 public override Binarizer createBinarizer(LuminanceSource source)\r
158                 {\r
159                         return new GlobalHistogramBinarizer(source);\r
160                 }\r
161                 \r
162                 private void  initArrays(int luminanceSize)\r
163                 {\r
164                         if (luminances == null || luminances.Length < luminanceSize)\r
165                         {\r
166                                 luminances = new sbyte[luminanceSize];\r
167                         }\r
168                         if (buckets == null)\r
169                         {\r
170                                 buckets = new int[LUMINANCE_BUCKETS];\r
171                         }\r
172                         else\r
173                         {\r
174                                 for (int x = 0; x < LUMINANCE_BUCKETS; x++)\r
175                                 {\r
176                                         buckets[x] = 0;\r
177                                 }\r
178                         }\r
179                 }\r
180                 \r
181                 private static int estimateBlackPoint(int[] buckets)\r
182                 {\r
183                         // Find the tallest peak in the histogram.\r
184                         int numBuckets = buckets.Length;\r
185                         int maxBucketCount = 0;\r
186                         int firstPeak = 0;\r
187                         int firstPeakSize = 0;\r
188                         for (int x = 0; x < numBuckets; x++)\r
189                         {\r
190                                 if (buckets[x] > firstPeakSize)\r
191                                 {\r
192                                         firstPeak = x;\r
193                                         firstPeakSize = buckets[x];\r
194                                 }\r
195                                 if (buckets[x] > maxBucketCount)\r
196                                 {\r
197                                         maxBucketCount = buckets[x];\r
198                                 }\r
199                         }\r
200                         \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
205                         {\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
210                                 {\r
211                                         secondPeak = x;\r
212                                         secondPeakScore = score;\r
213                                 }\r
214                         }\r
215                         \r
216                         // Make sure firstPeak corresponds to the black peak.\r
217                         if (firstPeak > secondPeak)\r
218                         {\r
219                                 int temp = firstPeak;\r
220                                 firstPeak = secondPeak;\r
221                                 secondPeak = temp;\r
222                         }\r
223                         \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
229                         {\r
230                                 throw ReaderException.Instance;\r
231                         }\r
232                         \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
237                         {\r
238                                 int fromFirst = x - firstPeak;\r
239                                 int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]);\r
240                                 if (score > bestValleyScore)\r
241                                 {\r
242                                         bestValley = x;\r
243                                         bestValleyScore = score;\r
244                                 }\r
245                         }\r
246                         \r
247                         return bestValley << LUMINANCE_SHIFT;\r
248                 }\r
249         }\r
250 }