← Back to blog

Anatomy of a QR Code: Reed-Solomon Error Correction and Matrix Encoding

PetexSpace

Quick Response (QR) codes, standardized internationally under ISO/IEC 18004, represent one of the most resilient and ubiquitous two-dimensional symbologies in modern computing. Originally developed in 1994 by Masahiro Hara at Denso Wave for tracking automotive components during assembly, the QR code was engineered to satisfy two demanding constraints: rapid optical acquisition at omnidirectional scanning angles, and robust data survival under severe physical damage, smudging, and tearing.

Unlike one-dimensional barcodes that encode information along a single horizontal spatial axis, a QR code functions as a discrete planar matrix of dark and light square elements termed "modules." The underlying data transport layer integrates finite field abstract algebra, specifically Reed-Solomon error-correcting codes computed over the Galois Field GF(28)GF(2^8). This algebraic foundation allows decoders to reconstruct lost, obstructed, or contaminated data without retransmission.

When engineering modern web platforms, developers frequently require utilities to generate ISO-compliant QR codes directly for authentication secrets, networking profiles, or data interchange. Conversely, verifying matrix payloads and auditing byte layouts requires tools to decode QR codes client-side, ensuring sensitive data remains confined to the user device without transmission over external networks.

1. Geometric Anatomy of the ISO/IEC 18004 Matrix

An ISO/IEC 18004 QR code matrix is structured upon a strict Cartesian grid. The physical dimensions range from Version 1 (21×2121 \times 21 modules) up to Version 40 (177×177177 \times 177 modules). Each successive version increments the grid dimensions by exactly 4 modules per side:

Dimension(V)=21+4(V−1)=17+4V\text{Dimension}(V) = 21 + 4(V - 1) = 17 + 4V

Where V∈[1,40]V \in [1, 40] denotes the matrix version.

The matrix surface is systematically divided between function patterns (which facilitate geometric discovery, synchronization, and perspective correction) and the encoding region (which contains data codewords, error-correction parity bytes, format information, and version indicators).

   +-----------------------------------------------------------------------+
   | [FINDER 1]                      [TIMING HORIZONTAL]        [FINDER 2] |
   |  7x7 Pattern                     Alternating B/W            7x7       |
   |  Ratio 1:1:3:1:1                                                      |
   |                                                                       |
   |  +---------+                                             +---------+  |
   |  | ### ### |                                             | ### ### |  |
   |  | #     # |                                             | #     # |  |
   |  | # ### # |             FORMAT INFORMATION              | # ### # |  |
   |  | # ### # |             (BCH Encoded Strip)             | # ### # |  |
   |  | #     # |                                             | #     # |  |
   |  | ### ### |                                             | ### ### |  |
   |  +---------+                                             +---------+  |
   |  [SEPARATOR]                                             [SEPARATOR]  |
   |                                                                       |
   |  [TIMING VERTICAL]            [DATA & PARITY AREA]                    |
   |   Alternating B/W              Interleaved Codewords                  |
   |                                Upward/Downward 2-module tracks        |
   |                                Mask Pattern Applied                   |
   |                                                                       |
   |                                              +-----------+            |
   |                                              | ALIGNMENT |            |
   |                                              | 5x5 Grid  |            |
   |                                              +-----------+            |
   |  +---------+                                                          |
   |  | ### ### |                                                          |
   |  | #     # |                                                          |
   |  | # ### # |                                                          |
   |  | # ### # |                                                          |
   |  | #     # |                                                          |
   |  | ### ### |               VERSION INFORMATION                        |
   |  +---------+               (Versions 7-40 only, 18-bit Golay)         |
   |  [FINDER 3]                                                           |
   +-----------------------------------------------------------------------+
      QUIET ZONE (Minimum 4 Modules of Unmarked Light Space on all 4 Sides)

Finder Patterns and Affine Invariance

The most recognizable visual features are the three identical Finder Patterns (Position Detection Patterns) positioned at the top-left, top-right, and bottom-left corners. Each finder consists of an inner 3×33 \times 3 dark solid square, surrounded by a 1-module light border, enclosed by a 1-module dark outer perimeter, forming a 7×77 \times 7 structure.

When a line scan traverses through the center of a finder pattern at any arbitrary orientation (0∘0^\circ to 360∘360^\circ), the ratio of black-to-white module widths consistently evaluates to:

Pattern Ratio=1:1:3:1:1\text{Pattern Ratio} = 1 : 1 : 3 : 1 : 1

