@zxing/library
Version:
TypeScript port of ZXing multi-format 1D/2D barcode image processing library.
320 lines • 15.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var ResultPoint_1 = require("../../ResultPoint");
var DetectorResult_1 = require("../../common/DetectorResult");
var GridSamplerInstance_1 = require("../../common/GridSamplerInstance");
var MathUtils_1 = require("../../common/detector/MathUtils");
var WhiteRectangleDetector_1 = require("../../common/detector/WhiteRectangleDetector");
var NotFoundException_1 = require("../../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
*/
var Detector = /** @class */ (function () {
function Detector(image) {
this.image = image;
this.rectangleDetector = new WhiteRectangleDetector_1.default(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
*/
Detector.prototype.detect = function () {
var cornerPoints = this.rectangleDetector.detect();
var pointA = cornerPoints[0];
var pointB = cornerPoints[1];
var pointC = cornerPoints[2];
var 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
var 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
var lSideOne = transitions[0];
var 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.
var pointCount = new Map();
Detector.increment(pointCount, lSideOne.getFrom());
Detector.increment(pointCount, lSideOne.getTo());
Detector.increment(pointCount, lSideTwo.getFrom());
Detector.increment(pointCount, lSideTwo.getTo());
var maybeTopLeft = null;
var bottomLeft = null;
var maybeBottomRight = null;
for (var _i = 0, _a = Array.from(pointCount.entries()); _i < _a.length; _i++) {
var _b = _a[_i], point = _b[0], value = _b[1];
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_1.default();
}
// Bottom left is correct but top left and bottom right might be switched
var corners = [maybeTopLeft, bottomLeft, maybeBottomRight];
// Use the dot product trick to sort them out
ResultPoint_1.default.orderBestPatterns(corners);
// Now we know which is which:
var bottomRight = corners[0];
bottomLeft = corners[1];
var topLeft = corners[2];
// Which point didn't we find in relation to the "L" sides? that's the top right corner
var 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.
var dimensionTop = this.transitionsBetween(topLeft, topRight).getTransitions();
var 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;
var bits;
var 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
var 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
var 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_1.default(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
*/
Detector.prototype.correctTopRightRectangular = function (bottomLeft, bottomRight, topLeft, topRight, dimensionTop, dimensionRight) {
var corr = Detector.distance(bottomLeft, bottomRight) / dimensionTop;
var norm = Detector.distance(topLeft, topRight);
var cos = (topRight.getX() - topLeft.getX()) / norm;
var sin = (topRight.getY() - topLeft.getY()) / norm;
var c1 = new ResultPoint_1.default(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;
var c2 = new ResultPoint_1.default(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;
}
var l1 = Math.abs(dimensionTop - this.transitionsBetween(topLeft, c1).getTransitions()) +
Math.abs(dimensionRight - this.transitionsBetween(bottomRight, c1).getTransitions());
var 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
*/
Detector.prototype.correctTopRight = function (bottomLeft, bottomRight, topLeft, topRight, dimension) {
var corr = Detector.distance(bottomLeft, bottomRight) / dimension;
var norm = Detector.distance(topLeft, topRight);
var cos = (topRight.getX() - topLeft.getX()) / norm;
var sin = (topRight.getY() - topLeft.getY()) / norm;
var c1 = new ResultPoint_1.default(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;
var c2 = new ResultPoint_1.default(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;
}
var l1 = Math.abs(this.transitionsBetween(topLeft, c1).getTransitions() -
this.transitionsBetween(bottomRight, c1).getTransitions());
var l2 = Math.abs(this.transitionsBetween(topLeft, c2).getTransitions() -
this.transitionsBetween(bottomRight, c2).getTransitions());
return l1 <= l2 ? c1 : c2;
};
Detector.prototype.isValid = function (p) {
return p.getX() >= 0 && p.getX() < this.image.getWidth() && p.getY() > 0 && p.getY() < this.image.getHeight();
};
Detector.distance = function (a, b) {
return MathUtils_1.default.round(ResultPoint_1.default.distance(a, b));
};
/**
* Increments the Integer associated with a key by one.
*/
Detector.increment = function (table, key) {
var value = table.get(key);
table.set(key, value == null ? 1 : value + 1);
};
Detector.sampleGrid = function (image, topLeft, bottomLeft, bottomRight, topRight, dimensionX, dimensionY) {
var sampler = GridSamplerInstance_1.default.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.
*/
Detector.prototype.transitionsBetween = function (from, to) {
// See QR Code Detector, sizeOfBlackWhiteBlackRun()
var fromX = from.getX() | 0;
var fromY = from.getY() | 0;
var toX = to.getX() | 0;
var toY = to.getY() | 0;
var steep = Math.abs(toY - fromY) > Math.abs(toX - fromX);
if (steep) {
var temp = fromX;
fromX = fromY;
fromY = temp;
temp = toX;
toX = toY;
toY = temp;
}
var dx = Math.abs(toX - fromX);
var dy = Math.abs(toY - fromY);
var error = -dx / 2;
var ystep = fromY < toY ? 1 : -1;
var xstep = fromX < toX ? 1 : -1;
var transitions = 0;
var inBlack = this.image.get(steep ? fromY : fromX, steep ? fromX : fromY);
for (var x = fromX, y = fromY; x !== toX; x += xstep) {
var 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);
};
return Detector;
}());
exports.default = Detector;
var ResultPointsAndTransitions = /** @class */ (function () {
function ResultPointsAndTransitions(from, to, transitions) {
this.from = from;
this.to = to;
this.transitions = transitions;
}
ResultPointsAndTransitions.prototype.getFrom = function () {
return this.from;
};
ResultPointsAndTransitions.prototype.getTo = function () {
return this.to;
};
ResultPointsAndTransitions.prototype.getTransitions = function () {
return this.transitions;
};
// @Override
ResultPointsAndTransitions.prototype.toString = function () {
return this.from + '/' + this.to + '/' + this.transitions;
};
ResultPointsAndTransitions.resultPointsAndTransitionsComparator = function (o1, o2) {
return o1.getTransitions() - o2.getTransitions();
};
return ResultPointsAndTransitions;
}());
//# sourceMappingURL=Detector.js.map