@nuintun/qrcode
Version:
A pure JavaScript QRCode encode and decode library.
150 lines (146 loc) • 5.51 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 { Polynomial } from './Polynomial.js';
import { QR_CODE_FIELD_256 } from './GaloisField.js';
/**
* @module Decoder
*/
function runEuclideanAlgorithm(field, a, b, ecLength) {
// Assume a's degree is >= b's.
if (a.getDegree() < b.getDegree()) {
[a, b] = [b, a];
}
let remainder = b;
let term = field.one;
let remainderLast = a;
let termLast = field.zero;
// Run Euclidean algorithm until r's degree is less than ecLength/2.
while (2 * remainder.getDegree() >= ecLength) {
let termLastLast = termLast;
let remainderLastLast = remainderLast;
termLast = term;
remainderLast = remainder;
// Divide remainder last last by remainder last, with quotient in quotient and remainder in remainder.
if (remainderLast.isZero()) {
// Oops, euclidean algorithm already terminated?
throw new Error('remainder last was zero');
}
remainder = remainderLastLast;
let quotient = field.zero;
let remainderDegree = remainder.getDegree();
const remainderLastDegree = remainderLast.getDegree();
const denominatorLeadingTerm = remainderLast.getCoefficient(remainderLastDegree);
const dltInverse = field.invert(denominatorLeadingTerm);
while (remainderDegree >= remainderLastDegree && !remainder.isZero()) {
const degreeDiff = remainder.getDegree() - remainderLastDegree;
const scale = field.multiply(remainder.getCoefficient(remainderDegree), dltInverse);
quotient = quotient.addOrSubtract(field.buildPolynomial(degreeDiff, scale));
remainder = remainder.addOrSubtract(remainderLast.multiplyByMonomial(degreeDiff, scale));
remainderDegree = remainder.getDegree();
}
term = quotient.multiply(termLast).addOrSubtract(termLastLast);
if (remainderDegree >= remainderLastDegree) {
throw new Error('division algorithm failed to reduce polynomial');
}
}
const sigmaTildeAtZero = term.getCoefficient(0);
if (sigmaTildeAtZero === 0) {
throw new Error('sigma tilde(0) was zero');
}
const invert = field.invert(sigmaTildeAtZero);
const sigma = term.multiply(invert);
const omega = remainder.multiply(invert);
return [sigma, omega];
}
function findErrorLocations(field, errorLocator) {
// This is a direct application of Chien's search.
const expectedErrors = errorLocator.getDegree();
if (expectedErrors === 1) {
// Shortcut
return new Int32Array([errorLocator.getCoefficient(1)]);
}
let errors = 0;
const { size } = field;
const locations = new Int32Array(expectedErrors);
for (let i = 1; i < size && errors < expectedErrors; i++) {
if (errorLocator.evaluate(i) === 0) {
locations[errors++] = field.invert(i);
}
}
if (errors !== expectedErrors) {
throw new Error('error locator degree does not match number of roots');
}
return locations;
}
function findErrorMagnitudes(field, errorEvaluator, errorLocations) {
// This is directly applying Forney's Formula.
const { length } = errorLocations;
const result = new Int32Array(length);
for (let i = 0; i < length; i++) {
let denominator = 1;
const invert = field.invert(errorLocations[i]);
for (let j = 0; j < length; j++) {
if (i !== j) {
// denominator = field.multiply(
// denominator,
// 1 ^ field.multiply(errorLocations[j], invert)
// )
// Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug.
// Below is a funny-looking workaround from Steven Parkes.
const term = field.multiply(errorLocations[j], invert);
const termPlus1 = (term & 0x01) === 0 ? term | 1 : term & -2;
denominator = field.multiply(denominator, termPlus1);
}
}
result[i] = field.multiply(errorEvaluator.evaluate(invert), field.invert(denominator));
if (field.generator !== 0) {
result[i] = field.multiply(result[i], invert);
}
}
return result;
}
class Decoder {
#field;
constructor(field = QR_CODE_FIELD_256) {
this.#field = field;
}
decode(received, ecLength) {
let noError = true;
const field = this.#field;
const { generator } = field;
const poly = new Polynomial(field, received);
const syndromeCoefficients = new Int32Array(ecLength);
for (let i = 0; i < ecLength; i++) {
const evaluate = poly.evaluate(field.exp(i + generator));
syndromeCoefficients[ecLength - 1 - i] = evaluate;
if (evaluate !== 0) {
noError = false;
}
}
if (!noError) {
const syndrome = new Polynomial(field, syndromeCoefficients);
const [sigma, omega] = runEuclideanAlgorithm(field, field.buildPolynomial(ecLength, 1), syndrome, ecLength);
const errorLocations = findErrorLocations(field, sigma);
const errorMagnitudes = findErrorMagnitudes(field, omega, errorLocations);
const errorLength = errorLocations.length;
const receivedLength = received.length;
for (let i = 0; i < errorLength; i++) {
const position = receivedLength - 1 - field.log(errorLocations[i]);
if (position < 0) {
throw new Error('bad error location');
}
received[position] = received[position] ^ errorMagnitudes[i];
}
return errorLength;
}
return 0;
}
}
export { Decoder };