Commit 029e171b authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

qr: add support for selecting the mask.

The QR standard says that the mask should be auto-selected, not fixed to
three, and specifies a fitness function for choosing the best mask for
each QR code.

This change adds support for that and lets the caller specify a mask if
it really wants … say if it wanted to cycle through a series of QR codes
for the same data with a dancing dinosaur on top.

Change-Id: Ief4dedce236aa2ae13cc94b2248a17a0488f88ec
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2365222
Commit-Queue: Adam Langley <agl@chromium.org>
Auto-Submit: Adam Langley <agl@chromium.org>
Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#800326}
parent 3a1b73b8
...@@ -232,6 +232,47 @@ const QRVersionInfo* GetVersionForDataSize(size_t num_data_bytes) { ...@@ -232,6 +232,47 @@ const QRVersionInfo* GetVersionForDataSize(size_t num_data_bytes) {
return nullptr; return nullptr;
} }
// kMaxMask is the maximum masking function number. See table 10.
constexpr uint8_t kMaxMask = 7;
// The following functions implement the masks specified in table 10.
uint8_t MaskFunction0(int x, int y) {
return (x + y) % 2 == 0;
}
uint8_t MaskFunction1(int x, int y) {
return y % 2 == 0;
}
uint8_t MaskFunction2(int x, int y) {
return x % 3 == 0;
}
uint8_t MaskFunction3(int x, int y) {
return (x + y) % 3 == 0;
}
uint8_t MaskFunction4(int x, int y) {
return ((y / 2) + (x / 3)) % 2 == 0;
}
uint8_t MaskFunction5(int x, int y) {
return ((x * y) % 2) + ((x * y) % 3) == 0;
}
uint8_t MaskFunction6(int x, int y) {
return (((x * y) % 2) + ((x * y) % 3)) % 2 == 0;
}
uint8_t MaskFunction7(int x, int y) {
return (((x + y) % 2) + ((x * y) % 3)) % 2 == 0;
}
static uint8_t (*const kMaskFunctions[kMaxMask + 1])(int x, int y) = {
MaskFunction0, MaskFunction1, MaskFunction2, MaskFunction3,
MaskFunction4, MaskFunction5, MaskFunction6, MaskFunction7,
};
// kFormatInformation is taken from table C.1 on page 80 and specifies the
// format value for each masking function, assuming ECC level 'M'.
static const uint16_t kFormatInformation[kMaxMask + 1] = {
0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0,
};
} // namespace } // namespace
QRCodeGenerator::QRCodeGenerator() = default; QRCodeGenerator::QRCodeGenerator() = default;
...@@ -244,7 +285,10 @@ QRCodeGenerator::GeneratedCode::GeneratedCode( ...@@ -244,7 +285,10 @@ QRCodeGenerator::GeneratedCode::GeneratedCode(
QRCodeGenerator::GeneratedCode::~GeneratedCode() = default; QRCodeGenerator::GeneratedCode::~GeneratedCode() = default;
base::Optional<QRCodeGenerator::GeneratedCode> QRCodeGenerator::Generate( base::Optional<QRCodeGenerator::GeneratedCode> QRCodeGenerator::Generate(
base::span<const uint8_t> in) { base::span<const uint8_t> in,
base::Optional<uint8_t> mask) {
CHECK(!mask || *mask <= kMaxMask);
// We're currently using a minimal set of versions to shrink test surface. // We're currently using a minimal set of versions to shrink test surface.
// When expanding, take care to validate across different platforms and // When expanding, take care to validate across different platforms and
// a selection of QR Scanner apps. // a selection of QR Scanner apps.
...@@ -287,16 +331,6 @@ base::Optional<QRCodeGenerator::GeneratedCode> QRCodeGenerator::Generate( ...@@ -287,16 +331,6 @@ base::Optional<QRCodeGenerator::GeneratedCode> QRCodeGenerator::Generate(
} }
} }
// kFormatInformation is the encoded formatting word for the QR code that
// this code generates. See tables 10 and 12.
// 00 011
// --|---
// error correction M | Mask pattern 3
//
// It's translated into the following, 15-bit value using the table on page
// 80.
constexpr uint16_t kFormatInformation = 0x5b4b;
PutFormatBits(kFormatInformation);
if (version_info_->encoded_version != 0) { if (version_info_->encoded_version != 0) {
PutVersionBlocks(version_info_->encoded_version); PutVersionBlocks(version_info_->encoded_version);
} }
...@@ -408,12 +442,36 @@ base::Optional<QRCodeGenerator::GeneratedCode> QRCodeGenerator::Generate( ...@@ -408,12 +442,36 @@ base::Optional<QRCodeGenerator::GeneratedCode> QRCodeGenerator::Generate(
} }
} }
// The mask pattern is fixed for this implementation. A full implementation uint8_t best_mask = mask.value_or(0);
// would generate QR codes with every mask pattern and evaluate a quality base::Optional<unsigned> lowest_penalty;
// score, ultimately picking the optimal pattern. Here it's assumed that a
// different QR code will soon be generated so any random issues will be // If |mask| was not specified, then evaluate each masking function to find
// transient. // the one with the lowest penalty score.
PutBits(interleaved_data, sizeof(interleaved_data), MaskFunction3); for (uint8_t mask_num = 0; !mask && mask_num <= kMaxMask; mask_num++) {
// kFormatInformation is the encoded formatting word for the QR code that
// this code generates. See tables 10 and 12. For example:
// 00 011
// --|---
// error correction M | Mask pattern 3
//
// It's translated into a 15-bit value using the table on page 80, which is
// stored in |kFormatInformation|.
PutFormatBits(kFormatInformation[mask_num]);
PutBits(interleaved_data, sizeof(interleaved_data),
kMaskFunctions[mask_num]);
const unsigned penalty = CountPenaltyPoints();
if (!lowest_penalty || *lowest_penalty > penalty) {
lowest_penalty = penalty;
best_mask = mask_num;
}
}
// Repaint with the best mask function.
PutFormatBits(kFormatInformation[best_mask]);
PutBits(interleaved_data, sizeof(interleaved_data),
kMaskFunctions[best_mask]);
GeneratedCode code; GeneratedCode code;
code.data = base::span<uint8_t>(d_.get(), version_info_->total_size()); code.data = base::span<uint8_t>(d_.get(), version_info_->total_size());
...@@ -421,12 +479,6 @@ base::Optional<QRCodeGenerator::GeneratedCode> QRCodeGenerator::Generate( ...@@ -421,12 +479,6 @@ base::Optional<QRCodeGenerator::GeneratedCode> QRCodeGenerator::Generate(
return code; return code;
} }
// MaskFunction3 implements one of the data-masking functions. See figure 21.
// static
uint8_t QRCodeGenerator::MaskFunction3(int x, int y) {
return (x + y) % 3 == 0;
}
// PutFinder paints a finder symbol at the given coordinates. // PutFinder paints a finder symbol at the given coordinates.
void QRCodeGenerator::PutFinder(int x, int y) { void QRCodeGenerator::PutFinder(int x, int y) {
DCHECK_GE(x, 3); DCHECK_GE(x, 3);
...@@ -578,12 +630,12 @@ void QRCodeGenerator::PutBits(const uint8_t* data, ...@@ -578,12 +630,12 @@ void QRCodeGenerator::PutBits(const uint8_t* data,
uint8_t& right = at(x, y); uint8_t& right = at(x, y);
// Test the current value in the QR code to avoid painting over any // Test the current value in the QR code to avoid painting over any
// existing structural elements. // existing structural elements.
if (right == 0) { if ((right & 2) == 0) {
right = stream.Next() ^ mask_func(x, y); right = stream.Next() ^ mask_func(x, y);
} }
uint8_t& left = at(x - 1, y); uint8_t& left = at(x - 1, y);
if (left == 0) { if ((left & 2) == 0) {
left = stream.Next() ^ mask_func(x - 1, y); left = stream.Next() ^ mask_func(x - 1, y);
} }
...@@ -768,3 +820,128 @@ void QRCodeGenerator::AddErrorCorrection(uint8_t out[], ...@@ -768,3 +820,128 @@ void QRCodeGenerator::AddErrorCorrection(uint8_t out[],
out[block_data_bytes + i] = remainder[block_ec_bytes - 1 - i]; out[block_data_bytes + i] = remainder[block_ec_bytes - 1 - i];
} }
} }
unsigned QRCodeGenerator::CountPenaltyPoints() const {
const int size = version_info_->size;
unsigned penalty = 0;
// The spec penalises the pattern X.XXX.X with four unpainted tiles to
// the left or right. These are "finder-like" patterns. To catch them, a
// sliding window of 11 tiles is used.
static const unsigned k11Bits = 0x7ff;
static const unsigned kFinderLeft = 0b00001011101;
static const unsigned kFinderRight = 0b10111010000;
// Count:
// * Horizontal runs of the same color, at least five tiles in a row.
// * The number of horizontal finder-like patterns.
// * Total number of painted tiles, which is used later.
unsigned current_run_length;
int current_color;
unsigned total_painted_tiles = 0;
unsigned window = 0;
const uint8_t* d = d_.get();
for (int y = 0; y < size; y++) {
current_color = (*d++) & 1;
current_run_length = 0;
window = current_color;
total_painted_tiles += current_color;
for (int x = 1; x < size; x++) {
const int color = (*d++) & 1;
window = k11Bits & ((window << 1) | color);
if (window == kFinderLeft || window == kFinderRight) {
penalty += 40;
}
total_painted_tiles += color;
if (color == current_color) {
current_run_length++;
continue;
}
if (current_run_length >= 5) {
penalty += current_run_length - 2;
}
current_run_length = 0;
current_color = color;
}
if (current_run_length >= 5) {
penalty += current_run_length - 2;
}
window = k11Bits & (window << 4);
if (window == kFinderRight) {
penalty += 40;
}
}
DCHECK_EQ(d, d_.get() + size * size);
// Count:
// * Vertical runs of the same color, at least five tiles in a row.
// * The number of vertical finder-like patterns.
for (int x = 0; x < size; x++) {
d = &d_[x];
current_run_length = 0;
current_color = (*d) & 1;
d += size;
window = current_color;
for (int y = 1; y < size; y++, d += size) {
const int color = (*d) & 1;
window = k11Bits & ((window << 1) | color);
if (window == kFinderLeft || window == kFinderRight) {
penalty += 40;
}
if (color == current_color) {
current_run_length++;
continue;
}
if (current_run_length >= 5) {
penalty += current_run_length - 2;
}
current_run_length = 0;
current_color = color;
}
if (current_run_length >= 5) {
penalty += current_run_length - 2;
}
window = k11Bits & (window << 4);
if (window == kFinderRight) {
penalty += 40;
}
}
DCHECK_EQ(d, d_.get() + size * size + size - 1);
// Count 2x2 blocks of the same color.
d = d_.get();
for (int y = 0; y < size - 1; y++) {
for (int x = 0; x < size - 1; x++) {
const int color = (*d++) & 1;
if ((d[1] & 1) == color && (d[size] & 1) == color &&
(d[size + 1] & 1) == color) {
penalty += 3;
}
}
}
// Each deviation of 5% away from 50%-painted costs five points.
DCHECK_LE(total_painted_tiles, static_cast<unsigned>(size) * size);
double painted_fraction = static_cast<double>(total_painted_tiles) /
(static_cast<double>(size) * size);
if (painted_fraction < 0.5) {
painted_fraction = 1.0 - painted_fraction;
}
const double deviation = (painted_fraction - 0.5) / 0.05;
penalty += 5 * static_cast<unsigned>(floor(deviation));
return penalty;
}
...@@ -55,13 +55,14 @@ class QRCodeGenerator { ...@@ -55,13 +55,14 @@ class QRCodeGenerator {
// Generates a QR code containing the given data. // Generates a QR code containing the given data.
// The generator will attempt to choose a version that fits the data. The // The generator will attempt to choose a version that fits the data. The
// returned span's length is input-dependent and not known at compile-time in // returned span's length is input-dependent and not known at compile-time in
// this case. // this case. The optional |mask| argument specifies the QR mask value to use
base::Optional<GeneratedCode> Generate(base::span<const uint8_t> in); // (from 0 to 7). If not specified, the optimal mask is calculated per the
// algorithm specified in the QR standard.
base::Optional<GeneratedCode> Generate(
base::span<const uint8_t> in,
base::Optional<uint8_t> mask = base::nullopt);
private: private:
// MaskFunction3 implements one of the data-masking functions. See figure 21.
static uint8_t MaskFunction3(int x, int y);
// PutFinder paints a finder symbol at the given coordinates. // PutFinder paints a finder symbol at the given coordinates.
void PutFinder(int x, int y); void PutFinder(int x, int y);
...@@ -117,6 +118,10 @@ class QRCodeGenerator { ...@@ -117,6 +118,10 @@ class QRCodeGenerator {
size_t block_bytes, size_t block_bytes,
size_t block_ec_bytes); size_t block_ec_bytes);
// CountPenaltyPoints sums the penalty points for the current, fully drawn,
// code. See table 11.
unsigned CountPenaltyPoints() const;
// Parameters for the currently-selected version of the QR code. // Parameters for the currently-selected version of the QR code.
// Generate() will pick a version that can contain enough data. // Generate() will pick a version that can contain enough data.
// Unowned; nullptr until initialized in Generate(). // Unowned; nullptr until initialized in Generate().
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
#include <utility> #include <utility>
#include "base/containers/span.h" #include "base/containers/span.h"
#include "base/optional.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/common/qr_code_generator/qr_code_generator.h" #include "chrome/common/qr_code_generator/qr_code_generator.h"
// kTerminalBackgroundIsBright controls the output polarity. Many QR scanners // kTerminalBackgroundIsBright controls the output polarity. Many QR scanners
...@@ -34,14 +36,24 @@ int main(int argc, char** argv) { ...@@ -34,14 +36,24 @@ int main(int argc, char** argv) {
// Presubmits don't allow fprintf to a variable called |stderr|. // Presubmits don't allow fprintf to a variable called |stderr|.
FILE* const STDERR = stderr; FILE* const STDERR = stderr;
if (argc != 2) { if (argc < 2 || argc > 3) {
fprintf(STDERR, "Usage: %s <input string>\n", argv[0]); fprintf(STDERR, "Usage: %s <input string> [mask number]\n", argv[0]);
return 1; return 1;
} }
const uint8_t* const input = reinterpret_cast<const uint8_t*>(argv[1]); const uint8_t* const input = reinterpret_cast<const uint8_t*>(argv[1]);
const size_t input_len = strlen(argv[1]); const size_t input_len = strlen(argv[1]);
base::Optional<uint8_t> mask;
if (argc == 3) {
unsigned mask_unsigned;
if (!base::StringToUint(argv[2], &mask_unsigned) || mask_unsigned > 7) {
fprintf(STDERR, "Mask numbers run from zero to seven.\n");
return 1;
}
mask = static_cast<uint8_t>(mask_unsigned);
}
const char* black = kNoPaint; const char* black = kNoPaint;
const char* white = kPaint; const char* white = kPaint;
if (kTerminalBackgroundIsBright) { if (kTerminalBackgroundIsBright) {
...@@ -50,7 +62,7 @@ int main(int argc, char** argv) { ...@@ -50,7 +62,7 @@ int main(int argc, char** argv) {
QRCodeGenerator generator; QRCodeGenerator generator;
base::Optional<QRCodeGenerator::GeneratedCode> code = base::Optional<QRCodeGenerator::GeneratedCode> code =
generator.Generate(base::span<const uint8_t>(input, input_len)); generator.Generate(base::span<const uint8_t>(input, input_len), mask);
if (!code) { if (!code) {
fprintf(STDERR, "Input too long to be encoded.\n"); fprintf(STDERR, "Input too long to be encoded.\n");
return 2; return 2;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment