diff --git a/main.cpp b/main.cpp index 505b0a3..2789393 100644 --- a/main.cpp +++ b/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -77,6 +78,49 @@ struct GF256 }; static constexpr GF256 s_gf256; +static constexpr std::vector get_generator(size_t ec_codewords) +{ + constexpr auto poly_mul = + [](std::span p, std::span q) -> std::vector + { + std::vector 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 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 get_remainder(std::span message, std::span generator) +{ + std::vector 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 get_generator(size_t ec_codewords) -{ - constexpr auto poly_mul = - [](std::span p, std::span q) -> std::vector - { - std::vector 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 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 get_remainder(std::span message, std::span generator) -{ - std::vector 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 data, ErrorCorrection error_correction) { QRInfo qr_info; @@ -358,49 +450,44 @@ static QRInfo generate_data(std::span 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>> prepare_matrix(uint8_t version) +static std::pair 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> 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>> // 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>> 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>> { 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>& 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>& 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>& 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>& 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>& 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>& 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>& 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>& qr_code) return score; } -static uint8_t apply_mask_pattern(std::vector>& matrix, const std::vector>& 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>& 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>& 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> 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(data.data()), data.size() }, ec); + const auto qr_info = generate_data({ reinterpret_cast(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; @@ -656,15 +745,15 @@ std::vector> generate_qr_code(std::string_view data, ErrorCorr toggle = !toggle; 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> 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(qr_code.error_correction)] << 13) | (pattern << 10); + const uint16_t format_data = (ec_to_val[static_cast(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 << "██";