TypeScript port of ZXing multi-format 1D/2D barcode image processing library
Reed-Solomon error correction is a fundamental component of @zxing/library used across multiple barcode formats (QR Code, Data Matrix, Aztec, PDF417) to detect and correct errors introduced during data transmission, printing, or damage. This implementation provides a complete Reed-Solomon codec built on Galois Field arithmetic.
core/common/reedsolomon/@zxing/libraryReed-Solomon error correction works by:
The implementation operates on Galois Fields GF(2^n), finite fields with 2^n elements where arithmetic follows special rules optimized for error correction. Different barcode formats use different Galois Fields:
The Reed-Solomon implementation consists of four core classes:
import {
// Galois Field
GenericGF,
AbstractGenericGF,
// Polynomials
GenericGFPoly,
// Encoder/Decoder
ReedSolomonEncoder,
ReedSolomonDecoder,
// Exceptions
ReedSolomonException,
// Supporting types
Int32Array
} from '@zxing/library';Represents a finite field GF(2^n) used for Reed-Solomon arithmetic operations.
/**
* Generic Galois Field representation for Reed-Solomon operations
* Represents a finite field GF(size) with specific primitive polynomial
* Provides arithmetic operations (multiplication, division, exponentiation, logarithms)
*/
class GenericGF extends AbstractGenericGF {
/**
* Create a Galois Field
* @param primitive - number: primitive polynomial defining the field
* @param size - number: field size (number of elements, typically 2^n)
* @param generatorBase - number: generator base for Reed-Solomon (0 or 1)
*/
constructor(primitive: number, size: number, generatorBase: number);
/**
* Get the zero polynomial in this field
* @returns GenericGFPoly: polynomial representing zero
*/
getZero(): GenericGFPoly;
/**
* Get the multiplicative identity polynomial (one) in this field
* @returns GenericGFPoly: polynomial representing one
*/
getOne(): GenericGFPoly;
/**
* Build a monomial polynomial: coefficient * x^degree
* @param degree - number: polynomial degree (exponent)
* @param coefficient - number: coefficient value in field
* @returns GenericGFPoly: monomial polynomial
*/
buildMonomial(degree: number, coefficient: number): GenericGFPoly;
/**
* Compute 2^a in the Galois Field (exponentiation)
* Uses logarithm table for efficiency
* @param a - number: exponent
* @returns number: field element equal to 2^a
*/
exp(a: number): number;
/**
* Compute base-2 logarithm of a in the Galois Field
* Inverse of exp()
* @param a - number: field element (must be non-zero)
* @returns number: logarithm value
* @throws IllegalArgumentException if a is 0
*/
log(a: number): number;
/**
* Compute multiplicative inverse of a field element
* For element a, returns b where a * b = 1 in the field
* @param a - number: field element (must be non-zero)
* @returns number: inverse element
* @throws ArithmeticException if a is 0
*/
inverse(a: number): number;
/**
* Multiply two field elements
* Uses logarithms for efficiency: a * b = exp(log(a) + log(b))
* @param a - number: first field element
* @param b - number: second field element
* @returns number: product in field
*/
multiply(a: number, b: number): number;
/**
* Get field size (number of elements)
* @returns number: field size
*/
getSize(): number;
/**
* Get generator base value (0 for QR Code, 1 for Data Matrix/Aztec)
* @returns number: generator base
*/
getGeneratorBase(): number;
/**
* Check equality with another field
* @param o - Object: object to compare
* @returns boolean: true if same field (same primitive, size, base)
*/
equals(o: Object): boolean;
/**
* String representation
* @returns string: field description
*/
toString(): string;
}Predefined Galois Fields:
/**
* Predefined Galois Field instances for various barcode formats
* Each field is optimized for its specific barcode format
*/
// Aztec Code fields (multiple fields for different symbol sizes)
static AZTEC_DATA_12: GenericGF; // GF(2^12): size=4096, primitive=0x1069 (x^12 + x^6 + x^5 + x^3 + 1)
static AZTEC_DATA_10: GenericGF; // GF(2^10): size=1024, primitive=0x409 (x^10 + x^3 + 1)
static AZTEC_DATA_8: GenericGF; // GF(2^8): size=256, primitive=0x12D (x^8 + x^5 + x^3 + x^2 + 1)
static AZTEC_DATA_6: GenericGF; // GF(2^6): size=64, primitive=0x43 (x^6 + x + 1)
static AZTEC_PARAM: GenericGF; // GF(2^4): size=16, primitive=0x13 (x^4 + x + 1)
// QR Code field
static QR_CODE_FIELD_256: GenericGF; // GF(2^8): size=256, primitive=0x11D, base=0
// Data Matrix field
static DATA_MATRIX_FIELD_256: GenericGF; // GF(2^8): size=256, primitive=0x12D, base=1
// MaxiCode field (alias)
static MAXICODE_FIELD_64: GenericGF; // Alias for AZTEC_DATA_6Field Properties:
Usage Examples:
import { GenericGF, ReedSolomonEncoder, ReedSolomonDecoder } from '@zxing/library';
// Select appropriate field for barcode format
const qrField: GenericGF = GenericGF.QR_CODE_FIELD_256; // For QR Code
const dmField: GenericGF = GenericGF.DATA_MATRIX_FIELD_256; // For Data Matrix
const aztec12Field: GenericGF = GenericGF.AZTEC_DATA_12; // For large Aztec (4096 symbols)
// Use field with encoder/decoder
const encoder = new ReedSolomonEncoder(qrField);
const decoder = new ReedSolomonDecoder(qrField);
// Field properties
console.log("QR Field size:", qrField.getSize()); // 256
console.log("QR Field generator base:", qrField.getGeneratorBase()); // 0
console.log("QR Field:", qrField.toString()); // "GF(0x11d,256)"
console.log("Data Matrix size:", dmField.getSize()); // 256
console.log("Data Matrix base:", dmField.getGeneratorBase()); // 1
console.log("Data Matrix:", dmField.toString()); // "GF(0x12d,256)"
// Galois Field operations
const element1 = 5;
const element2 = 3;
const product: number = qrField.multiply(element1, element2);
console.log(`${element1} * ${element2} in GF(256) = ${product}`);
const inverse: number = qrField.inverse(element1);
console.log(`Inverse of ${element1} = ${inverse}`);
// Verify: element * inverse = 1
const verifyInverse: number = qrField.multiply(element1, inverse);
console.log(`${element1} * ${inverse} = ${verifyInverse}`); // Should be 1
// Exponentiation and logarithm
const exp7: number = qrField.exp(7);
console.log(`2^7 in field = ${exp7}`);
const log128: number = qrField.log(128);
console.log(`log₂(128) in field = ${log128}`);
// Compare fields
console.log("QR and DM fields equal:", qrField.equals(dmField)); // false (different primitives)
// Get zero and one polynomials
const zero: GenericGFPoly = qrField.getZero();
const one: GenericGFPoly = qrField.getOne();
console.log("Zero is zero:", zero.isZero()); // true
console.log("One degree:", one.getDegree()); // 0
console.log("One coefficient:", one.getCoefficient(0)); // 1Represents an immutable polynomial with coefficients in a Galois Field.
/**
* Polynomial over a Galois Field with immutable coefficients
* Coefficients are arranged from most significant (highest power) to least significant
* Used for Reed-Solomon encoding and decoding computations
*/
class GenericGFPoly {
/**
* Create polynomial with coefficients in given field
* @param field - AbstractGenericGF: Galois Field for coefficients
* @param coefficients - Int32Array: polynomial coefficients (high to low degree)
*/
constructor(field: AbstractGenericGF, coefficients: Int32Array);
/**
* Get all polynomial coefficients
* @returns Int32Array: coefficients from highest to lowest degree
*/
getCoefficients(): Int32Array;
/**
* Get the degree of this polynomial
* Degree is highest power with non-zero coefficient
* @returns number: polynomial degree (0 for constant, -1 for zero polynomial)
*/
getDegree(): number;
/**
* Check if this is the zero polynomial (all coefficients are 0)
* @returns boolean: true if zero polynomial
*/
isZero(): boolean;
/**
* Get coefficient of x^degree term
* @param degree - number: degree of term
* @returns number: coefficient value (0 if degree exceeds polynomial degree)
*/
getCoefficient(degree: number): number;
/**
* Evaluate polynomial at given point: P(a)
* Uses Horner's method for efficiency
* @param a - number: evaluation point (field element)
* @returns number: P(a) result in field
*/
evaluateAt(a: number): number;
/**
* Add or subtract another polynomial (same operation in GF(2^n))
* In Galois Fields with characteristic 2, addition and subtraction are identical (XOR)
* @param other - GenericGFPoly: polynomial to add/subtract
* @returns GenericGFPoly: sum/difference polynomial
*/
addOrSubtract(other: GenericGFPoly): GenericGFPoly;
/**
* Multiply by another polynomial
* @param other - GenericGFPoly: polynomial to multiply with
* @returns GenericGFPoly: product polynomial
*/
multiply(other: GenericGFPoly): GenericGFPoly;
/**
* Multiply by a scalar field element
* @param scalar - number: scalar value in field
* @returns GenericGFPoly: scaled polynomial
*/
multiplyScalar(scalar: number): GenericGFPoly;
/**
* Multiply by monomial: coefficient * x^degree
* Efficiently shifts polynomial and scales
* @param degree - number: degree to shift by
* @param coefficient - number: coefficient to multiply by
* @returns GenericGFPoly: result polynomial
*/
multiplyByMonomial(degree: number, coefficient: number): GenericGFPoly;
/**
* Divide by another polynomial using polynomial long division
* @param other - GenericGFPoly: divisor polynomial (must not be zero)
* @returns GenericGFPoly[]: [quotient, remainder]
* @throws IllegalArgumentException if divisor is zero
*/
divide(other: GenericGFPoly): GenericGFPoly[];
}Mathematical Background:
Polynomials in GF(2^n) are expressions like:
P(x) = c₀x^n + c₁x^(n-1) + ... + cₙWhere each coefficient cᵢ is an element of the Galois Field. Operations:
Usage Examples:
import { GenericGF, GenericGFPoly } from '@zxing/library';
const field: GenericGF = GenericGF.QR_CODE_FIELD_256;
// Create polynomial: 3x^2 + 2x + 1
const poly1 = new GenericGFPoly(field, Int32Array.from([3, 2, 1]));
console.log("poly1 degree:", poly1.getDegree()); // 2
// Create polynomial: 2x + 1
const poly2 = new GenericGFPoly(field, Int32Array.from([2, 1]));
console.log("poly2 degree:", poly2.getDegree()); // 1
// Polynomial operations
const sum: GenericGFPoly = poly1.addOrSubtract(poly2);
console.log("Sum degree:", sum.getDegree());
const product: GenericGFPoly = poly1.multiply(poly2);
console.log("Product degree:", product.getDegree()); // 3 (2 + 1)
const [quotient, remainder] = poly1.divide(poly2);
console.log("Quotient degree:", quotient.getDegree());
console.log("Remainder degree:", remainder.getDegree());
// Evaluate at x = 5
const result: number = poly1.evaluateAt(5);
console.log("P(5) =", result);
// In regular arithmetic: 3*5^2 + 2*5 + 1 = 75 + 10 + 1 = 86
// In GF(256): uses field multiplication and addition
// Get coefficients
const coeffs: Int32Array = poly1.getCoefficients();
console.log("Coefficients:", Array.from(coeffs)); // [3, 2, 1]
// Get specific coefficient
const coeff2: number = poly1.getCoefficient(2); // Coefficient of x^2
const coeff1: number = poly1.getCoefficient(1); // Coefficient of x^1
const coeff0: number = poly1.getCoefficient(0); // Constant term
console.log("Coefficient of x^2:", coeff2); // 3
console.log("Coefficient of x^1:", coeff1); // 2
console.log("Coefficient of x^0:", coeff0); // 1
// Check for zero polynomial
console.log("Is zero:", poly1.isZero()); // false
const zeroPoly: GenericGFPoly = field.getZero();
console.log("Zero poly is zero:", zeroPoly.isZero()); // true
// Scalar multiplication: multiply by 4
const scaled: GenericGFPoly = poly1.multiplyScalar(4);
console.log("Scaled degree:", scaled.getDegree()); // Still 2
// Monomial multiplication: multiply by 4x^3
const shifted: GenericGFPoly = poly1.multiplyByMonomial(3, 4);
console.log("Shifted degree:", shifted.getDegree()); // 5 (2 + 3)
console.log("New polynomial degree:", shifted.getDegree());
// Build generator polynomial for Reed-Solomon
// g(x) = (x - α^b)(x - α^(b+1))...(x - α^(b+n-1))
function buildGeneratorPolynomial(field: GenericGF, degree: number): GenericGFPoly {
let generator: GenericGFPoly = field.getOne();
const base: number = field.getGeneratorBase();
for (let i = 0; i < degree; i++) {
const root = field.buildMonomial(1, field.exp(base + i));
generator = generator.multiply(root);
}
return generator;
}
const generator10: GenericGFPoly = buildGeneratorPolynomial(field, 10);
console.log("Generator polynomial degree:", generator10.getDegree()); // 10Adds error correction codewords to data using Reed-Solomon encoding.
/**
* Reed-Solomon encoder - adds error correction to data
* Generates error correction codewords using polynomial division over Galois Field
*/
class ReedSolomonEncoder {
/**
* Create encoder for specific Galois Field
* @param field - GenericGF: Galois Field to use (must match barcode format)
*/
constructor(field: GenericGF);
/**
* Encode data by adding error correction codewords in-place
* Modifies array in-place: data codewords remain at start, EC added at end
* @param toEncode - Int32Array: array containing data codewords followed by space for EC
* - Input length = data codewords + ecBytes
* - First N elements contain data, last ecBytes elements are 0 (will be filled)
* - Modified in-place to include error correction at the end
* @param ecBytes - number: number of error correction codewords to generate
* @throws IllegalArgumentException if ecBytes is 0 or exceeds array capacity
*/
encode(toEncode: Int32Array, ecBytes: number): void;
}Encoding Process:
Mathematical Formula:
Given data polynomial D(x) and generator G(x):
Usage Examples:
import { GenericGF, ReedSolomonEncoder } from '@zxing/library';
// Initialize encoder with QR Code field
const field: GenericGF = GenericGF.QR_CODE_FIELD_256;
const encoder = new ReedSolomonEncoder(field);
// Prepare data: 10 data codewords + 10 EC codewords
const totalLength = 20;
const dataLength = 10;
const ecLength = 10;
const data = new Int32Array(totalLength);
// Fill first 10 elements with data codewords
for (let i = 0; i < dataLength; i++) {
data[i] = i + 65; // Example: ASCII 'A', 'B', 'C', etc.
}
// Last 10 elements are initially 0 (reserved for error correction)
console.log("Before encoding:", Array.from(data));
// Encode: add 10 error correction codewords
encoder.encode(data, ecLength);
console.log("After encoding:", Array.from(data));
// Now contains: [data1, data2, ..., data10, ec1, ec2, ..., ec10]
// Extract portions
const dataCodewords = data.slice(0, dataLength);
const ecCodewords = data.slice(dataLength);
console.log("Data codewords:", Array.from(dataCodewords));
console.log("EC codewords:", Array.from(ecCodewords));
// Example with QR Code Version 1-M (16 data + 10 EC codewords)
function encodeQRCodeV1M(dataCodewords: number[]): Int32Array {
if (dataCodewords.length !== 16) {
throw new Error("Version 1-M requires exactly 16 data codewords");
}
const ecCount = 10;
const totalCount = dataCodewords.length + ecCount;
const codewords = new Int32Array(totalCount);
// Copy data codewords
for (let i = 0; i < dataCodewords.length; i++) {
codewords[i] = dataCodewords[i];
}
// Add error correction
const encoder = new ReedSolomonEncoder(GenericGF.QR_CODE_FIELD_256);
encoder.encode(codewords, ecCount);
return codewords;
}
// Example: Encode "HELLO" in QR Code format
// (simplified - actual QR encoding is more complex)
const helloData = [0x40, 0x56, 0x86, 0x56, 0xC6, 0xF6, 0xF7, 0x26, 0x00, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC];
const encoded: Int32Array = encodeQRCodeV1M(helloData);
console.log("QR Code encoded:", Array.from(encoded));
// Error handling
try {
const invalidData = new Int32Array(5);
encoder.encode(invalidData, 10); // ecBytes exceeds data capacity
} catch (error) {
console.error("Encoding error:", error.message); // "No data bytes provided"
}
try {
const invalidEC = new Int32Array(10);
encoder.encode(invalidEC, 0); // ecBytes cannot be 0
} catch (error) {
console.error("Encoding error:", error.message); // "No error correction bytes"
}Detects and corrects errors in received data using Reed-Solomon decoding.
/**
* Reed-Solomon decoder - detects and corrects errors in received data
* Uses Berlekamp-Massey algorithm and Forney's formula
* Can correct up to t/2 errors where t is number of EC codewords
*/
class ReedSolomonDecoder {
/**
* Create decoder for specific Galois Field
* @param field - GenericGF: Galois Field to use (must match encoder)
*/
constructor(field: GenericGF);
/**
* Decode received codewords, correcting errors in-place
* Modifies received array in-place with corrected values
* @param received - Int32Array: array of received codewords (data + error correction)
* - Modified in-place with errors corrected
* - Length = data codewords + error correction codewords
* @param twoS - number: number of error correction codewords (typically twice max errors)
* @throws ReedSolomonException if too many errors to correct
* - Can correct up to twoS/2 errors
* - Can detect up to twoS errors
*/
decode(received: Int32Array, twoS: number): void;
}Decoding Process:
Error Correction Capacity:
With t error correction codewords:
t/2 symbol errorst symbol errors2e + t ≤ numECCodewords where e=erasures, t=errorsExamples:
Usage Examples:
import { GenericGF, ReedSolomonDecoder, ReedSolomonException } from '@zxing/library';
// Initialize decoder with QR Code field
const field: GenericGF = GenericGF.QR_CODE_FIELD_256;
const decoder = new ReedSolomonDecoder(field);
// Received data with potential errors (data + EC codewords)
const received = new Int32Array([
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, // 10 data codewords
12, 34, 56, 78, 90, 11, 22, 33, 44, 55 // 10 error correction codewords
]);
// Store original for comparison
const original = new Int32Array(received);
// Simulate errors (corrupt some codewords)
received[2] = 99; // Error in position 2
received[5] = 123; // Error in position 5
console.log("Introduced 2 errors");
try {
// Decode with 10 error correction codewords
const ecCount = 10;
decoder.decode(received, ecCount);
console.log("Errors corrected successfully!");
console.log("Corrected data:", Array.from(received.slice(0, 10)));
// Verify correction
let allCorrect = true;
for (let i = 0; i < 10; i++) {
if (received[i] !== original[i]) {
allCorrect = false;
console.error(`Position ${i}: expected ${original[i]}, got ${received[i]}`);
}
}
if (allCorrect) {
console.log("✓ All errors corrected");
}
} catch (error) {
if (error instanceof ReedSolomonException) {
console.error("Too many errors to correct:", error.message);
} else {
console.error("Decoding error:", error);
}
}
// Real-world example: QR Code block decoding
function decodeQRCodeBlock(
codewords: Int32Array,
ecCount: number
): Int32Array | null {
const decoder = new ReedSolomonDecoder(GenericGF.QR_CODE_FIELD_256);
try {
// Attempt to decode and correct errors
decoder.decode(codewords, ecCount);
// Extract data codewords (exclude error correction)
const dataCount = codewords.length - ecCount;
const data = new Int32Array(dataCount);
for (let i = 0; i < dataCount; i++) {
data[i] = codewords[i];
}
return data;
} catch (error) {
if (error instanceof ReedSolomonException) {
console.error("QR Code damaged beyond repair:", error.message);
}
return null;
}
}
// Example: Decode QR Code Version 1-M (16 data + 10 EC)
const receivedCodewords = new Int32Array(26);
// ... fill with scanned QR code data (potentially with errors) ...
const correctedData: Int32Array | null = decodeQRCodeBlock(receivedCodewords, 10);
if (correctedData) {
console.log("Decoded data:", Array.from(correctedData));
}
// Test maximum error correction capacity
function testErrorCorrection(): void {
const field = GenericGF.QR_CODE_FIELD_256;
const encoder = new ReedSolomonEncoder(field);
const decoder = new ReedSolomonDecoder(field);
// Original data: 10 codewords
const dataCount = 10;
const ecCount = 10; // Can correct up to 5 errors
const original = new Int32Array(dataCount + ecCount);
// Fill with test data
for (let i = 0; i < dataCount; i++) {
original[i] = i * 10;
}
// Encode
encoder.encode(original, ecCount);
const encoded = new Int32Array(original);
// Test with increasing error counts
for (let errorCount = 1; errorCount <= 6; errorCount++) {
const testData = new Int32Array(encoded);
// Introduce errors
const errorPositions: number[] = [];
for (let i = 0; i < errorCount; i++) {
const pos = i * 2; // Spread errors out
testData[pos] ^= 0xFF; // Flip all bits
errorPositions.push(pos);
}
console.log(`Testing with ${errorCount} errors at positions:`, errorPositions);
try {
decoder.decode(testData, ecCount);
console.log(` ✓ Successfully corrected ${errorCount} errors`);
} catch (error) {
console.log(` ✗ Failed to correct ${errorCount} errors (maximum is 5)`);
}
}
}
testErrorCorrection();import { GenericGF, ReedSolomonEncoder, ReedSolomonDecoder } from '@zxing/library';
// QR Code error correction levels
const EC_CODEWORDS = {
L: { v1: 7, v5: 26, v10: 48, v20: 81, v40: 250 },
M: { v1: 10, v5: 32, v10: 60, v20: 106, v40: 308 },
Q: { v1: 13, v5: 36, v10: 72, v20: 132, v40: 382 },
H: { v1: 17, v5: 44, v10: 88, v20: 164, v40: 496 }
};
function encodeQRCode(
data: number[],
version: number,
ecLevel: 'L' | 'M' | 'Q' | 'H'
): Int32Array {
// Get EC count for version and level
const ecKey = `v${version}` as keyof typeof EC_CODEWORDS.L;
const ecCount: number = EC_CODEWORDS[ecLevel][ecKey];
const codewords = new Int32Array(data.length + ecCount);
// Copy data
for (let i = 0; i < data.length; i++) {
codewords[i] = data[i];
}
// Add error correction
const encoder = new ReedSolomonEncoder(GenericGF.QR_CODE_FIELD_256);
encoder.encode(codewords, ecCount);
return codewords;
}
function decodeQRCode(
received: Int32Array,
version: number,
ecLevel: 'L' | 'M' | 'Q' | 'H'
): Int32Array | null {
const ecKey = `v${version}` as keyof typeof EC_CODEWORDS.L;
const ecCount: number = EC_CODEWORDS[ecLevel][ecKey];
const decoder = new ReedSolomonDecoder(GenericGF.QR_CODE_FIELD_256);
try {
decoder.decode(received, ecCount);
// Return only data portion
const dataCount = received.length - ecCount;
return received.slice(0, dataCount);
} catch (error) {
console.error("QR decode failed:", error);
return null;
}
}
// Usage
const data = [0x40, 0x56, 0x86, 0x56, 0xC6, 0x96, 0xC0, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC];
const encoded: Int32Array = encodeQRCode(data, 1, 'M');
console.log("Encoded QR Version 1-M:", Array.from(encoded));
// Simulate transmission with errors
encoded[2] ^= 0x0F; // Introduce error in position 2
encoded[5] ^= 0x1F; // Introduce error in position 5
const decoded: Int32Array | null = decodeQRCode(encoded, 1, 'M');
if (decoded) {
console.log("Decoded with error correction:", Array.from(decoded));
}import { GenericGF, ReedSolomonEncoder, ReedSolomonDecoder } from '@zxing/library';
function encodeDataMatrix(data: Int32Array, ecCount: number): Int32Array {
const codewords = new Int32Array(data.length + ecCount);
// Copy data
for (let i = 0; i < data.length; i++) {
codewords[i] = data[i];
}
// Data Matrix uses different field (generator base = 1)
const encoder = new ReedSolomonEncoder(GenericGF.DATA_MATRIX_FIELD_256);
encoder.encode(codewords, ecCount);
return codewords;
}
function decodeDataMatrix(received: Int32Array, ecCount: number): Int32Array | null {
const decoder = new ReedSolomonDecoder(GenericGF.DATA_MATRIX_FIELD_256);
try {
decoder.decode(received, ecCount);
const dataCount = received.length - ecCount;
return received.slice(0, dataCount);
} catch (error) {
console.error("Data Matrix decode failed:", error);
return null;
}
}
// Example: Data Matrix ECC200
const dmData = new Int32Array([129, 142, 236, 145, 254]); // 5 data codewords
const dmEncoded: Int32Array = encodeDataMatrix(dmData, 8); // 8 EC codewords
console.log("Data Matrix encoded:", Array.from(dmEncoded));
// Introduce errors
dmEncoded[1] ^= 0xFF;
dmEncoded[3] ^= 0xFF;
// Decode with error correction
const dmDecoded: Int32Array | null = decodeDataMatrix(dmEncoded, 8);
if (dmDecoded) {
console.log("Data Matrix decoded:", Array.from(dmDecoded));
}import { GenericGF, ReedSolomonEncoder, ReedSolomonDecoder } from '@zxing/library';
// Select appropriate Galois Field based on Aztec mode/size
function getAztecField(nbDataBlocks: number): GenericGF {
if (nbDataBlocks <= 64) {
return GenericGF.AZTEC_DATA_6; // GF(64): 6-bit field
} else if (nbDataBlocks <= 256) {
return GenericGF.AZTEC_DATA_8; // GF(256): 8-bit field
} else if (nbDataBlocks <= 1024) {
return GenericGF.AZTEC_DATA_10; // GF(1024): 10-bit field
} else {
return GenericGF.AZTEC_DATA_12; // GF(4096): 12-bit field
}
}
function encodeAztec(data: Int32Array, ecCount: number): Int32Array {
const field: GenericGF = getAztecField(data.length + ecCount);
const codewords = new Int32Array(data.length + ecCount);
for (let i = 0; i < data.length; i++) {
codewords[i] = data[i];
}
const encoder = new ReedSolomonEncoder(field);
encoder.encode(codewords, ecCount);
return codewords;
}
function decodeAztec(received: Int32Array, ecCount: number): Int32Array | null {
const field: GenericGF = getAztecField(received.length);
const decoder = new ReedSolomonDecoder(field);
try {
decoder.decode(received, ecCount);
return received.slice(0, received.length - ecCount);
} catch (error) {
console.error("Aztec decode failed:", error);
return null;
}
}
// Example with 10-bit field (1024 symbols)
const aztecData = new Int32Array([512, 768, 256, 1023]); // 10-bit values (0-1023)
const aztecEncoded: Int32Array = encodeAztec(aztecData, 6); // 6 EC codewords
console.log("Aztec encoded:", Array.from(aztecEncoded));
// Introduce errors
aztecEncoded[1] = 999;
aztecEncoded[3] = 888;
// Decode
const aztecDecoded: Int32Array | null = decodeAztec(aztecEncoded, 6);
if (aztecDecoded) {
console.log("Aztec decoded:", Array.from(aztecDecoded));
}Good Practice:
import { GenericGF, ReedSolomonEncoder } from '@zxing/library';
// GOOD: Reuse field and encoder
const field = GenericGF.QR_CODE_FIELD_256;
const encoder = new ReedSolomonEncoder(field);
function encodeMultipleBlocks(blocks: Int32Array[], ecCount: number): Int32Array[] {
return blocks.map(block => {
const codewords = new Int32Array(block.length + ecCount);
for (let i = 0; i < block.length; i++) {
codewords[i] = block[i];
}
encoder.encode(codewords, ecCount); // Reuses cached generator
return codewords;
});
}
// Process 100 blocks efficiently
const blocks: Int32Array[] = [];
for (let i = 0; i < 100; i++) {
blocks.push(new Int32Array([1, 2, 3, 4, 5]));
}
const encodedBlocks: Int32Array[] = encodeMultipleBlocks(blocks, 5);
console.log(`Encoded ${encodedBlocks.length} blocks`);Bad Practice (wasteful):
// BAD: Creating new encoder each time (wasteful, recomputes generator)
function encodeMultipleBlocksSlow(blocks: Int32Array[], ecCount: number): Int32Array[] {
return blocks.map(block => {
const encoder = new ReedSolomonEncoder(GenericGF.QR_CODE_FIELD_256); // DON'T DO THIS
const codewords = new Int32Array(block.length + ecCount);
for (let i = 0; i < block.length; i++) {
codewords[i] = block[i];
}
encoder.encode(codewords, ecCount);
return codewords;
});
}
// This recomputes the generator polynomial for every block!Creating custom fields for specialized applications:
import { GenericGF, ReedSolomonEncoder, ReedSolomonDecoder } from '@zxing/library';
// Create custom GF(2^5) field
// Primitive polynomial: x^5 + x^2 + 1 = 0x25 in binary
const customField = new GenericGF(
0x25, // primitive polynomial
32, // field size (2^5 = 32 elements)
1 // generator base
);
console.log("Custom field:", customField.toString());
console.log("Field size:", customField.getSize());
console.log("Generator base:", customField.getGeneratorBase());
// Use with encoder/decoder
const customEncoder = new ReedSolomonEncoder(customField);
const customDecoder = new ReedSolomonDecoder(customField);
// Encode/decode with custom field (values must be 0-31 for GF(32))
const customData = new Int32Array([5, 10, 15, 20, 0, 0, 0, 0]); // 4 data + 4 EC
customEncoder.encode(customData, 4);
console.log("Custom field encoded:", Array.from(customData));
// Introduce error
customData[1] = 25;
// Decode
try {
customDecoder.decode(customData, 4);
console.log("Custom field decoded:", Array.from(customData));
} catch (error) {
console.error("Custom field decode failed:", error);
}import {
GenericGF,
ReedSolomonEncoder,
ReedSolomonDecoder,
ReedSolomonException
} from '@zxing/library';
/**
* Comprehensive Reed-Solomon test suite
*/
function testReedSolomon(): void {
const field = GenericGF.QR_CODE_FIELD_256;
const encoder = new ReedSolomonEncoder(field);
const decoder = new ReedSolomonDecoder(field);
// Test 1: Encoding and decoding without errors
console.log("\n=== Test 1: No errors ===");
const data1 = new Int32Array([10, 20, 30, 40, 50, 0, 0, 0, 0, 0]);
encoder.encode(data1, 5);
decoder.decode(data1, 5);
console.assert(data1[0] === 10, "Data unchanged");
console.log("✓ Passed");
// Test 2: Correcting single error
console.log("\n=== Test 2: Single error correction ===");
const data2 = new Int32Array([10, 20, 30, 40, 50, 0, 0, 0, 0, 0]);
encoder.encode(data2, 5);
const original2 = new Int32Array(data2);
data2[2] = 99; // Introduce error
decoder.decode(data2, 5);
console.assert(data2[2] === original2[2], "Error corrected");
console.log("✓ Passed");
// Test 3: Correcting multiple errors (up to t/2)
console.log("\n=== Test 3: Multiple error correction ===");
const data3 = new Int32Array([10, 20, 30, 40, 50, 0, 0, 0, 0, 0]);
encoder.encode(data3, 5);
const original3 = new Int32Array(data3);
data3[1] = 99; // Error 1
data3[3] = 88; // Error 2
decoder.decode(data3, 5);
console.assert(
data3[1] === original3[1] && data3[3] === original3[3],
"Both errors corrected"
);
console.log("✓ Passed");
// Test 4: Too many errors (should fail)
console.log("\n=== Test 4: Too many errors ===");
const data4 = new Int32Array([10, 20, 30, 40, 50, 0, 0, 0, 0, 0]);
encoder.encode(data4, 5);
// Introduce 4 errors (max correctable = 2 with 5 EC codewords)
data4[0] = 99;
data4[1] = 88;
data4[2] = 77;
data4[3] = 66;
try {
decoder.decode(data4, 5);
console.log("✗ Failed: Should have thrown ReedSolomonException");
} catch (error) {
if (error instanceof ReedSolomonException) {
console.log("✓ Passed: Exception thrown as expected");
} else {
console.log("✗ Failed: Wrong exception type");
}
}
// Test 5: Maximum correctable errors
console.log("\n=== Test 5: Maximum correctable errors ===");
const data5 = new Int32Array([10, 20, 30, 40, 50, 60, 70, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
encoder.encode(data5, 10); // 10 EC = can correct 5 errors
const original5 = new Int32Array(data5);
// Introduce exactly 5 errors (maximum)
for (let i = 0; i < 5; i++) {
data5[i] ^= 0xFF;
}
try {
decoder.decode(data5, 10);
// Verify all data corrected
let allCorrect = true;
for (let i = 0; i < 8; i++) {
if (data5[i] !== original5[i]) {
allCorrect = false;
}
}
console.assert(allCorrect, "All 5 errors corrected");
console.log("✓ Passed");
} catch (error) {
console.log("✗ Failed:", error);
}
console.log("\n=== All tests completed ===\n");
}
// Run tests
testReedSolomon();import {
ReedSolomonEncoder,
ReedSolomonDecoder,
ReedSolomonException,
IllegalArgumentException,
ArithmeticException,
GenericGF
} from '@zxing/library';
const field = GenericGF.QR_CODE_FIELD_256;
// Encoder errors
try {
const encoder = new ReedSolomonEncoder(field);
const data = new Int32Array(10);
encoder.encode(data, 0); // ecBytes cannot be 0
} catch (error) {
if (error instanceof IllegalArgumentException) {
console.error("Encoder parameter error:", error.message);
// "No error correction bytes"
}
}
try {
const encoder = new ReedSolomonEncoder(field);
const data = new Int32Array(5);
encoder.encode(data, 10); // ecBytes exceeds array size
} catch (error) {
if (error instanceof IllegalArgumentException) {
console.error("Encoder parameter error:", error.message);
// "No data bytes provided"
}
}
// Decoder errors
try {
const decoder = new ReedSolomonDecoder(field);
const tooManyErrors = new Int32Array(20);
// ... fill with heavily corrupted data ...
decoder.decode(tooManyErrors, 10); // Can correct max 5 errors
} catch (error) {
if (error instanceof ReedSolomonException) {
console.error("Too many errors to correct:", error.message);
// Possible messages:
// - "Bad error location"
// - "r_{i-1} was zero"
// - "sigmaTilde(0) was zero"
// - "Error locator degree does not match number of roots"
}
}
// Field operation errors
try {
const inv = field.inverse(0); // Cannot invert 0
} catch (error) {
if (error instanceof ArithmeticException) {
console.error("Invalid field operation:", error.message);
}
}
try {
const logZero = field.log(0); // Cannot take log of 0
} catch (error) {
if (error instanceof IllegalArgumentException) {
console.error("Invalid logarithm argument:", error.message);
}
}Galois Fields GF(2^n) are finite fields with 2^n elements where:
Example in GF(256):
import { GenericGF } from '@zxing/library';
const field = GenericGF.QR_CODE_FIELD_256;
// Addition (XOR in GF)
const a = 5; // 0b00000101
const b = 3; // 0b00000011
const sum = a ^ b; // 0b00000110 = 6
console.log(`${a} + ${b} = ${sum} in GF(256)`);
// Multiplication (uses lookup tables)
const product: number = field.multiply(5, 3);
console.log(`${a} * ${b} = ${product} in GF(256)`);
// Verify properties
const zero = 0;
const one = 1;
console.log("Additive identity:", a ^ zero === a); // true
console.log("Multiplicative identity:", field.multiply(a, one) === a); // true
// Multiplicative inverse
const inv: number = field.inverse(5);
const verify: number = field.multiply(5, inv);
console.log(`5 * inverse(5) = ${verify}`); // Should be 1
// Logarithm and exponentiation (inverses)
const val = 128;
const logVal: number = field.log(val);
const expLog: number = field.exp(logVal);
console.log(`exp(log(${val})) = ${expLog}`); // Should equal valEncoding Formula:
Given:
D(x) = data polynomial
g(x) = generator polynomial of degree t
Encoding:
1. Multiply data by x^t: D(x) * x^t
2. Divide by generator: D(x) * x^t = q(x) * g(x) + r(x)
3. Transmitted codeword: D(x) * x^t - r(x) = data || -remainder
Result:
Codeword is divisible by g(x), enabling error detection and correctionDecoding Steps:
1. Syndrome Calculation:
S_i = R(α^(i+b)) for i = 0 to 2t-1
where R(x) is received polynomial, α is primitive element, b is base
If all S_i = 0, no errors detected
2. Error Locator Polynomial:
Find σ(x) using Berlekamp-Massey or Euclidean algorithm
Degree of σ(x) equals number of errors
3. Error Location:
Find roots of σ(x) using Chien search
Roots indicate error positions
4. Error Magnitude:
Calculate error values using Forney's formula
e_j = -ω(X_j^-1) / σ'(X_j^-1)
where ω(x) is error evaluator polynomial
5. Correction:
Subtract error values from received: R'(x) = R(x) - E(x)Based on ZXing (Zebra Crossing) library:
Copyright 2007-2024 ZXing authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.Install with Tessl CLI
npx tessl i tessl/npm-zxing--library@0.21.14