CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-zxing--library

TypeScript port of ZXing multi-format 1D/2D barcode image processing library

Overview
Eval results
Files

error-correction.mddocs/reference/

Reed-Solomon Error Correction

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.

Package Information

  • Package Name: @zxing/library
  • Package Type: npm
  • Language: TypeScript
  • Module Path: core/common/reedsolomon/
  • Import Path: @zxing/library
  • Algorithm: Reed-Solomon error correction with Berlekamp-Massey and Forney's formula

Core Concepts

Reed-Solomon error correction works by:

  1. Encoding: Adding redundant error correction codewords to data using polynomial division in a Galois Field
  2. Decoding: Using syndrome calculation, error locator polynomials, and Forney's formula to find and correct errors

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:

  • QR Code: GF(256) with primitive polynomial x^8 + x^4 + x^3 + x^2 + 1
  • Data Matrix: GF(256) with primitive polynomial x^8 + x^5 + x^3 + x^2 + 1
  • Aztec: Multiple fields - GF(16), GF(64), GF(256), GF(1024), GF(4096)
  • PDF417: GF(929) matching the 929 possible codeword values

Architecture

The Reed-Solomon implementation consists of four core classes:

  • GenericGF: Represents a Galois Field with predefined fields for different barcode formats
  • GenericGFPoly: Represents polynomials over Galois Fields with coefficient operations
  • ReedSolomonEncoder: Adds error correction codewords to data
  • ReedSolomonDecoder: Detects and corrects errors in received data

Core Imports

import {
  // Galois Field
  GenericGF,
  AbstractGenericGF,
  
  // Polynomials
  GenericGFPoly,
  
  // Encoder/Decoder
  ReedSolomonEncoder,
  ReedSolomonDecoder,
  
  // Exceptions
  ReedSolomonException,
  
  // Supporting types
  Int32Array
} from '@zxing/library';

Capabilities

Galois Field

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_6

Field Properties:

  • Primitive Polynomial: Irreducible polynomial defining field arithmetic
    • Example: 0x011D = x^8 + x^4 + x^3 + x^2 + 1
  • Size: Number of elements in the field (2^n where n is degree)
  • Generator Base: Offset for generator polynomial construction (0 or 1)
    • QR Code uses base 0: generator = (x - α^0)(x - α^1)...(x - α^(n-1))
    • Data Matrix uses base 1: generator = (x - α^1)(x - α^2)...(x - α^n)

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)); // 1

Galois Field Polynomial

Represents 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:

  • Addition/Subtraction: XOR coefficients (same operation in GF(2^n))
  • Multiplication: Convolution of coefficients with GF multiplication
  • Evaluation: Horner's method with GF arithmetic
  • Division: Polynomial long division with GF 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()); // 10

Reed-Solomon Encoder

Adds 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:

  1. Input: Data codewords + allocated space for EC codewords
    • Array length = data length + EC length
    • First portion contains data, last portion is zeroed for EC
  2. Generator Polynomial: Build g(x) = (x - α^b)(x - α^(b+1))...(x - α^(b+ecBytes-1))
    • Cached after first use for performance
  3. Polynomial Division: Multiply data by x^ecBytes, divide by generator
  4. Output: Original data + remainder coefficients as EC codewords (in-place modification)

Mathematical Formula:

Given data polynomial D(x) and generator G(x):

  • Encode: D(x) · x^ecBytes = Q(x) · G(x) + R(x)
  • Transmitted: [D(x) | R(x)] (data followed by remainder as error correction)

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"
}

Reed-Solomon Decoder

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:

  1. Syndrome Calculation: Evaluate received polynomial at error correction roots
    • Syndromes: S_i = R(α^(i+b)) for i = 0 to twoS-1
    • If all syndromes are zero, no errors detected
  2. Error Locator Polynomial: Use Extended Euclidean algorithm to find σ(x)
    • Berlekamp-Massey algorithm or Euclidean algorithm
  3. Error Locations: Use Chien search to find roots of σ(x)
    • Roots indicate positions of errors
  4. Error Magnitudes: Apply Forney's formula to calculate error values
    • e_i = -ω(X_i^(-1)) / σ'(X_i^(-1))
  5. Correction: Subtract error values from received data (XOR in GF)

Error Correction Capacity:

With t error correction codewords:

  • Can correct up to t/2 symbol errors
  • Can detect up to t symbol errors
  • With erasures: 2e + t ≤ numECCodewords where e=erasures, t=errors

Examples:

  • 10 EC codewords: can correct 5 errors, or detect 10 errors
  • 10 EC codewords with 2 known erasures: can correct 3 additional errors (2*2 + 3 = 7 ≤ 10)

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();

Integration Examples

QR Code Error Correction

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));
}

Data Matrix Error Correction

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));
}

Aztec Error Correction

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));
}

Performance Considerations

Optimization Tips

  1. Field Reuse: Always reuse GenericGF static instances - never create new fields
  2. Encoder Caching: ReedSolomonEncoder caches generator polynomials for reuse
  3. Array Allocation: Pre-allocate arrays with correct size (data + EC)
  4. Batch Processing: Reuse same encoder/decoder instance for multiple operations

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!

Complexity Analysis

  • Encoding: O(n × m) where n = data length, m = EC codeword count
    • Dominated by polynomial multiplication
  • Decoding: O(t^2 + n) where t = number of errors, n = total codewords
    • Syndrome calculation: O(n)
    • Error locator: O(t^2) - Berlekamp-Massey or Euclidean algorithm
    • Error location: O(t × n) - Chien search
    • Error values: O(t)
  • Space: O(n) for coefficient arrays and lookup tables

Advanced Topics

Custom Galois Fields

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);
}

Testing and Validation

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();

Error Handling

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);
  }
}

Mathematical Background

Galois Field Arithmetic

Galois Fields GF(2^n) are finite fields with 2^n elements where:

  • Addition is XOR (⊕)
  • Multiplication is modular polynomial multiplication
  • Every non-zero element has a multiplicative inverse

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 val

Reed-Solomon Encoding Mathematics

Encoding 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 correction

Reed-Solomon Decoding Mathematics

Decoding 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)

References

Academic References

  1. Reed, I. S., & Solomon, G. (1960): "Polynomial Codes Over Certain Finite Fields"
    • Original Reed-Solomon paper
  2. Berlekamp, E. R. (1968): "Algebraic Coding Theory"
    • Berlekamp-Massey algorithm
  3. Forney, G. D. (1965): "On Decoding BCH Codes"
    • Forney's formula for error magnitudes
  4. Blahut, R. E. (2003): "Algebraic Codes for Data Transmission"
    • Comprehensive coding theory reference

Implementation Credits

Based on ZXing (Zebra Crossing) library:

  • Original Java implementation: Sean Owen, William Rucklidge, David Olivier
  • TypeScript port: @zxing/library team
  • Algorithm references: Bruce Maggs, J.I. Hall

Related Documentation

  • QR Code - QR Code encoding/decoding using Reed-Solomon
  • Data Matrix - Data Matrix ECC200 error correction
  • Aztec - Aztec barcode error correction with multiple fields
  • PDF417 - PDF417 error correction over GF(929)
  • Common Utilities - Bit manipulation and math utilities

License

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

docs

index.md

tile.json