From af872343c0e9740f1fd260b90352c3f4594ae3d3 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Fri, 26 Oct 2018 06:53:12 +0000 Subject: [PATCH] Completely rewrote the algorithm for detecting finder-like patterns, making it more accurate and compliant with the QR Code specification, in all languages. --- c/qrcodegen.c | 65 ++++++++++++++------- cpp/QrCode.cpp | 52 ++++++++++------- cpp/QrCode.hpp | 12 ++++ java/io/nayuki/qrcodegen/QrCode.java | 57 ++++++++++++------- javascript/qrcodegen.js | 57 ++++++++++++------- python/qrcodegen.py | 46 +++++++++------ rust/src/lib.rs | 84 ++++++++++++++++++++-------- typescript/qrcodegen.ts | 57 ++++++++++++------- 8 files changed, 292 insertions(+), 138 deletions(-) diff --git a/c/qrcodegen.c b/c/qrcodegen.c index dc3d99b..0772f65 100644 --- a/c/qrcodegen.c +++ b/c/qrcodegen.c @@ -72,6 +72,8 @@ static void fillRectangle(int left, int top, int width, int height, uint8_t qrco static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]); static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask); static long getPenaltyScore(const uint8_t qrcode[]); +static void addRunToHistory(unsigned char run, unsigned char history[7]); +static bool hasFinderLikePattern(unsigned char runHistory[7]); testable bool getModule(const uint8_t qrcode[], int x, int y); testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack); @@ -630,10 +632,11 @@ static long getPenaltyScore(const uint8_t qrcode[]) { int qrsize = qrcodegen_getSize(qrcode); long result = 0; - // Adjacent modules in row having same color + // Adjacent modules in row having same color, and finder-like patterns for (int y = 0; y < qrsize; y++) { + unsigned char runHistory[7] = {0}; bool color = false; - int runX = 0; + unsigned char runX = 0; for (int x = 0; x < qrsize; x++) { if (getModule(qrcode, x, y) == color) { runX++; @@ -642,15 +645,24 @@ static long getPenaltyScore(const uint8_t qrcode[]) { else if (runX > 5) result++; } else { + addRunToHistory(runX, runHistory); + if (!color && hasFinderLikePattern(runHistory)) + result += PENALTY_N3; color = getModule(qrcode, x, y); runX = 1; } } + addRunToHistory(runX, runHistory); + if (color) + addRunToHistory(0, runHistory); // Dummy run of white + if (hasFinderLikePattern(runHistory)) + result += PENALTY_N3; } - // Adjacent modules in column having same color + // Adjacent modules in column having same color, and finder-like patterns for (int x = 0; x < qrsize; x++) { + unsigned char runHistory[7] = {0}; bool color = false; - int runY = 0; + unsigned char runY = 0; for (int y = 0; y < qrsize; y++) { if (getModule(qrcode, x, y) == color) { runY++; @@ -659,10 +671,18 @@ static long getPenaltyScore(const uint8_t qrcode[]) { else if (runY > 5) result++; } else { + addRunToHistory(runY, runHistory); + if (!color && hasFinderLikePattern(runHistory)) + result += PENALTY_N3; color = getModule(qrcode, x, y); runY = 1; } } + addRunToHistory(runY, runHistory); + if (color) + addRunToHistory(0, runHistory); // Dummy run of white + if (hasFinderLikePattern(runHistory)) + result += PENALTY_N3; } // 2*2 blocks of modules having same color @@ -676,23 +696,6 @@ static long getPenaltyScore(const uint8_t qrcode[]) { } } - // Finder-like pattern in rows - for (int y = 0; y < qrsize; y++) { - for (int x = 0, bits = 0; x < qrsize; x++) { - bits = ((bits << 1) & 0x7FF) | (getModule(qrcode, x, y) ? 1 : 0); - if (x >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated - result += PENALTY_N3; - } - } - // Finder-like pattern in columns - for (int x = 0; x < qrsize; x++) { - for (int y = 0, bits = 0; y < qrsize; y++) { - bits = ((bits << 1) & 0x7FF) | (getModule(qrcode, x, y) ? 1 : 0); - if (y >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated - result += PENALTY_N3; - } - } - // Balance of black and white modules int black = 0; for (int y = 0; y < qrsize; y++) { @@ -709,6 +712,26 @@ static long getPenaltyScore(const uint8_t qrcode[]) { } +// Inserts the given value to the front of the given array, which shifts over the +// existing values and deletes the last value. A helper function for getPenaltyScore(). +static void addRunToHistory(unsigned char run, unsigned char history[7]) { + memmove(&history[1], &history[0], 6 * sizeof(history[0])); + history[0] = run; +} + + +// Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and +// surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore(). +// Must only be called immediately after a run of white modules has ended. +static bool hasFinderLikePattern(unsigned char runHistory[7]) { + unsigned char n = runHistory[1]; + // The maximum QR Code size is 177, hence the run length n <= 177. + // Arithmetic is promoted to int, so n*4 will not overflow. + return n > 0 && runHistory[2] == n && runHistory[4] == n && runHistory[5] == n + && runHistory[3] == n * 3 && (runHistory[0] >= n * 4 || runHistory[6] >= n * 4); +} + + /*---- Basic QR Code information ----*/ diff --git a/cpp/QrCode.cpp b/cpp/QrCode.cpp index d91343b..de23a7f 100644 --- a/cpp/QrCode.cpp +++ b/cpp/QrCode.cpp @@ -428,8 +428,9 @@ int QrCode::handleConstructorMasking(int mask) { long QrCode::getPenaltyScore() const { long result = 0; - // Adjacent modules in row having same color + // Adjacent modules in row having same color, and finder-like patterns for (int y = 0; y < size; y++) { + std::deque runHistory(7, 0); bool color = false; int runX = 0; for (int x = 0; x < size; x++) { @@ -440,13 +441,22 @@ long QrCode::getPenaltyScore() const { else if (runX > 5) result++; } else { + addRunToHistory(runX, runHistory); + if (!color && hasFinderLikePattern(runHistory)) + result += PENALTY_N3; color = module(x, y); runX = 1; } } + addRunToHistory(runX, runHistory); + if (color) + addRunToHistory(0, runHistory); // Dummy run of white + if (hasFinderLikePattern(runHistory)) + result += PENALTY_N3; } - // Adjacent modules in column having same color + // Adjacent modules in column having same color, and finder-like patterns for (int x = 0; x < size; x++) { + std::deque runHistory(7, 0); bool color = false; int runY = 0; for (int y = 0; y < size; y++) { @@ -457,10 +467,18 @@ long QrCode::getPenaltyScore() const { else if (runY > 5) result++; } else { + addRunToHistory(runY, runHistory); + if (!color && hasFinderLikePattern(runHistory)) + result += PENALTY_N3; color = module(x, y); runY = 1; } } + addRunToHistory(runY, runHistory); + if (color) + addRunToHistory(0, runHistory); // Dummy run of white + if (hasFinderLikePattern(runHistory)) + result += PENALTY_N3; } // 2*2 blocks of modules having same color @@ -474,23 +492,6 @@ long QrCode::getPenaltyScore() const { } } - // Finder-like pattern in rows - for (int y = 0; y < size; y++) { - for (int x = 0, bits = 0; x < size; x++) { - bits = ((bits << 1) & 0x7FF) | (module(x, y) ? 1 : 0); - if (x >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated - result += PENALTY_N3; - } - } - // Finder-like pattern in columns - for (int x = 0; x < size; x++) { - for (int y = 0, bits = 0; y < size; y++) { - bits = ((bits << 1) & 0x7FF) | (module(x, y) ? 1 : 0); - if (y >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated - result += PENALTY_N3; - } - } - // Balance of black and white modules int black = 0; for (const vector &row : modules) { @@ -544,6 +545,19 @@ int QrCode::getNumDataCodewords(int ver, Ecc ecl) { } +void QrCode::addRunToHistory(int run, std::deque &history) { + history.pop_back(); + history.push_front(run); +} + + +bool QrCode::hasFinderLikePattern(const std::deque &runHistory) { + int n = runHistory.at(1); + return n > 0 && runHistory.at(2) == n && runHistory.at(4) == n && runHistory.at(5) == n + && runHistory.at(3) == n * 3 && std::max(runHistory.at(0), runHistory.at(6)) >= n * 4; +} + + bool QrCode::getBit(long x, int i) { return ((x >> i) & 1) != 0; } diff --git a/cpp/QrCode.hpp b/cpp/QrCode.hpp index df2fcde..2f69843 100644 --- a/cpp/QrCode.hpp +++ b/cpp/QrCode.hpp @@ -24,6 +24,7 @@ #pragma once #include +#include #include #include #include "QrSegment.hpp" @@ -278,6 +279,17 @@ class QrCode final { private: static int getNumDataCodewords(int ver, Ecc ecl); + // Inserts the given value to the front of the given array, which shifts over the + // existing values and deletes the last value. A helper function for getPenaltyScore(). + private: static void addRunToHistory(int run, std::deque &history); + + + // Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and + // surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore(). + // Must only be called immediately after a run of white modules has ended. + private: static bool hasFinderLikePattern(const std::deque &runHistory); + + // Returns true iff the i'th bit of x is set to 1. private: static bool getBit(long x, int i); diff --git a/java/io/nayuki/qrcodegen/QrCode.java b/java/io/nayuki/qrcodegen/QrCode.java index fc2c708..ce28b10 100644 --- a/java/io/nayuki/qrcodegen/QrCode.java +++ b/java/io/nayuki/qrcodegen/QrCode.java @@ -595,8 +595,9 @@ public final class QrCode { private int getPenaltyScore() { int result = 0; - // Adjacent modules in row having same color + // Adjacent modules in row having same color, and finder-like patterns for (int y = 0; y < size; y++) { + int[] runHistory = new int[7]; boolean color = false; int runX = 0; for (int x = 0; x < size; x++) { @@ -607,13 +608,22 @@ public final class QrCode { else if (runX > 5) result++; } else { + addRunToHistory(runX, runHistory); + if (!color && hasFinderLikePattern(runHistory)) + result += PENALTY_N3; color = modules[y][x]; runX = 1; } } + addRunToHistory(runX, runHistory); + if (color) + addRunToHistory(0, runHistory); // Dummy run of white + if (hasFinderLikePattern(runHistory)) + result += PENALTY_N3; } - // Adjacent modules in column having same color + // Adjacent modules in column having same color, and finder-like patterns for (int x = 0; x < size; x++) { + int[] runHistory = new int[7]; boolean color = false; int runY = 0; for (int y = 0; y < size; y++) { @@ -624,10 +634,18 @@ public final class QrCode { else if (runY > 5) result++; } else { + addRunToHistory(runY, runHistory); + if (!color && hasFinderLikePattern(runHistory)) + result += PENALTY_N3; color = modules[y][x]; runY = 1; } } + addRunToHistory(runY, runHistory); + if (color) + addRunToHistory(0, runHistory); // Dummy run of white + if (hasFinderLikePattern(runHistory)) + result += PENALTY_N3; } // 2*2 blocks of modules having same color @@ -641,23 +659,6 @@ public final class QrCode { } } - // Finder-like pattern in rows - for (int y = 0; y < size; y++) { - for (int x = 0, bits = 0; x < size; x++) { - bits = ((bits << 1) & 0b11111111111) | (modules[y][x] ? 1 : 0); - if (x >= 10 && (bits == 0b00001011101 || bits == 0b10111010000)) // Needs 11 bits accumulated - result += PENALTY_N3; - } - } - // Finder-like pattern in columns - for (int x = 0; x < size; x++) { - for (int y = 0, bits = 0; y < size; y++) { - bits = ((bits << 1) & 0b11111111111) | (modules[y][x] ? 1 : 0); - if (y >= 10 && (bits == 0b00001011101 || bits == 0b10111010000)) // Needs 11 bits accumulated - result += PENALTY_N3; - } - } - // Balance of black and white modules int black = 0; for (boolean[] row : modules) { @@ -734,6 +735,24 @@ public final class QrCode { } + // Inserts the given value to the front of the given array, which shifts over the + // existing values and deletes the last value. A helper function for getPenaltyScore(). + private static void addRunToHistory(int run, int[] history) { + System.arraycopy(history, 0, history, 1, history.length - 1); + history[0] = run; + } + + + // Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and + // surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore(). + // Must only be called immediately after a run of white modules has ended. + private static boolean hasFinderLikePattern(int[] runHistory) { + int n = runHistory[1]; + return n > 0 && runHistory[2] == n && runHistory[4] == n && runHistory[5] == n + && runHistory[3] == n * 3 && Math.max(runHistory[0], runHistory[6]) >= n * 4; + } + + // Returns true iff the i'th bit of x is set to 1. static boolean getBit(int x, int i) { return ((x >>> i) & 1) != 0; diff --git a/javascript/qrcodegen.js b/javascript/qrcodegen.js index 398c850..b40bfd3 100644 --- a/javascript/qrcodegen.js +++ b/javascript/qrcodegen.js @@ -427,8 +427,9 @@ var qrcodegen = new function() { function getPenaltyScore() { var result = 0; - // Adjacent modules in row having same color + // Adjacent modules in row having same color, and finder-like patterns for (var y = 0; y < size; y++) { + var runHistory = [0,0,0,0,0,0,0]; var color = false; var runX = 0; for (var x = 0; x < size; x++) { @@ -439,13 +440,22 @@ var qrcodegen = new function() { else if (runX > 5) result++; } else { + QrCode.addRunToHistory(runX, runHistory); + if (!color && QrCode.hasFinderLikePattern(runHistory)) + result += QrCode.PENALTY_N3; color = modules[y][x]; runX = 1; } } + QrCode.addRunToHistory(runX, runHistory); + if (color) + QrCode.addRunToHistory(0, runHistory); // Dummy run of white + if (QrCode.hasFinderLikePattern(runHistory)) + result += QrCode.PENALTY_N3; } - // Adjacent modules in column having same color + // Adjacent modules in column having same color, and finder-like patterns for (var x = 0; x < size; x++) { + var runHistory = [0,0,0,0,0,0,0]; var color = false; var runY = 0; for (var y = 0; y < size; y++) { @@ -456,10 +466,18 @@ var qrcodegen = new function() { else if (runY > 5) result++; } else { + QrCode.addRunToHistory(runY, runHistory); + if (!color && QrCode.hasFinderLikePattern(runHistory)) + result += QrCode.PENALTY_N3; color = modules[y][x]; runY = 1; } } + QrCode.addRunToHistory(runY, runHistory); + if (color) + QrCode.addRunToHistory(0, runHistory); // Dummy run of white + if (QrCode.hasFinderLikePattern(runHistory)) + result += QrCode.PENALTY_N3; } // 2*2 blocks of modules having same color @@ -473,23 +491,6 @@ var qrcodegen = new function() { } } - // Finder-like pattern in rows - for (var y = 0; y < size; y++) { - for (var x = 0, bits = 0; x < size; x++) { - bits = ((bits << 1) & 0x7FF) | (modules[y][x] ? 1 : 0); - if (x >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated - result += QrCode.PENALTY_N3; - } - } - // Finder-like pattern in columns - for (var x = 0; x < size; x++) { - for (var y = 0, bits = 0; y < size; y++) { - bits = ((bits << 1) & 0x7FF) | (modules[y][x] ? 1 : 0); - if (y >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated - result += QrCode.PENALTY_N3; - } - } - // Balance of black and white modules var black = 0; modules.forEach(function(row) { @@ -666,6 +667,24 @@ var qrcodegen = new function() { }; + // Inserts the given value to the front of the given array, which shifts over the + // existing values and deletes the last value. A helper function for getPenaltyScore(). + QrCode.addRunToHistory = function(run, history) { + history.pop(); + history.unshift(run); + }; + + + // Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and + // surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore(). + // Must only be called immediately after a run of white modules has ended. + QrCode.hasFinderLikePattern = function(runHistory) { + var n = runHistory[1]; + return n > 0 && runHistory[2] == n && runHistory[4] == n && runHistory[5] == n + && runHistory[3] == n * 3 && Math.max(runHistory[0], runHistory[6]) >= n * 4; + }; + + /*---- Constants and tables for QrCode ----*/ var MIN_VERSION = 1; // The minimum version number supported in the QR Code Model 2 standard diff --git a/python/qrcodegen.py b/python/qrcodegen.py index c32f02c..1a3f417 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -21,7 +21,7 @@ # Software. # -import itertools, re, sys +import collections, itertools, re, sys """ @@ -462,8 +462,9 @@ class QrCode(object): size = self._size modules = self._modules - # Adjacent modules in row having same color + # Adjacent modules in row having same color, and finder-like patterns for y in range(size): + runhistory = collections.deque([0] * 7, 7) color = False runx = 0 for x in range(size): @@ -474,10 +475,19 @@ class QrCode(object): elif runx > 5: result += 1 else: + runhistory.appendleft(runx) + if not color and QrCode.has_finder_like_pattern(runhistory): + result += QrCode._PENALTY_N3 color = modules[y][x] runx = 1 - # Adjacent modules in column having same color + runhistory.appendleft(runx) + if color: + runhistory.appendleft(0) # Dummy run of white + if QrCode.has_finder_like_pattern(runhistory): + result += QrCode._PENALTY_N3 + # Adjacent modules in column having same color, and finder-like patterns for x in range(size): + runhistory = collections.deque([0] * 7, 7) color = False runy = 0 for y in range(size): @@ -488,8 +498,16 @@ class QrCode(object): elif runy > 5: result += 1 else: + runhistory.appendleft(runy) + if not color and QrCode.has_finder_like_pattern(runhistory): + result += QrCode._PENALTY_N3 color = modules[y][x] runy = 1 + runhistory.appendleft(runy) + if color: + runhistory.appendleft(0) # Dummy run of white + if QrCode.has_finder_like_pattern(runhistory): + result += QrCode._PENALTY_N3 # 2*2 blocks of modules having same color for y in range(size - 1): @@ -497,21 +515,6 @@ class QrCode(object): if modules[y][x] == modules[y][x + 1] == modules[y + 1][x] == modules[y + 1][x + 1]: result += QrCode._PENALTY_N2 - # Finder-like pattern in rows - for y in range(size): - bits = 0 - for x in range(size): - bits = ((bits << 1) & 0x7FF) | (1 if modules[y][x] else 0) - if x >= 10 and bits in (0x05D, 0x5D0): # Needs 11 bits accumulated - result += QrCode._PENALTY_N3 - # Finder-like pattern in columns - for x in range(size): - bits = 0 - for y in range(size): - bits = ((bits << 1) & 0x7FF) | (1 if modules[y][x] else 0) - if y >= 10 and bits in (0x05D, 0x5D0): # Needs 11 bits accumulated - result += QrCode._PENALTY_N3 - # Balance of black and white modules black = sum((1 if cell else 0) for row in modules for cell in row) total = size**2 # Note that size is odd, so black/total != 1/2 @@ -564,6 +567,13 @@ class QrCode(object): * QrCode._NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver] + @staticmethod + def has_finder_like_pattern(runhistory): + n = runhistory[1] + return n > 0 and n == runhistory[2] == runhistory[4] == runhistory[5] \ + and runhistory[3] == n * 3 and max(runhistory[0], runhistory[6]) >= n * 4 + + # ---- Constants and tables ---- MIN_VERSION = 1 # The minimum version number supported in the QR Code Model 2 standard diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 305939f..1b8ff44 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -645,8 +645,9 @@ impl QrCode { let mut result: i32 = 0; let size: i32 = self.size; - // Adjacent modules in row having same color + // Adjacent modules in row having same color, and finder-like patterns for y in 0 .. size { + let mut runhistory = RunHistory::new(); let mut color = false; let mut runx: i32 = 0; for x in 0 .. size { @@ -658,13 +659,25 @@ impl QrCode { result += 1; } } else { + runhistory.add_run(runx); + if !color && runhistory.has_finder_like_pattern() { + result += PENALTY_N3; + } color = self.module(x, y); runx = 1; } } + runhistory.add_run(runx); + if color { + runhistory.add_run(0); // Dummy run of white + } + if runhistory.has_finder_like_pattern() { + result += PENALTY_N3; + } } - // Adjacent modules in column having same color + // Adjacent modules in column having same color, and finder-like patterns for x in 0 .. size { + let mut runhistory = RunHistory::new(); let mut color = false; let mut runy: i32 = 0; for y in 0 .. size { @@ -676,10 +689,21 @@ impl QrCode { result += 1; } } else { + runhistory.add_run(runy); + if !color && runhistory.has_finder_like_pattern() { + result += PENALTY_N3; + } color = self.module(x, y); runy = 1; } } + runhistory.add_run(runy); + if color { + runhistory.add_run(0); // Dummy run of white + } + if runhistory.has_finder_like_pattern() { + result += PENALTY_N3; + } } // 2*2 blocks of modules having same color @@ -694,27 +718,6 @@ impl QrCode { } } - // Finder-like pattern in rows - for y in 0 .. size { - let mut bits: u32 = 0; - for x in 0 .. size { - bits = ((bits << 1) & 0x7FF) | (self.module(x, y) as u32); - if x >= 10 && (bits == 0x05D || bits == 0x5D0) { // Needs 11 bits accumulated - result += PENALTY_N3; - } - } - } - // Finder-like pattern in columns - for x in 0 .. size { - let mut bits: u32 = 0; - for y in 0 .. size { - bits = ((bits << 1) & 0x7FF) | (self.module(x, y) as u32); - if y >= 10 && (bits == 0x05D || bits == 0x5D0) { // Needs 11 bits accumulated - result += PENALTY_N3; - } - } - } - // Balance of black and white modules let mut black: i32 = 0; for color in &self.modules { @@ -939,6 +942,41 @@ impl ReedSolomonGenerator { +/*---- RunHistory functionality ----*/ + +struct RunHistory(std::collections::VecDeque); + + +impl RunHistory { + + fn new() -> Self { + let mut temp = std::collections::VecDeque::::new(); + temp.resize(7, 0); + RunHistory(temp) + } + + + // Inserts the given value to the front of this array, which shifts over the existing + // values and deletes the last value. A helper function for get_penalty_score(). + fn add_run(&mut self, run: i32) { + self.0.pop_back(); + self.0.push_front(run); + } + + + // Tests whether this run history has the pattern of ratio 1:1:3:1:1 in the middle, and + // surrounded by at least 4 on either or both ends. A helper function for get_penalty_score(). + // Must only be called immediately after a run of white modules has ended. + fn has_finder_like_pattern(&self) -> bool { + let n = self.0[1]; + n > 0 && self.0[2] == n && self.0[4] == n && self.0[5] == n + && self.0[3] == n * 3 && std::cmp::max(self.0[0], self.0[6]) >= n * 4 + } + +} + + + /*---- QrSegment functionality ----*/ /// A segment of character/binary/control data in a QR Code symbol. diff --git a/typescript/qrcodegen.ts b/typescript/qrcodegen.ts index 5ad0dbf..9c3d4ff 100644 --- a/typescript/qrcodegen.ts +++ b/typescript/qrcodegen.ts @@ -510,8 +510,9 @@ namespace qrcodegen { private getPenaltyScore(): int { let result: int = 0; - // Adjacent modules in row having same color + // Adjacent modules in row having same color, and finder-like patterns for (let y = 0; y < this.size; y++) { + let runHistory = [0,0,0,0,0,0,0]; let color = false; let runX = 0; for (let x = 0; x < this.size; x++) { @@ -522,13 +523,22 @@ namespace qrcodegen { else if (runX > 5) result++; } else { + QrCode.addRunToHistory(runX, runHistory); + if (!color && QrCode.hasFinderLikePattern(runHistory)) + result += QrCode.PENALTY_N3; color = this.modules[y][x]; runX = 1; } } + QrCode.addRunToHistory(runX, runHistory); + if (color) + QrCode.addRunToHistory(0, runHistory); // Dummy run of white + if (QrCode.hasFinderLikePattern(runHistory)) + result += QrCode.PENALTY_N3; } - // Adjacent modules in column having same color + // Adjacent modules in column having same color, and finder-like patterns for (let x = 0; x < this.size; x++) { + let runHistory = [0,0,0,0,0,0,0]; let color = false; let runY = 0; for (let y = 0; y < this.size; y++) { @@ -539,10 +549,18 @@ namespace qrcodegen { else if (runY > 5) result++; } else { + QrCode.addRunToHistory(runY, runHistory); + if (!color && QrCode.hasFinderLikePattern(runHistory)) + result += QrCode.PENALTY_N3; color = this.modules[y][x]; runY = 1; } } + QrCode.addRunToHistory(runY, runHistory); + if (color) + QrCode.addRunToHistory(0, runHistory); // Dummy run of white + if (QrCode.hasFinderLikePattern(runHistory)) + result += QrCode.PENALTY_N3; } // 2*2 blocks of modules having same color @@ -556,23 +574,6 @@ namespace qrcodegen { } } - // Finder-like pattern in rows - for (let y = 0; y < this.size; y++) { - for (let x = 0, bits = 0; x < this.size; x++) { - bits = ((bits << 1) & 0b11111111111) | (this.modules[y][x] ? 1 : 0); - if (x >= 10 && (bits == 0b00001011101 || bits == 0b10111010000)) // Needs 11 bits accumulated - result += QrCode.PENALTY_N3; - } - } - // Finder-like pattern in columns - for (let x = 0; x < this.size; x++) { - for (let y = 0, bits = 0; y < this.size; y++) { - bits = ((bits << 1) & 0b11111111111) | (this.modules[y][x] ? 1 : 0); - if (y >= 10 && (bits == 0b00001011101 || bits == 0b10111010000)) // Needs 11 bits accumulated - result += QrCode.PENALTY_N3; - } - } - // Balance of black and white modules let black: int = 0; for (let row of this.modules) { @@ -636,6 +637,24 @@ namespace qrcodegen { } + // Inserts the given value to the front of the given array, which shifts over the + // existing values and deletes the last value. A helper function for getPenaltyScore(). + private static addRunToHistory(run: int, history: Array): void { + history.pop(); + history.unshift(run); + } + + + // Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and + // surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore(). + // Must only be called immediately after a run of white modules has ended. + private static hasFinderLikePattern(runHistory: Array): boolean { + const n: int = runHistory[1]; + return n > 0 && runHistory[2] == n && runHistory[4] == n && runHistory[5] == n + && runHistory[3] == n * 3 && Math.max(runHistory[0], runHistory[6]) >= n * 4; + } + + /*-- Constants and tables --*/ // The minimum version number supported in the QR Code Model 2 standard.