UNPKG

@zxing/library

Version:

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

340 lines (339 loc) 16.3 kB
"use strict"; /* * Copyright 2009 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. */ var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; Object.defineProperty(exports, "__esModule", { value: true }); // import com.google.zxing.NotFoundException; // import com.google.zxing.ResultPoint; var ResultPoint_1 = require("../../ResultPoint"); var System_1 = require("../../util/System"); var Arrays_1 = require("../../util/Arrays"); var PDF417DetectorResult_1 = require("./PDF417DetectorResult"); // import java.util.ArrayList; // import java.util.Arrays; // import java.util.List; // import java.util.Map; /** * <p>Encapsulates logic that can detect a PDF417 Code in an image, even if the * PDF417 Code is rotated or skewed, or partially obscured.</p> * * @author SITA Lab (kevin.osullivan@sita.aero) * @author dswitkin@google.com (Daniel Switkin) * @author Guenther Grau */ var Detector = /** @class */ (function () { function Detector() { } /** * <p>Detects a PDF417 Code in an image. Only checks 0 and 180 degree rotations.</p> * * @param image barcode image to decode * @param hints optional hints to detector * @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will * be found and returned * @return {@link PDF417DetectorResult} encapsulating results of detecting a PDF417 code * @throws NotFoundException if no PDF417 Code can be found */ Detector.detectMultiple = function (image, hints, multiple) { // TODO detection improvement, tryHarder could try several different luminance thresholds/blackpoints or even // different binarizers // boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER); var bitMatrix = image.getBlackMatrix(); var barcodeCoordinates = Detector.detect(multiple, bitMatrix); if (!barcodeCoordinates.length) { bitMatrix = bitMatrix.clone(); bitMatrix.rotate180(); barcodeCoordinates = Detector.detect(multiple, bitMatrix); } return new PDF417DetectorResult_1.default(bitMatrix, barcodeCoordinates); }; /** * Detects PDF417 codes in an image. Only checks 0 degree rotation * @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will * be found and returned * @param bitMatrix bit matrix to detect barcodes in * @return List of ResultPoint arrays containing the coordinates of found barcodes */ Detector.detect = function (multiple, bitMatrix) { var e_1, _a; var barcodeCoordinates = new Array(); var row = 0; var column = 0; var foundBarcodeInRow = false; while (row < bitMatrix.getHeight()) { var vertices = Detector.findVertices(bitMatrix, row, column); if (vertices[0] == null && vertices[3] == null) { if (!foundBarcodeInRow) { // we didn't find any barcode so that's the end of searching break; } // we didn't find a barcode starting at the given column and row. Try again from the first column and slightly // below the lowest barcode we found so far. foundBarcodeInRow = false; column = 0; try { for (var barcodeCoordinates_1 = (e_1 = void 0, __values(barcodeCoordinates)), barcodeCoordinates_1_1 = barcodeCoordinates_1.next(); !barcodeCoordinates_1_1.done; barcodeCoordinates_1_1 = barcodeCoordinates_1.next()) { var barcodeCoordinate = barcodeCoordinates_1_1.value; if (barcodeCoordinate[1] != null) { row = Math.trunc(Math.max(row, barcodeCoordinate[1].getY())); } if (barcodeCoordinate[3] != null) { row = Math.max(row, Math.trunc(barcodeCoordinate[3].getY())); } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (barcodeCoordinates_1_1 && !barcodeCoordinates_1_1.done && (_a = barcodeCoordinates_1.return)) _a.call(barcodeCoordinates_1); } finally { if (e_1) throw e_1.error; } } row += Detector.ROW_STEP; continue; } foundBarcodeInRow = true; barcodeCoordinates.push(vertices); if (!multiple) { break; } // if we didn't find a right row indicator column, then continue the search for the next barcode after the // start pattern of the barcode just found. if (vertices[2] != null) { column = Math.trunc(vertices[2].getX()); row = Math.trunc(vertices[2].getY()); } else { column = Math.trunc(vertices[4].getX()); row = Math.trunc(vertices[4].getY()); } } return barcodeCoordinates; }; /** * Locate the vertices and the codewords area of a black blob using the Start * and Stop patterns as locators. * * @param matrix the scanned barcode image. * @return an array containing the vertices: * vertices[0] x, y top left barcode * vertices[1] x, y bottom left barcode * vertices[2] x, y top right barcode * vertices[3] x, y bottom right barcode * vertices[4] x, y top left codeword area * vertices[5] x, y bottom left codeword area * vertices[6] x, y top right codeword area * vertices[7] x, y bottom right codeword area */ Detector.findVertices = function (matrix, startRow, startColumn) { var height = matrix.getHeight(); var width = matrix.getWidth(); // const result = new ResultPoint[8]; var result = new Array(8); Detector.copyToResult(result, Detector.findRowsWithPattern(matrix, height, width, startRow, startColumn, Detector.START_PATTERN), Detector.INDEXES_START_PATTERN); if (result[4] != null) { startColumn = Math.trunc(result[4].getX()); startRow = Math.trunc(result[4].getY()); } Detector.copyToResult(result, Detector.findRowsWithPattern(matrix, height, width, startRow, startColumn, Detector.STOP_PATTERN), Detector.INDEXES_STOP_PATTERN); return result; }; Detector.copyToResult = function (result, tmpResult, destinationIndexes) { for (var i = 0; i < destinationIndexes.length; i++) { result[destinationIndexes[i]] = tmpResult[i]; } }; Detector.findRowsWithPattern = function (matrix, height, width, startRow, startColumn, pattern) { // const result = new ResultPoint[4]; var result = new Array(4); var found = false; var counters = new Int32Array(pattern.length); for (; startRow < height; startRow += Detector.ROW_STEP) { var loc = Detector.findGuardPattern(matrix, startColumn, startRow, width, false, pattern, counters); if (loc != null) { while (startRow > 0) { var previousRowLoc = Detector.findGuardPattern(matrix, startColumn, --startRow, width, false, pattern, counters); if (previousRowLoc != null) { loc = previousRowLoc; } else { startRow++; break; } } result[0] = new ResultPoint_1.default(loc[0], startRow); result[1] = new ResultPoint_1.default(loc[1], startRow); found = true; break; } } var stopRow = startRow + 1; // Last row of the current symbol that contains pattern if (found) { var skippedRowCount = 0; var previousRowLoc = Int32Array.from([Math.trunc(result[0].getX()), Math.trunc(result[1].getX())]); for (; stopRow < height; stopRow++) { var loc = Detector.findGuardPattern(matrix, previousRowLoc[0], stopRow, width, false, pattern, counters); // a found pattern is only considered to belong to the same barcode if the start and end positions // don't differ too much. Pattern drift should be not bigger than two for consecutive rows. With // a higher number of skipped rows drift could be larger. To keep it simple for now, we allow a slightly // larger drift and don't check for skipped rows. if (loc != null && Math.abs(previousRowLoc[0] - loc[0]) < Detector.MAX_PATTERN_DRIFT && Math.abs(previousRowLoc[1] - loc[1]) < Detector.MAX_PATTERN_DRIFT) { previousRowLoc = loc; skippedRowCount = 0; } else { if (skippedRowCount > Detector.SKIPPED_ROW_COUNT_MAX) { break; } else { skippedRowCount++; } } } stopRow -= skippedRowCount + 1; result[2] = new ResultPoint_1.default(previousRowLoc[0], stopRow); result[3] = new ResultPoint_1.default(previousRowLoc[1], stopRow); } if (stopRow - startRow < Detector.BARCODE_MIN_HEIGHT) { Arrays_1.default.fill(result, null); } return result; }; /** * @param matrix row of black/white values to search * @param column x position to start search * @param row y position to start search * @param width the number of pixels to search on this row * @param pattern pattern of counts of number of black and white pixels that are * being searched for as a pattern * @param counters array of counters, as long as pattern, to re-use * @return start/end horizontal offset of guard pattern, as an array of two ints. */ Detector.findGuardPattern = function (matrix, column, row, width, whiteFirst, pattern, counters) { Arrays_1.default.fillWithin(counters, 0, counters.length, 0); var patternStart = column; var pixelDrift = 0; // if there are black pixels left of the current pixel shift to the left, but only for MAX_PIXEL_DRIFT pixels while (matrix.get(patternStart, row) && patternStart > 0 && pixelDrift++ < Detector.MAX_PIXEL_DRIFT) { patternStart--; } var x = patternStart; var counterPosition = 0; var patternLength = pattern.length; for (var isWhite = whiteFirst; x < width; x++) { var pixel = matrix.get(x, row); if (pixel !== isWhite) { counters[counterPosition]++; } else { if (counterPosition === patternLength - 1) { if (Detector.patternMatchVariance(counters, pattern, Detector.MAX_INDIVIDUAL_VARIANCE) < Detector.MAX_AVG_VARIANCE) { return new Int32Array([patternStart, x]); } patternStart += counters[0] + counters[1]; System_1.default.arraycopy(counters, 2, counters, 0, counterPosition - 1); counters[counterPosition - 1] = 0; counters[counterPosition] = 0; counterPosition--; } else { counterPosition++; } counters[counterPosition] = 1; isWhite = !isWhite; } } if (counterPosition === patternLength - 1 && Detector.patternMatchVariance(counters, pattern, Detector.MAX_INDIVIDUAL_VARIANCE) < Detector.MAX_AVG_VARIANCE) { return new Int32Array([patternStart, x - 1]); } return null; }; /** * Determines how closely a set of observed counts of runs of black/white * values matches a given target pattern. This is reported as the ratio of * the total variance from the expected pattern proportions across all * pattern elements, to the length of the pattern. * * @param counters observed counters * @param pattern expected pattern * @param maxIndividualVariance The most any counter can differ before we give up * @return ratio of total variance between counters and pattern compared to total pattern size */ Detector.patternMatchVariance = function (counters, pattern, maxIndividualVariance) { var numCounters = counters.length; var total = 0; var patternLength = 0; for (var i = 0; i < numCounters; i++) { total += counters[i]; patternLength += pattern[i]; } if (total < patternLength) { // If we don't even have one pixel per unit of bar width, assume this // is too small to reliably match, so fail: return /*Float.POSITIVE_INFINITY*/ Infinity; } // We're going to fake floating-point math in integers. We just need to use more bits. // Scale up patternLength so that intermediate values below like scaledCounter will have // more "significant digits". var unitBarWidth = total / patternLength; maxIndividualVariance *= unitBarWidth; var totalVariance = 0.0; for (var x = 0; x < numCounters; x++) { var counter = counters[x]; var scaledPattern = pattern[x] * unitBarWidth; var variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter; if (variance > maxIndividualVariance) { return /*Float.POSITIVE_INFINITY*/ Infinity; } totalVariance += variance; } return totalVariance / total; }; Detector.INDEXES_START_PATTERN = Int32Array.from([0, 4, 1, 5]); Detector.INDEXES_STOP_PATTERN = Int32Array.from([6, 2, 7, 3]); Detector.MAX_AVG_VARIANCE = 0.42; Detector.MAX_INDIVIDUAL_VARIANCE = 0.8; // B S B S B S B S Bar/Space pattern // 11111111 0 1 0 1 0 1 000 Detector.START_PATTERN = Int32Array.from([8, 1, 1, 1, 1, 1, 1, 3]); // 1111111 0 1 000 1 0 1 00 1 Detector.STOP_PATTERN = Int32Array.from([7, 1, 1, 3, 1, 1, 1, 2, 1]); Detector.MAX_PIXEL_DRIFT = 3; Detector.MAX_PATTERN_DRIFT = 5; // if we set the value too low, then we don't detect the correct height of the bar if the start patterns are damaged. // if we set the value too high, then we might detect the start pattern from a neighbor barcode. Detector.SKIPPED_ROW_COUNT_MAX = 25; // A PDF471 barcode should have at least 3 rows, with each row being >= 3 times the module width. Therefore it should be at least // 9 pixels tall. To be conservative, we use about half the size to ensure we don't miss it. Detector.ROW_STEP = 5; Detector.BARCODE_MIN_HEIGHT = 10; return Detector; }()); exports.default = Detector;