diff options
Diffstat (limited to 'source/codes/Codes.mc')
| -rw-r--r-- | source/codes/Codes.mc | 279 |
1 files changed, 243 insertions, 36 deletions
diff --git a/source/codes/Codes.mc b/source/codes/Codes.mc index f157b37..247acfa 100644 --- a/source/codes/Codes.mc +++ b/source/codes/Codes.mc @@ -76,55 +76,262 @@ module Codes { return height * rowHeight; } - // Compact form for the encrypted cache: width, height, quiet zone, - // row height (one byte each), then the modules packed 8 per byte - // (eight per loop round: this runs in one event, under the watchdog). + // The cache form (Packed), in one go: tests and small codes. Jobs + // use Packer. function toBytes() as ByteArray { - var m = modules; + var packer = new Packer(self); + Jobs.runToCompletion(packer, 1); + return packer.result; + } + + // A matrix from its cache form, unpacked in one go (tests); the app + // draws from Packed through RunFinder instead. + static function fromBytes(data as ByteArray) as Matrix? { + var packed = Packed.parse(data); + if (packed == null) { + return null; + } + var n = packed.width * packed.height; + var bits = new [n]b; + for (var i = 0; i < n; i++) { + bits[i] = packed.moduleAt(i); + } + var matrix = new Matrix(packed.width, packed.height, packed.quiet, bits); + matrix.rowHeight = packed.rowHeight; + return matrix; + } + } + + // A matrix packed 8 modules per byte, as cached (sealed) in the vault: + // FORMAT (2) | width (2 bytes) | height (2 bytes) | quiet | rowHeight + // | modules, row-major, most significant bit first + // Version 0.7 cached one byte of width, too few for wide PDF417 + // symbols; such a cache doesn't parse, and the code is encoded again. + class Packed { + static const FORMAT = 2; + static const HEADER = 7; + + var width as Number; + var height as Number; + var quiet as Number; + var rowHeight as Number; + var bytes as ByteArray; + + function initialize(data as ByteArray) { + bytes = data; + width = (data[1] << 8) | data[2]; + height = (data[3] << 8) | data[4]; + quiet = data[5]; + rowHeight = data[6]; + } + + // The cache bytes as a Packed, or null when they are not format 2 or + // the size doesn't match. + static function parse(data as ByteArray?) as Packed? { + if (data == null || data.size() < HEADER || data[0] != FORMAT) { + return null; + } + var p = new Packed(data); + var n = p.width * p.height; + if (p.width == 0 || p.height == 0 || p.rowHeight == 0 || data.size() != HEADER + (n + 7) / 8) { + return null; + } + return p; + } + + function drawnHeight() as Number { + return height * rowHeight; + } + + // Module i (row-major), 1 = dark. + function moduleAt(i as Number) as Number { + return (bytes[HEADER + (i >> 3)] >> (7 - (i & 7))) & 1; + } + } + + // Matrix -> Packed bytes, BYTES_PER_STEP output bytes (8 modules each) + // per step. + class Packer extends Jobs.Job { + static const BYTES_PER_STEP = 300; + + var result as ByteArray = []b; + + private var _matrix as Matrix; + private var _i as Number = 0; + + function initialize(matrix as Matrix) { + Job.initialize(); + _matrix = matrix; + var w = matrix.width; + var h = matrix.height; + result = new [Packed.HEADER + (w * h + 7) / 8]b; + result[0] = Packed.FORMAT; + result[1] = (w >> 8) & 0xFF; + result[2] = w & 0xFF; + result[3] = (h >> 8) & 0xFF; + result[4] = h & 0xFF; + result[5] = matrix.quiet; + result[6] = matrix.rowHeight; + } + + function step(budget as Number) as Boolean { + var m = _matrix.modules; var n = m.size(); var full = n / 8; - var out = new [4 + (n + 7) / 8]b; - out[0] = width; - out[1] = height; - out[2] = quiet; - out[3] = rowHeight; - for (var i = 0; i < full; i++) { + var out = result; + var stop = _i + BYTES_PER_STEP < full ? _i + BYTES_PER_STEP : full; + for (var i = _i; i < stop; i++) { var k = i * 8; - out[4 + i] = (m[k] << 7) | (m[k + 1] << 6) | (m[k + 2] << 5) | (m[k + 3] << 4) + out[Packed.HEADER + i] = (m[k] << 7) | (m[k + 1] << 6) | (m[k + 2] << 5) | (m[k + 3] << 4) | (m[k + 4] << 3) | (m[k + 5] << 2) | (m[k + 6] << 1) | m[k + 7]; } + _i = stop; + if (_i < full) { + return false; + } for (var k = full * 8; k < n; k++) { - out[4 + full] = out[4 + full] | (m[k] << (7 - k % 8)); + out[Packed.HEADER + full] = out[Packed.HEADER + full] | (m[k] << (7 - k % 8)); } - return out; + return true; } + } - static function fromBytes(data as ByteArray) as Matrix? { - if (data.size() < 4) { - return null; + // A code as the dark runs of each stored row, ready to draw with one + // fillRectangle per run (drawing module by module in onUpdate would be + // too much for the watchdog with large codes). Each run is + // (row << 20) | (first column << 10) | length. + class Runs { + var width as Number; + var height as Number; + var quiet as Number; + var rowHeight as Number; + var runs as Array<Number> = [] as Array<Number>; + + function initialize(packed as Packed) { + width = packed.width; + height = packed.height; + quiet = packed.quiet; + rowHeight = packed.rowHeight; + } + + function drawnHeight() as Number { + return height * rowHeight; + } + } + + // Packed -> Runs, about MODULES_PER_STEP modules (whole rows) per step; + // all-light and all-dark bytes are taken eight modules at a time. + class RunFinder extends Jobs.Job { + static const MODULES_PER_STEP = 800; + + var result as Runs; + + private var _packed as Packed; + private var _row as Number = 0; + + function initialize(packed as Packed) { + Job.initialize(); + _packed = packed; + result = new Runs(packed); + } + + function step(budget as Number) as Boolean { + var p = _packed; + var w = p.width; + var bytes = p.bytes; + var runs = result.runs; + var rows = MODULES_PER_STEP / w > 0 ? MODULES_PER_STEP / w : 1; + var last = _row + rows < p.height ? _row + rows : p.height; + for (var y = _row; y < last; y++) { + var bit = y * w; + var end = bit + w; + while (bit < end) { + var b = bytes[Packed.HEADER + (bit >> 3)]; + if ((bit & 7) == 0 && bit + 8 <= end && b == 0) { + bit += 8; + continue; + } + if (((b >> (7 - (bit & 7))) & 1) == 0) { + bit++; + continue; + } + var start = bit; + while (bit < end) { + var c = bytes[Packed.HEADER + (bit >> 3)]; + if ((bit & 7) == 0 && bit + 8 <= end && c == 0xFF) { + bit += 8; + } else if (((c >> (7 - (bit & 7))) & 1) == 1) { + bit++; + } else { + break; + } + } + runs.add((y << 20) | ((start - y * w) << 10) | (bit - start)); + } } - var w = data[0]; - var h = data[1]; - var n = w * h; - if (w == 0 || h == 0 || data[3] == 0 || data.size() != 4 + (n + 7) / 8) { - return null; + _row = last; + return _row >= p.height; + } + } + + // A 2D code from the cache or a fresh encoding to Runs: encode (unless + // cached), pack (`cache` then holds the bytes to store), find the runs. + class DisplayJob extends Jobs.Job { + var error as String? = null; + var cache as ByteArray? = null; + var runs as Runs? = null; + + private var _encoder as MatrixJob?; + private var _packer as Packer? = null; + private var _finder as RunFinder? = null; + + // Either an encoder (fresh) or the cached bytes. + function initialize(encoder as MatrixJob?, packed as Packed?) { + Job.initialize(); + _encoder = encoder; + if (packed != null) { + _finder = new RunFinder(packed); } - var bits = new [(n + 7) / 8 * 8]b; - for (var i = 0; i < (n + 7) / 8; i++) { - var v = data[4 + i]; - var k = i * 8; - bits[k] = (v >> 7) & 1; - bits[k + 1] = (v >> 6) & 1; - bits[k + 2] = (v >> 5) & 1; - bits[k + 3] = (v >> 4) & 1; - bits[k + 4] = (v >> 3) & 1; - bits[k + 5] = (v >> 2) & 1; - bits[k + 6] = (v >> 1) & 1; - bits[k + 7] = v & 1; + } + + // Progress 0-100: encoding is most of it. + function percent() as Number { + var e = _encoder; + return e != null ? e.percent() * 9 / 10 : 90; + } + + function step(budget as Number) as Boolean { + var finder = _finder; + if (finder != null) { + if (!finder.step(budget)) { + return false; + } + runs = finder.result; + _finder = null; + return true; } - var matrix = new Matrix(w, h, data[2], bits.size() == n ? bits : bits.slice(0, n)); - matrix.rowHeight = data[3]; - return matrix; + var packer = _packer; + if (packer != null) { + if (packer.step(budget)) { + cache = packer.result; + _packer = null; + _finder = new RunFinder(new Packed(packer.result)); + } + return false; + } + var encoder = _encoder as MatrixJob; + if (!encoder.step(budget)) { + return false; + } + var matrix = encoder.matrix(); + var err = encoder.error; + if (err != null || matrix == null) { + error = err != null ? err : "could not encode"; + return true; + } + _packer = new Packer(matrix); + _encoder = null; + return false; } } |
