diff options
Diffstat (limited to 'docs/handbook/genqrcode/micro-qr.md')
| -rw-r--r-- | docs/handbook/genqrcode/micro-qr.md | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/docs/handbook/genqrcode/micro-qr.md b/docs/handbook/genqrcode/micro-qr.md new file mode 100644 index 0000000000..d202ceb76c --- /dev/null +++ b/docs/handbook/genqrcode/micro-qr.md @@ -0,0 +1,456 @@ +# genqrcode / libqrencode — Micro QR Code Support + +## Overview + +Micro QR Code is a compact variant of QR Code standardized in JIS X0510:2004 and ISO/IEC 18004. libqrencode supports Micro QR versions M1 through M4 via dedicated spec tables in `mqrspec.c`, masking in `mmask.c`, and encoding paths in `qrencode.c`. + +--- + +## Micro QR vs. Full QR + +| Feature | Full QR Code | Micro QR Code | +|---|---|---| +| Versions | 1–40 | M1–M4 (1–4 internally) | +| Max constant | `QRSPEC_VERSION_MAX = 40` | `MQRSPEC_VERSION_MAX = 4` | +| Finder patterns | 3 | 1 | +| Alignment patterns | 0–46 per version | None | +| Timing patterns | Horizontal + Vertical | Horizontal + Vertical | +| Version information | Versions 7+ | Never | +| Mask patterns | 8 | 4 | +| Mask selection | Minimize penalty | Maximize edge score | +| EC Levels | L, M, Q, H | Version-dependent subset | +| Structured append | Supported | Not supported | +| ECI mode | Supported | Not supported | +| FNC1 mode | Supported | Not supported | + +--- + +## Version Capacities + +From `mqrspec.c`: + +```c +typedef struct { + int width; + int ec[4]; +} MQRspec_Capacity; + +static const MQRspec_Capacity mqrspecCapacity[MQRSPEC_VERSION_MAX + 1] = { + {0, { 0, 0, 0, 0}}, + {11, { 2, 0, 0, 0}}, // M1: 11×11 + {13, { 5, 4, 0, 0}}, // M2: 13×13 + {15, {11, 9, 7, 0}}, // M3: 15×15 + {17, {16, 14, 10, 0}} // M4: 17×17 +}; +``` + +Width formula: `version * 2 + 9` (vs. `version * 4 + 17` for full QR). + +### Detailed Capacities + +| Version | Width | Total Data Words | L Data | M Data | Q Data | H Data | +|---|---|---|---|---|---|---| +| M1 | 11 | 5 | 2* | — | — | — | +| M2 | 13 | 10 | 5 | 4 | — | — | +| M3 | 15 | 17 | 11 | 9 | 7 | — | +| M4 | 17 | 24 | 16 | 14 | 10 | — | + +*M1 has error detection only, not error correction. + +### ECC Lengths + +```c +int MQRspec_getECCLength(int version, QRecLevel level) +{ + return mqrspecCapacity[version].width * mqrspecCapacity[version].width + - mqrspecCapacity[version].ec[level] + - ... (function pattern modules) ...; +} +``` + +Returns 0 for unsupported version/level combinations, which `QRinput_newMQR()` uses to reject invalid inputs. + +### Character Capacities by Mode + +| Version | Level | Numeric | Alphanumeric | Byte | Kanji | +|---|---|---|---|---|---| +| M1 | — | 5 | — | — | — | +| M2 | L | 10 | 6 | — | — | +| M2 | M | 8 | 5 | — | — | +| M3 | L | 23 | 14 | 9 | 6 | +| M3 | M | 18 | 11 | 7 | 4 | +| M4 | L | 35 | 21 | 15 | 9 | +| M4 | M | 30 | 18 | 13 | 8 | +| M4 | Q | 21 | 12 | 9 | 5 | + +--- + +## Mode Restrictions + +Not all encoding modes are available in every Micro QR version: + +| Version | Numeric | Alphanumeric | 8-bit | Kanji | ECI | FNC1 | Structured | +|---|---|---|---|---|---|---|---| +| M1 | Yes | No | No | No | No | No | No | +| M2 | Yes | Yes | No | No | No | No | No | +| M3 | Yes | Yes | Yes | Yes | No | No | No | +| M4 | Yes | Yes | Yes | Yes | No | No | No | + +This is enforced by `QRinput_encodeBitStream()` which calls validation functions: + +```c +static int QRinput_isModeNumValid(int version, QRinput_List *entry, int mqr) +{ + if(mqr) { + if(MQRspec_maximumWords(QR_MODE_NUM, version) < entry->size) + return -1; + } + return 0; +} +``` + +`MQRspec_maximumWords()` returns 0 for unsupported modes at a given version. + +--- + +## MQR-Specific Encoding + +### Mode Indicator Sizes + +Micro QR uses shorter mode indicators than full QR. From `mqrspec.c`: + +```c +// MQR mode indicator bit lengths per version: +// M1: 0 bits (only numeric, implied) +// M2: 1 bit +// M3: 2 bits +// M4: 3 bits +``` + +These are retrieved by `MQRspec_lengthIndicator()`. + +### Character Count Indicator Sizes + +```c +static const int lengthTableBits[4][4] = { + { 3, 0, 0, 0}, // QR_MODE_NUM: M1=3, M2=4, M3=5, M4=6 + { 0, 3, 0, 0}, // QR_MODE_AN: M1=0, M2=3, M3=4, M4=5 + { 0, 0, 4, 0}, // QR_MODE_8: M1=0, M2=0, M3=4, M4=5 + { 0, 0, 3, 0}, // QR_MODE_KANJI: M1=0, M2=0, M3=3, M4=4 +}; +``` + +A value of 0 means the mode is unsupported for that version. + +### Data Length in Bits + +A key difference: `MQRspec_getDataLengthBit()` returns data length in **bits**, not bytes: + +```c +int MQRspec_getDataLengthBit(int version, QRecLevel level) +{ + int w = mqrspecCapacity[version].width - 1; + return w * w - 64 - MQRspec_getECCLength(version, level) * 8; +} +``` + +This matters because M1 and some M2 configurations have data lengths that are not byte-aligned. + +--- + +## MQRRawCode — Micro QR Block Structure + +From `qrencode.c`: + +```c +typedef struct { + int version; + int dataLength; + int eccLength; + unsigned char *datacode; + unsigned char *ecccode; + int b1; + int rsblock_num; + RSblock *rsblock; + int count; + int oddbits; // Number of "odd" bits in last data byte +} MQRRawCode; +``` + +Micro QR always has exactly **one RS block** (no block interleaving): + +```c +static MQRRawCode *MQRraw_new(QRinput *input) +{ + MQRRawCode *raw; + raw->version = input->version; + raw->dataLength = MQRspec_getDataLength(input->version, input->level); + raw->eccLength = MQRspec_getECCLength(input->version, input->level); + raw->oddbits = raw->dataLength * 8 - MQRspec_getDataLengthBit(input->version, input->level); + + raw->datacode = QRinput_getByteStream(input); + raw->ecccode = (unsigned char *)malloc(raw->eccLength); + raw->rsblock_num = 1; + raw->rsblock = calloc(1, sizeof(RSblock)); + + RSblock_initBlock(raw->rsblock, raw->dataLength, raw->datacode, + raw->eccLength, raw->ecccode, RSECC_encode); + raw->count = 0; + return raw; +} +``` + +### Odd Bits Handling + +The `oddbits` field handles versions where data capacity is not byte-aligned: + +```c +unsigned char MQRraw_getCode(MQRRawCode *raw) +{ + if(raw->count < raw->dataLength) { + return raw->datacode[raw->count++]; + } else { + return raw->ecccode[raw->count++ - raw->dataLength]; + } +} +``` + +In `QRcode_encodeMaskMQR()`, the odd bits are handled after placing full codewords: + +```c +j = MQRspec_getDataLengthBit(input->version, input->level) + - MQRraw_getDataLength(raw) * 8; +if(j > 0) { + // Place remaining odd bits from last data byte + code = MQRraw_getCode(raw); + bit = 0x80; + for(i = 0; i < j; i++) { + p = FrameFiller_next(filler); + *p = 0x02 | ((code & bit) != 0); + bit >>= 1; + } +} +``` + +--- + +## Frame Creation + +`MQRspec_createFrame()` builds the base frame with function patterns: + +```c +unsigned char *MQRspec_createFrame(int version) +{ + unsigned char *frame; + int width = mqrspecCapacity[version].width; + + frame = (unsigned char *)calloc(width * width, 1); + + // 1. Finder pattern (only ONE, top-left) + putFinderPattern(frame, width, 0, 0); + + // 2. Separator (no full separator ring — only right and bottom) + + // 3. Timing pattern (horizontal and vertical) + for(int i = 0; i < width - 8; i++) { + // horizontal timing along row 0, starting at column 8 + frame[8 + i] = 0x90 | (i & 1); + // vertical timing along column 0, starting at row 8 + frame[(8 + i) * width] = 0x90 | (i & 1); + } + + // 4. Format information area (reserved) + // No version information (unlike QR versions 7+) + // No alignment patterns (unlike QR versions 2+) + + return frame; +} +``` + +Key differences from `QRspec_createFrame()`: +- Single finder pattern instead of three +- No alignment patterns +- No version information area +- Simpler separator structure + +--- + +## Micro QR Masking + +See [masking-algorithms.md](masking-algorithms.md) for the full details. Summary: + +### 4 Patterns + +```c +MMask_mask0: y % 2 == 0 +MMask_mask1: (y/2 + x/3) % 2 == 0 +MMask_mask2: ((y*x)%2 + (y*x)%3) % 2 == 0 +MMask_mask3: ((y+x)%2 + (y*x)%3) % 2 == 0 +``` + +### Selection Criterion + +Micro QR picks the mask with the **highest** score (opposite of full QR): + +```c +// Sum of dark modules in bottom row × 16 + sum of dark modules in right column +score = sum1 * 16 + sum2; +``` + +The right column gets lower weight (×1) than the bottom row (×16). + +--- + +## Format Information + +Micro QR encodes version, EC level, and mask pattern in a single 15-bit format info: + +```c +static const unsigned int typeTable[MQRSPEC_VERSION_MAX + 1][3] = { + {0x00000, 0x00000, 0x00000}, // unused + {0x04445, 0x04172, 0x04e2b}, // M1 + {0x02f7f, 0x02a48, 0x02511}, // M2 + {0x07f46, 0x07a71, 0x07528}, // M3 + {0x00dc5, 0x008f2, 0x007ab} // M4 +}; +``` + +Indexed as `typeTable[version][typeNumber]` where `typeNumber` depends on the EC level: + +```c +unsigned int MQRspec_getFormatInfo(int mask, int version, QRecLevel level) +{ + // ... compute typeNumber from version and level ... + // ... XOR with mask-dependent pattern ... +} +``` + +Written into the symbol by `MMask_writeFormatInformation()` in a single strip around the finder pattern (8 bits on the left side, 7 bits on the top). + +--- + +## API for Micro QR + +### Input Creation + +```c +QRinput *input = QRinput_newMQR(int version, QRecLevel level); +``` + +- `version` must be 1–4 (no auto-detection for manual input) +- Invalid version/level combinations are rejected + +### High-Level Encoding + +```c +QRcode *QRcode_encodeStringMQR(const char *string, int version, + QRecLevel level, QRencodeMode hint, + int casesensitive); +QRcode *QRcode_encodeString8bitMQR(const char *string, int version, + QRecLevel level); +QRcode *QRcode_encodeDataMQR(int size, const unsigned char *data, + int version, QRecLevel level); +``` + +When `version` is 0, these functions try versions M1 through M4 incrementally: + +```c +if(version == 0) { + for(i = 1; i <= MQRSPEC_VERSION_MAX; i++) { + QRcode *code = QRcode_encodeDataReal(data, size, i, level, 1); + if(code != NULL) return code; + } +} +``` + +### Version/Level Validation + +Use `QRinput_setVersionAndErrorCorrectionLevel()` for Micro QR — it validates the combination. Using `QRinput_setVersion()` or `QRinput_setErrorCorrectionLevel()` individually on MQR inputs returns `EINVAL`. + +--- + +## Structured Append — Not Supported + +Micro QR does not support structured append mode. Attempting to encode structured append with MQR results in an error: + +```c +static int QRinput_encodeModeStructure(QRinput_List *entry, int mqr) +{ + if(mqr) { + errno = EINVAL; + return -1; + } + // ... +} +``` + +`QRinput_Struct_appendInput()` also rejects MQR inputs: + +```c +int QRinput_Struct_appendInput(QRinput_Struct *s, QRinput *input) +{ + if(input == NULL || input->mqr) { + errno = EINVAL; + return -1; + } + // ... +} +``` + +--- + +## Encoding Pipeline Differences + +`QRcode_encodeMaskMQR()` in `qrencode.c` follows a modified pipeline: + +1. **Create frame**: `MQRspec_createFrame(version)` — single finder, no alignment +2. **Initialize RS**: `MQRraw_new(input)` — single block, with odd bits tracking +3. **Place data**: Via `FrameFiller_next()`, but handles odd bits at boundary: + ```c + // Place full data bytes + for(i = 0; i < MQRraw_getDataLength(raw) - 1; i++) { + code = MQRraw_getCode(raw); + bit = 0x80; + for(j = 0; j < 8; j++) { + p = FrameFiller_next(filler); + *p = 0x02 | ((bit & code) != 0); + bit >>= 1; + } + } + // Handle odd bits from last data byte + ``` +4. **Place ECC**: Full ECC bytes, then remainder bits (if any) +5. **Apply mask**: `MMask_mask(version, frame, level)` — 4 patterns, maximize score +6. **Package**: `QRcode_new(version, width, masked)` + +--- + +## CLI Support + +The `qrencode` CLI tool supports Micro QR via the `-M` / `--micro` flag: + +```bash +qrencode -M -v 3 -l M -o output.png "Hello" +``` + +In `qrenc.c`: + +```c +case 'M': + micro = 1; + break; +``` + +When `micro` is set, `encode()` calls `QRcode_encodeStringMQR()` or `QRcode_encodeDataMQR()` instead of the standard variants. + +--- + +## Limitations Summary + +1. **No H level**: Maximum EC is Q (M4 only) +2. **No structured append**: Cannot split data across multiple symbols +3. **No ECI**: Cannot specify character encodings +4. **No FNC1**: Cannot create GS1-compatible codes +5. **Small capacity**: Maximum 35 numeric or 15 byte characters (M4-L) +6. **Single finder**: Only top-left finder pattern — orientation from timing patterns +7. **Version must be specified**: For `QRinput_newMQR()`, version 0 is not auto-detect (though high-level `QRcode_encodeStringMQR()` with version 0 does try all versions) |
