-
Notifications
You must be signed in to change notification settings - Fork 88
Description
I've found a bug in your mask penalty algorithm. When comparing the results with Nayuki’s original Python library, I noticed that the same QR code configuration (version, error correction level, data, encoding mode, etc.) does not yield the same penalty score. This discrepancy became evident while comparing two parallel projects, where in some cases a different automatic mask was selected.
The detection of 2x2 blocks, black/white balance, and consecutive color runs are all correctly implemented. However, the issue lies in the detection of finder-like patterns. Specifically, your algorithm is missing a condition to prepend a white run at the beginning of each row. While you correctly add a white run at the end of each row, the absence of one at the start prevents the algorithm from detecting patterns that begin on the left edge of the QR code. This consistently results in a minimum error of 6 missed patterns (the three finder patterns on the left side) × 40 penalty points.
This scoring difference becomes especially significant in low-version QR codes, where the 240-point discrepancy (6 × 40) can drastically alter the behavior of the automatic mask selection.
Example:
Penalty for mask "0" with the following configuration:
string num = "";
string alpha = "";
var numericSegment = QrSegment.MakeNumeric(num);
var alphanumericSegment = QrSegment.MakeAlphanumeric(alpha);
var qr = QrCode.EncodeSegments(
new List<QrSegment> { numericSegment, alphanumericSegment },
QrCode.Ecc.Medium,
minVersion: 1,
maxVersion: 1,
mask: -1,
boostEcl: false
);
Expected score: 994
Actual score (with the bug): 474
Explanation:
Each row’s run history starts with 0, resulting in sequences like [0,1,1,3,1,1,...] instead of the correct [21,1,1,3,1,1,...]. This prevents proper detection of finder-like patterns at the beginning of rows.
Proposed Fix:
I added the following logic to correct the issue. While this works, I leave it to your discretion to implement a more efficient or idiomatic solution.
// Adds the given value to the run history
internal void AddHistory(int run)
{
if (run == 0) {
_runHistory[_length] = (short)_size; // Add a light border to the initial run
} else {
_runHistory[_length] = (short)run;
}
_length++;
}
Your actual code is:
// Adds the given value to the run history
internal void AddHistory(int run)
{
_runHistory[_length] = (short)run;
_length++;
}