From c5ad557eea19771a30d592399980e0da1bfa24e1 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 6 Jul 2019 03:30:51 +0000 Subject: [PATCH] Updated the finder pattern detector logic in the other 6 language versions to match Java code. --- c/qrcodegen.c | 81 +++++++++++++++++-------------- cpp/QrCode.cpp | 60 +++++++++++++---------- cpp/QrCode.hpp | 18 +++---- javascript/qrcodegen.js | 65 ++++++++++++++++--------- python/qrcodegen.py | 53 ++++++++++++--------- rust/src/lib.rs | 103 ++++++++++++++++++---------------------- typescript/qrcodegen.ts | 69 +++++++++++++++------------ 7 files changed, 248 insertions(+), 201 deletions(-) diff --git a/c/qrcodegen.c b/c/qrcodegen.c index 17f8aaa..84b47c8 100644 --- a/c/qrcodegen.c +++ b/c/qrcodegen.c @@ -72,8 +72,9 @@ 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(const unsigned char runHistory[7]); +static int finderPenaltyCountPatterns(const int runHistory[7], int qrsize); +static int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, int runHistory[7], int qrsize); +static void finderPenaltyAddHistory(int currentRunLength, int 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); @@ -634,9 +635,10 @@ static long getPenaltyScore(const uint8_t qrcode[]) { // Adjacent modules in row having same color, and finder-like patterns for (int y = 0; y < qrsize; y++) { - unsigned char runHistory[7] = {0}; bool runColor = false; - unsigned char runX = 0; + int runX = 0; + int runHistory[7] = {0}; + int padRun = qrsize; // Add white border to initial run for (int x = 0; x < qrsize; x++) { if (getModule(qrcode, x, y) == runColor) { runX++; @@ -645,24 +647,22 @@ static long getPenaltyScore(const uint8_t qrcode[]) { else if (runX > 5) result++; } else { - addRunToHistory(runX, runHistory); - if (!runColor && hasFinderLikePattern(runHistory)) - result += PENALTY_N3; + finderPenaltyAddHistory(runX + padRun, runHistory); + padRun = 0; + if (!runColor) + result += finderPenaltyCountPatterns(runHistory, qrsize) * PENALTY_N3; runColor = getModule(qrcode, x, y); runX = 1; } } - addRunToHistory(runX, runHistory); - if (runColor) - addRunToHistory(0, runHistory); // Dummy run of white - if (hasFinderLikePattern(runHistory)) - result += PENALTY_N3; + result += finderPenaltyTerminateAndCount(runColor, runX + padRun, runHistory, qrsize) * PENALTY_N3; } // Adjacent modules in column having same color, and finder-like patterns for (int x = 0; x < qrsize; x++) { - unsigned char runHistory[7] = {0}; bool runColor = false; - unsigned char runY = 0; + int runY = 0; + int runHistory[7] = {0}; + int padRun = qrsize; // Add white border to initial run for (int y = 0; y < qrsize; y++) { if (getModule(qrcode, x, y) == runColor) { runY++; @@ -671,18 +671,15 @@ static long getPenaltyScore(const uint8_t qrcode[]) { else if (runY > 5) result++; } else { - addRunToHistory(runY, runHistory); - if (!runColor && hasFinderLikePattern(runHistory)) - result += PENALTY_N3; + finderPenaltyAddHistory(runY + padRun, runHistory); + padRun = 0; + if (!runColor) + result += finderPenaltyCountPatterns(runHistory, qrsize) * PENALTY_N3; runColor = getModule(qrcode, x, y); runY = 1; } } - addRunToHistory(runY, runHistory); - if (runColor) - addRunToHistory(0, runHistory); // Dummy run of white - if (hasFinderLikePattern(runHistory)) - result += PENALTY_N3; + result += finderPenaltyTerminateAndCount(runColor, runY + padRun, runHistory, qrsize) * PENALTY_N3; } // 2*2 blocks of modules having same color @@ -712,23 +709,35 @@ 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; +// Can only be called immediately after a white run is added, and +// returns either 0, 1, or 2. A helper function for getPenaltyScore(). +static int finderPenaltyCountPatterns(const int runHistory[7], int qrsize) { + int n = runHistory[1]; + assert(n <= qrsize * 3); + bool core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n; + // The maximum QR Code size is 177, hence the black run length n <= 177. + // Arithmetic is promoted to int, so n*4 will not overflow. + return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) + + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0); } -// 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(const 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); +// Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). +static int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, int runHistory[7], int qrsize) { + if (currentRunColor) { // Terminate black run + finderPenaltyAddHistory(currentRunLength, runHistory); + currentRunLength = 0; + } + currentRunLength += qrsize; // Add white border to final run + finderPenaltyAddHistory(currentRunLength, runHistory); + return finderPenaltyCountPatterns(runHistory, qrsize); +} + + +// Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). +static void finderPenaltyAddHistory(int currentRunLength, int runHistory[7]) { + memmove(&runHistory[1], &runHistory[0], 6 * sizeof(runHistory[0])); + runHistory[0] = currentRunLength; } diff --git a/cpp/QrCode.cpp b/cpp/QrCode.cpp index dba2cc5..9880c54 100644 --- a/cpp/QrCode.cpp +++ b/cpp/QrCode.cpp @@ -427,9 +427,10 @@ long QrCode::getPenaltyScore() const { // Adjacent modules in row having same color, and finder-like patterns for (int y = 0; y < size; y++) { - std::deque runHistory(7, 0); bool runColor = false; int runX = 0; + std::array runHistory = {}; + int padRun = size; // Add white border to initial run for (int x = 0; x < size; x++) { if (module(x, y) == runColor) { runX++; @@ -438,24 +439,22 @@ long QrCode::getPenaltyScore() const { else if (runX > 5) result++; } else { - addRunToHistory(runX, runHistory); - if (!runColor && hasFinderLikePattern(runHistory)) - result += PENALTY_N3; + finderPenaltyAddHistory(runX + padRun, runHistory); + padRun = 0; + if (!runColor) + result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; runColor = module(x, y); runX = 1; } } - addRunToHistory(runX, runHistory); - if (runColor) - addRunToHistory(0, runHistory); // Dummy run of white - if (hasFinderLikePattern(runHistory)) - result += PENALTY_N3; + result += finderPenaltyTerminateAndCount(runColor, runX + padRun, runHistory) * PENALTY_N3; } // Adjacent modules in column having same color, and finder-like patterns for (int x = 0; x < size; x++) { - std::deque runHistory(7, 0); bool runColor = false; int runY = 0; + std::array runHistory = {}; + int padRun = size; // Add white border to initial run for (int y = 0; y < size; y++) { if (module(x, y) == runColor) { runY++; @@ -464,18 +463,15 @@ long QrCode::getPenaltyScore() const { else if (runY > 5) result++; } else { - addRunToHistory(runY, runHistory); - if (!runColor && hasFinderLikePattern(runHistory)) - result += PENALTY_N3; + finderPenaltyAddHistory(runY + padRun, runHistory); + padRun = 0; + if (!runColor) + result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; runColor = module(x, y); runY = 1; } } - addRunToHistory(runY, runHistory); - if (runColor) - addRunToHistory(0, runHistory); // Dummy run of white - if (hasFinderLikePattern(runHistory)) - result += PENALTY_N3; + result += finderPenaltyTerminateAndCount(runColor, runY + padRun, runHistory) * PENALTY_N3; } // 2*2 blocks of modules having same color @@ -542,16 +538,30 @@ int QrCode::getNumDataCodewords(int ver, Ecc ecl) { } -void QrCode::addRunToHistory(int run, std::deque &history) { - history.pop_back(); - history.push_front(run); +int QrCode::finderPenaltyCountPatterns(const std::array &runHistory) const { + int n = runHistory.at(1); + if (n > size * 3) + throw std::logic_error("Assertion error"); + bool core = n > 0 && runHistory.at(2) == n && runHistory.at(3) == n * 3 && runHistory.at(4) == n && runHistory.at(5) == n; + return (core && runHistory.at(0) >= n * 4 && runHistory.at(6) >= n ? 1 : 0) + + (core && runHistory.at(6) >= n * 4 && runHistory.at(0) >= n ? 1 : 0); } -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; +int QrCode::finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array &runHistory) const { + if (currentRunColor) { // Terminate black run + finderPenaltyAddHistory(currentRunLength, runHistory); + currentRunLength = 0; + } + currentRunLength += size; // Add white border to final run + finderPenaltyAddHistory(currentRunLength, runHistory); + return finderPenaltyCountPatterns(runHistory); +} + + +void QrCode::finderPenaltyAddHistory(int currentRunLength, std::array &runHistory) { + std::copy_backward(runHistory.cbegin(), runHistory.cend() - 1, runHistory.end()); + runHistory.at(0) = currentRunLength; } diff --git a/cpp/QrCode.hpp b/cpp/QrCode.hpp index d496e2a..6196355 100644 --- a/cpp/QrCode.hpp +++ b/cpp/QrCode.hpp @@ -23,8 +23,8 @@ #pragma once +#include #include -#include #include #include #include @@ -274,15 +274,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); + // Can only be called immediately after a white run is added, and + // returns either 0, 1, or 2. A helper function for getPenaltyScore(). + private: int finderPenaltyCountPatterns(const std::array &runHistory) const; - // 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); + // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). + private: int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array &runHistory) const; + + + // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). + private: static void finderPenaltyAddHistory(int currentRunLength, std::array &runHistory); // Returns true iff the i'th bit of x is set to 1. diff --git a/javascript/qrcodegen.js b/javascript/qrcodegen.js index cf77be8..40aff87 100644 --- a/javascript/qrcodegen.js +++ b/javascript/qrcodegen.js @@ -429,9 +429,10 @@ var qrcodegen = new function() { // 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 runColor = false; var runX = 0; + var runHistory = [0,0,0,0,0,0,0]; + var padRun = size; for (var x = 0; x < size; x++) { if (modules[y][x] == runColor) { runX++; @@ -440,24 +441,22 @@ var qrcodegen = new function() { else if (runX > 5) result++; } else { - QrCode.addRunToHistory(runX, runHistory); - if (!runColor && QrCode.hasFinderLikePattern(runHistory)) - result += QrCode.PENALTY_N3; + QrCode.finderPenaltyAddHistory(runX + padRun, runHistory); + padRun = 0; + if (!runColor) + result += finderPenaltyCountPatterns(runHistory) * QrCode.PENALTY_N3; runColor = modules[y][x]; runX = 1; } } - QrCode.addRunToHistory(runX, runHistory); - if (runColor) - QrCode.addRunToHistory(0, runHistory); // Dummy run of white - if (QrCode.hasFinderLikePattern(runHistory)) - result += QrCode.PENALTY_N3; + result += finderPenaltyTerminateAndCount(runColor, runX + padRun, runHistory) * QrCode.PENALTY_N3; } // 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 runColor = false; var runY = 0; + var runHistory = [0,0,0,0,0,0,0]; + var padRun = size; for (var y = 0; y < size; y++) { if (modules[y][x] == runColor) { runY++; @@ -466,18 +465,15 @@ var qrcodegen = new function() { else if (runY > 5) result++; } else { - QrCode.addRunToHistory(runY, runHistory); - if (!runColor && QrCode.hasFinderLikePattern(runHistory)) - result += QrCode.PENALTY_N3; + QrCode.finderPenaltyAddHistory(runY + padRun, runHistory); + padRun = 0; + if (!runColor) + result += finderPenaltyCountPatterns(runHistory) * QrCode.PENALTY_N3; runColor = modules[y][x]; runY = 1; } } - QrCode.addRunToHistory(runY, runHistory); - if (runColor) - QrCode.addRunToHistory(0, runHistory); // Dummy run of white - if (QrCode.hasFinderLikePattern(runHistory)) - result += QrCode.PENALTY_N3; + result += finderPenaltyTerminateAndCount(runColor, runY + padRun, runHistory) * QrCode.PENALTY_N3; } // 2*2 blocks of modules having same color @@ -525,6 +521,30 @@ var qrcodegen = new function() { } + // Can only be called immediately after a white run is added, and + // returns either 0, 1, or 2. A helper function for getPenaltyScore(). + function finderPenaltyCountPatterns(runHistory) { + var n = runHistory[1]; + if (n > size * 3) + throw "Assertion error"; + var core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n; + return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) + + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0); + } + + + // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). + function finderPenaltyTerminateAndCount(currentRunColor, currentRunLength, runHistory) { + if (currentRunColor) { // Terminate black run + QrCode.finderPenaltyAddHistory(currentRunLength, runHistory); + currentRunLength = 0; + } + currentRunLength += size; // Add white border to final run + QrCode.finderPenaltyAddHistory(currentRunLength, runHistory); + return finderPenaltyCountPatterns(runHistory); + } + + // Returns true iff the i'th bit of x is set to 1. function getBit(x, i) { return ((x >>> i) & 1) != 0; @@ -667,11 +687,10 @@ 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); + // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). + QrCode.finderPenaltyAddHistory = function(currentRunLength, runHistory) { + runHistory.pop(); + runHistory.unshift(currentRunLength); }; diff --git a/python/qrcodegen.py b/python/qrcodegen.py index 40487da..3dd8eca 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -464,9 +464,10 @@ class QrCode(object): # Adjacent modules in row having same color, and finder-like patterns for y in range(size): - runhistory = collections.deque([0] * 7, 7) runcolor = False runx = 0 + runhistory = collections.deque([0] * 7, 7) + padrun = size # Add white border to initial run for x in range(size): if modules[y][x] == runcolor: runx += 1 @@ -475,21 +476,19 @@ class QrCode(object): elif runx > 5: result += 1 else: - runhistory.appendleft(runx) - if not runcolor and QrCode.has_finder_like_pattern(runhistory): - result += QrCode._PENALTY_N3 + runhistory.appendleft(runx + padrun) + padrun = 0 + if not runcolor: + result += self._finder_penalty_count_patterns(runhistory) * QrCode._PENALTY_N3 runcolor = modules[y][x] runx = 1 - runhistory.appendleft(runx) - if runcolor: - runhistory.appendleft(0) # Dummy run of white - if QrCode.has_finder_like_pattern(runhistory): - result += QrCode._PENALTY_N3 + result += self._finder_penalty_terminate_and_count(runcolor, runx + padrun, runhistory) * 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) runcolor = False runy = 0 + runhistory = collections.deque([0] * 7, 7) + padrun = size # Add white border to initial run for y in range(size): if modules[y][x] == runcolor: runy += 1 @@ -498,16 +497,13 @@ class QrCode(object): elif runy > 5: result += 1 else: - runhistory.appendleft(runy) - if not runcolor and QrCode.has_finder_like_pattern(runhistory): - result += QrCode._PENALTY_N3 + runhistory.appendleft(runy + padrun) + padrun = 0 + if not runcolor: + result += self._finder_penalty_count_patterns(runhistory) * QrCode._PENALTY_N3 runcolor = modules[y][x] runy = 1 - runhistory.appendleft(runy) - if runcolor: - runhistory.appendleft(0) # Dummy run of white - if QrCode.has_finder_like_pattern(runhistory): - result += QrCode._PENALTY_N3 + result += self._finder_penalty_terminate_and_count(runcolor, runy + padrun, runhistory) * QrCode._PENALTY_N3 # 2*2 blocks of modules having same color for y in range(size - 1): @@ -567,11 +563,24 @@ class QrCode(object): * QrCode._NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver] - @staticmethod - def has_finder_like_pattern(runhistory): + # Can only be called immediately after a white run is added, and + # returns either 0, 1, or 2. A helper function for _get_penalty_score(). + def _finder_penalty_count_patterns(self, 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 + assert n <= self._size * 3 + core = n > 0 and (runhistory[2] == runhistory[4] == runhistory[5] == n) and runhistory[3] == n * 3 + return (1 if (core and runhistory[0] >= n * 4 and runhistory[6] >= n) else 0) \ + + (1 if (core and runhistory[6] >= n * 4 and runhistory[0] >= n) else 0) + + + # Must be called at the end of a line (row or column) of modules. A helper function for _get_penalty_score(). + def _finder_penalty_terminate_and_count(self, currentruncolor, currentrunlength, runhistory): + if currentruncolor: # Terminate black run + runhistory.appendleft(currentrunlength) + currentrunlength = 0 + currentrunlength += self._size # Add white border to final run + runhistory.appendleft(currentrunlength) + return self._finder_penalty_count_patterns(runhistory) # ---- Constants and tables ---- diff --git a/rust/src/lib.rs b/rust/src/lib.rs index d337040..28b5514 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -642,9 +642,10 @@ impl QrCode { // Adjacent modules in row having same color, and finder-like patterns for y in 0 .. size { - let mut runhistory = RunHistory::new(); let mut runcolor = false; let mut runx: i32 = 0; + let mut runhistory = [0i32; 7]; + let mut padrun = size; // Add white border to initial run for x in 0 .. size { if self.module(x, y) == runcolor { runx += 1; @@ -654,27 +655,23 @@ impl QrCode { result += 1; } } else { - runhistory.add_run(runx); - if !runcolor && runhistory.has_finder_like_pattern() { - result += PENALTY_N3; + QrCode::finder_penalty_add_history(runx + padrun, &mut runhistory); + padrun = 0; + if !runcolor { + result += self.finder_penalty_count_patterns(&runhistory) * PENALTY_N3; } runcolor = self.module(x, y); runx = 1; } } - runhistory.add_run(runx); - if runcolor { - runhistory.add_run(0); // Dummy run of white - } - if runhistory.has_finder_like_pattern() { - result += PENALTY_N3; - } + result += self.finder_penalty_terminate_and_count(runcolor, runx + padrun, &mut runhistory) * PENALTY_N3; } // Adjacent modules in column having same color, and finder-like patterns for x in 0 .. size { - let mut runhistory = RunHistory::new(); let mut runcolor = false; let mut runy: i32 = 0; + let mut runhistory = [0i32; 7]; + let mut padrun = size; // Add white border to initial run for y in 0 .. size { if self.module(x, y) == runcolor { runy += 1; @@ -684,21 +681,16 @@ impl QrCode { result += 1; } } else { - runhistory.add_run(runy); - if !runcolor && runhistory.has_finder_like_pattern() { - result += PENALTY_N3; + QrCode::finder_penalty_add_history(runy + padrun, &mut runhistory); + padrun = 0; + if !runcolor { + result += self.finder_penalty_count_patterns(&runhistory) * PENALTY_N3; } runcolor = self.module(x, y); runy = 1; } } - runhistory.add_run(runy); - if runcolor { - runhistory.add_run(0); // Dummy run of white - } - if runhistory.has_finder_like_pattern() { - result += PENALTY_N3; - } + result += self.finder_penalty_terminate_and_count(runcolor, runy + padrun, &mut runhistory) * PENALTY_N3; } // 2*2 blocks of modules having same color @@ -780,6 +772,38 @@ impl QrCode { table[ecl.ordinal()][ver.value() as usize] as usize } + + // Can only be called immediately after a white run is added, and + // returns either 0, 1, or 2. A helper function for get_penalty_score(). + fn finder_penalty_count_patterns(&self, runhistory: &[i32;7]) -> i32 { + let n = runhistory[1]; + assert!(n <= self.size * 3); + let core = n > 0 && runhistory[2] == n && runhistory[3] == n * 3 && runhistory[4] == n && runhistory[5] == n; + return if core && runhistory[0] >= n * 4 && runhistory[6] >= n { 1 } else { 0 } + + if core && runhistory[6] >= n * 4 && runhistory[0] >= n { 1 } else { 0 }; + } + + + // Must be called at the end of a line (row or column) of modules. A helper function for get_penalty_score(). + fn finder_penalty_terminate_and_count(&self, currentruncolor: bool, mut currentrunlength: i32, runhistory: &mut [i32;7]) -> i32 { + if currentruncolor { // Terminate black run + QrCode::finder_penalty_add_history(currentrunlength, runhistory); + currentrunlength = 0; + } + currentrunlength += self.size; // Add white border to final run + QrCode::finder_penalty_add_history(currentrunlength, runhistory); + self.finder_penalty_count_patterns(runhistory) + } + + + // Pushes the given value to the front and drops the last value. A helper function for get_penalty_score(). + fn finder_penalty_add_history(currentrunlength: i32, runhistory: &mut [i32;7]) { + for i in (0 .. runhistory.len()-1).rev() { + runhistory[i + 1] = runhistory[i]; + } + runhistory[0] = currentrunlength; + } + } @@ -937,41 +961,6 @@ 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 7366757..3a3b03a 100644 --- a/typescript/qrcodegen.ts +++ b/typescript/qrcodegen.ts @@ -512,9 +512,10 @@ namespace qrcodegen { // 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 runColor = false; let runX = 0; + let runHistory = [0,0,0,0,0,0,0]; + let padRun = this.size; for (let x = 0; x < this.size; x++) { if (this.modules[y][x] == runColor) { runX++; @@ -523,24 +524,22 @@ namespace qrcodegen { else if (runX > 5) result++; } else { - QrCode.addRunToHistory(runX, runHistory); - if (!runColor && QrCode.hasFinderLikePattern(runHistory)) - result += QrCode.PENALTY_N3; + QrCode.finderPenaltyAddHistory(runX + padRun, runHistory); + padRun = 0; + if (!runColor) + result += this.finderPenaltyCountPatterns(runHistory) * QrCode.PENALTY_N3; runColor = this.modules[y][x]; runX = 1; } } - QrCode.addRunToHistory(runX, runHistory); - if (runColor) - QrCode.addRunToHistory(0, runHistory); // Dummy run of white - if (QrCode.hasFinderLikePattern(runHistory)) - result += QrCode.PENALTY_N3; + result += this.finderPenaltyTerminateAndCount(runColor, runX + padRun, runHistory) * QrCode.PENALTY_N3; } // 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 runColor = false; let runY = 0; + let runHistory = [0,0,0,0,0,0,0]; + let padRun = this.size; for (let y = 0; y < this.size; y++) { if (this.modules[y][x] == runColor) { runY++; @@ -549,18 +548,15 @@ namespace qrcodegen { else if (runY > 5) result++; } else { - QrCode.addRunToHistory(runY, runHistory); - if (!runColor && QrCode.hasFinderLikePattern(runHistory)) - result += QrCode.PENALTY_N3; + QrCode.finderPenaltyAddHistory(runY + padRun, runHistory); + padRun = 0; + if (!runColor) + result += this.finderPenaltyCountPatterns(runHistory) * QrCode.PENALTY_N3; runColor = this.modules[y][x]; runY = 1; } } - QrCode.addRunToHistory(runY, runHistory); - if (runColor) - QrCode.addRunToHistory(0, runHistory); // Dummy run of white - if (QrCode.hasFinderLikePattern(runHistory)) - result += QrCode.PENALTY_N3; + result += this.finderPenaltyTerminateAndCount(runColor, runY + padRun, runHistory) * QrCode.PENALTY_N3; } // 2*2 blocks of modules having same color @@ -637,21 +633,34 @@ 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); + // Can only be called immediately after a white run is added, and + // returns either 0, 1, or 2. A helper function for getPenaltyScore(). + private finderPenaltyCountPatterns(runHistory: Array): int { + const n: int = runHistory[1]; + if (n > this.size * 3) + throw "Assertion error"; + const core: boolean = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n; + return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) + + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0); } - // 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; + // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). + private finderPenaltyTerminateAndCount(currentRunColor: boolean, currentRunLength: int, runHistory: Array): int { + if (currentRunColor) { // Terminate black run + QrCode.finderPenaltyAddHistory(currentRunLength, runHistory); + currentRunLength = 0; + } + currentRunLength += this.size; // Add white border to final run + QrCode.finderPenaltyAddHistory(currentRunLength, runHistory); + return this.finderPenaltyCountPatterns(runHistory); + } + + + // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). + private static finderPenaltyAddHistory(currentRunLength: int, runHistory: Array): void { + runHistory.pop(); + runHistory.unshift(currentRunLength); }