@nuintun/qrcode
Version:
A pure JavaScript QRCode encode and decode library.
127 lines (122 loc) • 5.3 kB
JavaScript
/**
* @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 Detected = require('./Detected.cjs');
const utils = require('../common/utils.cjs');
const transform = require('./utils/transform.cjs');
const timing = require('./utils/timing.cjs');
const FinderPatternGroup = require('./FinderPatternGroup.cjs');
const PatternRatios = require('./PatternRatios.cjs');
const FinderPatternFinder = require('./FinderPatternFinder.cjs');
const AlignmentPatternFinder = require('./AlignmentPatternFinder.cjs');
const Version = require('../common/Version.cjs');
/**
* @module Detector
*/
function getExpectAlignment(finderPatternGroup) {
const { x, y } = finderPatternGroup.topLeft;
const size = FinderPatternGroup.FinderPatternGroup.size(finderPatternGroup);
const expectAlignmentCorrectionToTopLeftRatio = 1 - 3 / (size - 7);
const bottomRight = FinderPatternGroup.FinderPatternGroup.bottomRight(finderPatternGroup);
const [xModuleSize, yModuleSize] = FinderPatternGroup.FinderPatternGroup.moduleSizes(finderPatternGroup);
const expectAlignmentX = x + (bottomRight.x - x) * expectAlignmentCorrectionToTopLeftRatio;
const expectAlignmentY = y + (bottomRight.y - y) * expectAlignmentCorrectionToTopLeftRatio;
return new Pattern.Pattern(
PatternRatios.ALIGNMENT_PATTERN_RATIOS,
expectAlignmentX,
expectAlignmentY,
xModuleSize * 5,
yModuleSize * 5,
0
);
}
function findAlignmentInRegion(matrix, finderPatternGroup, strict) {
const size = FinderPatternGroup.FinderPatternGroup.size(finderPatternGroup);
const scanAllowanceRatio = Math.min(20, utils.toInt32(size / 4));
const expectAlignment = getExpectAlignment(finderPatternGroup);
const alignmentFinder = new AlignmentPatternFinder.AlignmentPatternFinder(matrix, strict);
const moduleSize = FinderPatternGroup.FinderPatternGroup.moduleSize(finderPatternGroup);
const { x: expectAlignmentX, y: expectAlignmentY } = expectAlignment;
const alignmentAreaAllowanceSize = Math.ceil(moduleSize * scanAllowanceRatio);
const alignmentAreaTop = utils.toInt32(Math.max(0, expectAlignmentY - alignmentAreaAllowanceSize));
const alignmentAreaLeft = utils.toInt32(Math.max(0, expectAlignmentX - alignmentAreaAllowanceSize));
const alignmentAreaRight = utils.toInt32(Math.min(matrix.width - 1, expectAlignmentX + alignmentAreaAllowanceSize));
const alignmentAreaBottom = utils.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.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.FinderPatternGroup.size(finderPatternGroup);
// Find alignment.
if (size >= Version.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$1 = transform.createTransform(finderPatternGroup, alignmentPattern);
if (
// Top left to top right.
timing.checkMappingTimingLine(matrix, transform$1, size) &&
// Top left to bottom left.
timing.checkMappingTimingLine(matrix, transform$1, size, true)
) {
succeed = yield new Detected.Detected(matrix, transform$1, finderPatternGroup, alignmentPattern);
// Succeed, skip next alignment pattern.
if (succeed) {
break;
}
}
}
} else {
const transform$1 = transform.createTransform(finderPatternGroup);
if (
// Top left to top right.
timing.checkMappingTimingLine(matrix, transform$1, size) &&
// Top left to bottom left.
timing.checkMappingTimingLine(matrix, transform$1, size, true)
) {
// No alignment pattern version.
succeed = yield new Detected.Detected(matrix, transform$1, finderPatternGroup);
}
}
current = finderPatternGroups.next(succeed);
}
}
}
exports.Detector = Detector;