Issues 155.2 -- add %f for format
[zxing.git] / csharp / oned / UPCEANReader.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 ReaderException = com.google.zxing.ReaderException;\r
18 using Result = com.google.zxing.Result;\r
19 using ResultPointCallback = com.google.zxing.ResultPointCallback;\r
20 using DecodeHintType = com.google.zxing.DecodeHintType;\r
21 using ResultPoint = com.google.zxing.ResultPoint;\r
22 using BarcodeFormat = com.google.zxing.BarcodeFormat;\r
23 using BitArray = com.google.zxing.common.BitArray;\r
24 namespace com.google.zxing.oned\r
25 {\r
26         \r
27         /// <summary> <p>Encapsulates functionality and implementation that is common to UPC and EAN families\r
28         /// of one-dimensional barcodes.</p>\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>  alasdair@google.com (Alasdair Mackintosh)\r
36         /// </author>\r
37         /// <author>www.Redivivus.in (suraj.supekar@redivivus.in) - Ported from ZXING Java Source \r
38         /// </author>\r
39         public abstract class UPCEANReader:OneDReader\r
40         {\r
41                 /// <summary> Get the format of this decoder.\r
42                 /// \r
43                 /// </summary>\r
44                 /// <returns> The 1D format.\r
45                 /// </returns>\r
46                 internal abstract BarcodeFormat BarcodeFormat{get;}\r
47                 \r
48                 // These two values are critical for determining how permissive the decoding will be.\r
49                 // We've arrived at these values through a lot of trial and error. Setting them any higher\r
50                 // lets false positives creep in quickly.\r
51                 //UPGRADE_NOTE: Final was removed from the declaration of 'MAX_AVG_VARIANCE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
52                 //UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"\r
53                 private static readonly int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);\r
54                 //UPGRADE_NOTE: Final was removed from the declaration of 'MAX_INDIVIDUAL_VARIANCE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
55                 //UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"\r
56                 private static readonly int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7f);\r
57                 \r
58                 /// <summary> Start/end guard pattern.</summary>\r
59                 //UPGRADE_NOTE: Final was removed from the declaration of 'START_END_PATTERN'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
60                 internal static readonly int[] START_END_PATTERN = new int[]{1, 1, 1};\r
61                 \r
62                 /// <summary> Pattern marking the middle of a UPC/EAN pattern, separating the two halves.</summary>\r
63                 //UPGRADE_NOTE: Final was removed from the declaration of 'MIDDLE_PATTERN'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
64                 internal static readonly int[] MIDDLE_PATTERN = new int[]{1, 1, 1, 1, 1};\r
65                 \r
66                 /// <summary> "Odd", or "L" patterns used to encode UPC/EAN digits.</summary>\r
67                 //UPGRADE_NOTE: Final was removed from the declaration of 'L_PATTERNS'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
68                 internal static readonly int[][] L_PATTERNS = new int[][]{new int[]{3, 2, 1, 1}, new int[]{2, 2, 2, 1}, new int[]{2, 1, 2, 2}, new int[]{1, 4, 1, 1}, new int[]{1, 1, 3, 2}, new int[]{1, 2, 3, 1}, new int[]{1, 1, 1, 4}, new int[]{1, 3, 1, 2}, new int[]{1, 2, 1, 3}, new int[]{3, 1, 1, 2}};\r
69                 \r
70                 /// <summary> As above but also including the "even", or "G" patterns used to encode UPC/EAN digits.</summary>\r
71                 internal static int[][] L_AND_G_PATTERNS;\r
72                 \r
73                 //UPGRADE_NOTE: Final was removed from the declaration of 'decodeRowStringBuffer '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
74                 private System.Text.StringBuilder decodeRowStringBuffer;\r
75                 \r
76                 protected internal UPCEANReader()\r
77                 {\r
78                         decodeRowStringBuffer = new System.Text.StringBuilder(20);\r
79                 }\r
80                 \r
81                 internal static int[] findStartGuardPattern(BitArray row)\r
82                 {\r
83                         bool foundStart = false;\r
84                         int[] startRange = null;\r
85                         int nextStart = 0;\r
86                         while (!foundStart)\r
87                         {\r
88                                 startRange = findGuardPattern(row, nextStart, false, START_END_PATTERN);\r
89                                 int start = startRange[0];\r
90                                 nextStart = startRange[1];\r
91                                 // Make sure there is a quiet zone at least as big as the start pattern before the barcode.\r
92                                 // If this check would run off the left edge of the image, do not accept this barcode,\r
93                                 // as it is very likely to be a false positive.\r
94                                 int quietStart = start - (nextStart - start);\r
95                                 if (quietStart >= 0)\r
96                                 {\r
97                                         foundStart = row.isRange(quietStart, start, false);\r
98                                 }\r
99                         }\r
100                         return startRange;\r
101                 }\r
102                 \r
103                 public override Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints)\r
104                 {\r
105                         return decodeRow(rowNumber, row, findStartGuardPattern(row), hints);\r
106                 }\r
107                 \r
108                 /// <summary> <p>Like {@link #decodeRow(int, BitArray, java.util.Hashtable)}, but\r
109                 /// allows caller to inform method about where the UPC/EAN start pattern is\r
110                 /// found. This allows this to be computed once and reused across many implementations.</p>\r
111                 /// </summary>\r
112                 public virtual Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange, System.Collections.Hashtable hints)\r
113                 {\r
114                         \r
115                         ResultPointCallback resultPointCallback = hints == null?null:(ResultPointCallback) hints[DecodeHintType.NEED_RESULT_POINT_CALLBACK];\r
116                         \r
117                         if (resultPointCallback != null)\r
118                         {\r
119                                 resultPointCallback.foundPossibleResultPoint(new ResultPoint((startGuardRange[0] + startGuardRange[1]) / 2.0f, rowNumber));\r
120                         }\r
121                         \r
122                         System.Text.StringBuilder result = decodeRowStringBuffer;\r
123                         result.Length = 0;\r
124                         int endStart = decodeMiddle(row, startGuardRange, result);\r
125                         \r
126                         if (resultPointCallback != null)\r
127                         {\r
128                                 resultPointCallback.foundPossibleResultPoint(new ResultPoint(endStart, rowNumber));\r
129                         }\r
130                         \r
131                         int[] endRange = decodeEnd(row, endStart);\r
132                         \r
133                         if (resultPointCallback != null)\r
134                         {\r
135                                 resultPointCallback.foundPossibleResultPoint(new ResultPoint((endRange[0] + endRange[1]) / 2.0f, rowNumber));\r
136                         }\r
137                         \r
138                         \r
139                         // Make sure there is a quiet zone at least as big as the end pattern after the barcode. The\r
140                         // spec might want more whitespace, but in practice this is the maximum we can count on.\r
141                         int end = endRange[1];\r
142                         int quietEnd = end + (end - endRange[0]);\r
143                         if (quietEnd >= row.Size || !row.isRange(end, quietEnd, false))\r
144                         {\r
145                                 throw ReaderException.Instance;\r
146                         }\r
147                         \r
148                         System.String resultString = result.ToString();\r
149                         if (!checkChecksum(resultString))\r
150                         {\r
151                                 throw ReaderException.Instance;\r
152                         }\r
153                         \r
154                         //UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"\r
155                         float left = (float) (startGuardRange[1] + startGuardRange[0]) / 2.0f;\r
156                         //UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"\r
157                         float right = (float) (endRange[1] + endRange[0]) / 2.0f;\r
158                         //UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"\r
159                         return new Result(resultString, null, new ResultPoint[]{new ResultPoint(left, (float) rowNumber), new ResultPoint(right, (float) rowNumber)}, BarcodeFormat);\r
160                 }\r
161                 \r
162                 /// <returns> {@link #checkStandardUPCEANChecksum(String)}\r
163                 /// </returns>\r
164                 //UPGRADE_NOTE: Access modifiers of method 'checkChecksum' were changed to 'protected'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1204'"\r
165                 protected internal virtual bool checkChecksum(System.String s)\r
166                 {\r
167                         return checkStandardUPCEANChecksum(s);\r
168                 }\r
169                 \r
170                 /// <summary> Computes the UPC/EAN checksum on a string of digits, and reports\r
171                 /// whether the checksum is correct or not.\r
172                 /// \r
173                 /// </summary>\r
174                 /// <param name="s">string of digits to check\r
175                 /// </param>\r
176                 /// <returns> true iff string of digits passes the UPC/EAN checksum algorithm\r
177                 /// </returns>\r
178                 /// <throws>  ReaderException if the string does not contain only digits </throws>\r
179                 private static bool checkStandardUPCEANChecksum(System.String s)\r
180                 {\r
181                         int length = s.Length;\r
182                         if (length == 0)\r
183                         {\r
184                                 return false;\r
185                         }\r
186                         \r
187                         int sum = 0;\r
188                         for (int i = length - 2; i >= 0; i -= 2)\r
189                         {\r
190                                 int digit = (int) s[i] - (int) '0';\r
191                                 if (digit < 0 || digit > 9)\r
192                                 {\r
193                                         throw ReaderException.Instance;\r
194                                 }\r
195                                 sum += digit;\r
196                         }\r
197                         sum *= 3;\r
198                         for (int i = length - 1; i >= 0; i -= 2)\r
199                         {\r
200                                 int digit = (int) s[i] - (int) '0';\r
201                                 if (digit < 0 || digit > 9)\r
202                                 {\r
203                                         throw ReaderException.Instance;\r
204                                 }\r
205                                 sum += digit;\r
206                         }\r
207                         return sum % 10 == 0;\r
208                 }\r
209                 \r
210                 //UPGRADE_NOTE: Access modifiers of method 'decodeEnd' were changed to 'protected'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1204'"\r
211                 protected internal virtual int[] decodeEnd(BitArray row, int endStart)\r
212                 {\r
213                         return findGuardPattern(row, endStart, false, START_END_PATTERN);\r
214                 }\r
215                 \r
216                 /// <param name="row">row of black/white values to search\r
217                 /// </param>\r
218                 /// <param name="rowOffset">position to start search\r
219                 /// </param>\r
220                 /// <param name="whiteFirst">if true, indicates that the pattern specifies white/black/white/...\r
221                 /// pixel counts, otherwise, it is interpreted as black/white/black/...\r
222                 /// </param>\r
223                 /// <param name="pattern">pattern of counts of number of black and white pixels that are being\r
224                 /// searched for as a pattern\r
225                 /// </param>\r
226                 /// <returns> start/end horizontal offset of guard pattern, as an array of two ints\r
227                 /// </returns>\r
228                 /// <throws>  ReaderException if pattern is not found </throws>\r
229                 internal static int[] findGuardPattern(BitArray row, int rowOffset, bool whiteFirst, int[] pattern)\r
230                 {\r
231                         int patternLength = pattern.Length;\r
232                         int[] counters = new int[patternLength];\r
233                         int width = row.Size;\r
234                         bool isWhite = false;\r
235                         while (rowOffset < width)\r
236                         {\r
237                                 isWhite = !row.get_Renamed(rowOffset);\r
238                                 if (whiteFirst == isWhite)\r
239                                 {\r
240                                         break;\r
241                                 }\r
242                                 rowOffset++;\r
243                         }\r
244                         \r
245                         int counterPosition = 0;\r
246                         int patternStart = rowOffset;\r
247                         for (int x = rowOffset; x < width; x++)\r
248                         {\r
249                                 bool pixel = row.get_Renamed(x);\r
250                                 if (pixel ^ isWhite)\r
251                                 {\r
252                                         counters[counterPosition]++;\r
253                                 }\r
254                                 else\r
255                                 {\r
256                                         if (counterPosition == patternLength - 1)\r
257                                         {\r
258                                                 if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE)\r
259                                                 {\r
260                                                         return new int[]{patternStart, x};\r
261                                                 }\r
262                                                 patternStart += counters[0] + counters[1];\r
263                                                 for (int y = 2; y < patternLength; y++)\r
264                                                 {\r
265                                                         counters[y - 2] = counters[y];\r
266                                                 }\r
267                                                 counters[patternLength - 2] = 0;\r
268                                                 counters[patternLength - 1] = 0;\r
269                                                 counterPosition--;\r
270                                         }\r
271                                         else\r
272                                         {\r
273                                                 counterPosition++;\r
274                                         }\r
275                                         counters[counterPosition] = 1;\r
276                                         isWhite = !isWhite;\r
277                                 }\r
278                         }\r
279                         throw ReaderException.Instance;\r
280                 }\r
281                 \r
282                 /// <summary> Attempts to decode a single UPC/EAN-encoded digit.\r
283                 /// \r
284                 /// </summary>\r
285                 /// <param name="row">row of black/white values to decode\r
286                 /// </param>\r
287                 /// <param name="counters">the counts of runs of observed black/white/black/... values\r
288                 /// </param>\r
289                 /// <param name="rowOffset">horizontal offset to start decoding from\r
290                 /// </param>\r
291                 /// <param name="patterns">the set of patterns to use to decode -- sometimes different encodings\r
292                 /// for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should\r
293                 /// be used\r
294                 /// </param>\r
295                 /// <returns> horizontal offset of first pixel beyond the decoded digit\r
296                 /// </returns>\r
297                 /// <throws>  ReaderException if digit cannot be decoded </throws>\r
298                 internal static int decodeDigit(BitArray row, int[] counters, int rowOffset, int[][] patterns)\r
299                 {\r
300                         recordPattern(row, rowOffset, counters);\r
301                         int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept\r
302                         int bestMatch = - 1;\r
303                         int max = patterns.Length;\r
304                         for (int i = 0; i < max; i++)\r
305                         {\r
306                                 int[] pattern = patterns[i];\r
307                                 int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);\r
308                                 if (variance < bestVariance)\r
309                                 {\r
310                                         bestVariance = variance;\r
311                                         bestMatch = i;\r
312                                 }\r
313                         }\r
314                         if (bestMatch >= 0)\r
315                         {\r
316                                 return bestMatch;\r
317                         }\r
318                         else\r
319                         {\r
320                                 throw ReaderException.Instance;\r
321                         }\r
322                 }\r
323                 \r
324                 /// <summary> Subclasses override this to decode the portion of a barcode between the start\r
325                 /// and end guard patterns.\r
326                 /// \r
327                 /// </summary>\r
328                 /// <param name="row">row of black/white values to search\r
329                 /// </param>\r
330                 /// <param name="startRange">start/end offset of start guard pattern\r
331                 /// </param>\r
332                 /// <param name="resultString">{@link StringBuffer} to append decoded chars to\r
333                 /// </param>\r
334                 /// <returns> horizontal offset of first pixel after the "middle" that was decoded\r
335                 /// </returns>\r
336                 /// <throws>  ReaderException if decoding could not complete successfully </throws>\r
337                 protected internal abstract int decodeMiddle(BitArray row, int[] startRange, System.Text.StringBuilder resultString);\r
338                 static UPCEANReader()\r
339                 {\r
340                         {\r
341                                 L_AND_G_PATTERNS = new int[20][];\r
342                                 for (int i = 0; i < 10; i++)\r
343                                 {\r
344                                         L_AND_G_PATTERNS[i] = L_PATTERNS[i];\r
345                                 }\r
346                                 for (int i = 10; i < 20; i++)\r
347                                 {\r
348                                         int[] widths = L_PATTERNS[i - 10];\r
349                                         int[] reversedWidths = new int[widths.Length];\r
350                                         for (int j = 0; j < widths.Length; j++)\r
351                                         {\r
352                                                 reversedWidths[j] = widths[widths.Length - j - 1];\r
353                                         }\r
354                                         L_AND_G_PATTERNS[i] = reversedWidths;\r
355                                 }\r
356                         }\r
357                 }\r
358         }\r
359 }