@nuintun/qrcode
Version:
A pure JavaScript QRCode encode and decode library.
195 lines (191 loc) • 6.1 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 { toInt32 } from './utils.js';
/**
* @module mask
*/
// Penalty weights.
const N1 = 3;
const N2 = 3;
const N3 = 40;
const N4 = 10;
// Is dark point.
function isDark(matrix, x, y) {
return matrix.get(x, y) === 1;
}
// Helper function for applyMaskPenaltyRule1. We need this for doing this calculation in both
// horizontal and vertical orders respectively.
function applyMaskPenaltyRule1Internal(matrix, isVertical) {
let penalty = 0;
const { size } = matrix;
for (let y = 0; y < size; y++) {
let prevBit = -1;
let numSameBitCells = 0;
for (let x = 0; x < size; x++) {
const bit = isVertical ? matrix.get(y, x) : matrix.get(x, y);
if (bit === prevBit) {
numSameBitCells++;
} else {
if (numSameBitCells >= 5) {
penalty += N1 + (numSameBitCells - 5);
}
// set prev bit.
prevBit = bit;
// include the cell itself.
numSameBitCells = 1;
}
}
if (numSameBitCells >= 5) {
penalty += N1 + (numSameBitCells - 5);
}
}
return penalty;
}
// Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and
// give penalty to them. Example: 00000 or 11111.
function applyMaskPenaltyRule1(matrix) {
return applyMaskPenaltyRule1Internal(matrix) + applyMaskPenaltyRule1Internal(matrix, true);
}
// Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give
// penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a
// penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block.
function applyMaskPenaltyRule2(matrix) {
let penalty = 0;
const size = matrix.size - 1;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const bit = matrix.get(x, y);
if (
// Find 2x2 blocks with the same color.
bit === matrix.get(x + 1, y) &&
bit === matrix.get(x, y + 1) &&
bit === matrix.get(x + 1, y + 1)
) {
penalty += N2;
}
}
}
return penalty;
}
// Is is four white, check on horizontal and vertical.
function isFourWhite(matrix, offset, from, to, isVertical) {
if (from < 0 || to > matrix.size) {
return false;
}
for (let i = from; i < to; i++) {
if (isVertical ? isDark(matrix, offset, i) : isDark(matrix, i, offset)) {
return false;
}
}
return true;
}
// Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4
// starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we
// find patterns like 000010111010000, we give penalty once.
function applyMaskPenaltyRule3(matrix) {
let numPenalties = 0;
const { size } = matrix;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
if (
// Find consecutive runs of 1:1:3:1:1:4 or 4:1:1:3:1:1, patterns like 000010111010000.
x + 6 < size &&
isDark(matrix, x, y) &&
!isDark(matrix, x + 1, y) &&
isDark(matrix, x + 2, y) &&
isDark(matrix, x + 3, y) &&
isDark(matrix, x + 4, y) &&
!isDark(matrix, x + 5, y) &&
isDark(matrix, x + 6, y) &&
(isFourWhite(matrix, y, x - 4, x) || isFourWhite(matrix, y, x + 7, x + 11))
) {
numPenalties++;
}
if (
// Find consecutive runs of 1:1:3:1:1:4 or 4:1:1:3:1:1, patterns like 000010111010000.
y + 6 < size &&
isDark(matrix, x, y) &&
!isDark(matrix, x, y + 1) &&
isDark(matrix, x, y + 2) &&
isDark(matrix, x, y + 3) &&
isDark(matrix, x, y + 4) &&
!isDark(matrix, x, y + 5) &&
isDark(matrix, x, y + 6) &&
(isFourWhite(matrix, x, y - 4, y, true) || isFourWhite(matrix, x, y + 7, y + 11, true))
) {
numPenalties++;
}
}
}
return numPenalties * N3;
}
// Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give
// penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance.
function applyMaskPenaltyRule4(matrix) {
let numDarkCells = 0;
const { size } = matrix;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
if (isDark(matrix, x, y)) {
numDarkCells++;
}
}
}
const numTotalCells = size * size;
const fivePercentVariances = toInt32((Math.abs(numDarkCells * 2 - numTotalCells) * 10) / numTotalCells);
return fivePercentVariances * N4;
}
// The mask penalty calculation is complicated. See Table 11 of ISO/IEC 18004:2015(E)(p.54) for details.
// Basically it applies four rules and summate all penalties.
function calculateMaskPenalty(matrix) {
return (
applyMaskPenaltyRule1(matrix) +
applyMaskPenaltyRule2(matrix) +
applyMaskPenaltyRule3(matrix) +
applyMaskPenaltyRule4(matrix)
);
}
// Return is apply mask at "x" and "y". See 7.8 of ISO/IEC 18004:2015(E)(p.50) for mask pattern conditions.
function isApplyMask(mask, x, y) {
let temporary;
let intermediate;
switch (mask) {
case 0:
intermediate = (y + x) & 0x01;
break;
case 1:
intermediate = y & 0x01;
break;
case 2:
intermediate = x % 3;
break;
case 3:
intermediate = (y + x) % 3;
break;
case 4:
intermediate = (toInt32(y / 2) + toInt32(x / 3)) & 0x01;
break;
case 5:
temporary = y * x;
intermediate = (temporary & 0x01) + (temporary % 3);
break;
case 6:
temporary = y * x;
intermediate = ((temporary & 0x01) + (temporary % 3)) & 0x01;
break;
case 7:
intermediate = (((y * x) % 3) + ((y + x) & 0x01)) & 0x01;
break;
default:
throw new Error(`illegal mask: ${mask}`);
}
return intermediate === 0;
}
export { calculateMaskPenalty, isApplyMask };