Commit f5e0fc56 authored by Travis Skare's avatar Travis Skare Committed by Commit Bot

Add support for selectable versions in QR encoder.

Bug: 1088012
Change-Id: I5f09de4fb703864e13af8b67f6f4f130735f82fe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2238491
Commit-Queue: Travis Skare <skare@chromium.org>
Reviewed-by: default avatarAdam Langley <agl@chromium.org>
Cr-Commit-Position: refs/heads/master@{#786083}
parent 00d1fa84
...@@ -29,7 +29,7 @@ class QRView : public views::View { ...@@ -29,7 +29,7 @@ class QRView : public views::View {
static constexpr int kDinoTilePixels = 3; static constexpr int kDinoTilePixels = 3;
// kMid is the pixel offset from the top (or left) to the middle of the // kMid is the pixel offset from the top (or left) to the middle of the
// displayed QR code. // displayed QR code.
static constexpr int kMid = (kTilePixels * (2 + QRCode::kSize + 2)) / 2; static constexpr int kMid = (kTilePixels * (2 + QRCode::V5::kSize + 2)) / 2;
// kDinoX is the x-coordinate of the dino image. // kDinoX is the x-coordinate of the dino image.
static constexpr int kDinoX = static constexpr int kDinoX =
kMid - (dino_image::kDinoWidth * kDinoTilePixels) / 2; kMid - (dino_image::kDinoWidth * kDinoTilePixels) / 2;
...@@ -37,21 +37,30 @@ class QRView : public views::View { ...@@ -37,21 +37,30 @@ class QRView : public views::View {
static constexpr int kDinoY = static constexpr int kDinoY =
kMid - (dino_image::kDinoHeight * kDinoTilePixels) / 2; kMid - (dino_image::kDinoHeight * kDinoTilePixels) / 2;
explicit QRView(base::span<const uint8_t> qr_data) explicit QRView(base::span<const uint8_t> qr_data) {
: qr_tiles_(qr_.Generate(qr_data)) {} base::Optional<QRCode::GeneratedCode> code = qr_.Generate(qr_data);
DCHECK(code);
// The QR Encoder supports dynamic sizing but we expect our data to fit in
// a version five code.
DCHECK(code->qr_size == QRCode::V5::kSize);
qr_tiles_ = code->data;
}
~QRView() override = default; ~QRView() override = default;
void RefreshQRCode(base::span<const uint8_t> new_qr_data) { void RefreshQRCode(base::span<const uint8_t> new_qr_data) {
state_ = (state_ + 1) % 6; state_ = (state_ + 1) % 6;
qr_tiles_ = qr_.Generate(new_qr_data); base::Optional<QRCode::GeneratedCode> code = qr_.Generate(new_qr_data);
DCHECK(code);
qr_tiles_ = code->data;
SchedulePaint(); SchedulePaint();
} }
// View: // View:
gfx::Size CalculatePreferredSize() const override { gfx::Size CalculatePreferredSize() const override {
// A two-tile border is required around the QR code. // A two-tile border is required around the QR code.
return gfx::Size((2 + QRCode::kSize + 2) * kTilePixels, return gfx::Size((2 + QRCode::V5::kSize + 2) * kTilePixels,
(2 + QRCode::kSize + 2) * kTilePixels); (2 + QRCode::V5::kSize + 2) * kTilePixels);
} }
void OnPaint(gfx::Canvas* canvas) override { void OnPaint(gfx::Canvas* canvas) override {
...@@ -83,28 +92,28 @@ class QRView : public views::View { ...@@ -83,28 +92,28 @@ class QRView : public views::View {
// Draw the two-tile border around the edge. // Draw the two-tile border around the edge.
// Top. // Top.
canvas->FillRect( canvas->FillRect(gfx::Rect(0, 0, (2 + QRCode::V5::kSize + 2) * kTilePixels,
gfx::Rect(0, 0, (2 + QRCode::kSize + 2) * kTilePixels, 2 * kTilePixels), 2 * kTilePixels),
off); off);
// Bottom. // Bottom.
canvas->FillRect( canvas->FillRect(
gfx::Rect(0, (2 + QRCode::kSize) * kTilePixels, gfx::Rect(0, (2 + QRCode::V5::kSize) * kTilePixels,
(2 + QRCode::kSize + 2) * kTilePixels, 2 * kTilePixels), (2 + QRCode::V5::kSize + 2) * kTilePixels, 2 * kTilePixels),
off); off);
// Left // Left
canvas->FillRect(gfx::Rect(0, 2 * kTilePixels, 2 * kTilePixels, canvas->FillRect(gfx::Rect(0, 2 * kTilePixels, 2 * kTilePixels,
QRCode::kSize * kTilePixels), QRCode::V5::kSize * kTilePixels),
off); off);
// Right // Right
canvas->FillRect( canvas->FillRect(
gfx::Rect((2 + QRCode::kSize) * kTilePixels, 2 * kTilePixels, gfx::Rect((2 + QRCode::V5::kSize) * kTilePixels, 2 * kTilePixels,
2 * kTilePixels, QRCode::kSize * kTilePixels), 2 * kTilePixels, QRCode::V5::kSize * kTilePixels),
off); off);
// Paint the QR code. // Paint the QR code.
int index = 0; int index = 0;
for (int y = 0; y < QRCode::kSize; y++) { for (int y = 0; y < QRCode::V5::kSize; y++) {
for (int x = 0; x < QRCode::kSize; x++) { for (int x = 0; x < QRCode::V5::kSize; x++) {
SkColor tile_color = qr_tiles_[index++] & 1 ? on : off; SkColor tile_color = qr_tiles_[index++] & 1 ? on : off;
canvas->FillRect(gfx::Rect((x + 2) * kTilePixels, (y + 2) * kTilePixels, canvas->FillRect(gfx::Rect((x + 2) * kTilePixels, (y + 2) * kTilePixels,
kTilePixels, kTilePixels), kTilePixels, kTilePixels),
...@@ -152,7 +161,7 @@ class QRView : public views::View { ...@@ -152,7 +161,7 @@ class QRView : public views::View {
} }
QRCode qr_; QRCode qr_;
base::span<const uint8_t, QRCode::kTotalSize> qr_tiles_; base::span<const uint8_t> qr_tiles_;
int state_ = 0; int state_ = 0;
DISALLOW_COPY_AND_ASSIGN(QRView); DISALLOW_COPY_AND_ASSIGN(QRView);
...@@ -169,7 +178,7 @@ constexpr size_t Base64EncodedSize(size_t input_length) { ...@@ -169,7 +178,7 @@ constexpr size_t Base64EncodedSize(size_t input_length) {
// |qr_generator_key| and the current time such that the caBLE discovery code // |qr_generator_key| and the current time such that the caBLE discovery code
// can recognise the URL as valid. // can recognise the URL as valid.
base::span<uint8_t> QRDataForCurrentTime( base::span<uint8_t> QRDataForCurrentTime(
uint8_t out_buf[QRCode::kInputBytes], uint8_t out_buf[QRCode::V5::kInputBytes],
base::span<const uint8_t, 32> qr_generator_key) { base::span<const uint8_t, 32> qr_generator_key) {
const int64_t current_tick = device::CableDiscoveryData::CurrentTimeTick(); const int64_t current_tick = device::CableDiscoveryData::CurrentTimeTick();
const device::CableQRData qr_data = const device::CableQRData qr_data =
...@@ -187,7 +196,7 @@ base::span<uint8_t> QRDataForCurrentTime( ...@@ -187,7 +196,7 @@ base::span<uint8_t> QRDataForCurrentTime(
static constexpr char kPrefix[] = "fido://c1/"; static constexpr char kPrefix[] = "fido://c1/";
static constexpr size_t kPrefixLength = sizeof(kPrefix) - 1; static constexpr size_t kPrefixLength = sizeof(kPrefix) - 1;
static_assert(QRCode::kInputBytes >= kPrefixLength + kEncodedDataLength, static_assert(QRCode::V5::kInputBytes >= kPrefixLength + kEncodedDataLength,
"unexpected QR input length"); "unexpected QR input length");
memcpy(out_buf, kPrefix, kPrefixLength); memcpy(out_buf, kPrefix, kPrefixLength);
memcpy(&out_buf[kPrefixLength], base64_qr_data.data(), kEncodedDataLength); memcpy(&out_buf[kPrefixLength], base64_qr_data.data(), kEncodedDataLength);
...@@ -228,7 +237,7 @@ AuthenticatorQRSheetView::~AuthenticatorQRSheetView() = default; ...@@ -228,7 +237,7 @@ AuthenticatorQRSheetView::~AuthenticatorQRSheetView() = default;
std::unique_ptr<views::View> std::unique_ptr<views::View>
AuthenticatorQRSheetView::BuildStepSpecificContent() { AuthenticatorQRSheetView::BuildStepSpecificContent() {
uint8_t qr_data_buf[QRCode::kInputBytes]; uint8_t qr_data_buf[QRCode::V5::kInputBytes];
auto qr_view = std::make_unique<AuthenticatorQRViewCentered>( auto qr_view = std::make_unique<AuthenticatorQRViewCentered>(
QRDataForCurrentTime(qr_data_buf, qr_generator_key_)); QRDataForCurrentTime(qr_data_buf, qr_generator_key_));
qr_view_ = qr_view.get(); qr_view_ = qr_view.get();
...@@ -239,6 +248,6 @@ AuthenticatorQRSheetView::BuildStepSpecificContent() { ...@@ -239,6 +248,6 @@ AuthenticatorQRSheetView::BuildStepSpecificContent() {
} }
void AuthenticatorQRSheetView::Update() { void AuthenticatorQRSheetView::Update() {
uint8_t qr_data_buf[QRCode::kInputBytes]; uint8_t qr_data_buf[QRCode::V5::kInputBytes];
qr_view_->RefreshQRCode(QRDataForCurrentTime(qr_data_buf, qr_generator_key_)); qr_view_->RefreshQRCode(QRDataForCurrentTime(qr_data_buf, qr_generator_key_));
} }
...@@ -9,28 +9,148 @@ ...@@ -9,28 +9,148 @@
#include <ostream> #include <ostream>
#include "base/check_op.h" #include "base/check_op.h"
#include "base/notreached.h"
using GeneratedCode = QRCodeGenerator::GeneratedCode;
using QRVersionInfo = QRCodeGenerator::QRVersionInfo;
namespace {
// Default version five QR Code.
constexpr int kVersionDefault = 5;
// Extended-length QR code version used by service.
constexpr int kVersionExtended = 7;
// Threshold for switching between the two supported versions.
constexpr int kLargeVersionThresholdLength = 84;
} // namespace
// TODO(skare): tracking some items to resolve before submit in this block.
// - In the QRVersionInfo comment, "Error correction group" may not be a formal
// term in the spec. OK?
// if so, change naming: Group 0/1 -> Group 1/2 (1-based indexing).
constexpr QRCodeGenerator::QRVersionInfo version_infos[] = {
// Version data is specified as:
// version, size, total_bytes.
// Error correction Group 0 [see Table 9]
// group_bytes, num_segments, segment_data_bytes
// Error correction Group 1
// [may not apply for all versions, in which case num_segments is 0]
// group_bytes, num_segments, segment_data_bytes
// total_bytes for the overall code, and {num_segments, segment_data_bytes}
// or each group are available on table 9, page 38 of the spec.
// group_bytes may be calculated as num_segments*c from the table.
// 5-M
// 134 bytes, as 2 segments of 67.
{5, 37, 134, 134, 2, 43, 0, 0, 0},
// 7-M
// 196 bytes, as 4 segments of 49.
{7, 45, 196, 196, 4, 31, 0, 0, 0},
};
static_assert(QRCodeGenerator::kNumSegments != 0 && // static
QRCodeGenerator::kTotalBytes % const QRVersionInfo* QRCodeGenerator::GetVersionInfo(int version) {
QRCodeGenerator::kNumSegments == for (unsigned int i = 0; i < base::size(version_infos); i++) {
0, if (version_infos[i].version == version)
"invalid configuration"); return &version_infos[i];
}
NOTREACHED() << "No version info found for v" << version;
return nullptr;
}
base::span<uint8_t, QRCodeGenerator::kTotalSize> QRCodeGenerator::Generate( // Static assertions for constraints for commonly-used versions.
static_assert(version_infos[0].num_segments != 0 &&
version_infos[0].total_bytes %
version_infos[0].num_segments ==
0,
"Invalid configuration, version_infos[0]");
static_assert(
version_infos[1].total_bytes ==
version_infos[1].group_bytes + version_infos[1].group_bytes_1,
"Invalid configuration, version_infos[1]. Groups don't sum to total.");
static_assert(version_infos[1].group_bytes == version_infos[1].segment_bytes() *
version_infos[1].num_segments,
"Invalid configuration, version_infos[1], group 0.");
static_assert(version_infos[1].group_bytes_1 ==
version_infos[1].segment_bytes_1() *
version_infos[1].num_segments_1,
"Invalid configuration, version_infos[1], group 1.");
QRCodeGenerator::QRCodeGenerator() = default;
QRCodeGenerator::~QRCodeGenerator() = default;
QRCodeGenerator::GeneratedCode::GeneratedCode() = default;
QRCodeGenerator::GeneratedCode::GeneratedCode(
QRCodeGenerator::GeneratedCode&&) = default;
QRCodeGenerator::GeneratedCode::~GeneratedCode() = default;
base::Optional<GeneratedCode> QRCodeGenerator::Generate(
base::span<const uint8_t> in) { base::span<const uint8_t> in) {
// Can't pass a constexpr to CHECK_LE. // We're currently using a minimal set of versions to shrink test surface.
if (in.size() > kInputBytes) { // When expanding, take care to validate across different platforms and
CHECK(false) << "input too long"; // a selection of QR Scanner apps.
const QRVersionInfo* version_info =
(in.size() <= kLargeVersionThresholdLength)
? GetVersionInfo(kVersionDefault)
: GetVersionInfo(kVersionExtended);
if (version_info != version_info_) {
version_info_ = version_info;
d_ = std::make_unique<uint8_t[]>(version_info_->total_size());
}
// Previous data and "set" bits must be cleared.
memset(d_.get(), 0, version_info_->total_size());
// Input data is too long for any supported code.
if (in.size() > version_info->input_bytes()) {
return base::nullopt;
} }
memset(d_, 0, sizeof(d_));
PutVerticalTiming(6); PutVerticalTiming(6);
PutHorizontalTiming(6); PutHorizontalTiming(6);
PutFinder(3, 3); PutFinder(3, 3);
PutFinder(3, kSize - 4); PutFinder(3, version_info_->size - 4);
PutFinder(kSize - 4, 3); PutFinder(version_info_->size - 4, 3);
// See table E.1 for the location of alignment symbols. // See table E.1 for the location of alignment symbols.
if (version_info_->version == kVersionDefault) {
PutAlignment(30, 30); PutAlignment(30, 30);
} else if (version_info_->version == kVersionExtended) {
constexpr int kLocatorIndicesV7[] = {6, 22, 38};
constexpr int kLocatorIndicesV13[] = {6, 34, 62};
// Constant for now; may differ in higher versions.
constexpr int num_locator_coefficients = 3;
const int* locator_indices = nullptr;
switch (version_info_->version) {
case 7:
locator_indices = kLocatorIndicesV7;
break;
case 13:
locator_indices = kLocatorIndicesV13;
break;
default:
NOTREACHED() << "No Locator Indices found for v"
<< version_info_->version;
break;
}
int first_index = locator_indices[0];
int last_index = locator_indices[num_locator_coefficients - 1];
for (int i = 0; i < num_locator_coefficients; i++) {
for (int j = 0; j < num_locator_coefficients; j++) {
int row = locator_indices[i];
int col = locator_indices[j];
// Aligntment symbols must not overwrite locators.
if ((row == first_index && (col == first_index || col == last_index)) ||
(row == last_index && col == first_index)) {
continue;
}
PutAlignment(row, col);
}
}
}
// kFormatInformation is the encoded formatting word for the QR code that // kFormatInformation is the encoded formatting word for the QR code that
// this code generates. See tables 10 and 12. // this code generates. See tables 10 and 12.
...@@ -43,43 +163,104 @@ base::span<uint8_t, QRCodeGenerator::kTotalSize> QRCodeGenerator::Generate( ...@@ -43,43 +163,104 @@ base::span<uint8_t, QRCodeGenerator::kTotalSize> QRCodeGenerator::Generate(
constexpr uint16_t kFormatInformation = 0x5b4b; constexpr uint16_t kFormatInformation = 0x5b4b;
PutFormatBits(kFormatInformation); PutFormatBits(kFormatInformation);
// QR codes require some framing of the data which requires 12 bits of // Add the mode and character count.
// overhead. Since 12 is not a multiple of eight, a frame-shift of all
// QR codes require some framing of the data. This requires:
// Version 1-9: 4 bits for mode + 8 bits for char count = 12 bits
// Version 10-26: 4 bits for mode + 16 bits for char count = 20 bits
// Details are in Table 3.
// Since 12 and 20 are not a multiple of eight, a frame-shift of all
// subsequent bytes is required. // subsequent bytes is required.
uint8_t prefixed_data[kDataBytes]; size_t data_bytes = version_info_->data_bytes();
static_assert(kInputBytes < 256, "in too large for 8-bit length"); uint8_t prefixed_data[data_bytes];
int framing_offset_bytes = 0;
if (version_info->version <= 9) {
DCHECK_LT(in.size(), 256u) << "in.size() too large for 8-bit length";
const uint8_t len8 = static_cast<uint8_t>(in.size()); const uint8_t len8 = static_cast<uint8_t>(in.size());
prefixed_data[0] = 0x40 | (len8 >> 4); prefixed_data[0] = 0x40 | (len8 >> 4);
prefixed_data[1] = (len8 << 4); prefixed_data[1] = (len8 << 4);
if (!in.empty()) { if (!in.empty()) {
prefixed_data[1] |= (in[0] >> 4); prefixed_data[1] |= (in[0] >> 4);
} }
framing_offset_bytes = 2;
} else if (version_info->version <= 26) {
DCHECK_LT(in.size(), 0x10000u) << "in.size() too large for 16-bit length";
const uint16_t len16 = static_cast<uint16_t>(in.size());
prefixed_data[0] = 0x40 | (len16 >> 12);
prefixed_data[1] = (len16 >> 4);
prefixed_data[2] = (len16 << 4);
if (!in.empty()) {
prefixed_data[2] |= (in[0] >> 4);
}
framing_offset_bytes = 3;
} else {
NOTREACHED() << "Unsupported version in Generate(): "
<< version_info->version;
}
for (size_t i = 0; i < in.size() - 1; i++) { for (size_t i = 0; i < in.size() - 1; i++) {
prefixed_data[i + 2] = (in[i] << 4) | (in[i + 1] >> 4); prefixed_data[i + framing_offset_bytes] = (in[i] << 4) | (in[i + 1] >> 4);
} }
if (!in.empty()) { if (!in.empty()) {
prefixed_data[in.size() + 1] = in[in.size() - 1] << 4; prefixed_data[in.size() + 1] = in[in.size() + 1 - framing_offset_bytes]
<< 4;
} }
// The QR code looks a little odd with fixed padding. Thus replicate the // The QR code looks a little odd with fixed padding. Thus replicate the
// message to fill the input. // message to fill the input.
for (size_t i = in.size() + 2; i < kInputBytes; i++) { for (size_t i = in.size() + framing_offset_bytes;
prefixed_data[i] = prefixed_data[i % (in.size() + 2)]; i < version_info_->input_bytes(); i++) {
prefixed_data[i] = prefixed_data[i % (in.size() + framing_offset_bytes)];
} }
// Each segment of input data is expanded with error correcting // Each segment of input data is expanded with error correcting
// information and then interleaved. // information and then interleaved.
uint8_t expanded_segments[kNumSegments][kSegmentBytes];
for (size_t i = 0; i < kNumSegments; i++) { // Error Correction for Group 0, present for all versions.
size_t num_segments = version_info_->num_segments;
size_t segment_bytes = version_info_->segment_bytes();
size_t segment_ec_bytes = version_info_->segment_ec_bytes();
uint8_t expanded_segments[num_segments][segment_bytes];
for (size_t i = 0; i < num_segments; i++) {
AddErrorCorrection(&expanded_segments[i][0], AddErrorCorrection(&expanded_segments[i][0],
&prefixed_data[kSegmentDataBytes * i]); &prefixed_data[version_info_->segment_data_bytes * i],
} segment_bytes, segment_ec_bytes);
}
// Error Correction for Group 1, present for some versions.
// Factor out the number of bytes written by the prior group.
size_t num_segments_1 = version_info_->num_segments_1;
size_t segment_bytes_1 = version_info_->segment_bytes_1();
// TODO(skare): Reenable when extendiong to v13.
// Additionally do not use a zero-length array; nonstandard.
/*
int group_data_offset = version_info_->segment_data_bytes * num_segments;
size_t segment_ec_bytes_1 = version_info_->segment_ec_bytes_1();
uint8_t expanded_segments_1[num_segments_1][segment_bytes_1];
if (version_info_->num_segments_1 > 0) {
for (size_t i = 0; i < num_segments_1; i++) {
AddErrorCorrection(
&expanded_segments_1[i][0],
&prefixed_data[group_data_offset +
version_info_->segment_data_bytes_1 * i],
segment_bytes_1, segment_ec_bytes_1);
}
}
*/
size_t total_bytes = version_info_->total_bytes;
uint8_t interleaved_data[total_bytes];
CHECK(total_bytes ==
segment_bytes * num_segments + segment_bytes_1 * num_segments_1)
<< "internal error";
uint8_t interleaved_data[kTotalBytes];
static_assert(kTotalBytes == kSegmentBytes * kNumSegments, "internal error");
size_t k = 0; size_t k = 0;
for (size_t j = 0; j < kSegmentBytes; j++) { // Interleave data from all segments.
for (size_t i = 0; i < kNumSegments; i++) { // If we have multiple groups, the later groups may have more bytes in their
// segments after we exhaust data in the first group.
// TODO(skare): Extend when enabling v13.
for (size_t j = 0; j < segment_bytes; j++) {
for (size_t i = 0; i < num_segments; i++) {
interleaved_data[k++] = expanded_segments[i][j]; interleaved_data[k++] = expanded_segments[i][j];
} }
} }
...@@ -91,7 +272,10 @@ base::span<uint8_t, QRCodeGenerator::kTotalSize> QRCodeGenerator::Generate( ...@@ -91,7 +272,10 @@ base::span<uint8_t, QRCodeGenerator::kTotalSize> QRCodeGenerator::Generate(
// transient. // transient.
PutBits(interleaved_data, sizeof(interleaved_data), MaskFunction3); PutBits(interleaved_data, sizeof(interleaved_data), MaskFunction3);
return d_; GeneratedCode code;
code.data = base::span<uint8_t>(d_.get(), version_info_->total_size());
code.qr_size = version_info_->size;
return code;
} }
// MaskFunction3 implements one of the data-masking functions. See figure 21. // MaskFunction3 implements one of the data-masking functions. See figure 21.
...@@ -143,14 +327,14 @@ void QRCodeGenerator::PutAlignment(int x, int y) { ...@@ -143,14 +327,14 @@ void QRCodeGenerator::PutAlignment(int x, int y) {
// PutVerticalTiming paints the vertical timing signal. // PutVerticalTiming paints the vertical timing signal.
void QRCodeGenerator::PutVerticalTiming(int x) { void QRCodeGenerator::PutVerticalTiming(int x) {
for (int y = 0; y < kSize; y++) { for (int y = 0; y < version_info_->size; y++) {
at(x, y) = 0b10 | (1 ^ (y & 1)); at(x, y) = 0b10 | (1 ^ (y & 1));
} }
} }
// PutVerticalTiming paints the horizontal timing signal. // PutVerticalTiming paints the horizontal timing signal.
void QRCodeGenerator::PutHorizontalTiming(int y) { void QRCodeGenerator::PutHorizontalTiming(int y) {
for (int x = 0; x < kSize; x++) { for (int x = 0; x < version_info_->size; x++) {
at(x, y) = 0b10 | (1 ^ (x & 1)); at(x, y) = 0b10 | (1 ^ (x & 1));
} }
} }
...@@ -173,16 +357,49 @@ void QRCodeGenerator::PutFormatBits(const uint16_t format) { ...@@ -173,16 +357,49 @@ void QRCodeGenerator::PutFormatBits(const uint16_t format) {
} }
v = format; v = format;
for (int x = kSize - 1; x >= kSize - 1 - 7; x--) { for (int x = version_info_->size - 1; x >= version_info_->size - 1 - 7; x--) {
at(x, 8) = 0b10 | (v & 1); at(x, 8) = 0b10 | (v & 1);
v >>= 1; v >>= 1;
} }
at(8, kSize - 1 - 7) = 0b11; at(8, version_info_->size - 1 - 7) = 0b11;
for (int y = kSize - 1 - 6; y <= kSize - 1; y++) { for (int y = version_info_->size - 1 - 6; y <= version_info_->size - 1; y++) {
at(8, y) = 0b10 | (v & 1); at(8, y) = 0b10 | (v & 1);
v >>= 1; v >>= 1;
} }
// Version 7 and larger require 18-bit version information taking the form
// of 6x3 rectangles above the bottom-left locator and to the left of the
// top-right locator.
int size = version_info_->size;
int version = version_info_->version;
int vi_string = 0;
switch (version) {
case 5:
break;
case 7:
vi_string = 0b000111110010010100;
break;
case 13:
vi_string = 0b001101100001000111;
break;
default:
NOTREACHED() << "No version information string provided for QR v"
<< version;
break;
}
if (vi_string) {
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 3; j++) {
// Bottom-left rectangle is top-to-bottom, left-to-right
at(i, size - 8 - 3 + j) = 0b10 | (vi_string & 1);
// Top-right rectangle is left-to-right, top-to-bottom
at(size - 8 - 3 + j, i) = 0b10 | (vi_string & 1);
// Shift to consider the next bit.
vi_string >>= 1;
}
}
}
} }
// PutBits writes the given data into the QR code in correct order, avoiding // PutBits writes the given data into the QR code in correct order, avoiding
...@@ -225,8 +442,8 @@ void QRCodeGenerator::PutBits(const uint8_t* data, ...@@ -225,8 +442,8 @@ void QRCodeGenerator::PutBits(const uint8_t* data,
BitStream stream(data, data_len); BitStream stream(data, data_len);
bool going_up = true; bool going_up = true;
int x = kSize - 1; int x = version_info_->size - 1;
int y = kSize - 1; int y = version_info_->size - 1;
for (;;) { for (;;) {
uint8_t& right = at(x, y); uint8_t& right = at(x, y);
...@@ -241,7 +458,7 @@ void QRCodeGenerator::PutBits(const uint8_t* data, ...@@ -241,7 +458,7 @@ void QRCodeGenerator::PutBits(const uint8_t* data,
left = stream.Next() ^ mask_func(x - 1, y); left = stream.Next() ^ mask_func(x - 1, y);
} }
if ((going_up && y == 0) || (!going_up && y == kSize - 1)) { if ((going_up && y == 0) || (!going_up && y == version_info_->size - 1)) {
if (x == 1) { if (x == 1) {
break; break;
} }
...@@ -264,35 +481,35 @@ void QRCodeGenerator::PutBits(const uint8_t* data, ...@@ -264,35 +481,35 @@ void QRCodeGenerator::PutBits(const uint8_t* data,
// at returns a reference to the given element of |d_|. // at returns a reference to the given element of |d_|.
uint8_t& QRCodeGenerator::at(int x, int y) { uint8_t& QRCodeGenerator::at(int x, int y) {
DCHECK_LE(0, x); DCHECK_LE(0, x);
DCHECK_LT(x, kSize); DCHECK_LT(x, version_info_->size);
DCHECK_LE(0, y); DCHECK_LE(0, y);
DCHECK_LT(y, kSize); DCHECK_LT(y, version_info_->size);
return d_[kSize * y + x]; return d_[version_info_->size * y + x];
} }
// fillAt sets the |len| elements at (x, y) to |value|. // fillAt sets the |len| elements at (x, y) to |value|.
void QRCodeGenerator::fillAt(int x, int y, size_t len, uint8_t value) { void QRCodeGenerator::fillAt(int x, int y, size_t len, uint8_t value) {
DCHECK_LE(0, x); DCHECK_LE(0, x);
DCHECK_LE(static_cast<int>(x + len), kSize); DCHECK_LE(static_cast<int>(x + len), version_info_->size);
DCHECK_LE(0, y); DCHECK_LE(0, y);
DCHECK_LT(y, kSize); DCHECK_LT(y, version_info_->size);
memset(&d_[kSize * y + x], value, len); memset(&d_[version_info_->size * y + x], value, len);
} }
// copyTo copies |len| elements from |data| to the elements at (x, y). // copyTo copies |len| elements from |data| to the elements at (x, y).
void QRCodeGenerator::copyTo(int x, int y, const uint8_t* data, size_t len) { void QRCodeGenerator::copyTo(int x, int y, const uint8_t* data, size_t len) {
DCHECK_LE(0, x); DCHECK_LE(0, x);
DCHECK_LE(static_cast<int>(x + len), kSize); DCHECK_LE(static_cast<int>(x + len), version_info_->size);
DCHECK_LE(0, y); DCHECK_LE(0, y);
DCHECK_LT(y, kSize); DCHECK_LT(y, version_info_->size);
memcpy(&d_[kSize * y + x], data, len); memcpy(&d_[version_info_->size * y + x], data, len);
} }
// clipped returns a reference to the given element of |d_|, or to // clipped returns a reference to the given element of |d_|, or to
// |clip_dump_| if the coordinates are out of bounds. // |clip_dump_| if the coordinates are out of bounds.
uint8_t& QRCodeGenerator::clipped(int x, int y) { uint8_t& QRCodeGenerator::clipped(int x, int y) {
if (0 <= x && x < kSize && 0 <= y && y < kSize) { if (0 <= x && x < version_info_->size && 0 <= y && y < version_info_->size) {
return d_[kSize * y + x]; return d_[version_info_->size * y + x];
} }
return clip_dump_; return clip_dump_;
} }
...@@ -326,10 +543,13 @@ uint8_t QRCodeGenerator::GF28Mul(uint16_t a, uint16_t b) { ...@@ -326,10 +543,13 @@ uint8_t QRCodeGenerator::GF28Mul(uint16_t a, uint16_t b) {
// AddErrorCorrection writes the Reed-Solomon expanded version of |in| to // AddErrorCorrection writes the Reed-Solomon expanded version of |in| to
// |out|. // |out|.
// static // |out| should have length segment_bytes for the code's version.
void QRCodeGenerator::AddErrorCorrection(uint8_t out[kSegmentBytes], // |in| should have length segment_data_bytes for the code's version.
const uint8_t in[kSegmentDataBytes]) { void QRCodeGenerator::AddErrorCorrection(uint8_t out[],
// kGenerator is the product of (z - x^i) for 0 <= i < |kSegmentECBytes|, const uint8_t in[],
size_t segment_bytes,
size_t segment_ec_bytes) {
// kGenerator is the product of (z - x^i) for 0 <= i < |segment_ec_bytes|,
// where x is the term of GF(2^8) and z is the term of a polynomial ring // where x is the term of GF(2^8) and z is the term of a polynomial ring
// over GF(2^8). It's generated with the following Sage script: // over GF(2^8). It's generated with the following Sage script:
// //
...@@ -350,40 +570,73 @@ void QRCodeGenerator::AddErrorCorrection(uint8_t out[kSegmentBytes], ...@@ -350,40 +570,73 @@ void QRCodeGenerator::AddErrorCorrection(uint8_t out[kSegmentBytes],
// coeffs = list(gen) // coeffs = list(gen)
// gen = [toByte(x) for x in coeffs] // gen = [toByte(x) for x in coeffs]
// print 'uint8_t kGenerator[' + str(len(gen)) + '] = {' + str(gen) + '}' // print 'uint8_t kGenerator[' + str(len(gen)) + '] = {' + str(gen) + '}'
static constexpr uint8_t kGenerator[kSegmentECBytes + 1] = {
// Used for 7-M: 18 error correction codewords per block.
static std::vector<uint8_t> kGenerator18 = {
146, 217, 67, 32, 75, 173, 82, 73, 220, 240,
215, 199, 175, 149, 113, 183, 251, 239, 1,
};
// Used for 13-M; 22 error correction codewords per block.
static std::vector<uint8_t> kGenerator22 = {
245, 145, 26, 230, 218, 86, 253, 67, 123, 29, 137, 28,
40, 69, 189, 19, 244, 182, 176, 131, 179, 89, 1,
};
// Used for 5-M, 24 error correction codewords per block.
static std::vector<uint8_t> kGenerator24 = {
117, 144, 217, 127, 247, 237, 1, 206, 43, 61, 72, 130, 73, 117, 144, 217, 127, 247, 237, 1, 206, 43, 61, 72, 130, 73,
229, 150, 115, 102, 216, 237, 178, 70, 169, 118, 122, 1, 229, 150, 115, 102, 216, 237, 178, 70, 169, 118, 122, 1,
}; };
const std::vector<uint8_t>* generator = nullptr;
switch (segment_ec_bytes) {
case 18:
generator = &kGenerator18;
break;
case 22:
generator = &kGenerator22;
break;
case 24:
generator = &kGenerator24;
break;
default: {
NOTREACHED() << "Unsupported Generator Polynomial for segment_ec_bytes: "
<< segment_ec_bytes;
return;
}
}
// The error-correction bytes are the remainder of dividing |in| * x^k by // The error-correction bytes are the remainder of dividing |in| * x^k by
// |kGenerator|, where |k| is the number of EC codewords. Polynomials here // |kGenerator|, where |k| is the number of EC codewords. Polynomials here
// are represented in little-endian order, i.e. the value at index |i| is // are represented in little-endian order, i.e. the value at index |i| is
// the coefficient of z^i. // the coefficient of z^i.
// Multiplication of |in| by x^k thus just involves moving it up. // Multiplication of |in| by x^k thus just involves moving it up.
uint8_t remainder[kSegmentBytes]; std::unique_ptr<uint8_t[]> remainder(new uint8_t[segment_bytes]);
memset(remainder, 0, kSegmentECBytes); memset(remainder.get(), 0, segment_ec_bytes);
size_t segment_data_bytes = segment_bytes - segment_ec_bytes;
// Reed-Solomon input is backwards. See section 7.5.2. // Reed-Solomon input is backwards. See section 7.5.2.
for (size_t i = 0; i < kSegmentDataBytes; i++) { for (size_t i = 0; i < segment_data_bytes; i++) {
remainder[kSegmentECBytes + i] = in[kSegmentDataBytes - 1 - i]; remainder[segment_ec_bytes + i] = in[segment_data_bytes - 1 - i];
} }
// Progressively eliminate the leading coefficient by subtracting some // Progressively eliminate the leading coefficient by subtracting some
// multiple of |kGenerator| until we have a value smaller than |kGenerator|. // multiple of |generator| until we have a value smaller than |generator|.
for (size_t i = kSegmentBytes - 1; i >= kSegmentECBytes; i--) { for (size_t i = segment_bytes - 1; i >= segment_ec_bytes; i--) {
// The leading coefficient of |kGenerator| is 1, so the multiple to // The leading coefficient of |generator| is 1, so the multiple to
// subtract to eliminate the leading term of |remainder| is the value of // subtract to eliminate the leading term of |remainder| is the value of
// that leading term. The polynomial ring is characteristic two, so // that leading term. The polynomial ring is characteristic two, so
// subtraction is the same as addition, which is XOR. // subtraction is the same as addition, which is XOR.
for (size_t j = 0; j < sizeof(kGenerator) - 1; j++) { for (size_t j = 0; j < generator->size() - 1; j++) {
remainder[i - kSegmentECBytes + j] ^= remainder[i - segment_ec_bytes + j] ^=
GF28Mul(kGenerator[j], remainder[i]); GF28Mul(generator->at(j), remainder[i]);
} }
} }
memmove(out, in, kSegmentDataBytes); memmove(out, in, segment_data_bytes);
// Remove the Reed-Solomon remainder again to match QR's convention. // Remove the Reed-Solomon remainder again to match QR's convention.
for (size_t i = 0; i < kSegmentECBytes; i++) { for (size_t i = 0; i < segment_ec_bytes; i++) {
out[kSegmentDataBytes + i] = remainder[kSegmentECBytes - 1 - i]; out[segment_data_bytes + i] = remainder[segment_ec_bytes - 1 - i];
} }
} }
...@@ -9,38 +9,134 @@ ...@@ -9,38 +9,134 @@
#include <stdint.h> #include <stdint.h>
#include "base/containers/span.h" #include "base/containers/span.h"
#include "base/optional.h"
// QRCodeGenerator generates version five, class M QR codes that carry 84 // QRCodeGenerator generates class M QR codes of various versions.
// bytes of raw data. References in the following comments refer to ISO 18004 // References in the following comments refer to ISO 18004 (3rd edition).
// (3rd edition). // Supports versions up to 26 by adding constants.
class QRCodeGenerator { class QRCodeGenerator {
public: public:
// kSize is the number of "tiles" in each dimension for a v5 QR code. See // A structure containing QR version-specific constants and data.
// All versions currently use error correction at level M.
struct QRVersionInfo {
constexpr QRVersionInfo(const int version,
const int size,
const size_t total_bytes,
const size_t group_bytes,
const size_t num_segments,
const size_t segment_data_bytes,
const size_t group_bytes_1,
const size_t num_segments_1,
const size_t segment_data_bytes_1)
: version(version),
size(size),
total_bytes(total_bytes),
group_bytes(group_bytes),
num_segments(num_segments),
segment_data_bytes(segment_data_bytes),
group_bytes_1(group_bytes_1),
num_segments_1(num_segments_1),
segment_data_bytes_1(segment_data_bytes_1) {}
// The version of the QR code.
const int version;
// The number of "tiles" in each dimension for a QR code of |version|. See
// table 1. (The colored squares in in QR codes are called tiles in the // table 1. (The colored squares in in QR codes are called tiles in the
// spec.) // spec.)
const int size;
// Values taken from Table 9, page 38, for a QR code of version |version|.
const size_t total_bytes; // Sum of group_bytes for each group.
const size_t group_bytes;
const size_t num_segments;
const size_t segment_data_bytes;
const size_t group_bytes_1;
const size_t num_segments_1;
const size_t segment_data_bytes_1;
// Total number of tiles for the QR code, size*size.
constexpr int total_size() const { return size * size; }
constexpr size_t segment_bytes() const {
return group_bytes / num_segments;
}
constexpr size_t segment_ec_bytes() const {
return segment_bytes() - segment_data_bytes;
}
constexpr size_t data_bytes() const {
return segment_data_bytes * num_segments;
}
constexpr size_t segment_bytes_1() const {
if (num_segments_1 == 0)
return 0;
return group_bytes_1 / num_segments_1;
}
constexpr size_t segment_ec_bytes_1() const {
return segment_bytes_1() - segment_data_bytes_1;
}
constexpr size_t data_bytes_1() const {
return segment_data_bytes_1 * num_segments_1;
}
// Two bytes of overhead are needed for QR framing.
// If extending beyond version 26, framing would need to be updated.
size_t input_bytes() const {
int framing_bytes = version <= 9 ? 2 : 3;
return data_bytes() + data_bytes_1() - framing_bytes;
}
DISALLOW_COPY_AND_ASSIGN(QRVersionInfo);
};
// Contains output data for Generate().
// The default state contains no data.
struct GeneratedCode {
public:
GeneratedCode();
GeneratedCode(GeneratedCode&&);
~GeneratedCode();
// Pixel data; pointer to an array of bytes, where the least-significant
// bit of each byte is set if that tile should be "black".
// Clients should ensure four modules of padding when rendering the code.
// On error, will not be populated, and will evaluate to false.
base::span<uint8_t> data;
// Width and height (which are equal) of the generated data, in tiles.
int qr_size = 0;
DISALLOW_COPY_AND_ASSIGN(GeneratedCode);
};
// Static parameters for V5 QR codes.
// These exist while migrating clients to dynamic sizes.
struct V5 {
static constexpr int kSize = 37; static constexpr int kSize = 37;
// kTotalSize is the total number of tiles for a v5 QR code, in both
// directions.
static constexpr int kTotalSize = kSize * kSize; static constexpr int kTotalSize = kSize * kSize;
// These values are taken from table 9 (page 38) for a version five, class M
// QR code. (That table is very badly formatted for version five, the rows in
// the last column don't line up correctly. The correct value for 5M is
// (67,43,12).)
static constexpr size_t kTotalBytes = 134;
static constexpr size_t kNumSegments = 2; static constexpr size_t kNumSegments = 2;
static constexpr size_t kSegmentDataBytes = 43; static constexpr size_t kSegmentDataBytes = 43;
static constexpr size_t kSegmentBytes = kTotalBytes / kNumSegments;
static constexpr size_t kSegmentECBytes = kSegmentBytes - kSegmentDataBytes;
static constexpr size_t kDataBytes = kSegmentDataBytes * kNumSegments; static constexpr size_t kDataBytes = kSegmentDataBytes * kNumSegments;
// Two bytes of overhead are needed for QR framing.
static constexpr size_t kInputBytes = kDataBytes - 2; static constexpr size_t kInputBytes = kDataBytes - 2;
};
// Generate generates a QR code containing the given data and returns a QRCodeGenerator();
// pointer to an array of kTotalSize bytes where the least-significant bit of ~QRCodeGenerator();
// each byte is set if that tile should be "black". The length of |in| must be
// less than, or equal to, |kInputBytes|. // Returns parameters for different QR code versions, or nullptr if the
base::span<uint8_t, kTotalSize> Generate(base::span<const uint8_t> in); // version is not supported (you may provide support in the cc file).
static const QRVersionInfo* GetVersionInfo(int version);
// Generates a QR code containing the given data.
// 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
// this case.
base::Optional<GeneratedCode> Generate(base::span<const uint8_t> in);
private: private:
// MaskFunction3 implements one of the data-masking functions. See figure 21. // MaskFunction3 implements one of the data-masking functions. See figure 21.
...@@ -88,14 +184,27 @@ class QRCodeGenerator { ...@@ -88,14 +184,27 @@ class QRCodeGenerator {
// AddErrorCorrection writes the Reed-Solomon expanded version of |in| to // AddErrorCorrection writes the Reed-Solomon expanded version of |in| to
// |out|. // |out|.
static void AddErrorCorrection(uint8_t out[kSegmentBytes], // |out| should have length segment_bytes for the code's version.
const uint8_t in[kSegmentDataBytes]); // |in| should have length segment_data_bytes for the code's version.
// |segment_bytes| and |segment_ec_bytes| must be provided for the current
// version/level/group.
void AddErrorCorrection(uint8_t out[],
const uint8_t in[],
size_t segment_bytes,
size_t segment_ec_bytes);
// Parameters for the currently-selected version of the QR code.
// Generate() will pick a version that can contain enough data.
// Unowned; nullptr until initialized in Generate().
const QRVersionInfo* version_info_ = nullptr;
// d_ represents a QR code with one byte per pixel. Each byte is one pixel. // d_ represents a QR code with one byte per pixel. Each byte is one pixel.
// The LSB is set if the pixel is "black". The second bit is set if the pixel // The LSB is set if the pixel is "black". The second bit is set if the pixel
// is part of the structure of the QR code, i.e. finder or alignment symbols, // is part of the structure of the QR code, i.e. finder or alignment symbols,
// timing patterns, or format data. // timing patterns, or format data.
uint8_t d_[kSize * kSize]; // Initialized and possibly reinitialized in Generate().
std::unique_ptr<uint8_t[]> d_;
// clip_dump_ is the target of paints that would otherwise fall outside of the // clip_dump_ is the target of paints that would otherwise fall outside of the
// QR code. // QR code.
uint8_t clip_dump_; uint8_t clip_dump_;
......
...@@ -13,14 +13,43 @@ TEST(QRCodeGenerator, Generate) { ...@@ -13,14 +13,43 @@ TEST(QRCodeGenerator, Generate) {
// run under ASan, this will also check that every byte of the output has been // run under ASan, this will also check that every byte of the output has been
// written to. // written to.
QRCodeGenerator qr; QRCodeGenerator qr;
uint8_t input[QRCodeGenerator::kInputBytes]; uint8_t input[QRCodeGenerator::V5::kInputBytes];
memset(input, 'a', sizeof(input)); memset(input, 'a', sizeof(input));
auto qr_data = qr.Generate(input); base::Optional<QRCodeGenerator::GeneratedCode> qr_code = qr.Generate(input);
ASSERT_NE(qr_code, base::nullopt);
auto& qr_data = qr_code->data;
int index = 0; int index = 0;
for (int y = 0; y < QRCodeGenerator::kSize; y++) { ASSERT_EQ(qr_data.size(),
for (int x = 0; x < QRCodeGenerator::kSize; x++) { static_cast<size_t>(QRCodeGenerator::V5::kTotalSize));
for (int y = 0; y < QRCodeGenerator::V5::kSize; y++) {
for (int x = 0; x < QRCodeGenerator::V5::kSize; x++) {
ASSERT_EQ(0, qr_data[index++] & 0b11111100); ASSERT_EQ(0, qr_data[index++] & 0b11111100);
} }
} }
} }
TEST(QRCodeGenerator, GenerateVersionSelection) {
// Ensure that dynamic version selection works,
// even when reusing the same QR Generator object.
// Longer inputs produce longer codes.
QRCodeGenerator qr;
uint8_t input_small[10];
memset(input_small, 'a', sizeof(input_small));
base::Optional<QRCodeGenerator::GeneratedCode> qr_code_small(
qr.Generate(input_small));
ASSERT_NE(qr_code_small, base::nullopt);
int size_small = qr_code_small->data.size();
uint8_t input_large[100];
memset(input_large, 'a', sizeof(input_large));
base::Optional<QRCodeGenerator::GeneratedCode> qr_code_large(
qr.Generate(input_large));
ASSERT_NE(qr_code_large, base::nullopt);
int size_large = qr_code_large->data.size();
ASSERT_GT(size_small, 0);
ASSERT_GT(size_large, 0);
ASSERT_GT(size_large, size_small);
}
...@@ -226,34 +226,52 @@ void QRCodeGeneratorServiceImpl::GenerateQRCode( ...@@ -226,34 +226,52 @@ void QRCodeGeneratorServiceImpl::GenerateQRCode(
mojom::GenerateQRCodeResponsePtr response = mojom::GenerateQRCodeResponsePtr response =
mojom::GenerateQRCodeResponse::New(); mojom::GenerateQRCodeResponse::New();
// TODO(skare): Use a higher QR code version to allow for more data. if (!request->data.data()) {
// TODO(skare): cap string length with message in the UI. response->error_code = mojom::QRCodeGeneratorError::UNKNOWN_ERROR;
if (request->data.length() > QRCodeGenerator::kInputBytes) { std::move(callback).Run(std::move(response));
return;
}
// These lengths account for the dino we superimpose, which
// cuts into error correction bits.
constexpr size_t kLengthSmall = 84;
constexpr size_t kLengthMedium = 122;
// TODO(skare): Test very large codes with readers:
// constexpr size_t kLengthLarge = 331;
constexpr size_t kLengthMax = kLengthMedium;
if (request->data.length() > kLengthMax) {
response->error_code = mojom::QRCodeGeneratorError::INPUT_TOO_LONG; response->error_code = mojom::QRCodeGeneratorError::INPUT_TOO_LONG;
std::move(callback).Run(std::move(response)); std::move(callback).Run(std::move(response));
return; return;
} }
// TODO(skare): Use a higher QR code vdersion. Until then, use the size
// from the common encoder.
const gfx::Size qr_output_data_size = {QRCodeGenerator::kSize,
QRCodeGenerator::kSize};
// TODO(skare): cap string length with message in the UI. // TODO(skare): cap string length with message in the UI.
uint8_t input[QRCodeGenerator::kInputBytes + 1] = {0}; uint8_t input[kLengthMax + 1] = {0};
base::strlcpy(reinterpret_cast<char*>(input), request->data.c_str(), base::strlcpy(reinterpret_cast<char*>(input), request->data.c_str(),
QRCodeGenerator::kInputBytes); kLengthMax);
size_t data_size =
request->data.length() <= kLengthSmall ? kLengthSmall : kLengthMedium;
// Pad with spaces rather than null for better reader compatibility. // Pad with spaces rather than null for better reader compatibility.
// This will go away when supporting multiple versions/data length. for (size_t i = request->data.length(); i < data_size; i++)
for (size_t i = request->data.length(); i < QRCodeGenerator::kInputBytes; i++)
input[i] = 0x20; input[i] = 0x20;
input[QRCodeGenerator::kInputBytes] = 0; input[data_size - 1] = 0;
QRCodeGenerator qr; QRCodeGenerator qr;
auto qr_data_span = qr.Generate(base::span<const uint8_t>( base::Optional<QRCodeGenerator::GeneratedCode> qr_data =
qr.Generate(base::span<const uint8_t>(
reinterpret_cast<const uint8_t*>(request->data.data()), reinterpret_cast<const uint8_t*>(request->data.data()),
request->data.size())); request->data.size()));
if (!qr_data) {
// The above check should have caught the too-long-URL case.
// Remaining errors can be treated as UNKNOWN.
response->error_code = mojom::QRCodeGeneratorError::UNKNOWN_ERROR;
std::move(callback).Run(std::move(response));
return;
}
auto& qr_data_span = qr_data->data;
// The least significant bit of each byte in |qr_data_span| is set if the tile // The least significant bit of each byte in |qr_data.span| is set if the tile
// should be black. // should be black.
for (size_t i = 0; i < qr_data_span.size(); i++) { for (size_t i = 0; i < qr_data_span.size(); i++) {
qr_data_span[i] &= 1; qr_data_span[i] &= 1;
...@@ -261,8 +279,9 @@ void QRCodeGeneratorServiceImpl::GenerateQRCode( ...@@ -261,8 +279,9 @@ void QRCodeGeneratorServiceImpl::GenerateQRCode(
response->data.insert(response->data.begin(), qr_data_span.begin(), response->data.insert(response->data.begin(), qr_data_span.begin(),
qr_data_span.end()); qr_data_span.end());
response->data_size = qr_output_data_size; response->data_size = {qr_data_span.size(), qr_data_span.size()};
response->error_code = mojom::QRCodeGeneratorError::NONE; response->error_code = mojom::QRCodeGeneratorError::NONE;
gfx::Size qr_output_data_size = {qr_data->qr_size, qr_data->qr_size};
RenderBitmap(qr_data_span.data(), qr_output_data_size, request, &response); RenderBitmap(qr_data_span.data(), qr_output_data_size, request, &response);
std::move(callback).Run(std::move(response)); std::move(callback).Run(std::move(response));
......
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