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}.

pull/62/head
Project Nayuki 5 years ago
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…
Cancel
Save