mirror of
https://github.com/status-im/QR-Code-generator.git
synced 2025-02-23 18:08:20 +00:00
Inlined the Java-version finder-like pattern detector into the penalty score calculation logic in a non-trivial way, keeping behavior identical but reducing {declarations, computations, comments, explanations}.
This commit is contained in:
parent
1ca214499b
commit
6794ebefa7
@ -596,31 +596,36 @@ public final class QrCode {
|
||||
int result = 0;
|
||||
|
||||
// Adjacent modules in row having same color, and finder-like patterns
|
||||
FinderPatternDetector det = new FinderPatternDetector();
|
||||
int[] runHistory = new int[7];
|
||||
for (int y = 0; y < size; y++) {
|
||||
boolean runClor = false;
|
||||
boolean runColor = false;
|
||||
int runX = 0;
|
||||
det.reset();
|
||||
Arrays.fill(runHistory, 0);
|
||||
int padRun = size; // Add white border to initial run
|
||||
for (int x = 0; x < size; x++) {
|
||||
if (modules[y][x] == runClor) {
|
||||
if (modules[y][x] == runColor) {
|
||||
runX++;
|
||||
if (runX == 5)
|
||||
result += PENALTY_N1;
|
||||
else if (runX > 5)
|
||||
result++;
|
||||
} else {
|
||||
runClor = modules[y][x];
|
||||
finderPenaltyAddHistory(runX + padRun, runHistory);
|
||||
padRun = 0;
|
||||
if (!runColor)
|
||||
result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3;
|
||||
runColor = modules[y][x];
|
||||
runX = 1;
|
||||
}
|
||||
result += det.addModuleAndMatch(runClor) * PENALTY_N3;
|
||||
}
|
||||
result += det.terminateAndMatch() * 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++) {
|
||||
det.reset();
|
||||
boolean runColor = false;
|
||||
int runY = 0;
|
||||
Arrays.fill(runHistory, 0);
|
||||
int padRun = size; // Add white border to initial run
|
||||
for (int y = 0; y < size; y++) {
|
||||
if (modules[y][x] == runColor) {
|
||||
runY++;
|
||||
@ -629,12 +634,15 @@ public final class QrCode {
|
||||
else if (runY > 5)
|
||||
result++;
|
||||
} else {
|
||||
finderPenaltyAddHistory(runY + padRun, runHistory);
|
||||
padRun = 0;
|
||||
if (!runColor)
|
||||
result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3;
|
||||
runColor = modules[y][x];
|
||||
runY = 1;
|
||||
}
|
||||
result += det.addModuleAndMatch(runColor) * PENALTY_N3;
|
||||
}
|
||||
result += det.terminateAndMatch() * PENALTY_N3;
|
||||
result += finderPenaltyTerminateAndCount(runColor, runY + padRun, runHistory) * PENALTY_N3;
|
||||
}
|
||||
|
||||
// 2*2 blocks of modules having same color
|
||||
@ -724,6 +732,36 @@ public final class QrCode {
|
||||
}
|
||||
|
||||
|
||||
// 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(int[] runHistory) {
|
||||
int n = runHistory[1];
|
||||
assert n <= size * 3;
|
||||
boolean 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().
|
||||
private int finderPenaltyTerminateAndCount(boolean currentRunColor, int currentRunLength, int[] runHistory) {
|
||||
if (currentRunColor) { // Terminate black run
|
||||
finderPenaltyAddHistory(currentRunLength, runHistory);
|
||||
currentRunLength = 0;
|
||||
}
|
||||
currentRunLength += size; // Add white border to final run
|
||||
finderPenaltyAddHistory(currentRunLength, runHistory);
|
||||
return finderPenaltyCountPatterns(runHistory);
|
||||
}
|
||||
|
||||
|
||||
// Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore().
|
||||
private static void finderPenaltyAddHistory(int currentRunLength, int[] runHistory) {
|
||||
System.arraycopy(runHistory, 0, runHistory, 1, runHistory.length - 1);
|
||||
runHistory[0] = currentRunLength;
|
||||
}
|
||||
|
||||
|
||||
// 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;
|
||||
@ -882,118 +920,4 @@ public final class QrCode {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Private helper class ----*/
|
||||
|
||||
/**
|
||||
* Detects finder-like patterns in a line of modules, for the purpose of penalty score calculation.
|
||||
* A finder-like pattern has alternating black and white modules with run length ratios of 1:1:3:1:1,
|
||||
* such that the center run is black and this pattern is surrounded by at least a ratio
|
||||
* of 4:1 white modules on one side and at least 1:1 white modules on the other side.
|
||||
* The finite line of modules is conceptually padded with an infinite number of white modules on both sides.
|
||||
*
|
||||
* Here are graphic examples of the designed behavior, where '[' means start of line,
|
||||
* ']' means end of line, '.' means white module, and '#' means black module:
|
||||
* - [....#.###.#....] Two matches
|
||||
* - [#.###.#] Two matches, because of infinite white border
|
||||
* - [##..######..##] Two matches, with a scale of 2
|
||||
* - [#.###.#.#] One match, using the infinite white left border
|
||||
* - [#.#.###.#.#] Zero matches, due to insufficient white modules surrounding the 1:1:3:1:1 pattern
|
||||
* - [#.###.##] Zero matches, because the rightmost black bar is too long
|
||||
* - [#.###.#.###.#] Two matches, with the matches overlapping and sharing modules
|
||||
*/
|
||||
private static final class FinderPatternDetector {
|
||||
|
||||
/*-- Fields --*/
|
||||
|
||||
// Mutable running state
|
||||
private boolean currentRunColor; // false = white, true = black
|
||||
private int currentRunLength; // In modules, always positive
|
||||
// runHistory[0] = length of most recently ended run,
|
||||
// runHistory[1] = length of next older run of opposite color, etc.
|
||||
// This array begins as all zeros. Zero is not a valid run length.
|
||||
private int[] runHistory = new int[7];
|
||||
|
||||
|
||||
/*-- Methods --*/
|
||||
|
||||
/**
|
||||
* Re-initializes this detector to the start of a row or column.
|
||||
* This allows reuse of this object and its array, reducing memory allocation.
|
||||
*/
|
||||
public void reset() {
|
||||
currentRunColor = false;
|
||||
currentRunLength = QR_CODE_SIZE_MAX; // Add white border to initial run
|
||||
Arrays.fill(runHistory, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tells this detector that the next module has the specified color, and returns
|
||||
* the number of finder-like patterns detected due to processing the current module.
|
||||
* The result is usually 0, but can be 1 or 2 only when transitioning from
|
||||
* white to black (i.e. {@code currentRunColor == false && color == true}).
|
||||
* @param color the color of the next module, where {@code true} denotes black and {@code false} is white
|
||||
* @return either 0, 1, or 2
|
||||
*/
|
||||
public int addModuleAndMatch(boolean color) {
|
||||
if (color == currentRunColor)
|
||||
currentRunLength++;
|
||||
else {
|
||||
addToHistory(currentRunLength);
|
||||
currentRunColor = color;
|
||||
currentRunLength = 1;
|
||||
if (color) // Transitioning from white to black
|
||||
return countCurrentMatches();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tells this detector that the line of modules has ended, and
|
||||
* returns the number of finder-like patterns detected at the end.
|
||||
* After this, {@link #reset()} must be called before any other methods.
|
||||
* @return either 0, 1, or 2
|
||||
*/
|
||||
public int terminateAndMatch() {
|
||||
if (currentRunColor) { // Terminate black run
|
||||
addToHistory(currentRunLength);
|
||||
currentRunLength = 0;
|
||||
}
|
||||
currentRunLength += QR_CODE_SIZE_MAX; // Add white border to final run
|
||||
addToHistory(currentRunLength);
|
||||
return countCurrentMatches();
|
||||
}
|
||||
|
||||
|
||||
// Shifts the array back and puts the given value at the front.
|
||||
private void addToHistory(int run) {
|
||||
System.arraycopy(runHistory, 0, runHistory, 1, runHistory.length - 1);
|
||||
runHistory[0] = run;
|
||||
}
|
||||
|
||||
|
||||
// Can only be called immediately after a white run is added.
|
||||
private int countCurrentMatches() {
|
||||
int n = runHistory[1];
|
||||
assert n <= QR_CODE_SIZE_MAX * 3;
|
||||
boolean core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n;
|
||||
if (core) {
|
||||
return (runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0)
|
||||
+ (runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0);
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*-- Constant --*/
|
||||
|
||||
// This amount of padding is enough to guarantee at least 4 scaled
|
||||
// white modules at any pattern scale that fits inside any QR Code.
|
||||
private static final int QR_CODE_SIZE_MAX = QrCode.MAX_VERSION * 4 + 17;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user