diff options
Diffstat (limited to 'docs/handbook/genqrcode/masking-algorithms.md')
| -rw-r--r-- | docs/handbook/genqrcode/masking-algorithms.md | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/docs/handbook/genqrcode/masking-algorithms.md b/docs/handbook/genqrcode/masking-algorithms.md new file mode 100644 index 0000000000..120f774bf6 --- /dev/null +++ b/docs/handbook/genqrcode/masking-algorithms.md @@ -0,0 +1,578 @@ +# genqrcode / libqrencode — Masking Algorithms + +## Purpose + +After placing data and error correction codewords in the QR Code matrix, a mask pattern is XORed with the data area to avoid unfavorable patterns (large uniform regions, patterns resembling finder patterns). The library evaluates all candidate masks using a penalty scoring system and selects the mask with the lowest penalty. + +--- + +## Full QR Code — 8 Mask Patterns + +### The MASKMAKER Macro + +All 8 mask condition functions are generated by a single macro in `mask.c`: + +```c +#define MASKMAKER(__exp__) \ + int x, y;\ + int b = 0;\ + for(y = 0; y < width; y++) {\ + for(x = 0; x < width; x++) {\ + if(*s & 0x80) {\ + *d = *s;\ + } else {\ + *d = *s ^ ((__exp__) == 0);\ + }\ + s++; d++;\ + }\ + }\ + return b; +``` + +The `0x80` bit check skips non-data modules (finder patterns, timing patterns, alignment patterns, format/version info). The expression `(__exp__) == 0` evaluates the mask condition — when the condition is true (equals 0), the module is flipped. + +### Mask Pattern Definitions + +```c +static int Mask_mask0(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER((y+x) % 2) +} + +static int Mask_mask1(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER(y % 2) +} + +static int Mask_mask2(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER(x % 3) +} + +static int Mask_mask3(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER((y+x) % 3) +} + +static int Mask_mask4(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER((y/2 + x/3) % 2) +} + +static int Mask_mask5(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER(((y*x) % 2 + (y*x) % 3)) +} + +static int Mask_mask6(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER(((y*x) % 2 + (y*x) % 3) % 2) +} + +static int Mask_mask7(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER(((y*x) % 3 + (y+x) % 2) % 2) +} +``` + +Function pointer array: + +```c +typedef int MaskMaker(int, const unsigned char *, unsigned char *); +static MaskMaker *maskMakers[8] = { + Mask_mask0, Mask_mask1, Mask_mask2, Mask_mask3, + Mask_mask4, Mask_mask5, Mask_mask6, Mask_mask7 +}; +``` + +### Summary Table + +| Mask | Condition (module inverted when true) | Pattern | +|---|---|---| +| 0 | `(y + x) % 2 == 0` | Checkerboard | +| 1 | `y % 2 == 0` | Horizontal stripes | +| 2 | `x % 3 == 0` | Vertical stripes (every 3) | +| 3 | `(y + x) % 3 == 0` | Diagonal stripes | +| 4 | `(y/2 + x/3) % 2 == 0` | Block pattern | +| 5 | `(y*x)%2 + (y*x)%3 == 0` | Complex | +| 6 | `((y*x)%2 + (y*x)%3) % 2 == 0` | Complex | +| 7 | `((y*x)%3 + (y+x)%2) % 2 == 0` | Complex | + +--- + +## Penalty Scoring + +### Penalty Constants + +Defined in `mask.c`: + +```c +#define N1 3 +#define N2 3 +#define N3 40 +#define N4 10 +``` + +### Penalty Rule N1 + N3: Run Length + +`Mask_calcN1N3()` evaluates both Rule 1 (adjacent same-color modules) and Rule 3 (finder-like patterns) using run-length data: + +```c +static int Mask_calcN1N3(int length, int *runLength) +{ + int i; + int demerit = 0; + int fact; + + for(i = 0; i < length; i++) { + if(runLength[i] >= 5) { + demerit += N1 + (runLength[i] - 5); + } + if((i & 1)) { + // Check for 1:1:3:1:1 pattern embedded in dark-light sequence + if(i >= 3 && i < length - 2 + && (runLength[i] % 3) == 0) { + fact = runLength[i] / 3; + if(runLength[i-2] == fact && + runLength[i-1] == fact && + runLength[i+1] == fact && + runLength[i+2] == fact) { + // Check for 4-module light space on either side + if(i == 3 || runLength[i-3] >= 4 * fact) { + demerit += N3; + } else if(i+4 >= length || runLength[i+3] >= 4 * fact) { + demerit += N3; + } + } + } + } + } + return demerit; +} +``` + +**Rule N1**: Run of ≥ 5 same-color modules → penalty = 3 + (run_length − 5). For example, 7 consecutive dark modules → 3 + 2 = 5 penalty. + +**Rule N3**: Pattern 1:1:3:1:1 (the finder pattern ratio) with 4+ light modules on either side → 40 penalty. This prevents patterns that confuse QR Code scanners. + +### Run Length Calculation + +Horizontal runs via `Mask_calcRunLengthH()`: + +```c +static int Mask_calcRunLengthH(int width, const unsigned char *frame, int *runLength) +{ + int i; + int head; + int prev; + + if(frame[0] & 1) { + runLength[0] = -1; + head = 1; + } else { + head = 0; + } + runLength[head] = 1; + prev = frame[0]; + + for(i = 1; i < width; i++) { + if((frame[i] ^ prev) & 1) { + head++; + runLength[head] = 1; + prev = frame[i]; + } else { + runLength[head]++; + } + } + return head + 1; +} +``` + +Vertical runs via `Mask_calcRunLengthV()` — same logic but iterates `frame[i * width]`. + +### Penalty Rule N2: 2×2 Blocks + +`Mask_calcN2()` counts 2×2 same-color blocks: + +```c +static int Mask_calcN2(int width, unsigned char *frame) +{ + int x, y; + int demerit = 0; + unsigned char *p; + + p = frame; + for(y = 1; y < width; y++) { + for(x = 1; x < width; x++) { + // Check 2x2 block using bit 0 + if(((p[0]^p[1])|(p[width]^p[width+1])) & 1) { + // not all same + } else { + demerit += N2; + } + p++; + } + p++; + } + return demerit; +} +``` + +Each 2×2 same-color block adds N2 = 3 penalty points. + +### Penalty Rule N4: Dark/Light Balance + +The `Mask_evaluateSymbol()` function counts dark modules and applies the balance penalty: + +```c +static int Mask_evaluateSymbol(int width, unsigned char *frame) +{ + int x, y; + int demerit = 0; + int length; + int runLength[width + 1]; + unsigned char *p; + int dark = 0; + + demerit += Mask_calcN2(width, frame); + + p = frame; + for(y = 0; y < width; y++) { + length = Mask_calcRunLengthH(width, p, runLength); + demerit += Mask_calcN1N3(length, runLength); + p += width; + } + + for(x = 0; x < width; x++) { + length = Mask_calcRunLengthV(width, frame + x, runLength); + demerit += Mask_calcN1N3(length, runLength); + } + + // Count dark modules for N4 + p = frame; + for(y = 0; y < width * width; y++) { + if(p[y] & 1) dark++; + } + + // Calculate demerits for N4 + // dark ratio in percent, deviation from 50% + int ratio = (200 * dark + width * width) / (2 * width * width) - 50; + if(ratio < 0) ratio = -ratio; + demerit += ratio / 5 * N4; + + return demerit; +} +``` + +N4 penalty: For each 5% deviation from 50% dark/light balance, add N4 = 10 points. + +--- + +## Mask Selection Algorithm + +`Mask_mask()` tries all 8 patterns and selects the best: + +```c +unsigned char *Mask_mask(int width, unsigned char *frame, QRecLevel level) +{ + int i; + unsigned char *mask, *bestMask; + int minDemerit = INT_MAX; + int bestMaskNum = 0; + int blacks; + int bratio; + int demerit; + + bestMask = NULL; + + for(i = 0; i < 8; i++) { + mask = (unsigned char *)malloc(width * width); + if(mask == NULL) break; + + demerit = 0; + blacks = maskMakers[i](width, frame, mask); + demerit = Mask_evaluateSymbol(width, mask); + + if(demerit < minDemerit) { + minDemerit = demerit; + free(bestMask); + bestMask = mask; + bestMaskNum = i; + } else { + free(mask); + } + } + + Mask_writeFormatInformation(width, bestMask, bestMaskNum, level); + + return bestMask; +} +``` + +Key points: +- Allocates a new `width × width` buffer for each mask attempt +- Applies the mask via `maskMakers[i]` +- Evaluates penalty via `Mask_evaluateSymbol()` +- Keeps only the lowest-demerit mask, frees the rest +- Writes format information into the selected mask + +### Forced Mask + +`QRcode_encodeMask()` accepts a `mask` parameter. When >= 0, it skips penalty evaluation and uses the specified mask directly: + +```c +if(mask < 0) { + masked = Mask_mask(width, frame, input->level); +} else { + masked = Mask_makeMask(width, frame, mask, input->level); +} +``` + +`Mask_makeMask()` applies a single mask without evaluation: + +```c +unsigned char *Mask_makeMask(int width, unsigned char *frame, int mask, + QRecLevel level) +{ + unsigned char *masked = (unsigned char *)malloc(width * width); + maskMakers[mask](width, frame, masked); + Mask_writeFormatInformation(width, masked, mask, level); + return masked; +} +``` + +This is used by the test suite via `QRcode_encodeMask()` (exposed in `qrencode_inner.h`). + +--- + +## Format Information Writing + +`Mask_writeFormatInformation()` embeds the 15-bit format info (EC level + mask pattern) into the symbol at two fixed locations: + +```c +void Mask_writeFormatInformation(int width, unsigned char *frame, + int mask, QRecLevel level) +{ + unsigned int format; + unsigned char v; + int i; + + format = QRspec_getFormatInfo(mask, level); + + // Horizontal strip near top-left + for(i = 0; i < 8; i++) { + // ... write bits to specific positions around top-left finder ... + } + + // Vertical strip near top-left and bottom-left + for(i = 0; i < 7; i++) { + // ... write bits to specific positions ... + } +} +``` + +The format info is retrieved from the pre-computed `formatInfo[level][mask]` table (15-bit BCH code). + +--- + +## Micro QR Code — 4 Mask Patterns + +### Mask Definitions + +From `mmask.c`, only 4 patterns: + +```c +static int MMask_mask0(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER(y % 2) +} + +static int MMask_mask1(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER(((y/2) + (x/3)) % 2) +} + +static int MMask_mask2(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER((((y*x) % 2) + ((y*x) % 3)) % 2) +} + +static int MMask_mask3(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER((((y+x) % 2) + ((y*x) % 3)) % 2) +} +``` + +Function pointer array: +```c +static MaskMaker *maskMakers[4] = { + MMask_mask0, MMask_mask1, MMask_mask2, MMask_mask3 +}; +``` + +| Mask | Condition | +|---|---| +| 0 | `y % 2 == 0` | +| 1 | `(y/2 + x/3) % 2 == 0` | +| 2 | `((y*x)%2 + (y*x)%3) % 2 == 0` | +| 3 | `((y+x)%2 + (y*x)%3) % 2 == 0` | + +### Micro QR Mask Evaluation + +Micro QR uses a completely different scoring method. Instead of penalty rules, it maximizes a quality metric. + +`MMask_evaluateSymbol()`: + +```c +static int MMask_evaluateSymbol(int width, unsigned char *frame) +{ + int x, y; + int sum1 = 0, sum2 = 0; + + // Sum of bottom row (last row, only data area) + for(x = 1; x < width; x++) { + if(frame[width * (width-1) + x] & 1) { + sum1++; + } + } + // Sum of rightmost column (only data area) + for(y = 1; y < width; y++) { + if(frame[y * width + (width-1)] & 1) { + sum2++; + } + } + + return sum1 * 16 + sum2; +} +``` + +The score favors masks that produce more dark modules along the bottom row and right column. **Unlike full QR, higher scores are better.** + +### Micro QR Mask Selection + +`MMask_mask()`: + +```c +unsigned char *MMask_mask(int version, unsigned char *frame, QRecLevel level) +{ + int width = MQRspec_getWidth(version); + int i; + unsigned char *mask, *bestMask = NULL; + int maxScore = 0; + int bestMaskNum = 0; + int score; + + for(i = 0; i < 4; i++) { + mask = (unsigned char *)malloc(width * width); + maskMakers[i](width, frame, mask); + score = MMask_evaluateSymbol(width, mask); + + if(score > maxScore) { // Note: MAXIMUM, not minimum + maxScore = score; + free(bestMask); + bestMask = mask; + bestMaskNum = i; + } else { + free(mask); + } + } + + MMask_writeFormatInformation(version, bestMask, bestMaskNum, level); + + return bestMask; +} +``` + +### Micro QR Format Information + +`MMask_writeFormatInformation()` writes a 15-bit format info in a single strip around the top-left finder pattern: + +```c +void MMask_writeFormatInformation(int version, unsigned char *frame, + int mask, QRecLevel level) +{ + unsigned int format; + unsigned char v; + int i; + int width = MQRspec_getWidth(version); + + format = MQRspec_getFormatInfo(mask, version, level); + + for(i = 0; i < 8; i++) { + v = 0x84 | (format & 1); + frame[width * (i + 1) + 8] = v; // left column + format >>= 1; + } + for(i = 0; i < 7; i++) { + v = 0x84 | (format & 1); + frame[width * 8 + 7 - i] = v; // top row + format >>= 1; + } +} +``` + +--- + +## Module Bit Flags + +When placing modules, the frame builder uses bit flags to mark module types: + +```c +// Bit 7 (0x80): Non-data module flag +// When set, masking skips this module +// Set on: finder pattern, separator, timing, alignment, version info, format info +``` + +In `MASKMAKER`, this check appears as: +```c +if(*s & 0x80) { + *d = *s; // copy as-is (non-data module) +} else { + *d = *s ^ ((__exp__) == 0); // apply mask +} +``` + +This ensures that only the data and ECC area is affected by masking. + +--- + +## Integration with Encoding Pipeline + +In `QRcode_encodeMask()` (from `qrencode.c`): + +```c +QRcode *QRcode_encodeMask(QRinput *input, int mask) +{ + // ... setup ... + + // 1. Build frame with function patterns + frame = QRspec_createFrame(version); + + // 2. Place data/ECC codewords via FrameFiller + filler = FrameFiller_new(width, frame, 0); + for(i = 0; i < raw->dataLength + raw->eccLength; i++) { + code = QRraw_getCode(raw); + bit = 0x80; + for(j = 0; j < 8; j++) { + p = FrameFiller_next(filler); + if(p == NULL) goto EXIT; + *p = 0x02 | ((bit & code) != 0); + bit >>= 1; + } + } + // ... remainder bits ... + + // 3. Apply mask + if(mask < 0) { + masked = Mask_mask(width, frame, input->level); + } else { + masked = Mask_makeMask(width, frame, mask, input->level); + } + + // 4. Package result + qrcode = QRcode_new(version, width, masked); + // ... +} +``` + +The same flow applies for `QRcode_encodeMaskMQR()`, using `MMask_mask()` instead. |
