/** * <p>Detects a Data Matrix Code in an image.</p> * * @return {@link DetectorResult} encapsulating results of detecting a QR Code * @throws ReaderException if no Data Matrix Code can be found */ public DetectorResult detect() { if (!BlackPointEstimationMethod.TWO_D_SAMPLING.Equals(image.getLastEstimationMethod())) { image.estimateBlackPoint(BlackPointEstimationMethod.TWO_D_SAMPLING, 0); } int height = image.getHeight(); int width = image.getWidth(); int halfHeight = height >> 1; int halfWidth = width >> 1; int iSkip = Math.Max(1, height / (MAX_MODULES << 3)); int jSkip = Math.Max(1, width / (MAX_MODULES << 3)); int minI = 0; int maxI = height; int minJ = 0; int maxJ = width; ResultPoint pointA = findCornerFromCenter(halfHeight, -iSkip, minI, maxI, halfWidth, 0, minJ, maxJ, halfWidth >> 1); minI = (int)pointA.getY() - 1; ResultPoint pointB = findCornerFromCenter(halfHeight, 0, minI, maxI, halfWidth, -jSkip, minJ, maxJ, halfHeight >> 1); minJ = (int)pointB.getX() - 1; ResultPoint pointC = findCornerFromCenter(halfHeight, 0, minI, maxI, halfWidth, jSkip, minJ, maxJ, halfHeight >> 1); maxJ = (int)pointC.getX() + 1; ResultPoint pointD = findCornerFromCenter(halfHeight, iSkip, minI, maxI, halfWidth, 0, minJ, maxJ, halfWidth >> 1); maxI = (int)pointD.getY() + 1; // Go try to find point A again with better information -- might have been off at first. pointA = findCornerFromCenter(halfHeight, -iSkip, minI, maxI, halfWidth, 0, minJ, maxJ, halfWidth >> 2); // Point A and D are across the diagonal from one another, // as are B and C. Figure out which are the solid black lines // by counting transitions System.Collections.ArrayList transitions = new System.Collections.ArrayList(4); transitions.Add(transitionsBetween(pointA, pointB)); transitions.Add(transitionsBetween(pointA, pointC)); transitions.Add(transitionsBetween(pointB, pointD)); transitions.Add(transitionsBetween(pointC, pointD)); Collections.insertionSort(transitions, new ResultPointsAndTransitionsComparator()); // Sort by number of transitions. First two will be the two solid sides; last two // will be the two alternating black/white sides ResultPointsAndTransitions lSideOne = (ResultPointsAndTransitions)transitions[0]; ResultPointsAndTransitions lSideTwo = (ResultPointsAndTransitions)transitions[1]; // Figure out which point is their intersection by tallying up the number of times we see the // endpoints in the four endpoints. One will show up twice. System.Collections.Hashtable pointCount = new System.Collections.Hashtable(); increment(pointCount, lSideOne.getFrom()); increment(pointCount, lSideOne.getTo()); increment(pointCount, lSideTwo.getFrom()); increment(pointCount, lSideTwo.getTo()); ResultPoint maybeTopLeft = null; ResultPoint bottomLeft = null; ResultPoint maybeBottomRight = null; System.Collections.IEnumerator points = pointCount.GetEnumerator(); while (points.MoveNext()) { ResultPoint point = (ResultPoint)points.Current; int value = (int)pointCount[point]; if (value == 2) { bottomLeft = point; // this is definitely the bottom left, then -- end of two L sides } else { // Otherwise it's either top left or bottom right -- just assign the two arbitrarily now if (maybeTopLeft == null) { maybeTopLeft = point; } else { maybeBottomRight = point; } } } if (maybeTopLeft == null || bottomLeft == null || maybeBottomRight == null) { throw new ReaderException(); } // Bottom left is correct but top left and bottom right might be switched ResultPoint[] corners = { maybeTopLeft, bottomLeft, maybeBottomRight }; // Use the dot product trick to sort them out GenericResultPoint.orderBestPatterns(corners); // Now we know which is which: ResultPoint bottomRight = corners[0]; bottomLeft = corners[1]; ResultPoint topLeft = corners[2]; // Which point didn't we find in relation to the "L" sides? that's the top right corner ResultPoint topRight; if (!pointCount.ContainsKey(pointA)) { topRight = pointA; } else if (!pointCount.ContainsKey(pointB)) { topRight = pointB; } else if (!pointCount.ContainsKey(pointC)) { topRight = pointC; } else { topRight = pointD; } // Next determine the dimension by tracing along the top or right side and counting black/white // transitions. Since we start inside a black module, we should see a number of transitions // equal to 1 less than the code dimension. Well, actually 2 less, because we are going to // end on a black module: // The top right point is actually the corner of a module, which is one of the two black modules // adjacent to the white module at the top right. Tracing to that corner from either the top left // or bottom right should work here, but, one will be more reliable since it's traced straight // up or across, rather than at a slight angle. We use dot products to figure out which is // better to use: int dimension; if (GenericResultPoint.crossProductZ(bottomLeft, bottomRight, topRight) < GenericResultPoint.crossProductZ(topRight, topLeft, bottomLeft)) { dimension = transitionsBetween(topLeft, topRight).getTransitions(); } else { dimension = transitionsBetween(bottomRight, topRight).getTransitions(); } dimension += 2; BitMatrix bits = sampleGrid(image, topLeft, bottomLeft, bottomRight, dimension); return(new DetectorResult(bits, new ResultPoint[] { pointA, pointB, pointC, pointD })); }
public FinderPatternInfo find(System.Collections.Hashtable hints) { bool tryHarder = hints != null && hints.ContainsKey(DecodeHintType.TRY_HARDER); int maxI = image.getHeight(); int maxJ = image.getWidth(); // We are looking for black/white/black/white/black modules in // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the // image, and then account for the center being 3 modules in size. This gives the smallest // number of pixels the center could be, so skip this often. When trying harder, look for all // QR versions regardless of how dense they are. int iSkip = (int)(maxI / (MAX_MODULES * 4.0f) * 3); if (iSkip < MIN_SKIP || tryHarder) { iSkip = MIN_SKIP; } bool done = false; int[] stateCount = new int[5]; BitArray blackRow = new BitArray(maxJ); for (int i = iSkip - 1; i < maxI && !done; i += iSkip) { // Get a row of black/white values blackRow = image.getBlackRow(i, blackRow, 0, maxJ); stateCount[0] = 0; stateCount[1] = 0; stateCount[2] = 0; stateCount[3] = 0; stateCount[4] = 0; int currentState = 0; for (int j = 0; j < maxJ; j++) { if (blackRow.get(j)) { // Black pixel if ((currentState & 1) == 1) // Counting white pixels { currentState++; } stateCount[currentState]++; } else // White pixel { if ((currentState & 1) == 0) // Counting black pixels { if (currentState == 4) // A winner? { if (foundPatternCross(stateCount)) // Yes { bool confirmed = handlePossibleCenter(stateCount, i, j); if (confirmed) { // Start examining every other line. Checking each line turned out to be too // expensive and didn't improve performance. iSkip = 2; if (hasSkipped) { done = haveMulitplyConfirmedCenters(); } else { int rowSkip = findRowSkip(); if (rowSkip > stateCount[2]) { // Skip rows between row of lower confirmed center // and top of presumed third confirmed center // but back up a bit to get a full chance of detecting // it, entire width of center of finder pattern // Skip by rowSkip, but back off by stateCount[2] (size of last center // of pattern we saw) to be conservative, and also back off by iSkip which // is about to be re-added i += rowSkip - stateCount[2] - iSkip; j = maxJ - 1; } } } else { // Advance to next black pixel do { j++; } while (j < maxJ && !blackRow.get(j)); j--; // back up to that last white pixel } // Clear state to start looking again currentState = 0; stateCount[0] = 0; stateCount[1] = 0; stateCount[2] = 0; stateCount[3] = 0; stateCount[4] = 0; } else // No, shift counts back by two { stateCount[0] = stateCount[2]; stateCount[1] = stateCount[3]; stateCount[2] = stateCount[4]; stateCount[3] = 1; stateCount[4] = 0; currentState = 3; } } else { stateCount[++currentState]++; } } else // Counting white pixels { stateCount[currentState]++; } } } if (foundPatternCross(stateCount)) { bool confirmed = handlePossibleCenter(stateCount, i, maxJ); if (confirmed) { iSkip = stateCount[0]; if (hasSkipped) { // Found a third one done = haveMulitplyConfirmedCenters(); } } } } FinderPattern[] patternInfo = selectBestPatterns(); GenericResultPoint.orderBestPatterns(patternInfo); return(new FinderPatternInfo(patternInfo)); }