summaryrefslogtreecommitdiff
path: root/source/codes/Qr.mc
diff options
context:
space:
mode:
Diffstat (limited to 'source/codes/Qr.mc')
-rw-r--r--source/codes/Qr.mc971
1 files changed, 680 insertions, 291 deletions
diff --git a/source/codes/Qr.mc b/source/codes/Qr.mc
index 1fa69da..1fac051 100644
--- a/source/codes/Qr.mc
+++ b/source/codes/Qr.mc
@@ -9,11 +9,14 @@
// codewords 0xEC 0x11), split it into blocks, add Reed-Solomon
// codewords (QrTables.BLOCKS), interleave.
// 3. Draw the function patterns, place the codewords in the zigzag.
-// 4. Score all 8 masks with the ISO 18004:2015 penalty rules exactly as
-// segno computes them (format and version areas still light), keep
-// the lowest, then draw the format and version information.
+// 4. Pack the rows and columns 16 modules per word, then score all 8
+// masks with the ISO 18004:2015 penalty rules exactly as segno
+// computes them (format and version areas still light), with bit
+// operations on the packed lines; keep the lowest.
+// 5. Apply that mask, then draw the format and version information.
// Each step does a bounded amount of work (one RS block, a few columns or
-// rows), because version 40 is far beyond one event's watchdog budget.
+// rows, a few hundred packed words), because version 40 is far beyond one
+// event's watchdog budget.
// Verified against segno with forced version and mask, and by decoding.
import Toybox.Lang;
@@ -26,15 +29,31 @@ module Qr {
const ALPHANUMERIC = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; // lint: allow-literal (QR alphanumeric set)
// Format bits per level L, M, Q, H.
const LEVEL_BITS = [1, 0, 3, 2];
- // Modules looked at per job step: well below the 120,000-bytecode
- // watchdog limit of the smallest targets.
- const MODULES_PER_STEP = 2500;
- // Mask scoring costs more per module: the row pass applies the mask and
- // scores N1-N3, the column pass N1 and N3. Sized for the Forerunner
- // 255S's 120,000 bytecodes per event, where 1,200 row modules tripped
- // the watchdog in the UI flow.
- const ROW_MODULES_PER_STEP = 500;
- const COLUMN_MODULES_PER_STEP = 1000;
+ // Modules placed, packed or unmasked per job step. Every step size here
+ // was checked in the simulator by running two steps per event on the
+ // Forerunner 255S (120,000 bytecodes per event): each step stays below
+ // half the watchdog limit.
+ const PLACE_MODULES_PER_STEP = 300;
+ const PACK_MODULES_PER_STEP = 400;
+ const APPLY_MODULES_PER_STEP = 600;
+ // Mask scoring works on packed lines (16 modules per word, see
+ // startPacking); words scored per step. The row pass masks the line and
+ // scores N1, N2, N3 and the dark count, the column pass N1 and N3.
+ const ROW_WORDS_PER_STEP = 100;
+ const COLUMN_WORDS_PER_STEP = 150;
+ // Light bits before each packed line.
+ const PAD = 4;
+ // Phase 0: characters segmented or written per step (the segmentation
+ // costs about 150 bytecodes per character).
+ const SEGMENT_CHARS_PER_STEP = 80;
+ const WRITE_CHARS_PER_STEP = 300;
+ // Phase 1: data symbols times EC symbols per step (about 35 bytecodes each).
+ const ECC_WORK_PER_STEP = 400;
+ // Phase 2: codewords interleaved per step.
+ const INTERLEAVE_PER_STEP = 600;
+ const PREP_SEGMENT = 0;
+ const PREP_WRITE = 1;
+ const PREP_SPLIT = 2;
// The GF(256) field of QR (x^8 + x^4 + x^3 + x^2 + 1), shared.
var field as ReedSolomon.Field? = null;
@@ -70,12 +89,20 @@ module Qr {
return b >= 0x30 && b <= 0x39;
}
+ // The alphanumeric value of byte b, or -1 (table built on first use).
+ var alnumTable as ByteArray? = null;
+
function alnumValue(b as Number) as Number {
- if (b >= 0x80) {
- return -1;
+ var t = alnumTable;
+ if (t == null) {
+ t = new [128]b;
+ var chars = ALPHANUMERIC.toUtf8Array();
+ for (var k = 0; k < chars.size(); k++) {
+ t[chars[k]] = k + 1;
+ }
+ alnumTable = t;
}
- var at = ALPHANUMERIC.find(b.toChar().toString());
- return at == null ? -1 : at;
+ return b < 0x80 ? t[b] - 1 : -1;
}
// Optimal segmentation for a version class: [mode, start, end) triples
@@ -84,64 +111,133 @@ module Qr {
// alphanumeric 11/2, byte 8 bits per character). State j after
// character i: the cheapest encoding so far that continues in mode j;
// choice[i * 3 + j] is the mode character i itself was encoded in.
- function segment(data as ByteArray, v as Number) as Array<Number> {
- var n = data.size();
- var modes = [MODE_BYTE, MODE_ALPHANUMERIC, MODE_NUMERIC];
- var head = [(4 + charCountBits(MODE_BYTE, v)) * 6, (4 + charCountBits(MODE_ALPHANUMERIC, v)) * 6,
- (4 + charCountBits(MODE_NUMERIC, v)) * 6];
- var unit = [48, 33, 20];
- var inf = 0x3FFFFFFF;
- var prev = [head[0], head[1], head[2]];
- // choice[i * 3 + j] = the mode index the segment ending at i in mode j came from.
- var choice = new [n * 3]b;
- for (var i = 0; i < n; i++) {
- var b = data[i];
- var cur = [prev[0] + unit[0], alnumValue(b) >= 0 ? prev[1] + unit[1] : inf, isDigit(b) ? prev[2] + unit[2] : inf];
- choice[i * 3] = 0;
- choice[i * 3 + 1] = 1;
- choice[i * 3 + 2] = 2;
- for (var j = 0; j < 3; j++) {
- for (var k = 0; k < 3; k++) {
- if (k == j || (cur[k] as Number) >= inf) {
- continue;
+ // Resumable: step() handles a bounded number of characters (the
+ // programme, then the walk back and the collection of segments, which
+ // cost far less per character), so a 2,900-byte payload never runs in
+ // one event.
+ class Segmenter {
+ static const MODES = [MODE_BYTE, MODE_ALPHANUMERIC, MODE_NUMERIC];
+ static const INF = 0x3FFFFFFF;
+
+ var segments as Array<Number> = [] as Array<Number>;
+
+ private var _data as ByteArray;
+ private var _head as Array<Number>;
+ private var _prev as Array<Number>;
+ private var _cur as Array<Number> = [0, 0, 0];
+ private var _choice as ByteArray;
+ private var _charModes as ByteArray = []b;
+ private var _i as Number = 0;
+ private var _walk as Number = 0;
+ private var _state as Number = 0;
+ private var _start as Number = 0;
+ private var _collected as Number = 0;
+ private var _done as Boolean = false;
+
+ function initialize(data as ByteArray, v as Number) {
+ _data = data;
+ _head = [(4 + charCountBits(MODE_BYTE, v)) * 6, (4 + charCountBits(MODE_ALPHANUMERIC, v)) * 6,
+ (4 + charCountBits(MODE_NUMERIC, v)) * 6];
+ _prev = [_head[0], _head[1], _head[2]];
+ _choice = new [data.size() * 3]b;
+ }
+
+ // Up to `chars` more characters (of the programme, then of each walk
+ // back); true when segments is ready.
+ function step(chars as Number) as Boolean {
+ var n = _data.size();
+ if (_i < n) {
+ program(chars);
+ } else if (_walk > 0) {
+ walkBack(chars * 4);
+ } else {
+ collect(chars * 4);
+ }
+ return _done;
+ }
+
+ private function program(chars as Number) as Void {
+ var n = _data.size();
+ var head = _head;
+ var prev = _prev;
+ var cur = _cur;
+ var choice = _choice;
+ var stop = _i + chars < n ? _i + chars : n;
+ for (var i = _i; i < stop; i++) {
+ var b = _data[i];
+ cur[0] = prev[0] + 48;
+ cur[1] = alnumValue(b) >= 0 ? prev[1] + 33 : INF;
+ cur[2] = isDigit(b) ? prev[2] + 20 : INF;
+ choice[i * 3] = 0;
+ choice[i * 3 + 1] = 1;
+ choice[i * 3 + 2] = 2;
+ for (var j = 0; j < 3; j++) {
+ for (var k = 0; k < 3; k++) {
+ if (k == j || cur[k] >= INF) {
+ continue;
+ }
+ // State j may follow a character in mode k: a new
+ // segment in mode j starts after it (its header paid now).
+ var switched = (cur[k] + 5) / 6 * 6 + head[j];
+ if (switched < cur[j]) {
+ cur[j] = switched;
+ choice[i * 3 + j] = k;
+ }
}
- // State j may follow a character in mode k: a new segment
- // in mode j starts after it (its header paid now).
- var switched = ((cur[k] as Number) + 5) / 6 * 6 + (head[j] as Number);
- if (switched < (cur[j] as Number)) {
- cur[j] = switched;
- choice[i * 3 + j] = k;
+ }
+ var swap = prev;
+ prev = cur;
+ cur = swap;
+ }
+ _prev = prev;
+ _cur = cur;
+ _i = stop;
+ if (_i >= n) {
+ // The cheapest final mode starts the walk back.
+ var best = 0;
+ for (var j = 1; j < 3; j++) {
+ if (_prev[j] < _prev[best]) {
+ best = j;
}
}
+ _state = best;
+ _charModes = new [n]b;
+ _walk = n;
}
- prev = cur;
}
- // The cheapest final mode, then walk back.
- var best = 0;
- for (var j = 1; j < 3; j++) {
- if ((prev[j] as Number) < (prev[best] as Number)) {
- best = j;
+
+ // From the end: the state after character i says which mode i was in.
+ private function walkBack(chars as Number) as Void {
+ var stop = _walk - chars > 0 ? _walk - chars : 0;
+ for (var i = _walk - 1; i >= stop; i--) {
+ var charMode = _choice[i * 3 + _state];
+ _charModes[i] = charMode;
+ _state = charMode;
+ }
+ _walk = stop;
+ if (_walk == 0) {
+ _choice = []b;
}
}
- // Walk back: the state after character i says which mode i was in.
- var charModes = new [n]b;
- var state = best;
- for (var i = n - 1; i >= 0; i--) {
- var charMode = choice[i * 3 + state];
- charModes[i] = charMode;
- state = charMode;
- }
- var segments = [] as Array<Number>;
- var start = 0;
- for (var i = 1; i <= n; i++) {
- if (i == n || charModes[i] != charModes[start]) {
- segments.add(modes[charModes[start]] as Number);
- segments.add(start);
- segments.add(i);
- start = i;
+
+ // From the start: runs of one mode become segments.
+ private function collect(chars as Number) as Void {
+ var n = _data.size();
+ var stop = _collected + chars < n ? _collected + chars : n;
+ for (var i = _collected + 1; i <= stop; i++) {
+ if (i == n || _charModes[i] != _charModes[_start]) {
+ segments.add(MODES[_charModes[_start]] as Number);
+ segments.add(_start);
+ segments.add(i);
+ _start = i;
+ }
+ }
+ _collected = stop;
+ if (_collected >= n) {
+ _charModes = []b;
+ _done = true;
}
}
- return segments;
}
// Bits needed for segments at version v.
@@ -162,25 +258,28 @@ module Qr {
return total;
}
- // A growing bit stream packed into codewords.
+ // A growing bit stream packed into codewords. Bits wait in `_acc`
+ // until a whole byte is ready.
class BitWriter {
var bytes as ByteArray = []b;
var bitCount as Number = 0;
+ private var _acc as Number = 0;
+ private var _accBits as Number = 0;
+
function initialize() {
}
+ // The low `count` bits of value (count <= 16), most significant first.
function put(value as Number, count as Number) as Void {
- for (var i = count - 1; i >= 0; i--) {
- if (bitCount % 8 == 0) {
- bytes.add(0);
- }
- if (((value >> i) & 1) == 1) {
- var at = bitCount / 8;
- bytes[at] = bytes[at] | (0x80 >> (bitCount % 8));
- }
- bitCount++;
+ _acc = (_acc << count) | (value & ((1 << count) - 1));
+ _accBits += count;
+ bitCount += count;
+ while (_accBits >= 8) {
+ _accBits -= 8;
+ bytes.add((_acc >> _accBits) & 0xFF);
}
+ _acc = _acc & ((1 << _accBits) - 1);
}
}
@@ -230,8 +329,32 @@ module Qr {
private var _score as Number = 0;
private var _dark as Number = 0;
private var _maskPass as Number = 0;
- private var _masked as ByteArray = []b;
- private var _prevRow as ByteArray = []b;
+ // Phase 0 (buildCodewords).
+ private var _prep as Number = PREP_SEGMENT;
+ private var _class as Number = 0;
+ private var _segmenter as Segmenter? = null;
+ private var _segments as Array<Number> = [] as Array<Number>;
+ private var _writer as BitWriter? = null;
+ private var _charAt as Number = -1;
+ private var _encoder as ReedSolomon.Encoder? = null;
+ // Packed lines (startPacking): rows and columns, their non-function
+ // modules, the current mask's patterns, scratch lines.
+ private var _words as Number = 0;
+ private var _rows as Array<Number> = [] as Array<Number>;
+ private var _rowFree as Array<Number> = [] as Array<Number>;
+ private var _cols as Array<Number> = [] as Array<Number>;
+ private var _colFree as Array<Number> = [] as Array<Number>;
+ private var _lineMask as Array<Number> = [] as Array<Number>;
+ private var _n1Valid as Array<Number> = [] as Array<Number>;
+ private var _n2Valid as Array<Number> = [] as Array<Number>;
+ private var _rowPattern as Array<Array<Number>> = [] as Array<Array<Number>>;
+ private var _columnPattern as Array<Array<Number>> = [] as Array<Array<Number>>;
+ private var _patternMask as Number = -1;
+ private var _cur as Array<Number> = [] as Array<Number>;
+ private var _prev as Array<Number> = [] as Array<Number>;
+
+ // The penalty of each mask, in order (empty with a forced mask).
+ var scores as Array<Number> = [] as Array<Number>;
// ec: 0 L, 1 M, 2 Q, 3 H. forceVersion 0 = smallest that fits;
// forceMask -1 = best by penalty.
@@ -245,7 +368,7 @@ module Qr {
// Progress 0-100 for the UI.
function percent() as Number {
- return _phase * 100 / 6;
+ return _phase * 100 / 7;
}
// The code as a generic matrix with QR's 4-module quiet zone.
@@ -264,55 +387,146 @@ module Qr {
} else if (_phase == 3) {
placeStep();
} else if (_phase == 4) {
- maskStep();
+ packStep();
} else if (_phase == 5) {
- finish();
+ maskStep();
+ } else if (_phase == 6) {
+ applyStep();
}
- return _phase >= 6 || error != null;
+ return _phase >= 7 || error != null;
}
- // Phase 0: segments, version, bit stream, blocks.
+ // Phase 0: segments and version, then the bit stream, then blocks,
+ // each in bounded steps:
+ // segment the dynamic programme for one version class (versions
+ // 1-9, 10-26, 27-40 count characters with different
+ // widths), SEGMENT_CHARS_PER_STEP characters per step,
+ // until a class fits the payload;
+ // write the segments into the bit stream, WRITE_CHARS_PER_STEP
+ // characters per step, then terminator and padding;
+ // split the codewords into the Reed-Solomon blocks.
private function buildCodewords() as Void {
+ if (_prep == PREP_SEGMENT) {
+ segmentStep();
+ } else if (_prep == PREP_WRITE) {
+ writeStep();
+ } else {
+ splitBlocks();
+ }
+ }
+
+ private function segmentStep() as Void {
if (_data.size() == 0) {
error = "QR needs some text";
return;
}
- var segments = null;
- var v = _version;
- if (v > 0) {
- segments = segment(_data, v);
- if (segmentBits(segments, v) > dataCodewords(v, _ec) * 8) {
- error = "too long for QR version " + v;
+ var lowest = [1, 10, 27] as Array<Number>;
+ var highest = [9, 26, 40] as Array<Number>;
+ var forced = _version;
+ var segmenter = _segmenter;
+ if (segmenter == null) {
+ if (forced > 0) {
+ _class = forced <= 9 ? 0 : (forced <= 26 ? 1 : 2);
+ }
+ // Skip a class the payload can't fit even as digits (10
+ // bits per 3 characters), without segmenting for it.
+ while (forced == 0 && _class < 2 && _data.size() * 10 / 3 > dataCodewords(highest[_class], _ec) * 8) {
+ _class++;
+ }
+ segmenter = new Segmenter(_data, lowest[_class]);
+ _segmenter = segmenter;
+ }
+ if (!segmenter.step(SEGMENT_CHARS_PER_STEP)) {
+ return;
+ }
+ _segmenter = null;
+ var segments = segmenter.segments;
+ var bits = segmentBits(segments, lowest[_class]);
+ if (forced > 0) {
+ if (bits > dataCodewords(forced, _ec) * 8) {
+ error = "too long for QR version " + forced;
return;
}
} else {
- for (var c = 0; c < 3 && v == 0; c++) {
- var lo = [1, 10, 27][c] as Number;
- var hi = [9, 26, 40][c] as Number;
- var s = segment(_data, lo);
- var bits = segmentBits(s, lo);
- for (var tryV = lo; tryV <= hi; tryV++) {
- if (bits <= dataCodewords(tryV, _ec) * 8) {
- v = tryV;
- segments = s;
- break;
- }
+ for (var v = lowest[_class]; v <= highest[_class] && _version == 0; v++) {
+ if (bits <= dataCodewords(v, _ec) * 8) {
+ _version = v;
}
}
- if (v == 0) {
- error = "too long for a QR code at this error correction level (" + _data.size() + " bytes, at most "
- + dataCodewords(40, _ec) + " in byte mode)";
+ if (_version == 0) {
+ _class++;
+ if (_class > 2) {
+ error = "too long for a QR code at this error correction level (" + _data.size() + " bytes, at most "
+ + dataCodewords(40, _ec) + " in byte mode)";
+ }
return;
}
}
- _version = v;
- var capacity = dataCodewords(v, _ec);
- var w = new BitWriter();
- var segs = segments as Array<Number>;
- for (var s = 0; s < segs.size(); s += 3) {
- writeSegment(w, segs[s], segs[s + 1], segs[s + 2]);
+ _segments = segments;
+ _writer = new BitWriter();
+ _cursor = 0;
+ _charAt = -1;
+ _prep = PREP_WRITE;
+ }
+
+ // Write segment characters; _cursor is the segment (index into the
+ // triples), _charAt the next character, -1 before its header.
+ private function writeStep() as Void {
+ var w = _writer as BitWriter;
+ var segs = _segments;
+ var d = _data;
+ var budget = WRITE_CHARS_PER_STEP;
+ while (budget > 0 && _cursor < segs.size()) {
+ var mode = segs[_cursor];
+ var end = segs[_cursor + 2];
+ if (_charAt < 0) {
+ _charAt = segs[_cursor + 1];
+ w.put(mode, 4);
+ w.put(end - _charAt, charCountBits(mode, _version));
+ }
+ var i = _charAt;
+ // Whole groups (3 digits, 2 alphanumerics) per step.
+ var group = mode == MODE_NUMERIC ? 3 : (mode == MODE_ALPHANUMERIC ? 2 : 1);
+ var take = budget / group * group;
+ var stop = i + (take > group ? take : group);
+ stop = stop < end ? stop : end;
+ budget -= stop - i;
+ if (mode == MODE_NUMERIC) {
+ while (i < stop) {
+ var count = stop - i >= 3 ? 3 : stop - i;
+ var value = 0;
+ for (var k = 0; k < count; k++) {
+ value = value * 10 + d[i + k] - 0x30;
+ }
+ w.put(value, [0, 4, 7, 10][count] as Number);
+ i += count;
+ }
+ } else if (mode == MODE_ALPHANUMERIC) {
+ while (i < stop) {
+ if (stop - i >= 2) {
+ w.put(alnumValue(d[i]) * 45 + alnumValue(d[i + 1]), 11);
+ i += 2;
+ } else {
+ w.put(alnumValue(d[i]), 6);
+ i += 1;
+ }
+ }
+ } else {
+ for (; i < stop; i++) {
+ w.put(d[i], 8);
+ }
+ }
+ _charAt = stop;
+ if (stop >= end) {
+ _cursor += 3;
+ _charAt = -1;
+ }
+ }
+ if (_cursor < segs.size()) {
+ return;
}
// Terminator, byte alignment, pad codewords.
+ var capacity = dataCodewords(_version, _ec);
var terminator = capacity * 8 - w.bitCount;
w.put(0, terminator < 4 ? terminator : 4);
if (w.bitCount % 8 != 0) {
@@ -323,8 +537,14 @@ module Qr {
w.bytes.add(pad);
pad = pad == 0xEC ? 0x11 : 0xEC;
}
- // Split into blocks.
- var i = ((v - 1) * 4 + _ec) * 5;
+ _segments = [] as Array<Number>;
+ _prep = PREP_SPLIT;
+ }
+
+ private function splitBlocks() as Void {
+ var bytes = (_writer as BitWriter).bytes;
+ _writer = null;
+ var i = ((_version - 1) * 4 + _ec) * 5;
var t = QrTables.BLOCKS;
var at = 0;
for (var g = 0; g < 2; g++) {
@@ -333,7 +553,7 @@ module Qr {
for (var b = 0; b < count; b++) {
var block = new [len] as Array<Number>;
for (var k = 0; k < len; k++) {
- block[k] = w.bytes[at + k];
+ block[k] = bytes[at + k];
}
at += len;
_blocks.add(block);
@@ -343,88 +563,89 @@ module Qr {
_phase = 1;
}
- private function writeSegment(w as BitWriter, mode as Number, start as Number, end as Number) as Void {
- var count = end - start;
- w.put(mode, 4);
- w.put(count, charCountBits(mode, _version));
- var d = _data;
- if (mode == MODE_NUMERIC) {
- var i = start;
- while (i < end) {
- var take = end - i >= 3 ? 3 : end - i;
- var value = 0;
- for (var k = 0; k < take; k++) {
- value = value * 10 + d[i + k] - 0x30;
- }
- w.put(value, [0, 4, 7, 10][take] as Number);
- i += take;
- }
- } else if (mode == MODE_ALPHANUMERIC) {
- var i = start;
- while (i < end) {
- if (end - i >= 2) {
- w.put(alnumValue(d[i]) * 45 + alnumValue(d[i + 1]), 11);
- i += 2;
- } else {
- w.put(alnumValue(d[i]), 6);
- i += 1;
- }
- }
- } else {
- for (var i = start; i < end; i++) {
- w.put(d[i], 8);
+ // Phase 1: Reed-Solomon codewords, ECC_WORK_PER_STEP data symbols
+ // times EC symbols per step (a version 40 block is 30 x 118).
+ private function eccStep() as Void {
+ var encoder = _encoder;
+ if (encoder == null) {
+ var i = ((_version - 1) * 4 + _ec) * 5;
+ var n = QrTables.BLOCKS[i] as Number;
+ var fresh = !theField().hasGenerator(n);
+ encoder = new ReedSolomon.Encoder(theField(), _blocks[_cursor], n);
+ _encoder = encoder;
+ if (fresh) {
+ return; // the generator took this step
}
}
- }
-
- // Phase 1: Reed-Solomon codewords, one block per step.
- private function eccStep() as Void {
- var i = ((_version - 1) * 4 + _ec) * 5;
- var n = QrTables.BLOCKS[i] as Number;
- _ecBlocks.add(ReedSolomon.ecc(theField(), _blocks[_cursor], n));
+ if (!encoder.step(ECC_WORK_PER_STEP / encoder.remainder.size())) {
+ return;
+ }
+ _encoder = null;
+ _ecBlocks.add(encoder.remainder);
_cursor++;
if (_cursor >= _blocks.size()) {
+ _cursor = 0;
+ _codewords = []b;
_phase = 2;
}
}
- // Phase 2: interleave codewords; draw the function patterns.
- private function interleaveAndDrawFunctions() as Void {
- var out = []b;
- var longest = 0;
- for (var b = 0; b < _blocks.size(); b++) {
- if (_blocks[b].size() > longest) {
- longest = _blocks[b].size();
- }
- }
- for (var k = 0; k < longest; k++) {
- for (var b = 0; b < _blocks.size(); b++) {
- if (k < _blocks[b].size()) {
- out.add(_blocks[b][k]);
+ // Phase 2: interleave the codewords, INTERLEAVE_PER_STEP per step
+ // (_cursor is the position within the blocks, data then EC), then
+ // draw the function patterns in a step of their own.
+ private function interleaveStep() as Void {
+ var longest = _blocks[_blocks.size() - 1].size(); // group-2 blocks are the longer ones
+ var ecLen = _ecBlocks[0].size();
+ var out = _codewords;
+ var budget = INTERLEAVE_PER_STEP;
+ while (_cursor < longest + ecLen && budget > 0) {
+ var k = _cursor;
+ var blocks = k < longest ? _blocks : _ecBlocks;
+ var at = k < longest ? k : k - longest;
+ for (var b = 0; b < blocks.size(); b++) {
+ if (at < blocks[b].size()) {
+ out.add(blocks[b][at]);
}
}
+ budget -= blocks.size();
+ _cursor++;
}
- var ecLen = _ecBlocks[0].size();
- for (var k = 0; k < ecLen; k++) {
- for (var b = 0; b < _ecBlocks.size(); b++) {
- out.add(_ecBlocks[b][k]);
- }
+ if (_cursor < longest + ecLen) {
+ return;
}
- _codewords = out;
_blocks = [] as Array<Array<Number>>;
_ecBlocks = [] as Array<Array<Number>>;
- drawFunctionPatterns();
- _cursor = _size - 1; // right column of the current column pair
- _bitIndex = 0;
- _phase = 3;
+ _cursor = -1;
}
- private function drawFunctionPatterns() as Void {
+ // After interleaving, _cursor counts down through the drawing:
+ // -1 timing and finder patterns, -2 - a alignment row a, then the
+ // format and version areas.
+ private function interleaveAndDrawFunctions() as Void {
+ if (_cursor >= 0) {
+ interleaveStep();
+ return;
+ }
+ var pos = QrTables.ALIGNMENT[_version - 1] as Array<Number>;
+ if (_cursor == -1) {
+ drawTimingAndFinders();
+ } else if (-2 - _cursor < pos.size()) {
+ drawAlignmentRow(pos, -2 - _cursor);
+ } else {
+ reserveFormatAndVersion();
+ _cursor = _size - 1; // right column of the current column pair
+ _bitIndex = 0;
+ _phase = 3;
+ return;
+ }
+ _cursor--;
+ }
+
+ private function drawTimingAndFinders() as Void {
var n = 17 + 4 * _version;
_size = n;
_m = new [n * n]b;
_function = new [n * n]b;
- // Timing patterns.
for (var i = 0; i < n; i++) {
setFunction(6, i, i % 2 == 0 ? 1 : 0);
setFunction(i, 6, i % 2 == 0 ? 1 : 0);
@@ -433,17 +654,21 @@ module Qr {
drawFinder(3, 3);
drawFinder(n - 4, 3);
drawFinder(3, n - 4);
- // Alignment patterns, except where they'd overlap the finders.
- var pos = QrTables.ALIGNMENT[_version - 1] as Array<Number>;
+ }
+
+ // Alignment patterns in row a, except where they'd overlap the finders.
+ private function drawAlignmentRow(pos as Array<Number>, a as Number) as Void {
var count = pos.size();
- for (var a = 0; a < count; a++) {
- for (var b = 0; b < count; b++) {
- if ((a == 0 && b == 0) || (a == 0 && b == count - 1) || (a == count - 1 && b == 0)) {
- continue;
- }
- drawAlignment(pos[a], pos[b]);
+ for (var b = 0; b < count; b++) {
+ if ((a == 0 && b == 0) || (a == 0 && b == count - 1) || (a == count - 1 && b == 0)) {
+ continue;
}
+ drawAlignment(pos[a], pos[b]);
}
+ }
+
+ private function reserveFormatAndVersion() as Void {
+ var n = _size;
// Format areas and the dark module's place stay light until the
// mask is chosen (segno evaluates masks that way; ISO 18004:2015
// 7.8 leaves format information out of the evaluation).
@@ -503,7 +728,7 @@ module Qr {
private function placeStep() as Void {
var n = _size;
var totalBits = _codewords.size() * 8;
- var budget = MODULES_PER_STEP;
+ var budget = PLACE_MODULES_PER_STEP;
while (_cursor >= 1 && budget > 0) {
var right = _cursor == 6 ? 5 : _cursor;
var upward = ((right + 1) & 2) == 0;
@@ -523,32 +748,167 @@ module Qr {
}
if (_cursor < 1) {
_codewords = []b;
- _maskIndex = _forceMask >= 0 ? _forceMask : 0;
_cursor = 0;
- _score = 0;
- _dark = 0;
+ startPacking();
_phase = 4;
}
}
- // Phase 4: score masks (or apply the forced one). Per mask, two
- // passes: the rows (apply the mask into a copy, then N1, N2, N3 and
- // the dark count), then the columns (N1, N3), each in steps sized to
- // its cost per module.
+ // ---- Packed lines (M8) ----------------------------------------
+ // Mask scoring works on lines packed 16 modules per Number, so the
+ // penalty rules run as a few bit operations per 16 modules instead
+ // of a loop per module. Line layout: module i of a row or column is
+ // bit (i + PAD) of the line, counted from bit 0 of word 0; PAD light
+ // bits before the line and at least 16 after it let the N3 test look
+ // 4 modules back and 10 ahead without special cases (outside the
+ // symbol counts as light, which is what segno's clipped checks do).
+ // Rows and columns are packed once; each mask is then
+ // data XOR (pattern AND not-function), word by word.
+
+ private function startPacking() as Void {
+ var n = _size;
+ var w = (n + PAD) / 16 + 2;
+ _words = w;
+ // Zeroed line by line with addAll (native), not word by word.
+ var zeros = new [w] as Array<Number>;
+ for (var k = 0; k < w; k++) {
+ zeros[k] = 0;
+ }
+ _rows = [] as Array<Number>;
+ _rowFree = [] as Array<Number>;
+ _cols = [] as Array<Number>;
+ _colFree = [] as Array<Number>;
+ for (var line = 0; line < n; line++) {
+ _rows.addAll(zeros);
+ _rowFree.addAll(zeros);
+ _cols.addAll(zeros);
+ _colFree.addAll(zeros);
+ }
+ _lineMask = bitRange(PAD, n + PAD - 1);
+ _n1Valid = bitRange(PAD, n + PAD - 5);
+ _n2Valid = bitRange(PAD, n + PAD - 2);
+ _cur = new [w] as Array<Number>;
+ _prev = new [w] as Array<Number>;
+ }
+
+ // Words with bits first..last set.
+ private function bitRange(first as Number, last as Number) as Array<Number> {
+ var out = new [_words] as Array<Number>;
+ for (var k = 0; k < _words; k++) {
+ var word = 0;
+ for (var b = 0; b < 16; b++) {
+ var p = k * 16 + b;
+ if (p >= first && p <= last) {
+ word |= 1 << b;
+ }
+ }
+ out[k] = word;
+ }
+ return out;
+ }
+
+ // Phase 4: pack rows and columns (modules and the non-function
+ // mask), a few rows per step.
+ private function packStep() as Void {
+ var n = _size;
+ var w = _words;
+ var rows = PACK_MODULES_PER_STEP / n;
+ for (var r = 0; r < rows && _cursor < n; r++) {
+ var y = _cursor;
+ var py = y + PAD;
+ var colWord = py >> 4;
+ var colBit = 1 << (py & 15);
+ for (var x = 0; x < n; x++) {
+ var at = y * n + x;
+ var px = x + PAD;
+ var rowAt = y * w + (px >> 4);
+ var rowBit = 1 << (px & 15);
+ var colAt = x * w + colWord;
+ if (_m[at] != 0) {
+ _rows[rowAt] = _rows[rowAt] | rowBit;
+ _cols[colAt] = _cols[colAt] | colBit;
+ }
+ if (_function[at] == 0) {
+ _rowFree[rowAt] = _rowFree[rowAt] | rowBit;
+ _colFree[colAt] = _colFree[colAt] | colBit;
+ }
+ }
+ _cursor++;
+ }
+ if (_cursor < n) {
+ return;
+ }
+ _function = []b;
+ _cursor = 0;
+ _maskIndex = _forceMask >= 0 ? _forceMask : 0;
+ _patternMask = -1;
+ _score = 0;
+ _dark = 0;
+ _maskPass = 0;
+ _phase = 5;
+ }
+
+ // Mask m as packed lines: rowPattern[r] is row r (mod 12), across
+ // the columns; columnPattern[c] is column c (mod 12), down the
+ // rows. Every mask repeats every 12 modules along a row or a
+ // column (mask 4 every 6 across and 4 down, the others every 2, 3
+ // or 6), and every 12 lines; so twelve values are computed and
+ // repeated over 48 bits (three words, a multiple of 12), then over
+ // the line. maskBit gets coordinates shifted by 12 (the same mask
+ // value) so they are never negative.
+ private function buildPatterns(m as Number) as Void {
+ _rowPattern = [] as Array<Array<Number>>;
+ _columnPattern = [] as Array<Array<Number>>;
+ for (var r = 0; r < 12; r++) {
+ _rowPattern.add(repeated(m, r, true));
+ _columnPattern.add(repeated(m, r, false));
+ }
+ _patternMask = m;
+ }
+
+ private function repeated(m as Number, fixed as Number, alongRow as Boolean) as Array<Number> {
+ // Twelve mask values, starting at line position -PAD (+12).
+ var twelve = new [12]b;
+ for (var i = 0; i < 12; i++) {
+ if (alongRow ? maskBit(m, 12 - PAD + i, fixed) : maskBit(m, fixed, 12 - PAD + i)) {
+ twelve[i] = 1;
+ }
+ }
+ var period = [0, 0, 0];
+ var at = 0;
+ for (var p = 0; p < 48; p++) {
+ if (twelve[at] != 0) {
+ period[p >> 4] = (period[p >> 4] as Number) | (1 << (p & 15));
+ }
+ at = at == 11 ? 0 : at + 1;
+ }
+ var out = new [_words] as Array<Number>;
+ for (var k = 0; k < _words; k++) {
+ out[k] = (period[k % 3] as Number) & (_lineMask[k] as Number);
+ }
+ return out;
+ }
+
+ // Phase 5: score masks (or take the forced one). Per mask: build its
+ // patterns (one step), then the rows (N1, N2, N3 and the dark count)
+ // and the columns (N1, N3), a bounded number of words per step.
private function maskStep() as Void {
if (_forceMask >= 0) {
_bestMask = _forceMask;
- _phase = 5;
+ _phase = 6;
+ return;
+ }
+ if (_patternMask != _maskIndex) {
+ buildPatterns(_maskIndex);
return;
}
var n = _size;
- var lines = (_maskPass == 0 ? ROW_MODULES_PER_STEP : COLUMN_MODULES_PER_STEP) / n;
+ var lines = (_maskPass == 0 ? ROW_WORDS_PER_STEP : COLUMN_WORDS_PER_STEP) / _words;
if (lines < 1) {
lines = 1;
}
for (var l = 0; l < lines && _cursor < n; l++) {
if (_maskPass == 0) {
- applyMaskRow(_cursor);
scoreRow(_cursor);
} else {
scoreColumn(_cursor);
@@ -567,6 +927,7 @@ module Qr {
var total = n * n;
var deviation = (_dark * 100 - 50 * total).abs();
_score += 10 * (deviation / (5 * total));
+ scores.add(_score);
if (_score < _bestScore) {
_bestScore = _score;
_bestMask = _maskIndex;
@@ -576,126 +937,135 @@ module Qr {
_score = 0;
_dark = 0;
if (_maskIndex >= 8) {
- _masked = []b;
- _prevRow = []b;
- _phase = 5;
+ _phase = 6;
}
}
- private function applyMaskRow(y as Number) as Void {
- var n = _size;
- if (_masked.size() != n * n) {
- _masked = new [n * n]b;
- }
- var m = _maskIndex;
- var row = y * n;
- for (var x = 0; x < n; x++) {
- var at = row + x;
- var bit = _m[at];
- if (_function[at] == 0 && maskBit(m, x, y)) {
- bit = bit ^ 1;
- }
- _masked[at] = bit;
+ // Masked line into `into`: data XOR (pattern AND free).
+ private function maskLine(data as Array<Number>, free as Array<Number>, pattern as Array<Number>, line as Number,
+ into as Array<Number>) as Void {
+ var base = line * _words;
+ for (var k = 0; k < _words; k++) {
+ into[k] = (data[base + k] as Number) ^ ((pattern[k] as Number) & (free[base + k] as Number));
}
}
private function scoreRow(y as Number) as Void {
- var n = _size;
- var row = _masked.slice(y * n, y * n + n);
- _score += lineScore(row);
- var prev = _prevRow;
+ var swap = _prev;
+ _prev = _cur;
+ _cur = swap;
+ var cur = _cur;
+ maskLine(_rows, _rowFree, _rowPattern[y % 12], y, cur);
+ _score += lineScore(cur);
+ var pop = popTable();
var dark = 0;
- for (var x = 0; x < n; x++) {
- var c = row[x];
- dark += c;
- // N2: a 2x2 block of one colour with the row above.
- if (y > 0 && x > 0 && c == row[x - 1] && c == prev[x] && c == prev[x - 1]) {
- _score += 3;
- }
+ for (var k = 0; k < _words; k++) {
+ var c = cur[k] as Number;
+ dark += pop[c & 0xFF] + pop[c >> 8];
}
_dark += dark;
- _prevRow = row;
+ if (y == 0) {
+ return;
+ }
+ // N2: 3 for every 2x2 block of one colour (with the row above).
+ var prev = _prev;
+ var blocks = 0;
+ for (var k = 0; k < _words - 1; k++) {
+ var c = (cur[k] as Number) | ((cur[k + 1] as Number) << 16);
+ var q = (prev[k] as Number) | ((prev[k + 1] as Number) << 16);
+ var same = ~((c ^ (c >> 1)) | (q ^ (q >> 1)) | (c ^ q)) & (_n2Valid[k] as Number);
+ if (same != 0) {
+ blocks += pop[same & 0xFF] + pop[same >> 8];
+ }
+ }
+ _score += 3 * blocks;
}
private function scoreColumn(x as Number) as Void {
- var n = _size;
- var column = new [n]b;
- for (var y = 0; y < n; y++) {
- column[y] = _masked[y * n + x];
- }
- _score += lineScore(column);
+ maskLine(_cols, _colFree, _columnPattern[x % 12], x, _cur);
+ _score += lineScore(_cur);
}
- // N1 and N3 of one row or column, in one pass. N1: 3 + (run - 5)
- // for every run of 5 or more equal modules. N3 as segno counts it:
- // 40 for each 1011101 with light modules (up to 4, fewer at the edge)
- // before or after it; after a counted match the search continues
- // behind it, otherwise 4 modules on (found with a rolling 7-bit window).
- private function lineScore(line as ByteArray) as Number {
- var n = line.size();
+ // N1 and N3 of one packed row or column.
+ // N1: 3 + (run - 5) for every run of 5 or more equal modules. A
+ // run of length L has L - 4 windows of 5 equal modules, so N1
+ // is (windows) + 2 * (groups of windows).
+ // N3, as segno counts it: 40 for each 1011101 with 4 light
+ // modules before or after it (fewer at the edge); after a
+ // counted match the search continues behind it, otherwise 4
+ // modules on. Matches are rare, so they are walked one by one.
+ // Positions s below are bit positions 4 before a match's first
+ // module, so every look stays within the two words combined in e.
+ private function lineScore(line as Array<Number>) as Number {
+ var pop = popTable();
var score = 0;
- var run = 1;
- var window = line[0];
+ var carry = 0;
var nextAllowed = 0;
- for (var k = 1; k < n; k++) {
- var c = line[k];
- if (c == line[k - 1]) {
- run++;
- } else {
- if (run >= 5) {
- score += run - 2;
- }
- run = 1;
+ for (var k = 0; k < _words - 1; k++) {
+ var e = (line[k] as Number) | ((line[k + 1] as Number) << 16);
+ var same = ~(e ^ (e >> 1));
+ var windows = same & (same >> 1) & (same >> 2) & (same >> 3) & (_n1Valid[k] as Number);
+ if (windows != 0) {
+ var starts = windows & ~((windows << 1) | carry) & 0xFFFF;
+ score += pop[windows & 0xFF] + pop[windows >> 8] + 2 * (pop[starts & 0xFF] + pop[starts >> 8]);
}
- window = ((window << 1) | c) & 0x7F;
- if (window == 0x5D && k >= 6) {
- var idx = k - 6;
- if (idx >= nextAllowed) {
- var after = idx + 7;
- if (idx == 0 || idx == n - 7 || allLight(line, idx - 4 < 0 ? 0 : idx - 4, idx)
- || allLight(line, after, after + 4 > n ? n : after + 4)) {
- score += 40;
- nextAllowed = idx + 7;
- } else {
- nextAllowed = idx + 4;
- }
+ carry = (windows >> 15) & 1;
+ var match = (e >> 4) & ~(e >> 5) & (e >> 6) & (e >> 7) & (e >> 8) & ~(e >> 9) & (e >> 10) & 0xFFFF;
+ if (match == 0) {
+ continue;
+ }
+ var light = ~(e | (e >> 1) | (e >> 2) | (e >> 3)) | ~((e >> 11) | (e >> 12) | (e >> 13) | (e >> 14));
+ for (var b = 0; b < 16; b++) {
+ var s = k * 16 + b;
+ if (((match >> b) & 1) == 0 || s < nextAllowed) {
+ continue;
+ }
+ if (((light >> b) & 1) != 0) {
+ score += 40;
+ nextAllowed = s + 7;
+ } else {
+ nextAllowed = s + 4;
}
}
}
- if (run >= 5) {
- score += run - 2;
- }
return score;
}
- private function allLight(line as ByteArray, from as Number, to as Number) as Boolean {
- for (var i = from; i < to; i++) {
- if (line[i] != 0) {
- return false;
- }
+ // Phase 6: apply the chosen mask to the module matrix, a few rows
+ // per step (version 40 has 31,329 modules: far too many for one
+ // event).
+ private function applyStep() as Void {
+ if (_patternMask != _bestMask) {
+ buildPatterns(_bestMask);
+ return;
}
- return true;
- }
-
- // Phase 5: apply the chosen mask, draw format and version info.
- private function finish() as Void {
var n = _size;
- var m = _bestMask;
- for (var y = 0; y < n; y++) {
+ var rows = APPLY_MODULES_PER_STEP / n;
+ for (var r = 0; r < rows && _cursor < n; r++) {
+ var y = _cursor;
+ maskLine(_rows, _rowFree, _rowPattern[y % 12], y, _cur);
+ var row = y * n;
for (var x = 0; x < n; x++) {
- var at = y * n + x;
- if (_function[at] == 0 && maskBit(m, x, y)) {
- _m[at] = _m[at] ^ 1;
- }
+ var p = x + PAD;
+ _m[row + x] = ((_cur[p >> 4] as Number) >> (p & 15)) & 1;
}
+ _cursor++;
}
- drawFormat(m);
+ if (_cursor < n) {
+ return;
+ }
+ _rows = [] as Array<Number>;
+ _rowFree = [] as Array<Number>;
+ _cols = [] as Array<Number>;
+ _colFree = [] as Array<Number>;
+ _rowPattern = [] as Array<Array<Number>>;
+ _columnPattern = [] as Array<Array<Number>>;
+ drawFormat(_bestMask);
if (_version >= 7) {
drawVersion();
}
- result = new MatrixCode(n, _m, _version, m);
- _function = []b;
- _phase = 6;
+ result = new MatrixCode(n, _m, _version, _bestMask);
+ _phase = 7;
}
private function drawFormat(mask as Number) as Void {
@@ -707,21 +1077,21 @@ module Qr {
var bits = ((data << 10) | rem) ^ 0x5412;
var n = _size;
for (var i = 0; i <= 5; i++) {
- setFunction(8, i, (bits >> i) & 1);
+ setModule(8, i, (bits >> i) & 1);
}
- setFunction(8, 7, (bits >> 6) & 1);
- setFunction(8, 8, (bits >> 7) & 1);
- setFunction(7, 8, (bits >> 8) & 1);
+ setModule(8, 7, (bits >> 6) & 1);
+ setModule(8, 8, (bits >> 7) & 1);
+ setModule(7, 8, (bits >> 8) & 1);
for (var i = 9; i < 15; i++) {
- setFunction(14 - i, 8, (bits >> i) & 1);
+ setModule(14 - i, 8, (bits >> i) & 1);
}
for (var i = 0; i < 8; i++) {
- setFunction(n - 1 - i, 8, (bits >> i) & 1);
+ setModule(n - 1 - i, 8, (bits >> i) & 1);
}
for (var i = 8; i < 15; i++) {
- setFunction(8, n - 15 + i, (bits >> i) & 1);
+ setModule(8, n - 15 + i, (bits >> i) & 1);
}
- setFunction(8, n - 8, 1);
+ setModule(8, n - 8, 1);
}
private function drawVersion() as Void {
@@ -735,10 +1105,29 @@ module Qr {
var bit = (bits >> i) & 1;
var a = n - 11 + i % 3;
var b = i / 3;
- setFunction(a, b, bit);
- setFunction(b, a, bit);
+ setModule(a, b, bit);
+ setModule(b, a, bit);
+ }
+ }
+
+ private function setModule(x as Number, y as Number, dark as Number) as Void {
+ _m[y * _size + x] = dark;
+ }
+ }
+
+ // A 256-entry table of set-bit counts, built on first use.
+ var popCounts as ByteArray? = null;
+
+ function popTable() as ByteArray {
+ var t = popCounts;
+ if (t == null) {
+ t = new [256]b;
+ for (var i = 1; i < 256; i++) {
+ t[i] = t[i >> 1] + (i & 1);
}
+ popCounts = t;
}
+ return t;
}
// Mask pattern m at column x, row y: true flips a data module.