diff options
Diffstat (limited to 'test/codes/QrTest.mc')
| -rw-r--r-- | test/codes/QrTest.mc | 124 |
1 files changed, 123 insertions, 1 deletions
diff --git a/test/codes/QrTest.mc b/test/codes/QrTest.mc index 50cb35a..2f81887 100644 --- a/test/codes/QrTest.mc +++ b/test/codes/QrTest.mc @@ -1,5 +1,5 @@ // Author: cirakg & claude ai -// Summary: Tests for the QR encoder: segno reference matrices (forced and automatic version and mask), decoding dumps, capacity, timing. +// Summary: Tests for the QR encoder: segno reference matrices (forced and automatic version and mask), packed mask scores against a per-module reference, decoding dumps, capacity, timing. import Toybox.Lang; import Toybox.System; @@ -21,6 +21,128 @@ module QrFixtures { } } +// The mask penalty computed module by module, the way the encoder did +// before M8 packed its lines: the reference for the packed scorer. It +// scores a finished symbol (forced mask) with its format and version areas +// made light again, as the encoder scores them. +(:test) +module QrReference { + + function penalty(code as Qr.MatrixCode) as Number { + var n = code.size; + var m = Bytes.copy(code.modules); + for (var i = 0; i <= 8; i++) { + if (i != 6) { + m[i * n + 8] = 0; + m[8 * n + i] = 0; + } + } + for (var i = 0; i < 8; i++) { + m[8 * n + n - 1 - i] = 0; + m[(n - 1 - i) * n + 8] = 0; + } + m[(n - 8) * n + 8] = 0; + if (code.version >= 7) { + for (var i = 0; i < 18; i++) { + var a = n - 11 + i % 3; + var b = i / 3; + m[b * n + a] = 0; + m[a * n + b] = 0; + } + } + var score = 0; + var dark = 0; + for (var y = 0; y < n; y++) { + var row = m.slice(y * n, y * n + n); + score += line(row); + for (var x = 0; x < n; x++) { + dark += row[x]; + if (y > 0 && x > 0 && row[x] == row[x - 1] && row[x] == m[(y - 1) * n + x] && row[x] == m[(y - 1) * n + x - 1]) { + score += 3; + } + } + } + for (var x = 0; x < n; x++) { + var column = new [n]b; + for (var y = 0; y < n; y++) { + column[y] = m[y * n + x]; + } + score += line(column); + } + var total = n * n; + return score + 10 * ((dark * 100 - 50 * total).abs() / (5 * total)); + } + + function line(l as ByteArray) as Number { + var n = l.size(); + var score = 0; + var run = 1; + var window = l[0]; + var nextAllowed = 0; + for (var k = 1; k < n; k++) { + var c = l[k]; + if (c == l[k - 1]) { + run++; + } else { + if (run >= 5) { + score += run - 2; + } + run = 1; + } + 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 || light(l, idx - 4 < 0 ? 0 : idx - 4, idx) + || light(l, after, after + 4 > n ? n : after + 4)) { + score += 40; + nextAllowed = idx + 7; + } else { + nextAllowed = idx + 4; + } + } + } + } + return run >= 5 ? score + run - 2 : score; + } + + function light(l as ByteArray, from as Number, to as Number) as Boolean { + for (var i = from; i < to; i++) { + if (l[i] != 0) { + return false; + } + } + return true; + } +} + +// The packed scorer gives every mask exactly the per-module penalty, for +// small and large versions, every level, and versions with version areas. +(:test) +function qrPackedScoresMatchReference(logger as Test.Logger) as Boolean { + var cases = [["https://example.com/member/12345", 1, 0], ["HELLO WORLD 1234567890", 3, 0], ["Ünïcödé · 2026", 0, 7], + ["a", 2, 0], ["Version ten payload", 1, 10], ["0123456789", 0, 21]]; + for (var c = 0; c < cases.size(); c++) { + var row = cases[c] as Array; + var job = new Qr.QrJob(row[0] as String, row[1] as Number, row[2] as Number, -1); + Jobs.runToCompletion(job, 1); + if (job.scores.size() != 8) { + logger.error("case " + c + ": " + job.scores.size() + " scores"); + return false; + } + for (var mask = 0; mask < 8; mask++) { + var forced = Qr.encode(row[0] as String, row[1] as Number, (job.result as Qr.MatrixCode).version, mask); + var expected = QrReference.penalty(forced as Qr.MatrixCode); + if (job.scores[mask] != expected) { + logger.error("case " + c + " mask " + mask + ": packed " + job.scores[mask] + ", reference " + expected); + return false; + } + } + } + return true; +} + // README 12.4 check 1 for QR: identical to segno with forced version and // mask, and the same automatic version and mask choice. (:test) |