This ratio is mathematically unique within the matrix. Computer vision edge detectors scan for this characteristic signature to isolate the matrix boundary, compute affine transform matrices, correct for perspective skew, and determine the spatial rotation of the symbol in real time.

Secondary Structural Patterns

  1. Separators: Light borders of width 1 module surrounding each finder pattern, preventing data modules from visually corrupting the 1:1:3:1:11:1:3:1:1 ratio.
  2. Timing Patterns: Located on row 6 and column 6 (0-indexed) spanning between the finder patterns. Composed of alternating dark and light modules, they establish the physical sampling frequency and coordinate grid spacing across the matrix.
  3. Alignment Patterns: Required in Version 2 and higher. Composed of a 5×55 \times 5 concentric pattern (a single dark center surrounded by a light ring and dark outer perimeter). As version sizes scale up to Version 40, a regular grid of multiple alignment patterns compensates for physical surface curvature (such as scanning a QR code printed on a cylindrical can or wrinkled paper).
  4. Quiet Zone: An uninterrupted margin of white space measuring at least 4 modules wide surrounding the entire outer boundary, guaranteeing optical isolation from adjacent text or graphics.

2. Data Encoding Modes and Bitstream Packing

Payload bytes are not written directly as raw ASCII into the matrix. To maximize storage density across diverse character sets, ISO/IEC 18004 defines multiple specialized encoding modes.

Encoding ModeMode IndicatorCharacter Set SupportedBits per CharacterEfficiency Gain vs 8-bit
Numeric0001Digits 0 through 93.33 bits/char~58.3% reduction
Alphanumeric001045 chars: 0-9, A-Z, space, $ % * + - . / :5.5 bits/char~31.2% reduction
Byte0100Full ISO-8859-1 or UTF-8 binary stream8.0 bits/charBaseline
Kanji1000Shift JIS characters13.0 bits/char~18.7% reduction vs 16-bit

Compaction Mechanics

The Numeric and Alphanumeric modes achieve fractional bit densities through base-transformation grouping:

  • Numeric Compaction: The input string is partitioned into 3-digit clusters. Each 3-digit integer (000000 to 999999) is encoded into a 10-bit binary value (210=1024>9992^{10} = 1024 > 999). Any remaining two digits are encoded into 7 bits (27=128>992^7 = 128 > 99), and a solitary remaining digit occupies 4 bits.
  • Alphanumeric Compaction: Characters are paired. For characters C1C_1 and C2C_2 mapped to table values 0≤Vi≤440 \le V_i \le 44:
Pair Value=45×V(C1)+V(C2)\text{Pair Value} = 45 \times V(C_1) + V(C_2)

Because 45×44+44=202445 \times 44 + 44 = 2024, the resulting integer fits within an 11-bit word (211=20482^{11} = 2048), yielding an effective rate of 5.55.5 bits per character.

Bitstream Framing and Padding

The continuous bitstream is framed sequentially:

  1. Mode Indicator: 4-bit prefix defining the compression strategy.
  2. Character Count Indicator: Field length determined by the matrix version and active mode (for example, 8 bits for Byte mode in Versions 1-9; 16 bits in Versions 10-40).
  3. Data Bits: The compressed character payload.
  4. Terminator: A bit sequence of up to four zeroes (0000) signaling payload completion.
  5. Byte Alignment: Zero bits appended until the total bitstream length is an exact multiple of 8.
  6. Capacity Padding: If the assembled bytes do not saturate the total capacity allocated for the selected version and error correction tier, the encoder repeatedly alternates between two standard padding bytes:
Pad Byte 1=0xEC=111011002\text{Pad Byte 1} = 0xEC = 11101100_2 Pad Byte 2=0x11=000100012\text{Pad Byte 2} = 0x11 = 00010001_2

This alternating sequence maintains an even balance of dark and light modules across unfilled storage blocks, avoiding large unmodulated fields.

3. Galois Field Arithmetic: GF(28)GF(2^8) and Polynomial Mathematics

The mathematical engine enabling QR code survivability is the Reed-Solomon algebraic error-correction code. Reed-Solomon operates on symbols rather than individual bits. In ISO/IEC 18004, each symbol represents an 8-bit byte.

Traditional arithmetic operations (addition, multiplication, division) over real numbers or integers do not preserve a finite 8-bit range without overflow or precision loss. To perform closed arithmetic operations over exact byte structures, QR codes utilize Galois Field theory, specifically the finite field containing 28=2562^8 = 256 elements: GF(28)GF(2^8).

