diff options
Diffstat (limited to 'source/codes/DataMatrix.mc')
| -rw-r--r-- | source/codes/DataMatrix.mc | 298 |
1 files changed, 196 insertions, 102 deletions
diff --git a/source/codes/DataMatrix.mc b/source/codes/DataMatrix.mc index 45d5ff0..76a29e8 100644 --- a/source/codes/DataMatrix.mc +++ b/source/codes/DataMatrix.mc @@ -9,8 +9,8 @@ // 2. Pick the smallest square size, pad (129, then randomised pads). // 3. Reed-Solomon over GF(256) (x^8+x^5+x^3+x^2+1, roots a^1..a^n), // per interleaved block, in chunks. -// 4. Place the codewords with the standard "utah" placement, a few -// diagonal sweeps per step, then add the finder and clock patterns of +// 4. Place the codewords with the standard "utah" placement, one +// diagonal pass per step, then add the finder and clock patterns of // every data region. // Verified against ppf.datamatrix (ASCII mode) and by decoding with zxing-cpp. @@ -32,9 +32,14 @@ module DataMatrix { const PAD = 129; const UPPER_SHIFT = 235; const BASE256_LATCH = 231; - // Work per step, sized for the 120,000-bytecode watchdog. - const RS_SYMBOLS_PER_STEP = 40; - const SWEEPS_PER_STEP = 6; + // The placement grid's mark for the fixed dark corner modules. + const FIXED_DARK = 0xFFFF; + // Work per step: each was checked in the simulator by running two + // steps per event on the Forerunner 255S (120,000 bytecodes per event), + // with a 1,500-byte payload (144 x 144 modules). + const BYTES_PER_STEP = 250; // codeword encodation + const ECC_WORK_PER_STEP = 400; // data symbols x EC symbols + const BUILD_MODULES_PER_STEP = 300; // symbol modules var field as ReedSolomon.Field? = null; @@ -49,41 +54,83 @@ module DataMatrix { // Codewords for data: ASCII, or Base256 when cheaper (non-ASCII text). function codewords(data as ByteArray) as Array<Number> { - var ascii = [] as Array<Number>; - var highBytes = 0; - var i = 0; - while (i < data.size()) { - var b = data[i]; - if (b >= 0x30 && b <= 0x39 && i + 1 < data.size() && data[i + 1] >= 0x30 && data[i + 1] <= 0x39) { - ascii.add(130 + (b - 0x30) * 10 + data[i + 1] - 0x30); - i += 2; - continue; - } - if (b >= 128) { - ascii.add(UPPER_SHIFT); - ascii.add(b - 128 + 1); - highBytes++; - } else { - ascii.add(b + 1); - } - i++; + var builder = new CodewordBuilder(data); + while (!builder.step(data.size() + 1)) { } - var n = data.size(); - var base256Cost = 1 + (n <= 249 ? 1 : 2) + n; - if (highBytes == 0 || ascii.size() <= base256Cost) { - return ascii; - } - var out = [BASE256_LATCH] as Array<Number>; - if (n <= 249) { - out.add(randomise255(n, 2)); - } else { - out.add(randomise255(n / 250 + 249, 2)); - out.add(randomise255(n % 250, 3)); + return builder.result; + } + + // codewords() in bounded steps: the ASCII encodation, then (when + // Base256 is cheaper) the Base256 one, `bytes` input bytes per step. + class CodewordBuilder { + var result as Array<Number> = [] as Array<Number>; + + private var _data as ByteArray; + private var _ascii as Array<Number> = [] as Array<Number>; + private var _highBytes as Number = 0; + private var _i as Number = 0; + private var _base256 as Array<Number>? = null; + private var _k as Number = 0; + + function initialize(data as ByteArray) { + _data = data; } - for (var k = 0; k < n; k++) { - out.add(randomise255(data[k], out.size() + 1)); + + // True when result is ready. + function step(bytes as Number) as Boolean { + var data = _data; + var n = data.size(); + if (_i < n) { + var stop = _i + bytes < n ? _i + bytes : n; + var i = _i; + var ascii = _ascii; + while (i < stop) { + var b = data[i]; + if (b >= 0x30 && b <= 0x39 && i + 1 < n && data[i + 1] >= 0x30 && data[i + 1] <= 0x39) { + ascii.add(130 + (b - 0x30) * 10 + data[i + 1] - 0x30); + i += 2; + continue; + } + if (b >= 128) { + ascii.add(UPPER_SHIFT); + ascii.add(b - 128 + 1); + _highBytes++; + } else { + ascii.add(b + 1); + } + i++; + } + _i = i; + return false; + } + var out = _base256; + if (out == null) { + var base256Cost = 1 + (n <= 249 ? 1 : 2) + n; + if (_highBytes == 0 || _ascii.size() <= base256Cost) { + result = _ascii; + return true; + } + out = [BASE256_LATCH] as Array<Number>; + if (n <= 249) { + out.add(randomise255(n, 2)); + } else { + out.add(randomise255(n / 250 + 249, 2)); + out.add(randomise255(n % 250, 3)); + } + _base256 = out; + _ascii = [] as Array<Number>; + } + var stop = _k + bytes < n ? _k + bytes : n; + for (var k = _k; k < stop; k++) { + out.add(randomise255(data[k], out.size() + 1)); + } + _k = stop; + if (_k < n) { + return false; + } + result = out; + return true; } - return out; } // The 255-state randomising of Base256 values at 1-based position pos. @@ -113,15 +160,21 @@ module DataMatrix { private var _size as Array<Number> = [] as Array<Number>; private var _cw as Array<Number> = [] as Array<Number>; private var _blocks as Array<Array<Number>> = [] as Array<Array<Number>>; - private var _rem as Array<Number> = [] as Array<Number>; + private var _builder as CodewordBuilder? = null; + private var _encoder as ReedSolomon.Encoder? = null; private var _block as Number = 0; - private var _at as Number = 0; + private var _buildRow as Number = 0; + private var _modules as ByteArray = []b; private var _ecTotal as Array<Number> = [] as Array<Number>; // Placement state. private var _nrow as Number = 0; private var _ncol as Number = 0; - private var _place as Array<Number> = [] as Array<Number>; // 10 * codeword + bit, 0 = empty, -1 = fixed dark + // Placement grid, two bytes per module (big-endian): 10 * codeword + + // bit, 0 = empty, FIXED_DARK. Bytes, not Numbers: a 132 x 132 grid of + // Numbers took about 90 KB of the Forerunner 255S's 512 KB. + private var _place as ByteArray = []b; private var _chr as Number = 1; + private var _downPass as Boolean = false; private var _row as Number = 4; private var _col as Number = 0; private var _result as Codes.Matrix? = null; @@ -132,7 +185,7 @@ module DataMatrix { } function percent() as Number { - return _phase * 25; + return _phase * 20; } function matrix() as Codes.Matrix? { @@ -145,20 +198,31 @@ module DataMatrix { } else if (_phase == 1) { eccStep(); } else if (_phase == 2) { - placeStep(); + startPlacement(); } else if (_phase == 3) { + placeStep(); + } else if (_phase == 4) { build(); } - return _phase >= 4 || error != null; + return _phase >= 5 || error != null; } - // Codewords, size, padding, blocks. + // Codewords (BYTES_PER_STEP per step), then size, padding, blocks. private function prepare() as Void { if (_data.size() == 0) { error = "Data Matrix needs some text"; return; } - var cw = codewords(_data); + var builder = _builder; + if (builder == null) { + builder = new CodewordBuilder(_data); + _builder = builder; + } + if (!builder.step(BYTES_PER_STEP)) { + return; + } + _builder = null; + var cw = builder.result; for (var i = 0; i < SIZES.size() && _size.size() == 0; i++) { var s = SIZES[i] as Array<Number>; if (cw.size() <= s[3]) { @@ -186,63 +250,68 @@ module DataMatrix { _blocks.add(block); } _ecTotal = new [_size[4]] as Array<Number>; - startBlock(); _phase = 1; } - private function startBlock() as Void { - var n = _size[4] / _size[5]; - _rem = new [n] as Array<Number>; - for (var k = 0; k < n; k++) { - _rem[k] = 0; - } - _at = 0; - } - - // Reed-Solomon, RS_SYMBOLS_PER_STEP data symbols per step. + // Reed-Solomon (the shared encoder: the same remainder as ISO 16022 + // annex E), ECC_WORK_PER_STEP data symbols times EC symbols per step. private function eccStep() as Void { - var f = theField(); - var data = _blocks[_block]; - var n = _rem.size(); - var g = f.generator(n); - var end = _at + RS_SYMBOLS_PER_STEP < data.size() ? _at + RS_SYMBOLS_PER_STEP : data.size(); - for (var i = _at; i < end; i++) { - var factor = data[i] ^ _rem[0]; - for (var k = 0; k < n - 1; k++) { - _rem[k] = _rem[k + 1] ^ f.multiply(g[k], factor); + var blocks = _size[5]; + var n = _size[4] / blocks; + var encoder = _encoder; + if (encoder == null) { + var fresh = !theField().hasGenerator(n); + encoder = new ReedSolomon.Encoder(theField(), _blocks[_block], n); + _encoder = encoder; + if (fresh) { + return; // the generator took this step } - _rem[n - 1] = f.multiply(g[n - 1], factor); } - _at = end; - if (_at < data.size()) { + if (!encoder.step(ECC_WORK_PER_STEP / n > 0 ? ECC_WORK_PER_STEP / n : 1)) { return; } + _encoder = null; // Interleave this block's EC codewords. - var blocks = _size[5]; for (var k = 0; k < n; k++) { - _ecTotal[_block + k * blocks] = _rem[k]; + _ecTotal[_block + k * blocks] = encoder.remainder[k]; } _block++; if (_block < blocks) { - startBlock(); return; } _cw.addAll(_ecTotal); _blocks = [] as Array<Array<Number>>; + _phase = 2; + } + + // The empty placement grid (a new ByteArray is zeroed natively). + private function startPlacement() as Void { _nrow = _size[1] * _size[2]; _ncol = _nrow; - _place = new [_nrow * _ncol] as Array<Number>; - for (var k = 0; k < _place.size(); k++) { - _place[k] = 0; - } - _phase = 2; + _place = new [2 * _nrow * _ncol]b; + _phase = 3; + } + + private function placeAt(i as Number) as Number { + return (_place[2 * i] << 8) | _place[2 * i + 1]; } - // ISO 16022 annex F placement, SWEEPS_PER_STEP diagonal sweeps per step. + private function setPlace(i as Number, v as Number) as Void { + _place[2 * i] = (v >> 8) & 0xFF; + _place[2 * i + 1] = v & 0xFF; + } + + // ISO 16022 annex F placement, one diagonal pass per step: each + // sweep is a pass up and to the right (after the corner cases), then + // one down and to the left (_downPass). private function placeStep() as Void { var nrow = _nrow; var ncol = _ncol; - for (var s = 0; s < SWEEPS_PER_STEP && (_row < nrow || _col < ncol); s++) { + if (!_downPass) { + if (_row >= nrow && _col >= ncol) { + finishPlacement(); + return; + } if (_row == nrow && _col == 0) { corner1(); } @@ -256,7 +325,8 @@ module DataMatrix { corner4(); } do { - if (_row < nrow && _col >= 0 && _place[_row * ncol + _col] == 0) { + var at = 2 * (_row * ncol + _col); + if (_row < nrow && _col >= 0 && _place[at] == 0 && _place[at + 1] == 0) { utah(_row, _col); } _row -= 2; @@ -264,24 +334,33 @@ module DataMatrix { } while (_row >= 0 && _col < ncol); _row += 1; _col += 3; - do { - if (_row >= 0 && _col < ncol && _place[_row * ncol + _col] == 0) { - utah(_row, _col); - } - _row += 2; - _col -= 2; - } while (_row < nrow && _col >= 0); - _row += 3; - _col += 1; - } - if (_row < nrow || _col < ncol) { + _downPass = true; return; } - if (_place[nrow * ncol - 1] == 0) { - _place[nrow * ncol - 1] = -1; - _place[nrow * ncol - ncol - 2] = -1; + do { + var at = 2 * (_row * ncol + _col); + if (_row >= 0 && _col < ncol && _place[at] == 0 && _place[at + 1] == 0) { + utah(_row, _col); + } + _row += 2; + _col -= 2; + } while (_row < nrow && _col >= 0); + _row += 3; + _col += 1; + _downPass = false; + if (_row >= nrow && _col >= ncol) { + finishPlacement(); } - _phase = 3; + } + + // The fixed corner modules when the grid leaves them empty. + private function finishPlacement() as Void { + var n = _nrow * _ncol; + if (placeAt(n - 1) == 0) { + setPlace(n - 1, FIXED_DARK); + setPlace(n - _ncol - 2, FIXED_DARK); + } + _phase = 4; } private function setModule(row as Number, col as Number, bit as Number) as Void { @@ -295,7 +374,10 @@ module DataMatrix { c += _ncol; r += 4 - ((_ncol + 4) % 8); } - _place[r * _ncol + c] = 10 * _chr + bit; + var v = 10 * _chr + bit; + var at = 2 * (r * _ncol + c); + _place[at] = v >> 8; + _place[at + 1] = v & 0xFF; } private function utah(row as Number, col as Number) as Void { @@ -359,13 +441,19 @@ module DataMatrix { } // The symbol: data modules mapped into the regions, plus each - // region's finder (solid left and bottom) and clock (top, right). + // region's finder (solid left and bottom) and clock (top, right); + // BUILD_MODULES_PER_STEP modules (whole rows) per step. private function build() as Void { var size = _size[0]; var region = _size[1]; var box = region + 2; - var m = new [size * size]b; - for (var y = 0; y < size; y++) { + if (_modules.size() == 0) { + _modules = new [size * size]b; + } + var m = _modules; + var rows = BUILD_MODULES_PER_STEP / size > 0 ? BUILD_MODULES_PER_STEP / size : 1; + var last = _buildRow + rows < size ? _buildRow + rows : size; + for (var y = _buildRow; y < last; y++) { var ry = y % box; for (var x = 0; x < size; x++) { var rx = x % box; @@ -379,8 +467,9 @@ module DataMatrix { } else { var mr = (y / box) * region + ry - 1; var mc = (x / box) * region + rx - 1; - var v = _place[mr * _ncol + mc]; - if (v == -1) { + var at = 2 * (mr * _ncol + mc); + var v = (_place[at] << 8) | _place[at + 1]; + if (v == FIXED_DARK) { dark = 1; } else if (v > 0) { var cw = _cw[v / 10 - 1]; @@ -390,9 +479,14 @@ module DataMatrix { m[y * size + x] = dark; } } - _place = [] as Array<Number>; + _buildRow = last; + if (_buildRow < size) { + return; + } + _place = []b; + _modules = []b; _result = new Codes.Matrix(size, size, QUIET, m); - _phase = 4; + _phase = 5; } } } |
