New C# port from Suraj Supekar
[zxing.git] / csharp / qrcode / detector / Detector.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 using System;\r
17 using DecodeHintType = com.google.zxing.DecodeHintType;\r
18 using ReaderException = com.google.zxing.ReaderException;\r
19 using ResultPoint = com.google.zxing.ResultPoint;\r
20 using ResultPointCallback = com.google.zxing.ResultPointCallback;\r
21 using BitMatrix = com.google.zxing.common.BitMatrix;\r
22 using DetectorResult = com.google.zxing.common.DetectorResult;\r
23 using GridSampler = com.google.zxing.common.GridSampler;\r
24 using PerspectiveTransform = com.google.zxing.common.PerspectiveTransform;\r
25 using Version = com.google.zxing.qrcode.decoder.Version;\r
26 namespace com.google.zxing.qrcode.detector\r
27 {\r
28         \r
29         /// <summary> <p>Encapsulates logic that can detect a QR Code in an image, even if the QR Code\r
30         /// is rotated or skewed, or partially obscured.</p>\r
31         /// \r
32         /// </summary>\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 Detector\r
38         {\r
39                 virtual protected internal BitMatrix Image\r
40                 {\r
41                         get\r
42                         {\r
43                                 return image;\r
44                         }\r
45                         \r
46                 }\r
47                 virtual protected internal ResultPointCallback ResultPointCallback\r
48                 {\r
49                         get\r
50                         {\r
51                                 return resultPointCallback;\r
52                         }\r
53                         \r
54                 }\r
55                 \r
56                 //UPGRADE_NOTE: Final was removed from the declaration of 'image '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
57                 private BitMatrix image;\r
58                 private ResultPointCallback resultPointCallback;\r
59                 \r
60                 public Detector(BitMatrix image)\r
61                 {\r
62                         this.image = image;\r
63                 }\r
64                 \r
65                 /// <summary> <p>Detects a QR Code in an image, simply.</p>\r
66                 /// \r
67                 /// </summary>\r
68                 /// <returns> {@link DetectorResult} encapsulating results of detecting a QR Code\r
69                 /// </returns>\r
70                 /// <throws>  ReaderException if no QR Code can be found </throws>\r
71                 public virtual DetectorResult detect()\r
72                 {\r
73                         return detect(null);\r
74                 }\r
75                 \r
76                 /// <summary> <p>Detects a QR Code in an image, simply.</p>\r
77                 /// \r
78                 /// </summary>\r
79                 /// <param name="hints">optional hints to detector\r
80                 /// </param>\r
81                 /// <returns> {@link DetectorResult} encapsulating results of detecting a QR Code\r
82                 /// </returns>\r
83                 /// <throws>  ReaderException if no QR Code can be found </throws>\r
84                 public virtual DetectorResult detect(System.Collections.Hashtable hints)\r
85                 {\r
86                         \r
87                         resultPointCallback = hints == null?null:(ResultPointCallback) hints[DecodeHintType.NEED_RESULT_POINT_CALLBACK];\r
88                         \r
89                         FinderPatternFinder finder = new FinderPatternFinder(image, resultPointCallback);\r
90                         FinderPatternInfo info = finder.find(hints);\r
91                         \r
92                         return processFinderPatternInfo(info);\r
93                 }\r
94                 \r
95                 protected internal virtual DetectorResult processFinderPatternInfo(FinderPatternInfo info)\r
96                 {\r
97                         \r
98                         FinderPattern topLeft = info.TopLeft;\r
99                         FinderPattern topRight = info.TopRight;\r
100                         FinderPattern bottomLeft = info.BottomLeft;\r
101                         \r
102                         float moduleSize = calculateModuleSize(topLeft, topRight, bottomLeft);\r
103                         if (moduleSize < 1.0f)\r
104                         {\r
105                                 throw ReaderException.Instance;\r
106                         }\r
107                         int dimension = computeDimension(topLeft, topRight, bottomLeft, moduleSize);\r
108                         Version provisionalVersion = Version.getProvisionalVersionForDimension(dimension);\r
109                         int modulesBetweenFPCenters = provisionalVersion.DimensionForVersion - 7;\r
110                         \r
111                         AlignmentPattern alignmentPattern = null;\r
112                         // Anything above version 1 has an alignment pattern\r
113                         if (provisionalVersion.AlignmentPatternCenters.Length > 0)\r
114                         {\r
115                                 \r
116                                 // Guess where a "bottom right" finder pattern would have been\r
117                                 float bottomRightX = topRight.X - topLeft.X + bottomLeft.X;\r
118                                 float bottomRightY = topRight.Y - topLeft.Y + bottomLeft.Y;\r
119                                 \r
120                                 // Estimate that alignment pattern is closer by 3 modules\r
121                                 // from "bottom right" to known top left location\r
122                                 //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
123                                 float correctionToTopLeft = 1.0f - 3.0f / (float) modulesBetweenFPCenters;\r
124                                 //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
125                                 int estAlignmentX = (int) (topLeft.X + correctionToTopLeft * (bottomRightX - topLeft.X));\r
126                                 //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
127                                 int estAlignmentY = (int) (topLeft.Y + correctionToTopLeft * (bottomRightY - topLeft.Y));\r
128                                 \r
129                                 // Kind of arbitrary -- expand search radius before giving up\r
130                                 for (int i = 4; i <= 16; i <<= 1)\r
131                                 {\r
132                                         try\r
133                                         {\r
134                                                 //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
135                                                 alignmentPattern = findAlignmentInRegion(moduleSize, estAlignmentX, estAlignmentY, (float) i);\r
136                                                 break;\r
137                                         }\r
138                                         catch (ReaderException re)\r
139                                         {\r
140                                                 // try next round\r
141                                         }\r
142                                 }\r
143                                 // If we didn't find alignment pattern... well try anyway without it\r
144                         }\r
145                         \r
146                         PerspectiveTransform transform = createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension);\r
147                         \r
148                         BitMatrix bits = sampleGrid(image, transform, dimension);\r
149                         \r
150                         ResultPoint[] points;\r
151                         if (alignmentPattern == null)\r
152                         {\r
153                                 points = new ResultPoint[]{bottomLeft, topLeft, topRight};\r
154                         }\r
155                         else\r
156                         {\r
157                                 points = new ResultPoint[]{bottomLeft, topLeft, topRight, alignmentPattern};\r
158                         }\r
159                         return new DetectorResult(bits, points);\r
160                 }\r
161                 \r
162                 public virtual PerspectiveTransform createTransform(ResultPoint topLeft, ResultPoint topRight, ResultPoint bottomLeft, ResultPoint alignmentPattern, int dimension)\r
163                 {\r
164                         //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
165                         float dimMinusThree = (float) dimension - 3.5f;\r
166                         float bottomRightX;\r
167                         float bottomRightY;\r
168                         float sourceBottomRightX;\r
169                         float sourceBottomRightY;\r
170                         if (alignmentPattern != null)\r
171                         {\r
172                                 bottomRightX = alignmentPattern.X;\r
173                                 bottomRightY = alignmentPattern.Y;\r
174                                 sourceBottomRightX = sourceBottomRightY = dimMinusThree - 3.0f;\r
175                         }\r
176                         else\r
177                         {\r
178                                 // Don't have an alignment pattern, just make up the bottom-right point\r
179                                 bottomRightX = (topRight.X - topLeft.X) + bottomLeft.X;\r
180                                 bottomRightY = (topRight.Y - topLeft.Y) + bottomLeft.Y;\r
181                                 sourceBottomRightX = sourceBottomRightY = dimMinusThree;\r
182                         }\r
183                         \r
184                         PerspectiveTransform transform = PerspectiveTransform.quadrilateralToQuadrilateral(3.5f, 3.5f, dimMinusThree, 3.5f, sourceBottomRightX, sourceBottomRightY, 3.5f, dimMinusThree, topLeft.X, topLeft.Y, topRight.X, topRight.Y, bottomRightX, bottomRightY, bottomLeft.X, bottomLeft.Y);\r
185                         \r
186                         return transform;\r
187                 }\r
188                 \r
189                 private static BitMatrix sampleGrid(BitMatrix image, PerspectiveTransform transform, int dimension)\r
190                 {\r
191                         \r
192                         GridSampler sampler = GridSampler.Instance;\r
193                         return sampler.sampleGrid(image, dimension, transform);\r
194                 }\r
195                 \r
196                 /// <summary> <p>Computes the dimension (number of modules on a size) of the QR Code based on the position\r
197                 /// of the finder patterns and estimated module size.</p>\r
198                 /// </summary>\r
199                 protected internal static int computeDimension(ResultPoint topLeft, ResultPoint topRight, ResultPoint bottomLeft, float moduleSize)\r
200                 {\r
201                         int tltrCentersDimension = round(ResultPoint.distance(topLeft, topRight) / moduleSize);\r
202                         int tlblCentersDimension = round(ResultPoint.distance(topLeft, bottomLeft) / moduleSize);\r
203                         int dimension = ((tltrCentersDimension + tlblCentersDimension) >> 1) + 7;\r
204                         switch (dimension & 0x03)\r
205                         {\r
206                                 \r
207                                 // mod 4\r
208                                 case 0: \r
209                                         dimension++;\r
210                                         break;\r
211                                         // 1? do nothing\r
212                                 \r
213                                 case 2: \r
214                                         dimension--;\r
215                                         break;\r
216                                 \r
217                                 case 3: \r
218                                         throw ReaderException.Instance;\r
219                                 }\r
220                         return dimension;\r
221                 }\r
222                 \r
223                 /// <summary> <p>Computes an average estimated module size based on estimated derived from the positions\r
224                 /// of the three finder patterns.</p>\r
225                 /// </summary>\r
226                 protected internal virtual float calculateModuleSize(ResultPoint topLeft, ResultPoint topRight, ResultPoint bottomLeft)\r
227                 {\r
228                         // Take the average\r
229                         return (calculateModuleSizeOneWay(topLeft, topRight) + calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0f;\r
230                 }\r
231                 \r
232                 /// <summary> <p>Estimates module size based on two finder patterns -- it uses\r
233                 /// {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the\r
234                 /// width of each, measuring along the axis between their centers.</p>\r
235                 /// </summary>\r
236                 private float calculateModuleSizeOneWay(ResultPoint pattern, ResultPoint otherPattern)\r
237                 {\r
238                         //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
239                         float moduleSizeEst1 = sizeOfBlackWhiteBlackRunBothWays((int) pattern.X, (int) pattern.Y, (int) otherPattern.X, (int) otherPattern.Y);\r
240                         //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
241                         float moduleSizeEst2 = sizeOfBlackWhiteBlackRunBothWays((int) otherPattern.X, (int) otherPattern.Y, (int) pattern.X, (int) pattern.Y);\r
242                         if (System.Single.IsNaN(moduleSizeEst1))\r
243                         {\r
244                                 return moduleSizeEst2 / 7.0f;\r
245                         }\r
246                         if (System.Single.IsNaN(moduleSizeEst2))\r
247                         {\r
248                                 return moduleSizeEst1 / 7.0f;\r
249                         }\r
250                         // Average them, and divide by 7 since we've counted the width of 3 black modules,\r
251                         // and 1 white and 1 black module on either side. Ergo, divide sum by 14.\r
252                         return (moduleSizeEst1 + moduleSizeEst2) / 14.0f;\r
253                 }\r
254                 \r
255                 /// <summary> See {@link #sizeOfBlackWhiteBlackRun(int, int, int, int)}; computes the total width of\r
256                 /// a finder pattern by looking for a black-white-black run from the center in the direction\r
257                 /// of another point (another finder pattern center), and in the opposite direction too.</p>\r
258                 /// </summary>\r
259                 private float sizeOfBlackWhiteBlackRunBothWays(int fromX, int fromY, int toX, int toY)\r
260                 {\r
261                         \r
262                         float result = sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY);\r
263                         \r
264                         // Now count other way -- don't run off image though of course\r
265                         float scale = 1.0f;\r
266                         int otherToX = fromX - (toX - fromX);\r
267                         if (otherToX < 0)\r
268                         {\r
269                                 //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
270                                 scale = (float) fromX / (float) (fromX - otherToX);\r
271                                 otherToX = 0;\r
272                         }\r
273                         else if (otherToX >= image.Width)\r
274                         {\r
275                                 //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
276                                 scale = (float) (image.Width - 1 - fromX) / (float) (otherToX - fromX);\r
277                                 otherToX = image.Width - 1;\r
278                         }\r
279                         //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
280                         int otherToY = (int) (fromY - (toY - fromY) * scale);\r
281                         \r
282                         scale = 1.0f;\r
283                         if (otherToY < 0)\r
284                         {\r
285                                 //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
286                                 scale = (float) fromY / (float) (fromY - otherToY);\r
287                                 otherToY = 0;\r
288                         }\r
289                         else if (otherToY >= image.Height)\r
290                         {\r
291                                 //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
292                                 scale = (float) (image.Height - 1 - fromY) / (float) (otherToY - fromY);\r
293                                 otherToY = image.Height - 1;\r
294                         }\r
295                         //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
296                         otherToX = (int) (fromX + (otherToX - fromX) * scale);\r
297                         \r
298                         result += sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY);\r
299                         return result - 1.0f; // -1 because we counted the middle pixel twice\r
300                 }\r
301                 \r
302                 /// <summary> <p>This method traces a line from a point in the image, in the direction towards another point.\r
303                 /// It begins in a black region, and keeps going until it finds white, then black, then white again.\r
304                 /// It reports the distance from the start to this point.</p>\r
305                 /// \r
306                 /// <p>This is used when figuring out how wide a finder pattern is, when the finder pattern\r
307                 /// may be skewed or rotated.</p>\r
308                 /// </summary>\r
309                 private float sizeOfBlackWhiteBlackRun(int fromX, int fromY, int toX, int toY)\r
310                 {\r
311                         // Mild variant of Bresenham's algorithm;\r
312                         // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm\r
313                         bool steep = System.Math.Abs(toY - fromY) > System.Math.Abs(toX - fromX);\r
314                         if (steep)\r
315                         {\r
316                                 int temp = fromX;\r
317                                 fromX = fromY;\r
318                                 fromY = temp;\r
319                                 temp = toX;\r
320                                 toX = toY;\r
321                                 toY = temp;\r
322                         }\r
323                         \r
324                         int dx = System.Math.Abs(toX - fromX);\r
325                         int dy = System.Math.Abs(toY - fromY);\r
326                         int error = - dx >> 1;\r
327                         int ystep = fromY < toY?1:- 1;\r
328                         int xstep = fromX < toX?1:- 1;\r
329                         int state = 0; // In black pixels, looking for white, first or second time\r
330                         for (int x = fromX, y = fromY; x != toX; x += xstep)\r
331                         {\r
332                                 \r
333                                 int realX = steep?y:x;\r
334                                 int realY = steep?x:y;\r
335                                 if (state == 1)\r
336                                 {\r
337                                         // In white pixels, looking for black\r
338                                         if (image.get_Renamed(realX, realY))\r
339                                         {\r
340                                                 state++;\r
341                                         }\r
342                                 }\r
343                                 else\r
344                                 {\r
345                                         if (!image.get_Renamed(realX, realY))\r
346                                         {\r
347                                                 state++;\r
348                                         }\r
349                                 }\r
350                                 \r
351                                 if (state == 3)\r
352                                 {\r
353                                         // Found black, white, black, and stumbled back onto white; done\r
354                                         int diffX = x - fromX;\r
355                                         int diffY = y - fromY;\r
356                                         //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
357                                         return (float) System.Math.Sqrt((double) (diffX * diffX + diffY * diffY));\r
358                                 }\r
359                                 error += dy;\r
360                                 if (error > 0)\r
361                                 {\r
362                                         if (y == toY)\r
363                                         {\r
364                                                 break;\r
365                                         }\r
366                                         y += ystep;\r
367                                         error -= dx;\r
368                                 }\r
369                         }\r
370                         int diffX2 = toX - fromX;\r
371                         int diffY2 = toY - fromY;\r
372                         //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
373                         return (float) System.Math.Sqrt((double) (diffX2 * diffX2 + diffY2 * diffY2));\r
374                 }\r
375                 \r
376                 /// <summary> <p>Attempts to locate an alignment pattern in a limited region of the image, which is\r
377                 /// guessed to contain it. This method uses {@link AlignmentPattern}.</p>\r
378                 /// \r
379                 /// </summary>\r
380                 /// <param name="overallEstModuleSize">estimated module size so far\r
381                 /// </param>\r
382                 /// <param name="estAlignmentX">x coordinate of center of area probably containing alignment pattern\r
383                 /// </param>\r
384                 /// <param name="estAlignmentY">y coordinate of above\r
385                 /// </param>\r
386                 /// <param name="allowanceFactor">number of pixels in all directions to search from the center\r
387                 /// </param>\r
388                 /// <returns> {@link AlignmentPattern} if found, or null otherwise\r
389                 /// </returns>\r
390                 /// <throws>  ReaderException if an unexpected error occurs during detection </throws>\r
391                 protected internal virtual AlignmentPattern findAlignmentInRegion(float overallEstModuleSize, int estAlignmentX, int estAlignmentY, float allowanceFactor)\r
392                 {\r
393                         // Look for an alignment pattern (3 modules in size) around where it\r
394                         // should be\r
395                         //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
396                         int allowance = (int) (allowanceFactor * overallEstModuleSize);\r
397                         int alignmentAreaLeftX = System.Math.Max(0, estAlignmentX - allowance);\r
398                         int alignmentAreaRightX = System.Math.Min(image.Width - 1, estAlignmentX + allowance);\r
399                         if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3)\r
400                         {\r
401                                 throw ReaderException.Instance;\r
402                         }\r
403                         \r
404                         int alignmentAreaTopY = System.Math.Max(0, estAlignmentY - allowance);\r
405                         int alignmentAreaBottomY = System.Math.Min(image.Height - 1, estAlignmentY + allowance);\r
406                         \r
407                         AlignmentPatternFinder alignmentFinder = new AlignmentPatternFinder(image, alignmentAreaLeftX, alignmentAreaTopY, alignmentAreaRightX - alignmentAreaLeftX, alignmentAreaBottomY - alignmentAreaTopY, overallEstModuleSize, resultPointCallback);\r
408                         return alignmentFinder.find();\r
409                 }\r
410                 \r
411                 /// <summary> Ends up being a bit faster than Math.round(). This merely rounds its argument to the nearest int,\r
412                 /// where x.5 rounds up.\r
413                 /// </summary>\r
414                 private static int round(float d)\r
415                 {\r
416                         //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
417                         return (int) (d + 0.5f);\r
418                 }\r
419         }\r
420 }