Field Construction and the Primitive Polynomial

In GF(28)GF(2^8), elements are represented as polynomials of degree at most 7 with binary coefficients:

A(x)=a7x7+a6x6+a5x5+a4x4+a3x3+a2x2+a1x+a0where ai∈{0,1}A(x) = a_7 x^7 + a_6 x^6 + a_5 x^5 + a_4 x^4 + a_3 x^3 + a_2 x^2 + a_1 x + a_0 \quad \text{where } a_i \in \{0, 1\}

When two 8-bit polynomials are multiplied, the product can reach degree 14, exceeding the 8-bit byte boundary. To maintain closure, the product is reduced modulo an irreducible primitive polynomial p(x)p(x) that cannot be factored within the field. ISO/IEC 18004 defines p(x)p(x) as:

p(x)=x8+x4+x3+x2+1p(x) = x^8 + x^4 + x^3 + x^2 + 1

In hexadecimal notation, this polynomial corresponds to 0x11D (or 285 in decimal).

Field Operations

  1. Addition and Subtraction: In characteristic-2 finite fields, addition and subtraction are identical and correspond to bitwise XOR (⊕\oplus):
A+B=A−B=A⊕BA + B = A - B = A \oplus B

Crucially, no carry operations exist across bit boundaries.

  1. Multiplication via Log and Antilog Tables: Direct polynomial multiplication followed by modulo reduction with p(x)p(x) is computationally prohibitive during real-time encoding. Instead, because GF(28)GF(2^8) is cyclic, every non-zero field element can be expressed as a power of a primitive element generator α=2\alpha = 2 (00000010200000010_2).

For any two non-zero elements A=αiA = \alpha^i and B=αjB = \alpha^j:

A×B=αi×αj=α(i+j)(mod255)A \times B = \alpha^i \times \alpha^j = \alpha^{(i + j) \pmod{255}}

By precomputing two 256-element lookup tables (an exponentiation/antilog table expTable mapping k→αkk \to \alpha^k and a discrete logarithm table logTable mapping αk→k\alpha^k \to k), multiplication reduces to constant-time table lookups:

function gfMultiply(a: number, b: number, expTable: Uint8Array, logTable: Uint8Array): number {
  if (a === 0 || b === 0) return 0;
  const exponent = (logTable[a] + logTable[b]) % 255;
  return expTable[exponent];
}
                     GALOIS FIELD GF(2^8) MULTIPLICATION
   Byte A ----------------> logTable[A] --------+
                                               |
                                               v
                                         [ Integer Addition ]
                                         [ Modulo 255       ]
                                               |
                                               v
   Byte B ----------------> logTable[B] --------+----> expTable[Sum] ----> Product

Derivation of the Generator Polynomial

To generate ee error-correction parity bytes for a given data block, the encoder constructs a generator polynomial g(x)g(x). The polynomial roots are consecutive powers of the primitive field element α\alpha:

g(x)=∏i=0e−1(x−αi)=(x−α0)(x−α1)(x−α2)…(x−αe−1)g(x) = \prod_{i=0}^{e-1} (x - \alpha^i) = (x - \alpha^0)(x - \alpha^1)(x - \alpha^2)\dots(x - \alpha^{e-1})

For example, generating 2 error-correction bytes (e=2e = 2) requires expanding:

g(x)=(x−1)(x−α)=x2−(1+α)x+α=x2+(α0⊕α1)x+α1g(x) = (x - 1)(x - \alpha) = x^2 - (1 + \alpha)x + \alpha = x^2 + (\alpha^0 \oplus \alpha^1)x + \alpha^1

Since α0=1\alpha^0 = 1 and α1=2\alpha^1 = 2, 1⊕2=3=α251 \oplus 2 = 3 = \alpha^{25}, yielding:

g(x)=x2+α25x+α1g(x) = x^2 + \alpha^{25}x + \alpha^1

Polynomial Synthetic Division

Let the data stream of kk bytes be represented as a message polynomial M(x)M(x) of degree k−1k - 1:

M(x)=mk−1xk−1+mk−2xk−2+⋯+m1x+m0M(x) = m_{k-1}x^{k-1} + m_{k-2}x^{k-2} + \dots + m_1 x + m_0

The message polynomial is shifted left by ee positions (multiplied by xex^e) to reserve space for parity symbols:

