UNPKG

@nuintun/qrcode

Version:

A pure JavaScript QRCode encode and decode library.

169 lines (164 loc) 6.45 kB
/** * @module QRCode * @package @nuintun/qrcode * @license MIT * @version 5.0.2 * @author nuintun <nuintun@qq.com> * @description A pure JavaScript QRCode encode and decode library. * @see https://github.com/nuintun/qrcode#readme */ 'use strict'; const Pattern = require('./Pattern.cjs'); const utils = require('../common/utils.cjs'); const Version = require('../common/Version.cjs'); const module$1 = require('./utils/module.cjs'); const Point = require('../common/Point.cjs'); /** * @module FinderPatternGroup */ function calculateSizeRatio(size1, size2) { return size1 > size2 ? size1 / size2 : size2 / size1; } function calculateDistanceRatio(pattern1, pattern2) { const ratio = Math.max( calculateSizeRatio(Pattern.Pattern.width(pattern1), Pattern.Pattern.width(pattern2)), calculateSizeRatio(Pattern.Pattern.height(pattern1), Pattern.Pattern.height(pattern2)) ); return ratio * ratio; } function crossProductZ(pattern1, pattern2, pattern3) { const { x, y } = pattern2; return (pattern3.x - x) * (pattern1.y - y) - (pattern3.y - y) * (pattern1.x - x); } function orderFinderPatterns(patterns) { let topLeft; let topRight; let bottomLeft; // Find distances between pattern centers. const [pattern1, pattern2, pattern3] = patterns; // @see https://github.com/zxing-cpp/zxing-cpp/blob/master/core/src/qrcode/QRDetector.cpp const oneTwoDistance = Point.squaredDistance(pattern1, pattern2) * calculateDistanceRatio(pattern1, pattern2); const oneThreeDistance = Point.squaredDistance(pattern1, pattern3) * calculateDistanceRatio(pattern1, pattern3); const twoThreeDistance = Point.squaredDistance(pattern2, pattern3) * calculateDistanceRatio(pattern2, pattern3); // Assume one closest to other two is B; A and C will just be guesses at first. if (twoThreeDistance >= oneTwoDistance && twoThreeDistance >= oneThreeDistance) { [topLeft, bottomLeft, topRight] = patterns; } else if (oneThreeDistance >= twoThreeDistance && oneThreeDistance >= oneTwoDistance) { [bottomLeft, topLeft, topRight] = patterns; } else { [bottomLeft, topRight, topLeft] = patterns; } // Use cross product to figure out whether A and C are correct or flipped. // This asks whether BC x BA has a positive z component, which is the arrangement // we want for A, B, C. If it's negative, then we've got it flipped around and // should swap A and C. if (crossProductZ(bottomLeft, topLeft, topRight) < 0) { [bottomLeft, topRight] = [topRight, bottomLeft]; } return [topLeft, topRight, bottomLeft]; } function calculateBottomRightPoint([topLeft, topRight, bottomLeft]) { const { x, y } = topLeft; const bottomRightX = topRight.x + bottomLeft.x - x; const bottomRightY = topRight.y + bottomLeft.y - y; return new Point.Point(bottomRightX, bottomRightY); } function calculateSymbolSize([topLeft, topRight, bottomLeft], moduleSize) { const width = Point.distance(topLeft, topRight); const height = Point.distance(topLeft, bottomLeft); const size = utils.round((width + height) / moduleSize / 2) + 7; switch (size & 0x03) { case 0: return size + 1; case 2: return size - 1; case 3: return Math.min(size + 2, Version.MAX_VERSION_SIZE); } return size; } class FinderPatternGroup { #area; #size; #matrix; #bottomRight; #moduleSize; #patterns; #moduleSizes; static moduleSizes(finderPatternGroup) { if (finderPatternGroup.#moduleSizes == null) { const matrix = finderPatternGroup.#matrix; const [topLeft, topRight, bottomLeft] = finderPatternGroup.#patterns; finderPatternGroup.#moduleSizes = [ module$1.calculateModuleSizeOneWay(matrix, topLeft, topRight), module$1.calculateModuleSizeOneWay(matrix, topLeft, bottomLeft) ]; } return finderPatternGroup.#moduleSizes; } static size(finderPatternGroup) { if (finderPatternGroup.#size == null) { const moduleSize = FinderPatternGroup.moduleSize(finderPatternGroup); finderPatternGroup.#size = calculateSymbolSize(finderPatternGroup.#patterns, moduleSize); } return finderPatternGroup.#size; } static moduleSize(finderPatternGroup) { if (finderPatternGroup.#moduleSize == null) { finderPatternGroup.#moduleSize = utils.accumulate(FinderPatternGroup.moduleSizes(finderPatternGroup)) / 2; } return finderPatternGroup.#moduleSize; } static contains(finderPatternGroup, pattern) { const area = finderPatternGroup.#calculateArea(); const [topLeft, topRight, bottomLeft] = finderPatternGroup.#patterns; const bottomRight = FinderPatternGroup.bottomRight(finderPatternGroup); const s1 = Point.calculateTriangleArea(topLeft, topRight, pattern); const s2 = Point.calculateTriangleArea(topRight, bottomRight, pattern); const s3 = Point.calculateTriangleArea(bottomRight, bottomLeft, pattern); const s4 = Point.calculateTriangleArea(bottomLeft, topLeft, pattern); // Pattern not a point, increase the detection margin appropriately. return s1 + s2 + s3 + s4 - area < 1; } static bottomRight(finderPatternGroup) { if (finderPatternGroup.#bottomRight == null) { finderPatternGroup.#bottomRight = calculateBottomRightPoint(finderPatternGroup.#patterns); } return finderPatternGroup.#bottomRight; } constructor(matrix, patterns) { this.#matrix = matrix; this.#patterns = orderFinderPatterns(patterns); } get topLeft() { return this.#patterns[0]; } get topRight() { return this.#patterns[1]; } get bottomLeft() { return this.#patterns[2]; } #calculateArea() { const [topLeft, topRight, bottomLeft] = this.#patterns; const bottomRight = FinderPatternGroup.bottomRight(this); if (this.#area == null) { const s1 = Point.calculateTriangleArea(topLeft, topRight, bottomRight); const s2 = Point.calculateTriangleArea(bottomRight, bottomLeft, topLeft); this.#area = s1 + s2; } return this.#area; } } function calculateTopLeftAngle({ topLeft, topRight, bottomLeft }) { const { x, y } = topLeft; const dx1 = topRight.x - x; const dy1 = topRight.y - y; const dx2 = bottomLeft.x - x; const dy2 = bottomLeft.y - y; const d = dx1 * dx2 + dy1 * dy2; const l2 = (dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2); return Math.acos(d / Math.sqrt(l2)); } exports.FinderPatternGroup = FinderPatternGroup; exports.calculateTopLeftAngle = calculateTopLeftAngle;