aboutsummaryrefslogtreecommitdiff
path: root/source/codes/Codes.mc
diff options
context:
space:
mode:
Diffstat (limited to 'source/codes/Codes.mc')
-rw-r--r--source/codes/Codes.mc279
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;
}
}