Replace 2d std::vector<bool> with 1d manual allocation

This makes this a bit faster and each qr code is now stored in a
single linear chunck of memory
This commit is contained in:
Oskari Alaranta 2025-10-20 22:07:51 +03:00
parent 840e9769fa
commit 6b6f685f39
1 changed files with 257 additions and 165 deletions

416
main.cpp
View File

@ -4,6 +4,7 @@
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <span>
#include <string_view>
@ -77,6 +78,49 @@ struct GF256
};
static constexpr GF256 s_gf256;
static constexpr std::vector<uint8_t> get_generator(size_t ec_codewords)
{
constexpr auto poly_mul =
[](std::span<const uint8_t> p, std::span<const uint8_t> q) -> std::vector<uint8_t>
{
std::vector<uint8_t> result(p.size() + q.size() - 1, 0);
for (size_t i = 0; i < p.size(); i++)
{
if (p[i] == 0)
continue;
for (size_t j = 0; j < q.size(); j++)
result[i + j] ^= s_gf256.mult(p[i], q[j]);
}
return result;
};
std::vector<uint8_t> generator { 1 };
for (size_t i = 0; i < ec_codewords; i++)
{
const uint8_t term[] { s_gf256.exp[i], 1 };
generator = poly_mul(generator, term);
}
std::reverse(generator.begin(), generator.end());
return generator;
}
static constexpr std::vector<uint8_t> get_remainder(std::span<const uint8_t> message, std::span<const uint8_t> generator)
{
std::vector<uint8_t> dividend(message.begin(), message.end());
dividend.resize(dividend.size() + generator.size() - 1, 0);
while (dividend.size() >= generator.size())
{
if (const uint8_t scale = dividend[0])
for (size_t i = 0; i < generator.size(); i++)
dividend[i] ^= s_gf256.mult(generator[i], scale);
dividend.erase(dividend.begin());
}
return dividend;
}
// s_qr_capacities[x - 1][y] tells the number of bytes version x qr code with error correction y fits
constexpr size_t s_qr_capacities[][4] {
{ 17, 14, 11, 7 },
@ -172,11 +216,6 @@ static constexpr uint8_t s_ec_block_info[][4][5] {
{ { 30, 19, 118, 6, 119 }, { 28, 18, 47, 31, 48 }, { 30, 34, 24, 34, 25 }, { 30, 20, 15, 61, 16 }, },
};
// s_remainer_bits[x - 1] tells the number of required remainer bits for qr code version x
static constexpr uint8_t s_remainer_bits[] {
0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0,
};
// s_alignment_coords[x - 1] tells the coordinates of alignment patterns in version x qr code
static constexpr size_t s_alignment_coords[][8] {
{ 0 },
@ -221,49 +260,6 @@ static constexpr size_t s_alignment_coords[][8] {
{ 6, 30, 58, 86, 114, 142, 170, 0 },
};
static constexpr std::vector<uint8_t> get_generator(size_t ec_codewords)
{
constexpr auto poly_mul =
[](std::span<const uint8_t> p, std::span<const uint8_t> q) -> std::vector<uint8_t>
{
std::vector<uint8_t> result(p.size() + q.size() - 1, 0);
for (size_t i = 0; i < p.size(); i++)
{
if (p[i] == 0)
continue;
for (size_t j = 0; j < q.size(); j++)
result[i + j] ^= s_gf256.mult(p[i], q[j]);
}
return result;
};
std::vector<uint8_t> generator { 1 };
for (size_t i = 0; i < ec_codewords; i++)
{
const uint8_t term[] { s_gf256.exp[i], 1 };
generator = poly_mul(generator, term);
}
std::reverse(generator.begin(), generator.end());
return generator;
}
static constexpr std::vector<uint8_t> get_remainder(std::span<const uint8_t> message, std::span<const uint8_t> generator)
{
std::vector<uint8_t> dividend(message.begin(), message.end());
dividend.resize(dividend.size() + generator.size() - 1, 0);
while (dividend.size() >= generator.size())
{
if (const uint8_t scale = dividend[0])
for (size_t i = 0; i < generator.size(); i++)
dividend[i] ^= s_gf256.mult(generator[i], scale);
dividend.erase(dividend.begin());
}
return dividend;
}
enum class ErrorCorrection { L, M, Q, H };
struct QRInfo
@ -273,6 +269,102 @@ struct QRInfo
BitStream bits;
};
class QRCode
{
public:
static QRCode create(size_t size)
{
const size_t bytes = (size * size + 7) / 8;
uint8_t* data = new uint8_t[bytes];
assert(data);
memset(data, 0, bytes);
return QRCode(size, data);
}
QRCode copy() const
{
const size_t bytes = (m_size * m_size + 7) / 8;
uint8_t* data = new uint8_t[bytes];
assert(data);
memcpy(data, m_data, bytes);
return QRCode(m_size, data);
}
QRCode(QRCode&& other)
: m_size(other.m_size)
, m_data(other.m_data)
{
other.m_data = nullptr;
}
~QRCode()
{
if (m_data != nullptr)
delete[] m_data;
m_data = nullptr;
}
void set(size_t x, size_t y, bool value)
{
assert(x < m_size && y < m_size);
const size_t index = y * m_size + x;
const size_t byte = index / 8;
const size_t bit = index % 8;
if (value)
m_data[byte] |= 1 << bit;
else
m_data[byte] &= ~(1 << bit);
}
void toggle(size_t x, size_t y)
{
assert(x < m_size && y < m_size);
const size_t index = y * m_size + x;
const size_t byte = index / 8;
const size_t bit = index % 8;
m_data[byte] ^= 1 << bit;
}
bool get(size_t x, size_t y) const
{
assert(x < m_size && y < m_size);
const size_t index = y * m_size + x;
const size_t byte = index / 8;
const size_t bit = index % 8;
return (m_data[byte] >> bit) & 1;
}
size_t size() const
{
return m_size;
}
private:
QRCode(size_t size, uint8_t* data)
: m_size(size)
, m_data(data)
{ }
QRCode(const QRCode&) = delete;
QRCode& operator=(const QRCode&) = delete;
QRCode& operator=(QRCode&&) = delete;
const size_t m_size;
uint8_t* m_data;
};
static QRInfo generate_data(std::span<const uint8_t> data, ErrorCorrection error_correction)
{
QRInfo qr_info;
@ -358,49 +450,44 @@ static QRInfo generate_data(std::span<const uint8_t> data, ErrorCorrection error
// update returned info and append required remainder bits
qr_info.bits.data = std::move(interleaved);
qr_info.bits.length = qr_info.bits.data.size() * 8;
qr_info.bits.append(0, s_remainer_bits[qr_info.version - 1]);
constexpr uint8_t remainer_bits[] {
0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0,
};
qr_info.bits.append(0, remainer_bits[qr_info.version - 1]);
return qr_info;
}
static std::pair<std::vector<std::vector<bool>>, std::vector<std::vector<bool>>> prepare_matrix(uint8_t version)
static std::pair<QRCode, QRCode> prepare_matrix(uint8_t version)
{
const size_t size = (version - 1) * 4 + 21;
const auto resize_matrix =
[size](auto& matrix) -> void
{
matrix.resize(size);
for (auto& row : matrix)
row.resize(size, 0);
};
std::vector<std::vector<bool>> matrix, reserved;
resize_matrix(matrix);
resize_matrix(reserved);
auto qr_code = QRCode::create(size);
auto reserved = QRCode::create(size);
// finder patterns
{
const auto place_finder =
[&matrix, &reserved](size_t x, size_t y)
[&qr_code, &reserved](size_t x, size_t y)
{
for (size_t i = 0; i < 7; i++)
{
matrix[y + i][x ] = true;
matrix[y ][x + i] = true;
matrix[y + i][x + 6] = true;
matrix[y + 6][x + i] = true;
qr_code.set(x, y + i, true);
qr_code.set(x + i, y, true);
qr_code.set(x + 6, y + i, true);
qr_code.set(x + i, y + 6, true);
}
for (size_t i = 0; i < 3; i++)
for (size_t j = 0; j < 3; j++)
matrix[y + i + 2][x + j + 2] = true;
qr_code.set(x + j + 2, y + i + 2, true);
if (x) x--;
if (y) y--;
for (size_t i = 0; i < 8; i++)
for (size_t j = 0; j < 8; j++)
reserved[y + i][x + j] = true;
reserved.set(x + j, y + i, true);
};
place_finder(0, 0);
@ -411,25 +498,25 @@ static std::pair<std::vector<std::vector<bool>>, std::vector<std::vector<bool>>>
// alignment patterns
{
const auto place_alignment =
[&matrix, &reserved](size_t x, size_t y)
[&qr_code, &reserved](size_t x, size_t y)
{
for (ssize_t i = -2; i <= 2; i++)
for (ssize_t j = -2; j <= 2; j++)
if (reserved[y + i][x + j])
if (reserved.get(x + j, y + i))
return;
matrix[y][x] = true;
qr_code.set(x, y, true);
for (ssize_t i = -2; i <= 2; i++)
{
matrix[y - 2][x + i] = true;
matrix[y + 2][x + i] = true;
matrix[y + i][x - 2] = true;
matrix[y + i][x + 2] = true;
qr_code.set(x + i, y - 2, true);
qr_code.set(x + i, y + 2, true);
qr_code.set(x - 2, y + i, true);
qr_code.set(x + 2, y + i, true);
}
for (ssize_t i = -2; i <= 2; i++)
for (ssize_t j = -2; j <= 2; j++)
reserved[y + i][x + j] = true;
reserved.set(x + j, y + i, true);
};
const auto& coords = s_alignment_coords[version - 1];
@ -443,24 +530,26 @@ static std::pair<std::vector<std::vector<bool>>, std::vector<std::vector<bool>>>
bool toggle = true;
for (size_t i = 8; i < size - 8; i++)
{
matrix[6][i] = matrix[i][6] = toggle;
qr_code.set(i, 6, toggle);
qr_code.set(6, i, toggle);
toggle = !toggle;
reserved[6][i] = reserved[i][6] = true;
reserved.set(i, 6, true);
reserved.set(6, i, true);
}
}
// dark module and format information area
{
matrix[size - 8][8] = true;
qr_code.set(8, size - 8, true);
reserved[8][8] = true;
reserved.set(8, 8, true);
for (size_t i = 0; i < 8; i++)
{
reserved[8][i] = true;
reserved[i][8] = true;
reserved[size - 8 + i][8] = true;
reserved[8][size - 8 + i] = true;
reserved.set(i, 8, true);
reserved.set(8, i, true);
reserved.set(8, size - 8 + i, true);
reserved.set(size - 8 + i, 8, true);
}
}
@ -471,16 +560,16 @@ static std::pair<std::vector<std::vector<bool>>, std::vector<std::vector<bool>>>
{
for (size_t j = 0; j < 3; j++)
{
reserved[size - 11 + j][i] = true;
reserved[i][size - 11 + j] = true;
reserved.set(i, size - 11 + j, true);
reserved.set(size - 11 + j, i, true);
}
}
}
return std::make_pair(std::move(matrix), std::move(reserved));
return std::make_pair(std::move(qr_code), std::move(reserved));
}
static size_t evaluate_qr_code(const std::vector<std::vector<bool>>& qr_code)
static size_t evaluate_qr_code(const QRCode& qr_code)
{
const size_t size = qr_code.size();
@ -493,7 +582,7 @@ static size_t evaluate_qr_code(const std::vector<std::vector<bool>>& qr_code)
for (size_t x = 0; x < size;)
{
size_t consecutive = 1;
while (x + consecutive + 1 < size && qr_code[y][x] == qr_code[y][x + consecutive + 1])
while (x + consecutive + 1 < size && qr_code.get(x, y) == qr_code.get(x + consecutive + 1, y))
consecutive++;
if (consecutive >= 5)
score += consecutive - 2;
@ -506,7 +595,7 @@ static size_t evaluate_qr_code(const std::vector<std::vector<bool>>& qr_code)
for (size_t y = 0; y < size;)
{
size_t consecutive = 1;
while (y + consecutive + 1 < size && qr_code[y][x] == qr_code[y + consecutive + 1][x])
while (y + consecutive + 1 < size && qr_code.get(x, y) == qr_code.get(x, y + consecutive + 1))
consecutive++;
if (consecutive >= 5)
score += consecutive - 2;
@ -521,11 +610,11 @@ static size_t evaluate_qr_code(const std::vector<std::vector<bool>>& qr_code)
{
for (size_t x = 0; x < size - 1; x++)
{
if (qr_code[y][x] != qr_code[y][x + 1])
if (qr_code.get(x, y) != qr_code.get(x + 1, y))
continue;
if (qr_code[y][x] != qr_code[y + 1][x])
if (qr_code.get(x, y) != qr_code.get(x, y + 1))
continue;
if (qr_code[y][x] != qr_code[y + 1][x + 1])
if (qr_code.get(x, y) != qr_code.get(x + 1, y + 1))
continue;
score += 3;
}
@ -547,7 +636,7 @@ static size_t evaluate_qr_code(const std::vector<std::vector<bool>>& qr_code)
{
bool match = true;
for (size_t i = 0; i < 11 && match; i++)
if (qr_code[y][x + i] != target[i])
if (qr_code.get(x + 1, y) != target[i])
match = false;
if (match)
score += 40;
@ -563,7 +652,7 @@ static size_t evaluate_qr_code(const std::vector<std::vector<bool>>& qr_code)
{
bool match = true;
for (size_t i = 0; i < 11 && match; i++)
if (qr_code[y + i][x] != target[i])
if (qr_code.get(x, y + i) != target[i])
match = false;
if (match)
score += 40;
@ -575,9 +664,9 @@ static size_t evaluate_qr_code(const std::vector<std::vector<bool>>& qr_code)
// condition 4
{
size_t dark_modules = 0;
for (const auto& row : qr_code)
for (bool module : row)
dark_modules += module;
for (size_t y = 0; y < qr_code.size(); y++)
for (size_t x = 0; x < qr_code.size(); x++)
dark_modules += qr_code.get(x, y);
const size_t ratio = 100 * dark_modules / (size * size * 5);
@ -589,19 +678,19 @@ static size_t evaluate_qr_code(const std::vector<std::vector<bool>>& qr_code)
return score;
}
static uint8_t apply_mask_pattern(std::vector<std::vector<bool>>& matrix, const std::vector<std::vector<bool>>& reserved)
static uint8_t apply_mask_pattern(QRCode& qr_code, const QRCode& reserved)
{
const size_t size = matrix.size();
const size_t size = qr_code.size();
bool (*mask_pattern_funcs[])(size_t, size_t) {
[](size_t r, size_t c) { return (r + c) % 2 == 0; },
[](size_t r, size_t ) { return r % 2 == 0; },
[](size_t , size_t c) { return c % 3 == 0; },
[](size_t r, size_t c) { return (r + c) % 3 == 0; },
[](size_t r, size_t c) { return (r / 2 + c / 3) % 2 == 0; },
[](size_t r, size_t c) { return (r * c) % 2 + (r * c) % 3 == 0; },
[](size_t r, size_t c) { return ((r * c) % 3 + r * c) % 2 == 0; },
[](size_t r, size_t c) { return ((r * c) % 3 + r + c) % 2 == 0; },
[](size_t c, size_t r) { return (r + c) % 2 == 0; },
[](size_t , size_t r) { return r % 2 == 0; },
[](size_t c, size_t ) { return c % 3 == 0; },
[](size_t c, size_t r) { return (r + c) % 3 == 0; },
[](size_t c, size_t r) { return (r / 2 + c / 3) % 2 == 0; },
[](size_t c, size_t r) { return (r * c) % 2 + (r * c) % 3 == 0; },
[](size_t c, size_t r) { return ((r * c) % 3 + r * c) % 2 == 0; },
[](size_t c, size_t r) { return ((r * c) % 3 + r + c) % 2 == 0; },
};
size_t best_pattern = 0;
@ -609,12 +698,12 @@ static uint8_t apply_mask_pattern(std::vector<std::vector<bool>>& matrix, const
for (size_t i = 0; i < sizeof(mask_pattern_funcs) / sizeof(*mask_pattern_funcs); i++)
{
auto temp = matrix;
auto temp = qr_code.copy();
for (size_t y = 0; y < size; y++)
for (size_t x = 0; x < size; x++)
if (!reserved[y][x] && mask_pattern_funcs[i](y, x))
temp[y][x] = !temp[y][x];
if (!reserved.get(x, y) && mask_pattern_funcs[i](x, y))
temp.toggle(x, y);
if (const size_t score = evaluate_qr_code(temp); score < best_score)
{
@ -625,18 +714,18 @@ static uint8_t apply_mask_pattern(std::vector<std::vector<bool>>& matrix, const
for (size_t y = 0; y < size; y++)
for (size_t x = 0; x < size; x++)
if (!reserved[y][x] && mask_pattern_funcs[best_pattern](y, x))
matrix[y][x] = !matrix[y][x];
if (!reserved.get(x, y) && mask_pattern_funcs[best_pattern](x, y))
qr_code.toggle(x, y);
return best_pattern;
}
std::vector<std::vector<bool>> generate_qr_code(std::string_view data, ErrorCorrection ec)
QRCode generate_qr_code(std::string_view data, ErrorCorrection ec)
{
const auto qr_code = generate_data({ reinterpret_cast<const uint8_t*>(data.data()), data.size() }, ec);
const auto qr_info = generate_data({ reinterpret_cast<const uint8_t*>(data.data()), data.size() }, ec);
auto [matrix, reserved] = prepare_matrix(qr_code.version);
const size_t size = matrix.size();
auto [qr_code, reserved] = prepare_matrix(qr_info.version);
const size_t size = qr_code.size();
{
size_t index = 0;
@ -657,14 +746,14 @@ std::vector<std::vector<bool>> generate_qr_code(std::string_view data, ErrorCorr
for (ssize_t y = y_s; y != y_e; y += dir)
{
if (!reserved[y][x + 1])
matrix[y][x + 1] = qr_code.bits[index++];
if (!reserved[y][x + 0])
matrix[y][x + 0] = qr_code.bits[index++];
if (!reserved.get(x + 1, y))
qr_code.set(x + 1, y, qr_info.bits[index++]);
if (!reserved.get(x + 0, y))
qr_code.set(x + 0, y, qr_info.bits[index++]);
}
}
assert(index == qr_code.bits.length);
assert(index == qr_info.bits.length);
}
const auto mod2_remainder =
@ -678,62 +767,62 @@ std::vector<std::vector<bool>> generate_qr_code(std::string_view data, ErrorCorr
// format string
{
const uint8_t pattern = apply_mask_pattern(matrix, reserved);
const uint8_t pattern = apply_mask_pattern(qr_code, reserved);
const uint8_t ec_to_val[] { 1, 0, 3, 2 };
const uint16_t format_data = (ec_to_val[static_cast<size_t>(qr_code.error_correction)] << 13) | (pattern << 10);
const uint16_t format_data = (ec_to_val[static_cast<size_t>(qr_info.error_correction)] << 13) | (pattern << 10);
const uint16_t format_string = (format_data | mod2_remainder(format_data, 0b10100110111, 10)) ^ 0b101010000010010;
matrix[8][0] = (format_string >> 14) & 1;
matrix[8][1] = (format_string >> 13) & 1;
matrix[8][2] = (format_string >> 12) & 1;
matrix[8][3] = (format_string >> 11) & 1;
matrix[8][4] = (format_string >> 10) & 1;
matrix[8][5] = (format_string >> 9) & 1;
matrix[8][7] = (format_string >> 8) & 1;
matrix[8][8] = (format_string >> 7) & 1;
matrix[7][8] = (format_string >> 6) & 1;
matrix[5][8] = (format_string >> 5) & 1;
matrix[4][8] = (format_string >> 4) & 1;
matrix[3][8] = (format_string >> 3) & 1;
matrix[2][8] = (format_string >> 2) & 1;
matrix[1][8] = (format_string >> 1) & 1;
matrix[0][8] = (format_string >> 0) & 1;
qr_code.set(0, 8, (format_string >> 14) & 1);
qr_code.set(1, 8, (format_string >> 13) & 1);
qr_code.set(2, 8, (format_string >> 12) & 1);
qr_code.set(3, 8, (format_string >> 11) & 1);
qr_code.set(4, 8, (format_string >> 10) & 1);
qr_code.set(5, 8, (format_string >> 9) & 1);
qr_code.set(7, 8, (format_string >> 8) & 1);
qr_code.set(8, 8, (format_string >> 7) & 1);
qr_code.set(8, 7, (format_string >> 6) & 1);
qr_code.set(8, 5, (format_string >> 5) & 1);
qr_code.set(8, 4, (format_string >> 4) & 1);
qr_code.set(8, 3, (format_string >> 3) & 1);
qr_code.set(8, 2, (format_string >> 2) & 1);
qr_code.set(8, 1, (format_string >> 1) & 1);
qr_code.set(8, 0, (format_string >> 0) & 1);
matrix[size - 1][8] = (format_string >> 14) & 1;
matrix[size - 2][8] = (format_string >> 13) & 1;
matrix[size - 3][8] = (format_string >> 12) & 1;
matrix[size - 4][8] = (format_string >> 11) & 1;
matrix[size - 5][8] = (format_string >> 10) & 1;
matrix[size - 6][8] = (format_string >> 9) & 1;
matrix[size - 7][8] = (format_string >> 8) & 1;
matrix[8][size - 8] = (format_string >> 7) & 1;
matrix[8][size - 7] = (format_string >> 6) & 1;
matrix[8][size - 6] = (format_string >> 5) & 1;
matrix[8][size - 5] = (format_string >> 4) & 1;
matrix[8][size - 4] = (format_string >> 3) & 1;
matrix[8][size - 3] = (format_string >> 2) & 1;
matrix[8][size - 2] = (format_string >> 1) & 1;
matrix[8][size - 1] = (format_string >> 0) & 1;
qr_code.set(8, size - 1, (format_string >> 14) & 1);
qr_code.set(8, size - 2, (format_string >> 13) & 1);
qr_code.set(8, size - 3, (format_string >> 12) & 1);
qr_code.set(8, size - 4, (format_string >> 11) & 1);
qr_code.set(8, size - 5, (format_string >> 10) & 1);
qr_code.set(8, size - 6, (format_string >> 9) & 1);
qr_code.set(8, size - 7, (format_string >> 8) & 1);
qr_code.set(size - 8, 8, (format_string >> 7) & 1);
qr_code.set(size - 7, 8, (format_string >> 6) & 1);
qr_code.set(size - 6, 8, (format_string >> 5) & 1);
qr_code.set(size - 5, 8, (format_string >> 4) & 1);
qr_code.set(size - 4, 8, (format_string >> 3) & 1);
qr_code.set(size - 3, 8, (format_string >> 2) & 1);
qr_code.set(size - 2, 8, (format_string >> 1) & 1);
qr_code.set(size - 1, 8, (format_string >> 0) & 1);
}
// version string
if (qr_code.version >= 7)
if (qr_info.version >= 7)
{
const uint32_t version_data = qr_code.version << 12;
const uint32_t version_data = qr_info.version << 12;
const uint32_t version_string = (version_data | mod2_remainder(version_data, 0b1111100100101, 12));
for (size_t i = 0; i < 6; i++)
{
for (size_t j = 0; j < 3; j++)
{
matrix[size - 11 + j][i] = (version_string >> (i * 3 + j)) & 1;
matrix[i][size - 11 + j] = (version_string >> (i * 3 + j)) & 1;
qr_code.set(i, size - 11 + j, (version_string >> (i * 3 + j)) & 1);
qr_code.set(size - 11 + j, i, (version_string >> (i * 3 + j)) & 1);
}
}
}
return matrix;
return std::move(qr_code);
}
int main()
@ -745,15 +834,18 @@ int main()
std::cout << "██";
std::cout << '\n';
}
for (const auto& row : qr_code) {
for (size_t y = 0; y < qr_code.size(); y++)
{
for (int i = 0; i < 4; i++)
std::cout << "██";
for (bool val : row)
std::cout << (val ? " " : "██");
for (size_t x = 0; x < qr_code.size(); x++)
std::cout << (qr_code.get(x, y) ? " " : "██");
for (int i = 0; i < 4; i++)
std::cout << "██";
std::cout << '\n';
}
for (int i = 0; i < 4; i++) {
for (size_t i = 0; i < qr_code.size() + 8; i++)
std::cout << "██";