This document explains how all the pieces fit together to form an Aztec barcode.
An Aztec symbol is built from the center outward:
┌─────────────────────────────────┐
│ │
│ ┌─────────────────────┐ │
│ │ │ │
│ │ ┌─────────────┐ │ │
│ │ │ │ │ │
│ │ │ ┌─────┐ │ │ │
│ │ │ │█████│ │ │ │ Layer 2
│ │ │ │█ █ █│ │ │ │
│ │ │ │█████│ │ │ │ │
│ │ │ │█ █ █│ ← │ ← │ ←────┼── Layer 1
│ │ │ │█████│ │ │ │
│ │ │ └─────┘ │ │ │
│ │ │ Mode Msg │ │ │
│ │ └─────────────┘ │ │
│ │ Finder Pattern │ │
│ └─────────────────────┘ │
│ Data Layers │
└─────────────────────────────────┘
The center of every Aztec symbol has a distinctive pattern of alternating black and white concentric squares.
█████████
█ █
█ █████ █
█ █ █ █
█ █ █ █ █ ← Center module (always black)
█ █ █ █
█ █████ █
█ █
█████████
The pattern follows a simple rule: distance from center determines color.
- Distance 0 (center): Black
- Distance 1: White
- Distance 2: Black
- Distance 3: White
- Distance 4: Black
█████████████
█ █
█ █████████ █
█ █ █ █
█ █ █████ █ █
█ █ █ █ █ █
█ █ █ █ █ █ █ ← Center
█ █ █ █ █ █
█ █ █████ █ █
█ █ █ █
█ █████████ █
█ █
█████████████
Same alternating pattern, extended to distance 6.
Near the finder pattern, small marks indicate which way is "up." This lets scanners read the code from any angle.
For compact symbols, orientation marks are at the corners of the mode message ring:
██
██
█████████
█ █
█ █████ █
█ █ █ █
█ █ █ █ █
█ █ █ █
█ █████ █
█ █
█████████
The marks break the rotational symmetry so there's only one correct orientation.
A ring of modules around the finder pattern encodes metadata about the symbol.
Layout: [2 bits: layers-1] [6 bits: data_codewords-1] [20 bits: RS parity]
The 28 bits are arranged in 4 segments of 7 bits each, placed on the 4 sides of the finder:
←←←←←←←
↑ ↓
███████████████
██ ↓ ██
██ ███████ ↓ ██
██ ██ ██ ↓ ██
↑ ██ ██ █ ██ ██ ↓
↑ ██ ██ ██ ████
↑ ██ ███████ ██
↑ ██ ██
███████████████
→→→→→→→
Layout: [5 bits: layers-1] [11 bits: data_codewords-1] [24 bits: RS parity]
The 40 bits are arranged in 4 segments of 10 bits each.
The mode message has its own Reed-Solomon protection over GF(16):
- Compact: 2 data nibbles + 5 parity nibbles
- Full: 4 data nibbles + 6 parity nibbles
This heavy redundancy ensures scanners can decode the symbol structure even if the center is damaged.
Data is arranged in layers that spiral outward from the mode message.
Each layer is a ring 2 modules wide. The spiral goes counter-clockwise:
← ← ← ← ← ← ←
↓ ↑
↓ ┌───────┐ ↑
↓ │ │ ↑
↓ │ Finder│ ↑
↓ │ │ ↑
↓ └───────┘ ↑
↓ ↑
→ → → → → → →
Within each layer, bits are read in a specific pattern, two modules at a time.
Codewords are placed MSB-first (most significant bit first). For a 6-bit codeword like 101011:
Position: [1][0][1][0][1][1]
↓
First bit placed is '1' (MSB)
Last bit placed is '1' (LSB)
For full symbols with many layers, alignment can be tricky. The reference grid helps scanners stay on track.
Every 16 modules from the center, horizontal and vertical lines of alternating black/white modules are drawn:
█ █ █ █ █ █ █ █ █ █ █ █ █
█ █
█ ███████ █
██ ██
█ ██ █ ██ █
██ ██
█ ███████ █
█ █
█ █ █ █ █ █ █ █ █ █ █ █ █
Full symbols only (layers 4-32):
- Layers 4-15: No reference grid
- Layers 16-30: 1 reference grid line each direction
- Layers 31-32: 2 reference grid lines each direction
The grid lines interrupt data placement—codeword bits skip over grid positions.
| Layers | Size | Formula |
|---|---|---|
| 1 | 15×15 | 11 + 4×1 |
| 2 | 19×19 | 11 + 4×2 |
| 3 | 23×23 | 11 + 4×3 |
| 4 | 27×27 | 11 + 4×4 |
Formula: size = 11 + 4 × layers
Note: Full symbols start at layer 4 (layers 1-3 are not available due to coordinate overlap with the mode message ring).
| Layers | Size | Ref Grid Lines |
|---|---|---|
| 4 | 31×31 | 0 |
| 5 | 37×37 | 1 |
| 10 | 57×57 | 1 |
| 15 | 79×79 | 2 |
| 16 | 83×83 | 2 |
| 20 | 101×101 | 3 |
| 31 | 147×147 | 4 |
| 32 | 151×151 | 4 |
Formula (per ZXing):
baseMatrixSize = 14 + 4 × layers
refLines = ⌊(baseMatrixSize / 2 - 1) / 15⌋
size = baseMatrixSize + 1 + 2 × refLines
The extra term accounts for reference grid lines.
Here's the complete rendering sequence:
let size = symbolSize // e.g., 15 for compact 1-layer
var matrix = Array(repeating: Array(repeating: false, count: size), count: size)let center = size / 2
for y in (center-radius)...(center+radius) {
for x in (center-radius)...(center+radius) {
let distance = max(abs(x-center), abs(y-center))
matrix[y][x] = (distance % 2 == 0) // Even distance = black
}
}Place distinctive patterns to break rotational symmetry.
Encode layers and data codeword count, add RS parity, place around finder.
for gridLine in gridPositions {
// Horizontal line
for x in 0..<size {
if !isInFinder(x, gridLine) {
matrix[gridLine][x] = (x % 2 == 0)
}
}
// Vertical line (same pattern)
}var path = buildSpiralPath() // Counter-clockwise from center out
var pathIndex = 0
for codeword in allCodewords {
for bit in codeword.bits(msbFirst: true) {
let (x, y) = path[pathIndex]
matrix[y][x] = bit
pathIndex += 1
}
}The symbol uses a standard coordinate system:
- Origin (0, 0) is top-left
- X increases rightward
- Y increases downward
(0,0) → → → (size-1, 0)
↓
↓
↓
(0, size-1) → → → (size-1, size-1)
Each row is packed into bytes with bit 0 being the leftmost module:
Modules: [M0][M1][M2][M3][M4][M5][M6][M7]
Byte: b0 b1 b2 b3 b4 b5 b6 b7
To check module x: (byte[x/8] >> (x%8)) & 1
Bit 7 is the leftmost module:
Modules: [M0][M1][M2][M3][M4][M5][M6][M7]
Byte: b7 b6 b5 b4 b3 b2 b1 b0
To check module x: (byte[x/8] >> (7 - x%8)) & 1
Use MSB-first when generating PNG images, as most image formats expect this ordering.
- Finder pattern at center identifies and orients the symbol
- Orientation marks break symmetry for rotation detection
- Mode message encodes symbol metadata with heavy error protection
- Reference grid (full symbols) helps scanner alignment
- Data layers spiral counter-clockwise from center outward
- Codewords are placed MSB-first within the spiral path
The result is a robust 2D barcode that can be read from any angle, even when partially damaged!