Issues 155.2 -- add %f for format
[zxing.git] / csharp / oned / OneDReader.cs
1 /*\r
2 * Copyright 2008 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 BinaryBitmap = com.google.zxing.BinaryBitmap;\r
18 using DecodeHintType = com.google.zxing.DecodeHintType;\r
19 using Reader = com.google.zxing.Reader;\r
20 using ReaderException = com.google.zxing.ReaderException;\r
21 using Result = com.google.zxing.Result;\r
22 using ResultMetadataType = com.google.zxing.ResultMetadataType;\r
23 using ResultPoint = com.google.zxing.ResultPoint;\r
24 using BitArray = com.google.zxing.common.BitArray;\r
25 namespace com.google.zxing.oned\r
26 {\r
27         \r
28         /// <summary> Encapsulates functionality and implementation that is common to all families\r
29         /// of one-dimensional barcodes.\r
30         /// \r
31         /// </summary>\r
32         /// <author>  dswitkin@google.com (Daniel Switkin)\r
33         /// </author>\r
34         /// <author>  Sean Owen\r
35         /// </author>\r
36         /// <author>www.Redivivus.in (suraj.supekar@redivivus.in) - Ported from ZXING Java Source \r
37         /// </author>\r
38         public abstract class OneDReader : Reader\r
39         {\r
40                 \r
41                 private const int INTEGER_MATH_SHIFT = 8;\r
42                 //UPGRADE_NOTE: Final was removed from the declaration of 'PATTERN_MATCH_RESULT_SCALE_FACTOR '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
43                 internal static readonly int PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT;\r
44                 \r
45                 public virtual Result decode(BinaryBitmap image)\r
46                 {\r
47                         return decode(image, null);\r
48                 }\r
49                 \r
50                 // Note that we don't try rotation without the try harder flag, even if rotation was supported.\r
51                 public virtual Result decode(BinaryBitmap image, System.Collections.Hashtable hints)\r
52                 {\r
53                         try\r
54                         {\r
55                                 return doDecode(image, hints);\r
56                         }\r
57                         catch (ReaderException re)\r
58                         {\r
59                                 bool tryHarder = hints != null && hints.ContainsKey(DecodeHintType.TRY_HARDER);\r
60                                 if (tryHarder && image.RotateSupported)\r
61                                 {\r
62                                         BinaryBitmap rotatedImage = image.rotateCounterClockwise();\r
63                                         Result result = doDecode(rotatedImage, hints);\r
64                                         // Record that we found it rotated 90 degrees CCW / 270 degrees CW\r
65                                         System.Collections.Hashtable metadata = result.ResultMetadata;\r
66                                         int orientation = 270;\r
67                                         if (metadata != null && metadata.ContainsKey(ResultMetadataType.ORIENTATION))\r
68                                         {\r
69                                                 // But if we found it reversed in doDecode(), add in that result here:\r
70                                                 orientation = (orientation + ((System.Int32) metadata[ResultMetadataType.ORIENTATION])) % 360;\r
71                                         }\r
72                                         result.putMetadata(ResultMetadataType.ORIENTATION, (System.Object) orientation);\r
73                                         // Update result points\r
74                                         ResultPoint[] points = result.ResultPoints;\r
75                                         int height = rotatedImage.Height;\r
76                                         for (int i = 0; i < points.Length; i++)\r
77                                         {\r
78                                                 points[i] = new ResultPoint(height - points[i].Y - 1, points[i].X);\r
79                                         }\r
80                                         return result;\r
81                                 }\r
82                                 else\r
83                                 {\r
84                                         throw re;\r
85                                 }\r
86                         }\r
87                 }\r
88                 \r
89                 /// <summary> We're going to examine rows from the middle outward, searching alternately above and below the\r
90                 /// middle, and farther out each time. rowStep is the number of rows between each successive\r
91                 /// attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then\r
92                 /// middle + rowStep, then middle - (2 * rowStep), etc.\r
93                 /// rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily\r
94                 /// decided that moving up and down by about 1/16 of the image is pretty good; we try more of the\r
95                 /// image if "trying harder".\r
96                 /// \r
97                 /// </summary>\r
98                 /// <param name="image">The image to decode\r
99                 /// </param>\r
100                 /// <param name="hints">Any hints that were requested\r
101                 /// </param>\r
102                 /// <returns> The contents of the decoded barcode\r
103                 /// </returns>\r
104                 /// <throws>  ReaderException Any spontaneous errors which occur </throws>\r
105                 private Result doDecode(BinaryBitmap image, System.Collections.Hashtable hints)\r
106                 {\r
107                         int width = image.Width;\r
108                         int height = image.Height;\r
109                         BitArray row = new BitArray(width);\r
110                         \r
111                         int middle = height >> 1;\r
112                         bool tryHarder = hints != null && hints.ContainsKey(DecodeHintType.TRY_HARDER);\r
113                         int rowStep = System.Math.Max(1, height >> (tryHarder?7:4));\r
114                         int maxLines;\r
115                         if (tryHarder)\r
116                         {\r
117                                 maxLines = height; // Look at the whole image, not just the center\r
118                         }\r
119                         else\r
120                         {\r
121                                 maxLines = 9; // Nine rows spaced 1/16 apart is roughly the middle half of the image\r
122                         }\r
123                         \r
124                         for (int x = 0; x < maxLines; x++)\r
125                         {\r
126                                 \r
127                                 // Scanning from the middle out. Determine which row we're looking at next:\r
128                                 int rowStepsAboveOrBelow = (x + 1) >> 1;\r
129                                 bool isAbove = (x & 0x01) == 0; // i.e. is x even?\r
130                                 int rowNumber = middle + rowStep * (isAbove?rowStepsAboveOrBelow:- rowStepsAboveOrBelow);\r
131                                 if (rowNumber < 0 || rowNumber >= height)\r
132                                 {\r
133                                         // Oops, if we run off the top or bottom, stop\r
134                                         break;\r
135                                 }\r
136                                 \r
137                                 // Estimate black point for this row and load it:\r
138                                 try\r
139                                 {\r
140                                         row = image.getBlackRow(rowNumber, row);\r
141                                 }\r
142                                 catch (ReaderException re)\r
143                                 {\r
144                                         continue;\r
145                                 }\r
146                                 \r
147                                 // While we have the image data in a BitArray, it's fairly cheap to reverse it in place to\r
148                                 // handle decoding upside down barcodes.\r
149                                 for (int attempt = 0; attempt < 2; attempt++)\r
150                                 {\r
151                                         if (attempt == 1)\r
152                                         {\r
153                                                 // trying again?\r
154                                                 row.reverse(); // reverse the row and continue\r
155                                                 // This means we will only ever draw result points *once* in the life of this method\r
156                                                 // since we want to avoid drawing the wrong points after flipping the row, and,\r
157                                                 // don't want to clutter with noise from every single row scan -- just the scans\r
158                                                 // that start on the center line.\r
159                                                 if (hints != null && hints.ContainsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK))\r
160                                                 {\r
161                                                         System.Collections.Hashtable newHints = System.Collections.Hashtable.Synchronized(new System.Collections.Hashtable()); // Can't use clone() in J2ME\r
162                                                         System.Collections.IEnumerator hintEnum = hints.Keys.GetEnumerator();\r
163                                                         //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"\r
164                                                         while (hintEnum.MoveNext())\r
165                                                         {\r
166                                                                 //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"\r
167                                                                 System.Object key = hintEnum.Current;\r
168                                                                 if (!key.Equals(DecodeHintType.NEED_RESULT_POINT_CALLBACK))\r
169                                                                 {\r
170                                                                         newHints[key] = hints[key];\r
171                                                                 }\r
172                                                         }\r
173                                                         hints = newHints;\r
174                                                 }\r
175                                         }\r
176                                         try\r
177                                         {\r
178                                                 // Look for a barcode\r
179                                                 Result result = decodeRow(rowNumber, row, hints);\r
180                                                 // We found our barcode\r
181                                                 if (attempt == 1)\r
182                                                 {\r
183                                                         // But it was upside down, so note that\r
184                                                         result.putMetadata(ResultMetadataType.ORIENTATION, (System.Object) 180);\r
185                                                         // And remember to flip the result points horizontally.\r
186                                                         ResultPoint[] points = result.ResultPoints;\r
187                                                         points[0] = new ResultPoint(width - points[0].X - 1, points[0].Y);\r
188                                                         points[1] = new ResultPoint(width - points[1].X - 1, points[1].Y);\r
189                                                 }\r
190                                                 return result;\r
191                                         }\r
192                                         catch (ReaderException re)\r
193                                         {\r
194                                                 // continue -- just couldn't decode this row\r
195                                         }\r
196                                 }\r
197                         }\r
198                         \r
199                         throw ReaderException.Instance;\r
200                 }\r
201                 \r
202                 /// <summary> Records the size of successive runs of white and black pixels in a row, starting at a given point.\r
203                 /// The values are recorded in the given array, and the number of runs recorded is equal to the size\r
204                 /// of the array. If the row starts on a white pixel at the given start point, then the first count\r
205                 /// recorded is the run of white pixels starting from that point; likewise it is the count of a run\r
206                 /// of black pixels if the row begin on a black pixels at that point.\r
207                 /// \r
208                 /// </summary>\r
209                 /// <param name="row">row to count from\r
210                 /// </param>\r
211                 /// <param name="start">offset into row to start at\r
212                 /// </param>\r
213                 /// <param name="counters">array into which to record counts\r
214                 /// </param>\r
215                 /// <throws>  ReaderException if counters cannot be filled entirely from row before running out </throws>\r
216                 /// <summary>  of pixels\r
217                 /// </summary>\r
218                 internal static void  recordPattern(BitArray row, int start, int[] counters)\r
219                 {\r
220                         int numCounters = counters.Length;\r
221                         for (int i = 0; i < numCounters; i++)\r
222                         {\r
223                                 counters[i] = 0;\r
224                         }\r
225                         int end = row.Size;\r
226                         if (start >= end)\r
227                         {\r
228                                 throw ReaderException.Instance;\r
229                         }\r
230                         bool isWhite = !row.get_Renamed(start);\r
231                         int counterPosition = 0;\r
232                         int i2 = start;\r
233                         while (i2 < end)\r
234                         {\r
235                                 bool pixel = row.get_Renamed(i2);\r
236                                 if (pixel ^ isWhite)\r
237                                 {\r
238                                         // that is, exactly one is true\r
239                                         counters[counterPosition]++;\r
240                                 }\r
241                                 else\r
242                                 {\r
243                                         counterPosition++;\r
244                                         if (counterPosition == numCounters)\r
245                                         {\r
246                                                 break;\r
247                                         }\r
248                                         else\r
249                                         {\r
250                                                 counters[counterPosition] = 1;\r
251                                                 isWhite ^= true; // isWhite = !isWhite;\r
252                                         }\r
253                                 }\r
254                                 i2++;\r
255                         }\r
256                         // If we read fully the last section of pixels and filled up our counters -- or filled\r
257                         // the last counter but ran off the side of the image, OK. Otherwise, a problem.\r
258                         if (!(counterPosition == numCounters || (counterPosition == numCounters - 1 && i2 == end)))\r
259                         {\r
260                                 throw ReaderException.Instance;\r
261                         }\r
262                 }\r
263                 \r
264                 /// <summary> Determines how closely a set of observed counts of runs of black/white values matches a given\r
265                 /// target pattern. This is reported as the ratio of the total variance from the expected pattern\r
266                 /// proportions across all pattern elements, to the length of the pattern.\r
267                 /// \r
268                 /// </summary>\r
269                 /// <param name="counters">observed counters\r
270                 /// </param>\r
271                 /// <param name="pattern">expected pattern\r
272                 /// </param>\r
273                 /// <param name="maxIndividualVariance">The most any counter can differ before we give up\r
274                 /// </param>\r
275                 /// <returns> ratio of total variance between counters and pattern compared to total pattern size,\r
276                 /// where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means\r
277                 /// the total variance between counters and patterns equals the pattern length, higher values mean\r
278                 /// even more variance\r
279                 /// </returns>\r
280                 internal static int patternMatchVariance(int[] counters, int[] pattern, int maxIndividualVariance)\r
281                 {\r
282                         int numCounters = counters.Length;\r
283                         int total = 0;\r
284                         int patternLength = 0;\r
285                         for (int i = 0; i < numCounters; i++)\r
286                         {\r
287                                 total += counters[i];\r
288                                 patternLength += pattern[i];\r
289                         }\r
290                         if (total < patternLength)\r
291                         {\r
292                                 // If we don't even have one pixel per unit of bar width, assume this is too small\r
293                                 // to reliably match, so fail:\r
294                                 return System.Int32.MaxValue;\r
295                         }\r
296                         // We're going to fake floating-point math in integers. We just need to use more bits.\r
297                         // Scale up patternLength so that intermediate values below like scaledCounter will have\r
298                         // more "significant digits"\r
299                         int unitBarWidth = (total << INTEGER_MATH_SHIFT) / patternLength;\r
300                         maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> INTEGER_MATH_SHIFT;\r
301                         \r
302                         int totalVariance = 0;\r
303                         for (int x = 0; x < numCounters; x++)\r
304                         {\r
305                                 int counter = counters[x] << INTEGER_MATH_SHIFT;\r
306                                 int scaledPattern = pattern[x] * unitBarWidth;\r
307                                 int variance = counter > scaledPattern?counter - scaledPattern:scaledPattern - counter;\r
308                                 if (variance > maxIndividualVariance)\r
309                                 {\r
310                                         return System.Int32.MaxValue;\r
311                                 }\r
312                                 totalVariance += variance;\r
313                         }\r
314                         return totalVariance / total;\r
315                 }\r
316                 \r
317                 /// <summary> <p>Attempts to decode a one-dimensional barcode format given a single row of\r
318                 /// an image.</p>\r
319                 /// \r
320                 /// </summary>\r
321                 /// <param name="rowNumber">row number from top of the row\r
322                 /// </param>\r
323                 /// <param name="row">the black/white pixel data of the row\r
324                 /// </param>\r
325                 /// <param name="hints">decode hints\r
326                 /// </param>\r
327                 /// <returns> {@link Result} containing encoded string and start/end of barcode\r
328                 /// </returns>\r
329                 /// <throws>  ReaderException if an error occurs or barcode cannot be found </throws>\r
330                 public abstract Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints);\r
331         }\r
332 }