UNPKG

@nuintun/qrcode

Version:

A pure JavaScript QRCode encode and decode library.

196 lines (192 loc) 7.09 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 { round } from '../common/utils.js'; import { distance } from '../common/Point.js'; import { scanlineUpdate } from './utils/scanline.js'; import { FINDER_PATTERN_RATIOS } from './PatternRatios.js'; import { checkEstimateTimingLine } from './utils/timing.js'; import { PatternFinder } from './PatternFinder.js'; import { isEqualsSize, isMatchPattern } from './utils/pattern.js'; import { MIN_VERSION_SIZE, MAX_VERSION_SIZE } from '../common/Version.js'; import { FinderPatternGroup, calculateTopLeftAngle } from './FinderPatternGroup.js'; import { MIN_TOP_LEFT_ANGLE, MAX_TOP_LEFT_ANGLE, DIFF_MODULE_SIZE_RATIO } from './utils/constants.js'; /** * @module FinderPatternFinder */ function isGroupNested(finderPatternGroup, patterns, used) { let count = 0; const { topLeft, topRight, bottomLeft } = finderPatternGroup; for (const pattern of patterns) { if (pattern !== topLeft && pattern !== topRight && pattern !== bottomLeft) { let contain; if (used.has(pattern)) { contain = FinderPatternGroup.contains(finderPatternGroup, pattern); if (contain) { return true; } } if ( Pattern.noise(pattern) < 1 && (contain == null ? FinderPatternGroup.contains(finderPatternGroup, pattern) : contain) ) { // Maybe contain another QR code, but we only allow one, because this is not a normal mode. if (++count > 3) { return true; } } } } return false; } class FinderPatternFinder extends PatternFinder { constructor(matrix, strict) { super(matrix, FINDER_PATTERN_RATIOS, strict); } *groups() { const patterns = this.patterns.filter(pattern => { return Pattern.combined(pattern) >= 3 && Pattern.noise(pattern) <= 1.5; }); const { length } = patterns; if (length === 3) { const finderPatternGroup = new FinderPatternGroup(this.matrix, patterns); const size = FinderPatternGroup.size(finderPatternGroup); if (size >= MIN_VERSION_SIZE && size <= MAX_VERSION_SIZE) { yield finderPatternGroup; } } else if (length > 3) { const maxI1 = length - 2; const maxI2 = length - 1; const used = new Map(); for (let i1 = 0; i1 < maxI1; i1++) { const pattern1 = patterns[i1]; const moduleSize1 = pattern1.moduleSize; // Pattern 1 used. if (used.has(pattern1)) { continue; } for (let i2 = i1 + 1; i2 < maxI2; i2++) { const pattern2 = patterns[i2]; const moduleSize2 = pattern2.moduleSize; // Pattern 1 used. if (used.has(pattern1)) { break; } if ( // Pattern 2 used. used.has(pattern2) || // Non equals module size. !isEqualsSize(moduleSize1, moduleSize2, DIFF_MODULE_SIZE_RATIO) ) { continue; } for (let i3 = i2 + 1; i3 < length; i3++) { const pattern3 = patterns[i3]; const moduleSize3 = pattern3.moduleSize; if ( // Pattern 1 used. used.has(pattern1) || // Pattern 2 used. used.has(pattern2) ) { break; } if ( // Non equals module size. !isEqualsSize(moduleSize1, moduleSize3, DIFF_MODULE_SIZE_RATIO) || // Non equals module size. !isEqualsSize(moduleSize2, moduleSize3, DIFF_MODULE_SIZE_RATIO) ) { continue; } const { matrix } = this; const finderPatternGroup = new FinderPatternGroup(matrix, [pattern1, pattern2, pattern3]); const angle = calculateTopLeftAngle(finderPatternGroup); if (angle >= MIN_TOP_LEFT_ANGLE && angle <= MAX_TOP_LEFT_ANGLE) { const [xModuleSize, yModuleSize] = FinderPatternGroup.moduleSizes(finderPatternGroup); if (xModuleSize >= 1 && yModuleSize >= 1) { const { topLeft, topRight, bottomLeft } = finderPatternGroup; const edge1 = distance(topLeft, topRight); const edge2 = distance(topLeft, bottomLeft); const edge1Modules = round(edge1 / xModuleSize); const edge2Modules = round(edge2 / yModuleSize); if (Math.abs(edge1Modules - edge2Modules) <= 4) { const size = FinderPatternGroup.size(finderPatternGroup); if ( size >= MIN_VERSION_SIZE && size <= MAX_VERSION_SIZE && !isGroupNested(finderPatternGroup, patterns, used) ) { if ( checkEstimateTimingLine(matrix, finderPatternGroup) || checkEstimateTimingLine(matrix, finderPatternGroup, true) ) { if (yield finderPatternGroup) { used.set(pattern1, true); used.set(pattern2, true); used.set(pattern3, true); } } } } } } } } } } } find(left, top, width, height) { const { matrix } = this; const right = left + width; const bottom = top + height; const match = (x, y, scanline, count, scanlineBits, lastBit) => { scanlineUpdate(scanline, count); scanlineUpdate(scanlineBits, lastBit); // Match pattern black-white-black-white-black. if ( scanlineBits[0] === 1 && scanlineBits[1] === 0 && scanlineBits[2] === 1 && scanlineBits[3] === 0 && scanlineBits[4] === 1 && isMatchPattern(scanline, FINDER_PATTERN_RATIOS) ) { this.match(x, y, scanline, scanline[2]); } }; for (let y = top; y < bottom; y++) { let x = left; // Burn off leading white pixels before anything else; if we start in the middle of // a white run, it doesn't make sense to count its length, since we don't know if the // white run continued to the left of the start point. while (x < right && !matrix.get(x, y)) { x++; } let count = 0; let lastBit = matrix.get(x, y); const scanline = [0, 0, 0, 0, 0]; const scanlineBits = [-1, -1, -1, -1, -1]; while (x < right) { const bit = matrix.get(x, y); if (bit === lastBit) { count++; } else { match(x, y, scanline, count, scanlineBits, lastBit); count = 1; lastBit = bit; } x++; } match(x, y, scanline, count, scanlineBits, lastBit); } } } export { FinderPatternFinder };