@nuintun/qrcode
Version:
A pure JavaScript QRCode encode and decode library.
118 lines (114 loc) • 4.99 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
*/
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 };