UNPKG

@zxing/library

Version:

TypeScript port of ZXing multi-format 1D/2D barcode image processing library.

314 lines 14.6 kB
import ResultPoint from '../../ResultPoint'; import DetectorResult from '../../common/DetectorResult'; import GridSamplerInstance from '../../common/GridSamplerInstance'; import MathUtils from '../../common/detector/MathUtils'; import WhiteRectangleDetector from '../../common/detector/WhiteRectangleDetector'; import NotFoundException from '../../NotFoundException'; /* * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * <p>Encapsulates logic that can detect a Data Matrix Code in an image, even if the Data Matrix Code * is rotated or skewed, or partially obscured.</p> * * @author Sean Owen */ export default class Detector { constructor(image) { this.image = image; this.rectangleDetector = new WhiteRectangleDetector(image); } /** * <p>Detects a Data Matrix Code in an image.</p> * * @return {@link DetectorResult} encapsulating results of detecting a Data Matrix Code * @throws NotFoundException if no Data Matrix Code can be found */ detect() { const cornerPoints = this.rectangleDetector.detect(); const pointA = cornerPoints[0]; const pointB = cornerPoints[1]; const pointC = cornerPoints[2]; const pointD = cornerPoints[3]; // 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 const transitions = []; transitions.push(this.transitionsBetween(pointA, pointB)); transitions.push(this.transitionsBetween(pointA, pointC)); transitions.push(this.transitionsBetween(pointB, pointD)); transitions.push(this.transitionsBetween(pointC, pointD)); transitions.sort(ResultPointsAndTransitions.resultPointsAndTransitionsComparator); // Sort by number of transitions. First two will be the two solid sides; last two // will be the two alternating black/white sides const lSideOne = transitions[0]; const lSideTwo = 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. const pointCount = new Map(); Detector.increment(pointCount, lSideOne.getFrom()); Detector.increment(pointCount, lSideOne.getTo()); Detector.increment(pointCount, lSideTwo.getFrom()); Detector.increment(pointCount, lSideTwo.getTo()); let maybeTopLeft = null; let bottomLeft = null; let maybeBottomRight = null; for (let [point, value] of Array.from(pointCount.entries())) { 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 NotFoundException(); } // Bottom left is correct but top left and bottom right might be switched const corners = [maybeTopLeft, bottomLeft, maybeBottomRight]; // Use the dot product trick to sort them out ResultPoint.orderBestPatterns(corners); // Now we know which is which: const bottomRight = corners[0]; bottomLeft = corners[1]; const topLeft = corners[2]; // Which point didn't we find in relation to the "L" sides? that's the top right corner let topRight; if (!pointCount.has(pointA)) { topRight = pointA; } else if (!pointCount.has(pointB)) { topRight = pointB; } else if (!pointCount.has(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. let dimensionTop = this.transitionsBetween(topLeft, topRight).getTransitions(); let dimensionRight = this.transitionsBetween(bottomRight, topRight).getTransitions(); if ((dimensionTop & 0x01) === 1) { // it can't be odd, so, round... up? dimensionTop++; } dimensionTop += 2; if ((dimensionRight & 0x01) === 1) { // it can't be odd, so, round... up? dimensionRight++; } dimensionRight += 2; let bits; let correctedTopRight; // Rectangular symbols are 6x16, 6x28, 10x24, 10x32, 14x32, or 14x44. If one dimension is more // than twice the other, it's certainly rectangular, but to cut a bit more slack we accept it as // rectangular if the bigger side is at least 7/4 times the other: if (4 * dimensionTop >= 7 * dimensionRight || 4 * dimensionRight >= 7 * dimensionTop) { // The matrix is rectangular correctedTopRight = this.correctTopRightRectangular(bottomLeft, bottomRight, topLeft, topRight, dimensionTop, dimensionRight); if (correctedTopRight == null) { correctedTopRight = topRight; } dimensionTop = this.transitionsBetween(topLeft, correctedTopRight).getTransitions(); dimensionRight = this.transitionsBetween(bottomRight, correctedTopRight).getTransitions(); if ((dimensionTop & 0x01) === 1) { // it can't be odd, so, round... up? dimensionTop++; } if ((dimensionRight & 0x01) === 1) { // it can't be odd, so, round... up? dimensionRight++; } bits = Detector.sampleGrid(this.image, topLeft, bottomLeft, bottomRight, correctedTopRight, dimensionTop, dimensionRight); } else { // The matrix is square const dimension = Math.min(dimensionRight, dimensionTop); // correct top right point to match the white module correctedTopRight = this.correctTopRight(bottomLeft, bottomRight, topLeft, topRight, dimension); if (correctedTopRight == null) { correctedTopRight = topRight; } // Redetermine the dimension using the corrected top right point let dimensionCorrected = Math.max(this.transitionsBetween(topLeft, correctedTopRight).getTransitions(), this.transitionsBetween(bottomRight, correctedTopRight).getTransitions()); dimensionCorrected++; if ((dimensionCorrected & 0x01) === 1) { dimensionCorrected++; } bits = Detector.sampleGrid(this.image, topLeft, bottomLeft, bottomRight, correctedTopRight, dimensionCorrected, dimensionCorrected); } return new DetectorResult(bits, [topLeft, bottomLeft, bottomRight, correctedTopRight]); } /** * Calculates the position of the white top right module using the output of the rectangle detector * for a rectangular matrix */ correctTopRightRectangular(bottomLeft, bottomRight, topLeft, topRight, dimensionTop, dimensionRight) { let corr = Detector.distance(bottomLeft, bottomRight) / dimensionTop; let norm = Detector.distance(topLeft, topRight); let cos = (topRight.getX() - topLeft.getX()) / norm; let sin = (topRight.getY() - topLeft.getY()) / norm; const c1 = new ResultPoint(topRight.getX() + corr * cos, topRight.getY() + corr * sin); corr = Detector.distance(bottomLeft, topLeft) / dimensionRight; norm = Detector.distance(bottomRight, topRight); cos = (topRight.getX() - bottomRight.getX()) / norm; sin = (topRight.getY() - bottomRight.getY()) / norm; const c2 = new ResultPoint(topRight.getX() + corr * cos, topRight.getY() + corr * sin); if (!this.isValid(c1)) { if (this.isValid(c2)) { return c2; } return null; } if (!this.isValid(c2)) { return c1; } const l1 = Math.abs(dimensionTop - this.transitionsBetween(topLeft, c1).getTransitions()) + Math.abs(dimensionRight - this.transitionsBetween(bottomRight, c1).getTransitions()); const l2 = Math.abs(dimensionTop - this.transitionsBetween(topLeft, c2).getTransitions()) + Math.abs(dimensionRight - this.transitionsBetween(bottomRight, c2).getTransitions()); if (l1 <= l2) { return c1; } return c2; } /** * Calculates the position of the white top right module using the output of the rectangle detector * for a square matrix */ correctTopRight(bottomLeft, bottomRight, topLeft, topRight, dimension) { let corr = Detector.distance(bottomLeft, bottomRight) / dimension; let norm = Detector.distance(topLeft, topRight); let cos = (topRight.getX() - topLeft.getX()) / norm; let sin = (topRight.getY() - topLeft.getY()) / norm; const c1 = new ResultPoint(topRight.getX() + corr * cos, topRight.getY() + corr * sin); corr = Detector.distance(bottomLeft, topLeft) / dimension; norm = Detector.distance(bottomRight, topRight); cos = (topRight.getX() - bottomRight.getX()) / norm; sin = (topRight.getY() - bottomRight.getY()) / norm; const c2 = new ResultPoint(topRight.getX() + corr * cos, topRight.getY() + corr * sin); if (!this.isValid(c1)) { if (this.isValid(c2)) { return c2; } return null; } if (!this.isValid(c2)) { return c1; } const l1 = Math.abs(this.transitionsBetween(topLeft, c1).getTransitions() - this.transitionsBetween(bottomRight, c1).getTransitions()); const l2 = Math.abs(this.transitionsBetween(topLeft, c2).getTransitions() - this.transitionsBetween(bottomRight, c2).getTransitions()); return l1 <= l2 ? c1 : c2; } isValid(p) { return p.getX() >= 0 && p.getX() < this.image.getWidth() && p.getY() > 0 && p.getY() < this.image.getHeight(); } static distance(a, b) { return MathUtils.round(ResultPoint.distance(a, b)); } /** * Increments the Integer associated with a key by one. */ static increment(table, key) { const value = table.get(key); table.set(key, value == null ? 1 : value + 1); } static sampleGrid(image, topLeft, bottomLeft, bottomRight, topRight, dimensionX, dimensionY) { const sampler = GridSamplerInstance.getInstance(); return sampler.sampleGrid(image, dimensionX, dimensionY, 0.5, 0.5, dimensionX - 0.5, 0.5, dimensionX - 0.5, dimensionY - 0.5, 0.5, dimensionY - 0.5, topLeft.getX(), topLeft.getY(), topRight.getX(), topRight.getY(), bottomRight.getX(), bottomRight.getY(), bottomLeft.getX(), bottomLeft.getY()); } /** * Counts the number of black/white transitions between two points, using something like Bresenham's algorithm. */ transitionsBetween(from, to) { // See QR Code Detector, sizeOfBlackWhiteBlackRun() let fromX = from.getX() | 0; let fromY = from.getY() | 0; let toX = to.getX() | 0; let toY = to.getY() | 0; const steep = Math.abs(toY - fromY) > Math.abs(toX - fromX); if (steep) { let temp = fromX; fromX = fromY; fromY = temp; temp = toX; toX = toY; toY = temp; } const dx = Math.abs(toX - fromX); const dy = Math.abs(toY - fromY); let error = -dx / 2; const ystep = fromY < toY ? 1 : -1; const xstep = fromX < toX ? 1 : -1; let transitions = 0; let inBlack = this.image.get(steep ? fromY : fromX, steep ? fromX : fromY); for (let x = fromX, y = fromY; x !== toX; x += xstep) { const isBlack = this.image.get(steep ? y : x, steep ? x : y); if (isBlack !== inBlack) { transitions++; inBlack = isBlack; } error += dy; if (error > 0) { if (y === toY) { break; } y += ystep; error -= dx; } } return new ResultPointsAndTransitions(from, to, transitions); } } class ResultPointsAndTransitions { constructor(from, to, transitions) { this.from = from; this.to = to; this.transitions = transitions; } getFrom() { return this.from; } getTo() { return this.to; } getTransitions() { return this.transitions; } // @Override toString() { return this.from + '/' + this.to + '/' + this.transitions; } static resultPointsAndTransitionsComparator(o1, o2) { return o1.getTransitions() - o2.getTransitions(); } } //# sourceMappingURL=Detector.js.map