xeM(x)=Q(x)g(x)+R(x)x^e M(x) = Q(x) g(x) + R(x)

Where R(x)R(x) is the remainder polynomial of degree at most e−1e - 1. The coefficients of this remainder polynomial R(x)R(x) constitute the exact Reed-Solomon parity codewords appended to the data stream.

/**
 * Computes Reed-Solomon parity codewords for a given data buffer over GF(2^8).
 */
export function computeReedSolomonParity(
  data: Uint8Array,
  parityCount: number,
  expTable: Uint8Array,
  logTable: Uint8Array,
  generatorPoly: Uint8Array
): Uint8Array {
  const remainder = new Uint8Array(parityCount);

  for (let i = 0; i < data.length; i++) {
    const factor = data[i] ^ remainder[0];
    // Shift remainder register left
    for (let j = 0; j < parityCount - 1; j++) {
      remainder[j] = remainder[j + 1];
    }
    remainder[parityCount - 1] = 0;

    if (factor !== 0) {
      const logFactor = logTable[factor];
      for (let j = 0; j < parityCount; j++) {
        const genCoeff = generatorPoly[j];
        if (genCoeff !== 0) {
          const product = expTable[(logTable[genCoeff] + logFactor) % 255];
          remainder[j] ^= product;
        }
      }
    }
  }

  return remainder;
}

4. Error Correction Levels and Reed-Solomon Block Interleaving

ISO/IEC 18004 specifies four distinct Error Correction Levels, granting developers the ability to trade information density for physical recovery capability.

EC LevelParity OverheadApproximate Recovery BudgetTypical Deployment Scenario
Level L (Low)~7% of total codewords~7% corrupted symbolsHigh-density clean displays, mobile screens
Level M (Medium)~15% of total codewords~15% corrupted symbolsDefault general-purpose industrial labeling
Level Q (Quartile)~25% of total codewords~25% corrupted symbolsEnvironments subject to high abrasion or dirt
Level H (High)~30% of total codewords~30% corrupted symbolsBadges with central embedded logos or artwork

Mathematical Error Recovery Bounds

Reed-Solomon codes belong to the family of Maximum Distance Separable (MDS) codes, achieving the optimal theoretical singleton bound. For an MDS code with ee parity symbols:

2t+s≤e2t + s \le e

Where:

  • tt represents the number of unknown symbol errors (both error position and error value are unknown).
  • ss represents the number of known symbol erasures (error position is optically known due to scratches or occlusions, but value is lost).

Therefore, ee parity symbols can correct up to:

  • t=⌊e/2⌋t = \lfloor e / 2 \rfloor random errors, OR
  • s=es = e known erasures, OR
  • Any intermediate linear combination satisfying 2t+s≤e2t + s \le e.

Block Interleaving Architecture

In Version 2 and larger matrices, data is rarely processed as a single monolithic block. Physical damage typically occurs as contiguous spatial abrasions or smudges, creating burst errors. If a single Reed-Solomon block suffered a contiguous 30-byte tear, the recovery threshold for that block would be exceeded, rendering the entire code unreadable.

To combat this, the specification splits data into Group 1 and Group 2 blocks, generates parity independently, and systematically interleaves both data and parity bytes across the physical layout.

   Original Data Stream: [ D1, D2, D3, D4, D5, D6, D7, D8 ]
   Partitioned into 2 Blocks:
     Block A: [ D1, D3, D5, D7 ] --> Parity A: [ PA1, PA2 ]
     Block B: [ D2, D4, D6, D8 ] --> Parity B: [ PB1, PB2 ]

   INTERLEAVED PHYSICAL TRANSMISSION SEQUENCE:
   Data Codewords:   [ D1, D2, D3, D4, D5, D6, D7, D8 ]
   Parity Codewords: [ PA1, PB1, PA2, PB2 ]

When an optical scratch destroys four contiguous bytes on a printed label (for example, destroying bytes at transmission indices 2 through 5: D3,D4,D5,D6D3, D4, D5, D6), the interleaving ensures that Block A and Block B each suffer only two corrupted symbols. Because each block possesses multiple parity words, both blocks recover successfully.

5. Matrix Placement, Masking Heuristics, and In-Browser Rendering

