+++ /dev/null
-/*\r
- * Copyright 2008 ZXing authors\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-using System;\r
-using System.Collections.Generic;\r
-using System.Linq;\r
-using System.Text;\r
-using com.google.zxing;\r
-using com.google.zxing.common; \r
-\r
-namespace com.google.zxing.datamatrix.detector\r
-{\r
- /**\r
- * <p>Encapsulates logic that can detect a Data Matrix Code in an image, even if the Data Matrix Code\r
- * is rotated or skewed, or partially obscured.</p>\r
- *\r
- * @author Sean Owen\r
- */\r
- public sealed class Detector\r
- {\r
- private static int MAX_MODULES = 32;\r
-\r
- // Trick to avoid creating new int objects below -- a sort of crude copy of\r
- // the int.valueOf(int) optimization added in Java 5, not in J2ME\r
- private static int[] intS =\r
- {0, 1, 2, 3, 4};\r
-\r
- private MonochromeBitmapSource image;\r
-\r
- public Detector(MonochromeBitmapSource image) {\r
- this.image = image;\r
- }\r
-\r
- /**\r
- * <p>Detects a Data Matrix Code in an image.</p>\r
- *\r
- * @return {@link DetectorResult} encapsulating results of detecting a QR Code\r
- * @throws ReaderException if no Data Matrix Code can be found\r
- */\r
- public DetectorResult detect() {\r
-\r
- if (!BlackPointEstimationMethod.TWO_D_SAMPLING.Equals(image.getLastEstimationMethod())) {\r
- image.estimateBlackPoint(BlackPointEstimationMethod.TWO_D_SAMPLING, 0);\r
- }\r
-\r
- int height = image.getHeight();\r
- int width = image.getWidth();\r
- int halfHeight = height >> 1;\r
- int halfWidth = width >> 1;\r
- int iSkip = Math.Max(1, height / (MAX_MODULES << 3));\r
- int jSkip = Math.Max(1, width / (MAX_MODULES << 3));\r
-\r
- int minI = 0;\r
- int maxI = height;\r
- int minJ = 0;\r
- int maxJ = width;\r
- ResultPoint pointA = findCornerFromCenter(halfHeight, -iSkip, minI, maxI, halfWidth, 0, minJ, maxJ, halfWidth >> 1);\r
- minI = (int) pointA.getY() - 1;\r
- ResultPoint pointB = findCornerFromCenter(halfHeight, 0, minI, maxI, halfWidth, -jSkip, minJ, maxJ, halfHeight >> 1);\r
- minJ = (int) pointB.getX() - 1;\r
- ResultPoint pointC = findCornerFromCenter(halfHeight, 0, minI, maxI, halfWidth, jSkip, minJ, maxJ, halfHeight >> 1);\r
- maxJ = (int) pointC.getX() + 1;\r
- ResultPoint pointD = findCornerFromCenter(halfHeight, iSkip, minI, maxI, halfWidth, 0, minJ, maxJ, halfWidth >> 1);\r
- maxI = (int) pointD.getY() + 1;\r
- // Go try to find point A again with better information -- might have been off at first.\r
- pointA = findCornerFromCenter(halfHeight, -iSkip, minI, maxI, halfWidth, 0, minJ, maxJ, halfWidth >> 2);\r
-\r
- // Point A and D are across the diagonal from one another,\r
- // as are B and C. Figure out which are the solid black lines\r
- // by counting transitions\r
- System.Collections.ArrayList transitions = new System.Collections.ArrayList(4);\r
- transitions.Add(transitionsBetween(pointA, pointB));\r
- transitions.Add(transitionsBetween(pointA, pointC));\r
- transitions.Add(transitionsBetween(pointB, pointD));\r
- transitions.Add(transitionsBetween(pointC, pointD));\r
- Collections.insertionSort(transitions, new ResultPointsAndTransitionsComparator());\r
-\r
- // Sort by number of transitions. First two will be the two solid sides; last two\r
- // will be the two alternating black/white sides\r
- ResultPointsAndTransitions lSideOne = (ResultPointsAndTransitions) transitions[0];\r
- ResultPointsAndTransitions lSideTwo = (ResultPointsAndTransitions) transitions[1];\r
-\r
- // Figure out which point is their intersection by tallying up the number of times we see the\r
- // endpoints in the four endpoints. One will show up twice.\r
- System.Collections.Hashtable pointCount = new System.Collections.Hashtable();\r
- increment(pointCount, lSideOne.getFrom());\r
- increment(pointCount, lSideOne.getTo());\r
- increment(pointCount, lSideTwo.getFrom());\r
- increment(pointCount, lSideTwo.getTo());\r
-\r
- ResultPoint maybeTopLeft = null;\r
- ResultPoint bottomLeft = null;\r
- ResultPoint maybeBottomRight = null;\r
- System.Collections.IEnumerator points = pointCount.GetEnumerator();\r
-\r
- while (points.MoveNext()) {\r
- ResultPoint point = (ResultPoint) points.Current;\r
- int value = (int) pointCount[point];\r
- if (value == 2) {\r
- bottomLeft = point; // this is definitely the bottom left, then -- end of two L sides\r
- } else {\r
- // Otherwise it's either top left or bottom right -- just assign the two arbitrarily now\r
- if (maybeTopLeft == null) {\r
- maybeTopLeft = point;\r
- } else {\r
- maybeBottomRight = point;\r
- }\r
- }\r
- }\r
-\r
- if (maybeTopLeft == null || bottomLeft == null || maybeBottomRight == null) {\r
- throw new ReaderException();\r
- }\r
-\r
- // Bottom left is correct but top left and bottom right might be switched\r
- ResultPoint[] corners = { maybeTopLeft, bottomLeft, maybeBottomRight };\r
- // Use the dot product trick to sort them out\r
- GenericResultPoint.orderBestPatterns(corners);\r
-\r
- // Now we know which is which:\r
- ResultPoint bottomRight = corners[0];\r
- bottomLeft = corners[1];\r
- ResultPoint topLeft = corners[2];\r
-\r
- // Which point didn't we find in relation to the "L" sides? that's the top right corner\r
- ResultPoint topRight;\r
- if (!pointCount.ContainsKey(pointA)) {\r
- topRight = pointA;\r
- } else if (!pointCount.ContainsKey(pointB)) {\r
- topRight = pointB;\r
- } else if (!pointCount.ContainsKey(pointC)) {\r
- topRight = pointC;\r
- } else {\r
- topRight = pointD;\r
- }\r
-\r
- // Next determine the dimension by tracing along the top or right side and counting black/white\r
- // transitions. Since we start inside a black module, we should see a number of transitions\r
- // equal to 1 less than the code dimension. Well, actually 2 less, because we are going to\r
- // end on a black module:\r
-\r
- // The top right point is actually the corner of a module, which is one of the two black modules\r
- // adjacent to the white module at the top right. Tracing to that corner from either the top left\r
- // or bottom right should work here, but, one will be more reliable since it's traced straight\r
- // up or across, rather than at a slight angle. We use dot products to figure out which is\r
- // better to use:\r
- int dimension;\r
- if (GenericResultPoint.crossProductZ(bottomLeft, bottomRight, topRight) <\r
- GenericResultPoint.crossProductZ(topRight, topLeft, bottomLeft)) {\r
- dimension = transitionsBetween(topLeft, topRight).getTransitions();\r
- } else {\r
- dimension = transitionsBetween(bottomRight, topRight).getTransitions();\r
- }\r
- dimension += 2;\r
-\r
- BitMatrix bits = sampleGrid(image, topLeft, bottomLeft, bottomRight, dimension);\r
- return new DetectorResult(bits, new ResultPoint[] {pointA, pointB, pointC, pointD});\r
- }\r
-\r
- /**\r
- * Attempts to locate a corner of the barcode by scanning up, down, left or right from a center\r
- * point which should be within the barcode.\r
- *\r
- * @param centerI center's i componennt (vertical)\r
- * @param di change in i per step. If scanning up this is negative; down, positive; left or right, 0\r
- * @param minI minimum value of i to search through (meaningless when di == 0)\r
- * @param maxI maximum value of i\r
- * @param centerJ center's j component (horizontal)\r
- * @param dj same as di but change in j per step instead\r
- * @param minJ see minI\r
- * @param maxJ see minJ\r
- * @param maxWhiteRun maximum run of white pixels that can still be considered to be within\r
- * the barcode\r
- * @return a {@link ResultPoint} encapsulating the corner that was found\r
- * @throws ReaderException if such a point cannot be found\r
- */\r
- private ResultPoint findCornerFromCenter(int centerI, int di, int minI, int maxI,\r
- int centerJ, int dj, int minJ, int maxJ,\r
- int maxWhiteRun) {\r
- int[] lastRange = null;\r
- for (int i = centerI, j = centerJ;\r
- i < maxI && i >= minI && j < maxJ && j >= minJ;\r
- i += di, j += dj) {\r
- int[] range;\r
- if (dj == 0) {\r
- // horizontal slices, up and down\r
- range = blackWhiteRange(i, maxWhiteRun, minJ, maxJ, true);\r
- } else {\r
- // vertical slices, left and right\r
- range = blackWhiteRange(j, maxWhiteRun, minI, maxI, false);\r
- }\r
- if (range == null) {\r
- if (lastRange == null) {\r
- throw new ReaderException();\r
- }\r
- // lastRange was found\r
- if (dj == 0) {\r
- int lastI = i - di;\r
- if (lastRange[0] < centerJ) {\r
- if (lastRange[1] > centerJ) {\r
- // straddle, choose one or the other based on direction\r
- return new GenericResultPoint(di > 0 ? lastRange[0] : lastRange[1], lastI);\r
- }\r
- return new GenericResultPoint(lastRange[0], lastI);\r
- } else {\r
- return new GenericResultPoint(lastRange[1], lastI);\r
- }\r
- } else {\r
- int lastJ = j - dj;\r
- if (lastRange[0] < centerI) {\r
- if (lastRange[1] > centerI) {\r
- return new GenericResultPoint(lastJ, dj < 0 ? lastRange[0] : lastRange[1]);\r
- }\r
- return new GenericResultPoint(lastJ, lastRange[0]);\r
- } else {\r
- return new GenericResultPoint(lastJ, lastRange[1]);\r
- }\r
- }\r
- }\r
- lastRange = range;\r
- }\r
- throw new ReaderException();\r
- }\r
-\r
- /**\r
- * Increments the int associated with a key by one.\r
- */\r
- private static void increment(System.Collections.Hashtable table, ResultPoint key) {\r
- int value = (int) table[key];\r
- table[key] = value.Equals(null) ? intS[1] : intS[value + 1];\r
- //table.put(key, value == null ? intS[1] : intS[value.intValue() + 1]);\r
- }\r
-\r
- /**\r
- * Computes the start and end of a region of pixels, either horizontally or vertically, that could be\r
- * part of a Data Matrix barcode.\r
- *\r
- * @param fixedDimension if scanning horizontally, this is the row (the fixed vertical location) where\r
- * we are scanning. If scanning vertically it's the colummn, the fixed horizontal location\r
- * @param maxWhiteRun largest run of white pixels that can still be considered part of the barcode region\r
- * @param minDim minimum pixel location, horizontally or vertically, to consider\r
- * @param maxDim maximum pixel location, horizontally or vertically, to consider\r
- * @param horizontal if true, we're scanning left-right, instead of up-down\r
- * @return int[] with start and end of found range, or null if no such range is found (e.g. only white was found)\r
- */\r
- private int[] blackWhiteRange(int fixedDimension, int maxWhiteRun, int minDim, int maxDim, bool horizontal) {\r
-\r
- int center = (minDim + maxDim) / 2;\r
-\r
- BitArray rowOrColumn = horizontal ? image.getBlackRow(fixedDimension, null, 0, image.getWidth())\r
- : image.getBlackColumn(fixedDimension, null, 0, image.getHeight());\r
-\r
- // Scan left/up first\r
- int start = center;\r
- while (start >= minDim) {\r
- if (rowOrColumn.get(start)) {\r
- start--;\r
- } else {\r
- int whiteRunStart = start;\r
- do {\r
- start--;\r
- } while (start >= minDim && !rowOrColumn.get(start));\r
- int whiteRunSize = whiteRunStart - start;\r
- if (start < minDim || whiteRunSize > maxWhiteRun) {\r
- start = whiteRunStart + 1; // back up\r
- break;\r
- }\r
- }\r
- }\r
- start++;\r
-\r
- // Then try right/down\r
- int end = center;\r
- while (end < maxDim) {\r
- if (rowOrColumn.get(end)) {\r
- end++;\r
- } else {\r
- int whiteRunStart = end;\r
- do {\r
- end++;\r
- } while (end < maxDim && !rowOrColumn.get(end));\r
- int whiteRunSize = end - whiteRunStart;\r
- if (end >= maxDim || whiteRunSize > maxWhiteRun) {\r
- end = whiteRunStart - 1;\r
- break;\r
- }\r
- }\r
- }\r
- end--;\r
-\r
- if (end > start) {\r
- return new int[] { start, end };\r
- } else {\r
- return null;\r
- }\r
- }\r
-\r
- private static BitMatrix sampleGrid(MonochromeBitmapSource image,\r
- ResultPoint topLeft,\r
- ResultPoint bottomLeft,\r
- ResultPoint bottomRight,\r
- int dimension) {\r
-\r
- // We make up the top right point for now, based on the others.\r
- // TODO: we actually found a fourth corner above and figured out which of two modules\r
- // it was the corner of. We could use that here and adjust for perspective distortion.\r
- float topRightX = (bottomRight.getX() - bottomLeft.getX()) + topLeft.getX();\r
- float topRightY = (bottomRight.getY() - bottomLeft.getY()) + topLeft.getY();\r
-\r
- // Note that unlike in the QR Code sampler, we didn't find the center of modules, but the\r
- // very corners. So there is no 0.5f here; 0.0f is right.\r
- GridSampler sampler = GridSampler.Instance;\r
- return sampler.sampleGrid(\r
- image,\r
- dimension,\r
- 0.0f,\r
- 0.0f,\r
- dimension,\r
- 0.0f,\r
- dimension,\r
- dimension,\r
- 0.0f,\r
- dimension,\r
- topLeft.getX(),\r
- topLeft.getY(),\r
- topRightX,\r
- topRightY,\r
- bottomRight.getX(),\r
- bottomRight.getY(),\r
- bottomLeft.getX(),\r
- bottomLeft.getY());\r
- }\r
-\r
- /**\r
- * Counts the number of black/white transitions between two points, using something like Bresenham's algorithm.\r
- */\r
- private ResultPointsAndTransitions transitionsBetween(ResultPoint from, ResultPoint to) {\r
- // See QR Code Detector, sizeOfBlackWhiteBlackRun()\r
- int fromX = (int) from.getX();\r
- int fromY = (int) from.getY();\r
- int toX = (int) to.getX();\r
- int toY = (int) to.getY();\r
- bool steep = Math.Abs(toY - fromY) > Math.Abs(toX - fromX);\r
- if (steep) {\r
- int temp = fromX;\r
- fromX = fromY;\r
- fromY = temp;\r
- temp = toX;\r
- toX = toY;\r
- toY = temp;\r
- }\r
-\r
- int dx = Math.Abs(toX - fromX);\r
- int dy = Math.Abs(toY - fromY);\r
- int error = -dx >> 1;\r
- int ystep = fromY < toY ? 1 : -1;\r
- int xstep = fromX < toX ? 1 : -1;\r
- int transitions = 0;\r
- bool inBlack = image.isBlack(steep ? fromY : fromX, steep ? fromX : fromY);\r
- for (int x = fromX, y = fromY; x != toX; x += xstep) {\r
- bool isBlack = image.isBlack(steep ? y : x, steep ? x : y);\r
- if (isBlack == !inBlack) {\r
- transitions++;\r
- inBlack = isBlack;\r
- }\r
- error += dy;\r
- if (error > 0) {\r
- y += ystep;\r
- error -= dx;\r
- }\r
- }\r
- return new ResultPointsAndTransitions(from, to, transitions);\r
- }\r
-\r
- /**\r
- * Simply encapsulates two points and a number of transitions between them.\r
- */\r
- private class ResultPointsAndTransitions {\r
- private ResultPoint from;\r
- private ResultPoint to;\r
- private int transitions;\r
-\r
- public ResultPointsAndTransitions(ResultPoint from, ResultPoint to, int transitions) {\r
- this.from = from;\r
- this.to = to;\r
- this.transitions = transitions;\r
- }\r
-\r
- public ResultPoint getFrom() {\r
- return from;\r
- }\r
- public ResultPoint getTo() {\r
- return to;\r
- }\r
- public int getTransitions() {\r
- return transitions;\r
- }\r
- public String toString() {\r
- return from + "/" + to + '/' + transitions;\r
- }\r
- }\r
-\r
- /**\r
- * Orders ResultPointsAndTransitions by number of transitions, ascending.\r
- */\r
- private class ResultPointsAndTransitionsComparator : Comparator {\r
- public int compare(Object o1, Object o2) {\r
- return ((ResultPointsAndTransitions) o1).getTransitions() - ((ResultPointsAndTransitions) o2).getTransitions();\r
- }\r
- }\r
- }\r
-}\r