Committed C# port from Mohamad
[zxing.git] / csharp / oned / AbstractOneDReader.cs
1 /*\r
2 * Copyright 2007 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 namespace com.google.zxing.oned\r
17 {\r
18     using System;\r
19     using com.google.zxing.common;\r
20     \r
21     public abstract class AbstractOneDReader \r
22     {\r
23           private static  int INTEGER_MATH_SHIFT = 8;\r
24           public static  int PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT;\r
25 \r
26           public  Result decode(MonochromeBitmapSource image) {\r
27             return decode(image, null);\r
28           }\r
29 \r
30           public  Result decode(MonochromeBitmapSource image, System.Collections.Hashtable hints) {\r
31             try {\r
32               return doDecode(image, hints);\r
33             } catch (ReaderException re) {\r
34               bool tryHarder = hints != null && hints.ContainsKey(DecodeHintType.TRY_HARDER);\r
35               if (tryHarder && image.isRotateSupported()) {\r
36                 MonochromeBitmapSource rotatedImage = image.rotateCounterClockwise();\r
37                 Result result = doDecode(rotatedImage, hints);\r
38                 // Record that we found it rotated 90 degrees CCW / 270 degrees CW\r
39                 System.Collections.Hashtable metadata = result.getResultMetadata();\r
40                 int orientation = 270;\r
41                 if (metadata != null && metadata.ContainsKey(ResultMetadataType.ORIENTATION)) {\r
42                   // But if we found it reversed in doDecode(), add in that result here:\r
43                   orientation = (orientation + ((int) metadata[ResultMetadataType.ORIENTATION])) % 360;\r
44                 }\r
45                 result.putMetadata(ResultMetadataType.ORIENTATION, orientation);\r
46                 return result;\r
47               } else {\r
48                 throw re;\r
49               }\r
50             }\r
51           }\r
52 \r
53           /**\r
54            * We're going to examine rows from the middle outward, searching alternately above and below the\r
55            * middle, and farther out each time. rowStep is the number of rows between each successive\r
56            * attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then\r
57            * middle + rowStep, then middle - (2 * rowStep), etc.\r
58            * rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily\r
59            * decided that moving up and down by about 1/16 of the image is pretty good; we try more of the\r
60            * image if "trying harder".\r
61            *\r
62            * @param image The image to decode\r
63            * @param hints Any hints that were requested\r
64            * @return The contents of the decoded barcode\r
65            * @throws ReaderException Any spontaneous errors which occur\r
66            */\r
67           private Result doDecode(MonochromeBitmapSource image, System.Collections.Hashtable hints) {\r
68             int width = image.getWidth();\r
69             int height = image.getHeight();\r
70             BitArray row = new BitArray(width);\r
71 \r
72             int middle = height >> 1;\r
73             bool tryHarder = hints != null && hints.ContainsKey(DecodeHintType.TRY_HARDER);\r
74             int rowStep = Math.Max(1, height >> (tryHarder ? 7 : 4));\r
75             int MaxLines;\r
76             if (tryHarder) {\r
77               MaxLines = height; // Look at the whole image, not just the center\r
78             } else {\r
79               MaxLines = 9; // Nine rows spaced 1/16 apart is roughly the middle half of the image\r
80             }\r
81 \r
82             for (int x = 0; x < MaxLines; x++) {\r
83 \r
84               // Scanning from the middle out. Determine which row we're looking at next:\r
85               int rowStepsAboveOrBelow = (x + 1) >> 1;\r
86               bool isAbove = (x & 0x01) == 0; // i.e. is x even?\r
87               int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);\r
88               if (rowNumber < 0 || rowNumber >= height) {\r
89                 // Oops, if we run off the top or bottom, stop\r
90                 break;\r
91               }\r
92 \r
93               // Estimate black point for this row and load it:\r
94               try {\r
95                 image.estimateBlackPoint(BlackPointEstimationMethod.ROW_SAMPLING, rowNumber);\r
96               } catch (ReaderException re) {\r
97                 continue;\r
98               }\r
99 \r
100               image.getBlackRow(rowNumber, row,0, width);\r
101 \r
102               // While we have the image data in a BitArray, it's fairly cheap to reverse it in place to\r
103               // handle decoding upside down barcodes.\r
104               for (int attempt = 0; attempt < 2; attempt++) {\r
105                 if (attempt == 1) { // trying again?\r
106                   row.reverse(); // reverse the row and continue\r
107                 }\r
108                 try {\r
109                   // Look for a barcode\r
110                   Result result = decodeRow(rowNumber, row, hints);\r
111                   // We found our barcode\r
112                   if (attempt == 1) {\r
113                     // But it was upside down, so note that\r
114                     result.putMetadata(ResultMetadataType.ORIENTATION, 180);\r
115                     // And remember to flip the result points horizontally.\r
116                     ResultPoint[] points = result.getResultPoints();\r
117                     points[0] = (ResultPoint) new GenericResultPoint(width - points[0].getX() - 1, points[0].getY());\r
118                     points[1] = (ResultPoint)new GenericResultPoint(width - points[1].getX() - 1, points[1].getY());\r
119                   }\r
120                   return result;\r
121                 } catch (ReaderException re) {\r
122                   // continue -- just couldn't decode this row\r
123                 }\r
124               }\r
125             }\r
126 \r
127             throw new ReaderException();\r
128           }\r
129 \r
130           /**\r
131            * Records the size of successive runs of white and black pixels in a row, starting at a given point.\r
132            * The values are recorded in the given array, and the number of runs recorded is equal to the size\r
133            * of the array. If the row starts on a white pixel at the given start point, then the first count\r
134            * recorded is the run of white pixels starting from that point; likewise it is the count of a run\r
135            * of black pixels if the row begin on a black pixels at that point.\r
136            *\r
137            * @param row row to count from\r
138            * @param start offset into row to start at\r
139            * @param counters array into which to record counts\r
140            * @throws ReaderException if counters cannot be filled entirely from row before running out of pixels\r
141            */\r
142           public static void recordPattern(BitArray row, int start, int[] counters) {\r
143             int numCounters = counters.Length;\r
144             for (int i = 0; i < numCounters; i++) {\r
145               counters[i] = 0;\r
146             }\r
147             int end = row.getSize();\r
148             if (start >= end) {\r
149               throw new ReaderException();\r
150             }\r
151             bool isWhite = !row.get(start);\r
152             int counterPosition = 0;\r
153 \r
154             int k = start;\r
155             while (k < end) {\r
156               bool pixel = row.get(k);\r
157               if ((!pixel && isWhite) || (pixel && !isWhite)) {\r
158                 counters[counterPosition]++;\r
159               } else {\r
160                 counterPosition++;\r
161                 if (counterPosition == numCounters) {\r
162                   break;\r
163                 } else {\r
164                   counters[counterPosition] = 1;\r
165                   isWhite = !isWhite;\r
166                 }\r
167               }\r
168               k++;\r
169             }\r
170             // If we read fully the last section of pixels and filled up our counters -- or filled\r
171             // the last counter but ran off the side of the image, OK. Otherwise, a problem.\r
172             if (!(counterPosition == numCounters || (counterPosition == numCounters - 1 && k == end))) {\r
173               throw new ReaderException();\r
174             }\r
175           }\r
176 \r
177           /**\r
178            * Determines how closely a set of observed counts of runs of black/white values matches a given\r
179            * target pattern. This is reported as the ratio of the total variance from the expected pattern\r
180            * proportions across all pattern elements, to the length of the pattern.\r
181            *\r
182            * @param counters observed counters\r
183            * @param pattern expected pattern\r
184            * @param MaxIndividualVariance The most any counter can differ before we give up\r
185            * @return ratio of total variance between counters and pattern compared to total pattern size,\r
186            *  where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means\r
187            *  the total variance between counters and patterns equals the pattern length, higher values mean\r
188            *  even more variance\r
189            */\r
190           public static int patternMatchVariance(int[] counters, int[] pattern, int MaxIndividualVariance) {\r
191             int numCounters = counters.Length;\r
192             int total = 0;\r
193             int patternLength = 0;\r
194             for (int i = 0; i < numCounters; i++) {\r
195               total += counters[i];\r
196               patternLength += pattern[i];\r
197             }\r
198             if (total < patternLength) {\r
199               // If we don't even have one pixel per unit of bar width, assume this is too small\r
200               // to reliably match, so fail:\r
201               return int.MaxValue;\r
202             }\r
203             // We're going to fake floating-point math in integers. We just need to use more bits.\r
204             // Scale up patternLength so that intermediate values below like scaledCounter will have\r
205             // more "significant digits"\r
206             int unitBarWidth = (total << INTEGER_MATH_SHIFT) / patternLength;\r
207             MaxIndividualVariance = (MaxIndividualVariance * unitBarWidth) >> INTEGER_MATH_SHIFT;\r
208 \r
209             int totalVariance = 0;\r
210             for (int x = 0; x < numCounters; x++) {\r
211               int counter = counters[x] << INTEGER_MATH_SHIFT;\r
212               int scaledPattern = pattern[x] * unitBarWidth;\r
213               int variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;\r
214               if (variance > MaxIndividualVariance) {\r
215                 return int.MaxValue;\r
216               }\r
217               totalVariance += variance; \r
218             }\r
219             return totalVariance / total;\r
220           }\r
221 \r
222           // This declaration should not be necessary, since this class is\r
223           // abstract and so does not have to provide an implementation for every\r
224           // method of an interface it implements, but it is causing NoSuchMethodError\r
225           // issues on some Nokia JVMs. So we add this superfluous declaration:\r
226 \r
227           public abstract Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints);\r
228   \r
229     \r
230     }\r
231 }