Once the interleaved bitstream is assembled, codewords are mapped into the physical matrix grid. Placement begins at the bottom-right corner and proceeds upward in 2-module-wide columns. When reaching the top border, the track steps two modules to the left and proceeds downward, skipping reserved functional patterns (finders, timing tracks, and alignment markers).

   Module Placement Vector (Zigzag 2-Column Track)
   ^  | [Col 20] [Col 19] |
   |  |   (6)      (5)   |  Upward Phase
   |  |   (4)      (3)   |
   |  |   (2)      (1)   |
   |  +-------------------+
   |  Steps left 2 columns, reverses direction
   |  +-------------------+
   v  |   (7)      (8)   |  Downward Phase
      |   (9)     (10)   |

Optical Confusion and Masking Patterns

If raw data contains long runs of identical bits (such as repeated zeroes from padding), the physical matrix will develop broad swaths of solid black or white modules. Large unmodulated zones destabilize optical sensor gain controls and mimic finder patterns, confusing decoder orientation logic.

To ensure uniform optical entropy, the entire encoding area is XOR-masked with one of eight standardized binary grid functions:

Mask PatternAlgorithmic Condition (Condition==0→Invert ModuleCondition == 0 \to \text{Invert Module})
000(row+col)(mod2)==0(row + col) \pmod 2 == 0
001row(mod2)==0row \pmod 2 == 0
010col(mod3)==0col \pmod 3 == 0
011(row+col)(mod3)==0(row + col) \pmod 3 == 0
100(⌊row/2⌋+⌊col/3⌋)(mod2)==0(\lfloor row / 2 \rfloor + \lfloor col / 3 \rfloor) \pmod 2 == 0
101(row×col)(mod2)+(row×col)(mod3)==0(row \times col) \pmod 2 + (row \times col) \pmod 3 == 0
110((row×col)(mod2)+(row×col)(mod3))(mod2)==0\Big((row \times col) \pmod 2 + (row \times col) \pmod 3\Big) \pmod 2 == 0
111((row+col)(mod2)+(row×col)(mod3))(mod2)==0\Big((row + col) \pmod 2 + (row \times col) \pmod 3\Big) \pmod 2 == 0

The ISO/IEC 18004 Penalty Scoring Model

The encoder generates eight candidate matrices by applying each mask pattern in sequence. Each candidate matrix is evaluated against four strict penalty formulas (N1N_1 through N4N_4):

  1. N1N_1 Penalty (Adjacent Identical Modules): Penalizes runs of 5 or more consecutive modules of the same color in any row or column. Score: 3+(k−5)3 + (k - 5) points for a run of length kk.
  2. N2N_2 Penalty (Block Clusters): Scans for solid 2×22 \times 2 module blocks of identical color. Score: 3×(m−1)(n−1)3 \times (m - 1)(n - 1) for an m×nm \times n block.
  3. N3N_3 Penalty (Finder Pattern Mimicry): Scans for occurrences of the dark-light pattern sequence 10111010000 or 00001011101 in any row or column. Score: 40 points per occurrence.
  4. N4N_4 Penalty (Dark/Light Ratio Imbalance): Calculates the percentage of total dark modules PdarkP_{dark}. Evaluates distance from an ideal 50% distribution:
Deviation=∣Pdark−50∣\text{Deviation} = |P_{dark} - 50| Penalty N4=10×⌊Deviation5⌋\text{Penalty } N_4 = 10 \times \left\lfloor \frac{\text{Deviation}}{5} \right\rfloor

The candidate matrix yielding the lowest total score (N1+N2+N3+N4N_1 + N_2 + N_3 + N_4) is selected as the optimal production pattern. The 3-bit mask index and the 2-bit error-correction tier are encoded together into a 15-bit sequence protected by a (15,5)(15, 5) Bose-Chaudhuri-Hocquenghem (BCH) code, XOR-masked with 101010000010010, and written adjacent to the finder patterns.

Client-Side Matrix Generation

Traditional developer workflows frequently utilize cloud rendering endpoints to convert strings into barcode images. Transmitting proprietary authentication keys, internal device addresses, or sensitive credentials over remote HTTP connections exposes critical data to intermediary logging, caching proxies, and interception.

PetexSpace resolves this through in-browser execution. The entire pipeline, from UTF-8 string tokenization and Galois Field polynomial division to penalty evaluation and canvas rasterization, runs in pure TypeScript within the browser sandbox. Utilizing standard HTML5 Canvas 2D contexts or declarative vector Scalable Vector Graphics (SVG), barcode rendering occurs at native machine execution speeds with guaranteed zero network telemetry.