UNPKG

@nuintun/qrcode

Version:

A pure JavaScript QRCode encode and decode library.

118 lines (114 loc) 4.99 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 */ import { Pattern } from './Pattern.js'; import { Detected } from './Detected.js'; import { toInt32 } from '../common/utils.js'; import { createTransform } from './utils/transform.js'; import { checkMappingTimingLine } from './utils/timing.js'; import { FinderPatternGroup } from './FinderPatternGroup.js'; import { ALIGNMENT_PATTERN_RATIOS } from './PatternRatios.js'; import { FinderPatternFinder } from './FinderPatternFinder.js'; import { AlignmentPatternFinder } from './AlignmentPatternFinder.js'; import { MIN_VERSION_SIZE_WITH_ALIGNMENTS } from '../common/Version.js'; /** * @module Detector */ function getExpectAlignment(finderPatternGroup) { const { x, y } = finderPatternGroup.topLeft; const size = FinderPatternGroup.size(finderPatternGroup); const expectAlignmentCorrectionToTopLeftRatio = 1 - 3 / (size - 7); const bottomRight = FinderPatternGroup.bottomRight(finderPatternGroup); const [xModuleSize, yModuleSize] = FinderPatternGroup.moduleSizes(finderPatternGroup); const expectAlignmentX = x + (bottomRight.x - x) * expectAlignmentCorrectionToTopLeftRatio; const expectAlignmentY = y + (bottomRight.y - y) * expectAlignmentCorrectionToTopLeftRatio; return new Pattern(ALIGNMENT_PATTERN_RATIOS, expectAlignmentX, expectAlignmentY, xModuleSize * 5, yModuleSize * 5, 0); } function findAlignmentInRegion(matrix, finderPatternGroup, strict) { const size = FinderPatternGroup.size(finderPatternGroup); const scanAllowanceRatio = Math.min(20, toInt32(size / 4)); const expectAlignment = getExpectAlignment(finderPatternGroup); const alignmentFinder = new AlignmentPatternFinder(matrix, strict); const moduleSize = FinderPatternGroup.moduleSize(finderPatternGroup); const { x: expectAlignmentX, y: expectAlignmentY } = expectAlignment; const alignmentAreaAllowanceSize = Math.ceil(moduleSize * scanAllowanceRatio); const alignmentAreaTop = toInt32(Math.max(0, expectAlignmentY - alignmentAreaAllowanceSize)); const alignmentAreaLeft = toInt32(Math.max(0, expectAlignmentX - alignmentAreaAllowanceSize)); const alignmentAreaRight = toInt32(Math.min(matrix.width - 1, expectAlignmentX + alignmentAreaAllowanceSize)); const alignmentAreaBottom = toInt32(Math.min(matrix.height - 1, expectAlignmentY + alignmentAreaAllowanceSize)); alignmentFinder.find( alignmentAreaLeft, alignmentAreaTop, alignmentAreaRight - alignmentAreaLeft, alignmentAreaBottom - alignmentAreaTop ); return alignmentFinder.filter(expectAlignment, moduleSize); } class Detector { #options; /** * @constructor * @param options The options of detector. */ constructor(options = {}) { this.#options = options; } /** * @method detect Detect the binarized image matrix. * @param matrix The binarized image matrix. */ *detect(matrix) { const { strict } = this.#options; const { width, height } = matrix; const finderFinder = new FinderPatternFinder(matrix, strict); finderFinder.find(0, 0, width, height); const finderPatternGroups = finderFinder.groups(); let current = finderPatternGroups.next(); while (!current.done) { let succeed = false; const finderPatternGroup = current.value; const size = FinderPatternGroup.size(finderPatternGroup); // Find alignment. if (size >= MIN_VERSION_SIZE_WITH_ALIGNMENTS) { // Kind of arbitrary -- expand search radius before giving up // If we didn't find alignment pattern... well try anyway without it. const alignmentPatterns = findAlignmentInRegion(matrix, finderPatternGroup, strict); // Founded alignment. for (const alignmentPattern of alignmentPatterns) { const transform = createTransform(finderPatternGroup, alignmentPattern); if ( // Top left to top right. checkMappingTimingLine(matrix, transform, size) && // Top left to bottom left. checkMappingTimingLine(matrix, transform, size, true) ) { succeed = yield new Detected(matrix, transform, finderPatternGroup, alignmentPattern); // Succeed, skip next alignment pattern. if (succeed) { break; } } } } else { const transform = createTransform(finderPatternGroup); if ( // Top left to top right. checkMappingTimingLine(matrix, transform, size) && // Top left to bottom left. checkMappingTimingLine(matrix, transform, size, true) ) { // No alignment pattern version. succeed = yield new Detected(matrix, transform, finderPatternGroup); } } current = finderPatternGroups.next(succeed); } } } export { Detector };