Implement basic qr code generator
This commit is contained in:
commit
840e9769fa
|
|
@ -0,0 +1,25 @@
|
|||
BUILD=build
|
||||
OUT=qrgen
|
||||
|
||||
CXXFLAGS=-O3 -std=c++20 -Wall -Wextra
|
||||
LDFLAGS=
|
||||
|
||||
SRCS=$(wildcard *.cpp)
|
||||
OBJS=$(addprefix $(BUILD)/,$(addsuffix .o,$(SRCS)))
|
||||
|
||||
.PHONY: all run clean
|
||||
|
||||
all: $(OUT)
|
||||
|
||||
run: $(OUT)
|
||||
@./$(OUT)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD) $(OUT)
|
||||
|
||||
$(OUT): $(OBJS)
|
||||
$(CXX) $(LDFLAGS) $(OBJS) -o $@
|
||||
|
||||
$(BUILD)/%.cpp.o: %.cpp Makefile
|
||||
@mkdir -p $(@D)
|
||||
$(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@
|
||||
|
|
@ -0,0 +1,762 @@
|
|||
// written based on https://www.thonky.com/qr-code-tutorial/ and https://tomverbeure.github.io/2022/08/07/Reed-Solomon.html
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <sys/types.h>
|
||||
#include <vector>
|
||||
|
||||
struct BitStream
|
||||
{
|
||||
void append(uint32_t value, size_t bits)
|
||||
{
|
||||
for (size_t i = bits; i > 0; i--)
|
||||
{
|
||||
if ((length % 8) == 0)
|
||||
data.emplace_back(0);
|
||||
|
||||
data.back() <<= 1;
|
||||
if ((value >> (i - 1)) & 1)
|
||||
data.back() |= 1;
|
||||
|
||||
length++;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator[](size_t index) const
|
||||
{
|
||||
assert(index < length);
|
||||
|
||||
const size_t byte = index / 8;
|
||||
const size_t bit = index % 8;
|
||||
|
||||
const size_t bits = std::min<size_t>(8, length - byte * 8);
|
||||
|
||||
return !!((data[byte] >> (bits - bit - 1)) & 1);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
size_t length { 0 };
|
||||
};
|
||||
|
||||
struct GF256
|
||||
{
|
||||
consteval GF256()
|
||||
{
|
||||
uint8_t x = 1;
|
||||
|
||||
for (size_t i = 0; i < 256; i++)
|
||||
{
|
||||
log[x] = i;
|
||||
exp[i] = x;
|
||||
|
||||
const uint16_t next = x << 1;
|
||||
x = (next < 256) ? next : next ^ 285;
|
||||
}
|
||||
|
||||
for (size_t i = 255; i < 512; i++)
|
||||
exp[i] = exp[i - 255];
|
||||
|
||||
log[0] = -1;
|
||||
log[1] = 0;
|
||||
}
|
||||
|
||||
constexpr uint8_t mult(uint8_t a, uint8_t b) const
|
||||
{
|
||||
if (a == 0 || b == 0)
|
||||
return 0;
|
||||
return exp[log[a] + log[b]];
|
||||
}
|
||||
|
||||
uint8_t exp[512];
|
||||
uint8_t log[256];
|
||||
};
|
||||
static constexpr GF256 s_gf256;
|
||||
|
||||
// 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 },
|
||||
{ 32, 26, 20, 14 },
|
||||
{ 53, 42, 32, 24 },
|
||||
{ 78, 62, 46, 34 },
|
||||
{ 106, 84, 60, 44 },
|
||||
{ 134, 106, 74, 58 },
|
||||
{ 154, 122, 86, 64 },
|
||||
{ 192, 152, 108, 84 },
|
||||
{ 230, 180, 130, 98 },
|
||||
{ 271, 213, 151, 119 },
|
||||
{ 321, 251, 177, 137 },
|
||||
{ 367, 287, 203, 155 },
|
||||
{ 425, 331, 241, 177 },
|
||||
{ 458, 362, 258, 194 },
|
||||
{ 520, 412, 292, 220 },
|
||||
{ 586, 450, 322, 250 },
|
||||
{ 644, 504, 364, 280 },
|
||||
{ 718, 560, 394, 310 },
|
||||
{ 792, 624, 442, 338 },
|
||||
{ 858, 666, 482, 382 },
|
||||
{ 929, 711, 509, 403 },
|
||||
{ 1003, 779, 565, 439 },
|
||||
{ 1091, 857, 611, 461 },
|
||||
{ 1171, 911, 661, 511 },
|
||||
{ 1273, 997, 715, 535 },
|
||||
{ 1367, 1059, 751, 593 },
|
||||
{ 1465, 1125, 805, 625 },
|
||||
{ 1528, 1190, 868, 658 },
|
||||
{ 1628, 1264, 908, 698 },
|
||||
{ 1732, 1370, 982, 742 },
|
||||
{ 1840, 1452, 1030, 790 },
|
||||
{ 1952, 1538, 1112, 842 },
|
||||
{ 2068, 1628, 1168, 898 },
|
||||
{ 2188, 1722, 1228, 958 },
|
||||
{ 2303, 1809, 1283, 983 },
|
||||
{ 2431, 1911, 1351, 1051 },
|
||||
{ 2563, 1989, 1423, 1093 },
|
||||
{ 2699, 2099, 1499, 1139 },
|
||||
{ 2809, 2213, 1579, 1219 },
|
||||
{ 2953, 2331, 1663, 1273 },
|
||||
};
|
||||
|
||||
/* s_ec_block_info[x - 1][y] describes qr code with version x and error correction y
|
||||
elements:
|
||||
- ec codewords per block
|
||||
- number of blocks in group 1
|
||||
- number of data codewords in group 1 blocks
|
||||
- number of blocks in group 2
|
||||
- number of data codewords in group 2 blocks
|
||||
*/
|
||||
static constexpr uint8_t s_ec_block_info[][4][5] {
|
||||
{ { 7, 1, 19, 0, 0 }, { 10, 1, 16, 0, 0 }, { 13, 1, 13, 0, 0 }, { 17, 1, 9, 0, 0 }, },
|
||||
{ { 10, 1, 34, 0, 0 }, { 16, 1, 28, 0, 0 }, { 22, 1, 22, 0, 0 }, { 28, 1, 16, 0, 0 }, },
|
||||
{ { 15, 1, 55, 0, 0 }, { 26, 1, 44, 0, 0 }, { 18, 2, 17, 0, 0 }, { 22, 2, 13, 0, 0 }, },
|
||||
{ { 20, 1, 80, 0, 0 }, { 18, 2, 32, 0, 0 }, { 26, 2, 24, 0, 0 }, { 16, 4, 9, 0, 0 }, },
|
||||
{ { 26, 1, 108, 0, 0 }, { 24, 2, 43, 0, 0 }, { 18, 2, 15, 2, 16 }, { 22, 2, 11, 2, 12 }, },
|
||||
{ { 18, 2, 68, 0, 0 }, { 16, 4, 27, 0, 0 }, { 24, 4, 19, 0, 0 }, { 28, 4, 15, 0, 0 }, },
|
||||
{ { 20, 2, 78, 0, 0 }, { 18, 4, 31, 0, 0 }, { 18, 2, 14, 4, 15 }, { 26, 4, 13, 1, 14 }, },
|
||||
{ { 24, 2, 97, 0, 0 }, { 22, 2, 38, 2, 39 }, { 22, 4, 18, 2, 19 }, { 26, 4, 14, 2, 15 }, },
|
||||
{ { 30, 2, 116, 0, 0 }, { 22, 3, 36, 2, 37 }, { 20, 4, 16, 4, 17 }, { 24, 4, 12, 4, 13 }, },
|
||||
{ { 18, 2, 68, 2, 69 }, { 26, 4, 43, 1, 44 }, { 24, 6, 19, 2, 20 }, { 28, 6, 15, 2, 16 }, },
|
||||
{ { 20, 4, 81, 0, 0 }, { 30, 1, 50, 4, 51 }, { 28, 4, 22, 4, 23 }, { 24, 3, 12, 8, 13 }, },
|
||||
{ { 24, 2, 92, 2, 93 }, { 22, 6, 36, 2, 37 }, { 26, 4, 20, 6, 21 }, { 28, 7, 14, 4, 15 }, },
|
||||
{ { 26, 4, 107, 0, 0 }, { 22, 8, 37, 1, 38 }, { 24, 8, 20, 4, 21 }, { 22, 12, 11, 4, 12 }, },
|
||||
{ { 30, 3, 115, 1, 116 }, { 24, 4, 40, 5, 41 }, { 20, 11, 16, 5, 17 }, { 24, 11, 12, 5, 13 }, },
|
||||
{ { 22, 5, 87, 1, 88 }, { 24, 5, 41, 5, 42 }, { 30, 5, 24, 7, 25 }, { 24, 11, 12, 7, 13 }, },
|
||||
{ { 24, 5, 98, 1, 99 }, { 28, 7, 45, 3, 46 }, { 24, 15, 19, 2, 20 }, { 30, 3, 15, 13, 16 }, },
|
||||
{ { 28, 1, 107, 5, 108 }, { 28, 10, 46, 1, 47 }, { 28, 1, 22, 15, 23 }, { 28, 2, 14, 17, 15 }, },
|
||||
{ { 30, 5, 120, 1, 121 }, { 26, 9, 43, 4, 44 }, { 28, 17, 22, 1, 23 }, { 28, 2, 14, 19, 15 }, },
|
||||
{ { 28, 3, 113, 4, 114 }, { 26, 3, 44, 11, 45 }, { 26, 17, 21, 4, 22 }, { 26, 9, 13, 16, 14 }, },
|
||||
{ { 28, 3, 107, 5, 108 }, { 26, 3, 41, 13, 42 }, { 30, 15, 24, 5, 25 }, { 28, 15, 15, 10, 16 }, },
|
||||
{ { 28, 4, 116, 4, 117 }, { 26, 17, 42, 0, 0 }, { 28, 17, 22, 6, 23 }, { 30, 19, 16, 6, 17 }, },
|
||||
{ { 28, 2, 111, 7, 112 }, { 28, 17, 46, 0, 0 }, { 30, 7, 24, 16, 25 }, { 24, 34, 13, 0, 0 }, },
|
||||
{ { 30, 4, 121, 5, 122 }, { 28, 4, 47, 14, 48 }, { 30, 11, 24, 14, 25 }, { 30, 16, 15, 14, 16 }, },
|
||||
{ { 30, 6, 117, 4, 118 }, { 28, 6, 45, 14, 46 }, { 30, 11, 24, 16, 25 }, { 30, 30, 16, 2, 17 }, },
|
||||
{ { 26, 8, 106, 4, 107 }, { 28, 8, 47, 13, 48 }, { 30, 7, 24, 22, 25 }, { 30, 22, 15, 13, 16 }, },
|
||||
{ { 28, 10, 114, 2, 115 }, { 28, 19, 46, 4, 47 }, { 28, 28, 22, 6, 23 }, { 30, 33, 16, 4, 17 }, },
|
||||
{ { 30, 8, 122, 4, 123 }, { 28, 22, 45, 3, 46 }, { 30, 8, 23, 26, 24 }, { 30, 12, 15, 28, 16 }, },
|
||||
{ { 30, 3, 117, 10, 118 }, { 28, 3, 45, 23, 46 }, { 30, 4, 24, 31, 25 }, { 30, 11, 15, 31, 16 }, },
|
||||
{ { 30, 7, 116, 7, 117 }, { 28, 21, 45, 7, 46 }, { 30, 1, 23, 37, 24 }, { 30, 19, 15, 26, 16 }, },
|
||||
{ { 30, 5, 115, 10, 116 }, { 28, 19, 47, 10, 48 }, { 30, 15, 24, 25, 25 }, { 30, 23, 15, 25, 16 }, },
|
||||
{ { 30, 13, 115, 3, 116 }, { 28, 2, 46, 29, 47 }, { 30, 42, 24, 1, 25 }, { 30, 23, 15, 28, 16 }, },
|
||||
{ { 30, 17, 115, 0, 0 }, { 28, 10, 46, 23, 47 }, { 30, 10, 24, 35, 25 }, { 30, 19, 15, 35, 16 }, },
|
||||
{ { 30, 17, 115, 1, 116 }, { 28, 14, 46, 21, 47 }, { 30, 29, 24, 19, 25 }, { 30, 11, 15, 46, 16 }, },
|
||||
{ { 30, 13, 115, 6, 116 }, { 28, 14, 46, 23, 47 }, { 30, 44, 24, 7, 25 }, { 30, 59, 16, 1, 17 }, },
|
||||
{ { 30, 12, 121, 7, 122 }, { 28, 12, 47, 26, 48 }, { 30, 39, 24, 14, 25 }, { 30, 22, 15, 41, 16 }, },
|
||||
{ { 30, 6, 121, 14, 122 }, { 28, 6, 47, 34, 48 }, { 30, 46, 24, 10, 25 }, { 30, 2, 15, 64, 16 }, },
|
||||
{ { 30, 17, 122, 4, 123 }, { 28, 29, 46, 14, 47 }, { 30, 49, 24, 10, 25 }, { 30, 24, 15, 46, 16 }, },
|
||||
{ { 30, 4, 122, 18, 123 }, { 28, 13, 46, 32, 47 }, { 30, 48, 24, 14, 25 }, { 30, 42, 15, 32, 16 }, },
|
||||
{ { 30, 20, 117, 4, 118 }, { 28, 40, 47, 7, 48 }, { 30, 43, 24, 22, 25 }, { 30, 10, 15, 67, 16 }, },
|
||||
{ { 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 },
|
||||
{ 6, 18, 0 },
|
||||
{ 6, 22, 0 },
|
||||
{ 6, 26, 0 },
|
||||
{ 6, 30, 0 },
|
||||
{ 6, 34, 0 },
|
||||
{ 6, 22, 38, 0 },
|
||||
{ 6, 24, 42, 0 },
|
||||
{ 6, 26, 46, 0 },
|
||||
{ 6, 28, 50, 0 },
|
||||
{ 6, 30, 54, 0 },
|
||||
{ 6, 32, 58, 0 },
|
||||
{ 6, 34, 62, 0 },
|
||||
{ 6, 26, 46, 66, 0 },
|
||||
{ 6, 26, 48, 70, 0 },
|
||||
{ 6, 26, 50, 74, 0 },
|
||||
{ 6, 30, 54, 78, 0 },
|
||||
{ 6, 30, 56, 82, 0 },
|
||||
{ 6, 30, 58, 86, 0 },
|
||||
{ 6, 34, 62, 90, 0 },
|
||||
{ 6, 28, 50, 72, 94, 0 },
|
||||
{ 6, 26, 50, 74, 98, 0 },
|
||||
{ 6, 30, 54, 78, 102, 0 },
|
||||
{ 6, 28, 54, 80, 106, 0 },
|
||||
{ 6, 32, 58, 84, 110, 0 },
|
||||
{ 6, 30, 58, 86, 114, 0 },
|
||||
{ 6, 34, 62, 90, 118, 0 },
|
||||
{ 6, 26, 50, 74, 98, 122, 0 },
|
||||
{ 6, 30, 54, 78, 102, 126, 0 },
|
||||
{ 6, 26, 52, 78, 104, 130, 0 },
|
||||
{ 6, 30, 56, 82, 108, 134, 0 },
|
||||
{ 6, 34, 60, 86, 112, 138, 0 },
|
||||
{ 6, 30, 58, 86, 114, 142, 0 },
|
||||
{ 6, 34, 62, 90, 118, 146, 0 },
|
||||
{ 6, 30, 54, 78, 102, 126, 150, 0 },
|
||||
{ 6, 24, 50, 76, 102, 128, 154, 0 },
|
||||
{ 6, 28, 54, 80, 106, 132, 158, 0 },
|
||||
{ 6, 32, 58, 84, 110, 136, 162, 0 },
|
||||
{ 6, 26, 54, 82, 110, 138, 166, 0 },
|
||||
{ 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
|
||||
{
|
||||
uint8_t version;
|
||||
ErrorCorrection error_correction;
|
||||
BitStream bits;
|
||||
};
|
||||
|
||||
static QRInfo generate_data(std::span<const uint8_t> data, ErrorCorrection error_correction)
|
||||
{
|
||||
QRInfo qr_info;
|
||||
qr_info.error_correction = error_correction;
|
||||
|
||||
qr_info.version = 0xFF;
|
||||
for (size_t i = 0; i < sizeof(s_qr_capacities) / sizeof(s_qr_capacities[0]); i++)
|
||||
{
|
||||
if (data.size() > s_qr_capacities[i][static_cast<size_t>(error_correction)])
|
||||
continue;
|
||||
qr_info.version = i + 1;
|
||||
break;
|
||||
}
|
||||
assert(qr_info.version != 0xFF);
|
||||
|
||||
// byte mode
|
||||
qr_info.bits.append(0b0100, 4);
|
||||
|
||||
// data length
|
||||
qr_info.bits.append(data.size(), (qr_info.version <= 9) ? 8 : 16);
|
||||
|
||||
// data
|
||||
for (size_t i = 0; i < data.size(); i++)
|
||||
qr_info.bits.append(data[i], 8);
|
||||
|
||||
auto ec_info = s_ec_block_info[qr_info.version - 1][static_cast<size_t>(qr_info.error_correction)];
|
||||
const size_t max_bits = (ec_info[1] * ec_info[2] + ec_info[3] * ec_info[4]) * 8;
|
||||
assert(qr_info.bits.length <= max_bits);
|
||||
|
||||
// terminator
|
||||
if (const size_t missing = max_bits - qr_info.bits.length; missing < 4)
|
||||
qr_info.bits.append(0, missing);
|
||||
else
|
||||
qr_info.bits.append(0, 4);
|
||||
|
||||
// byte align
|
||||
if (const size_t rem = qr_info.bits.length % 8)
|
||||
qr_info.bits.append(0, 8 - rem);
|
||||
|
||||
// add pad bytes
|
||||
for (bool toggle = true; qr_info.bits.length < max_bits; toggle = !toggle)
|
||||
qr_info.bits.append(toggle ? 0b11101100 : 0b00010001, 8);
|
||||
|
||||
std::span<const uint8_t> data_words = qr_info.bits.data;
|
||||
|
||||
// break into data blocks for error correction
|
||||
std::vector<std::span<const uint8_t>> data_blocks;
|
||||
for (size_t group = 0; group < 2; group++)
|
||||
{
|
||||
const size_t nblock = ec_info[group * 2 + 1];
|
||||
const size_t nwords = ec_info[group * 2 + 2];
|
||||
|
||||
for (size_t i = 0; i < nblock; i++)
|
||||
{
|
||||
data_blocks.push_back(data_words.subspan(0, nwords));
|
||||
data_words = data_words.subspan(nwords);
|
||||
}
|
||||
}
|
||||
|
||||
assert(data_words.empty());
|
||||
|
||||
// calculate error blocks
|
||||
const auto generator = get_generator(ec_info[0]);
|
||||
std::vector<std::vector<uint8_t>> ec_blocks;
|
||||
for (const auto& data_block : data_blocks)
|
||||
ec_blocks.emplace_back(get_remainder(data_block, generator));
|
||||
|
||||
// interleave data and error blocks
|
||||
std::vector<uint8_t> interleaved;
|
||||
interleaved.reserve(
|
||||
ec_info[1] * (ec_info[2] + ec_info[0]) +
|
||||
ec_info[3] * (ec_info[4] + ec_info[0])
|
||||
);
|
||||
for (size_t i = 0; i < ec_info[2]; i++)
|
||||
for (size_t j = 0; j < ec_info[1] + ec_info[3]; j++)
|
||||
interleaved.push_back(data_blocks[j][i]);
|
||||
for (size_t j = 0; j < ec_info[3]; j++)
|
||||
interleaved.push_back(data_blocks[ec_info[1] + j][ec_info[2]]);
|
||||
for (size_t i = 0; i < ec_info[0]; i++)
|
||||
for (size_t j = 0; j < ec_blocks.size(); j++)
|
||||
interleaved.push_back(ec_blocks[j][i]);
|
||||
|
||||
// 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]);
|
||||
|
||||
return qr_info;
|
||||
}
|
||||
|
||||
static std::pair<std::vector<std::vector<bool>>, std::vector<std::vector<bool>>> 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);
|
||||
|
||||
// finder patterns
|
||||
{
|
||||
const auto place_finder =
|
||||
[&matrix, &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;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 3; i++)
|
||||
for (size_t j = 0; j < 3; j++)
|
||||
matrix[y + i + 2][x + j + 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;
|
||||
};
|
||||
|
||||
place_finder(0, 0);
|
||||
place_finder(0, size - 7);
|
||||
place_finder(size - 7, 0);
|
||||
}
|
||||
|
||||
// alignment patterns
|
||||
{
|
||||
const auto place_alignment =
|
||||
[&matrix, &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])
|
||||
return;
|
||||
|
||||
matrix[y][x] = 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;
|
||||
}
|
||||
|
||||
for (ssize_t i = -2; i <= 2; i++)
|
||||
for (ssize_t j = -2; j <= 2; j++)
|
||||
reserved[y + i][x + j] = true;
|
||||
};
|
||||
|
||||
const auto& coords = s_alignment_coords[version - 1];
|
||||
for (size_t i = 0; coords[i]; i++)
|
||||
for (size_t j = 0; coords[j]; j++)
|
||||
place_alignment(coords[i], coords[j]);
|
||||
}
|
||||
|
||||
// timing patterns
|
||||
{
|
||||
bool toggle = true;
|
||||
for (size_t i = 8; i < size - 8; i++)
|
||||
{
|
||||
matrix[6][i] = matrix[i][6] = toggle;
|
||||
toggle = !toggle;
|
||||
|
||||
reserved[6][i] = reserved[i][6] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// dark module and format information area
|
||||
{
|
||||
matrix[size - 8][8] = true;
|
||||
|
||||
reserved[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;
|
||||
}
|
||||
}
|
||||
|
||||
// version information area
|
||||
if (version >= 7)
|
||||
{
|
||||
for (size_t i = 0; i < 6; i++)
|
||||
{
|
||||
for (size_t j = 0; j < 3; j++)
|
||||
{
|
||||
reserved[size - 11 + j][i] = true;
|
||||
reserved[i][size - 11 + j] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(std::move(matrix), std::move(reserved));
|
||||
}
|
||||
|
||||
static size_t evaluate_qr_code(const std::vector<std::vector<bool>>& qr_code)
|
||||
{
|
||||
const size_t size = qr_code.size();
|
||||
|
||||
size_t score = 0;
|
||||
|
||||
// condition 1
|
||||
{
|
||||
for (size_t y = 0; y < size; y++)
|
||||
{
|
||||
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])
|
||||
consecutive++;
|
||||
if (consecutive >= 5)
|
||||
score += consecutive - 2;
|
||||
x += consecutive;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t x = 0; x < size; x++)
|
||||
{
|
||||
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])
|
||||
consecutive++;
|
||||
if (consecutive >= 5)
|
||||
score += consecutive - 2;
|
||||
y += consecutive;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// condition 2
|
||||
{
|
||||
for (size_t y = 0; y < size - 1; y++)
|
||||
{
|
||||
for (size_t x = 0; x < size - 1; x++)
|
||||
{
|
||||
if (qr_code[y][x] != qr_code[y][x + 1])
|
||||
continue;
|
||||
if (qr_code[y][x] != qr_code[y + 1][x])
|
||||
continue;
|
||||
if (qr_code[y][x] != qr_code[y + 1][x + 1])
|
||||
continue;
|
||||
score += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// condition 3
|
||||
{
|
||||
const bool targets[][11] {
|
||||
{ 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1 },
|
||||
};
|
||||
|
||||
for (size_t y = 0; y < size; y++)
|
||||
{
|
||||
for (size_t x = 0; x < size - 11; x++)
|
||||
{
|
||||
for (auto& target : targets)
|
||||
{
|
||||
bool match = true;
|
||||
for (size_t i = 0; i < 11 && match; i++)
|
||||
if (qr_code[y][x + i] != target[i])
|
||||
match = false;
|
||||
if (match)
|
||||
score += 40;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t x = 0; x < size; x++)
|
||||
{
|
||||
for (size_t y = 0; y < size - 11; y++)
|
||||
{
|
||||
for (auto& target : targets)
|
||||
{
|
||||
bool match = true;
|
||||
for (size_t i = 0; i < 11 && match; i++)
|
||||
if (qr_code[y + i][x] != target[i])
|
||||
match = false;
|
||||
if (match)
|
||||
score += 40;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// condition 4
|
||||
{
|
||||
size_t dark_modules = 0;
|
||||
for (const auto& row : qr_code)
|
||||
for (bool module : row)
|
||||
dark_modules += module;
|
||||
|
||||
const size_t ratio = 100 * dark_modules / (size * size * 5);
|
||||
|
||||
const size_t temp1 = std::max<size_t>(ratio, 10) - std::min<size_t>(ratio, 10);
|
||||
const size_t temp2 = std::max<size_t>(ratio + 1, 10) - std::min<size_t>(ratio + 1, 10);
|
||||
score += std::min<size_t>(temp1, temp2) * 10;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
static uint8_t apply_mask_pattern(std::vector<std::vector<bool>>& matrix, const std::vector<std::vector<bool>>& reserved)
|
||||
{
|
||||
const size_t size = matrix.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 best_pattern = 0;
|
||||
size_t best_score = SIZE_MAX;
|
||||
|
||||
for (size_t i = 0; i < sizeof(mask_pattern_funcs) / sizeof(*mask_pattern_funcs); i++)
|
||||
{
|
||||
auto temp = matrix;
|
||||
|
||||
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 (const size_t score = evaluate_qr_code(temp); score < best_score)
|
||||
{
|
||||
best_pattern = i;
|
||||
best_score = score;
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
return best_pattern;
|
||||
}
|
||||
|
||||
std::vector<std::vector<bool>> 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);
|
||||
|
||||
auto [matrix, reserved] = prepare_matrix(qr_code.version);
|
||||
const size_t size = matrix.size();
|
||||
|
||||
{
|
||||
size_t index = 0;
|
||||
|
||||
bool toggle = true;
|
||||
|
||||
size_t x = size;
|
||||
while (x > 0)
|
||||
{
|
||||
x -= 2;
|
||||
if (x == 5)
|
||||
x--;
|
||||
|
||||
const ssize_t y_s = toggle ? size - 1 : 0;
|
||||
const ssize_t y_e = toggle ? -1 : size;
|
||||
const ssize_t dir = toggle ? -1 : 1;
|
||||
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++];
|
||||
}
|
||||
}
|
||||
|
||||
assert(index == qr_code.bits.length);
|
||||
}
|
||||
|
||||
const auto mod2_remainder =
|
||||
[](uint32_t data, uint32_t generator, uint32_t degree)
|
||||
{
|
||||
constexpr auto bits = [](uint32_t val) -> uint32_t { return 31 - __builtin_clz(val | 1); };
|
||||
while (bits(data) >= degree)
|
||||
data ^= generator << (bits(data) - degree);
|
||||
return data;
|
||||
};
|
||||
|
||||
// format string
|
||||
{
|
||||
const uint8_t pattern = apply_mask_pattern(matrix, 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_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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// version string
|
||||
if (qr_code.version >= 7)
|
||||
{
|
||||
const uint32_t version_data = qr_code.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
auto qr_code = generate_qr_code("https://git.bananymous.com/Bananymous/banan-os", ErrorCorrection::L);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (size_t i = 0; i < qr_code.size() + 8; i++)
|
||||
std::cout << "██";
|
||||
std::cout << '\n';
|
||||
}
|
||||
for (const auto& row : qr_code) {
|
||||
for (int i = 0; i < 4; i++)
|
||||
std::cout << "██";
|
||||
for (bool val : row)
|
||||
std::cout << (val ? " " : "██");
|
||||
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 << "██";
|
||||
std::cout << '\n';
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue