From fc690ea36930d70fe76af2ef6405038f53c4ddc8 Mon Sep 17 00:00:00 2001 From: "Luiz FM. Barni" Date: Tue, 1 Dec 2020 18:05:47 -0300 Subject: [PATCH 01/19] WIP code --- .vscode/launch.json | 18 ++ src/core/Reader.ts | 3 +- src/core/Result.ts | 34 +- src/core/multi/MultipleBarcodeReader.ts | 3 +- src/core/multi/qrcode/QRCodeMultiReader.ts | 181 +++++++++++ .../multi/qrcode/detector/MultiDetector.ts | 89 ++++++ .../detector/MultiFinderPatternFinder.ts | 297 ++++++++++++++++++ src/core/qrcode/QRCodeReader.ts | 9 +- src/core/qrcode/decoder/Decoder.ts | 1 + .../qrcode/detector/FinderPatternFinder.ts | 227 +++++++++++-- src/core/util/Collections.ts | 13 +- src/core/util/Comparator.ts | 13 + src/core/util/Float.ts | 4 + src/core/util/Integer.ts | 17 + src/customTypings.ts | 1 + .../core/multi/qrcode/MultiQRCode.spec.ts | 113 +++++++ 16 files changed, 968 insertions(+), 55 deletions(-) create mode 100644 src/core/multi/qrcode/QRCodeMultiReader.ts create mode 100644 src/core/multi/qrcode/detector/MultiDetector.ts create mode 100644 src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts create mode 100644 src/core/util/Comparator.ts create mode 100644 src/test/core/multi/qrcode/MultiQRCode.spec.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 370616e6..b7e99c36 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -152,6 +152,24 @@ "./src/test/core/aztec/**/*.spec.ts" ], "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "node", + "request": "launch", + "name": "Multi-reader Tests - ts-node", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "--require", + "ts-node/register", + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "--recursive", + "./src/test/core/multi/**/*.spec.ts" + ], + "internalConsoleOptions": "openOnSessionStart" } ] } diff --git a/src/core/Reader.ts b/src/core/Reader.ts index 0757759d..66cabf25 100644 --- a/src/core/Reader.ts +++ b/src/core/Reader.ts @@ -46,8 +46,9 @@ interface Reader { * @throws NotFoundException if no potential barcode is found * @throws ChecksumException if a potential barcode is found but does not pass its checksum * @throws FormatException if a potential barcode is found but format is invalid + * @override decode */ - // decode(image: BinaryBitmap): Result /*throws NotFoundException, ChecksumException, FormatException*/ + decodeWithoutHints(image: BinaryBitmap): Result; /** * Locates and decodes a barcode in some format within an image. This method also accepts diff --git a/src/core/Result.ts b/src/core/Result.ts index 3af8b8e2..dcf568ff 100644 --- a/src/core/Result.ts +++ b/src/core/Result.ts @@ -32,21 +32,25 @@ export default class Result { private resultMetadata: Map; - // public constructor(private text: string, - // Uint8Array rawBytes, - // ResultPoconst resultPoints: Int32Array, - // BarcodeFormat format) { - // this(text, rawBytes, resultPoints, format, System.currentTimeMillis()) - // } - - // public constructor(text: string, - // Uint8Array rawBytes, - // ResultPoconst resultPoints: Int32Array, - // BarcodeFormat format, - // long timestamp) { - // this(text, rawBytes, rawBytes == null ? 0 : 8 * rawBytes.length, - // resultPoints, format, timestamp) - // } + public static constructor4Args( + text: string, + rawBytes: Uint8Array, + resultPoints: ResultPoint[], + format: BarcodeFormat, + ) { + return Result.constructor5Args(text, rawBytes, resultPoints, format, System.currentTimeMillis()); + } + + public static constructor5Args( + text: string, + rawBytes: Uint8Array, + resultPoints: ResultPoint[], + format: BarcodeFormat, + timestamp: number /* long */, + ) { + return new Result(text, rawBytes, rawBytes == null ? 0 : 8 * rawBytes.length, + resultPoints, format, timestamp); + } public constructor(private text: string, private rawBytes: Uint8Array, diff --git a/src/core/multi/MultipleBarcodeReader.ts b/src/core/multi/MultipleBarcodeReader.ts index b8d7c4e6..2cd29115 100644 --- a/src/core/multi/MultipleBarcodeReader.ts +++ b/src/core/multi/MultipleBarcodeReader.ts @@ -36,8 +36,9 @@ export default /*public*/ interface MultipleBarcodeReader { /** * @throws NotFoundException + * @override decodeMultiple */ - decodeMultiple(image: BinaryBitmap): Result[]; + decodeMultipleWithoutHints(image: BinaryBitmap): Result[]; /** * @throws NotFoundException diff --git a/src/core/multi/qrcode/QRCodeMultiReader.ts b/src/core/multi/qrcode/QRCodeMultiReader.ts new file mode 100644 index 00000000..9efba61d --- /dev/null +++ b/src/core/multi/qrcode/QRCodeMultiReader.ts @@ -0,0 +1,181 @@ +/* + * 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. + */ + +import BarcodeFormat from "src/core/BarcodeFormat"; +import BinaryBitmap from "src/core/BinaryBitmap"; +import DecoderResult from "src/core/common/DecoderResult"; +import DetectorResult from "src/core/common/DetectorResult"; +import DecodeHintType from "src/core/DecodeHintType"; +import QRCodeDecoderMetaData from "src/core/qrcode/decoder/QRCodeDecoderMetaData"; +import QRCodeReader from "src/core/qrcode/QRCodeReader"; +import ReaderException from "src/core/ReaderException"; +import Result from "src/core/Result"; +import ResultMetadataType from "src/core/ResultMetadataType"; +import ResultPoint from "src/core/ResultPoint"; +import ByteArrayOutputStream from "src/core/util/ByteArrayOutputStream"; +import Collections from "src/core/util/Collections"; +import Comparator from "src/core/util/Comparator"; +import Integer from "src/core/util/Integer"; +import StringBuilder from "src/core/util/StringBuilder"; +import { int, List } from "src/customTypings"; +import MultipleBarcodeReader from "../MultipleBarcodeReader"; +import MultiDetector from "./detector/MultiDetector"; + +// package com.google.zxing.multi.qrcode; + +// import com.google.zxing.BarcodeFormat; +// import com.google.zxing.BinaryBitmap; +// import com.google.zxing.DecodeHintType; +// import com.google.zxing.NotFoundException; +// import com.google.zxing.ReaderException; +// import com.google.zxing.Result; +// import com.google.zxing.ResultMetadataType; +// import com.google.zxing.ResultPoint; +// import com.google.zxing.common.DecoderResult; +// import com.google.zxing.common.DetectorResult; +// import com.google.zxing.multi.MultipleBarcodeReader; +// import com.google.zxing.multi.qrcode.detector.MultiDetector; +// import com.google.zxing.qrcode.QRCodeReader; +// import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData; + +// import java.io.ByteArrayOutputStream; +// import java.io.Serializable; +// import java.util.ArrayList; +// import java.util.List; +// import java.util.Map; +// import java.util.Collections; +// import java.util.Comparator; + +/** + * This implementation can detect and decode multiple QR Codes in an image. + * + * @author Sean Owen + * @author Hannes Erven + */ +export default /*public final*/ class QRCodeMultiReader extends QRCodeReader implements MultipleBarcodeReader { + + private static /* final */ EMPTY_RESULT_ARRAY: Result[] = []; + protected static /* final */ NO_POINTS = new Array(); + + /** + * @throws NotFoundException + * @override decodeMultiple + */ + public decodeMultipleWithoutHints(image: BinaryBitmap): Result[] { + return this.decodeMultiple(image, null); + } + + /** + * @override + * @throws NotFoundException + */ + public decodeMultiple(image: BinaryBitmap, hints: Map): Result[] { + let results: List = []; + const detectorResults: DetectorResult[] = new MultiDetector(image.getBlackMatrix()).detectMulti(hints); + for (const detectorResult of detectorResults) { + try { + const decoderResult: DecoderResult = this.getDecoder().decodeBitMatrix(detectorResult.getBits(), hints); + const points: ResultPoint[] = detectorResult.getPoints(); + // If the code was mirrored: swap the bottom-left and the top-right points. + if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) { + ( decoderResult.getOther()).applyMirroredCorrection(points); + } + const result: Result = Result.constructor4Args(decoderResult.getText(), decoderResult.getRawBytes(), points, + BarcodeFormat.QR_CODE); + const byteSegments: List = decoderResult.getByteSegments(); + if (byteSegments != null) { + result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments); + } + const ecLevel: string = decoderResult.getECLevel(); + if (ecLevel != null) { + result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel); + } + if (decoderResult.hasStructuredAppend()) { + result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, + decoderResult.getStructuredAppendSequenceNumber()); + result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY, + decoderResult.getStructuredAppendParity()); + } + results.push(result); + } catch (re) { + if (re instanceof ReaderException) { + // ignore and continue + } else { + throw re; + } + } + } + if (results.length === 0) { + return QRCodeMultiReader.EMPTY_RESULT_ARRAY; + } else { + results = QRCodeMultiReader.processStructuredAppend(results); + return results/* .toArray(QRCodeMultiReader.EMPTY_RESULT_ARRAY) */; + } + } + + static processStructuredAppend( results: List): List { + const newResults: List = []; + const saResults: List = []; + for (const result of results) { + if (result.getResultMetadata().has(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE)) { + saResults.push(result); + } else { + newResults.push(result); + } + } + if (saResults.length === 0) { + return results; + } + + // sort and concatenate the SA list items + Collections.sort(saResults, new SAComparator()); + const newText: StringBuilder = new StringBuilder(); + const newRawBytes: ByteArrayOutputStream = new ByteArrayOutputStream(); + const newByteSegment: ByteArrayOutputStream = new ByteArrayOutputStream(); + for (const saResult of saResults) { + newText.append(saResult.getText()); + const saBytes: Uint8Array = saResult.getRawBytes(); + newRawBytes.writeBytesOffset(saBytes, 0, saBytes.length); + // @SuppressWarnings("unchecked") + const byteSegments: Iterable = + > saResult.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS); + if (byteSegments != null) { + for (const segment of byteSegments) { + newByteSegment.writeBytesOffset(segment, 0, segment.length); + } + } + } + + const newResult: Result = Result.constructor4Args(newText.toString(), newRawBytes.toByteArray(), QRCodeMultiReader.NO_POINTS, BarcodeFormat.QR_CODE); + if (newByteSegment.size() > 0) { + newResult.putMetadata(ResultMetadataType.BYTE_SEGMENTS, Collections.singletonList(newByteSegment.toByteArray())); + } + newResults.push(newResult); + return newResults; + } + +} + +/* private static final*/ class SAComparator implements Comparator/*, Serializable*/ { + /** + * @override + */ + public compare(a: Result, b: Result): int { + const aNumber: int = a.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE); + const bNumber: int = b.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE); + return Integer.compare(aNumber, bNumber); + } +} diff --git a/src/core/multi/qrcode/detector/MultiDetector.ts b/src/core/multi/qrcode/detector/MultiDetector.ts new file mode 100644 index 00000000..96d90d11 --- /dev/null +++ b/src/core/multi/qrcode/detector/MultiDetector.ts @@ -0,0 +1,89 @@ +/* + * 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. + */ + +import BitMatrix from "src/core/common/BitMatrix"; +import DetectorResult from "src/core/common/DetectorResult"; +import DecodeHintType from "src/core/DecodeHintType"; +import NotFoundException from "src/core/NotFoundException"; +import Detector from "src/core/qrcode/detector/Detector"; +import FinderPatternInfo from "src/core/qrcode/detector/FinderPatternInfo"; +import ReaderException from "src/core/ReaderException"; +import ResultPointCallback from "src/core/ResultPointCallback"; +import { List } from "src/customTypings"; +import MultiFinderPatternFinder from "./MultiFinderPatternFinder"; + +// package com.google.zxing.multi.qrcode.detector; + +// import com.google.zxing.DecodeHintType; +// import com.google.zxing.NotFoundException; +// import com.google.zxing.ReaderException; +// import com.google.zxing.ResultPointCallback; +// import com.google.zxing.common.BitMatrix; +// import com.google.zxing.common.DetectorResult; +// import com.google.zxing.qrcode.detector.Detector; +// import com.google.zxing.qrcode.detector.FinderPatternInfo; + +// import java.util.ArrayList; +// import java.util.List; +// import java.util.Map; + +/** + *

Encapsulates logic that can detect one or more QR Codes in an image, even if the QR Code + * is rotated or skewed, or partially obscured.

+ * + * @author Sean Owen + * @author Hannes Erven + */ +export default /* public final */ class MultiDetector extends Detector { + + private static /* final */ EMPTY_DETECTOR_RESULTS: DetectorResult[] = []; + + public constructor( image: BitMatrix) { + super(image); + } + + /** @throws NotFoundException */ + public detectMulti( hints: Map): DetectorResult[] { + const image: BitMatrix = this.getImage(); + const resultPointCallback: ResultPointCallback = + hints == null ? null : hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); + const finder: MultiFinderPatternFinder = new MultiFinderPatternFinder(image, resultPointCallback); + const infos: FinderPatternInfo[] = finder.findMulti(hints); + + if (infos.length === 0) { + throw NotFoundException.getNotFoundInstance(); + } + + const result: List = []; + for (const info of infos) { + try { + result.push(this.processFinderPatternInfo(info)); + } catch (e) { + if (e instanceof ReaderException) { + // ignore + } else { + throw e; + } + } + } + if (result.length === 0) { + return MultiDetector.EMPTY_DETECTOR_RESULTS; + } else { + return result/* .toArray(EMPTY_DETECTOR_RESULTS) */; + } + } + +} diff --git a/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts b/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts new file mode 100644 index 00000000..e4131ee7 --- /dev/null +++ b/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts @@ -0,0 +1,297 @@ +/* + * 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. + */ + +import { BitMatrix, NotFoundException, ResultPoint, DecodeHintType } from "src"; +import FinderPattern from "src/core/qrcode/detector/FinderPattern"; +import FinderPatternFinder from "src/core/qrcode/detector/FinderPatternFinder"; +import FinderPatternInfo from "src/core/qrcode/detector/FinderPatternInfo"; +import ResultPointCallback from "src/core/ResultPointCallback"; +import Collections from "src/core/util/Collections"; +import Comparator from "src/core/util/Comparator"; +import { double, float, int, List } from "src/customTypings"; + +// package com.google.zxing.multi.qrcode.detector; + +// import com.google.zxing.DecodeHintType; +// import com.google.zxing.NotFoundException; +// import com.google.zxing.ResultPoint; +// import com.google.zxing.ResultPointCallback; +// import com.google.zxing.common.BitMatrix; +// import com.google.zxing.qrcode.detector.FinderPattern; +// import com.google.zxing.qrcode.detector.FinderPatternFinder; +// import com.google.zxing.qrcode.detector.FinderPatternInfo; + +// import java.io.Serializable; +// import java.util.ArrayList; +// import java.util.Collections; +// import java.util.Comparator; +// import java.util.List; +// import java.util.Map; + +/** + *

This class attempts to find finder patterns in a QR Code. Finder patterns are the square + * markers at three corners of a QR Code.

+ * + *

This class is thread-safe but not reentrant. Each thread must allocate its own object. + * + *

In contrast to {@link FinderPatternFinder}, this class will return an array of all possible + * QR code locations in the image.

+ * + *

Use the TRY_HARDER hint to ask for a more thorough detection.

+ * + * @author Sean Owen + * @author Hannes Erven + */ +export default /* public final */ class MultiFinderPatternFinder extends FinderPatternFinder { + + private static /* final */ EMPTY_RESULT_ARRAY: FinderPatternInfo[] = []; + private static /* final */ EMPTY_FP_ARRAY: FinderPattern[] = []; + private static /* final */ EMPTY_FP_2D_ARRAY: FinderPattern[][] = [[]]; + + // TODO MIN_MODULE_COUNT and MAX_MODULE_COUNT would be great hints to ask the user for + // since it limits the number of regions to decode + + // max. legal count of modules per QR code edge (177) + private static /* final */ MAX_MODULE_COUNT_PER_EDGE: float = 180; + // min. legal count per modules per QR code edge (11) + private static /* final */ MIN_MODULE_COUNT_PER_EDGE: float = 9; + + /** + * More or less arbitrary cutoff point for determining if two finder patterns might belong + * to the same code if they differ less than DIFF_MODSIZE_CUTOFF_PERCENT percent in their + * estimated modules sizes. + */ + private static /* final */ DIFF_MODSIZE_CUTOFF_PERCENT: float = 0.05; + + /** + * More or less arbitrary cutoff point for determining if two finder patterns might belong + * to the same code if they differ less than DIFF_MODSIZE_CUTOFF pixels/module in their + * estimated modules sizes. + */ + private static /* final */ DIFF_MODSIZE_CUTOFF: float = 0.5; + + + public constructor(image: BitMatrix, resultPointCallback: ResultPointCallback) { + super(image, resultPointCallback); + } + + /** + * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are + * those that have been detected at least 2 times, and whose module + * size differs from the average among those patterns the least + * @throws NotFoundException if 3 such finder patterns do not exist + */ + private selectMultipleBestPatterns(): FinderPattern[][] { + const possibleCenters: List = this.getPossibleCenters(); + const size: int = possibleCenters.length; + + if (size < 3) { + // Couldn't find enough finder patterns + throw NotFoundException.getNotFoundInstance(); + } + + /* + * Begin HE modifications to safely detect multiple codes of equal size + */ + if (size === 3) { + return [ possibleCenters ]; + } + + // Sort by estimated module size to speed up the upcoming checks + Collections.sort(possibleCenters, new ModuleSizeComparator()); + + /* + * Now lets start: build a list of tuples of three finder locations that + * - feature similar module sizes + * - are placed in a distance so the estimated module count is within the QR specification + * - have similar distance between upper left/right and left top/bottom finder patterns + * - form a triangle with 90° angle (checked by comparing top right/bottom left distance + * with pythagoras) + * + * Note: we allow each point to be used for more than one code region: this might seem + * counterintuitive at first, but the performance penalty is not that big. At this point, + * we cannot make a good quality decision whether the three finders actually represent + * a QR code, or are just by chance laid out so it looks like there might be a QR code there. + * So, if the layout seems right, lets have the decoder try to decode. + */ + + const results: List = new Array(); // holder for the results + + for (let i1: int = 0; i1 < (size - 2); i1++) { + const p1: FinderPattern = possibleCenters[i1]; + if (p1 == null) { + continue; + } + + for (let i2: int = i1 + 1; i2 < (size - 1); i2++) { + const p2: FinderPattern = possibleCenters[i2]; + if (p2 == null) { + continue; + } + + // Compare the expected module sizes; if they are really off, skip + const vModSize12: float = (p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize()) / + Math.min(p1.getEstimatedModuleSize(), p2.getEstimatedModuleSize()); + const vModSize12A: float = Math.abs(p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize()); + if (vModSize12A > MultiFinderPatternFinder.DIFF_MODSIZE_CUTOFF && vModSize12 >= MultiFinderPatternFinder.DIFF_MODSIZE_CUTOFF_PERCENT) { + // break, since elements are ordered by the module size deviation there cannot be + // any more interesting elements for the given p1. + break; + } + + for (let i3: int = i2 + 1; i3 < size; i3++) { + const p3: FinderPattern = possibleCenters[i3]; + if (p3 == null) { + continue; + } + + // Compare the expected module sizes; if they are really off, skip + const vModSize23: float = (p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize()) / + Math.min(p2.getEstimatedModuleSize(), p3.getEstimatedModuleSize()); + const vModSize23A: float = Math.abs(p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize()); + if (vModSize23A > MultiFinderPatternFinder.DIFF_MODSIZE_CUTOFF && vModSize23 >= MultiFinderPatternFinder.DIFF_MODSIZE_CUTOFF_PERCENT) { + // break, since elements are ordered by the module size deviation there cannot be + // any more interesting elements for the given p1. + break; + } + + const test: FinderPattern[] = [p1, p2, p3]; + ResultPoint.orderBestPatterns(test); + + // Calculate the distances: a = topleft-bottomleft, b=topleft-topright, c = diagonal + const info: FinderPatternInfo = new FinderPatternInfo(test); + const dA: float = ResultPoint.distance(info.getTopLeft(), info.getBottomLeft()); + const dC: float = ResultPoint.distance(info.getTopRight(), info.getBottomLeft()); + const dB: float = ResultPoint.distance(info.getTopLeft(), info.getTopRight()); + + // Check the sizes + const estimatedModuleCount: float = (dA + dB) / (p1.getEstimatedModuleSize() * 2.0); + if (estimatedModuleCount > MultiFinderPatternFinder.MAX_MODULE_COUNT_PER_EDGE || + estimatedModuleCount < MultiFinderPatternFinder.MIN_MODULE_COUNT_PER_EDGE) { + continue; + } + + // Calculate the difference of the edge lengths in percent + const vABBC: float = Math.abs((dA - dB) / Math.min(dA, dB)); + if (vABBC >= 0.1) { + continue; + } + + // Calculate the diagonal length by assuming a 90° angle at topleft + const dCpy: float = Math.sqrt( dA * dA + dB * dB); + // Compare to the real distance in % + const vPyC: float = Math.abs((dC - dCpy) / Math.min(dC, dCpy)); + + if (vPyC >= 0.1) { + continue; + } + + // All tests passed! + results.push(test); + } + } + } + + if (results.length > 0) { + return results/* .toArray(MultiFinderPatternFinder.EMPTY_FP_2D_ARRAY) */; + } + + // Nothing found! + throw NotFoundException.getNotFoundInstance(); + } + + /** + * @throws NotFoundException + */ + public findMulti(hints: Map): FinderPatternInfo[] { + const tryHarder: boolean = hints != null && hints.has(DecodeHintType.TRY_HARDER); + const image: BitMatrix = this.getImage(); + const maxI: int = image.getHeight(); + const maxJ: int = 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. + let iSkip: int = (3 * maxI) / (4 * MultiFinderPatternFinder.MAX_MODULES); + if (iSkip < MultiFinderPatternFinder.MIN_SKIP || tryHarder) { + iSkip = MultiFinderPatternFinder.MIN_SKIP; + } + + const stateCount: Int32Array = Int32Array.from({ length: 5 }); + for (let i: int = iSkip - 1; i < maxI; i += iSkip) { + // Get a row of black/white values + MultiFinderPatternFinder.doClearCounts(stateCount); + let currentState: int = 0; + for (let j: int = 0; j < maxJ; j++) { + if (image.get(j, i)) { + // 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 (MultiFinderPatternFinder.foundPatternCross(stateCount) && this.handlePossibleCenter2(stateCount, i, j)) { // Yes + // Clear state to start looking again + currentState = 0; + MultiFinderPatternFinder.doClearCounts(stateCount); + } else { // No, shift counts back by two + MultiFinderPatternFinder.doShiftCounts2(stateCount); + currentState = 3; + } + } else { + stateCount[++currentState]++; + } + } else { // Counting white pixels + stateCount[currentState]++; + } + } + } // for j=... + + if (MultiFinderPatternFinder.foundPatternCross(stateCount)) { + this.handlePossibleCenter2(stateCount, i, maxJ); + } + } // for i=iSkip-1 ... + const patternInfo: FinderPattern[][] = this.selectMultipleBestPatterns(); + const result: List = new Array(); + for (const pattern of patternInfo) { + ResultPoint.orderBestPatterns(pattern); + result.push(new FinderPatternInfo(pattern)); + } + + if (result.length === 0) { + return MultiFinderPatternFinder.EMPTY_RESULT_ARRAY; + } else { + return result/* .toArray(MultiFinderPatternFinder.EMPTY_RESULT_ARRAY) */; + } + } + +} + + /** + * A comparator that orders FinderPatterns by their estimated module size. + */ + /* private static final */ class ModuleSizeComparator implements Comparator/* , Serializable */ { + /** @override */ + public compare(center1: FinderPattern, center2: FinderPattern): int { + const value: float = center2.getEstimatedModuleSize() - center1.getEstimatedModuleSize(); + return value < 0.0 ? -1 : value > 0.0 ? 1 : 0; + } + } diff --git a/src/core/qrcode/QRCodeReader.ts b/src/core/qrcode/QRCodeReader.ts index 6380bcea..5a552af7 100644 --- a/src/core/qrcode/QRCodeReader.ts +++ b/src/core/qrcode/QRCodeReader.ts @@ -42,7 +42,7 @@ import Detector from './detector/Detector'; */ export default class QRCodeReader implements Reader { - private static NO_POINTS = new Array(); + protected static NO_POINTS = new Array(); private decoder = new Decoder(); @@ -58,10 +58,9 @@ export default class QRCodeReader implements Reader { * @throws FormatException if a QR code cannot be decoded * @throws ChecksumException if error correction fails */ - /*@Override*/ - // public decode(image: BinaryBitmap): Result /*throws NotFoundException, ChecksumException, FormatException */ { - // return this.decode(image, null) - // } + public decodeWithoutHints(image: BinaryBitmap): Result /*throws NotFoundException, ChecksumException, FormatException */ { + return this.decode(image, null); + } /*@Override*/ public decode(image: BinaryBitmap, hints?: Map): Result { diff --git a/src/core/qrcode/decoder/Decoder.ts b/src/core/qrcode/decoder/Decoder.ts index 7a5a9264..b080a08f 100644 --- a/src/core/qrcode/decoder/Decoder.ts +++ b/src/core/qrcode/decoder/Decoder.ts @@ -74,6 +74,7 @@ export default class Decoder { * @return text and bytes encoded within the QR Code * @throws FormatException if the QR Code cannot be decoded * @throws ChecksumException if error correction fails + * @override decode */ public decodeBitMatrix(bits: BitMatrix, hints?: Map): DecoderResult { diff --git a/src/core/qrcode/detector/FinderPatternFinder.ts b/src/core/qrcode/detector/FinderPatternFinder.ts index 8415b19c..88228f02 100644 --- a/src/core/qrcode/detector/FinderPatternFinder.ts +++ b/src/core/qrcode/detector/FinderPatternFinder.ts @@ -25,7 +25,9 @@ import FinderPatternInfo from './FinderPatternInfo'; import NotFoundException from '../../NotFoundException'; -import { float } from '../../../customTypings'; +import { float, int } from '../../../customTypings'; +import Float from 'src/core/util/Float'; +import Arrays from 'src/core/util/Arrays'; /*import java.io.Serializable;*/ /*import java.util.ArrayList;*/ @@ -322,6 +324,102 @@ export default class FinderPatternFinder { FinderPatternFinder.foundPatternCross(stateCount); } + /** + * After a vertical and horizontal scan finds a potential finder pattern, this method + * "cross-cross-cross-checks" by scanning down diagonally through the center of the possible + * finder pattern to see if the same proportion is detected. + * + * @param centerI row where a finder pattern was detected + * @param centerJ center of the section that appears to cross a finder pattern + * @return true if proportions are withing expected limits + */ + private crossCheckDiagonal2(centerI: int, centerJ: int): boolean { + const stateCount: Int32Array = this.getCrossCheckStateCount(); + + // Start counting up, left from center finding black center mass + let i: int = 0; + while (centerI >= i && centerJ >= i && this.image.get(centerJ - i, centerI - i)) { + stateCount[2]++; + i++; + } + if (stateCount[2] === 0) { + return false; + } + + // Continue up, left finding white space + while (centerI >= i && centerJ >= i && !this.image.get(centerJ - i, centerI - i)) { + stateCount[1]++; + i++; + } + if (stateCount[1] === 0) { + return false; + } + + // Continue up, left finding black border + while (centerI >= i && centerJ >= i && this.image.get(centerJ - i, centerI - i)) { + stateCount[0]++; + i++; + } + if (stateCount[0] === 0) { + return false; + } + + let maxI: int = this.image.getHeight(); + let maxJ: int = this.image.getWidth(); + + // Now also count down, right from center + i = 1; + while (centerI + i < maxI && centerJ + i < maxJ && this.image.get(centerJ + i, centerI + i)) { + stateCount[2]++; + i++; + } + + while (centerI + i < maxI && centerJ + i < maxJ && !this.image.get(centerJ + i, centerI + i)) { + stateCount[3]++; + i++; + } + if (stateCount[3] === 0) { + return false; + } + + while (centerI + i < maxI && centerJ + i < maxJ && this.image.get(centerJ + i, centerI + i)) { + stateCount[4]++; + i++; + } + if (stateCount[4] === 0) { + return false; + } + + return FinderPatternFinder.foundPatternDiagonal(stateCount); + } + + /** + * @param stateCount count of black/white/black/white/black pixels just read + * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios + * used by finder patterns to be considered a match + */ + protected static foundPatternDiagonal(stateCount: Int32Array): boolean { + let totalModuleSize: int = 0; + for (let i: int = 0; i < 5; i++) { + let count: int = stateCount[i]; + if (count === 0) { + return false; + } + totalModuleSize += count; + } + if (totalModuleSize < 7) { + return false; + } + const moduleSize: float = totalModuleSize / 7.0; + const maxVariance: float = moduleSize / 1.333; + // Allow less than 75% variance from 1-1-3-1-1 proportions + return Math.abs(moduleSize - stateCount[0]) < maxVariance && + Math.abs(moduleSize - stateCount[1]) < maxVariance && + Math.abs(3.0 * moduleSize - stateCount[2]) < 3 * maxVariance && + Math.abs(moduleSize - stateCount[3]) < maxVariance && + Math.abs(moduleSize - stateCount[4]) < maxVariance; + } + /** *

After a horizontal scan finds a potential finder pattern, this method * "cross-checks" by scanning down vertically through the center of the possible @@ -469,6 +567,51 @@ export default class FinderPatternFinder { return FinderPatternFinder.foundPatternCross(stateCount) ? FinderPatternFinder.centerFromEnd(stateCount, j) : NaN; } + /** + * @param stateCount reading state module counts from horizontal scan + * @param i row where finder pattern may be found + * @param j end of possible finder pattern in row + * @param pureBarcode ignored + * @return true if a finder pattern candidate was found this time + * @deprecated only exists for backwards compatibility + * @see #handlePossibleCenter(int[], int, int) + * @override handlePossibleCenter + */ + protected /* final */ handlePossibleCenter(stateCount: Int32Array, i: number /*int*/, j: number /*int*/, pureBarcode: boolean): boolean { + const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + + stateCount[4]; + let centerJ: number /*float*/ = FinderPatternFinder.centerFromEnd(stateCount, j); + let centerI: number /*float*/ = this.crossCheckVertical(i, /*(int) */Math.floor(centerJ), stateCount[2], stateCountTotal); + if (!isNaN(centerI)) { + // Re-cross check + centerJ = this.crossCheckHorizontal(/*(int) */Math.floor(centerJ), /*(int) */Math.floor(centerI), stateCount[2], stateCountTotal); + if (!isNaN(centerJ) && + (!pureBarcode || this.crossCheckDiagonal(/*(int) */Math.floor(centerI), /*(int) */Math.floor(centerJ), stateCount[2], stateCountTotal))) { + const estimatedModuleSize: number /*float*/ = stateCountTotal / 7.0; + let found: boolean = false; + const possibleCenters = this.possibleCenters; + for (let index = 0, length = possibleCenters.length; index < length; index++) { + const center: FinderPattern = possibleCenters[index]; + // Look for about the same center and module size: + if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { + possibleCenters[index] = center.combineEstimate(centerI, centerJ, estimatedModuleSize); + found = true; + break; + } + } + if (!found) { + const point: FinderPattern = new FinderPattern(centerJ, centerI, estimatedModuleSize); + possibleCenters.push(point); + if (this.resultPointCallback !== null && this.resultPointCallback !== undefined) { + this.resultPointCallback.foundPossibleResultPoint(point); + } + } + return true; + } + } + return false; + } + /** *

This is called when a horizontal scan finds a possible alignment pattern. It will * cross check with a vertical scan, and if successful, will, ah, cross-cross-check @@ -487,39 +630,37 @@ export default class FinderPatternFinder { * @param pureBarcode true if in "pure barcode" mode * @return true if a finder pattern candidate was found this time */ - protected handlePossibleCenter(stateCount: Int32Array, i: number /*int*/, j: number /*int*/, pureBarcode: boolean): boolean { - const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + - stateCount[4]; - let centerJ: number /*float*/ = FinderPatternFinder.centerFromEnd(stateCount, j); - let centerI: number /*float*/ = this.crossCheckVertical(i, /*(int) */Math.floor(centerJ), stateCount[2], stateCountTotal); - if (!isNaN(centerI)) { - // Re-cross check - centerJ = this.crossCheckHorizontal(/*(int) */Math.floor(centerJ), /*(int) */Math.floor(centerI), stateCount[2], stateCountTotal); - if (!isNaN(centerJ) && - (!pureBarcode || this.crossCheckDiagonal(/*(int) */Math.floor(centerI), /*(int) */Math.floor(centerJ), stateCount[2], stateCountTotal))) { - const estimatedModuleSize: number /*float*/ = stateCountTotal / 7.0; - let found: boolean = false; - const possibleCenters = this.possibleCenters; - for (let index = 0, length = possibleCenters.length; index < length; index++) { - const center: FinderPattern = possibleCenters[index]; - // Look for about the same center and module size: - if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { - possibleCenters[index] = center.combineEstimate(centerI, centerJ, estimatedModuleSize); - found = true; - break; - } - } - if (!found) { - const point: FinderPattern = new FinderPattern(centerJ, centerI, estimatedModuleSize); - possibleCenters.push(point); - if (this.resultPointCallback !== null && this.resultPointCallback !== undefined) { - this.resultPointCallback.foundPossibleResultPoint(point); - } - } - return true; + protected handlePossibleCenter2(stateCount: Int32Array, i: number /*int*/, j: number /*int*/): boolean { + const stateCountTotal: int = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + + stateCount[4]; + let centerJ: float = FinderPatternFinder.centerFromEnd(stateCount, j); + let centerI: float = this.crossCheckVertical(i, centerJ, stateCount[2], stateCountTotal); + if (!Float.isNaN(centerI)) { + // Re-cross check + centerJ = this.crossCheckHorizontal( centerJ, centerI, stateCount[2], stateCountTotal); + if (!Float.isNaN(centerJ) && this.crossCheckDiagonal2( centerI, centerJ)) { + const estimatedModuleSize: float = stateCountTotal / 7.0; + let found: boolean = false; + for (let index: int = 0; index < this.possibleCenters.length; index++) { + const center: FinderPattern = this.possibleCenters[index]; + // Look for about the same center and module size: + if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { + this.possibleCenters[index] = center.combineEstimate(centerI, centerJ, estimatedModuleSize); + found = true; + break; + } + } + if (!found) { + const point: FinderPattern = new FinderPattern(centerJ, centerI, estimatedModuleSize); + this.possibleCenters.push(point); + if (this.resultPointCallback) { + this.resultPointCallback.foundPossibleResultPoint(point); } + } + return true; } - return false; + } + return false; } /** @@ -669,4 +810,26 @@ export default class FinderPatternFinder { possibleCenters[2] ]; } + + /** @deprecated */ + protected /* final */ clearCounts(counts: Int32Array): void { + FinderPatternFinder.doClearCounts(counts); + } + + /** @deprecated */ + protected /* final */ shiftCounts2(stateCount: Int32Array): void { + FinderPatternFinder.doShiftCounts2(stateCount); + } + + protected static doClearCounts(counts: Int32Array): void { + Arrays.fill(counts, 0); + } + + protected static doShiftCounts2(stateCount: Int32Array): void { + stateCount[0] = stateCount[2]; + stateCount[1] = stateCount[3]; + stateCount[2] = stateCount[4]; + stateCount[3] = 1; + stateCount[4] = 0; + } } diff --git a/src/core/util/Collections.ts b/src/core/util/Collections.ts index 67142fb7..f863a002 100644 --- a/src/core/util/Collections.ts +++ b/src/core/util/Collections.ts @@ -1,4 +1,5 @@ -import { Collection, int } from '../../customTypings'; +import { Collection, int, List } from '../../customTypings'; +import Comparator from './Comparator'; export default class Collections { @@ -9,6 +10,16 @@ export default class Collections { return [item]; } + /** + * Sorts the specified list according to the order induced by the specified comparator. + */ + static sort( + list: List | Array | TToBeCompared[], + comparator: Comparator, + ) { + list.sort(comparator.compare); + } + /** * The min(Collection, Comparator) method is used to return the minimum element of the given collection, according to the order induced by the specified comparator. */ diff --git a/src/core/util/Comparator.ts b/src/core/util/Comparator.ts new file mode 100644 index 00000000..5b39452b --- /dev/null +++ b/src/core/util/Comparator.ts @@ -0,0 +1,13 @@ +import { int } from 'src/customTypings'; + +/** + * Java Comparator interface polyfill. + */ +export default interface Comparator { + /** + * Compares its two arguments for order. Returns a negative integer, zero, + * or a positive integer as the first argument is less than, equal to, + * or greater than the second. + */ + compare(a: T, b: T): int; +} diff --git a/src/core/util/Float.ts b/src/core/util/Float.ts index 86f8458d..4258fc86 100644 --- a/src/core/util/Float.ts +++ b/src/core/util/Float.ts @@ -15,4 +15,8 @@ export default class Float { public static floatToIntBits(f: number): number { return f; } + + public static isNaN(num: number) { + return isNaN(num); + } } diff --git a/src/core/util/Integer.ts b/src/core/util/Integer.ts index 80a9c36e..13f8556d 100644 --- a/src/core/util/Integer.ts +++ b/src/core/util/Integer.ts @@ -1,3 +1,5 @@ +import { int } from "src/customTypings"; + /** * Ponyfill for Java's Integer class. */ @@ -6,6 +8,21 @@ export default class Integer { static MIN_VALUE_32_BITS = -2147483648; static MAX_VALUE: number = Number.MAX_SAFE_INTEGER; + /** + * Parameter : + * x : the first int to compare + * y : the second int to compare + * Return : + * This method returns the value zero if (x==y), + * if (x < y) then it returns a value less than zero + * and if (x > y) then it returns a value greater than zero. + */ + static compare(x: int, y: int): number { + if (x === y) return 0; + if (x < y) return -1; + if (x > y) return 1; + } + public static numberOfTrailingZeros(i: number): number { let y: number; diff --git a/src/customTypings.ts b/src/customTypings.ts index 78d284aa..9e1df3fe 100644 --- a/src/customTypings.ts +++ b/src/customTypings.ts @@ -12,6 +12,7 @@ export declare type byte = number; export declare type short = number; export declare type int = number; export declare type float = number; +export declare type double = number; // special formats export type char = number; diff --git a/src/test/core/multi/qrcode/MultiQRCode.spec.ts b/src/test/core/multi/qrcode/MultiQRCode.spec.ts new file mode 100644 index 00000000..58a6848d --- /dev/null +++ b/src/test/core/multi/qrcode/MultiQRCode.spec.ts @@ -0,0 +1,113 @@ +/* + * Copyright 2016 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. + */ + +import { BinaryBitmap, HybridBinarizer, LuminanceSource, Result, ResultMetadataType } from "src"; +import BarcodeFormat from "src/core/BarcodeFormat"; +import MultipleBarcodeReader from "src/core/multi/MultipleBarcodeReader"; +import QRCodeMultiReader from "src/core/multi/qrcode/QRCodeMultiReader"; +import Arrays from "src/core/util/Arrays"; +import { Collection, List } from "src/customTypings"; +import AbstractBlackBoxSpec from "../../common/AbstractBlackBox"; +import SharpImageLuminanceSource from "../../SharpImageLuminanceSource"; +import { assertEquals, assertNotNull } from "../../util/AssertUtils"; +import SharpImage from "../../util/SharpImage"; + +// package com.google.zxing.multi.qrcode; + +// import javax.imageio.ImageIO; +// import java.awt.image.BufferedImage; +// import java.nio.file.Path; +// import java.util.Arrays; +// import java.util.Collection; +// import java.util.HashSet; +// import java.util.List; + +// import com.google.zxing.BarcodeFormat; +// import com.google.zxing.BinaryBitmap; +// import com.google.zxing.BufferedImageLuminanceSource; +// import com.google.zxing.LuminanceSource; +// import com.google.zxing.Result; +// import com.google.zxing.ResultMetadataType; +// import com.google.zxing.ResultPoint; +// import com.google.zxing.common.AbstractBlackBoxTestCase; +// import com.google.zxing.common.HybridBinarizer; +// import com.google.zxing.multi.MultipleBarcodeReader; +// import org.junit.Assert; +// import org.junit.Test; + +/** + * Tests {@link QRCodeMultiReader}. + */ +describe('MultiQRCodeTestCase', () => { + + it('testMultiQRCodes', () => { + // Very basic test for now + const testBase: string = AbstractBlackBoxSpec.buildTestBase("src/test/resources/blackbox/multi-qrcode-1"); + + const testImage: string = path.resolve(testBase, "1.png"); + const image: SharpImage = SharpImage.load(testImage, 0); + const source: LuminanceSource = new SharpImageLuminanceSource(image); + const bitmap: BinaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); + + const reader: MultipleBarcodeReader = new QRCodeMultiReader(); + const results :Result[] = reader.decodeMultipleWithoutHints(bitmap); + assertNotNull(results); + assertEquals(4, results.length); + + const barcodeContents: Collection = []; + for (const result of results) { + barcodeContents.push(result.getText()); + assertEquals(BarcodeFormat.QR_CODE, result.getBarcodeFormat()); + assertNotNull(result.getResultMetadata()); + } + const expectedContents: Collection = []; + expectedContents.push("You earned the class a 5 MINUTE DANCE PARTY!! Awesome! Way to go! Let's boogie!"); + expectedContents.push("You earned the class 5 EXTRA MINUTES OF RECESS!! Fabulous!! Way to go!!"); + expectedContents.push("You get to SIT AT MRS. SIGMON'S DESK FOR A DAY!! Awesome!! Way to go!! Guess I better clean up! :)"); + expectedContents.push("You get to CREATE OUR JOURNAL PROMPT FOR THE DAY! Yay! Way to go! "); + assertEquals(expectedContents, barcodeContents); + }); + + it('testProcessStructuredAppend', () => { + const sa1: Result = Result.constructor4Args("SA1", new Uint8Array(0), [], BarcodeFormat.QR_CODE); + const sa2: Result = Result.constructor4Args("SA2", new Uint8Array(0), [], BarcodeFormat.QR_CODE); + const sa3: Result = Result.constructor4Args("SA3", new Uint8Array(0), [], BarcodeFormat.QR_CODE); + sa1.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, 2); + sa1.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, "L"); + sa2.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, (1 << 4) + 2); + sa2.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, "L"); + sa3.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, (2 << 4) + 2); + sa3.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, "L"); + + const nsa: Result = Result.constructor4Args("NotSA", new Uint8Array(0), [], BarcodeFormat.QR_CODE); + nsa.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, "L"); + + const inputs: List = Arrays.asList(sa3, sa1, nsa, sa2); + + const results: List = QRCodeMultiReader.processStructuredAppend(inputs); + assertNotNull(results); + assertEquals(2, results.length); + + const barcodeContents: Collection = []; + for (const result of results) { + barcodeContents.push(result.getText()); + } + const expectedContents: Collection = []; + expectedContents.push("SA1SA2SA3"); + expectedContents.push("NotSA"); + assertEquals(expectedContents, barcodeContents); + }); +}); From 09c4ea328c1b0a4f2691794162ffe4f6a72c37a0 Mon Sep 17 00:00:00 2001 From: Luiz Machado Date: Wed, 2 Dec 2020 11:59:27 -0300 Subject: [PATCH 02/19] WIP: commit meant to save the code --- .vscode/launch.json | 2 + src/core/Reader.ts | 2 +- src/core/multi/qrcode/QRCodeMultiReader.ts | 38 ++++++------ src/core/pdf417/PDF417Reader.ts | 12 +++- src/index.ts | 10 +++ src/test/core/common/AbstractBlackBox.ts | 2 + .../core/multi/qrcode/MultiQRCode.spec.ts | 61 +++++++++++-------- 7 files changed, 78 insertions(+), 49 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index b7e99c36..cc8f2f39 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -161,6 +161,8 @@ "args": [ "--require", "ts-node/register", + "--require", + "tsconfig-paths/register", "-u", "tdd", "--timeout", diff --git a/src/core/Reader.ts b/src/core/Reader.ts index 66cabf25..30ed4d4b 100644 --- a/src/core/Reader.ts +++ b/src/core/Reader.ts @@ -48,7 +48,7 @@ interface Reader { * @throws FormatException if a potential barcode is found but format is invalid * @override decode */ - decodeWithoutHints(image: BinaryBitmap): Result; + // decodeWithoutHints(image: BinaryBitmap): Result; /** * Locates and decodes a barcode in some format within an image. This method also accepts diff --git a/src/core/multi/qrcode/QRCodeMultiReader.ts b/src/core/multi/qrcode/QRCodeMultiReader.ts index 9efba61d..7d7347c7 100644 --- a/src/core/multi/qrcode/QRCodeMultiReader.ts +++ b/src/core/multi/qrcode/QRCodeMultiReader.ts @@ -14,25 +14,25 @@ * limitations under the License. */ -import BarcodeFormat from "src/core/BarcodeFormat"; -import BinaryBitmap from "src/core/BinaryBitmap"; -import DecoderResult from "src/core/common/DecoderResult"; -import DetectorResult from "src/core/common/DetectorResult"; -import DecodeHintType from "src/core/DecodeHintType"; -import QRCodeDecoderMetaData from "src/core/qrcode/decoder/QRCodeDecoderMetaData"; -import QRCodeReader from "src/core/qrcode/QRCodeReader"; -import ReaderException from "src/core/ReaderException"; -import Result from "src/core/Result"; -import ResultMetadataType from "src/core/ResultMetadataType"; -import ResultPoint from "src/core/ResultPoint"; -import ByteArrayOutputStream from "src/core/util/ByteArrayOutputStream"; -import Collections from "src/core/util/Collections"; -import Comparator from "src/core/util/Comparator"; -import Integer from "src/core/util/Integer"; -import StringBuilder from "src/core/util/StringBuilder"; -import { int, List } from "src/customTypings"; -import MultipleBarcodeReader from "../MultipleBarcodeReader"; -import MultiDetector from "./detector/MultiDetector"; +import { int, List } from 'src/customTypings'; +import BarcodeFormat from '../../BarcodeFormat'; +import BinaryBitmap from '../../BinaryBitmap'; +import DecoderResult from '../../common/DecoderResult'; +import DetectorResult from '../../common/DetectorResult'; +import DecodeHintType from '../../DecodeHintType'; +import QRCodeDecoderMetaData from '../../qrcode/decoder/QRCodeDecoderMetaData'; +import QRCodeReader from '../../qrcode/QRCodeReader'; +import ReaderException from '../../ReaderException'; +import Result from '../../Result'; +import ResultMetadataType from '../../ResultMetadataType'; +import ResultPoint from '../../ResultPoint'; +import ByteArrayOutputStream from '../../util/ByteArrayOutputStream'; +import Collections from '../../util/Collections'; +import Comparator from '../../util/Comparator'; +import Integer from '../../util/Integer'; +import StringBuilder from '../../util/StringBuilder'; +import MultipleBarcodeReader from '../MultipleBarcodeReader'; +import MultiDetector from './detector/MultiDetector'; // package com.google.zxing.multi.qrcode; diff --git a/src/core/pdf417/PDF417Reader.ts b/src/core/pdf417/PDF417Reader.ts index 55c78450..f63067c7 100644 --- a/src/core/pdf417/PDF417Reader.ts +++ b/src/core/pdf417/PDF417Reader.ts @@ -72,8 +72,8 @@ export default /*public final*/ class PDF417Reader implements Reader, MultipleBa * @throws NotFoundException if a PDF417 code cannot be found, * @throws FormatException if a PDF417 cannot be decoded * @throws ChecksumException + * @override decode */ - // @Override public decode(image: BinaryBitmap, hints: Map = null): Result { let result = PDF417Reader.decode(image, hints, false); if (result == null || result.length === 0 || result[0] == null) { @@ -82,13 +82,21 @@ export default /*public final*/ class PDF417Reader implements Reader, MultipleBa return result[0]; } + /** + * + * @override decodeMultiple + */ + public decodeMultipleWithoutHints(image: BinaryBitmap): Result[] { + return this.decodeMultiple(image, null); + } + /** * * @param BinaryBitmap * @param image * @throws NotFoundException + * @override */ - // @Override public decodeMultiple(image: BinaryBitmap, hints: Map = null): Result[] { try { return PDF417Reader.decode(image, hints, true); diff --git a/src/index.ts b/src/index.ts index 372596c5..11f7f4b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -116,3 +116,13 @@ export { default as Code39Reader } from './core/oned/Code39Reader'; export { default as RSS14Reader } from './core/oned/rss/RSS14Reader'; export { default as RSSExpandedReader } from './core/oned/rss/expanded/RSSExpandedReader'; export { default as MultiFormatOneDReader } from './core/oned/MultiFormatOneDReader'; + + +// core/multi +export { default as MultipleBarcodeReader } from './core/multi/MultipleBarcodeReader'; + + +// core/multi/qrcode +export { default as QRCodeMultiReader } from './core/multi/qrcode/QRCodeMultiReader'; +export { default as MultiDetector } from './core/multi/qrcode/detector/MultiDetector'; +export { default as MultiFinderPatternFinder } from './core/multi/qrcode/detector/MultiFinderPatternFinder'; diff --git a/src/test/core/common/AbstractBlackBox.ts b/src/test/core/common/AbstractBlackBox.ts index f1bb89f3..de944a25 100644 --- a/src/test/core/common/AbstractBlackBox.ts +++ b/src/test/core/common/AbstractBlackBox.ts @@ -16,6 +16,8 @@ /*package com.google.zxing.common;*/ +import * as fs from 'fs'; +import * as path from 'path'; import { assertEquals } from '../util/AssertUtils'; import SharpImage from '../util/SharpImage'; import SharpImageLuminanceSource from '../SharpImageLuminanceSource'; diff --git a/src/test/core/multi/qrcode/MultiQRCode.spec.ts b/src/test/core/multi/qrcode/MultiQRCode.spec.ts index 58a6848d..ce52cc72 100644 --- a/src/test/core/multi/qrcode/MultiQRCode.spec.ts +++ b/src/test/core/multi/qrcode/MultiQRCode.spec.ts @@ -14,16 +14,23 @@ * limitations under the License. */ -import { BinaryBitmap, HybridBinarizer, LuminanceSource, Result, ResultMetadataType } from "src"; -import BarcodeFormat from "src/core/BarcodeFormat"; -import MultipleBarcodeReader from "src/core/multi/MultipleBarcodeReader"; -import QRCodeMultiReader from "src/core/multi/qrcode/QRCodeMultiReader"; -import Arrays from "src/core/util/Arrays"; -import { Collection, List } from "src/customTypings"; -import AbstractBlackBoxSpec from "../../common/AbstractBlackBox"; -import SharpImageLuminanceSource from "../../SharpImageLuminanceSource"; -import { assertEquals, assertNotNull } from "../../util/AssertUtils"; -import SharpImage from "../../util/SharpImage"; +import { + BarcodeFormat, + BinaryBitmap, + HybridBinarizer, + LuminanceSource, + MultipleBarcodeReader, + QRCodeMultiReader, + Result, + ResultMetadataType +} from '@zxing/library'; +import * as path from 'path'; +import Arrays from 'src/core/util/Arrays'; +import { Collection, List } from 'src/customTypings'; +import AbstractBlackBoxSpec from '../../common/AbstractBlackBox'; +import SharpImageLuminanceSource from '../../SharpImageLuminanceSource'; +import { assertEquals, assertNotNull } from '../../util/AssertUtils'; +import SharpImage from '../../util/SharpImage'; // package com.google.zxing.multi.qrcode; @@ -55,15 +62,15 @@ describe('MultiQRCodeTestCase', () => { it('testMultiQRCodes', () => { // Very basic test for now - const testBase: string = AbstractBlackBoxSpec.buildTestBase("src/test/resources/blackbox/multi-qrcode-1"); + const testBase: string = AbstractBlackBoxSpec.buildTestBase('src/test/resources/blackbox/multi-qrcode-1'); - const testImage: string = path.resolve(testBase, "1.png"); + const testImage: string = path.resolve(testBase, '1.png'); const image: SharpImage = SharpImage.load(testImage, 0); const source: LuminanceSource = new SharpImageLuminanceSource(image); const bitmap: BinaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); const reader: MultipleBarcodeReader = new QRCodeMultiReader(); - const results :Result[] = reader.decodeMultipleWithoutHints(bitmap); + const results: Result[] = reader.decodeMultipleWithoutHints(bitmap); assertNotNull(results); assertEquals(4, results.length); @@ -74,26 +81,26 @@ describe('MultiQRCodeTestCase', () => { assertNotNull(result.getResultMetadata()); } const expectedContents: Collection = []; - expectedContents.push("You earned the class a 5 MINUTE DANCE PARTY!! Awesome! Way to go! Let's boogie!"); - expectedContents.push("You earned the class 5 EXTRA MINUTES OF RECESS!! Fabulous!! Way to go!!"); - expectedContents.push("You get to SIT AT MRS. SIGMON'S DESK FOR A DAY!! Awesome!! Way to go!! Guess I better clean up! :)"); - expectedContents.push("You get to CREATE OUR JOURNAL PROMPT FOR THE DAY! Yay! Way to go! "); + expectedContents.push('You earned the class a 5 MINUTE DANCE PARTY!! Awesome! Way to go! Let\'s boogie!'); + expectedContents.push('You earned the class 5 EXTRA MINUTES OF RECESS!! Fabulous!! Way to go!!'); + expectedContents.push('You get to SIT AT MRS. SIGMON\'S DESK FOR A DAY!! Awesome!! Way to go!! Guess I better clean up! :)'); + expectedContents.push('You get to CREATE OUR JOURNAL PROMPT FOR THE DAY! Yay! Way to go! '); assertEquals(expectedContents, barcodeContents); }); it('testProcessStructuredAppend', () => { - const sa1: Result = Result.constructor4Args("SA1", new Uint8Array(0), [], BarcodeFormat.QR_CODE); - const sa2: Result = Result.constructor4Args("SA2", new Uint8Array(0), [], BarcodeFormat.QR_CODE); - const sa3: Result = Result.constructor4Args("SA3", new Uint8Array(0), [], BarcodeFormat.QR_CODE); + const sa1: Result = Result.constructor4Args('SA1', new Uint8Array(0), [], BarcodeFormat.QR_CODE); + const sa2: Result = Result.constructor4Args('SA2', new Uint8Array(0), [], BarcodeFormat.QR_CODE); + const sa3: Result = Result.constructor4Args('SA3', new Uint8Array(0), [], BarcodeFormat.QR_CODE); sa1.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, 2); - sa1.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, "L"); + sa1.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, 'L'); sa2.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, (1 << 4) + 2); - sa2.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, "L"); + sa2.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, 'L'); sa3.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, (2 << 4) + 2); - sa3.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, "L"); + sa3.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, 'L'); - const nsa: Result = Result.constructor4Args("NotSA", new Uint8Array(0), [], BarcodeFormat.QR_CODE); - nsa.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, "L"); + const nsa: Result = Result.constructor4Args('NotSA', new Uint8Array(0), [], BarcodeFormat.QR_CODE); + nsa.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, 'L'); const inputs: List = Arrays.asList(sa3, sa1, nsa, sa2); @@ -106,8 +113,8 @@ describe('MultiQRCodeTestCase', () => { barcodeContents.push(result.getText()); } const expectedContents: Collection = []; - expectedContents.push("SA1SA2SA3"); - expectedContents.push("NotSA"); + expectedContents.push('SA1SA2SA3'); + expectedContents.push('NotSA'); assertEquals(expectedContents, barcodeContents); }); }); From 9d0fae6bc39ad29915b0e5ceced1a8b67ff4128a Mon Sep 17 00:00:00 2001 From: Luiz Machado Date: Wed, 2 Dec 2020 13:03:55 -0300 Subject: [PATCH 03/19] dependecies: updated sharp --- package.json | 2 +- yarn.lock | 260 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 167 insertions(+), 95 deletions(-) diff --git a/package.json b/package.json index 6a4ad5a5..5cff1ffa 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "nyc": "^15.1.0", "rollup": "^2.8.2", "seedrandom": "^2.4.4", - "sharp": "^0.22.1", + "sharp": "^0.26.3", "shx": "0.3.2", "sinon": "^7.2.7", "terser": "^5.3.7", diff --git a/yarn.lock b/yarn.lock index 1f4fe895..dc576f1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -509,6 +509,11 @@ array-find-index@^1.0.1: resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= +array-flatten@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-3.0.0.tgz#6428ca2ee52c7b823192ec600fa3ed2f157cd541" + integrity sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA== + array-from@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195" @@ -640,6 +645,11 @@ base64-js@^1.0.2: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base64id@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" @@ -677,13 +687,14 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== -bl@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" - integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA== +bl@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" + integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== dependencies: - readable-stream "^2.3.5" - safe-buffer "^5.1.1" + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" blob@0.0.5: version "0.0.5" @@ -859,6 +870,14 @@ buffer@^5.0.6: base64-js "^1.0.2" ieee754 "^1.1.4" +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -987,7 +1006,7 @@ chokidar@^2.0.3: optionalDependencies: fsevents "^1.2.7" -chownr@^1.0.1, chownr@^1.1.1: +chownr@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== @@ -1098,21 +1117,21 @@ color-name@^1.0.0, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" - integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== +color-string@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" + integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" -color@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" - integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== +color@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" + integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== dependencies: color-convert "^1.9.1" - color-string "^1.5.2" + color-string "^1.5.4" colors@^1.1.0: version "1.3.3" @@ -1426,6 +1445,13 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-eql@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" @@ -1594,13 +1620,20 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.0.0, end-of-stream@^1.1.0: +end-of-stream@^1.1.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== dependencies: once "^1.4.0" +end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + engine.io-client@~3.2.0: version "3.2.1" resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.2.1.tgz#6f54c0475de487158a1a7c77d10178708b6add36" @@ -2060,11 +2093,6 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-copy-file-sync@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/fs-copy-file-sync/-/fs-copy-file-sync-1.1.1.tgz#11bf32c096c10d126e5f6b36d06eece776062918" - integrity sha512-2QY5eeqVv4m2PfyMiEuy9adxNP+ajf+8AR05cEi+OAzPcOj90hvFImeZhTmKLBgSd9EvG33jsD7ZRxsx9dThkQ== - fs-minipass@^1.2.5: version "1.2.6" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" @@ -2412,6 +2440,11 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" @@ -2467,7 +2500,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3182,6 +3215,13 @@ lru-cache@4.1.x: pseudomap "^1.0.2" yallist "^2.1.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + make-dir@^3.0.0, make-dir@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -3295,6 +3335,11 @@ mimic-response@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -3322,7 +3367,7 @@ minimist@1.2.0, minimist@^1.1.3, minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minimist@^1.2.5: +minimist@^1.2.3, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -3355,6 +3400,11 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -3406,7 +3456,7 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@^2.12.1, nan@^2.13.2: +nan@^2.12.1: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -3480,6 +3530,11 @@ node-abi@^2.7.0: dependencies: semver "^5.4.1" +node-addon-api@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.2.tgz#04bc7b83fd845ba785bb6eae25bc857e1ef75681" + integrity sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg== + node-pre-gyp@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" @@ -3695,7 +3750,7 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-homedir@^1.0.0, os-homedir@^1.0.1: +os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= @@ -3940,25 +3995,24 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -prebuild-install@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.0.tgz#58b4d8344e03590990931ee088dd5401b03004c8" - integrity sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg== +prebuild-install@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.0.0.tgz#669022bcde57c710a869e39c5ca6bf9cd207f316" + integrity sha512-h2ZJ1PXHKWZpp1caLw0oX9sagVpL2YTk+ZwInQbQ3QqNd4J03O6MpFNmMTJlkfgPENWqe5kP0WjQLqz5OjLfsw== dependencies: detect-libc "^1.0.3" expand-template "^2.0.3" github-from-package "0.0.0" - minimist "^1.2.0" - mkdirp "^0.5.1" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" napi-build-utils "^1.0.1" node-abi "^2.7.0" noop-logger "^0.1.1" npmlog "^4.0.1" - os-homedir "^1.0.1" - pump "^2.0.1" + pump "^3.0.0" rc "^1.2.7" - simple-get "^2.7.0" - tar-fs "^1.13.0" + simple-get "^3.0.3" + tar-fs "^2.0.0" tunnel-agent "^0.6.0" which-pm-runs "^1.0.0" @@ -4016,18 +4070,10 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" -pump@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" - integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pump@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== dependencies: end-of-stream "^1.1.0" once "^1.3.1" @@ -4139,7 +4185,7 @@ readable-stream@^1.1.7: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6: +readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -4152,6 +4198,15 @@ readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.0, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@~2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" @@ -4410,6 +4465,11 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -4447,6 +4507,13 @@ semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.3.2: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -4480,20 +4547,20 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -sharp@^0.22.1: - version "0.22.1" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.22.1.tgz#a67c0e75567f03dd5a7861b901fec04072c5b0f4" - integrity sha512-lXzSk/FL5b/MpWrT1pQZneKe25stVjEbl6uhhJcTULm7PhmJgKKRbTDM/vtjyUuC/RLqL2PRyC4rpKwbv3soEw== +sharp@^0.26.3: + version "0.26.3" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.26.3.tgz#9de8577a986b22538e6e12ced1f7e8a53f9728de" + integrity sha512-NdEJ9S6AMr8Px0zgtFo1TJjMK/ROMU92MkDtYn2BBrDjIx3YfH9TUyGdzPC+I/L619GeYQc690Vbaxc5FPCCWg== dependencies: - color "^3.1.1" + array-flatten "^3.0.0" + color "^3.1.3" detect-libc "^1.0.3" - fs-copy-file-sync "^1.1.1" - nan "^2.13.2" + node-addon-api "^3.0.2" npmlog "^4.1.2" - prebuild-install "^5.3.0" - semver "^6.0.0" - simple-get "^3.0.3" - tar "^4.4.8" + prebuild-install "^6.0.0" + semver "^7.3.2" + simple-get "^4.0.0" + tar-fs "^2.1.1" tunnel-agent "^0.6.0" shebang-command@^1.2.0: @@ -4548,15 +4615,6 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= -simple-get@^2.7.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.1.tgz#0e22e91d4575d87620620bc91308d57a77f44b5d" - integrity sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw== - dependencies: - decompress-response "^3.3.0" - once "^1.3.1" - simple-concat "^1.0.0" - simple-get@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.0.3.tgz#924528ac3f9d7718ce5e9ec1b1a69c0be4d62efa" @@ -4566,6 +4624,15 @@ simple-get@^3.0.3: once "^1.3.1" simple-concat "^1.0.0" +simple-get@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675" + integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -4881,6 +4948,13 @@ string_decoder@^1.0.3: dependencies: safe-buffer "~5.1.0" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -4988,30 +5062,28 @@ table@^5.2.3: slice-ansi "^2.1.0" string-width "^3.0.0" -tar-fs@^1.13.0: - version "1.16.3" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" - integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw== +tar-fs@^2.0.0, tar-fs@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== dependencies: - chownr "^1.0.1" - mkdirp "^0.5.1" - pump "^1.0.0" - tar-stream "^1.1.2" + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" -tar-stream@^1.1.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" - integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== +tar-stream@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.4.tgz#c4fb1a11eb0da29b893a5b25476397ba2d053bfa" + integrity sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw== dependencies: - bl "^1.0.0" - buffer-alloc "^1.2.0" - end-of-stream "^1.0.0" + bl "^4.0.3" + end-of-stream "^1.4.1" fs-constants "^1.0.0" - readable-stream "^2.3.0" - to-buffer "^1.1.1" - xtend "^4.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" -tar@^4, tar@^4.4.8: +tar@^4: version "4.4.10" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== @@ -5091,11 +5163,6 @@ to-arraybuffer@^1.0.0: resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= -to-buffer@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" - integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -5359,7 +5426,7 @@ useragent@2.3.0: lru-cache "4.1.x" tmp "0.0.x" -util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -5548,6 +5615,11 @@ yallist@^3.0.0, yallist@^3.0.3: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" From 9a7527c0bf87114f2fe580cc560519b30270a3a0 Mon Sep 17 00:00:00 2001 From: Luiz Machado Date: Wed, 2 Dec 2020 14:21:57 -0300 Subject: [PATCH 04/19] tests(multi qrcode): fix array equals assertion --- src/core/multi/qrcode/QRCodeMultiReader.ts | 2 +- src/test/core/multi/qrcode/MultiQRCode.spec.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/multi/qrcode/QRCodeMultiReader.ts b/src/core/multi/qrcode/QRCodeMultiReader.ts index 7d7347c7..2081c9a3 100644 --- a/src/core/multi/qrcode/QRCodeMultiReader.ts +++ b/src/core/multi/qrcode/QRCodeMultiReader.ts @@ -163,7 +163,7 @@ export default /*public final*/ class QRCodeMultiReader extends QRCodeReader imp if (newByteSegment.size() > 0) { newResult.putMetadata(ResultMetadataType.BYTE_SEGMENTS, Collections.singletonList(newByteSegment.toByteArray())); } - newResults.push(newResult); + newResults.unshift(newResult); // TYPESCRIPTPORT: inserted element at the start of the array because it seems the Java version does that as well. return newResults; } diff --git a/src/test/core/multi/qrcode/MultiQRCode.spec.ts b/src/test/core/multi/qrcode/MultiQRCode.spec.ts index ce52cc72..dd1882cd 100644 --- a/src/test/core/multi/qrcode/MultiQRCode.spec.ts +++ b/src/test/core/multi/qrcode/MultiQRCode.spec.ts @@ -29,7 +29,7 @@ import Arrays from 'src/core/util/Arrays'; import { Collection, List } from 'src/customTypings'; import AbstractBlackBoxSpec from '../../common/AbstractBlackBox'; import SharpImageLuminanceSource from '../../SharpImageLuminanceSource'; -import { assertEquals, assertNotNull } from '../../util/AssertUtils'; +import { assertArrayEquals, assertEquals, assertNotNull } from '../../util/AssertUtils'; import SharpImage from '../../util/SharpImage'; // package com.google.zxing.multi.qrcode; @@ -85,7 +85,7 @@ describe('MultiQRCodeTestCase', () => { expectedContents.push('You earned the class 5 EXTRA MINUTES OF RECESS!! Fabulous!! Way to go!!'); expectedContents.push('You get to SIT AT MRS. SIGMON\'S DESK FOR A DAY!! Awesome!! Way to go!! Guess I better clean up! :)'); expectedContents.push('You get to CREATE OUR JOURNAL PROMPT FOR THE DAY! Yay! Way to go! '); - assertEquals(expectedContents, barcodeContents); + assertArrayEquals(expectedContents, barcodeContents); }); it('testProcessStructuredAppend', () => { @@ -115,6 +115,6 @@ describe('MultiQRCodeTestCase', () => { const expectedContents: Collection = []; expectedContents.push('SA1SA2SA3'); expectedContents.push('NotSA'); - assertEquals(expectedContents, barcodeContents); + assertArrayEquals(expectedContents, barcodeContents); }); }); From abeafd74b5113ab1f65bbabc4e6f0522d7e225e2 Mon Sep 17 00:00:00 2001 From: Luiz Machado Date: Wed, 2 Dec 2020 20:57:32 -0300 Subject: [PATCH 05/19] image loading fix --- src/test/core/multi/qrcode/MultiQRCode.spec.ts | 4 ++-- src/test/core/util/SharpImage.ts | 13 +++++++++++++ .../resources/blackbox/multi-qrcode-1/1.png | Bin 0 -> 68266 bytes 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/blackbox/multi-qrcode-1/1.png diff --git a/src/test/core/multi/qrcode/MultiQRCode.spec.ts b/src/test/core/multi/qrcode/MultiQRCode.spec.ts index dd1882cd..bfa79301 100644 --- a/src/test/core/multi/qrcode/MultiQRCode.spec.ts +++ b/src/test/core/multi/qrcode/MultiQRCode.spec.ts @@ -60,12 +60,12 @@ import SharpImage from '../../util/SharpImage'; */ describe('MultiQRCodeTestCase', () => { - it('testMultiQRCodes', () => { + it('testMultiQRCodes', async () => { // Very basic test for now const testBase: string = AbstractBlackBoxSpec.buildTestBase('src/test/resources/blackbox/multi-qrcode-1'); const testImage: string = path.resolve(testBase, '1.png'); - const image: SharpImage = SharpImage.load(testImage, 0); + const image: SharpImage = await SharpImage.loadAsync(testImage); const source: LuminanceSource = new SharpImageLuminanceSource(image); const bitmap: BinaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); diff --git a/src/test/core/util/SharpImage.ts b/src/test/core/util/SharpImage.ts index 91b29e7d..9c9041a3 100644 --- a/src/test/core/util/SharpImage.ts +++ b/src/test/core/util/SharpImage.ts @@ -50,6 +50,19 @@ export default class SharpImage { return new SharpImage(wrapper, undefined, undefined, undefined); } + public static async loadAsync(path: string): Promise { + + const wrapper = sharp(path).raw(); + + const { data, info } = await wrapper.toBuffer({ resolveWithObject: true }); + + const width = info.width; + const height = info.height; + const buffer = new Uint8ClampedArray(data.buffer); + + return new SharpImage(wrapper, buffer, width, height); + } + public static async loadAsBitMatrix(path: string): Promise { const wrapper = sharp(path).raw(); diff --git a/src/test/resources/blackbox/multi-qrcode-1/1.png b/src/test/resources/blackbox/multi-qrcode-1/1.png new file mode 100644 index 0000000000000000000000000000000000000000..c82508b24f0acff394e234f13823d0796f39a547 GIT binary patch literal 68266 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 QQeHz7qlYfMaP`6e1JZHpDgXcg literal 0 HcmV?d00001 From 7e7c13fa5fa573e926d5833b69bd31eb85f82f27 Mon Sep 17 00:00:00 2001 From: Luiz Filipe Machado Barni Date: Fri, 4 Dec 2020 01:07:41 -0300 Subject: [PATCH 06/19] tests corrections --- src/core/multi/qrcode/QRCodeMultiReader.ts | 2 +- src/test/core/multi/qrcode/MultiQRCode.spec.ts | 10 ++++++---- src/test/core/util/SharpImage.ts | 13 +++++++++++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/core/multi/qrcode/QRCodeMultiReader.ts b/src/core/multi/qrcode/QRCodeMultiReader.ts index 2081c9a3..6f783e24 100644 --- a/src/core/multi/qrcode/QRCodeMultiReader.ts +++ b/src/core/multi/qrcode/QRCodeMultiReader.ts @@ -163,7 +163,7 @@ export default /*public final*/ class QRCodeMultiReader extends QRCodeReader imp if (newByteSegment.size() > 0) { newResult.putMetadata(ResultMetadataType.BYTE_SEGMENTS, Collections.singletonList(newByteSegment.toByteArray())); } - newResults.unshift(newResult); // TYPESCRIPTPORT: inserted element at the start of the array because it seems the Java version does that as well. + newResults.push(newResult); // TYPESCRIPTPORT: inserted element at the start of the array because it seems the Java version does that as well. return newResults; } diff --git a/src/test/core/multi/qrcode/MultiQRCode.spec.ts b/src/test/core/multi/qrcode/MultiQRCode.spec.ts index bfa79301..2608aec9 100644 --- a/src/test/core/multi/qrcode/MultiQRCode.spec.ts +++ b/src/test/core/multi/qrcode/MultiQRCode.spec.ts @@ -65,7 +65,7 @@ describe('MultiQRCodeTestCase', () => { const testBase: string = AbstractBlackBoxSpec.buildTestBase('src/test/resources/blackbox/multi-qrcode-1'); const testImage: string = path.resolve(testBase, '1.png'); - const image: SharpImage = await SharpImage.loadAsync(testImage); + const image: SharpImage = await SharpImage.loadWithRotation(testImage, 0); const source: LuminanceSource = new SharpImageLuminanceSource(image); const bitmap: BinaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); @@ -81,10 +81,11 @@ describe('MultiQRCodeTestCase', () => { assertNotNull(result.getResultMetadata()); } const expectedContents: Collection = []; - expectedContents.push('You earned the class a 5 MINUTE DANCE PARTY!! Awesome! Way to go! Let\'s boogie!'); + // TYPESCRIPTPORT: following lines are in different order from Java because JavaScript's push works in a different way HashSet<>.add, but the results are actually the same + expectedContents.push('You get to CREATE OUR JOURNAL PROMPT FOR THE DAY! Yay! Way to go! '); expectedContents.push('You earned the class 5 EXTRA MINUTES OF RECESS!! Fabulous!! Way to go!!'); + expectedContents.push('You earned the class a 5 MINUTE DANCE PARTY!! Awesome! Way to go! Let\'s boogie!'); expectedContents.push('You get to SIT AT MRS. SIGMON\'S DESK FOR A DAY!! Awesome!! Way to go!! Guess I better clean up! :)'); - expectedContents.push('You get to CREATE OUR JOURNAL PROMPT FOR THE DAY! Yay! Way to go! '); assertArrayEquals(expectedContents, barcodeContents); }); @@ -113,8 +114,9 @@ describe('MultiQRCodeTestCase', () => { barcodeContents.push(result.getText()); } const expectedContents: Collection = []; - expectedContents.push('SA1SA2SA3'); + // TYPESCRIPTPORT: following lines are in different order from Java because JavaScript's push works in a different way HashSet<>.add, but the results are actually the same expectedContents.push('NotSA'); + expectedContents.push('SA1SA2SA3'); assertArrayEquals(expectedContents, barcodeContents); }); }); diff --git a/src/test/core/util/SharpImage.ts b/src/test/core/util/SharpImage.ts index 9c9041a3..38d2665d 100644 --- a/src/test/core/util/SharpImage.ts +++ b/src/test/core/util/SharpImage.ts @@ -58,7 +58,7 @@ export default class SharpImage { const width = info.width; const height = info.height; - const buffer = new Uint8ClampedArray(data.buffer); + const buffer = SharpImage.toGrayscaleBuffer(new Uint8ClampedArray(data.buffer), info.width, info.height, info.channels); return new SharpImage(wrapper, buffer, width, height); } @@ -79,11 +79,20 @@ export default class SharpImage { const height = info.height; const grayscaleBuffer = SharpImage.toGrayscaleBuffer(new Uint8ClampedArray(data.buffer), width, height, channels); // const image = new SharpImage(wrapper, grayscaleBuffer, info.width, info.height) + + return SharpImage.bufferToBitMatrix(grayscaleBuffer, width, height); + } + + private static bufferToBitMatrix( + imageBuffer: Uint8ClampedArray, + width: number, + height: number + ): BitMatrix { const matrix = new BitMatrix(width, height); for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { - const pixel = grayscaleBuffer[y * width + x]; + const pixel = imageBuffer[y * width + x]; if (pixel <= 0x7F) { matrix.set(x, y); } From abcff1ae388d89abf31227733920c53c10056559 Mon Sep 17 00:00:00 2001 From: Luiz Filipe Machado Barni Date: Fri, 4 Dec 2020 01:08:38 -0300 Subject: [PATCH 07/19] new finder pattern finder --- .../detector/MultiFinderPatternFinder.ts | 37 +- .../qrcode/detector/FinderPatternFinder.ts | 1323 ++++++++--------- src/core/util/Arrays.ts | 4 + src/core/util/Double.ts | 5 + src/core/util/Float.ts | 10 + 5 files changed, 637 insertions(+), 742 deletions(-) create mode 100644 src/core/util/Double.ts diff --git a/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts b/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts index e4131ee7..697d70a4 100644 --- a/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts +++ b/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts @@ -14,14 +14,17 @@ * limitations under the License. */ -import { BitMatrix, NotFoundException, ResultPoint, DecodeHintType } from "src"; -import FinderPattern from "src/core/qrcode/detector/FinderPattern"; -import FinderPatternFinder from "src/core/qrcode/detector/FinderPatternFinder"; -import FinderPatternInfo from "src/core/qrcode/detector/FinderPatternInfo"; -import ResultPointCallback from "src/core/ResultPointCallback"; -import Collections from "src/core/util/Collections"; -import Comparator from "src/core/util/Comparator"; -import { double, float, int, List } from "src/customTypings"; +import BitMatrix from 'src/core/common/BitMatrix'; +import DecodeHintType from 'src/core/DecodeHintType'; +import NotFoundException from 'src/core/NotFoundException'; +import FinderPattern from 'src/core/qrcode/detector/FinderPattern'; +import FinderPatternFinder from 'src/core/qrcode/detector/FinderPatternFinder'; +import FinderPatternInfo from 'src/core/qrcode/detector/FinderPatternInfo'; +import ResultPoint from 'src/core/ResultPoint'; +import ResultPointCallback from 'src/core/ResultPointCallback'; +import Collections from 'src/core/util/Collections'; +import Comparator from 'src/core/util/Comparator'; +import { double, float, int, List } from 'src/customTypings'; // package com.google.zxing.multi.qrcode.detector; @@ -228,7 +231,7 @@ export default /* public final */ class MultiFinderPatternFinder extends FinderP // 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. - let iSkip: int = (3 * maxI) / (4 * MultiFinderPatternFinder.MAX_MODULES); + let iSkip: int = Math.trunc((3 * maxI) / (4 * MultiFinderPatternFinder.MAX_MODULES)); // TYPESCRIPTPORT: Java integer divisions always discard decimal chars. if (iSkip < MultiFinderPatternFinder.MIN_SKIP || tryHarder) { iSkip = MultiFinderPatternFinder.MIN_SKIP; } @@ -236,24 +239,24 @@ export default /* public final */ class MultiFinderPatternFinder extends FinderP const stateCount: Int32Array = Int32Array.from({ length: 5 }); for (let i: int = iSkip - 1; i < maxI; i += iSkip) { // Get a row of black/white values - MultiFinderPatternFinder.doClearCounts(stateCount); + this.clearCounts(stateCount); let currentState: int = 0; for (let j: int = 0; j < maxJ; j++) { if (image.get(j, i)) { // Black pixel - if ((currentState & 1) == 1) { // Counting white pixels + 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 (MultiFinderPatternFinder.foundPatternCross(stateCount) && this.handlePossibleCenter2(stateCount, i, j)) { // Yes + if ((currentState & 1) === 0) { // Counting black pixels + if (currentState === 4) { // A winner? + if (MultiFinderPatternFinder.foundPatternCross(stateCount) && this.handlePossibleCenter(stateCount, i, j)) { // Yes // Clear state to start looking again currentState = 0; - MultiFinderPatternFinder.doClearCounts(stateCount); + this.clearCounts(stateCount); } else { // No, shift counts back by two - MultiFinderPatternFinder.doShiftCounts2(stateCount); + this.shiftCounts2(stateCount); currentState = 3; } } else { @@ -266,7 +269,7 @@ export default /* public final */ class MultiFinderPatternFinder extends FinderP } // for j=... if (MultiFinderPatternFinder.foundPatternCross(stateCount)) { - this.handlePossibleCenter2(stateCount, i, maxJ); + this.handlePossibleCenter(stateCount, i, maxJ); } } // for i=iSkip-1 ... const patternInfo: FinderPattern[][] = this.selectMultipleBestPatterns(); diff --git a/src/core/qrcode/detector/FinderPatternFinder.ts b/src/core/qrcode/detector/FinderPatternFinder.ts index 88228f02..54c2a2c2 100644 --- a/src/core/qrcode/detector/FinderPatternFinder.ts +++ b/src/core/qrcode/detector/FinderPatternFinder.ts @@ -14,27 +14,46 @@ * limitations under the License. */ -/*namespace com.google.zxing.qrcode.detector {*/ - -import DecodeHintType from '../../DecodeHintType'; -import ResultPoint from '../../ResultPoint'; -import ResultPointCallback from '../../ResultPointCallback'; -import BitMatrix from '../../common/BitMatrix'; +import BitMatrix from 'src/core/common/BitMatrix'; +import DecodeHintType from 'src/core/DecodeHintType'; +import NotFoundException from 'src/core/NotFoundException'; +import ResultPoint from 'src/core/ResultPoint'; +import ResultPointCallback from 'src/core/ResultPointCallback'; +import Arrays from 'src/core/util/Arrays'; +import Comparator from 'src/core/util/Comparator'; +import Double from 'src/core/util/Double'; +import Float from 'src/core/util/Float'; +import { double, float, int, List } from 'src/customTypings'; import FinderPattern from './FinderPattern'; import FinderPatternInfo from './FinderPatternInfo'; -import NotFoundException from '../../NotFoundException'; +// package com.google.zxing.qrcode.detector; -import { float, int } from '../../../customTypings'; -import Float from 'src/core/util/Float'; -import Arrays from 'src/core/util/Arrays'; +// import com.google.zxing.DecodeHintType; +// import com.google.zxing.NotFoundException; +// import com.google.zxing.ResultPoint; +// import com.google.zxing.ResultPointCallback; +// import com.google.zxing.common.BitMatrix; + +// import java.io.Serializable; +// import java.util.ArrayList; +// import java.util.Arrays; +// import java.util.Comparator; +// import java.util.List; +// import java.util.Map; + + +// TYPESCRIPTPORT: this class woudl normaly exist at the end of this file, but it's here due to ESLint. +/** + *

Orders by {@link FinderPattern#getEstimatedModuleSize()}

+ */ +/* private static final */ class EstimatedModuleComparator implements Comparator/*, Serializable*/ { + /** @override */ + public compare(center1: FinderPattern, center2: FinderPattern): int { + return Float.compare(center1.getEstimatedModuleSize(), center2.getEstimatedModuleSize()); + } +} -/*import java.io.Serializable;*/ -/*import java.util.ArrayList;*/ -/*import java.util.Collections;*/ -/*import java.util.Comparator;*/ -/*import java.util.List;*/ -/*import java.util.Map;*/ /** *

This class attempts to find finder patterns in a QR Code. Finder patterns are the square @@ -46,527 +65,449 @@ import Arrays from 'src/core/util/Arrays'; */ export default class FinderPatternFinder { - private static CENTER_QUORUM = 2; - protected static MIN_SKIP = 3; // 1 pixel/module times 3 modules/center - protected static MAX_MODULES = 57; // support up to version 10 for mobile clients + private static /* final*/ CENTER_QUORUM: int = 2; + private static /* final*/ moduleComparator: EstimatedModuleComparator = new EstimatedModuleComparator(); + protected static /* final*/ MIN_SKIP: int = 3; // 1 pixel/module times 3 modules/center + protected static /* final*/ MAX_MODULES: int = 97; // support up to version 20 for mobile clients - private possibleCenters: FinderPattern[]; - private hasSkipped: boolean; - private crossCheckStateCount: Int32Array; + private /* final*/ image: BitMatrix; + private /* final*/ possibleCenters: List; + private hasSkipped: boolean; + private /* final*/ crossCheckStateCount: Int32Array; + private /* final*/ resultPointCallback: ResultPointCallback; + + /** + *

Creates a finder that will search the image for three finder patterns.

+ * + * @param image image to search + */ + private constructorOverload1(image: BitMatrix) { + this.constructorOverload2(image, null); + } /** - *

Creates a finder that will search the image for three finder patterns.

- * - * @param image image to search - */ - // public constructor(image: BitMatrix) { - // this(image, null) - // } - - public constructor(private image: BitMatrix, private resultPointCallback: ResultPointCallback) { - this.possibleCenters = []; - this.crossCheckStateCount = new Int32Array(5); - this.resultPointCallback = resultPointCallback; - } - - protected getImage(): BitMatrix { - return this.image; - } - - protected getPossibleCenters(): FinderPattern[] { - return this.possibleCenters; - } - - public find(hints: Map): FinderPatternInfo /*throws NotFoundException */ { - const tryHarder: boolean = (hints !== null && hints !== undefined) && undefined !== hints.get(DecodeHintType.TRY_HARDER); - const pureBarcode: boolean = (hints !== null && hints !== undefined) && undefined !== hints.get(DecodeHintType.PURE_BARCODE); - const image = this.image; - const maxI = image.getHeight(); - const 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. - let iSkip = Math.floor((3 * maxI) / (4 * FinderPatternFinder.MAX_MODULES)); - if (iSkip < FinderPatternFinder.MIN_SKIP || tryHarder) { - iSkip = FinderPatternFinder.MIN_SKIP; - } + * @param image image to search + * @param resultPointCallback + */ + private constructorOverload2(image: BitMatrix, resultPointCallback: ResultPointCallback) { + this.image = image; + this.possibleCenters = new Array(); + this.crossCheckStateCount = Int32Array.from({ length: 5 }); + this.resultPointCallback = resultPointCallback; + } - let done: boolean = false; - const stateCount = new Int32Array(5); - for (let i = iSkip - 1; i < maxI && !done; i += iSkip) { - // Get a row of black/white values - stateCount[0] = 0; - stateCount[1] = 0; - stateCount[2] = 0; - stateCount[3] = 0; - stateCount[4] = 0; - let currentState = 0; - for (let j = 0; j < maxJ; j++) { - if (image.get(j, i)) { - // 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 (FinderPatternFinder.foundPatternCross(stateCount)) { // Yes - const confirmed: boolean = this.handlePossibleCenter(stateCount, i, j, pureBarcode); - if (confirmed === true) { - // Start examining every other line. Checking each line turned out to be too - // expensive and didn't improve performance. - iSkip = 2; - if (this.hasSkipped === true) { - done = this.haveMultiplyConfirmedCenters(); - } else { - const rowSkip = this.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 { - stateCount[0] = stateCount[2]; - stateCount[1] = stateCount[3]; - stateCount[2] = stateCount[4]; - stateCount[3] = 1; - stateCount[4] = 0; - currentState = 3; - continue; - } - // 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 (FinderPatternFinder.foundPatternCross(stateCount)) { - const confirmed: boolean = this.handlePossibleCenter(stateCount, i, maxJ, pureBarcode); - if (confirmed === true) { - iSkip = stateCount[0]; - if (this.hasSkipped) { - // Found a third one - done = this.haveMultiplyConfirmedCenters(); - } - } - } - } + /** + * @param image image to search + */ + constructor(image: BitMatrix, resultPointCallback?: ResultPointCallback) { + // TYPESCRIPTPORT: this contructor only serves as entrypoint for the original Java overloads + if (resultPointCallback) { + this.constructorOverload2(image, resultPointCallback); + return; + } + this.constructorOverload1(image); + } - const patternInfo: FinderPattern[] = this.selectBestPatterns(); - ResultPoint.orderBestPatterns(patternInfo); + protected /* final */ getImage(): BitMatrix { + return this.image; + } - return new FinderPatternInfo(patternInfo); - } + protected /* final */ getPossibleCenters(): List { + return this.possibleCenters; + } - /** - * Given a count of black/white/black/white/black pixels just seen and an end position, - * figures the location of the center of this run. - */ - private static centerFromEnd(stateCount: Int32Array, end: number /*int*/): number/*float*/ { - return (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0; + /** + * + * @throws NotFoundException + */ + /* final */ find(hints: Map): FinderPatternInfo { + const tryHarder: boolean = hints != null && hints.has(DecodeHintType.TRY_HARDER); + const maxI: int = this.image.getHeight(); + const maxJ: int = this.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. + let iSkip: int = Math.trunc((3 * maxI) / (4 * FinderPatternFinder.MAX_MODULES)); + if (iSkip < FinderPatternFinder.MIN_SKIP || tryHarder) { + iSkip = FinderPatternFinder.MIN_SKIP; } - /** - * @param stateCount count of black/white/black/white/black pixels just read - * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios - * used by finder patterns to be considered a match - */ - protected static foundPatternCross(stateCount: Int32Array): boolean { - let totalModuleSize = 0; - for (let i = 0; i < 5; i++) { - const count = stateCount[i]; - if (count === 0) { - return false; + let done: boolean = false; + const stateCount: Int32Array = Int32Array.from({ length: 5 }); + for (let i: int = iSkip - 1; i < maxI && !done; i += iSkip) { + // Get a row of black/white values + this.clearCounts(stateCount); + let currentState: int = 0; + for (let j: int = 0; j < maxJ; j++) { + if (this.image.get(j, i)) { + // 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 (FinderPatternFinder.foundPatternCross(stateCount)) { // Yes + let confirmed: boolean = this.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 (this.hasSkipped) { + done = this.haveMultiplyConfirmedCenters(); + } else { + let rowSkip: int = this.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 { + this.shiftCounts2(stateCount); + currentState = 3; + continue; + } + // Clear state to start looking again + currentState = 0; + this.clearCounts(stateCount); + } else { // No, shift counts back by two + this.shiftCounts2(stateCount); + currentState = 3; + } + } else { + stateCount[++currentState]++; } - totalModuleSize += count; + } else { // Counting white pixels + stateCount[currentState]++; + } } - if (totalModuleSize < 7) { - return false; + } + if (FinderPatternFinder.foundPatternCross(stateCount)) { + let confirmed: boolean = this.handlePossibleCenter(stateCount, i, maxJ); + if (confirmed) { + iSkip = stateCount[0]; + if (this.hasSkipped) { + // Found a third one + done = this.haveMultiplyConfirmedCenters(); + } } - const moduleSize: number /*float*/ = totalModuleSize / 7.0; - const maxVariance: number /*float*/ = moduleSize / 2.0; - // Allow less than 50% variance from 1-1-3-1-1 proportions - return Math.abs(moduleSize - stateCount[0]) < maxVariance && - Math.abs(moduleSize - stateCount[1]) < maxVariance && - Math.abs(3.0 * moduleSize - stateCount[2]) < 3 * maxVariance && - Math.abs(moduleSize - stateCount[3]) < maxVariance && - Math.abs(moduleSize - stateCount[4]) < maxVariance; - } - - private getCrossCheckStateCount(): Int32Array { - const crossCheckStateCount = this.crossCheckStateCount; - crossCheckStateCount[0] = 0; - crossCheckStateCount[1] = 0; - crossCheckStateCount[2] = 0; - crossCheckStateCount[3] = 0; - crossCheckStateCount[4] = 0; - return crossCheckStateCount; + } } - /** - * After a vertical and horizontal scan finds a potential finder pattern, this method - * "cross-cross-cross-checks" by scanning down diagonally through the center of the possible - * finder pattern to see if the same proportion is detected. - * - * @param startI row where a finder pattern was detected - * @param centerJ center of the section that appears to cross a finder pattern - * @param maxCount maximum reasonable number of modules that should be - * observed in any reading state, based on the results of the horizontal scan - * @param originalStateCountTotal The original state count total. - * @return true if proportions are withing expected limits - */ - private crossCheckDiagonal(startI: number /*int*/, centerJ: number /*int*/, maxCount: number /*int*/, originalStateCountTotal: number /*int*/): boolean { - const stateCount: Int32Array = this.getCrossCheckStateCount(); - - // Start counting up, left from center finding black center mass - let i = 0; - const image = this.image; - while (startI >= i && centerJ >= i && image.get(centerJ - i, startI - i)) { - stateCount[2]++; - i++; - } - - if (startI < i || centerJ < i) { - return false; - } - - // Continue up, left finding white space - while (startI >= i && centerJ >= i && !image.get(centerJ - i, startI - i) && - stateCount[1] <= maxCount) { - stateCount[1]++; - i++; - } - - // If already too many modules in this state or ran off the edge: - if (startI < i || centerJ < i || stateCount[1] > maxCount) { - return false; - } + const patternInfo: FinderPattern[] = this.selectBestPatterns(); + ResultPoint.orderBestPatterns(patternInfo); - // Continue up, left finding black border - while (startI >= i && centerJ >= i && image.get(centerJ - i, startI - i) && - stateCount[0] <= maxCount) { - stateCount[0]++; - i++; - } - if (stateCount[0] > maxCount) { - return false; - } + return new FinderPatternInfo(patternInfo); + } - const maxI = image.getHeight(); - const maxJ = image.getWidth(); + /** + * Given a count of black/white/black/white/black pixels just seen and an end position, + * figures the location of the center of this run. + */ + private static centerFromEnd(stateCount: Int32Array, end: int): float { + return (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0; + } - // Now also count down, right from center - i = 1; - while (startI + i < maxI && centerJ + i < maxJ && image.get(centerJ + i, startI + i)) { - stateCount[2]++; - i++; - } + /** + * @param stateCount count of black/white/black/white/black pixels just read + * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios + * used by finder patterns to be considered a match + */ + protected static foundPatternCross(stateCount: Int32Array): boolean { + let totalModuleSize: int = 0; + for (let i: int = 0; i < 5; i++) { + let count: int = stateCount[i]; + if (count === 0) { + return false; + } + totalModuleSize += count; + } + if (totalModuleSize < 7) { + return false; + } + const moduleSize: float = totalModuleSize / 7.0; // TYPESCRIPTPORT: check if a precision reduction is needed + const maxVariance: float = moduleSize / 2.0; // TYPESCRIPTPORT: check if a precision reduction is needed + // Allow less than 50% variance from 1-1-3-1-1 proportions + return Math.abs(moduleSize - stateCount[0]) < maxVariance && + Math.abs(moduleSize - stateCount[1]) < maxVariance && + Math.abs(3.0 * moduleSize - stateCount[2]) < 3 * maxVariance && + Math.abs(moduleSize - stateCount[3]) < maxVariance && + Math.abs(moduleSize - stateCount[4]) < maxVariance; + } - // Ran off the edge? - if (startI + i >= maxI || centerJ + i >= maxJ) { - return false; - } + /** + * @param stateCount count of black/white/black/white/black pixels just read + * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios + * used by finder patterns to be considered a match + */ + protected static foundPatternDiagonal(stateCount: Int32Array): boolean { + let totalModuleSize: int = 0; + for (let i: int = 0; i < 5; i++) { + const count: int = stateCount[i]; + if (count === 0) { + return false; + } + totalModuleSize += count; + } + if (totalModuleSize < 7) { + return false; + } + const moduleSize: float = totalModuleSize / 7.0; + const maxVariance: float = moduleSize / 1.333; + // Allow less than 75% variance from 1-1-3-1-1 proportions + return Math.abs(moduleSize - stateCount[0]) < maxVariance && + Math.abs(moduleSize - stateCount[1]) < maxVariance && + Math.abs(3.0 * moduleSize - stateCount[2]) < 3 * maxVariance && + Math.abs(moduleSize - stateCount[3]) < maxVariance && + Math.abs(moduleSize - stateCount[4]) < maxVariance; + } - while (startI + i < maxI && centerJ + i < maxJ && !image.get(centerJ + i, startI + i) && - stateCount[3] < maxCount) { - stateCount[3]++; - i++; - } + private getCrossCheckStateCount(): Int32Array { + this.clearCounts(this.crossCheckStateCount); + return this.crossCheckStateCount; + } - if (startI + i >= maxI || centerJ + i >= maxJ || stateCount[3] >= maxCount) { - return false; - } + protected /* final */ clearCounts(counts: Int32Array): void { + Arrays.fill(counts, 0); + } - while (startI + i < maxI && centerJ + i < maxJ && image.get(centerJ + i, startI + i) && - stateCount[4] < maxCount) { - stateCount[4]++; - i++; - } + protected /* final */ shiftCounts2(stateCount: Int32Array): void { + stateCount[0] = stateCount[2]; + stateCount[1] = stateCount[3]; + stateCount[2] = stateCount[4]; + stateCount[3] = 1; + stateCount[4] = 0; + } - if (stateCount[4] >= maxCount) { - return false; - } + /** + * After a vertical and horizontal scan finds a potential finder pattern, this method + * "cross-cross-cross-checks" by scanning down diagonally through the center of the possible + * finder pattern to see if the same proportion is detected. + * + * @param centerI row where a finder pattern was detected + * @param centerJ center of the section that appears to cross a finder pattern + * @return true if proportions are withing expected limits + */ + private crossCheckDiagonal(centerI: int, centerJ: int): boolean { + const stateCount: Int32Array = this.getCrossCheckStateCount(); + + // Start counting up, left from center finding black center mass + let i: int = 0; + while (centerI >= i && centerJ >= i && this.image.get(centerJ - i, centerI - i)) { + stateCount[2]++; + i++; + } + if (stateCount[2] === 0) { + return false; + } - // If we found a finder-pattern-like section, but its size is more than 100% different than - // the original, assume it's a false positive - const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; - return Math.abs(stateCountTotal - originalStateCountTotal) < 2 * originalStateCountTotal && - FinderPatternFinder.foundPatternCross(stateCount); + // Continue up, left finding white space + while (centerI >= i && centerJ >= i && !this.image.get(centerJ - i, centerI - i)) { + stateCount[1]++; + i++; + } + if (stateCount[1] === 0) { + return false; } - /** - * After a vertical and horizontal scan finds a potential finder pattern, this method - * "cross-cross-cross-checks" by scanning down diagonally through the center of the possible - * finder pattern to see if the same proportion is detected. - * - * @param centerI row where a finder pattern was detected - * @param centerJ center of the section that appears to cross a finder pattern - * @return true if proportions are withing expected limits - */ - private crossCheckDiagonal2(centerI: int, centerJ: int): boolean { - const stateCount: Int32Array = this.getCrossCheckStateCount(); - - // Start counting up, left from center finding black center mass - let i: int = 0; - while (centerI >= i && centerJ >= i && this.image.get(centerJ - i, centerI - i)) { - stateCount[2]++; - i++; - } - if (stateCount[2] === 0) { - return false; - } + // Continue up, left finding black border + while (centerI >= i && centerJ >= i && this.image.get(centerJ - i, centerI - i)) { + stateCount[0]++; + i++; + } + if (stateCount[0] === 0) { + return false; + } - // Continue up, left finding white space - while (centerI >= i && centerJ >= i && !this.image.get(centerJ - i, centerI - i)) { - stateCount[1]++; - i++; - } - if (stateCount[1] === 0) { - return false; - } + const maxI: int = this.image.getHeight(); + const maxJ: int = this.image.getWidth(); - // Continue up, left finding black border - while (centerI >= i && centerJ >= i && this.image.get(centerJ - i, centerI - i)) { - stateCount[0]++; - i++; - } - if (stateCount[0] === 0) { - return false; - } + // Now also count down, right from center + i = 1; + while (centerI + i < maxI && centerJ + i < maxJ && this.image.get(centerJ + i, centerI + i)) { + stateCount[2]++; + i++; + } - let maxI: int = this.image.getHeight(); - let maxJ: int = this.image.getWidth(); + while (centerI + i < maxI && centerJ + i < maxJ && !this.image.get(centerJ + i, centerI + i)) { + stateCount[3]++; + i++; + } + if (stateCount[3] === 0) { + return false; + } - // Now also count down, right from center - i = 1; - while (centerI + i < maxI && centerJ + i < maxJ && this.image.get(centerJ + i, centerI + i)) { - stateCount[2]++; - i++; - } + while (centerI + i < maxI && centerJ + i < maxJ && this.image.get(centerJ + i, centerI + i)) { + stateCount[4]++; + i++; + } + if (stateCount[4] === 0) { + return false; + } - while (centerI + i < maxI && centerJ + i < maxJ && !this.image.get(centerJ + i, centerI + i)) { - stateCount[3]++; - i++; - } - if (stateCount[3] === 0) { - return false; - } + return FinderPatternFinder.foundPatternDiagonal(stateCount); + } - while (centerI + i < maxI && centerJ + i < maxJ && this.image.get(centerJ + i, centerI + i)) { - stateCount[4]++; - i++; - } - if (stateCount[4] === 0) { - return false; - } + /** + *

After a horizontal scan finds a potential finder pattern, this method + * "cross-checks" by scanning down vertically through the center of the possible + * finder pattern to see if the same proportion is detected.

+ * + * @param startI row where a finder pattern was detected + * @param centerJ center of the section that appears to cross a finder pattern + * @param maxCount maximum reasonable number of modules that should be + * observed in any reading state, based on the results of the horizontal scan + * @return vertical center of finder pattern, or {@link Float#NaN} if not found + */ + private crossCheckVertical(startI: int, centerJ: int, maxCount: int, + originalStateCountTotal: int): float { + const image: BitMatrix = this.image; + + const maxI: int = image.getHeight(); + let stateCount: Int32Array = this.getCrossCheckStateCount(); + + // Start counting up from center + let i: int = startI; + while (i >= 0 && image.get(centerJ, i)) { + stateCount[2]++; + i--; + } + if (i < 0) { + return Float.NaN; + } + while (i >= 0 && !image.get(centerJ, i) && stateCount[1] <= maxCount) { + stateCount[1]++; + i--; + } + // If already too many modules in this state or ran off the edge: + if (i < 0 || stateCount[1] > maxCount) { + return Float.NaN; + } + while (i >= 0 && image.get(centerJ, i) && stateCount[0] <= maxCount) { + stateCount[0]++; + i--; + } + if (stateCount[0] > maxCount) { + return Float.NaN; + } - return FinderPatternFinder.foundPatternDiagonal(stateCount); + // Now also count down from center + i = startI + 1; + while (i < maxI && image.get(centerJ, i)) { + stateCount[2]++; + i++; + } + if (i === maxI) { + return Float.NaN; + } + while (i < maxI && !image.get(centerJ, i) && stateCount[3] < maxCount) { + stateCount[3]++; + i++; + } + if (i === maxI || stateCount[3] >= maxCount) { + return Float.NaN; + } + while (i < maxI && image.get(centerJ, i) && stateCount[4] < maxCount) { + stateCount[4]++; + i++; + } + if (stateCount[4] >= maxCount) { + return Float.NaN; } - /** - * @param stateCount count of black/white/black/white/black pixels just read - * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios - * used by finder patterns to be considered a match - */ - protected static foundPatternDiagonal(stateCount: Int32Array): boolean { - let totalModuleSize: int = 0; - for (let i: int = 0; i < 5; i++) { - let count: int = stateCount[i]; - if (count === 0) { - return false; - } - totalModuleSize += count; - } - if (totalModuleSize < 7) { - return false; - } - const moduleSize: float = totalModuleSize / 7.0; - const maxVariance: float = moduleSize / 1.333; - // Allow less than 75% variance from 1-1-3-1-1 proportions - return Math.abs(moduleSize - stateCount[0]) < maxVariance && - Math.abs(moduleSize - stateCount[1]) < maxVariance && - Math.abs(3.0 * moduleSize - stateCount[2]) < 3 * maxVariance && - Math.abs(moduleSize - stateCount[3]) < maxVariance && - Math.abs(moduleSize - stateCount[4]) < maxVariance; + // If we found a finder-pattern-like section, but its size is more than 40% different than + // the original, assume it's a false positive + const stateCountTotal: int = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + + stateCount[4]; + if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) { + return Float.NaN; } - /** - *

After a horizontal scan finds a potential finder pattern, this method - * "cross-checks" by scanning down vertically through the center of the possible - * finder pattern to see if the same proportion is detected.

- * - * @param startI row where a finder pattern was detected - * @param centerJ center of the section that appears to cross a finder pattern - * @param maxCount maximum reasonable number of modules that should be - * observed in any reading state, based on the results of the horizontal scan - * @return vertical center of finder pattern, or {@link Float#NaN} if not found - */ - private crossCheckVertical(startI: number /*int*/, centerJ: number /*int*/, maxCount: number /*int*/, - originalStateCountTotal: number /*int*/): number/*float*/ { - const image: BitMatrix = this.image; - - const maxI = image.getHeight(); - const stateCount: Int32Array = this.getCrossCheckStateCount(); - - // Start counting up from center - let i = startI; - while (i >= 0 && image.get(centerJ, i)) { - stateCount[2]++; - i--; - } - if (i < 0) { - return NaN; - } - while (i >= 0 && !image.get(centerJ, i) && stateCount[1] <= maxCount) { - stateCount[1]++; - i--; - } - // If already too many modules in this state or ran off the edge: - if (i < 0 || stateCount[1] > maxCount) { - return NaN; - } - while (i >= 0 && image.get(centerJ, i) && stateCount[0] <= maxCount) { - stateCount[0]++; - i--; - } - if (stateCount[0] > maxCount) { - return NaN; - } + return FinderPatternFinder.foundPatternCross(stateCount) ? FinderPatternFinder.centerFromEnd(stateCount, i) : Float.NaN; + } - // Now also count down from center - i = startI + 1; - while (i < maxI && image.get(centerJ, i)) { - stateCount[2]++; - i++; - } - if (i === maxI) { - return NaN; - } - while (i < maxI && !image.get(centerJ, i) && stateCount[3] < maxCount) { - stateCount[3]++; - i++; - } - if (i === maxI || stateCount[3] >= maxCount) { - return NaN; - } - while (i < maxI && image.get(centerJ, i) && stateCount[4] < maxCount) { - stateCount[4]++; - i++; - } - if (stateCount[4] >= maxCount) { - return NaN; - } + /** + *

Like {@link #crossCheckVertical(int, int, int, int)}, and in fact is basically identical, + * except it reads horizontally instead of vertically. This is used to cross-cross + * check a vertical cross check and locate the real center of the alignment pattern.

+ */ + private crossCheckHorizontal(startJ: int, centerI: int, maxCount: int, + originalStateCountTotal: int): float { + const image: BitMatrix = this.image; - // If we found a finder-pattern-like section, but its size is more than 40% different than - // the original, assume it's a false positive - const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + - stateCount[4]; - if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) { - return NaN; - } + const maxJ: int = image.getWidth(); + const stateCount: Int32Array = this.getCrossCheckStateCount(); - return FinderPatternFinder.foundPatternCross(stateCount) ? FinderPatternFinder.centerFromEnd(stateCount, i) : NaN; + let j: int = startJ; + while (j >= 0 && image.get(j, centerI)) { + stateCount[2]++; + j--; + } + if (j < 0) { + return Float.NaN; + } + while (j >= 0 && !image.get(j, centerI) && stateCount[1] <= maxCount) { + stateCount[1]++; + j--; + } + if (j < 0 || stateCount[1] > maxCount) { + return Float.NaN; + } + while (j >= 0 && image.get(j, centerI) && stateCount[0] <= maxCount) { + stateCount[0]++; + j--; + } + if (stateCount[0] > maxCount) { + return Float.NaN; } - /** - *

Like {@link #crossCheckVertical(int, int, int, int)}, and in fact is basically identical, - * except it reads horizontally instead of vertically. This is used to cross-cross - * check a vertical cross check and locate the real center of the alignment pattern.

- */ - private crossCheckHorizontal(startJ: number /*int*/, centerI: number /*int*/, maxCount: number /*int*/, - originalStateCountTotal: number /*int*/): number/*float*/ { - const image: BitMatrix = this.image; - - const maxJ = image.getWidth(); - const stateCount: Int32Array = this.getCrossCheckStateCount(); - - let j = startJ; - while (j >= 0 && image.get(j, centerI)) { - stateCount[2]++; - j--; - } - if (j < 0) { - return NaN; - } - while (j >= 0 && !image.get(j, centerI) && stateCount[1] <= maxCount) { - stateCount[1]++; - j--; - } - if (j < 0 || stateCount[1] > maxCount) { - return NaN; - } - while (j >= 0 && image.get(j, centerI) && stateCount[0] <= maxCount) { - stateCount[0]++; - j--; - } - if (stateCount[0] > maxCount) { - return NaN; - } - - j = startJ + 1; - while (j < maxJ && image.get(j, centerI)) { - stateCount[2]++; - j++; - } - if (j === maxJ) { - return NaN; - } - while (j < maxJ && !image.get(j, centerI) && stateCount[3] < maxCount) { - stateCount[3]++; - j++; - } - if (j === maxJ || stateCount[3] >= maxCount) { - return NaN; - } - while (j < maxJ && image.get(j, centerI) && stateCount[4] < maxCount) { - stateCount[4]++; - j++; - } - if (stateCount[4] >= maxCount) { - return NaN; - } - - // If we found a finder-pattern-like section, but its size is significantly different than - // the original, assume it's a false positive - const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + - stateCount[4]; - if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) { - return NaN; - } + j = startJ + 1; + while (j < maxJ && image.get(j, centerI)) { + stateCount[2]++; + j++; + } + if (j === maxJ) { + return Float.NaN; + } + while (j < maxJ && !image.get(j, centerI) && stateCount[3] < maxCount) { + stateCount[3]++; + j++; + } + if (j === maxJ || stateCount[3] >= maxCount) { + return Float.NaN; + } + while (j < maxJ && image.get(j, centerI) && stateCount[4] < maxCount) { + stateCount[4]++; + j++; + } + if (stateCount[4] >= maxCount) { + return Float.NaN; + } - return FinderPatternFinder.foundPatternCross(stateCount) ? FinderPatternFinder.centerFromEnd(stateCount, j) : NaN; + // If we found a finder-pattern-like section, but its size is significantly different than + // the original, assume it's a false positive + const stateCountTotal: int = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + + stateCount[4]; + if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) { + return Float.NaN; } + return FinderPatternFinder.foundPatternCross(stateCount) ? FinderPatternFinder.centerFromEnd(stateCount, j) : Float.NaN; + } + /** * @param stateCount reading state module counts from horizontal scan * @param i row where finder pattern may be found @@ -574,262 +515,194 @@ export default class FinderPatternFinder { * @param pureBarcode ignored * @return true if a finder pattern candidate was found this time * @deprecated only exists for backwards compatibility - * @see #handlePossibleCenter(int[], int, int) - * @override handlePossibleCenter + * @see #handlePossibleCenter(Int32Array, int, int) + * @Deprecated */ - protected /* final */ handlePossibleCenter(stateCount: Int32Array, i: number /*int*/, j: number /*int*/, pureBarcode: boolean): boolean { - const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + - stateCount[4]; - let centerJ: number /*float*/ = FinderPatternFinder.centerFromEnd(stateCount, j); - let centerI: number /*float*/ = this.crossCheckVertical(i, /*(int) */Math.floor(centerJ), stateCount[2], stateCountTotal); - if (!isNaN(centerI)) { - // Re-cross check - centerJ = this.crossCheckHorizontal(/*(int) */Math.floor(centerJ), /*(int) */Math.floor(centerI), stateCount[2], stateCountTotal); - if (!isNaN(centerJ) && - (!pureBarcode || this.crossCheckDiagonal(/*(int) */Math.floor(centerI), /*(int) */Math.floor(centerJ), stateCount[2], stateCountTotal))) { - const estimatedModuleSize: number /*float*/ = stateCountTotal / 7.0; - let found: boolean = false; - const possibleCenters = this.possibleCenters; - for (let index = 0, length = possibleCenters.length; index < length; index++) { - const center: FinderPattern = possibleCenters[index]; - // Look for about the same center and module size: - if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { - possibleCenters[index] = center.combineEstimate(centerI, centerJ, estimatedModuleSize); - found = true; - break; - } - } - if (!found) { - const point: FinderPattern = new FinderPattern(centerJ, centerI, estimatedModuleSize); - possibleCenters.push(point); - if (this.resultPointCallback !== null && this.resultPointCallback !== undefined) { - this.resultPointCallback.foundPossibleResultPoint(point); - } - } - return true; - } - } - return false; + protected /* final */ handlePossibleCenterX(stateCount: Int32Array, i: int, j: int, pureBarcode: boolean): boolean { + return this.handlePossibleCenter(stateCount, i, j); } - /** - *

This is called when a horizontal scan finds a possible alignment pattern. It will - * cross check with a vertical scan, and if successful, will, ah, cross-cross-check - * with another horizontal scan. This is needed primarily to locate the real horizontal - * center of the pattern in cases of extreme skew. - * And then we cross-cross-cross check with another diagonal scan.

- * - *

If that succeeds the finder pattern location is added to a list that tracks - * the number of times each location has been nearly-matched as a finder pattern. - * Each additional find is more evidence that the location is in fact a finder - * pattern center - * - * @param stateCount reading state module counts from horizontal scan - * @param i row where finder pattern may be found - * @param j end of possible finder pattern in row - * @param pureBarcode true if in "pure barcode" mode - * @return true if a finder pattern candidate was found this time - */ - protected handlePossibleCenter2(stateCount: Int32Array, i: number /*int*/, j: number /*int*/): boolean { - const stateCountTotal: int = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + - stateCount[4]; - let centerJ: float = FinderPatternFinder.centerFromEnd(stateCount, j); - let centerI: float = this.crossCheckVertical(i, centerJ, stateCount[2], stateCountTotal); - if (!Float.isNaN(centerI)) { - // Re-cross check - centerJ = this.crossCheckHorizontal( centerJ, centerI, stateCount[2], stateCountTotal); - if (!Float.isNaN(centerJ) && this.crossCheckDiagonal2( centerI, centerJ)) { - const estimatedModuleSize: float = stateCountTotal / 7.0; - let found: boolean = false; - for (let index: int = 0; index < this.possibleCenters.length; index++) { - const center: FinderPattern = this.possibleCenters[index]; - // Look for about the same center and module size: - if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { - this.possibleCenters[index] = center.combineEstimate(centerI, centerJ, estimatedModuleSize); - found = true; - break; - } + /** + *

This is called when a horizontal scan finds a possible alignment pattern. It will + * cross check with a vertical scan, and if successful, will, ah, cross-cross-check + * with another horizontal scan. This is needed primarily to locate the real horizontal + * center of the pattern in cases of extreme skew. + * And then we cross-cross-cross check with another diagonal scan.

+ * + *

If that succeeds the finder pattern location is added to a list that tracks + * the number of times each location has been nearly-matched as a finder pattern. + * Each additional find is more evidence that the location is in fact a finder + * pattern center + * + * @param stateCount reading state module counts from horizontal scan + * @param i row where finder pattern may be found + * @param j end of possible finder pattern in row + * @return true if a finder pattern candidate was found this time + */ + protected /* final */ handlePossibleCenter(stateCount: Int32Array, i: int, j: int): boolean { + const stateCountTotal: int = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + + stateCount[4]; + let centerJ: float = FinderPatternFinder.centerFromEnd(stateCount, j); + const centerI: float = this.crossCheckVertical(i, centerJ, stateCount[2], stateCountTotal); + if (!Float.isNaN(centerI)) { + // Re-cross check + centerJ = this.crossCheckHorizontal(Math.trunc(centerJ), Math.trunc(centerI), stateCount[2], stateCountTotal); + if (!Float.isNaN(centerJ) && this.crossCheckDiagonal(Math.trunc(centerI), Math.trunc(centerJ))) { + const estimatedModuleSize: float = stateCountTotal / 7.0; + let found: boolean = false; + for (let index: int = 0; index < this.possibleCenters.length; index++) { + const center: FinderPattern = this.possibleCenters[index]; + // Look for about the same center and module size: + if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { + this.possibleCenters[index] = center.combineEstimate(centerI, centerJ, estimatedModuleSize); + found = true; + break; } - if (!found) { - const point: FinderPattern = new FinderPattern(centerJ, centerI, estimatedModuleSize); - this.possibleCenters.push(point); - if (this.resultPointCallback) { - this.resultPointCallback.foundPossibleResultPoint(point); - } + } + if (!found) { + const point: FinderPattern = new FinderPattern(centerJ, centerI, estimatedModuleSize); + this.possibleCenters.push(point); + if (this.resultPointCallback != null) { + this.resultPointCallback.foundPossibleResultPoint(point); } - return true; } + return true; } - return false; } + return false; + } - /** - * @return number of rows we could safely skip during scanning, based on the first - * two finder patterns that have been located. In some cases their position will - * allow us to infer that the third pattern must lie below a certain point farther - * down in the image. - */ - private findRowSkip(): number /*int*/ { - const max = this.possibleCenters.length; - if (max <= 1) { - return 0; - } - let firstConfirmedCenter: ResultPoint = null; - for (const center of this.possibleCenters) { - if (center.getCount() >= FinderPatternFinder.CENTER_QUORUM) { - if (firstConfirmedCenter == null) { - firstConfirmedCenter = center; - } else { - // We have two confirmed centers - // How far down can we skip before resuming looking for the next - // pattern? In the worst case, only the difference between the - // difference in the x / y coordinates of the two centers. - // This is the case where you find top left last. - this.hasSkipped = true; - return /*(int) */Math.floor((Math.abs(firstConfirmedCenter.getX() - center.getX()) - - Math.abs(firstConfirmedCenter.getY() - center.getY())) / 2); - } - } + /** + * @return number of rows we could safely skip during scanning, based on the first + * two finder patterns that have been located. In some cases their position will + * allow us to infer that the third pattern must lie below a certain point farther + * down in the image. + */ + private findRowSkip(): int { + const max: int = this.possibleCenters.length; + if (max <= 1) { + return 0; + } + let firstConfirmedCenter: ResultPoint = null; + for (const center/*: FinderPattern*/ of this.possibleCenters) { + if (center.getCount() >= FinderPatternFinder.CENTER_QUORUM) { + if (firstConfirmedCenter == null) { + firstConfirmedCenter = center; + } else { + // We have two confirmed centers + // How far down can we skip before resuming looking for the next + // pattern? In the worst case, only the difference between the + // difference in the x / y coordinates of the two centers. + // This is the case where you find top left last. + this.hasSkipped = true; + return /* TYPESCRIPTPORT: Math.trunc here to emulate Java's `int` cast see CONTRIBUTING */ Math.trunc((Math.abs(firstConfirmedCenter.getX() - center.getX()) - + Math.abs(firstConfirmedCenter.getY() - center.getY())) / 2); } - return 0; + } } + return 0; + } - /** - * @return true iff we have found at least 3 finder patterns that have been detected - * at least {@link #CENTER_QUORUM} times each, and, the estimated module size of the - * candidates is "pretty similar" - */ - private haveMultiplyConfirmedCenters(): boolean { - let confirmedCount = 0; - let totalModuleSize: number /*float*/ = 0.0; - const max = this.possibleCenters.length; - for (const pattern of this.possibleCenters) { - if (pattern.getCount() >= FinderPatternFinder.CENTER_QUORUM) { - confirmedCount++; - totalModuleSize += pattern.getEstimatedModuleSize(); - } - } - if (confirmedCount < 3) { - return false; - } - // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive" - // and that we need to keep looking. We detect this by asking if the estimated module sizes - // vary too much. We arbitrarily say that when the total deviation from average exceeds - // 5% of the total module size estimates, it's too much. - const average: number /*float*/ = totalModuleSize / max; - let totalDeviation: number /*float*/ = 0.0; - for (const pattern of this.possibleCenters) { - totalDeviation += Math.abs(pattern.getEstimatedModuleSize() - average); - } - return totalDeviation <= 0.05 * totalModuleSize; + /** + * @return true iff we have found at least 3 finder patterns that have been detected + * at least {@link #CENTER_QUORUM} times each, and, the estimated module size of the + * candidates is "pretty similar" + */ + private haveMultiplyConfirmedCenters(): boolean { + let confirmedCount: int = 0; + let totalModuleSize: float = 0.0; + const max: int = this.possibleCenters.length; + for (const pattern/*: FinderPattern*/ of this.possibleCenters) { + if (pattern.getCount() >= FinderPatternFinder.CENTER_QUORUM) { + confirmedCount++; + totalModuleSize += pattern.getEstimatedModuleSize(); + } + } + if (confirmedCount < 3) { + return false; + } + // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive" + // and that we need to keep looking. We detect this by asking if the estimated module sizes + // vary too much. We arbitrarily say that when the total deviation from average exceeds + // 5% of the total module size estimates, it's too much. + const average: float = totalModuleSize / max; + let totalDeviation: float = 0.0; + for (const pattern/*: FinderPattern*/ of this.possibleCenters) { + totalDeviation += Math.abs(pattern.getEstimatedModuleSize() - average); } + return totalDeviation <= 0.05 * totalModuleSize; + } - /** - * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are - * those that have been detected at least {@link #CENTER_QUORUM} times, and whose module - * size differs from the average among those patterns the least - * @throws NotFoundException if 3 such finder patterns do not exist - */ - private selectBestPatterns(): FinderPattern[] /*throws NotFoundException */ { - - const startSize = this.possibleCenters.length; - if (startSize < 3) { - // Couldn't find enough finder patterns - throw new NotFoundException(); - } + /** + * Get square of distance between a and b. + */ + private static squaredDistance(a: FinderPattern, b: FinderPattern): double { + const x: double = a.getX() - b.getX(); + const y: double = a.getY() - b.getY(); + return x * x + y * y; + } - const possibleCenters = this.possibleCenters; - - let average: float; - // Filter outlier possibilities whose module size is too different - if (startSize > 3) { - // But we can only afford to do so if we have at least 4 possibilities to choose from - let totalModuleSize: float = 0.0; - let square: float = 0.0; - for (const center of this.possibleCenters) { - const size: float = center.getEstimatedModuleSize(); - totalModuleSize += size; - square += size * size; - } - average = totalModuleSize / startSize; - let stdDev: float = Math.sqrt(square / startSize - average * average); - - possibleCenters.sort( - /** - *

Orders by furthest from average

- */ - // FurthestFromAverageComparator implements Comparator - (center1: FinderPattern, center2: FinderPattern) => { - const dA: float = Math.abs(center2.getEstimatedModuleSize() - average); - const dB: float = Math.abs(center1.getEstimatedModuleSize() - average); - return dA < dB ? -1 : dA > dB ? 1 : 0; - }); - - const limit: float = Math.max(0.2 * average, stdDev); - - for (let i = 0; i < possibleCenters.length && possibleCenters.length > 3; i++) { - const pattern: FinderPattern = possibleCenters[i]; - if (Math.abs(pattern.getEstimatedModuleSize() - average) > limit) { - possibleCenters.splice(i, 1); - i--; - } - } - } + /** + * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are + * those have similar module size and form a shape closer to a isosceles right triangle. + * @throws {@link NotFoundException} if 3 such finder patterns do not exist + */ + private selectBestPatterns(): FinderPattern[] { - if (possibleCenters.length > 3) { - // Throw away all but those first size candidate points we found. + const startSize: int = this.possibleCenters.length; + if (startSize < 3) { + // Couldn't find enough finder patterns + throw NotFoundException.getNotFoundInstance(); + } - let totalModuleSize: float = 0.0; - for (const possibleCenter of possibleCenters) { - totalModuleSize += possibleCenter.getEstimatedModuleSize(); - } + this.possibleCenters.sort(FinderPatternFinder.moduleComparator.compare); - average = totalModuleSize / possibleCenters.length; - - possibleCenters.sort( - /** - *

Orders by {@link FinderPattern#getCount()}, descending.

- */ - // CenterComparator implements Comparator - (center1: FinderPattern, center2: FinderPattern) => { - if (center2.getCount() === center1.getCount()) { - const dA: float = Math.abs(center2.getEstimatedModuleSize() - average); - const dB: float = Math.abs(center1.getEstimatedModuleSize() - average); - return dA < dB ? 1 : dA > dB ? -1 : 0; - } else { - return center2.getCount() - center1.getCount(); - } - }); + let distortion: double = Double.MAX_VALUE; + const squares: Float64Array = Float64Array.from({ length: 3 }); + const bestPatterns: FinderPattern[] = new FinderPattern[3]; - possibleCenters.splice(3); // this is not realy necessary as we only return first 3 anyway - } + for (let i /*int*/ = 0; i < this.possibleCenters.length - 2; i++) { + const fpi: FinderPattern = this.possibleCenters[i]; + const minModuleSize: float = fpi.getEstimatedModuleSize(); - return [ - possibleCenters[0], - possibleCenters[1], - possibleCenters[2] - ]; - } + for (let j /*int*/ = i + 1; j < this.possibleCenters.length - 1; j++) { + const fpj: FinderPattern = this.possibleCenters[j]; + const squares0: double = FinderPatternFinder.squaredDistance(fpi, fpj); - /** @deprecated */ - protected /* final */ clearCounts(counts: Int32Array): void { - FinderPatternFinder.doClearCounts(counts); - } + for (let k /*int*/ = j + 1; k < this.possibleCenters.length; k++) { + const fpk: FinderPattern = this.possibleCenters[k]; + const maxModuleSize: float = fpk.getEstimatedModuleSize(); + if (maxModuleSize > minModuleSize * 1.4) { + // module size is not similar + continue; + } - /** @deprecated */ - protected /* final */ shiftCounts2(stateCount: Int32Array): void { - FinderPatternFinder.doShiftCounts2(stateCount); + squares[0] = squares0; + squares[1] = FinderPatternFinder.squaredDistance(fpj, fpk); + squares[2] = FinderPatternFinder.squaredDistance(fpi, fpk); + Arrays.sort(squares); + + // a^2 + b^2 = c^2 (Pythagorean theorem), and a = b (isosceles triangle). + // Since any right triangle satisfies the formula c^2 - b^2 - a^2 = 0, + // we need to check both two equal sides separately. + // The value of |c^2 - 2 * b^2| + |c^2 - 2 * a^2| increases as dissimilarity + // from isosceles right triangle. + const d: double = Math.abs(squares[2] - 2 * squares[1]) + Math.abs(squares[2] - 2 * squares[0]); + if (d < distortion) { + distortion = d; + bestPatterns[0] = fpi; + bestPatterns[1] = fpj; + bestPatterns[2] = fpk; + } + } + } } - protected static doClearCounts(counts: Int32Array): void { - Arrays.fill(counts, 0); + if (distortion === Double.MAX_VALUE) { + throw NotFoundException.getNotFoundInstance(); } - protected static doShiftCounts2(stateCount: Int32Array): void { - stateCount[0] = stateCount[2]; - stateCount[1] = stateCount[3]; - stateCount[2] = stateCount[4]; - stateCount[3] = 1; - stateCount[4] = 0; - } + return bestPatterns; + } + } diff --git a/src/core/util/Arrays.ts b/src/core/util/Arrays.ts index e487efaa..1cd8a42e 100644 --- a/src/core/util/Arrays.ts +++ b/src/core/util/Arrays.ts @@ -176,4 +176,8 @@ export default class Arrays { public static numberComparator(a: number, b: number) { return a - b; } + + public static sort(squares: Array | Float64Array) { + return squares.sort(); + } } diff --git a/src/core/util/Double.ts b/src/core/util/Double.ts new file mode 100644 index 00000000..e5bda203 --- /dev/null +++ b/src/core/util/Double.ts @@ -0,0 +1,5 @@ +import { double } from 'src/customTypings'; + +export default class Double { + static MAX_VALUE: double = 1.7 * 10 ^ 308; +} diff --git a/src/core/util/Float.ts b/src/core/util/Float.ts index 4258fc86..4675f02f 100644 --- a/src/core/util/Float.ts +++ b/src/core/util/Float.ts @@ -1,3 +1,5 @@ +import { float, int } from 'src/customTypings'; + /** * Ponyfill for Java's Float class. */ @@ -8,6 +10,8 @@ export default class Float { */ static MAX_VALUE: number = Number.MAX_SAFE_INTEGER; + static NaN = NaN; + /** * SincTS has no difference between int and float, there's all numbers, * this is used only to polyfill Java code. @@ -19,4 +23,10 @@ export default class Float { public static isNaN(num: number) { return isNaN(num); } + + public static compare(x: float, y: float): int { + if (x === y) return 0; + if (x < y) return -1; + if (x > y) return 1; + } } From e0ae73731378fcaddceb25cfd68d7f6ad88145e4 Mon Sep 17 00:00:00 2001 From: Luiz Filipe Machado Barni Date: Fri, 4 Dec 2020 01:08:44 -0300 Subject: [PATCH 08/19] added some notes --- CONTRIBUTING.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 91b0d15d..df1b6ace 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,6 +62,8 @@ https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html - `int` has 32 bits, signed, so `int[]` transforms to `Int32Array`. - `char` has 2 bytes, so `char[]` transforms to `Uint16Array`. - `long` has 64 bit two's complement `integer`, can be signed or unsigned. +- `float[]` can be ported to `Float32Array`. +- `double[]` can be ported to `Float64Array`. ### JavaScript's TypedArray @@ -71,8 +73,8 @@ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects - Take care of `int` -> `number` (integer to number) port when doing bitwise transformation especially `<<`. Do a `& 0xFFFFFFFF` for ints, a &0xFF for bytes. - Take care of array initialization, in Java `new Array(N)` initializes capacity NOT size/length. -- Use `Math.floor` for any division of ints otherwise the `number` type is a floating point and keeps the numbers after the dot. -- For `float` to `int` casting use `Math.trunc`, to replicate the same effect as Java casting does. +- Use `Math.floor` for any division of `int`s otherwise the `number` type is a floating point and keeps the numbers after the dot. +- For `float`/`number` to `int` casting use `Math.trunc`, to replicate the same effect as Java casting does. ## Encoding From 9f073f9d01097aabed3b4818a0fe59da359ed786 Mon Sep 17 00:00:00 2001 From: Luiz Machado Date: Sun, 6 Dec 2020 22:50:47 -0300 Subject: [PATCH 09/19] working on overloads --- src/core/Result.ts | 111 ++++++++++++++++----- src/core/multi/MultipleBarcodeReader.ts | 2 +- src/core/multi/qrcode/QRCodeMultiReader.ts | 22 +++- src/core/oned/MultiFormatUPCEANReader.ts | 1 - src/core/oned/UPCAReader.ts | 2 +- src/core/pdf417/PDF417Reader.ts | 2 +- src/core/util/BarcodeFormaHelpers.ts | 6 ++ src/customTypings.ts | 1 + 8 files changed, 113 insertions(+), 34 deletions(-) create mode 100644 src/core/util/BarcodeFormaHelpers.ts diff --git a/src/core/Result.ts b/src/core/Result.ts index dcf568ff..19da1be2 100644 --- a/src/core/Result.ts +++ b/src/core/Result.ts @@ -22,6 +22,8 @@ import ResultPoint from './ResultPoint'; import BarcodeFormat from './BarcodeFormat'; import System from './util/System'; import ResultMetadataType from './ResultMetadataType'; +import { long } from 'src/customTypings'; +import { isBarcodeFormatValue } from './util/BarcodeFormaHelpers'; /** *

Encapsulates the result of decoding a barcode within an image.

@@ -31,48 +33,107 @@ import ResultMetadataType from './ResultMetadataType'; export default class Result { private resultMetadata: Map; + private numBits: number; + private resultPoints: ResultPoint[]; + private format: BarcodeFormat; - public static constructor4Args( + public constructor( + text: string, + rawBytes: Uint8Array, + resultPoints: ResultPoint[], + format: BarcodeFormat, + ); + public constructor( + text: string, + rawBytes: Uint8Array, + resultPoints: ResultPoint[], + format: BarcodeFormat, + timestamp: long, + ); + public constructor( + text: string, + rawBytes: Uint8Array, + numBits: number, + resultPoints: ResultPoint[], + format: BarcodeFormat, + timestamp: number + ); + public constructor( + private text: string, + private rawBytes: Uint8Array, + numBits_resultPoints: number | ResultPoint[], + resultPoints_format: ResultPoint[] | BarcodeFormat | any, + format_timestamp: BarcodeFormat | long | any = null, + private timestamp: long = System.currentTimeMillis() + ) { + // checks overloading order from most to least params + + // check overload 3 + if (numBits_resultPoints instanceof Number && Array.isArray(resultPoints_format) && isBarcodeFormatValue(format_timestamp)) { + numBits_resultPoints = rawBytes == null ? 0 : 8 * rawBytes.length; + this.constructor_Overload3(text, rawBytes, numBits_resultPoints, resultPoints_format, format_timestamp, timestamp); + return; + } + + // check overload 2 + if (Array.isArray(resultPoints_format) && isBarcodeFormatValue(format_timestamp)) { + this.constructor_Overload2(text, rawBytes, resultPoints_format, format_timestamp, timestamp); + return; + } + + // check overload 1 + if (typeof text === 'string' && rawBytes instanceof Uint8Array && Array.isArray(numBits_resultPoints) && isBarcodeFormatValue(resultPoints_format)) { + this.constructor_Overload1(text, rawBytes, numBits_resultPoints, resultPoints_format); + return; + } + + // throw no supported overload exception + throw new Error('No supported overload for the given combination of parameters.'); + } + + private constructor_Overload1( text: string, rawBytes: Uint8Array, resultPoints: ResultPoint[], format: BarcodeFormat, ) { - return Result.constructor5Args(text, rawBytes, resultPoints, format, System.currentTimeMillis()); + return this.constructor_Overload2(text, rawBytes, resultPoints, format, System.currentTimeMillis()); } - public static constructor5Args( + private constructor_Overload2( text: string, rawBytes: Uint8Array, resultPoints: ResultPoint[], format: BarcodeFormat, timestamp: number /* long */, ) { - return new Result(text, rawBytes, rawBytes == null ? 0 : 8 * rawBytes.length, + return this.constructor_Overload3(text, rawBytes, rawBytes == null ? 0 : 8 * rawBytes.length, resultPoints, format, timestamp); } - public constructor(private text: string, - private rawBytes: Uint8Array, - private numBits: number /*int*/ = rawBytes == null ? 0 : 8 * rawBytes.length, - private resultPoints: ResultPoint[], - private format: BarcodeFormat, - private timestamp: number /*long*/ = System.currentTimeMillis()) { - this.text = text; - this.rawBytes = rawBytes; - if (undefined === numBits || null === numBits) { - this.numBits = (rawBytes === null || rawBytes === undefined) ? 0 : 8 * rawBytes.length; - } else { - this.numBits = numBits; - } - this.resultPoints = resultPoints; - this.format = format; - this.resultMetadata = null; - if (undefined === timestamp || null === timestamp) { - this.timestamp = System.currentTimeMillis(); - } else { - this.timestamp = timestamp; - } + private constructor_Overload3( + text: string, + rawBytes: Uint8Array, + numBits: number, + resultPoints: ResultPoint[], + format: BarcodeFormat, + timestamp: number + ) { + this.text = text; + this.rawBytes = rawBytes; + if (undefined === numBits || null === numBits) { + this.numBits = (rawBytes === null || rawBytes === undefined) ? 0 : 8 * rawBytes.length; + } else { + this.numBits = numBits; + } + this.resultPoints = resultPoints; + this.format = format; + this.resultMetadata = null; + if (undefined === timestamp || null === timestamp) { + this.timestamp = System.currentTimeMillis(); + } else { + this.timestamp = timestamp; + } } /** diff --git a/src/core/multi/MultipleBarcodeReader.ts b/src/core/multi/MultipleBarcodeReader.ts index 2cd29115..b00811ef 100644 --- a/src/core/multi/MultipleBarcodeReader.ts +++ b/src/core/multi/MultipleBarcodeReader.ts @@ -38,7 +38,7 @@ export default /*public*/ interface MultipleBarcodeReader { * @throws NotFoundException * @override decodeMultiple */ - decodeMultipleWithoutHints(image: BinaryBitmap): Result[]; + decodeMultiple(image: BinaryBitmap): Result[]; /** * @throws NotFoundException diff --git a/src/core/multi/qrcode/QRCodeMultiReader.ts b/src/core/multi/qrcode/QRCodeMultiReader.ts index 2081c9a3..a87449b4 100644 --- a/src/core/multi/qrcode/QRCodeMultiReader.ts +++ b/src/core/multi/qrcode/QRCodeMultiReader.ts @@ -70,19 +70,31 @@ export default /*public final*/ class QRCodeMultiReader extends QRCodeReader imp private static /* final */ EMPTY_RESULT_ARRAY: Result[] = []; protected static /* final */ NO_POINTS = new Array(); + /** + * TYPESCRIPTPORT: this is an overloaded method so here it'll work only as a entrypoint for choosing which overload to call. + */ + public decodeMultiple(image: BinaryBitmap, hints: Map = null): Result[] { + + if (hints && hints instanceof Map) { + return this.decodeMultiple_Overload2(image, hints); + } + + return this.decodeMultiple_Overload1(image); + } + /** * @throws NotFoundException * @override decodeMultiple */ - public decodeMultipleWithoutHints(image: BinaryBitmap): Result[] { - return this.decodeMultiple(image, null); + public decodeMultiple_Overload1(image: BinaryBitmap): Result[] { + return this.decodeMultiple_Overload2(image, null); } /** * @override * @throws NotFoundException */ - public decodeMultiple(image: BinaryBitmap, hints: Map): Result[] { + public decodeMultiple_Overload2(image: BinaryBitmap, hints: Map): Result[] { let results: List = []; const detectorResults: DetectorResult[] = new MultiDetector(image.getBlackMatrix()).detectMulti(hints); for (const detectorResult of detectorResults) { @@ -93,7 +105,7 @@ export default /*public final*/ class QRCodeMultiReader extends QRCodeReader imp if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) { ( decoderResult.getOther()).applyMirroredCorrection(points); } - const result: Result = Result.constructor4Args(decoderResult.getText(), decoderResult.getRawBytes(), points, + const result: Result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE); const byteSegments: List = decoderResult.getByteSegments(); if (byteSegments != null) { @@ -159,7 +171,7 @@ export default /*public final*/ class QRCodeMultiReader extends QRCodeReader imp } } - const newResult: Result = Result.constructor4Args(newText.toString(), newRawBytes.toByteArray(), QRCodeMultiReader.NO_POINTS, BarcodeFormat.QR_CODE); + const newResult: Result = new Result(newText.toString(), newRawBytes.toByteArray(), QRCodeMultiReader.NO_POINTS, BarcodeFormat.QR_CODE); if (newByteSegment.size() > 0) { newResult.putMetadata(ResultMetadataType.BYTE_SEGMENTS, Collections.singletonList(newByteSegment.toByteArray())); } diff --git a/src/core/oned/MultiFormatUPCEANReader.ts b/src/core/oned/MultiFormatUPCEANReader.ts index 94455f5d..0f9d282f 100644 --- a/src/core/oned/MultiFormatUPCEANReader.ts +++ b/src/core/oned/MultiFormatUPCEANReader.ts @@ -100,7 +100,6 @@ export default class MultiFormatUPCEANReader extends OneDReader { const resultUPCA: Result = new Result( result.getText().substring(1), rawBytes, - rawBytes.length, result.getResultPoints(), BarcodeFormat.UPC_A ); diff --git a/src/core/oned/UPCAReader.ts b/src/core/oned/UPCAReader.ts index 49ea740d..01a23283 100644 --- a/src/core/oned/UPCAReader.ts +++ b/src/core/oned/UPCAReader.ts @@ -71,7 +71,7 @@ export default class UPCAReader extends UPCEANReader { public maybeReturnResult(result: Result) { let text = result.getText(); if (text.charAt(0) === '0') { - let upcaResult = new Result(text.substring(1), null, null, result.getResultPoints(), BarcodeFormat.UPC_A); + let upcaResult = new Result(text.substring(1), null, null, BarcodeFormat.UPC_A); if (result.getResultMetadata() != null) { upcaResult.putAllMetadata(result.getResultMetadata()); } diff --git a/src/core/pdf417/PDF417Reader.ts b/src/core/pdf417/PDF417Reader.ts index f63067c7..a0fa6842 100644 --- a/src/core/pdf417/PDF417Reader.ts +++ b/src/core/pdf417/PDF417Reader.ts @@ -125,7 +125,7 @@ export default /*public final*/ class PDF417Reader implements Reader, MultipleBa for (const points of detectorResult.getPoints()) { const decoderResult = PDF417ScanningDecoder.decode(detectorResult.getBits(), points[4], points[5], points[6], points[7], PDF417Reader.getMinCodewordWidth(points), PDF417Reader.getMaxCodewordWidth(points)); - const result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), undefined, points, BarcodeFormat.PDF_417); + const result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.PDF_417); result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, decoderResult.getECLevel()); const pdf417ResultMetadata: PDF417ResultMetadata = decoderResult.getOther(); if (pdf417ResultMetadata != null) { diff --git a/src/core/util/BarcodeFormaHelpers.ts b/src/core/util/BarcodeFormaHelpers.ts new file mode 100644 index 00000000..86293c3f --- /dev/null +++ b/src/core/util/BarcodeFormaHelpers.ts @@ -0,0 +1,6 @@ +import BarcodeFormat from '../BarcodeFormat'; + +export function isBarcodeFormatValue(num: number) { + const values = Object.keys(BarcodeFormat).map(i => Number(i)).filter(Number.isInteger); + return values.includes(num); +} diff --git a/src/customTypings.ts b/src/customTypings.ts index 9e1df3fe..b3765644 100644 --- a/src/customTypings.ts +++ b/src/customTypings.ts @@ -11,6 +11,7 @@ export declare type byte = number; export declare type short = number; export declare type int = number; +export declare type long = number; export declare type float = number; export declare type double = number; From 384c84ab7467309ac81bf82ed1739a1b1796274c Mon Sep 17 00:00:00 2001 From: Luiz Filipe Machado Barni Date: Mon, 7 Dec 2020 10:31:37 -0300 Subject: [PATCH 10/19] test: fix overload usage --- src/test/core/multi/qrcode/MultiQRCode.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/core/multi/qrcode/MultiQRCode.spec.ts b/src/test/core/multi/qrcode/MultiQRCode.spec.ts index 2608aec9..40a4b5a3 100644 --- a/src/test/core/multi/qrcode/MultiQRCode.spec.ts +++ b/src/test/core/multi/qrcode/MultiQRCode.spec.ts @@ -70,7 +70,7 @@ describe('MultiQRCodeTestCase', () => { const bitmap: BinaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); const reader: MultipleBarcodeReader = new QRCodeMultiReader(); - const results: Result[] = reader.decodeMultipleWithoutHints(bitmap); + const results: Result[] = reader.decodeMultiple(bitmap); assertNotNull(results); assertEquals(4, results.length); @@ -90,9 +90,9 @@ describe('MultiQRCodeTestCase', () => { }); it('testProcessStructuredAppend', () => { - const sa1: Result = Result.constructor4Args('SA1', new Uint8Array(0), [], BarcodeFormat.QR_CODE); - const sa2: Result = Result.constructor4Args('SA2', new Uint8Array(0), [], BarcodeFormat.QR_CODE); - const sa3: Result = Result.constructor4Args('SA3', new Uint8Array(0), [], BarcodeFormat.QR_CODE); + const sa1: Result = new Result('SA1', new Uint8Array(0), [], BarcodeFormat.QR_CODE); + const sa2: Result = new Result('SA2', new Uint8Array(0), [], BarcodeFormat.QR_CODE); + const sa3: Result = new Result('SA3', new Uint8Array(0), [], BarcodeFormat.QR_CODE); sa1.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, 2); sa1.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, 'L'); sa2.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, (1 << 4) + 2); @@ -100,7 +100,7 @@ describe('MultiQRCodeTestCase', () => { sa3.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, (2 << 4) + 2); sa3.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, 'L'); - const nsa: Result = Result.constructor4Args('NotSA', new Uint8Array(0), [], BarcodeFormat.QR_CODE); + const nsa: Result = new Result('NotSA', new Uint8Array(0), [], BarcodeFormat.QR_CODE); nsa.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, 'L'); const inputs: List = Arrays.asList(sa3, sa1, nsa, sa2); From f5212ac87138eeb5ab3d34187547943fb185699d Mon Sep 17 00:00:00 2001 From: Luiz Filipe Machado Barni Date: Tue, 22 Dec 2020 20:22:42 -0300 Subject: [PATCH 11/19] fix implementation and overload names --- src/core/Result.ts | 16 ++++++++-------- src/core/multi/qrcode/QRCodeMultiReader.ts | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/core/Result.ts b/src/core/Result.ts index 19da1be2..2399a0b3 100644 --- a/src/core/Result.ts +++ b/src/core/Result.ts @@ -71,19 +71,19 @@ export default class Result { // check overload 3 if (numBits_resultPoints instanceof Number && Array.isArray(resultPoints_format) && isBarcodeFormatValue(format_timestamp)) { numBits_resultPoints = rawBytes == null ? 0 : 8 * rawBytes.length; - this.constructor_Overload3(text, rawBytes, numBits_resultPoints, resultPoints_format, format_timestamp, timestamp); + this.constructorImpl(text, rawBytes, numBits_resultPoints, resultPoints_format, format_timestamp, timestamp); return; } // check overload 2 if (Array.isArray(resultPoints_format) && isBarcodeFormatValue(format_timestamp)) { - this.constructor_Overload2(text, rawBytes, resultPoints_format, format_timestamp, timestamp); + this.constructorOverload2(text, rawBytes, resultPoints_format, format_timestamp, timestamp); return; } // check overload 1 if (typeof text === 'string' && rawBytes instanceof Uint8Array && Array.isArray(numBits_resultPoints) && isBarcodeFormatValue(resultPoints_format)) { - this.constructor_Overload1(text, rawBytes, numBits_resultPoints, resultPoints_format); + this.constructorOverload1(text, rawBytes, numBits_resultPoints, resultPoints_format); return; } @@ -91,27 +91,27 @@ export default class Result { throw new Error('No supported overload for the given combination of parameters.'); } - private constructor_Overload1( + private constructorOverload1( text: string, rawBytes: Uint8Array, resultPoints: ResultPoint[], format: BarcodeFormat, ) { - return this.constructor_Overload2(text, rawBytes, resultPoints, format, System.currentTimeMillis()); + return this.constructorOverload2(text, rawBytes, resultPoints, format, System.currentTimeMillis()); } - private constructor_Overload2( + private constructorOverload2( text: string, rawBytes: Uint8Array, resultPoints: ResultPoint[], format: BarcodeFormat, timestamp: number /* long */, ) { - return this.constructor_Overload3(text, rawBytes, rawBytes == null ? 0 : 8 * rawBytes.length, + return this.constructorImpl(text, rawBytes, rawBytes == null ? 0 : 8 * rawBytes.length, resultPoints, format, timestamp); } - private constructor_Overload3( + private constructorImpl( text: string, rawBytes: Uint8Array, numBits: number, diff --git a/src/core/multi/qrcode/QRCodeMultiReader.ts b/src/core/multi/qrcode/QRCodeMultiReader.ts index cc7f58a8..c7f72878 100644 --- a/src/core/multi/qrcode/QRCodeMultiReader.ts +++ b/src/core/multi/qrcode/QRCodeMultiReader.ts @@ -76,25 +76,25 @@ export default /*public final*/ class QRCodeMultiReader extends QRCodeReader imp public decodeMultiple(image: BinaryBitmap, hints: Map = null): Result[] { if (hints && hints instanceof Map) { - return this.decodeMultiple_Overload2(image, hints); + return this.decodeMultipleImpl(image, hints); } - return this.decodeMultiple_Overload1(image); + return this.decodeMultipleOverload1(image); } /** * @throws NotFoundException * @override decodeMultiple */ - public decodeMultiple_Overload1(image: BinaryBitmap): Result[] { - return this.decodeMultiple_Overload2(image, null); + public decodeMultipleOverload1(image: BinaryBitmap): Result[] { + return this.decodeMultipleImpl(image, null); } /** * @override * @throws NotFoundException */ - public decodeMultiple_Overload2(image: BinaryBitmap, hints: Map): Result[] { + public decodeMultipleImpl(image: BinaryBitmap, hints: Map): Result[] { let results: List = []; const detectorResults: DetectorResult[] = new MultiDetector(image.getBlackMatrix()).detectMulti(hints); for (const detectorResult of detectorResults) { From ebd9756c5e862974d83623e01088c4c68f6f59e9 Mon Sep 17 00:00:00 2001 From: Luiz Filipe Machado Barni Date: Tue, 22 Dec 2020 20:23:17 -0300 Subject: [PATCH 12/19] fix spacing in func declarations --- src/core/multi/qrcode/detector/MultiDetector.ts | 2 +- src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts | 2 +- src/core/pdf417/decoder/Codeword.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/multi/qrcode/detector/MultiDetector.ts b/src/core/multi/qrcode/detector/MultiDetector.ts index 96d90d11..ec675f29 100644 --- a/src/core/multi/qrcode/detector/MultiDetector.ts +++ b/src/core/multi/qrcode/detector/MultiDetector.ts @@ -56,7 +56,7 @@ export default /* public final */ class MultiDetector extends Detector { } /** @throws NotFoundException */ - public detectMulti( hints: Map): DetectorResult[] { + public detectMulti(hints: Map): DetectorResult[] { const image: BitMatrix = this.getImage(); const resultPointCallback: ResultPointCallback = hints == null ? null : hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); diff --git a/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts b/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts index 697d70a4..d7d6d072 100644 --- a/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts +++ b/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts @@ -97,7 +97,7 @@ export default /* public final */ class MultiFinderPatternFinder extends FinderP * size differs from the average among those patterns the least * @throws NotFoundException if 3 such finder patterns do not exist */ - private selectMultipleBestPatterns(): FinderPattern[][] { + private selectMultipleBestPatterns(): FinderPattern[][] { const possibleCenters: List = this.getPossibleCenters(); const size: int = possibleCenters.length; diff --git a/src/core/pdf417/decoder/Codeword.ts b/src/core/pdf417/decoder/Codeword.ts index 6b2118fa..16d27ad9 100644 --- a/src/core/pdf417/decoder/Codeword.ts +++ b/src/core/pdf417/decoder/Codeword.ts @@ -78,8 +78,8 @@ export default /*final*/ class Codeword { this.rowNumber = rowNumber; } -// @Override - public toString(): string { + // @Override + public toString(): string { return this.rowNumber + '|' + this.value; } From 6efcdca4960b1549289db5e99b1f044b0972233f Mon Sep 17 00:00:00 2001 From: Luiz Filipe Machado Barni Date: Tue, 22 Dec 2020 21:15:48 -0300 Subject: [PATCH 13/19] implemented overloading pattern --- src/core/multi/qrcode/QRCodeMultiReader.ts | 4 +-- src/core/pdf417/PDF417Reader.ts | 27 ++++++++++++++++--- src/core/qrcode/QRCodeReader.ts | 31 +++++++++++++++++++--- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/core/multi/qrcode/QRCodeMultiReader.ts b/src/core/multi/qrcode/QRCodeMultiReader.ts index c7f72878..0cf9b81b 100644 --- a/src/core/multi/qrcode/QRCodeMultiReader.ts +++ b/src/core/multi/qrcode/QRCodeMultiReader.ts @@ -86,7 +86,7 @@ export default /*public final*/ class QRCodeMultiReader extends QRCodeReader imp * @throws NotFoundException * @override decodeMultiple */ - public decodeMultipleOverload1(image: BinaryBitmap): Result[] { + private decodeMultipleOverload1(image: BinaryBitmap): Result[] { return this.decodeMultipleImpl(image, null); } @@ -94,7 +94,7 @@ export default /*public final*/ class QRCodeMultiReader extends QRCodeReader imp * @override * @throws NotFoundException */ - public decodeMultipleImpl(image: BinaryBitmap, hints: Map): Result[] { + private decodeMultipleImpl(image: BinaryBitmap, hints: Map): Result[] { let results: List = []; const detectorResults: DetectorResult[] = new MultiDetector(image.getBlackMatrix()).detectMulti(hints); for (const detectorResult of detectorResults) { diff --git a/src/core/pdf417/PDF417Reader.ts b/src/core/pdf417/PDF417Reader.ts index a0fa6842..3c4ca6e1 100644 --- a/src/core/pdf417/PDF417Reader.ts +++ b/src/core/pdf417/PDF417Reader.ts @@ -86,8 +86,29 @@ export default /*public final*/ class PDF417Reader implements Reader, MultipleBa * * @override decodeMultiple */ - public decodeMultipleWithoutHints(image: BinaryBitmap): Result[] { - return this.decodeMultiple(image, null); + public decodeMultiple(image: BinaryBitmap): Result[]; + /** + * + * @param BinaryBitmap + * @param image + * @throws NotFoundException + * @override + */ + public decodeMultiple(image: BinaryBitmap, hints: Map = null): Result[] { + + if (!hints) { + return this.decodeMultipleOverload1(image); + } + + return this.decodeMultipleImpl(image, hints); + } + + /** + * + * @override decodeMultiple + */ + private decodeMultipleOverload1(image: BinaryBitmap): Result[] { + return this.decodeMultipleImpl(image, null); } /** @@ -97,7 +118,7 @@ export default /*public final*/ class PDF417Reader implements Reader, MultipleBa * @throws NotFoundException * @override */ - public decodeMultiple(image: BinaryBitmap, hints: Map = null): Result[] { + private decodeMultipleImpl(image: BinaryBitmap, hints: Map = null): Result[] { try { return PDF417Reader.decode(image, hints, true); } catch (ignored) { diff --git a/src/core/qrcode/QRCodeReader.ts b/src/core/qrcode/QRCodeReader.ts index 5a552af7..a324598d 100644 --- a/src/core/qrcode/QRCodeReader.ts +++ b/src/core/qrcode/QRCodeReader.ts @@ -58,12 +58,35 @@ export default class QRCodeReader implements Reader { * @throws FormatException if a QR code cannot be decoded * @throws ChecksumException if error correction fails */ - public decodeWithoutHints(image: BinaryBitmap): Result /*throws NotFoundException, ChecksumException, FormatException */ { - return this.decode(image, null); + public decode(image: BinaryBitmap): Result /*throws NotFoundException, ChecksumException, FormatException */; + /** + * @override + */ + public decode(image: BinaryBitmap, hints?: Map): Result { + + if (!hints) { + this.decodeOverload1(image); + } + + return this.decodeImpl(image, hints); } - /*@Override*/ - public decode(image: BinaryBitmap, hints?: Map): Result { + /** + * Locates and decodes a QR code in an image. + * + * @return a representing: string the content encoded by the QR code + * @throws NotFoundException if a QR code cannot be found + * @throws FormatException if a QR code cannot be decoded + * @throws ChecksumException if error correction fails + */ + public decodeOverload1(image: BinaryBitmap): Result /*throws NotFoundException, ChecksumException, FormatException */ { + return this.decodeImpl(image, null); + } + + /** + * @override + */ + public decodeImpl(image: BinaryBitmap, hints?: Map): Result { let decoderResult: DecoderResult; let points: Array; if (hints !== undefined && hints !== null && undefined !== hints.get(DecodeHintType.PURE_BARCODE)) { From 9e92e55cdb5cbf229d994da00775eefd248d79fa Mon Sep 17 00:00:00 2001 From: Luiz Filipe Machado Barni Date: Wed, 23 Dec 2020 14:06:50 -0300 Subject: [PATCH 14/19] fix general build/test errors --- src/core/MultiFormatReader.ts | 5 +---- src/core/Reader.ts | 2 +- src/core/Result.ts | 2 +- .../multi/qrcode/detector/MultiDetector.ts | 20 ++++++++--------- .../detector/MultiFinderPatternFinder.ts | 22 +++++++++---------- .../rss/expanded/decoders/createDecoder.ts | 3 ++- .../qrcode/detector/FinderPatternFinder.ts | 20 ++++++++--------- src/core/util/StringBuilder.ts | 1 - src/index.ts | 5 +++++ .../core/multi/qrcode/MultiQRCode.spec.ts | 4 ++-- .../core/oned/rss/expanded/BinaryUtil.spec.ts | 2 +- src/test/core/oned/rss/expanded/BinaryUtil.ts | 7 ++++-- .../ExpandedInformationDecoder.spec.ts | 6 ++--- .../rss/expanded/RSSExpandedBlackBox1.spec.ts | 7 +++--- .../rss/expanded/RSSExpandedBlackBox2.spec.ts | 7 +++--- .../rss/expanded/RSSExpandedBlackBox3.spec.ts | 7 +++--- .../RSSExpandedStackedBlackBox1.spec.ts | 6 ++--- .../RSSExpandedStackedBlackBox2.spec.ts | 7 +++--- .../core/oned/rss/expanded/TestCaseUtil.ts | 8 +++---- 19 files changed, 70 insertions(+), 71 deletions(-) diff --git a/src/core/MultiFormatReader.ts b/src/core/MultiFormatReader.ts index 3b0c1de4..b91ca7e7 100644 --- a/src/core/MultiFormatReader.ts +++ b/src/core/MultiFormatReader.ts @@ -54,10 +54,7 @@ export default class MultiFormatReader implements Reader { * @throws NotFoundException Any errors which occurred */ /*@Override*/ - // public decode(image: BinaryBitmap): Result { - // setHints(null) - // return decodeInternal(image) - // } + public decode(image: BinaryBitmap): Result; /** * Decode an image using the hints provided. Does not honor existing state. diff --git a/src/core/Reader.ts b/src/core/Reader.ts index 30ed4d4b..bef8bfab 100644 --- a/src/core/Reader.ts +++ b/src/core/Reader.ts @@ -48,7 +48,7 @@ interface Reader { * @throws FormatException if a potential barcode is found but format is invalid * @override decode */ - // decodeWithoutHints(image: BinaryBitmap): Result; + decode(image: BinaryBitmap): Result; /** * Locates and decodes a barcode in some format within an image. This method also accepts diff --git a/src/core/Result.ts b/src/core/Result.ts index 2399a0b3..f05c3ea7 100644 --- a/src/core/Result.ts +++ b/src/core/Result.ts @@ -22,7 +22,7 @@ import ResultPoint from './ResultPoint'; import BarcodeFormat from './BarcodeFormat'; import System from './util/System'; import ResultMetadataType from './ResultMetadataType'; -import { long } from 'src/customTypings'; +import { long } from '../customTypings'; import { isBarcodeFormatValue } from './util/BarcodeFormaHelpers'; /** diff --git a/src/core/multi/qrcode/detector/MultiDetector.ts b/src/core/multi/qrcode/detector/MultiDetector.ts index ec675f29..f669a4af 100644 --- a/src/core/multi/qrcode/detector/MultiDetector.ts +++ b/src/core/multi/qrcode/detector/MultiDetector.ts @@ -14,16 +14,16 @@ * limitations under the License. */ -import BitMatrix from "src/core/common/BitMatrix"; -import DetectorResult from "src/core/common/DetectorResult"; -import DecodeHintType from "src/core/DecodeHintType"; -import NotFoundException from "src/core/NotFoundException"; -import Detector from "src/core/qrcode/detector/Detector"; -import FinderPatternInfo from "src/core/qrcode/detector/FinderPatternInfo"; -import ReaderException from "src/core/ReaderException"; -import ResultPointCallback from "src/core/ResultPointCallback"; -import { List } from "src/customTypings"; -import MultiFinderPatternFinder from "./MultiFinderPatternFinder"; +import BitMatrix from '../../../common/BitMatrix'; +import DetectorResult from '../../../common/DetectorResult'; +import DecodeHintType from '../../../DecodeHintType'; +import NotFoundException from '../../../NotFoundException'; +import Detector from '../../../qrcode/detector/Detector'; +import FinderPatternInfo from '../../../qrcode/detector/FinderPatternInfo'; +import ReaderException from '../../../ReaderException'; +import ResultPointCallback from '../../../ResultPointCallback'; +import { List } from '../../../../customTypings'; +import MultiFinderPatternFinder from './MultiFinderPatternFinder'; // package com.google.zxing.multi.qrcode.detector; diff --git a/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts b/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts index d7d6d072..6d6dbba9 100644 --- a/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts +++ b/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts @@ -14,17 +14,17 @@ * limitations under the License. */ -import BitMatrix from 'src/core/common/BitMatrix'; -import DecodeHintType from 'src/core/DecodeHintType'; -import NotFoundException from 'src/core/NotFoundException'; -import FinderPattern from 'src/core/qrcode/detector/FinderPattern'; -import FinderPatternFinder from 'src/core/qrcode/detector/FinderPatternFinder'; -import FinderPatternInfo from 'src/core/qrcode/detector/FinderPatternInfo'; -import ResultPoint from 'src/core/ResultPoint'; -import ResultPointCallback from 'src/core/ResultPointCallback'; -import Collections from 'src/core/util/Collections'; -import Comparator from 'src/core/util/Comparator'; -import { double, float, int, List } from 'src/customTypings'; +import BitMatrix from '../../../common/BitMatrix'; +import DecodeHintType from '../../../DecodeHintType'; +import NotFoundException from '../../../NotFoundException'; +import FinderPattern from '../../../qrcode/detector/FinderPattern'; +import FinderPatternFinder from '../../../qrcode/detector/FinderPatternFinder'; +import FinderPatternInfo from '../../../qrcode/detector/FinderPatternInfo'; +import ResultPoint from '../../../ResultPoint'; +import ResultPointCallback from '../../../ResultPointCallback'; +import Collections from '../../../util/Collections'; +import Comparator from '../../../util/Comparator'; +import { double, float, int, List } from '../../../../customTypings'; // package com.google.zxing.multi.qrcode.detector; diff --git a/src/core/oned/rss/expanded/decoders/createDecoder.ts b/src/core/oned/rss/expanded/decoders/createDecoder.ts index 2c0efc4c..27965777 100644 --- a/src/core/oned/rss/expanded/decoders/createDecoder.ts +++ b/src/core/oned/rss/expanded/decoders/createDecoder.ts @@ -1,4 +1,5 @@ -import { BitArray, IllegalStateException } from '../../../../..'; +import BitArray from '../../../../common/BitArray'; +import IllegalStateException from '../../../../IllegalStateException'; import AbstractExpandedDecoder from './AbstractExpandedDecoder'; import AI013103decoder from './AI013103decoder'; import AI01320xDecoder from './AI01320xDecoder'; diff --git a/src/core/qrcode/detector/FinderPatternFinder.ts b/src/core/qrcode/detector/FinderPatternFinder.ts index 54c2a2c2..c987a1c9 100644 --- a/src/core/qrcode/detector/FinderPatternFinder.ts +++ b/src/core/qrcode/detector/FinderPatternFinder.ts @@ -14,16 +14,16 @@ * limitations under the License. */ -import BitMatrix from 'src/core/common/BitMatrix'; -import DecodeHintType from 'src/core/DecodeHintType'; -import NotFoundException from 'src/core/NotFoundException'; -import ResultPoint from 'src/core/ResultPoint'; -import ResultPointCallback from 'src/core/ResultPointCallback'; -import Arrays from 'src/core/util/Arrays'; -import Comparator from 'src/core/util/Comparator'; -import Double from 'src/core/util/Double'; -import Float from 'src/core/util/Float'; -import { double, float, int, List } from 'src/customTypings'; +import BitMatrix from '../../common/BitMatrix'; +import DecodeHintType from '../../DecodeHintType'; +import NotFoundException from '../../NotFoundException'; +import ResultPoint from '../../ResultPoint'; +import ResultPointCallback from '../../ResultPointCallback'; +import Arrays from '../../util/Arrays'; +import Comparator from '../../util/Comparator'; +import Double from '../../util/Double'; +import Float from '../../util/Float'; +import { double, float, int, List } from '../../../customTypings'; import FinderPattern from './FinderPattern'; import FinderPatternInfo from './FinderPatternInfo'; diff --git a/src/core/util/StringBuilder.ts b/src/core/util/StringBuilder.ts index 1d4f34df..58b085cd 100644 --- a/src/core/util/StringBuilder.ts +++ b/src/core/util/StringBuilder.ts @@ -1,5 +1,4 @@ import CharacterSetECI from '../common/CharacterSetECI'; -import StringEncoding from './StringEncoding'; import { int, char } from '../../customTypings'; import StringUtils from '../common/StringUtils'; diff --git a/src/index.ts b/src/index.ts index 11f7f4b9..b9986300 100644 --- a/src/index.ts +++ b/src/index.ts @@ -118,6 +118,11 @@ export { default as RSSExpandedReader } from './core/oned/rss/expanded/RSSExpand export { default as MultiFormatOneDReader } from './core/oned/MultiFormatOneDReader'; +// core/oned/rss/expanded +export { default as AbstractExpandedDecoder } from './core/oned/rss/expanded/decoders/AbstractExpandedDecoder'; +export { default as createDecoder } from './core/oned/rss/expanded/decoders/createDecoder'; + + // core/multi export { default as MultipleBarcodeReader } from './core/multi/MultipleBarcodeReader'; diff --git a/src/test/core/multi/qrcode/MultiQRCode.spec.ts b/src/test/core/multi/qrcode/MultiQRCode.spec.ts index 40a4b5a3..612a60ab 100644 --- a/src/test/core/multi/qrcode/MultiQRCode.spec.ts +++ b/src/test/core/multi/qrcode/MultiQRCode.spec.ts @@ -25,8 +25,8 @@ import { ResultMetadataType } from '@zxing/library'; import * as path from 'path'; -import Arrays from 'src/core/util/Arrays'; -import { Collection, List } from 'src/customTypings'; +import Arrays from '../../../../core/util/Arrays'; +import { Collection, List } from '../../../../customTypings'; import AbstractBlackBoxSpec from '../../common/AbstractBlackBox'; import SharpImageLuminanceSource from '../../SharpImageLuminanceSource'; import { assertArrayEquals, assertEquals, assertNotNull } from '../../util/AssertUtils'; diff --git a/src/test/core/oned/rss/expanded/BinaryUtil.spec.ts b/src/test/core/oned/rss/expanded/BinaryUtil.spec.ts index 760f5c08..69ebfd4b 100644 --- a/src/test/core/oned/rss/expanded/BinaryUtil.spec.ts +++ b/src/test/core/oned/rss/expanded/BinaryUtil.spec.ts @@ -1,4 +1,4 @@ -import BitArray from '../../../../../core/common/BitArray'; +import { BitArray } from '@zxing/library'; import { assertEquals } from '../../../util/AssertUtils'; import BinaryUtil from './BinaryUtil'; diff --git a/src/test/core/oned/rss/expanded/BinaryUtil.ts b/src/test/core/oned/rss/expanded/BinaryUtil.ts index 66b21924..04d38a24 100644 --- a/src/test/core/oned/rss/expanded/BinaryUtil.ts +++ b/src/test/core/oned/rss/expanded/BinaryUtil.ts @@ -1,7 +1,10 @@ -import { BitArray } from '../../../../..'; -import IllegalStateException from '../../../../../core/IllegalStateException'; +import { + BitArray, + IllegalStateException +} from '@zxing/library'; import StringBuilder from '../../../../../core/util/StringBuilder'; + /* * Copyright (C) 2010 ZXing authors * diff --git a/src/test/core/oned/rss/expanded/ExpandedInformationDecoder.spec.ts b/src/test/core/oned/rss/expanded/ExpandedInformationDecoder.spec.ts index 373e7fd8..a99c4d57 100644 --- a/src/test/core/oned/rss/expanded/ExpandedInformationDecoder.spec.ts +++ b/src/test/core/oned/rss/expanded/ExpandedInformationDecoder.spec.ts @@ -1,10 +1,8 @@ -import { BitArray } from '../../../../..'; -import AbstractExpandedDecoder from '../../../../../core/oned/rss/expanded/decoders/AbstractExpandedDecoder'; +import { BitArray, AbstractExpandedDecoder, createDecoder } from '@zxing/library'; import { assertEquals } from '../../../util/AssertUtils'; import BinaryUtil from './BinaryUtil'; - /* * Copyright (C) 2010 ZXing authors * @@ -48,7 +46,7 @@ it('ExpandedInformationDecoderTest', () => { it('testNoAi', () => { let information: BitArray = BinaryUtil.buildBitArrayFromString(' .......X ..XX..X. X.X....X .......X ....'); - let decoder: AbstractExpandedDecoder = AbstractExpandedDecoder.createDecoder(information); + let decoder: AbstractExpandedDecoder = createDecoder(information); let decoded: String = decoder.parseInformation(); assertEquals('(10)12A', decoded); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox1.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox1.spec.ts index f69457a9..2c4d0283 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox1.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox1.spec.ts @@ -26,8 +26,7 @@ // package com.google.zxing.oned; -import BarcodeFormat from '../../../../../core/BarcodeFormat'; -import MultiFormatReader from '../../../../../core/MultiFormatReader'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /** @@ -43,8 +42,8 @@ class RSSExpandedBlackBox1Spec extends AbstractBlackBoxSpec { } describe('RSSExpandedBlackBox1Spec', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedBlackBox1Spec(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox2.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox2.spec.ts index 75a470d9..3cae643c 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox2.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox2.spec.ts @@ -26,8 +26,7 @@ // package com.google.zxing.oned; -import BarcodeFormat from '../../../../../core/BarcodeFormat'; -import MultiFormatReader from '../../../../../core/MultiFormatReader'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /** @@ -43,9 +42,9 @@ class RSSExpandedBlackBox2TestCase extends AbstractBlackBoxSpec { } describe('RSSExpandedBlackBox2TestCase', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedBlackBox2TestCase(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox3.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox3.spec.ts index 42c60c91..0ff4fa4e 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox3.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox3.spec.ts @@ -26,8 +26,7 @@ // package com.google.zxing.oned; -import BarcodeFormat from '../../../../../core/BarcodeFormat'; -import MultiFormatReader from '../../../../../core/MultiFormatReader'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /** @@ -43,8 +42,8 @@ class RSSExpandedBlackBox3TestCase extends AbstractBlackBoxSpec { } describe('RSSExpandedBlackBox3TestCase', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedBlackBox3TestCase(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox1.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox1.spec.ts index fc92f186..2e24e846 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox1.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox1.spec.ts @@ -1,4 +1,4 @@ -import { BarcodeFormat, MultiFormatReader } from '../../../../..'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /* @@ -48,9 +48,9 @@ class RSSExpandedStackedBlackBox1TestCase extends AbstractBlackBoxSpec { } describe('RSSExpandedStackedBlackBox1TestCase', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedStackedBlackBox1TestCase(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox2.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox2.spec.ts index 755350f9..9bf4fb8b 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox2.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox2.spec.ts @@ -1,5 +1,4 @@ -import { BarcodeFormat } from '../../../../..'; -import MultiFormatReader from '../../../../../core/MultiFormatReader'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /* @@ -49,9 +48,9 @@ class RSSExpandedStackedBlackBox2TestCase extends AbstractBlackBoxSpec { } describe('RSSExpandedStackedBlackBox2TestCase', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedStackedBlackBox2TestCase(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/TestCaseUtil.ts b/src/test/core/oned/rss/expanded/TestCaseUtil.ts index 96d28138..0a596030 100644 --- a/src/test/core/oned/rss/expanded/TestCaseUtil.ts +++ b/src/test/core/oned/rss/expanded/TestCaseUtil.ts @@ -1,7 +1,7 @@ -import { BinaryBitmap, GlobalHistogramBinarizer } from "../../../../.."; -import AbstractBlackBoxSpec from "../../../common/AbstractBlackBox"; -import SharpImageLuminanceSource from "../../../SharpImageLuminanceSource"; -import SharpImage from "../../../util/SharpImage"; +import { BinaryBitmap, GlobalHistogramBinarizer } from '@zxing/library'; +import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; +import SharpImageLuminanceSource from '../../../SharpImageLuminanceSource'; +import SharpImage from '../../../util/SharpImage'; /* * Copyright (C) 2012 ZXing authors From e5aac7cb43784f5980fe1aaf4ed051100f5c008a Mon Sep 17 00:00:00 2001 From: Luiz Filipe Machado Barni Date: Sat, 10 Apr 2021 21:17:47 -0300 Subject: [PATCH 15/19] updates and added info about porting overloads --- CONTRIBUTING.md | 93 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index df1b6ace..c2aaae77 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ Initial port from 3.3.1-SNAPSHOT on May 2017 by Adrian Toșcă (@aleris). ### Approach -The Java files are transformed using regexps for some obvious syntax transformation (see ./autotransform) and then modified manually. +The Java files are transformed using RegExps for some obvious syntax transformation, see `` for a starting point. Using http://www.jsweet.org was considered but rejected because of loosing type information early on (for example number versus int is essential for bitwise operations), language style and older TypeScript version. @@ -50,6 +50,84 @@ number versus int is essential for bitwise operations), language style and older | `byte[]` | `Uint8ClampedArray` | | `int[]` | `Int32Array` | +### Java numbers to TS numbers + +- Take care of `int` -> `number` (integer to number) port when doing bitwise transformation especially `<<`. Do a `& 0xFFFFFFFF` for ints, a &0xFF for bytes. +- Take care of array initialization, in Java `new Array(N)` initializes capacity NOT size/length. +- Use `Math.floor` for any division of `int`s otherwise the `number` type is a floating point and keeps the numbers after the dot. +- For `float`/`number` to `int` casting use `Math.trunc`, to replicate the same effect as Java casting does. + +### Porting overloads + +> [but don't rewrite JavaScript to be Java](https://github.com/zxing-js/library/pull/376#commitcomment-44928885) + +Strong words and you should agree, so we're in favor of mixing implementations using a prefered order: + +1. Don't implement overloading if not needed but document it. +2. Missing argument handling and then calling a (one) implementation method (this is easily preferable than 3 bellow). +3. Missing argument handling and calling vastly (multiple) different implementations as the arguments matches. + +All this in favor of keeping the interfaces similar to Java and the code as close as possible for porting and debugging. Both should be very well commented in the code so they explain why they're there and what they're doing. + +> [Most of the contributors to this library will most likely have a JavaScript background rather than Java.](https://github.com/zxing-js/library/pull/376#commitcomment-44928885) + +Yeah but most will have to have a very good understanding of both languages so they can port the `core` and porting is terrible hard when code doesn't matches. For new modules **not based** in the Java version we're **against** the use of overloading pattern, JavaScript simply doesn't fits it well and should be avoided in here. + +> You can find more on this discussion in [this Pull Request](https://github.com/zxing-js/library/pull/376). + +#### Examples + +Based on the rules set above, this is where we land, first with a simpler yet effective approach: + +```typescript +constructor(arg1: any); +constructor(arg1: any, arg2: any); +constructor(arg1: any, arg2: any, arg3: any); +constructor(arg1: any, arg2?: any, arg3?: any) { + if (arg2 == null) arg2 = {}; + if (arg3 == null) arg3 = {}; + return constructorImpl(arg1, arg2, arg3) +} + +constructorImpl(arg1: any, arg2: any, arg3: any) { + /* Implementation code */ +} +``` + +And less preferred if more advanced logic needed: + +```typescript +constructor(arg1: any); +constructor(arg1: any, arg2: any); +constructor(arg1: any, arg2: any, arg3: any); +constructor(arg1: any, arg2?: any, arg3?: any) { + if (arg3 != null) return constructorImpl(arg1, arg2, arg3); + if (arg2 != null) return constructorOverload2(arg1, arg2); + return constructorOverload1(arg1) +} + +private constructorOverload1( + arg1: any, +) { + return this.constructorOverload2(arg1, {}); +} + +private constructorOverload2( + arg1: any, + arg2: any, +) { + return this.constructorImpl(arg1, arg2, {}); +} + +private constructorImpl( + arg1: any, + arg2: any, + arg3: any, +) { + /* Implementation code */ +} +``` + ## Types ### Java types @@ -67,14 +145,7 @@ https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html ### JavaScript's TypedArray -https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray - -## Things to look for - -- Take care of `int` -> `number` (integer to number) port when doing bitwise transformation especially `<<`. Do a `& 0xFFFFFFFF` for ints, a &0xFF for bytes. -- Take care of array initialization, in Java `new Array(N)` initializes capacity NOT size/length. -- Use `Math.floor` for any division of `int`s otherwise the `number` type is a floating point and keeps the numbers after the dot. -- For `float`/`number` to `int` casting use `Math.trunc`, to replicate the same effect as Java casting does. +Read about JavaScript TypedArray [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray). ## Encoding @@ -91,8 +162,8 @@ Will became: `StringEncoding.decode(, encoding)`. - `common/AbstractBlackBoxTestCase.java` - `Cp437` not supported by TextEncoding library see `DecodedBitStreamParserTestCase`. - Replace `instanceof` with something more robust. -- Simplify double `null !== && undefined !== ` checks. +- Simplify double ` !== null && !== undefined` checks. ----- +--- Most of things here are opinions and were written by the first porter, please feel free to discuss and help us to make it better. From 49a0363adffc1d0010606e8a519c516d459a13a8 Mon Sep 17 00:00:00 2001 From: Luiz Filipe Machado Barni Date: Sat, 10 Apr 2021 22:11:19 -0300 Subject: [PATCH 16/19] fixed tests build --- src/index.ts | 2 ++ src/test/core/common/AbstractBlackBox.ts | 26 +++++++++++-------- .../core/oned/rss/expanded/BinaryUtil.spec.ts | 2 +- src/test/core/oned/rss/expanded/BinaryUtil.ts | 5 ++-- .../ExpandedInformationDecoder.spec.ts | 13 ++++------ .../rss/expanded/RSSExpandedBlackBox1.spec.ts | 7 +++-- .../rss/expanded/RSSExpandedBlackBox2.spec.ts | 7 +++-- .../rss/expanded/RSSExpandedBlackBox3.spec.ts | 7 +++-- .../RSSExpandedStackedBlackBox1.spec.ts | 6 ++--- .../RSSExpandedStackedBlackBox2.spec.ts | 7 +++-- .../core/oned/rss/expanded/TestCaseUtil.ts | 8 +++--- 11 files changed, 44 insertions(+), 46 deletions(-) diff --git a/src/index.ts b/src/index.ts index 372596c5..7b045bfc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -115,4 +115,6 @@ export { default as ITFReader } from './core/oned/ITFReader'; export { default as Code39Reader } from './core/oned/Code39Reader'; export { default as RSS14Reader } from './core/oned/rss/RSS14Reader'; export { default as RSSExpandedReader } from './core/oned/rss/expanded/RSSExpandedReader'; +export { default as AbstractExpandedDecoder } from './core/oned/rss/expanded/decoders/AbstractExpandedDecoder'; +export { createDecoder as createAbstractExpandedDecoder } from './core/oned/rss/expanded/decoders/AbstractExpandedDecoderComplement'; export { default as MultiFormatOneDReader } from './core/oned/MultiFormatOneDReader'; diff --git a/src/test/core/common/AbstractBlackBox.ts b/src/test/core/common/AbstractBlackBox.ts index f1bb89f3..b2a8f3fb 100644 --- a/src/test/core/common/AbstractBlackBox.ts +++ b/src/test/core/common/AbstractBlackBox.ts @@ -16,19 +16,23 @@ /*package com.google.zxing.common;*/ +import { + BarcodeFormat, + BinaryBitmap, + DecodeHintType, + HybridBinarizer, + LuminanceSource, + Reader, + Result, + ResultMetadataType, + ZXingStringEncoding +} from '@zxing/library'; +import * as fs from 'fs'; +import * as path from 'path'; +import TestResult from '../common/TestResult'; +import SharpImageLuminanceSource from '../SharpImageLuminanceSource'; import { assertEquals } from '../util/AssertUtils'; import SharpImage from '../util/SharpImage'; -import SharpImageLuminanceSource from '../SharpImageLuminanceSource'; -import { BarcodeFormat } from '@zxing/library'; -import { BinaryBitmap } from '@zxing/library'; -import { DecodeHintType } from '@zxing/library'; -import { LuminanceSource } from '@zxing/library'; -import { Reader } from '@zxing/library'; -import { Result } from '@zxing/library'; -import { ResultMetadataType } from '@zxing/library'; -import TestResult from '../common/TestResult'; -import { HybridBinarizer } from '@zxing/library'; -import { ZXingStringEncoding } from '@zxing/library'; /*import javax.imageio.ImageIO;*/ diff --git a/src/test/core/oned/rss/expanded/BinaryUtil.spec.ts b/src/test/core/oned/rss/expanded/BinaryUtil.spec.ts index 760f5c08..69ebfd4b 100644 --- a/src/test/core/oned/rss/expanded/BinaryUtil.spec.ts +++ b/src/test/core/oned/rss/expanded/BinaryUtil.spec.ts @@ -1,4 +1,4 @@ -import BitArray from '../../../../../core/common/BitArray'; +import { BitArray } from '@zxing/library'; import { assertEquals } from '../../../util/AssertUtils'; import BinaryUtil from './BinaryUtil'; diff --git a/src/test/core/oned/rss/expanded/BinaryUtil.ts b/src/test/core/oned/rss/expanded/BinaryUtil.ts index 66b21924..2f0e9e21 100644 --- a/src/test/core/oned/rss/expanded/BinaryUtil.ts +++ b/src/test/core/oned/rss/expanded/BinaryUtil.ts @@ -1,6 +1,5 @@ -import { BitArray } from '../../../../..'; -import IllegalStateException from '../../../../../core/IllegalStateException'; -import StringBuilder from '../../../../../core/util/StringBuilder'; +import { BitArray, IllegalStateException } from '@zxing/library'; +import StringBuilder from 'src/core/util/StringBuilder'; /* * Copyright (C) 2010 ZXing authors diff --git a/src/test/core/oned/rss/expanded/ExpandedInformationDecoder.spec.ts b/src/test/core/oned/rss/expanded/ExpandedInformationDecoder.spec.ts index 373e7fd8..c2958832 100644 --- a/src/test/core/oned/rss/expanded/ExpandedInformationDecoder.spec.ts +++ b/src/test/core/oned/rss/expanded/ExpandedInformationDecoder.spec.ts @@ -1,10 +1,3 @@ -import { BitArray } from '../../../../..'; -import AbstractExpandedDecoder from '../../../../../core/oned/rss/expanded/decoders/AbstractExpandedDecoder'; -import { assertEquals } from '../../../util/AssertUtils'; -import BinaryUtil from './BinaryUtil'; - - - /* * Copyright (C) 2010 ZXing authors * @@ -39,6 +32,10 @@ import BinaryUtil from './BinaryUtil'; // import org.junit.Assert; // import org.junit.Test; +import { BitArray, AbstractExpandedDecoder, createAbstractExpandedDecoder } from '@zxing/library'; +import { assertEquals } from '../../../util/AssertUtils'; +import BinaryUtil from './BinaryUtil'; + /** * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es) * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es) @@ -48,7 +45,7 @@ it('ExpandedInformationDecoderTest', () => { it('testNoAi', () => { let information: BitArray = BinaryUtil.buildBitArrayFromString(' .......X ..XX..X. X.X....X .......X ....'); - let decoder: AbstractExpandedDecoder = AbstractExpandedDecoder.createDecoder(information); + let decoder: AbstractExpandedDecoder = createAbstractExpandedDecoder(information); let decoded: String = decoder.parseInformation(); assertEquals('(10)12A', decoded); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox1.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox1.spec.ts index f69457a9..2c4d0283 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox1.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox1.spec.ts @@ -26,8 +26,7 @@ // package com.google.zxing.oned; -import BarcodeFormat from '../../../../../core/BarcodeFormat'; -import MultiFormatReader from '../../../../../core/MultiFormatReader'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /** @@ -43,8 +42,8 @@ class RSSExpandedBlackBox1Spec extends AbstractBlackBoxSpec { } describe('RSSExpandedBlackBox1Spec', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedBlackBox1Spec(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox2.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox2.spec.ts index 75a470d9..3cae643c 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox2.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox2.spec.ts @@ -26,8 +26,7 @@ // package com.google.zxing.oned; -import BarcodeFormat from '../../../../../core/BarcodeFormat'; -import MultiFormatReader from '../../../../../core/MultiFormatReader'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /** @@ -43,9 +42,9 @@ class RSSExpandedBlackBox2TestCase extends AbstractBlackBoxSpec { } describe('RSSExpandedBlackBox2TestCase', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedBlackBox2TestCase(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox3.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox3.spec.ts index 42c60c91..0ff4fa4e 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox3.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox3.spec.ts @@ -26,8 +26,7 @@ // package com.google.zxing.oned; -import BarcodeFormat from '../../../../../core/BarcodeFormat'; -import MultiFormatReader from '../../../../../core/MultiFormatReader'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /** @@ -43,8 +42,8 @@ class RSSExpandedBlackBox3TestCase extends AbstractBlackBoxSpec { } describe('RSSExpandedBlackBox3TestCase', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedBlackBox3TestCase(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox1.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox1.spec.ts index fc92f186..2e24e846 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox1.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox1.spec.ts @@ -1,4 +1,4 @@ -import { BarcodeFormat, MultiFormatReader } from '../../../../..'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /* @@ -48,9 +48,9 @@ class RSSExpandedStackedBlackBox1TestCase extends AbstractBlackBoxSpec { } describe('RSSExpandedStackedBlackBox1TestCase', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedStackedBlackBox1TestCase(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox2.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox2.spec.ts index 755350f9..9bf4fb8b 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox2.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox2.spec.ts @@ -1,5 +1,4 @@ -import { BarcodeFormat } from '../../../../..'; -import MultiFormatReader from '../../../../../core/MultiFormatReader'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /* @@ -49,9 +48,9 @@ class RSSExpandedStackedBlackBox2TestCase extends AbstractBlackBoxSpec { } describe('RSSExpandedStackedBlackBox2TestCase', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedStackedBlackBox2TestCase(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/TestCaseUtil.ts b/src/test/core/oned/rss/expanded/TestCaseUtil.ts index 96d28138..0a596030 100644 --- a/src/test/core/oned/rss/expanded/TestCaseUtil.ts +++ b/src/test/core/oned/rss/expanded/TestCaseUtil.ts @@ -1,7 +1,7 @@ -import { BinaryBitmap, GlobalHistogramBinarizer } from "../../../../.."; -import AbstractBlackBoxSpec from "../../../common/AbstractBlackBox"; -import SharpImageLuminanceSource from "../../../SharpImageLuminanceSource"; -import SharpImage from "../../../util/SharpImage"; +import { BinaryBitmap, GlobalHistogramBinarizer } from '@zxing/library'; +import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; +import SharpImageLuminanceSource from '../../../SharpImageLuminanceSource'; +import SharpImage from '../../../util/SharpImage'; /* * Copyright (C) 2012 ZXing authors From 3599b38f79ca50d582e4d17f51fee27d5f8388d8 Mon Sep 17 00:00:00 2001 From: Luiz Filipe Machado Barni Date: Sat, 29 May 2021 14:25:19 -0300 Subject: [PATCH 17/19] ts-node launches --- .vscode/launch.json | 141 ++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 70 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index cc8f2f39..ea8866b5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,158 +5,159 @@ "type": "node", "request": "launch", "name": "Unit Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ - "./src/**/*.spec.ts", - "--colors", + "--require", + "ts-node/register", + "--require", + "tsconfig-paths/register", + "-u", + "tdd", "--timeout", "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" + "--colors", + "--recursive", + "./src/**/*.spec.ts" ] }, { "type": "node", "request": "launch", - "name": "Unit Tests - ts-node", + "name": "Code 39 Tests", "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "--require", "ts-node/register", + "--require", + "tsconfig-paths/register", "-u", "tdd", "--timeout", "999999", "--colors", "--recursive", - "./src/test/**/*.spec.ts" - ], - "internalConsoleOptions": "openOnSessionStart" - }, - { - "type": "node", - "request": "launch", - "name": "Code 39 Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", - "args": [ "./src/test/core/oned/Code39*.spec.ts", - "--colors", - "--timeout", - "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" ] }, { "type": "node", "request": "launch", "name": "EAN 13 Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ - "./src/test/core/oned/Ean13*.spec.ts", - "--colors", + "--require", + "ts-node/register", + "--require", + "tsconfig-paths/register", + "-u", + "tdd", "--timeout", "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" + "--colors", + "--recursive", + "./src/test/core/oned/Ean13*.spec.ts", ] }, { "type": "node", "request": "launch", "name": "PDF417 Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ - "./src/test/core/pdf417/", - "--recursive", - "--colors", + "--require", + "ts-node/register", + "--require", + "tsconfig-paths/register", + "-u", + "tdd", "--timeout", "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" + "--colors", + "--recursive", + "./src/test/core/pdf417/**/*.spec.ts" ] }, { "type": "node", "request": "launch", "name": "QR Code Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ - "./src/test/core/qrcode/", - "--recursive", - "--colors", + "--require", + "ts-node/register", + "--require", + "tsconfig-paths/register", + "-u", + "tdd", "--timeout", "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" + "--colors", + "--recursive", + "./src/test/core/qrcode/**/*.spec.ts" ] }, { "type": "node", "request": "launch", "name": "Data Matrix Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ - "./src/test/core/datamatrix/", - "--recursive", - "--colors", + "--require", + "ts-node/register", + "--require", + "tsconfig-paths/register", + "-u", + "tdd", "--timeout", "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" - ] + "--colors", + "--recursive", + "./src/test/core/datamatrix/**/*.spec.ts" + ], + "internalConsoleOptions": "openOnSessionStart" }, { "type": "node", "request": "launch", "name": "Aztec 2D Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ - "./src/test/core/aztec/", - "--recursive", - "--colors", + "--require", + "ts-node/register", + "-u", + "tdd", "--timeout", "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" - ] + "--colors", + "--recursive", + "./src/test/core/aztec/**/*.spec.ts" + ], + "internalConsoleOptions": "openOnSessionStart" }, { "type": "node", "request": "launch", - "name": "Aztec 2D Tests - ts-node", + "name": "StringBuilder tests", "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "--require", "ts-node/register", + "--require", + "tsconfig-paths/register", "-u", "tdd", "--timeout", "999999", "--colors", "--recursive", - "./src/test/core/aztec/**/*.spec.ts" + "./src/test/core/util/StringBuilder.spec.ts" ], "internalConsoleOptions": "openOnSessionStart" }, { "type": "node", "request": "launch", - "name": "Multi-reader Tests - ts-node", + "name": "Result tests", "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "--require", @@ -169,7 +170,7 @@ "999999", "--colors", "--recursive", - "./src/test/core/multi/**/*.spec.ts" + "./src/test/core/Result.spec.ts" ], "internalConsoleOptions": "openOnSessionStart" } From 7c0beabd5776674a08db23bad4376922d53e40ab Mon Sep 17 00:00:00 2001 From: "shahr.a.shahrulzaman" Date: Wed, 11 Aug 2021 12:56:44 +0900 Subject: [PATCH 18/19] Remove duplicate import and add type to assertion to allow tests to run --- src/test/core/oned/rss/expanded/BinaryUtil.ts | 4 +--- src/test/core/util/AssertUtils.ts | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/core/oned/rss/expanded/BinaryUtil.ts b/src/test/core/oned/rss/expanded/BinaryUtil.ts index 64459fa1..7fb0bdc7 100644 --- a/src/test/core/oned/rss/expanded/BinaryUtil.ts +++ b/src/test/core/oned/rss/expanded/BinaryUtil.ts @@ -1,7 +1,5 @@ -import StringBuilder from '../../../../../core/util/StringBuilder'; import { BitArray, IllegalStateException } from '@zxing/library'; -import StringBuilder from 'src/core/util/StringBuilder'; - +import StringBuilder from '../../../../../core/util/StringBuilder'; /* * Copyright (C) 2010 ZXing authors diff --git a/src/test/core/util/AssertUtils.ts b/src/test/core/util/AssertUtils.ts index a04df3ae..f54fc5bf 100644 --- a/src/test/core/util/AssertUtils.ts +++ b/src/test/core/util/AssertUtils.ts @@ -1,3 +1,4 @@ +/** @type {any} */ import * as assert from 'assert'; export default class AssertUtils { @@ -20,7 +21,7 @@ export default class AssertUtils { } -export const assertEquals = assert.strictEqual; +export const assertEquals: any = assert.strictEqual; export const assertArrayEquals = (a: Array | Uint8Array | Int32Array, b: Array | Uint8Array | Int32Array) => assert.deepStrictEqual(a, b); export const assertFalse = x => assert.strictEqual(!!x, false); export const assertTrue = x => assert.strictEqual(!!x, true); From fa890efa1d4ecb10794a4078fc73d42939845bf3 Mon Sep 17 00:00:00 2001 From: "shahr.a.shahrulzaman" Date: Wed, 11 Aug 2021 12:58:54 +0900 Subject: [PATCH 19/19] Remove duplicate import and add type to assertion to allow tests to run --- src/test/core/util/AssertUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/core/util/AssertUtils.ts b/src/test/core/util/AssertUtils.ts index f54fc5bf..8f8c127d 100644 --- a/src/test/core/util/AssertUtils.ts +++ b/src/test/core/util/AssertUtils.ts @@ -1,4 +1,3 @@ -/** @type {any} */ import * as assert from 'assert'; export default class AssertUtils {