From f3f8a6f96ab370d38e4f53dfb33856ea5d873c4a Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Sun, 8 Sep 2024 09:28:35 +0200 Subject: [PATCH 01/22] feat: add qrcode --- examples/clicking_scrolling/MainWin.cpp | 17 +- preview/interfaces/CMakeLists.txt | 2 + preview/interfaces/QRCode.cpp | 837 ++++++++++++++++++++++++ preview/interfaces/QRCode.hpp | 72 ++ preview/sdl/DirectDisplaySdl.cpp | 2 +- 5 files changed, 928 insertions(+), 2 deletions(-) create mode 100644 preview/interfaces/QRCode.cpp create mode 100644 preview/interfaces/QRCode.hpp diff --git a/examples/clicking_scrolling/MainWin.cpp b/examples/clicking_scrolling/MainWin.cpp index 7de27d4..f8f996a 100644 --- a/examples/clicking_scrolling/MainWin.cpp +++ b/examples/clicking_scrolling/MainWin.cpp @@ -1,11 +1,13 @@ #include "examples/clicking_scrolling/TouchViewClickingScrolling.hpp" #include "hal/generic/TimerServiceGeneric.hpp" #include "infra/event/LowPowerEventDispatcher.hpp" +#include "preview/interfaces/QRCode.hpp" #include "preview/interfaces/ViewPainterDirectDisplay.hpp" #include "preview/interfaces/ViewRepainter.hpp" #include "preview/sdl/DirectDisplaySdl.hpp" #include "preview/sdl/LowPowerStrategySdl.hpp" #include "preview/sdl/SdlTouchInteractor.hpp" +#include "preview/views/ViewBitmap.hpp" #include int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) @@ -14,13 +16,26 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine services::LowPowerStrategySdl lowPowerStrategy(timerService); infra::LowPowerEventDispatcher::WithSize<50> eventDispatcher(lowPowerStrategy); + QRCode qrcode; + uint8_t* qrcodeBytes = reinterpret_cast(malloc(qrcode_getBufferSize(3))); + qrcode_initText(&qrcode, qrcodeBytes, 3, ECC_LOW, "HELLO WORLD"); + + infra::Bitmap::BlackAndWhite<29, 29> qrCodeBitmap; + + for (uint8_t y = 0; y < qrcode.size; y++) + for (uint8_t x = 0; x < qrcode.size; x++) + qrCodeBitmap.SetBlackAndWhitePixel(infra::Point(x, y), qrcode_getModule(&qrcode, x, y) == 0); + + services::ViewBitmap viewBitmap(qrCodeBitmap); + hal::DirectDisplaySdl display(infra::Vector(480, 272)); services::ViewPainterDirectDisplay painter(display); application::TouchViewClickingScrolling touchView; services::SdlTouchInteractor touchInteractor(lowPowerStrategy, touchView); - services::ViewRepainterPaintWhenDirty repainter(painter, touchView.GetView()); + services::ViewRepainterPaintWhenDirty repainter(painter, viewBitmap); + viewBitmap.ResetLayout(display.Size()); touchView.GetView().ResetLayout(display.Size()); eventDispatcher.Run(); diff --git a/preview/interfaces/CMakeLists.txt b/preview/interfaces/CMakeLists.txt index 6c22cc9..fdfc9e2 100644 --- a/preview/interfaces/CMakeLists.txt +++ b/preview/interfaces/CMakeLists.txt @@ -35,6 +35,8 @@ target_sources(preview.interfaces PRIVATE Geometry.cpp Geometry.hpp MultiBufferDisplay.hpp + QRCode.cpp + QRCode.hpp View.cpp View.hpp ViewOverlay.cpp diff --git a/preview/interfaces/QRCode.cpp b/preview/interfaces/QRCode.cpp new file mode 100644 index 0000000..c992a2f --- /dev/null +++ b/preview/interfaces/QRCode.cpp @@ -0,0 +1,837 @@ +/** + * The MIT License (MIT) + * + * This library is written and maintained by Richard Moore. + * Major parts were derived from Project Nayuki's library. + * + * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) + * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Special thanks to Nayuki (https://www.nayuki.io/) from which this library was + * heavily inspired and compared against. + * + * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp + */ + +#include "qrcode.hpp" +#include +#include +#include +#include + +#define AllocOnStack(amount) (reinterpret_cast(_alloca(amount))); + +static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = { + // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + { 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372 }, // Medium + { 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750 }, // Low + { 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430 }, // High + { 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040 }, // Quartile +}; + +static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + // 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + { 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49 }, // Medium + { 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25 }, // Low + { 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81 }, // High + { 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68 }, // Quartile +}; + +static const uint16_t NUM_RAW_DATA_MODULES[40] = { + // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523, + // 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587, + // 32, 33, 34, 35, 36, 37, 38, 39, 40 + 19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648 +}; + +static int8_t getAlphanumeric(char c) +{ + if (c >= '0' && c <= '9') + return (c - '0'); + if (c >= 'A' && c <= 'Z') + return (c - 'A' + 10); + + switch (c) + { + case ' ': + return 36; + case '$': + return 37; + case '%': + return 38; + case '*': + return 39; + case '+': + return 40; + case '-': + return 41; + case '.': + return 42; + case '/': + return 43; + case ':': + return 44; + default: + return -1; + } +} + +static bool isAlphanumeric(const char* text, uint16_t length) +{ + while (length != 0) + if (getAlphanumeric(text[--length]) == -1) + return false; + + return true; +} + +static bool isNumeric(const char* text, uint16_t length) +{ + while (length != 0) + { + char c = text[--length]; + if (c < '0' || c > '9') + return false; + } + + return true; +} + +// We store the following tightly packed (less 8) in modeInfo +// <=9 <=26 <= 40 +// NUMERIC ( 10, 12, 14); +// ALPHANUMERIC ( 9, 11, 13); +// BYTE ( 8, 16, 16); +static char getModeBits(uint8_t version, uint8_t mode) +{ + // Note: We use 15 instead of 16; since 15 doesn't exist and we cannot store 16 (8 + 8) in 3 bits + // hex(int("".join(reversed([('00' + bin(x - 8)[2:])[-3:] for x in [10, 9, 8, 12, 11, 15, 14, 13, 15]])), 2)) + unsigned int modeInfo = 0x7bbb80a; + + if (version > 9) + modeInfo >>= 9; + + if (version > 26) + modeInfo >>= 9; + + char result = 8 + ((modeInfo >> (3 * mode)) & 0x07); + + if (result == 15) + result = 16; + + return result; +} + +struct BitBucket +{ + uint32_t bitOffsetOrWidth; + uint16_t capacityBytes; + uint8_t* data; +}; + +static uint16_t bb_getGridSizeBytes(uint8_t size) +{ + return ((size * size) + 7) / 8; +} + +static uint16_t bb_getBufferSizeBytes(uint32_t bits) +{ + return (bits + 7) / 8; +} + +static void bb_initBuffer(BitBucket* bitBuffer, uint8_t* data, int32_t capacityBytes) +{ + bitBuffer->bitOffsetOrWidth = 0; + bitBuffer->capacityBytes = capacityBytes; + bitBuffer->data = data; + + memset(data, 0, bitBuffer->capacityBytes); +} + +static void bb_initGrid(BitBucket* bitGrid, uint8_t* data, uint8_t size) +{ + bitGrid->bitOffsetOrWidth = size; + bitGrid->capacityBytes = bb_getGridSizeBytes(size); + bitGrid->data = data; + + memset(data, 0, bitGrid->capacityBytes); +} + +static void bb_appendBits(BitBucket* bitBuffer, uint32_t val, uint8_t length) +{ + uint32_t offset = bitBuffer->bitOffsetOrWidth; + + for (int8_t i = length - 1; i >= 0; i--, offset++) + bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7)); + + bitBuffer->bitOffsetOrWidth = offset; +} + +static void bb_setBit(BitBucket* bitGrid, uint8_t x, uint8_t y, bool on) +{ + uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; + uint8_t mask = 1 << (7 - (offset & 0x07)); + + if (on) + bitGrid->data[offset >> 3] |= mask; + else + bitGrid->data[offset >> 3] &= ~mask; +} + +static void bb_invertBit(BitBucket* bitGrid, uint8_t x, uint8_t y, bool invert) +{ + uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; + uint8_t mask = 1 << (7 - (offset & 0x07)); + bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0); + if (on ^ invert) + bitGrid->data[offset >> 3] |= mask; + else + bitGrid->data[offset >> 3] &= ~mask; +} + +static bool bb_getBit(BitBucket* bitGrid, uint8_t x, uint8_t y) +{ + uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; + return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; +} + +// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical +// properties, calling applyMask(m) twice with the same value is equivalent to no change at all. +// This means it is possible to apply a mask, undo it, and try another mask. Note that a final +// well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). +static void applyMask(BitBucket* modules, BitBucket* isFunction, uint8_t mask) +{ + uint8_t size = modules->bitOffsetOrWidth; + + for (uint8_t y = 0; y < size; y++) + { + for (uint8_t x = 0; x < size; x++) + { + if (bb_getBit(isFunction, x, y)) + continue; + + bool invert = 0; + switch (mask) + { + case 0: + invert = (x + y) % 2 == 0; + break; + case 1: + invert = y % 2 == 0; + break; + case 2: + invert = x % 3 == 0; + break; + case 3: + invert = (x + y) % 3 == 0; + break; + case 4: + invert = (x / 3 + y / 2) % 2 == 0; + break; + case 5: + invert = x * y % 2 + x * y % 3 == 0; + break; + case 6: + invert = (x * y % 2 + x * y % 3) % 2 == 0; + break; + case 7: + invert = ((x + y) % 2 + x * y % 3) % 2 == 0; + break; + } + bb_invertBit(modules, x, y, invert); + } + } +} + +static void setFunctionModule(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y, bool on) +{ + bb_setBit(modules, x, y, on); + bb_setBit(isFunction, x, y, true); +} + +// Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). +static void drawFinderPattern(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y) +{ + uint8_t size = modules->bitOffsetOrWidth; + + for (int8_t i = -4; i <= 4; i++) + { + for (int8_t j = -4; j <= 4; j++) + { + uint8_t dist = std::max(std::abs(i), std::abs(j)); // Chebyshev/infinity norm + int16_t xx = x + j, yy = y + i; + if (0 <= xx && xx < size && 0 <= yy && yy < size) + setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4); + } + } +} + +// Draws a 5*5 alignment pattern, with the center module at (x, y). +static void drawAlignmentPattern(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y) +{ + for (int8_t i = -2; i != 3; ++i) + for (int8_t j = -2; j != 3; ++j) + setFunctionModule(modules, isFunction, x + j, y + i, std::max(std::abs(i), std::abs(j)) != 1); +} + +// Draws two copies of the format bits (with its own error correction code) +// based on the given mask and this object's error correction level field. +static void drawFormatBits(BitBucket* modules, BitBucket* isFunction, uint8_t ecc, uint8_t mask) +{ + uint8_t size = modules->bitOffsetOrWidth; + + // Calculate error correction code and pack bits + uint32_t data = ecc << 3 | mask; // ecc is uint2, mask is uint3 + uint32_t rem = data; + for (int i = 0; i != 10; ++i) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + + data = data << 10 | rem; + data ^= 0x5412; // uint15 + + // Draw first copy + for (uint8_t i = 0; i <= 5; i++) + setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0); + + setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0); + setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0); + setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0); + + for (int8_t i = 9; i < 15; i++) + setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0); + + // Draw second copy + for (int8_t i = 0; i <= 7; i++) + setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0); + + for (int8_t i = 8; i < 15; i++) + setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0); + + setFunctionModule(modules, isFunction, 8, size - 8, true); +} + +// Draws two copies of the version bits (with its own error correction code), +// based on this object's version field (which only has an effect for 7 <= version <= 40). +static void drawVersion(BitBucket* modules, BitBucket* isFunction, uint8_t version) +{ + int8_t size = modules->bitOffsetOrWidth; + + if (version < 7) + return; + + // Calculate error correction code and pack bits + uint32_t rem = version; // version is uint6, in the range [7, 40] + for (uint8_t i = 0; i != 12; ++i) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + + uint32_t data = version << 12 | rem; // uint18 + + // Draw two copies + for (uint8_t i = 0; i < 18; i++) + { + bool bit = ((data >> i) & 1) != 0; + uint8_t a = size - 11 + i % 3, b = i / 3; + setFunctionModule(modules, isFunction, a, b, bit); + setFunctionModule(modules, isFunction, b, a, bit); + } +} + +static void drawFunctionPatterns(BitBucket* modules, BitBucket* isFunction, uint8_t version, uint8_t ecc) +{ + uint8_t size = modules->bitOffsetOrWidth; + + // Draw the horizontal and vertical timing patterns + for (uint8_t i = 0; i < size; i++) + { + setFunctionModule(modules, isFunction, 6, i, i % 2 == 0); + setFunctionModule(modules, isFunction, i, 6, i % 2 == 0); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + drawFinderPattern(modules, isFunction, 3, 3); + drawFinderPattern(modules, isFunction, size - 4, 3); + drawFinderPattern(modules, isFunction, 3, size - 4); + + if (version > 1) + { + // Draw the numerous alignment patterns + + uint8_t alignCount = version / 7 + 2; + uint8_t step; + if (version != 32) + step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; // ceil((size - 13) / (2*numAlign - 2)) * 2 + else + step = 26; + + uint8_t alignPositionIndex = alignCount - 1; + uint8_t* alignPosition = AllocOnStack(alignCount); + std::memset(alignPosition, 0, alignCount); + + alignPosition[0] = 6; + + uint8_t size = version * 4 + 17; + for (uint8_t i = 0, pos = size - 7; i != alignCount - 1; ++i, pos -= step) + alignPosition[alignPositionIndex--] = pos; + + for (uint8_t i = 0; i != alignCount; ++i) + for (uint8_t j = 0; j != alignCount; ++j) + if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) + continue; // Skip the three finder corners + else + drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]); + } + + // Draw configuration data + drawFormatBits(modules, isFunction, ecc, 0); // Dummy mask value; overwritten later in the constructor + drawVersion(modules, isFunction, version); +} + +// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire +// data area of this QR Code symbol. Function modules need to be marked off before this is called. +static void drawCodewords(BitBucket* modules, BitBucket* isFunction, BitBucket* codewords) +{ + uint32_t bitLength = codewords->bitOffsetOrWidth; + uint8_t* data = codewords->data; + + uint8_t size = modules->bitOffsetOrWidth; + + // Bit index into the data + uint32_t i = 0; + + // Do the funny zigzag scan + for (int16_t right = size - 1; right >= 1; right -= 2) + { // Index of right column in each column pair + if (right == 6) + right = 5; + + for (uint8_t vert = 0; vert < size; vert++) + { // Vertical counter + for (int j = 0; j != 2; ++j) + { + uint8_t x = right - j; // Actual x coordinate + bool upwards = ((right & 2) == 0) ^ (x < 6); + uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate + if (!bb_getBit(isFunction, x, y) && i < bitLength) + { + bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0); + ++i; + } + // If there are any remainder bits (0 to 7), they are already + // set to 0/false/white when the grid of modules was initialized + } + } + } +} + +#define PENALTY_N1 3 +#define PENALTY_N2 3 +#define PENALTY_N3 40 +#define PENALTY_N4 10 + +// Calculates and returns the penalty score based on state of this QR Code's current modules. +// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. +static uint32_t getPenaltyScore(BitBucket* modules) +{ + uint32_t result = 0; + + uint8_t size = modules->bitOffsetOrWidth; + + // Adjacent modules in row having same color + for (uint8_t y = 0; y < size; y++) + { + bool colorX = bb_getBit(modules, 0, y); + for (uint8_t x = 1, runX = 1; x != size; x++) + { + bool cx = bb_getBit(modules, x, y); + if (cx != colorX) + { + colorX = cx; + runX = 1; + } + else + { + ++runX; + if (runX == 5) + result += PENALTY_N1; + else if (runX > 5) + ++result; + } + } + } + + // Adjacent modules in column having same color + for (uint8_t x = 0; x != size; ++x) + { + bool colorY = bb_getBit(modules, x, 0); + for (uint8_t y = 1, runY = 1; y != size; ++y) + { + bool cy = bb_getBit(modules, x, y); + if (cy != colorY) + { + colorY = cy; + runY = 1; + } + else + { + ++runY; + if (runY == 5) + result += PENALTY_N1; + else if (runY > 5) + ++result; + } + } + } + + uint16_t black = 0; + for (uint8_t y = 0; y != size; ++y) + { + uint16_t bitsRow = 0, bitsCol = 0; + for (uint8_t x = 0; x != size; ++x) + { + bool color = bb_getBit(modules, x, y); + + // 2*2 blocks of modules having same color + if (x > 0 && y > 0) + { + bool colorUL = bb_getBit(modules, x - 1, y - 1); + bool colorUR = bb_getBit(modules, x, y - 1); + bool colorL = bb_getBit(modules, x - 1, y); + if (color == colorUL && color == colorUR && color == colorL) + result += PENALTY_N2; + } + + // Finder-like pattern in rows and columns + bitsRow = ((bitsRow << 1) & 0x7FF) | static_cast(color); + bitsCol = ((bitsCol << 1) & 0x7FF) | static_cast(bb_getBit(modules, y, x)); + + // Needs 11 bits accumulated + if (x >= 10) + { + if (bitsRow == 0x05D || bitsRow == 0x5D0) + result += PENALTY_N3; + + if (bitsCol == 0x05D || bitsCol == 0x5D0) + result += PENALTY_N3; + } + + // Balance of black and white modules + if (color) + { + ++black; + } + } + } + + // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% + uint16_t total = size * size; + for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) + result += PENALTY_N4; + + return result; +} + +static uint8_t rs_multiply(uint8_t x, uint8_t y) +{ + // Russian peasant multiplication + // See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication + uint16_t z = 0; + for (int8_t i = 7; i >= 0; i--) + { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + return z; +} + +static void rs_init(uint8_t degree, uint8_t* coeff) +{ + memset(coeff, 0, degree); + coeff[degree - 1] = 1; + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // drop the highest term, and store the rest of the coefficients in order of descending powers. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint16_t root = 1; + for (uint8_t i = 0; i < degree; i++) + { + // Multiply the current product by (x - r^i) + for (uint8_t j = 0; j != degree; ++j) + { + coeff[j] = rs_multiply(coeff[j], root); + if (j + 1 < degree) + coeff[j] ^= coeff[j + 1]; + } + root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) + } +} + +static void rs_getRemainder(uint8_t degree, uint8_t* coeff, uint8_t* data, uint8_t length, uint8_t* result, uint8_t stride) +{ + // Compute the remainder by performing polynomial division + for (uint8_t i = 0; i < length; i++) + { + uint8_t factor = data[i] ^ result[0]; + for (uint8_t j = 1; j != degree; ++j) + result[(j - 1) * stride] = result[j * stride]; + + result[(degree - 1) * stride] = 0; + + for (uint8_t j = 0; j != degree; ++j) + result[j * stride] ^= rs_multiply(coeff[j], factor); + } +} + +static int8_t encodeDataCodewords(BitBucket* dataCodewords, const uint8_t* text, uint16_t length, uint8_t version) +{ + int8_t mode = MODE_BYTE; + + if (isNumeric((char*)text, length)) + { + mode = MODE_NUMERIC; + bb_appendBits(dataCodewords, 1 << MODE_NUMERIC, 4); + bb_appendBits(dataCodewords, length, getModeBits(version, MODE_NUMERIC)); + + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (uint16_t i = 0; i < length; i++) + { + accumData = accumData * 10 + ((char)(text[i]) - '0'); + ++accumCount; + if (accumCount == 3) + { + bb_appendBits(dataCodewords, accumData, 10); + accumData = 0; + accumCount = 0; + } + } + + // 1 or 2 digits remaining + if (accumCount > 0) + bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1); + } + else if (isAlphanumeric((char*)text, length)) + { + mode = MODE_ALPHANUMERIC; + bb_appendBits(dataCodewords, 1 << MODE_ALPHANUMERIC, 4); + bb_appendBits(dataCodewords, length, getModeBits(version, MODE_ALPHANUMERIC)); + + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (uint16_t i = 0; i != length; ++i) + { + accumData = accumData * 45 + getAlphanumeric((char)(text[i])); + ++accumCount; + if (accumCount == 2) + { + bb_appendBits(dataCodewords, accumData, 11); + accumData = 0; + accumCount = 0; + } + } + + if (accumCount == 1) + bb_appendBits(dataCodewords, accumData, 6); + } + else + { + bb_appendBits(dataCodewords, 1 << MODE_BYTE, 4); + bb_appendBits(dataCodewords, length, getModeBits(version, MODE_BYTE)); + for (uint16_t i = 0; i != length; ++i) + bb_appendBits(dataCodewords, (char)(text[i]), 8); + } + + return mode; +} + +static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket* data) +{ + // See: http://www.thonky.com/qr-code-tutorial/structure-final-message + uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1]; + uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1]; + uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; + + uint8_t blockEccLen = totalEcc / numBlocks; + uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks; + uint8_t shortBlockLen = moduleCount / 8 / numBlocks; + + uint8_t shortDataBlockLen = shortBlockLen - blockEccLen; + + uint8_t* result = AllocOnStack(data->capacityBytes); + std::memset(result, 0, data->capacityBytes); + + uint8_t* coeff = AllocOnStack(blockEccLen); + std::memset(coeff, 0, blockEccLen); + rs_init(blockEccLen, coeff); + + uint16_t offset = 0; + uint8_t* dataBytes = data->data; + + // Interleave all short blocks + for (uint8_t i = 0; i != shortDataBlockLen; ++i) + { + uint16_t index = i; + uint8_t stride = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum != numBlocks; ++blockNum) + { + result[offset++] = dataBytes[index]; + + if (blockNum == numShortBlocks) + ++stride; + + index += stride; + } + } + + // Version less than 5 only have short blocks + { + // Interleave long blocks + uint16_t index = shortDataBlockLen * (numShortBlocks + 1); + uint8_t stride = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum != numBlocks - numShortBlocks; ++blockNum) + { + result[offset++] = dataBytes[index]; + + if (blockNum == 0) + ++stride; + + index += stride; + } + } + + // Add all ecc blocks, interleaved + uint8_t blockSize = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum != numBlocks; ++blockNum) + { + + if (blockNum == numShortBlocks) + ++blockSize; + + rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks); + dataBytes += blockSize; + } + + memcpy(data->data, result, data->capacityBytes); + data->bitOffsetOrWidth = moduleCount; +} + +// We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits) +// The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc) +static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); + +uint16_t qrcode_getBufferSize(uint8_t version) +{ + return bb_getGridSizeBytes(4 * version + 17); +} + +int8_t qrcode_initBytes(QRCode* qrcode, uint8_t* modules, uint8_t version, uint8_t ecc, uint8_t* data, uint16_t length) +{ + uint8_t size = version * 4 + 17; + qrcode->version = version; + qrcode->size = size; + qrcode->ecc = ecc; + qrcode->modules = modules; + + uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03; + + uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; + uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1]; + + struct BitBucket codewords; + uint8_t* codewordBytes = AllocOnStack(bb_getBufferSizeBytes(moduleCount)); + std::memset(codewordBytes, 0, bb_getBufferSizeBytes(moduleCount)); + bb_initBuffer(&codewords, codewordBytes, (int32_t)bb_getBufferSizeBytes(moduleCount)); + + // Place the data code words into the buffer + int8_t mode = encodeDataCodewords(&codewords, data, length, version); + + if (mode < 0) + return -1; + + qrcode->mode = mode; + + // Add terminator and pad up to a byte if applicable + uint32_t padding = std::min((dataCapacity * 8) - codewords.bitOffsetOrWidth, 4); + + bb_appendBits(&codewords, 0, padding); + bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8); + + // Pad with alternate bytes until data capacity is reached + for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) + bb_appendBits(&codewords, padByte, 8); + + BitBucket modulesGrid; + bb_initGrid(&modulesGrid, modules, size); + + BitBucket isFunctionGrid; + uint8_t* isFunctionGridBytes = AllocOnStack(bb_getGridSizeBytes(size)); + std::memset(isFunctionGridBytes, 0, bb_getGridSizeBytes(size)); + bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size); + + // Draw function patterns, draw all codewords, do masking + drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits); + performErrorCorrection(version, eccFormatBits, &codewords); + drawCodewords(&modulesGrid, &isFunctionGrid, &codewords); + + // Find the best (lowest penalty) mask + uint8_t mask = 0; + int32_t minPenalty = INT32_MAX; + for (uint8_t i = 0; i != 8; ++i) + { + drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i); + applyMask(&modulesGrid, &isFunctionGrid, i); + int penalty = getPenaltyScore(&modulesGrid); + if (penalty < minPenalty) + { + mask = i; + minPenalty = penalty; + } + applyMask(&modulesGrid, &isFunctionGrid, i); // Undoes the mask due to XOR + } + + qrcode->mask = mask; + + // Overwrite old format bits + drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask); + + // Apply the final choice of mask + applyMask(&modulesGrid, &isFunctionGrid, mask); + + return 0; +} + +int8_t qrcode_initText(QRCode* qrcode, uint8_t* modules, uint8_t version, uint8_t ecc, const char* data) +{ + return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data)); +} + +bool qrcode_getModule(QRCode* qrcode, uint8_t x, uint8_t y) +{ + if (x < 0 || x >= qrcode->size || y < 0 || y >= qrcode->size) + return false; + + uint32_t offset = y * qrcode->size + x; + return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; +} diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QRCode.hpp new file mode 100644 index 0000000..9112148 --- /dev/null +++ b/preview/interfaces/QRCode.hpp @@ -0,0 +1,72 @@ +/** + * The MIT License (MIT) + * + * This library is written and maintained by Richard Moore. + * Major parts were derived from Project Nayuki's library. + * + * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) + * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Special thanks to Nayuki (https://www.nayuki.io/) from which this library was + * heavily inspired and compared against. + * + * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp + */ + + +#ifndef QR_CODE_HPP +#define QR_CODE_HPP + +#include + + +// QR Code Format Encoding +#define MODE_NUMERIC 0 +#define MODE_ALPHANUMERIC 1 +#define MODE_BYTE 2 + + +// Error Correction Code Levels +#define ECC_LOW 0 +#define ECC_MEDIUM 1 +#define ECC_QUARTILE 2 +#define ECC_HIGH 3 + +struct QRCode +{ + uint8_t version; + uint8_t size; + uint8_t ecc; + uint8_t mode; + uint8_t mask; + uint8_t *modules; +}; + +uint16_t qrcode_getBufferSize(uint8_t version); + +int8_t qrcode_initText(QRCode* qrcode, uint8_t* modules, uint8_t version, uint8_t ecc, const char* data); +int8_t qrcode_initBytes(QRCode* qrcode, uint8_t* modules, uint8_t version, uint8_t ecc, uint8_t* data, uint16_t length); + +bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y); + +#endif diff --git a/preview/sdl/DirectDisplaySdl.cpp b/preview/sdl/DirectDisplaySdl.cpp index bbcf075..164bc6a 100644 --- a/preview/sdl/DirectDisplaySdl.cpp +++ b/preview/sdl/DirectDisplaySdl.cpp @@ -343,7 +343,7 @@ namespace hal for (auto x = 0; x != sourceBitmap.size.deltaX; ++x) { auto i = y * sourceBitmap.size.deltaX + x; - auto colour = (sourceBitmap.buffer[i / 8] & (1 << (7 - i % 8))) == 0 ? infra::Colour::black : infra::Colour::white; + auto colour = (sourceBitmap.buffer[i / 8] & (1 << (i % 8))) == 0 ? infra::Colour::black : infra::Colour::white; DrawPixel(destination.TopLeft() + infra::Vector(x, y), colour, boundingBox); } } From ef093f7742a64411853229a6e3c0385f69185776 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Tue, 10 Sep 2024 08:18:52 +0200 Subject: [PATCH 02/22] Extract classes --- examples/clicking_scrolling/MainWin.cpp | 6 +- preview/interfaces/QRCode.cpp | 311 +++++++++++------------- preview/interfaces/QRCode.hpp | 54 ++-- 3 files changed, 179 insertions(+), 192 deletions(-) diff --git a/examples/clicking_scrolling/MainWin.cpp b/examples/clicking_scrolling/MainWin.cpp index f8f996a..7af1a43 100644 --- a/examples/clicking_scrolling/MainWin.cpp +++ b/examples/clicking_scrolling/MainWin.cpp @@ -16,15 +16,13 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine services::LowPowerStrategySdl lowPowerStrategy(timerService); infra::LowPowerEventDispatcher::WithSize<50> eventDispatcher(lowPowerStrategy); - QRCode qrcode; - uint8_t* qrcodeBytes = reinterpret_cast(malloc(qrcode_getBufferSize(3))); - qrcode_initText(&qrcode, qrcodeBytes, 3, ECC_LOW, "HELLO WORLD"); + QRCode::Version<3> qrcode(3, QRCode::Ecc::low, "HELLO WORLD"); infra::Bitmap::BlackAndWhite<29, 29> qrCodeBitmap; for (uint8_t y = 0; y < qrcode.size; y++) for (uint8_t x = 0; x < qrcode.size; x++) - qrCodeBitmap.SetBlackAndWhitePixel(infra::Point(x, y), qrcode_getModule(&qrcode, x, y) == 0); + qrCodeBitmap.SetBlackAndWhitePixel(infra::Point(x, y), qrcode.getModule(x, y) == 0); services::ViewBitmap viewBitmap(qrCodeBitmap); diff --git a/preview/interfaces/QRCode.cpp b/preview/interfaces/QRCode.cpp index c992a2f..97f6aca 100644 --- a/preview/interfaces/QRCode.cpp +++ b/preview/interfaces/QRCode.cpp @@ -35,11 +35,11 @@ #include "qrcode.hpp" #include -#include #include #include +#include -#define AllocOnStack(amount) (reinterpret_cast(_alloca(amount))); +#define AllocOnStack(amount) (reinterpret_cast(_alloca(amount))) static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = { // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level @@ -99,23 +99,20 @@ static int8_t getAlphanumeric(char c) } } -static bool isAlphanumeric(const char* text, uint16_t length) +static bool isAlphanumeric(infra::BoundedConstString text) { - while (length != 0) - if (getAlphanumeric(text[--length]) == -1) + for (auto c : text) + if (getAlphanumeric(c) == -1) return false; return true; } -static bool isNumeric(const char* text, uint16_t length) +static bool isNumeric(infra::BoundedConstString text) { - while (length != 0) - { - char c = text[--length]; + for (auto c : text) if (c < '0' || c > '9') return false; - } return true; } @@ -145,77 +142,91 @@ static char getModeBits(uint8_t version, uint8_t mode) return result; } -struct BitBucket +class BitBuffer { - uint32_t bitOffsetOrWidth; +public: + BitBuffer(uint8_t* data, int32_t capacityBytes); + + void Append(uint32_t val, uint8_t length); + void EncodeDataCodewords(infra::BoundedConstString text, uint8_t version); + void PerformErrorCorrection(uint8_t version, uint8_t ecc); + +public: + uint32_t bitOffset = 0; uint16_t capacityBytes; uint8_t* data; }; -static uint16_t bb_getGridSizeBytes(uint8_t size) + +BitBuffer::BitBuffer(uint8_t* data, int32_t capacityBytes) + : capacityBytes(capacityBytes) + , data(data) { - return ((size * size) + 7) / 8; + memset(data, 0, capacityBytes); } -static uint16_t bb_getBufferSizeBytes(uint32_t bits) +void BitBuffer::Append(uint32_t val, uint8_t length) { - return (bits + 7) / 8; + for (int8_t i = length - 1; i >= 0; --i, ++bitOffset) + data[bitOffset >> 3] |= ((val >> i) & 1) << (7 - (bitOffset & 7)); } -static void bb_initBuffer(BitBucket* bitBuffer, uint8_t* data, int32_t capacityBytes) +class BitBucket { - bitBuffer->bitOffsetOrWidth = 0; - bitBuffer->capacityBytes = capacityBytes; - bitBuffer->data = data; +public: + BitBucket(uint8_t* data, uint8_t size); - memset(data, 0, bitBuffer->capacityBytes); -} + void Set(uint8_t x, uint8_t y, bool on); + bool Get(uint8_t x, uint8_t y) const; + void Invert(uint8_t x, uint8_t y, bool invert); -static void bb_initGrid(BitBucket* bitGrid, uint8_t* data, uint8_t size) -{ - bitGrid->bitOffsetOrWidth = size; - bitGrid->capacityBytes = bb_getGridSizeBytes(size); - bitGrid->data = data; + uint32_t PenaltyScore() const; - memset(data, 0, bitGrid->capacityBytes); -} +public: + uint32_t width; + uint16_t capacityBytes; + uint8_t* data; +}; -static void bb_appendBits(BitBucket* bitBuffer, uint32_t val, uint8_t length) +static uint16_t RoundBitsToByte(uint32_t bits) { - uint32_t offset = bitBuffer->bitOffsetOrWidth; - - for (int8_t i = length - 1; i >= 0; i--, offset++) - bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7)); + return (bits + 7) / 8; +} - bitBuffer->bitOffsetOrWidth = offset; +BitBucket::BitBucket(uint8_t* data, uint8_t size) + : width(size) + , capacityBytes(QRCode::GridSizeInBytes(size)) + , data(data) +{ + memset(data, 0, capacityBytes); } -static void bb_setBit(BitBucket* bitGrid, uint8_t x, uint8_t y, bool on) +void BitBucket::Set(uint8_t x, uint8_t y, bool on) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; + uint32_t offset = y * width + x; uint8_t mask = 1 << (7 - (offset & 0x07)); if (on) - bitGrid->data[offset >> 3] |= mask; + data[offset >> 3] |= mask; else - bitGrid->data[offset >> 3] &= ~mask; + data[offset >> 3] &= ~mask; } -static void bb_invertBit(BitBucket* bitGrid, uint8_t x, uint8_t y, bool invert) +bool BitBucket::Get(uint8_t x, uint8_t y) const { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - uint8_t mask = 1 << (7 - (offset & 0x07)); - bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0); - if (on ^ invert) - bitGrid->data[offset >> 3] |= mask; - else - bitGrid->data[offset >> 3] &= ~mask; + uint32_t offset = y * width + x; + return (data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; } -static bool bb_getBit(BitBucket* bitGrid, uint8_t x, uint8_t y) +void BitBucket::Invert(uint8_t x, uint8_t y, bool invert) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; + uint32_t offset = y * width + x; + uint8_t mask = 1 << (7 - (offset & 0x07)); + bool on = ((data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0); + if (on ^ invert) + data[offset >> 3] |= mask; + else + data[offset >> 3] &= ~mask; } // XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical @@ -224,13 +235,13 @@ static bool bb_getBit(BitBucket* bitGrid, uint8_t x, uint8_t y) // well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). static void applyMask(BitBucket* modules, BitBucket* isFunction, uint8_t mask) { - uint8_t size = modules->bitOffsetOrWidth; + uint8_t size = modules->width; - for (uint8_t y = 0; y < size; y++) + for (uint8_t y = 0; y != size; ++y) { - for (uint8_t x = 0; x < size; x++) + for (uint8_t x = 0; x != size; ++x) { - if (bb_getBit(isFunction, x, y)) + if (isFunction->Get(x, y)) continue; bool invert = 0; @@ -261,21 +272,21 @@ static void applyMask(BitBucket* modules, BitBucket* isFunction, uint8_t mask) invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; } - bb_invertBit(modules, x, y, invert); + modules->Invert(x, y, invert); } } } static void setFunctionModule(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y, bool on) { - bb_setBit(modules, x, y, on); - bb_setBit(isFunction, x, y, true); + modules->Set(x, y, on); + isFunction->Set(x, y, true); } // Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). static void drawFinderPattern(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y) { - uint8_t size = modules->bitOffsetOrWidth; + uint8_t size = modules->width; for (int8_t i = -4; i <= 4; i++) { @@ -301,7 +312,7 @@ static void drawAlignmentPattern(BitBucket* modules, BitBucket* isFunction, uint // based on the given mask and this object's error correction level field. static void drawFormatBits(BitBucket* modules, BitBucket* isFunction, uint8_t ecc, uint8_t mask) { - uint8_t size = modules->bitOffsetOrWidth; + uint8_t size = modules->width; // Calculate error correction code and pack bits uint32_t data = ecc << 3 | mask; // ecc is uint2, mask is uint3 @@ -337,7 +348,7 @@ static void drawFormatBits(BitBucket* modules, BitBucket* isFunction, uint8_t ec // based on this object's version field (which only has an effect for 7 <= version <= 40). static void drawVersion(BitBucket* modules, BitBucket* isFunction, uint8_t version) { - int8_t size = modules->bitOffsetOrWidth; + int8_t size = modules->width; if (version < 7) return; @@ -347,13 +358,14 @@ static void drawVersion(BitBucket* modules, BitBucket* isFunction, uint8_t versi for (uint8_t i = 0; i != 12; ++i) rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); - uint32_t data = version << 12 | rem; // uint18 + uint32_t data = version << 12 | rem; // Draw two copies - for (uint8_t i = 0; i < 18; i++) + for (uint8_t i = 0; i != 18; ++i) { bool bit = ((data >> i) & 1) != 0; - uint8_t a = size - 11 + i % 3, b = i / 3; + uint8_t a = size - 11 + i % 3; + uint8_t b = i / 3; setFunctionModule(modules, isFunction, a, b, bit); setFunctionModule(modules, isFunction, b, a, bit); } @@ -361,10 +373,10 @@ static void drawVersion(BitBucket* modules, BitBucket* isFunction, uint8_t versi static void drawFunctionPatterns(BitBucket* modules, BitBucket* isFunction, uint8_t version, uint8_t ecc) { - uint8_t size = modules->bitOffsetOrWidth; + uint8_t size = modules->width; // Draw the horizontal and vertical timing patterns - for (uint8_t i = 0; i < size; i++) + for (uint8_t i = 0; i != size; ++i) { setFunctionModule(modules, isFunction, 6, i, i % 2 == 0); setFunctionModule(modules, isFunction, i, 6, i % 2 == 0); @@ -411,12 +423,12 @@ static void drawFunctionPatterns(BitBucket* modules, BitBucket* isFunction, uint // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire // data area of this QR Code symbol. Function modules need to be marked off before this is called. -static void drawCodewords(BitBucket* modules, BitBucket* isFunction, BitBucket* codewords) +static void drawCodewords(BitBucket* modules, BitBucket* isFunction, BitBuffer* codewords) { - uint32_t bitLength = codewords->bitOffsetOrWidth; + uint32_t bitLength = codewords->bitOffset; uint8_t* data = codewords->data; - uint8_t size = modules->bitOffsetOrWidth; + uint8_t size = modules->width; // Bit index into the data uint32_t i = 0; @@ -427,16 +439,16 @@ static void drawCodewords(BitBucket* modules, BitBucket* isFunction, BitBucket* if (right == 6) right = 5; - for (uint8_t vert = 0; vert < size; vert++) + for (uint8_t vert = 0; vert != size; ++vert) { // Vertical counter for (int j = 0; j != 2; ++j) { uint8_t x = right - j; // Actual x coordinate bool upwards = ((right & 2) == 0) ^ (x < 6); uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate - if (!bb_getBit(isFunction, x, y) && i < bitLength) + if (!isFunction->Get(x, y) && i < bitLength) { - bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0); + modules->Set(x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0); ++i; } // If there are any remainder bits (0 to 7), they are already @@ -453,19 +465,19 @@ static void drawCodewords(BitBucket* modules, BitBucket* isFunction, BitBucket* // Calculates and returns the penalty score based on state of this QR Code's current modules. // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. -static uint32_t getPenaltyScore(BitBucket* modules) +uint32_t BitBucket::PenaltyScore() const { uint32_t result = 0; - uint8_t size = modules->bitOffsetOrWidth; + uint8_t size = width; // Adjacent modules in row having same color - for (uint8_t y = 0; y < size; y++) + for (uint8_t y = 0; y < size; ++y) { - bool colorX = bb_getBit(modules, 0, y); - for (uint8_t x = 1, runX = 1; x != size; x++) + bool colorX = Get(0, y); + for (uint8_t x = 1, runX = 1; x != size; ++x) { - bool cx = bb_getBit(modules, x, y); + bool cx = Get(x, y); if (cx != colorX) { colorX = cx; @@ -485,10 +497,10 @@ static uint32_t getPenaltyScore(BitBucket* modules) // Adjacent modules in column having same color for (uint8_t x = 0; x != size; ++x) { - bool colorY = bb_getBit(modules, x, 0); + bool colorY = Get(x, 0); for (uint8_t y = 1, runY = 1; y != size; ++y) { - bool cy = bb_getBit(modules, x, y); + bool cy = Get(x, y); if (cy != colorY) { colorY = cy; @@ -511,21 +523,21 @@ static uint32_t getPenaltyScore(BitBucket* modules) uint16_t bitsRow = 0, bitsCol = 0; for (uint8_t x = 0; x != size; ++x) { - bool color = bb_getBit(modules, x, y); + bool color = Get(x, y); // 2*2 blocks of modules having same color if (x > 0 && y > 0) { - bool colorUL = bb_getBit(modules, x - 1, y - 1); - bool colorUR = bb_getBit(modules, x, y - 1); - bool colorL = bb_getBit(modules, x - 1, y); + bool colorUL = Get(x - 1, y - 1); + bool colorUR = Get(x, y - 1); + bool colorL = Get(x - 1, y); if (color == colorUL && color == colorUR && color == colorL) result += PENALTY_N2; } // Finder-like pattern in rows and columns bitsRow = ((bitsRow << 1) & 0x7FF) | static_cast(color); - bitsCol = ((bitsCol << 1) & 0x7FF) | static_cast(bb_getBit(modules, y, x)); + bitsCol = ((bitsCol << 1) & 0x7FF) | static_cast(Get(y, x)); // Needs 11 bits accumulated if (x >= 10) @@ -539,9 +551,7 @@ static uint32_t getPenaltyScore(BitBucket* modules) // Balance of black and white modules if (color) - { ++black; - } } } @@ -604,25 +614,26 @@ static void rs_getRemainder(uint8_t degree, uint8_t* coeff, uint8_t* data, uint8 } } -static int8_t encodeDataCodewords(BitBucket* dataCodewords, const uint8_t* text, uint16_t length, uint8_t version) +void BitBuffer::EncodeDataCodewords(infra::BoundedConstString text, uint8_t version) { - int8_t mode = MODE_BYTE; +#define MODE_NUMERIC 0 +#define MODE_ALPHANUMERIC 1 +#define MODE_BYTE 2 - if (isNumeric((char*)text, length)) + if (isNumeric(text)) { - mode = MODE_NUMERIC; - bb_appendBits(dataCodewords, 1 << MODE_NUMERIC, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_NUMERIC)); + Append(1 << MODE_NUMERIC, 4); + Append(text.size(), getModeBits(version, MODE_NUMERIC)); uint16_t accumData = 0; uint8_t accumCount = 0; - for (uint16_t i = 0; i < length; i++) + for (auto c : text) { - accumData = accumData * 10 + ((char)(text[i]) - '0'); + accumData = accumData * 10 + (c - '0'); ++accumCount; if (accumCount == 3) { - bb_appendBits(dataCodewords, accumData, 10); + Append(accumData, 10); accumData = 0; accumCount = 0; } @@ -630,43 +641,40 @@ static int8_t encodeDataCodewords(BitBucket* dataCodewords, const uint8_t* text, // 1 or 2 digits remaining if (accumCount > 0) - bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1); + Append(accumData, accumCount * 3 + 1); } - else if (isAlphanumeric((char*)text, length)) + else if (isAlphanumeric(text)) { - mode = MODE_ALPHANUMERIC; - bb_appendBits(dataCodewords, 1 << MODE_ALPHANUMERIC, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_ALPHANUMERIC)); + Append(1 << MODE_ALPHANUMERIC, 4); + Append(text.size(), getModeBits(version, MODE_ALPHANUMERIC)); uint16_t accumData = 0; uint8_t accumCount = 0; - for (uint16_t i = 0; i != length; ++i) + for (auto c : text) { - accumData = accumData * 45 + getAlphanumeric((char)(text[i])); + accumData = accumData * 45 + getAlphanumeric(c); ++accumCount; if (accumCount == 2) { - bb_appendBits(dataCodewords, accumData, 11); + Append(accumData, 11); accumData = 0; accumCount = 0; } } if (accumCount == 1) - bb_appendBits(dataCodewords, accumData, 6); + Append(accumData, 6); } else { - bb_appendBits(dataCodewords, 1 << MODE_BYTE, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_BYTE)); - for (uint16_t i = 0; i != length; ++i) - bb_appendBits(dataCodewords, (char)(text[i]), 8); + Append(1 << MODE_BYTE, 4); + Append(text.size(), getModeBits(version, MODE_BYTE)); + for (auto c : text) + Append(c, 8); } - - return mode; } -static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket* data) +void BitBuffer::PerformErrorCorrection(uint8_t version, uint8_t ecc) { // See: http://www.thonky.com/qr-code-tutorial/structure-final-message uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1]; @@ -679,15 +687,15 @@ static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket* data uint8_t shortDataBlockLen = shortBlockLen - blockEccLen; - uint8_t* result = AllocOnStack(data->capacityBytes); - std::memset(result, 0, data->capacityBytes); + uint8_t* result = AllocOnStack(capacityBytes); + std::memset(result, 0, capacityBytes); uint8_t* coeff = AllocOnStack(blockEccLen); std::memset(coeff, 0, blockEccLen); rs_init(blockEccLen, coeff); uint16_t offset = 0; - uint8_t* dataBytes = data->data; + uint8_t* dataBytes = data; // Interleave all short blocks for (uint8_t i = 0; i != shortDataBlockLen; ++i) @@ -733,66 +741,47 @@ static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket* data dataBytes += blockSize; } - memcpy(data->data, result, data->capacityBytes); - data->bitOffsetOrWidth = moduleCount; + memcpy(data, result, capacityBytes); + bitOffset = moduleCount; } // We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits) // The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc) static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); -uint16_t qrcode_getBufferSize(uint8_t version) -{ - return bb_getGridSizeBytes(4 * version + 17); -} - -int8_t qrcode_initBytes(QRCode* qrcode, uint8_t* modules, uint8_t version, uint8_t ecc, uint8_t* data, uint16_t length) +QRCode::QRCode(infra::ByteRange modules, uint8_t version, Ecc ecc, infra::BoundedConstString text) + : version(version) + , size(version * 4 + 17) + , ecc(ecc) + , modules(modules) { - uint8_t size = version * 4 + 17; - qrcode->version = version; - qrcode->size = size; - qrcode->ecc = ecc; - qrcode->modules = modules; - - uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03; + uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * static_cast(ecc))) & 0x03; uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1]; - struct BitBucket codewords; - uint8_t* codewordBytes = AllocOnStack(bb_getBufferSizeBytes(moduleCount)); - std::memset(codewordBytes, 0, bb_getBufferSizeBytes(moduleCount)); - bb_initBuffer(&codewords, codewordBytes, (int32_t)bb_getBufferSizeBytes(moduleCount)); + BitBuffer codewords(AllocOnStack(RoundBitsToByte(moduleCount)), (int32_t)RoundBitsToByte(moduleCount)); // Place the data code words into the buffer - int8_t mode = encodeDataCodewords(&codewords, data, length, version); - - if (mode < 0) - return -1; - - qrcode->mode = mode; + codewords.EncodeDataCodewords(text, version); // Add terminator and pad up to a byte if applicable - uint32_t padding = std::min((dataCapacity * 8) - codewords.bitOffsetOrWidth, 4); + uint32_t padding = std::min((dataCapacity * 8) - codewords.bitOffset, 4); - bb_appendBits(&codewords, 0, padding); - bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8); + codewords.Append(0, padding); + codewords.Append(0, (8 - codewords.bitOffset % 8) % 8); // Pad with alternate bytes until data capacity is reached - for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) - bb_appendBits(&codewords, padByte, 8); + for (uint8_t padByte = 0xEC; codewords.bitOffset < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) + codewords.Append(padByte, 8); - BitBucket modulesGrid; - bb_initGrid(&modulesGrid, modules, size); + BitBucket modulesGrid(modules.begin(), size); - BitBucket isFunctionGrid; - uint8_t* isFunctionGridBytes = AllocOnStack(bb_getGridSizeBytes(size)); - std::memset(isFunctionGridBytes, 0, bb_getGridSizeBytes(size)); - bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size); + BitBucket isFunctionGrid(AllocOnStack(GridSizeInBytes(size)), size); // Draw function patterns, draw all codewords, do masking drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits); - performErrorCorrection(version, eccFormatBits, &codewords); + codewords.PerformErrorCorrection(version, eccFormatBits); drawCodewords(&modulesGrid, &isFunctionGrid, &codewords); // Find the best (lowest penalty) mask @@ -802,7 +791,7 @@ int8_t qrcode_initBytes(QRCode* qrcode, uint8_t* modules, uint8_t version, uint8 { drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i); applyMask(&modulesGrid, &isFunctionGrid, i); - int penalty = getPenaltyScore(&modulesGrid); + int penalty = modulesGrid.PenaltyScore(); if (penalty < minPenalty) { mask = i; @@ -811,27 +800,19 @@ int8_t qrcode_initBytes(QRCode* qrcode, uint8_t* modules, uint8_t version, uint8 applyMask(&modulesGrid, &isFunctionGrid, i); // Undoes the mask due to XOR } - qrcode->mask = mask; - // Overwrite old format bits drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask); // Apply the final choice of mask applyMask(&modulesGrid, &isFunctionGrid, mask); - - return 0; } -int8_t qrcode_initText(QRCode* qrcode, uint8_t* modules, uint8_t version, uint8_t ecc, const char* data) +bool QRCode::getModule(uint8_t x, uint8_t y) const { - return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data)); -} - -bool qrcode_getModule(QRCode* qrcode, uint8_t x, uint8_t y) -{ - if (x < 0 || x >= qrcode->size || y < 0 || y >= qrcode->size) + if (x < 0 || x >= size || y < 0 || y >= size) return false; - uint32_t offset = y * qrcode->size + x; - return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; + uint32_t offset = y * size + x; + return (modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; } + diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QRCode.hpp index 9112148..efba969 100644 --- a/preview/interfaces/QRCode.hpp +++ b/preview/interfaces/QRCode.hpp @@ -33,40 +33,48 @@ * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp */ - #ifndef QR_CODE_HPP #define QR_CODE_HPP +#include "infra/util/BoundedString.hpp" +#include "infra/util/ByteRange.hpp" +#include "preview/interfaces/Bitmap.hpp" #include +class QRCode +{ +public: + static constexpr uint16_t GridSizeInBytes(uint8_t bits) + { + return (bits * bits + 7) / 8; + } + + static constexpr uint16_t BufferSize(uint8_t version) + { + return GridSizeInBytes(4 * version + 17); + } -// QR Code Format Encoding -#define MODE_NUMERIC 0 -#define MODE_ALPHANUMERIC 1 -#define MODE_BYTE 2 +public: + enum class Ecc : uint8_t + { + low, + medium, + quartile, + high + }; +public: + template + using Version = infra::WithStorage>; -// Error Correction Code Levels -#define ECC_LOW 0 -#define ECC_MEDIUM 1 -#define ECC_QUARTILE 2 -#define ECC_HIGH 3 + QRCode(infra::ByteRange modules, uint8_t version, Ecc ecc, infra::BoundedConstString text); + bool getModule(uint8_t x, uint8_t y) const; -struct QRCode -{ +public: uint8_t version; uint8_t size; - uint8_t ecc; - uint8_t mode; - uint8_t mask; - uint8_t *modules; + Ecc ecc; + infra::ByteRange modules; }; -uint16_t qrcode_getBufferSize(uint8_t version); - -int8_t qrcode_initText(QRCode* qrcode, uint8_t* modules, uint8_t version, uint8_t ecc, const char* data); -int8_t qrcode_initBytes(QRCode* qrcode, uint8_t* modules, uint8_t version, uint8_t ecc, uint8_t* data, uint16_t length); - -bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y); - #endif From b6d11593976d690e63ad44f12cb934fc20fcd6e9 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Tue, 10 Sep 2024 08:52:38 +0200 Subject: [PATCH 03/22] Use infra::Bitmap --- examples/clicking_scrolling/MainWin.cpp | 2 +- preview/interfaces/Bitmap.cpp | 2 +- preview/interfaces/QRCode.cpp | 74 +++++++------------------ preview/interfaces/QRCode.hpp | 24 ++++++-- 4 files changed, 40 insertions(+), 62 deletions(-) diff --git a/examples/clicking_scrolling/MainWin.cpp b/examples/clicking_scrolling/MainWin.cpp index 7af1a43..3ae9d6f 100644 --- a/examples/clicking_scrolling/MainWin.cpp +++ b/examples/clicking_scrolling/MainWin.cpp @@ -16,7 +16,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine services::LowPowerStrategySdl lowPowerStrategy(timerService); infra::LowPowerEventDispatcher::WithSize<50> eventDispatcher(lowPowerStrategy); - QRCode::Version<3> qrcode(3, QRCode::Ecc::low, "HELLO WORLD"); + QRCode qrcode(3, QRCode::Ecc::low, "HELLO WORLD"); infra::Bitmap::BlackAndWhite<29, 29> qrCodeBitmap; diff --git a/preview/interfaces/Bitmap.cpp b/preview/interfaces/Bitmap.cpp index ce62de8..a9f274e 100644 --- a/preview/interfaces/Bitmap.cpp +++ b/preview/interfaces/Bitmap.cpp @@ -40,7 +40,7 @@ namespace infra assert(pixelFormat == PixelFormat::blackandwhite); auto bitIndex = position.y * size.deltaX + position.x; - return (buffer[bitIndex / 8] & (1 << (7 - bitIndex % 8))) != 0; + return (buffer[bitIndex / 8] & (1 << (bitIndex % 8))) != 0; } uint32_t Bitmap::PixelColour(infra::Point position) const diff --git a/preview/interfaces/QRCode.cpp b/preview/interfaces/QRCode.cpp index 97f6aca..221aa29 100644 --- a/preview/interfaces/QRCode.cpp +++ b/preview/interfaces/QRCode.cpp @@ -171,62 +171,32 @@ void BitBuffer::Append(uint32_t val, uint8_t length) data[bitOffset >> 3] |= ((val >> i) & 1) << (7 - (bitOffset & 7)); } -class BitBucket -{ -public: - BitBucket(uint8_t* data, uint8_t size); - - void Set(uint8_t x, uint8_t y, bool on); - bool Get(uint8_t x, uint8_t y) const; - void Invert(uint8_t x, uint8_t y, bool invert); - - uint32_t PenaltyScore() const; - -public: - uint32_t width; - uint16_t capacityBytes; - uint8_t* data; -}; - static uint16_t RoundBitsToByte(uint32_t bits) { return (bits + 7) / 8; } -BitBucket::BitBucket(uint8_t* data, uint8_t size) - : width(size) - , capacityBytes(QRCode::GridSizeInBytes(size)) - , data(data) +BitBucket::BitBucket(uint8_t size) + : buffer(new uint8_t[QRCode::GridSizeInBytes(size)]) + , bitmap(infra::ByteRange(buffer, buffer + QRCode::GridSizeInBytes(size)), infra::Vector(size, size), infra::PixelFormat::blackandwhite) { - memset(data, 0, capacityBytes); + memset(buffer, 0, QRCode::GridSizeInBytes(size)); } void BitBucket::Set(uint8_t x, uint8_t y, bool on) { - uint32_t offset = y * width + x; - uint8_t mask = 1 << (7 - (offset & 0x07)); - - if (on) - data[offset >> 3] |= mask; - else - data[offset >> 3] &= ~mask; + bitmap.SetBlackAndWhitePixel(infra::Point(x, y), on); } bool BitBucket::Get(uint8_t x, uint8_t y) const { - uint32_t offset = y * width + x; - return (data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; + return bitmap.BlackAndWhitePixel(infra::Point(x, y)); } void BitBucket::Invert(uint8_t x, uint8_t y, bool invert) { - uint32_t offset = y * width + x; - uint8_t mask = 1 << (7 - (offset & 0x07)); - bool on = ((data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0); - if (on ^ invert) - data[offset >> 3] |= mask; - else - data[offset >> 3] &= ~mask; + if (invert) + Set(x, y, !Get(x, y)); } // XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical @@ -235,7 +205,7 @@ void BitBucket::Invert(uint8_t x, uint8_t y, bool invert) // well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). static void applyMask(BitBucket* modules, BitBucket* isFunction, uint8_t mask) { - uint8_t size = modules->width; + uint8_t size = modules->bitmap.size.deltaX; for (uint8_t y = 0; y != size; ++y) { @@ -286,7 +256,7 @@ static void setFunctionModule(BitBucket* modules, BitBucket* isFunction, uint8_t // Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). static void drawFinderPattern(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y) { - uint8_t size = modules->width; + uint8_t size = modules->bitmap.size.deltaX; for (int8_t i = -4; i <= 4; i++) { @@ -312,7 +282,7 @@ static void drawAlignmentPattern(BitBucket* modules, BitBucket* isFunction, uint // based on the given mask and this object's error correction level field. static void drawFormatBits(BitBucket* modules, BitBucket* isFunction, uint8_t ecc, uint8_t mask) { - uint8_t size = modules->width; + uint8_t size = modules->bitmap.size.deltaX; // Calculate error correction code and pack bits uint32_t data = ecc << 3 | mask; // ecc is uint2, mask is uint3 @@ -348,7 +318,7 @@ static void drawFormatBits(BitBucket* modules, BitBucket* isFunction, uint8_t ec // based on this object's version field (which only has an effect for 7 <= version <= 40). static void drawVersion(BitBucket* modules, BitBucket* isFunction, uint8_t version) { - int8_t size = modules->width; + int8_t size = modules->bitmap.size.deltaX; if (version < 7) return; @@ -373,7 +343,7 @@ static void drawVersion(BitBucket* modules, BitBucket* isFunction, uint8_t versi static void drawFunctionPatterns(BitBucket* modules, BitBucket* isFunction, uint8_t version, uint8_t ecc) { - uint8_t size = modules->width; + uint8_t size = modules->bitmap.size.deltaX; // Draw the horizontal and vertical timing patterns for (uint8_t i = 0; i != size; ++i) @@ -428,7 +398,7 @@ static void drawCodewords(BitBucket* modules, BitBucket* isFunction, BitBuffer* uint32_t bitLength = codewords->bitOffset; uint8_t* data = codewords->data; - uint8_t size = modules->width; + uint8_t size = modules->bitmap.size.deltaX; // Bit index into the data uint32_t i = 0; @@ -469,7 +439,7 @@ uint32_t BitBucket::PenaltyScore() const { uint32_t result = 0; - uint8_t size = width; + uint8_t size = bitmap.size.deltaX; // Adjacent modules in row having same color for (uint8_t y = 0; y < size; ++y) @@ -749,11 +719,11 @@ void BitBuffer::PerformErrorCorrection(uint8_t version, uint8_t ecc) // The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc) static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); -QRCode::QRCode(infra::ByteRange modules, uint8_t version, Ecc ecc, infra::BoundedConstString text) +QRCode::QRCode(uint8_t version, Ecc ecc, infra::BoundedConstString text) : version(version) , size(version * 4 + 17) , ecc(ecc) - , modules(modules) + , modulesGrid(size) { uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * static_cast(ecc))) & 0x03; @@ -775,9 +745,7 @@ QRCode::QRCode(infra::ByteRange modules, uint8_t version, Ecc ecc, infra::Bounde for (uint8_t padByte = 0xEC; codewords.bitOffset < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) codewords.Append(padByte, 8); - BitBucket modulesGrid(modules.begin(), size); - - BitBucket isFunctionGrid(AllocOnStack(GridSizeInBytes(size)), size); + BitBucket isFunctionGrid(size); // Draw function patterns, draw all codewords, do masking drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits); @@ -809,10 +777,6 @@ QRCode::QRCode(infra::ByteRange modules, uint8_t version, Ecc ecc, infra::Bounde bool QRCode::getModule(uint8_t x, uint8_t y) const { - if (x < 0 || x >= size || y < 0 || y >= size) - return false; - - uint32_t offset = y * size + x; - return (modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; + return modulesGrid.bitmap.BlackAndWhitePixel(infra::Point(x, y)); } diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QRCode.hpp index efba969..6773fa7 100644 --- a/preview/interfaces/QRCode.hpp +++ b/preview/interfaces/QRCode.hpp @@ -41,6 +41,22 @@ #include "preview/interfaces/Bitmap.hpp" #include +class BitBucket +{ +public: + BitBucket(uint8_t size); + + void Set(uint8_t x, uint8_t y, bool on); + bool Get(uint8_t x, uint8_t y) const; + void Invert(uint8_t x, uint8_t y, bool invert); + + uint32_t PenaltyScore() const; + +public: + std::uint8_t* buffer; + infra::Bitmap bitmap; +}; + class QRCode { public: @@ -64,17 +80,15 @@ class QRCode }; public: - template - using Version = infra::WithStorage>; - - QRCode(infra::ByteRange modules, uint8_t version, Ecc ecc, infra::BoundedConstString text); + QRCode(uint8_t version, Ecc ecc, infra::BoundedConstString text); bool getModule(uint8_t x, uint8_t y) const; public: uint8_t version; uint8_t size; Ecc ecc; - infra::ByteRange modules; + + BitBucket modulesGrid; }; #endif From 66915edc2837c877631d078c6fe5a28f0c2e0e0a Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Tue, 10 Sep 2024 14:38:10 +0200 Subject: [PATCH 04/22] Use infra::Point --- examples/clicking_scrolling/MainWin.cpp | 9 +- preview/interfaces/Geometry.cpp | 58 ++++++++ preview/interfaces/Geometry.hpp | 21 +++ preview/interfaces/QRCode.cpp | 169 +++++++++++------------ preview/interfaces/QRCode.hpp | 9 +- preview/interfaces/test/TestGeometry.cpp | 20 +++ preview/views/ViewBitmap.cpp | 6 +- preview/views/ViewBitmap.hpp | 8 +- 8 files changed, 193 insertions(+), 107 deletions(-) diff --git a/examples/clicking_scrolling/MainWin.cpp b/examples/clicking_scrolling/MainWin.cpp index 3ae9d6f..40000a1 100644 --- a/examples/clicking_scrolling/MainWin.cpp +++ b/examples/clicking_scrolling/MainWin.cpp @@ -17,14 +17,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine infra::LowPowerEventDispatcher::WithSize<50> eventDispatcher(lowPowerStrategy); QRCode qrcode(3, QRCode::Ecc::low, "HELLO WORLD"); - - infra::Bitmap::BlackAndWhite<29, 29> qrCodeBitmap; - - for (uint8_t y = 0; y < qrcode.size; y++) - for (uint8_t x = 0; x < qrcode.size; x++) - qrCodeBitmap.SetBlackAndWhitePixel(infra::Point(x, y), qrcode.getModule(x, y) == 0); - - services::ViewBitmap viewBitmap(qrCodeBitmap); + services::ViewBitmap viewBitmap(qrcode.GetBitmap()); hal::DirectDisplaySdl display(infra::Vector(480, 272)); services::ViewPainterDirectDisplay painter(display); diff --git a/preview/interfaces/Geometry.cpp b/preview/interfaces/Geometry.cpp index 4c88fa2..c0e7d4a 100644 --- a/preview/interfaces/Geometry.cpp +++ b/preview/interfaces/Geometry.cpp @@ -522,6 +522,59 @@ namespace infra return !(*this == other); } + RowFirstPoints::RowFirstPoints(infra::Region region) + : region(region) + , current(region.TopLeft()) + {} + + RowFirstPoints RowFirstPoints::begin() const + { + return *this; + } + + RowFirstPoints RowFirstPoints::end() const + { + RowFirstPoints result(*this); + result.current = region.BottomRight(); + result.current.x = region.Left(); + return result; + } + + Point RowFirstPoints::operator*() const + { + return current; + } + + RowFirstPoints& RowFirstPoints::operator++() + { + ++current.x; + + if (current.x == region.Right()) + { + current.x = region.Left(); + ++current.y; + } + + return *this; + } + + RowFirstPoints RowFirstPoints::operator++(int) const + { + RowFirstPoints result(*this); + ++result; + return result; + } + + bool RowFirstPoints::operator==(const RowFirstPoints& other) const + { + return region == other.region && current == other.current; + } + + bool RowFirstPoints::operator!=(const RowFirstPoints& other) const + { + return !(*this == other); + } + Region Intersection(Region first, Region second) { if (first.Top() >= second.Bottom() || first.Bottom() <= second.Top() || first.Left() >= second.Right() || first.Right() <= second.Left()) @@ -583,6 +636,11 @@ namespace infra return std::abs(first.x - second.x) + std::abs(first.y - second.y); } + uint32_t ChebyshevDistance(Point first, Point second) + { + return std::max(std::abs(first.x - second.x), std::abs(first.y - second.y)); + } + uint32_t Distance(Point first, Point second) { return SquareRoot(Square(first.x - second.x) + Square(first.y - second.y)); diff --git a/preview/interfaces/Geometry.hpp b/preview/interfaces/Geometry.hpp index df194db..10ba3e2 100644 --- a/preview/interfaces/Geometry.hpp +++ b/preview/interfaces/Geometry.hpp @@ -176,6 +176,26 @@ namespace infra Vector size; }; + class RowFirstPoints + { + public: + explicit RowFirstPoints(infra::Region region); + + RowFirstPoints begin() const; + RowFirstPoints end() const; + + Point operator*() const; + RowFirstPoints& operator++(); + RowFirstPoints operator++(int) const; + + bool operator==(const RowFirstPoints& other) const; + bool operator!=(const RowFirstPoints& other) const; + + private: + infra::Region region; + infra::Point current; + }; + Region Intersection(Region first, Region second); Region Union(Region first, Region second); Region operator&(Region first, Region second); @@ -186,6 +206,7 @@ namespace infra Vector Flip(Vector vector); Region Flip(Region region); uint32_t ManhattanDistance(Point first, Point second); + uint32_t ChebyshevDistance(Point first, Point second); uint32_t Distance(Point first, Point second); Point AlignedUp(Point point, uint16_t alignX, uint16_t alignY); Point AlignedDown(Point point, uint16_t alignX, uint16_t alignY); diff --git a/preview/interfaces/QRCode.cpp b/preview/interfaces/QRCode.cpp index 221aa29..3b3387b 100644 --- a/preview/interfaces/QRCode.cpp +++ b/preview/interfaces/QRCode.cpp @@ -157,7 +157,6 @@ class BitBuffer uint8_t* data; }; - BitBuffer::BitBuffer(uint8_t* data, int32_t capacityBytes) : capacityBytes(capacityBytes) , data(data) @@ -183,20 +182,20 @@ BitBucket::BitBucket(uint8_t size) memset(buffer, 0, QRCode::GridSizeInBytes(size)); } -void BitBucket::Set(uint8_t x, uint8_t y, bool on) +void BitBucket::Set(infra::Point position, bool on) { - bitmap.SetBlackAndWhitePixel(infra::Point(x, y), on); + bitmap.SetBlackAndWhitePixel(position, on); } -bool BitBucket::Get(uint8_t x, uint8_t y) const +bool BitBucket::Get(infra::Point position) const { - return bitmap.BlackAndWhitePixel(infra::Point(x, y)); + return bitmap.BlackAndWhitePixel(position); } -void BitBucket::Invert(uint8_t x, uint8_t y, bool invert) +void BitBucket::Invert(infra::Point position, bool invert) { if (invert) - Set(x, y, !Get(x, y)); + Set(position, !Get(position)); } // XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical @@ -207,75 +206,69 @@ static void applyMask(BitBucket* modules, BitBucket* isFunction, uint8_t mask) { uint8_t size = modules->bitmap.size.deltaX; - for (uint8_t y = 0; y != size; ++y) + for (auto position : infra::RowFirstPoints(infra::Region(infra::Point(), modules->bitmap.size))) { - for (uint8_t x = 0; x != size; ++x) - { - if (isFunction->Get(x, y)) - continue; + if (isFunction->Get(position)) + continue; - bool invert = 0; - switch (mask) - { - case 0: - invert = (x + y) % 2 == 0; - break; - case 1: - invert = y % 2 == 0; - break; - case 2: - invert = x % 3 == 0; - break; - case 3: - invert = (x + y) % 3 == 0; - break; - case 4: - invert = (x / 3 + y / 2) % 2 == 0; - break; - case 5: - invert = x * y % 2 + x * y % 3 == 0; - break; - case 6: - invert = (x * y % 2 + x * y % 3) % 2 == 0; - break; - case 7: - invert = ((x + y) % 2 + x * y % 3) % 2 == 0; - break; - } - modules->Invert(x, y, invert); + bool invert = 0; + switch (mask) + { + case 0: + invert = (position.x + position.y) % 2 == 0; + break; + case 1: + invert = position.y % 2 == 0; + break; + case 2: + invert = position.x % 3 == 0; + break; + case 3: + invert = (position.x + position.y) % 3 == 0; + break; + case 4: + invert = (position.x / 3 + position.y / 2) % 2 == 0; + break; + case 5: + invert = position.x * position.y % 2 + position.x * position.y % 3 == 0; + break; + case 6: + invert = (position.x * position.y % 2 + position.x * position.y % 3) % 2 == 0; + break; + case 7: + invert = ((position.x + position.y) % 2 + position.x * position.y % 3) % 2 == 0; + break; } + modules->Invert(position, invert); } } -static void setFunctionModule(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y, bool on) +static void setFunctionModule(BitBucket* modules, BitBucket* isFunction, infra::Point position, bool on) { - modules->Set(x, y, on); - isFunction->Set(x, y, true); + modules->Set(position, on); + isFunction->Set(position, true); } // Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). -static void drawFinderPattern(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y) +static void drawFinderPattern(BitBucket* modules, BitBucket* isFunction, infra::Point position) { - uint8_t size = modules->bitmap.size.deltaX; + auto bitmapRegion = infra::Region(infra::Point(), modules->bitmap.size); + auto finderRegion = infra::Region(infra::Point(-4, -4), infra::Point(5, 5)) >> (position - infra::Point()); - for (int8_t i = -4; i <= 4; i++) + for (auto i : infra::RowFirstPoints(infra::Intersection(finderRegion, bitmapRegion))) { - for (int8_t j = -4; j <= 4; j++) - { - uint8_t dist = std::max(std::abs(i), std::abs(j)); // Chebyshev/infinity norm - int16_t xx = x + j, yy = y + i; - if (0 <= xx && xx < size && 0 <= yy && yy < size) - setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4); - } + auto dist = infra::ChebyshevDistance(i, position); + setFunctionModule(modules, isFunction, i, dist != 2 && dist != 4); } } // Draws a 5*5 alignment pattern, with the center module at (x, y). -static void drawAlignmentPattern(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y) +static void drawAlignmentPattern(BitBucket* modules, BitBucket* isFunction, infra::Point position) { - for (int8_t i = -2; i != 3; ++i) - for (int8_t j = -2; j != 3; ++j) - setFunctionModule(modules, isFunction, x + j, y + i, std::max(std::abs(i), std::abs(j)) != 1); + auto alignmentRegion = infra::Region(infra::Point(-2, -2), infra::Point(3, 3)) >> (position - infra::Point()); + + for (auto i : infra::RowFirstPoints(alignmentRegion)) + setFunctionModule(modules, isFunction, i, infra::ChebyshevDistance(i, position) != 1); } // Draws two copies of the format bits (with its own error correction code) @@ -295,23 +288,23 @@ static void drawFormatBits(BitBucket* modules, BitBucket* isFunction, uint8_t ec // Draw first copy for (uint8_t i = 0; i <= 5; i++) - setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0); + setFunctionModule(modules, isFunction, infra::Point(8, i), ((data >> i) & 1) != 0); - setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0); - setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0); - setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0); + setFunctionModule(modules, isFunction, infra::Point(8, 7), ((data >> 6) & 1) != 0); + setFunctionModule(modules, isFunction, infra::Point(8, 8), ((data >> 7) & 1) != 0); + setFunctionModule(modules, isFunction, infra::Point(7, 8), ((data >> 8) & 1) != 0); for (int8_t i = 9; i < 15; i++) - setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0); + setFunctionModule(modules, isFunction, infra::Point(14 - i, 8), ((data >> i) & 1) != 0); // Draw second copy for (int8_t i = 0; i <= 7; i++) - setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0); + setFunctionModule(modules, isFunction, infra::Point(size - 1 - i, 8), ((data >> i) & 1) != 0); for (int8_t i = 8; i < 15; i++) - setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0); + setFunctionModule(modules, isFunction, infra::Point(8, size - 15 + i), ((data >> i) & 1) != 0); - setFunctionModule(modules, isFunction, 8, size - 8, true); + setFunctionModule(modules, isFunction, infra::Point(8, size - 8), true); } // Draws two copies of the version bits (with its own error correction code), @@ -336,8 +329,8 @@ static void drawVersion(BitBucket* modules, BitBucket* isFunction, uint8_t versi bool bit = ((data >> i) & 1) != 0; uint8_t a = size - 11 + i % 3; uint8_t b = i / 3; - setFunctionModule(modules, isFunction, a, b, bit); - setFunctionModule(modules, isFunction, b, a, bit); + setFunctionModule(modules, isFunction, infra::Point(a, b), bit); + setFunctionModule(modules, isFunction, infra::Point(b, a), bit); } } @@ -348,14 +341,14 @@ static void drawFunctionPatterns(BitBucket* modules, BitBucket* isFunction, uint // Draw the horizontal and vertical timing patterns for (uint8_t i = 0; i != size; ++i) { - setFunctionModule(modules, isFunction, 6, i, i % 2 == 0); - setFunctionModule(modules, isFunction, i, 6, i % 2 == 0); + setFunctionModule(modules, isFunction, infra::Point(6, i), i % 2 == 0); + setFunctionModule(modules, isFunction, infra::Point(i, 6), i % 2 == 0); } // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) - drawFinderPattern(modules, isFunction, 3, 3); - drawFinderPattern(modules, isFunction, size - 4, 3); - drawFinderPattern(modules, isFunction, 3, size - 4); + drawFinderPattern(modules, isFunction, infra::Point(3, 3)); + drawFinderPattern(modules, isFunction, infra::Point(size - 4, 3)); + drawFinderPattern(modules, isFunction, infra::Point(3, size - 4)); if (version > 1) { @@ -383,7 +376,7 @@ static void drawFunctionPatterns(BitBucket* modules, BitBucket* isFunction, uint if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) continue; // Skip the three finder corners else - drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]); + drawAlignmentPattern(modules, isFunction, infra::Point(alignPosition[i], alignPosition[j])); } // Draw configuration data @@ -416,9 +409,10 @@ static void drawCodewords(BitBucket* modules, BitBucket* isFunction, BitBuffer* uint8_t x = right - j; // Actual x coordinate bool upwards = ((right & 2) == 0) ^ (x < 6); uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate - if (!isFunction->Get(x, y) && i < bitLength) + auto position = infra::Point(x, y); + if (!isFunction->Get(position) && i < bitLength) { - modules->Set(x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0); + modules->Set(position, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0); ++i; } // If there are any remainder bits (0 to 7), they are already @@ -444,10 +438,10 @@ uint32_t BitBucket::PenaltyScore() const // Adjacent modules in row having same color for (uint8_t y = 0; y < size; ++y) { - bool colorX = Get(0, y); + bool colorX = Get(infra::Point(0, y)); for (uint8_t x = 1, runX = 1; x != size; ++x) { - bool cx = Get(x, y); + bool cx = Get(infra::Point(x, y)); if (cx != colorX) { colorX = cx; @@ -467,10 +461,10 @@ uint32_t BitBucket::PenaltyScore() const // Adjacent modules in column having same color for (uint8_t x = 0; x != size; ++x) { - bool colorY = Get(x, 0); + bool colorY = Get(infra::Point(x, 0)); for (uint8_t y = 1, runY = 1; y != size; ++y) { - bool cy = Get(x, y); + bool cy = Get(infra::Point(x, y)); if (cy != colorY) { colorY = cy; @@ -493,21 +487,21 @@ uint32_t BitBucket::PenaltyScore() const uint16_t bitsRow = 0, bitsCol = 0; for (uint8_t x = 0; x != size; ++x) { - bool color = Get(x, y); + bool color = Get(infra::Point(x, y)); // 2*2 blocks of modules having same color if (x > 0 && y > 0) { - bool colorUL = Get(x - 1, y - 1); - bool colorUR = Get(x, y - 1); - bool colorL = Get(x - 1, y); + bool colorUL = Get(infra::Point(x - 1, y - 1)); + bool colorUR = Get(infra::Point(x, y - 1)); + bool colorL = Get(infra::Point(x - 1, y)); if (color == colorUL && color == colorUR && color == colorL) result += PENALTY_N2; } // Finder-like pattern in rows and columns bitsRow = ((bitsRow << 1) & 0x7FF) | static_cast(color); - bitsCol = ((bitsCol << 1) & 0x7FF) | static_cast(Get(y, x)); + bitsCol = ((bitsCol << 1) & 0x7FF) | static_cast(Get(infra::Point(y, x))); // Needs 11 bits accumulated if (x >= 10) @@ -775,8 +769,7 @@ QRCode::QRCode(uint8_t version, Ecc ecc, infra::BoundedConstString text) applyMask(&modulesGrid, &isFunctionGrid, mask); } -bool QRCode::getModule(uint8_t x, uint8_t y) const +const infra::Bitmap& QRCode::GetBitmap() const { - return modulesGrid.bitmap.BlackAndWhitePixel(infra::Point(x, y)); + return modulesGrid.bitmap; } - diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QRCode.hpp index 6773fa7..b6143bf 100644 --- a/preview/interfaces/QRCode.hpp +++ b/preview/interfaces/QRCode.hpp @@ -46,9 +46,9 @@ class BitBucket public: BitBucket(uint8_t size); - void Set(uint8_t x, uint8_t y, bool on); - bool Get(uint8_t x, uint8_t y) const; - void Invert(uint8_t x, uint8_t y, bool invert); + void Set(infra::Point position, bool on); + bool Get(infra::Point position) const; + void Invert(infra::Point position, bool invert); uint32_t PenaltyScore() const; @@ -81,7 +81,8 @@ class QRCode public: QRCode(uint8_t version, Ecc ecc, infra::BoundedConstString text); - bool getModule(uint8_t x, uint8_t y) const; + + const infra::Bitmap& GetBitmap() const; public: uint8_t version; diff --git a/preview/interfaces/test/TestGeometry.cpp b/preview/interfaces/test/TestGeometry.cpp index 3572e76..496d5d6 100644 --- a/preview/interfaces/test/TestGeometry.cpp +++ b/preview/interfaces/test/TestGeometry.cpp @@ -207,6 +207,16 @@ TEST(GeometryTest, RegionOffset) EXPECT_EQ(infra::Region(infra::Point(1, 2), infra::Vector(3, 4)), infra::Region(infra::Point(1, 2), infra::Vector(3, 4)) + infra::RegionOffset({ 1, 0 }, { 1, 50 }, { 1, 0 }, { 1, 50 }) - infra::RegionOffset({ 1, 0 }, { 1, 50 }, { 1, 0 }, { 1, 50 })); } +TEST(GeometryTest, RowFirstPoints) +{ + std::vector points; + + for (auto i : infra::RowFirstPoints(infra::Region(infra::Point(1, 2), infra::Point(3, 5)))) + points.push_back(i); + + EXPECT_EQ((std::vector{ { 1, 2 }, { 2, 2 }, { 1, 3 }, { 2, 3 }, { 1, 4 }, { 2, 4 } }), points); +} + TEST(GeometryTest, RestrictedSum) { EXPECT_EQ(9, infra::RestrictedInt16Sum(5, 4)); @@ -230,6 +240,16 @@ TEST(GeometryTest, ManhattanDistance) EXPECT_EQ(20, infra::ManhattanDistance(infra::Point(10, 10), infra::Point())); } +TEST(GeometryTest, ChebyshevDistance) +{ + EXPECT_EQ(0, infra::ChebyshevDistance(infra::Point(), infra::Point())); + EXPECT_EQ(10, infra::ChebyshevDistance(infra::Point(), infra::Point(10, 0))); + EXPECT_EQ(10, infra::ChebyshevDistance(infra::Point(), infra::Point(0, 10))); + EXPECT_EQ(10, infra::ChebyshevDistance(infra::Point(10, 0), infra::Point())); + EXPECT_EQ(10, infra::ChebyshevDistance(infra::Point(0, 10), infra::Point())); + EXPECT_EQ(10, infra::ChebyshevDistance(infra::Point(10, 10), infra::Point())); +} + TEST(GeometryTest, Aligned) { EXPECT_EQ(infra::Region(infra::Point(2, 3), infra::Vector(4, 6)), infra::AlignedRegion(infra::Region(infra::Point(2, 3), infra::Vector(4, 6)), 2, 3)); diff --git a/preview/views/ViewBitmap.cpp b/preview/views/ViewBitmap.cpp index bc3cba6..30f3db9 100644 --- a/preview/views/ViewBitmap.cpp +++ b/preview/views/ViewBitmap.cpp @@ -2,7 +2,7 @@ namespace services { - ViewBitmap::ViewBitmap(infra::Bitmap& bitmap) + ViewBitmap::ViewBitmap(const infra::Bitmap& bitmap) : source(bitmap) { Resize(bitmap.size); @@ -13,13 +13,13 @@ namespace services canvas.DrawBitmap(ViewRegion().TopLeft(), source, ViewRegion() & boundingRegion); } - void ViewBitmap::Source(infra::Bitmap& source) + void ViewBitmap::Source(const infra::Bitmap& source) { this->source = source; Dirty(ViewRegion()); } - infra::Bitmap& ViewBitmap::Source() const + const infra::Bitmap& ViewBitmap::Source() const { return source; } diff --git a/preview/views/ViewBitmap.hpp b/preview/views/ViewBitmap.hpp index 85fbc2e..9a6ba20 100644 --- a/preview/views/ViewBitmap.hpp +++ b/preview/views/ViewBitmap.hpp @@ -10,16 +10,16 @@ namespace services : public View { public: - explicit ViewBitmap(infra::Bitmap& bitmap); + explicit ViewBitmap(const infra::Bitmap& bitmap); // Implementation of View void Paint(hal::Canvas& canvas, infra::Region boundingRegion) override; - void Source(infra::Bitmap& source); - infra::Bitmap& Source() const; + void Source(const infra::Bitmap& source); + const infra::Bitmap& Source() const; private: - infra::Bitmap& source; + infra::Bitmap source; }; } From cd5faf08d9a0eec0917401d650c6fd3946a701a4 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Wed, 11 Sep 2024 07:13:11 +0200 Subject: [PATCH 05/22] Refactor into BitBuffer --- preview/interfaces/QRCode.cpp | 818 +++++++++++++++++----------------- preview/interfaces/QRCode.hpp | 72 ++- 2 files changed, 472 insertions(+), 418 deletions(-) diff --git a/preview/interfaces/QRCode.cpp b/preview/interfaces/QRCode.cpp index 3b3387b..384f92b 100644 --- a/preview/interfaces/QRCode.cpp +++ b/preview/interfaces/QRCode.cpp @@ -58,15 +58,6 @@ static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = { { 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68 }, // Quartile }; -static const uint16_t NUM_RAW_DATA_MODULES[40] = { - // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523, - // 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587, - // 32, 33, 34, 35, 36, 37, 38, 39, 40 - 19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648 -}; - static int8_t getAlphanumeric(char c) { if (c >= '0' && c <= '9') @@ -142,284 +133,248 @@ static char getModeBits(uint8_t version, uint8_t mode) return result; } -class BitBuffer -{ -public: - BitBuffer(uint8_t* data, int32_t capacityBytes); - - void Append(uint32_t val, uint8_t length); - void EncodeDataCodewords(infra::BoundedConstString text, uint8_t version); - void PerformErrorCorrection(uint8_t version, uint8_t ecc); - -public: - uint32_t bitOffset = 0; - uint16_t capacityBytes; - uint8_t* data; -}; - -BitBuffer::BitBuffer(uint8_t* data, int32_t capacityBytes) - : capacityBytes(capacityBytes) - , data(data) +static uint8_t rs_multiply(uint8_t x, uint8_t y) { - memset(data, 0, capacityBytes); + // Russian peasant multiplication + // See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication + uint16_t z = 0; + for (int8_t i = 7; i >= 0; i--) + { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + return z; } -void BitBuffer::Append(uint32_t val, uint8_t length) +static void rs_init(uint8_t degree, uint8_t* coeff) { - for (int8_t i = length - 1; i >= 0; --i, ++bitOffset) - data[bitOffset >> 3] |= ((val >> i) & 1) << (7 - (bitOffset & 7)); + memset(coeff, 0, degree); + coeff[degree - 1] = 1; + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // drop the highest term, and store the rest of the coefficients in order of descending powers. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint16_t root = 1; + for (uint8_t i = 0; i < degree; i++) + { + // Multiply the current product by (x - r^i) + for (uint8_t j = 0; j != degree; ++j) + { + coeff[j] = rs_multiply(coeff[j], root); + if (j + 1 < degree) + coeff[j] ^= coeff[j + 1]; + } + root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) + } } -static uint16_t RoundBitsToByte(uint32_t bits) +static void rs_getRemainder(uint8_t degree, uint8_t* coeff, uint8_t* data, uint8_t length, uint8_t* result, uint8_t stride) { - return (bits + 7) / 8; + // Compute the remainder by performing polynomial division + for (uint8_t i = 0; i < length; i++) + { + uint8_t factor = data[i] ^ result[0]; + for (uint8_t j = 1; j != degree; ++j) + result[(j - 1) * stride] = result[j * stride]; + + result[(degree - 1) * stride] = 0; + + for (uint8_t j = 0; j != degree; ++j) + result[j * stride] ^= rs_multiply(coeff[j], factor); + } } -BitBucket::BitBucket(uint8_t size) - : buffer(new uint8_t[QRCode::GridSizeInBytes(size)]) - , bitmap(infra::ByteRange(buffer, buffer + QRCode::GridSizeInBytes(size)), infra::Vector(size, size), infra::PixelFormat::blackandwhite) +BitBuffer::BitBuffer(uint8_t version, infra::BoundedConstString text, uint8_t ecc) + : version(version) + , moduleCount(numRawDataModulesForVersion[version]) + , capacityBytes(RoundBitsToByte(moduleCount)) + , data(new uint8_t[capacityBytes]) + , result(new uint8_t[capacityBytes]) { - memset(buffer, 0, QRCode::GridSizeInBytes(size)); + std::memset(data, 0, capacityBytes); + std::memset(result, 0, capacityBytes); + + uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1]; + + // Place the data code words into the buffer + EncodeDataCodewords(text); + + // Add terminator and pad up to a byte if applicable + uint32_t padding = std::min((dataCapacity * 8) - bitOffset, 4); + + Append(0, padding); + Append(0, (8 - bitOffset % 8) % 8); + + // Pad with alternate bytes until data capacity is reached + for (uint8_t padByte = 0xEC; bitOffset != dataCapacity * 8; padByte ^= 0xEC ^ 0x11) + Append(padByte, 8); + + PerformErrorCorrection(ecc); } -void BitBucket::Set(infra::Point position, bool on) +uint16_t BitBuffer::Length() const { - bitmap.SetBlackAndWhitePixel(position, on); + return bitOffset; } -bool BitBucket::Get(infra::Point position) const +bool BitBuffer::Bit(uint16_t index) const { - return bitmap.BlackAndWhitePixel(position); + return ((result[index >> 3] >> (7 - (index & 7))) & 1) != 0; } -void BitBucket::Invert(infra::Point position, bool invert) +void BitBuffer::Append(uint32_t val, uint8_t length) { - if (invert) - Set(position, !Get(position)); + for (int8_t i = length - 1; i >= 0; --i, ++bitOffset) + data[bitOffset >> 3] |= ((val >> i) & 1) << (7 - (bitOffset & 7)); } -// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical -// properties, calling applyMask(m) twice with the same value is equivalent to no change at all. -// This means it is possible to apply a mask, undo it, and try another mask. Note that a final -// well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). -static void applyMask(BitBucket* modules, BitBucket* isFunction, uint8_t mask) +void BitBuffer::EncodeDataCodewords(infra::BoundedConstString text) { - uint8_t size = modules->bitmap.size.deltaX; +#define MODE_NUMERIC 0 +#define MODE_ALPHANUMERIC 1 +#define MODE_BYTE 2 - for (auto position : infra::RowFirstPoints(infra::Region(infra::Point(), modules->bitmap.size))) + if (isNumeric(text)) { - if (isFunction->Get(position)) - continue; + Append(1 << MODE_NUMERIC, 4); + Append(text.size(), getModeBits(version, MODE_NUMERIC)); - bool invert = 0; - switch (mask) + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (auto c : text) { - case 0: - invert = (position.x + position.y) % 2 == 0; - break; - case 1: - invert = position.y % 2 == 0; - break; - case 2: - invert = position.x % 3 == 0; - break; - case 3: - invert = (position.x + position.y) % 3 == 0; - break; - case 4: - invert = (position.x / 3 + position.y / 2) % 2 == 0; - break; - case 5: - invert = position.x * position.y % 2 + position.x * position.y % 3 == 0; - break; - case 6: - invert = (position.x * position.y % 2 + position.x * position.y % 3) % 2 == 0; - break; - case 7: - invert = ((position.x + position.y) % 2 + position.x * position.y % 3) % 2 == 0; - break; + accumData = accumData * 10 + (c - '0'); + ++accumCount; + if (accumCount == 3) + { + Append(accumData, 10); + accumData = 0; + accumCount = 0; + } } - modules->Invert(position, invert); - } -} -static void setFunctionModule(BitBucket* modules, BitBucket* isFunction, infra::Point position, bool on) -{ - modules->Set(position, on); - isFunction->Set(position, true); -} + // 1 or 2 digits remaining + if (accumCount > 0) + Append(accumData, accumCount * 3 + 1); + } + else if (isAlphanumeric(text)) + { + Append(1 << MODE_ALPHANUMERIC, 4); + Append(text.size(), getModeBits(version, MODE_ALPHANUMERIC)); -// Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). -static void drawFinderPattern(BitBucket* modules, BitBucket* isFunction, infra::Point position) -{ - auto bitmapRegion = infra::Region(infra::Point(), modules->bitmap.size); - auto finderRegion = infra::Region(infra::Point(-4, -4), infra::Point(5, 5)) >> (position - infra::Point()); + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (auto c : text) + { + accumData = accumData * 45 + getAlphanumeric(c); + ++accumCount; + if (accumCount == 2) + { + Append(accumData, 11); + accumData = 0; + accumCount = 0; + } + } - for (auto i : infra::RowFirstPoints(infra::Intersection(finderRegion, bitmapRegion))) + if (accumCount == 1) + Append(accumData, 6); + } + else { - auto dist = infra::ChebyshevDistance(i, position); - setFunctionModule(modules, isFunction, i, dist != 2 && dist != 4); + Append(1 << MODE_BYTE, 4); + Append(text.size(), getModeBits(version, MODE_BYTE)); + for (auto c : text) + Append(c, 8); } } -// Draws a 5*5 alignment pattern, with the center module at (x, y). -static void drawAlignmentPattern(BitBucket* modules, BitBucket* isFunction, infra::Point position) +void BitBuffer::PerformErrorCorrection(uint8_t ecc) { - auto alignmentRegion = infra::Region(infra::Point(-2, -2), infra::Point(3, 3)) >> (position - infra::Point()); + // See: http://www.thonky.com/qr-code-tutorial/structure-final-message + uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1]; + uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1]; - for (auto i : infra::RowFirstPoints(alignmentRegion)) - setFunctionModule(modules, isFunction, i, infra::ChebyshevDistance(i, position) != 1); -} + uint8_t blockEccLen = totalEcc / numBlocks; + uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks; + uint8_t shortBlockLen = moduleCount / 8 / numBlocks; -// Draws two copies of the format bits (with its own error correction code) -// based on the given mask and this object's error correction level field. -static void drawFormatBits(BitBucket* modules, BitBucket* isFunction, uint8_t ecc, uint8_t mask) -{ - uint8_t size = modules->bitmap.size.deltaX; + uint8_t shortDataBlockLen = shortBlockLen - blockEccLen; - // Calculate error correction code and pack bits - uint32_t data = ecc << 3 | mask; // ecc is uint2, mask is uint3 - uint32_t rem = data; - for (int i = 0; i != 10; ++i) - rem = (rem << 1) ^ ((rem >> 9) * 0x537); + uint8_t* coeff = AllocOnStack(blockEccLen); + std::memset(coeff, 0, blockEccLen); + rs_init(blockEccLen, coeff); - data = data << 10 | rem; - data ^= 0x5412; // uint15 + uint16_t offset = 0; + uint8_t* dataBytes = data; - // Draw first copy - for (uint8_t i = 0; i <= 5; i++) - setFunctionModule(modules, isFunction, infra::Point(8, i), ((data >> i) & 1) != 0); + // Interleave all short blocks + for (uint8_t i = 0; i != shortDataBlockLen; ++i) + { + uint16_t index = i; + uint8_t stride = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum != numBlocks; ++blockNum) + { + result[offset++] = dataBytes[index]; - setFunctionModule(modules, isFunction, infra::Point(8, 7), ((data >> 6) & 1) != 0); - setFunctionModule(modules, isFunction, infra::Point(8, 8), ((data >> 7) & 1) != 0); - setFunctionModule(modules, isFunction, infra::Point(7, 8), ((data >> 8) & 1) != 0); + if (blockNum == numShortBlocks) + ++stride; - for (int8_t i = 9; i < 15; i++) - setFunctionModule(modules, isFunction, infra::Point(14 - i, 8), ((data >> i) & 1) != 0); + index += stride; + } + } - // Draw second copy - for (int8_t i = 0; i <= 7; i++) - setFunctionModule(modules, isFunction, infra::Point(size - 1 - i, 8), ((data >> i) & 1) != 0); + // Version less than 5 only have short blocks + { + // Interleave long blocks + uint16_t index = shortDataBlockLen * (numShortBlocks + 1); + uint8_t stride = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum != numBlocks - numShortBlocks; ++blockNum) + { + result[offset++] = dataBytes[index]; - for (int8_t i = 8; i < 15; i++) - setFunctionModule(modules, isFunction, infra::Point(8, size - 15 + i), ((data >> i) & 1) != 0); + if (blockNum == 0) + ++stride; - setFunctionModule(modules, isFunction, infra::Point(8, size - 8), true); -} + index += stride; + } + } -// Draws two copies of the version bits (with its own error correction code), -// based on this object's version field (which only has an effect for 7 <= version <= 40). -static void drawVersion(BitBucket* modules, BitBucket* isFunction, uint8_t version) -{ - int8_t size = modules->bitmap.size.deltaX; + // Add all ecc blocks, interleaved + uint8_t blockSize = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum != numBlocks; ++blockNum) + { - if (version < 7) - return; + if (blockNum == numShortBlocks) + ++blockSize; - // Calculate error correction code and pack bits - uint32_t rem = version; // version is uint6, in the range [7, 40] - for (uint8_t i = 0; i != 12; ++i) - rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks); + dataBytes += blockSize; + } - uint32_t data = version << 12 | rem; + bitOffset = moduleCount; +} - // Draw two copies - for (uint8_t i = 0; i != 18; ++i) - { - bool bit = ((data >> i) & 1) != 0; - uint8_t a = size - 11 + i % 3; - uint8_t b = i / 3; - setFunctionModule(modules, isFunction, infra::Point(a, b), bit); - setFunctionModule(modules, isFunction, infra::Point(b, a), bit); - } +BitBucket::BitBucket(uint8_t size) + : buffer(new uint8_t[QRCode::GridSizeInBytes(size)]) + , bitmap(infra::ByteRange(buffer, buffer + QRCode::GridSizeInBytes(size)), infra::Vector(size, size), infra::PixelFormat::blackandwhite) +{ + memset(buffer, 0, QRCode::GridSizeInBytes(size)); } -static void drawFunctionPatterns(BitBucket* modules, BitBucket* isFunction, uint8_t version, uint8_t ecc) +void BitBucket::Set(infra::Point position, bool on) { - uint8_t size = modules->bitmap.size.deltaX; + bitmap.SetBlackAndWhitePixel(position, on); +} - // Draw the horizontal and vertical timing patterns - for (uint8_t i = 0; i != size; ++i) - { - setFunctionModule(modules, isFunction, infra::Point(6, i), i % 2 == 0); - setFunctionModule(modules, isFunction, infra::Point(i, 6), i % 2 == 0); - } - - // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) - drawFinderPattern(modules, isFunction, infra::Point(3, 3)); - drawFinderPattern(modules, isFunction, infra::Point(size - 4, 3)); - drawFinderPattern(modules, isFunction, infra::Point(3, size - 4)); - - if (version > 1) - { - // Draw the numerous alignment patterns - - uint8_t alignCount = version / 7 + 2; - uint8_t step; - if (version != 32) - step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; // ceil((size - 13) / (2*numAlign - 2)) * 2 - else - step = 26; - - uint8_t alignPositionIndex = alignCount - 1; - uint8_t* alignPosition = AllocOnStack(alignCount); - std::memset(alignPosition, 0, alignCount); - - alignPosition[0] = 6; - - uint8_t size = version * 4 + 17; - for (uint8_t i = 0, pos = size - 7; i != alignCount - 1; ++i, pos -= step) - alignPosition[alignPositionIndex--] = pos; - - for (uint8_t i = 0; i != alignCount; ++i) - for (uint8_t j = 0; j != alignCount; ++j) - if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) - continue; // Skip the three finder corners - else - drawAlignmentPattern(modules, isFunction, infra::Point(alignPosition[i], alignPosition[j])); - } - - // Draw configuration data - drawFormatBits(modules, isFunction, ecc, 0); // Dummy mask value; overwritten later in the constructor - drawVersion(modules, isFunction, version); +bool BitBucket::Get(infra::Point position) const +{ + return bitmap.BlackAndWhitePixel(position); } -// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire -// data area of this QR Code symbol. Function modules need to be marked off before this is called. -static void drawCodewords(BitBucket* modules, BitBucket* isFunction, BitBuffer* codewords) +void BitBucket::Invert(infra::Point position, bool invert) { - uint32_t bitLength = codewords->bitOffset; - uint8_t* data = codewords->data; - - uint8_t size = modules->bitmap.size.deltaX; - - // Bit index into the data - uint32_t i = 0; - - // Do the funny zigzag scan - for (int16_t right = size - 1; right >= 1; right -= 2) - { // Index of right column in each column pair - if (right == 6) - right = 5; - - for (uint8_t vert = 0; vert != size; ++vert) - { // Vertical counter - for (int j = 0; j != 2; ++j) - { - uint8_t x = right - j; // Actual x coordinate - bool upwards = ((right & 2) == 0) ^ (x < 6); - uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate - auto position = infra::Point(x, y); - if (!isFunction->Get(position) && i < bitLength) - { - modules->Set(position, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0); - ++i; - } - // If there are any remainder bits (0 to 7), they are already - // set to 0/false/white when the grid of modules was initialized - } - } - } + if (invert) + Set(position, !Get(position)); } #define PENALTY_N1 3 @@ -527,249 +482,286 @@ uint32_t BitBucket::PenaltyScore() const return result; } -static uint8_t rs_multiply(uint8_t x, uint8_t y) +// We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits) +// The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc) +static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); + +QRCode::QRCodeGenerator::QRCodeGenerator(BitBucket& modules, uint8_t version, Ecc ecc, infra::BoundedConstString text) + : version(version) + , size(version * 4 + 17) + , ecc((ECC_FORMAT_BITS >> (2 * static_cast(ecc))) & 0x03) + , modules(modules) + , isFunction(size) + , codewords(version, text, this->ecc) + , alignCount(version / 7 + 2) + , alignPosition(new uint8_t[alignCount]) { - // Russian peasant multiplication - // See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication - uint16_t z = 0; - for (int8_t i = 7; i >= 0; i--) - { - z = (z << 1) ^ ((z >> 7) * 0x11D); - z ^= ((y >> i) & 1) * x; - } - return z; + std::memset(alignPosition, 0, alignCount); + + // Draw function patterns, draw all codewords, do masking + DrawFunctionPatterns(); + DrawCodewords(); + + // Find the best (lowest penalty) mask + uint8_t mask = BestMask(); + + // Overwrite old format bits + DrawFormatBits(mask); + + // Apply the final choice of mask + ApplyMask(mask); } -static void rs_init(uint8_t degree, uint8_t* coeff) +uint8_t QRCode::QRCodeGenerator::BestMask() { - memset(coeff, 0, degree); - coeff[degree - 1] = 1; + uint8_t mask = 0; - // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), - // drop the highest term, and store the rest of the coefficients in order of descending powers. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - uint16_t root = 1; - for (uint8_t i = 0; i < degree; i++) + int32_t minPenalty = INT32_MAX; + for (uint8_t i = 0; i != 8; ++i) { - // Multiply the current product by (x - r^i) - for (uint8_t j = 0; j != degree; ++j) + DrawFormatBits(i); + ApplyMask(i); + int penalty = modules.PenaltyScore(); + if (penalty < minPenalty) { - coeff[j] = rs_multiply(coeff[j], root); - if (j + 1 < degree) - coeff[j] ^= coeff[j + 1]; + mask = i; + minPenalty = penalty; } - root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) + ApplyMask(i); // Undoes the mask due to XOR } + + return mask; } -static void rs_getRemainder(uint8_t degree, uint8_t* coeff, uint8_t* data, uint8_t length, uint8_t* result, uint8_t stride) +// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical +// properties, calling ApplyMask(m) twice with the same value is equivalent to no change at all. +// This means it is possible to apply a mask, undo it, and try another mask. Note that a final +// well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). +void QRCode::QRCodeGenerator::ApplyMask(uint8_t mask) { - // Compute the remainder by performing polynomial division - for (uint8_t i = 0; i < length; i++) + uint8_t size = modules.bitmap.size.deltaX; + + for (auto position : infra::RowFirstPoints(infra::Region(infra::Point(), modules.bitmap.size))) { - uint8_t factor = data[i] ^ result[0]; - for (uint8_t j = 1; j != degree; ++j) - result[(j - 1) * stride] = result[j * stride]; + if (isFunction.Get(position)) + continue; - result[(degree - 1) * stride] = 0; + bool invert = 0; + switch (mask) + { + case 0: + invert = (position.x + position.y) % 2 == 0; + break; + case 1: + invert = position.y % 2 == 0; + break; + case 2: + invert = position.x % 3 == 0; + break; + case 3: + invert = (position.x + position.y) % 3 == 0; + break; + case 4: + invert = (position.x / 3 + position.y / 2) % 2 == 0; + break; + case 5: + invert = position.x * position.y % 2 + position.x * position.y % 3 == 0; + break; + case 6: + invert = (position.x * position.y % 2 + position.x * position.y % 3) % 2 == 0; + break; + case 7: + invert = ((position.x + position.y) % 2 + position.x * position.y % 3) % 2 == 0; + break; + } - for (uint8_t j = 0; j != degree; ++j) - result[j * stride] ^= rs_multiply(coeff[j], factor); + modules.Invert(position, invert); } } -void BitBuffer::EncodeDataCodewords(infra::BoundedConstString text, uint8_t version) +void QRCode::QRCodeGenerator::DrawFunctionPatterns() { -#define MODE_NUMERIC 0 -#define MODE_ALPHANUMERIC 1 -#define MODE_BYTE 2 + uint8_t size = modules.bitmap.size.deltaX; - if (isNumeric(text)) + // Draw the horizontal and vertical timing patterns + for (uint8_t i = 0; i != size; ++i) { - Append(1 << MODE_NUMERIC, 4); - Append(text.size(), getModeBits(version, MODE_NUMERIC)); + SetFunctionModule(infra::Point(6, i), i % 2 == 0); + SetFunctionModule(infra::Point(i, 6), i % 2 == 0); + } - uint16_t accumData = 0; - uint8_t accumCount = 0; - for (auto c : text) - { - accumData = accumData * 10 + (c - '0'); - ++accumCount; - if (accumCount == 3) - { - Append(accumData, 10); - accumData = 0; - accumCount = 0; - } - } + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + DrawFinderPattern(infra::Point(3, 3)); + DrawFinderPattern(infra::Point(size - 4, 3)); + DrawFinderPattern(infra::Point(3, size - 4)); - // 1 or 2 digits remaining - if (accumCount > 0) - Append(accumData, accumCount * 3 + 1); - } - else if (isAlphanumeric(text)) + if (version > 1) { - Append(1 << MODE_ALPHANUMERIC, 4); - Append(text.size(), getModeBits(version, MODE_ALPHANUMERIC)); + // Draw the numerous alignment patterns - uint16_t accumData = 0; - uint8_t accumCount = 0; - for (auto c : text) - { - accumData = accumData * 45 + getAlphanumeric(c); - ++accumCount; - if (accumCount == 2) - { - Append(accumData, 11); - accumData = 0; - accumCount = 0; - } - } + uint8_t step; + if (version != 32) + step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; + else + step = 26; - if (accumCount == 1) - Append(accumData, 6); - } - else - { - Append(1 << MODE_BYTE, 4); - Append(text.size(), getModeBits(version, MODE_BYTE)); - for (auto c : text) - Append(c, 8); + uint8_t alignPositionIndex = alignCount - 1; + + alignPosition[0] = 6; + + uint8_t size = version * 4 + 17; + for (uint8_t i = 0, pos = size - 7; i != alignCount - 1; ++i, pos -= step) + alignPosition[alignPositionIndex--] = pos; + + for (uint8_t i = 0; i != alignCount; ++i) + for (uint8_t j = 0; j != alignCount; ++j) + if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) + continue; // Skip the three finder corners + else + DrawAlignmentPattern(infra::Point(alignPosition[i], alignPosition[j])); } + + // Draw configuration data + DrawFormatBits(0); // Dummy mask value; overwritten later in the constructor + DrawVersion(); } -void BitBuffer::PerformErrorCorrection(uint8_t version, uint8_t ecc) +// Draws two copies of the version bits (with its own error correction code), +// based on this object's version field (which only has an effect for 7 <= version <= 40). +void QRCode::QRCodeGenerator::DrawVersion() { - // See: http://www.thonky.com/qr-code-tutorial/structure-final-message - uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1]; - uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1]; - uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; - - uint8_t blockEccLen = totalEcc / numBlocks; - uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks; - uint8_t shortBlockLen = moduleCount / 8 / numBlocks; + int8_t size = modules.bitmap.size.deltaX; - uint8_t shortDataBlockLen = shortBlockLen - blockEccLen; - - uint8_t* result = AllocOnStack(capacityBytes); - std::memset(result, 0, capacityBytes); + if (version < 7) + return; - uint8_t* coeff = AllocOnStack(blockEccLen); - std::memset(coeff, 0, blockEccLen); - rs_init(blockEccLen, coeff); + // Calculate error correction code and pack bits + uint32_t rem = version; // version is uint6, in the range [7, 40] + for (uint8_t i = 0; i != 12; ++i) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); - uint16_t offset = 0; - uint8_t* dataBytes = data; + uint32_t data = version << 12 | rem; - // Interleave all short blocks - for (uint8_t i = 0; i != shortDataBlockLen; ++i) + // Draw two copies + for (uint8_t i = 0; i != 18; ++i) { - uint16_t index = i; - uint8_t stride = shortDataBlockLen; - for (uint8_t blockNum = 0; blockNum != numBlocks; ++blockNum) - { - result[offset++] = dataBytes[index]; + bool bit = ((data >> i) & 1) != 0; + uint8_t a = size - 11 + i % 3; + uint8_t b = i / 3; + SetFunctionModule(infra::Point(a, b), bit); + SetFunctionModule(infra::Point(b, a), bit); + } +} - if (blockNum == numShortBlocks) - ++stride; +// Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). +void QRCode::QRCodeGenerator::DrawFinderPattern(infra::Point position) +{ + auto bitmapRegion = infra::Region(infra::Point(), modules.bitmap.size); + auto finderRegion = infra::Region(infra::Point(-4, -4), infra::Point(5, 5)) >> (position - infra::Point()); - index += stride; - } + for (auto i : infra::RowFirstPoints(infra::Intersection(finderRegion, bitmapRegion))) + { + auto dist = infra::ChebyshevDistance(i, position); + SetFunctionModule(i, dist != 2 && dist != 4); } +} - // Version less than 5 only have short blocks - { - // Interleave long blocks - uint16_t index = shortDataBlockLen * (numShortBlocks + 1); - uint8_t stride = shortDataBlockLen; - for (uint8_t blockNum = 0; blockNum != numBlocks - numShortBlocks; ++blockNum) - { - result[offset++] = dataBytes[index]; +// Draws a 5*5 alignment pattern, with the center module at (x, y). +void QRCode::QRCodeGenerator::DrawAlignmentPattern(infra::Point position) +{ + auto alignmentRegion = infra::Region(infra::Point(-2, -2), infra::Point(3, 3)) >> (position - infra::Point()); - if (blockNum == 0) - ++stride; + for (auto i : infra::RowFirstPoints(alignmentRegion)) + SetFunctionModule(i, infra::ChebyshevDistance(i, position) != 1); +} - index += stride; - } - } +// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire +// data area of this QR Code symbol. Function modules need to be marked off before this is called. +void QRCode::QRCodeGenerator::DrawCodewords() +{ + uint8_t size = modules.bitmap.size.deltaX; - // Add all ecc blocks, interleaved - uint8_t blockSize = shortDataBlockLen; - for (uint8_t blockNum = 0; blockNum != numBlocks; ++blockNum) - { + // Bit index into the data + uint32_t i = 0; - if (blockNum == numShortBlocks) - ++blockSize; + // Do the funny zigzag scan + for (int16_t right = size - 1; right >= 1; right -= 2) + { // Index of right column in each column pair + if (right == 6) + right = 5; - rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks); - dataBytes += blockSize; - } + for (uint8_t vert = 0; vert != size; ++vert) + { // Vertical counter + for (int j = 0; j != 2; ++j) + { + uint8_t x = right - j; // Actual x coordinate + bool upwards = ((right & 2) == 0) ^ (x < 6); + uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate + auto position = infra::Point(x, y); + if (!isFunction.Get(position)) + { + modules.Set(position, codewords.Bit(i)); + ++i; - memcpy(data, result, capacityBytes); - bitOffset = moduleCount; + if (i == codewords.Length()) + return; + } + // If there are any remainder bits (0 to 7), they are already + // set to 0/false/white when the grid of modules was initialized + } + } + } } -// We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits) -// The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc) -static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); - -QRCode::QRCode(uint8_t version, Ecc ecc, infra::BoundedConstString text) - : version(version) - , size(version * 4 + 17) - , ecc(ecc) - , modulesGrid(size) +// Draws two copies of the format bits (with its own error correction code) +// based on the given mask and this object's error correction level field. +void QRCode::QRCodeGenerator::DrawFormatBits(uint8_t mask) { - uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * static_cast(ecc))) & 0x03; - - uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; - uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1]; + uint8_t size = modules.bitmap.size.deltaX; - BitBuffer codewords(AllocOnStack(RoundBitsToByte(moduleCount)), (int32_t)RoundBitsToByte(moduleCount)); + // Calculate error correction code and pack bits + uint32_t data = ecc << 3 | mask; // ecc is uint2, mask is uint3 + uint32_t rem = data; + for (int i = 0; i != 10; ++i) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); - // Place the data code words into the buffer - codewords.EncodeDataCodewords(text, version); + data = data << 10 | rem; + data ^= 0x5412; // uint15 - // Add terminator and pad up to a byte if applicable - uint32_t padding = std::min((dataCapacity * 8) - codewords.bitOffset, 4); + // Draw first copy + for (uint8_t i = 0; i != 6; ++i) + SetFunctionModule(infra::Point(8, i), ((data >> i) & 1) != 0); - codewords.Append(0, padding); - codewords.Append(0, (8 - codewords.bitOffset % 8) % 8); + SetFunctionModule(infra::Point(8, 7), ((data >> 6) & 1) != 0); + SetFunctionModule(infra::Point(8, 8), ((data >> 7) & 1) != 0); + SetFunctionModule(infra::Point(7, 8), ((data >> 8) & 1) != 0); - // Pad with alternate bytes until data capacity is reached - for (uint8_t padByte = 0xEC; codewords.bitOffset < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) - codewords.Append(padByte, 8); + for (int8_t i = 9; i != 15; ++i) + SetFunctionModule(infra::Point(14 - i, 8), ((data >> i) & 1) != 0); - BitBucket isFunctionGrid(size); + // Draw second copy + for (int8_t i = 0; i != 8; ++i) + SetFunctionModule(infra::Point(size - 1 - i, 8), ((data >> i) & 1) != 0); - // Draw function patterns, draw all codewords, do masking - drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits); - codewords.PerformErrorCorrection(version, eccFormatBits); - drawCodewords(&modulesGrid, &isFunctionGrid, &codewords); + for (int8_t i = 8; i != 15; ++i) + SetFunctionModule(infra::Point(8, size - 15 + i), ((data >> i) & 1) != 0); - // Find the best (lowest penalty) mask - uint8_t mask = 0; - int32_t minPenalty = INT32_MAX; - for (uint8_t i = 0; i != 8; ++i) - { - drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i); - applyMask(&modulesGrid, &isFunctionGrid, i); - int penalty = modulesGrid.PenaltyScore(); - if (penalty < minPenalty) - { - mask = i; - minPenalty = penalty; - } - applyMask(&modulesGrid, &isFunctionGrid, i); // Undoes the mask due to XOR - } + SetFunctionModule(infra::Point(8, size - 8), true); +} - // Overwrite old format bits - drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask); +void QRCode::QRCodeGenerator::SetFunctionModule(infra::Point position, bool on) +{ + modules.Set(position, on); + isFunction.Set(position, true); +} - // Apply the final choice of mask - applyMask(&modulesGrid, &isFunctionGrid, mask); +QRCode::QRCode(uint8_t version, Ecc ecc, infra::BoundedConstString text) + : modules(version * 4 + 17) +{ + QRCodeGenerator generator(modules, version, ecc, text); } const infra::Bitmap& QRCode::GetBitmap() const { - return modulesGrid.bitmap; + return modules.bitmap; } diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QRCode.hpp index b6143bf..c45ccfa 100644 --- a/preview/interfaces/QRCode.hpp +++ b/preview/interfaces/QRCode.hpp @@ -57,6 +57,40 @@ class BitBucket infra::Bitmap bitmap; }; +class BitBuffer +{ +private: + static constexpr std::array numRawDataModulesForVersion{ + 0, 208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523, + 7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587, + 19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648 + }; + + static constexpr uint16_t RoundBitsToByte(uint16_t bits) + { + return (bits + 7) / 8; + } + +public: + BitBuffer(uint8_t version, infra::BoundedConstString text, uint8_t ecc); + + uint16_t Length() const; + bool Bit(uint16_t index) const; + +private: + void Append(uint32_t val, uint8_t length); + void EncodeDataCodewords(infra::BoundedConstString text); + void PerformErrorCorrection(uint8_t ecc); + +private: + uint8_t version; + uint16_t moduleCount; + uint16_t bitOffset = 0; + uint16_t capacityBytes; + uint8_t* data; + uint8_t* result; +}; + class QRCode { public: @@ -84,12 +118,40 @@ class QRCode const infra::Bitmap& GetBitmap() const; -public: - uint8_t version; - uint8_t size; - Ecc ecc; +private: + class QRCodeGenerator + { + public: + QRCodeGenerator(BitBucket& modules, uint8_t version, Ecc ecc, infra::BoundedConstString text); + + private: + uint8_t BestMask(); + + void ApplyMask(uint8_t mask); + void DrawFunctionPatterns(); + void DrawVersion(); + void DrawFinderPattern(infra::Point position); + void DrawAlignmentPattern(infra::Point position); + void DrawCodewords(); + void DrawFormatBits(uint8_t mask); + void SetFunctionModule(infra::Point position, bool on); - BitBucket modulesGrid; + private: + uint8_t version; + uint8_t size; + uint8_t ecc; + + BitBucket& modules; + BitBucket isFunction; + + BitBuffer codewords; + + uint8_t alignCount; + uint8_t* alignPosition; + }; + +public: + BitBucket modules; }; #endif From 0c5c6336dd3e43fee92036965dfb5b53dcbd8504 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Wed, 11 Sep 2024 19:14:07 +0200 Subject: [PATCH 06/22] Refactor towards proper variable lifetime --- examples/clicking_scrolling/MainWin.cpp | 2 +- preview/interfaces/QRCode.cpp | 1176 +++++++++++------------ preview/interfaces/QRCode.hpp | 288 ++++-- 3 files changed, 771 insertions(+), 695 deletions(-) diff --git a/examples/clicking_scrolling/MainWin.cpp b/examples/clicking_scrolling/MainWin.cpp index 40000a1..7cc5855 100644 --- a/examples/clicking_scrolling/MainWin.cpp +++ b/examples/clicking_scrolling/MainWin.cpp @@ -16,7 +16,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine services::LowPowerStrategySdl lowPowerStrategy(timerService); infra::LowPowerEventDispatcher::WithSize<50> eventDispatcher(lowPowerStrategy); - QRCode qrcode(3, QRCode::Ecc::low, "HELLO WORLD"); + services::QRCode<3, services::QRCodeEcc::low> qrcode("HELLO WORLD"); services::ViewBitmap viewBitmap(qrcode.GetBitmap()); hal::DirectDisplaySdl display(infra::Vector(480, 272)); diff --git a/preview/interfaces/QRCode.cpp b/preview/interfaces/QRCode.cpp index 384f92b..b33e85d 100644 --- a/preview/interfaces/QRCode.cpp +++ b/preview/interfaces/QRCode.cpp @@ -39,729 +39,707 @@ #include #include -#define AllocOnStack(amount) (reinterpret_cast(_alloca(amount))) - -static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = { - // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - { 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372 }, // Medium - { 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750 }, // Low - { 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430 }, // High - { 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040 }, // Quartile -}; - -static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = { - // Version: (note that index 0 is for padding, and is set to an illegal value) - // 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - { 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49 }, // Medium - { 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25 }, // Low - { 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81 }, // High - { 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68 }, // Quartile -}; - -static int8_t getAlphanumeric(char c) +namespace services { - if (c >= '0' && c <= '9') - return (c - '0'); - if (c >= 'A' && c <= 'Z') - return (c - 'A' + 10); - - switch (c) + namespace detail { - case ' ': - return 36; - case '$': - return 37; - case '%': - return 38; - case '*': - return 39; - case '+': - return 40; - case '-': - return 41; - case '.': - return 42; - case '/': - return 43; - case ':': - return 44; - default: - return -1; - } -} + static int8_t getAlphanumeric(char c) + { + if (c >= '0' && c <= '9') + return (c - '0'); + if (c >= 'A' && c <= 'Z') + return (c - 'A' + 10); -static bool isAlphanumeric(infra::BoundedConstString text) -{ - for (auto c : text) - if (getAlphanumeric(c) == -1) - return false; + switch (c) + { + case ' ': + return 36; + case '$': + return 37; + case '%': + return 38; + case '*': + return 39; + case '+': + return 40; + case '-': + return 41; + case '.': + return 42; + case '/': + return 43; + case ':': + return 44; + default: + return -1; + } + } - return true; -} + static bool isAlphanumeric(infra::BoundedConstString text) + { + for (auto c : text) + if (getAlphanumeric(c) == -1) + return false; -static bool isNumeric(infra::BoundedConstString text) -{ - for (auto c : text) - if (c < '0' || c > '9') - return false; + return true; + } - return true; -} + static bool isNumeric(infra::BoundedConstString text) + { + for (auto c : text) + if (c < '0' || c > '9') + return false; -// We store the following tightly packed (less 8) in modeInfo -// <=9 <=26 <= 40 -// NUMERIC ( 10, 12, 14); -// ALPHANUMERIC ( 9, 11, 13); -// BYTE ( 8, 16, 16); -static char getModeBits(uint8_t version, uint8_t mode) -{ - // Note: We use 15 instead of 16; since 15 doesn't exist and we cannot store 16 (8 + 8) in 3 bits - // hex(int("".join(reversed([('00' + bin(x - 8)[2:])[-3:] for x in [10, 9, 8, 12, 11, 15, 14, 13, 15]])), 2)) - unsigned int modeInfo = 0x7bbb80a; + return true; + } - if (version > 9) - modeInfo >>= 9; + // We store the following tightly packed (less 8) in modeInfo + // <=9 <=26 <= 40 + // NUMERIC ( 10, 12, 14); + // ALPHANUMERIC ( 9, 11, 13); + // BYTE ( 8, 16, 16); + static char getModeBits(uint8_t version, uint8_t mode) + { + // Note: We use 15 instead of 16; since 15 doesn't exist and we cannot store 16 (8 + 8) in 3 bits + // hex(int("".join(reversed([('00' + bin(x - 8)[2:])[-3:] for x in [10, 9, 8, 12, 11, 15, 14, 13, 15]])), 2)) + unsigned int modeInfo = 0x7bbb80a; - if (version > 26) - modeInfo >>= 9; + if (version > 9) + modeInfo >>= 9; - char result = 8 + ((modeInfo >> (3 * mode)) & 0x07); + if (version > 26) + modeInfo >>= 9; - if (result == 15) - result = 16; + char result = 8 + ((modeInfo >> (3 * mode)) & 0x07); - return result; -} + if (result == 15) + result = 16; -static uint8_t rs_multiply(uint8_t x, uint8_t y) -{ - // Russian peasant multiplication - // See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication - uint16_t z = 0; - for (int8_t i = 7; i >= 0; i--) - { - z = (z << 1) ^ ((z >> 7) * 0x11D); - z ^= ((y >> i) & 1) * x; - } - return z; -} + return result; + } -static void rs_init(uint8_t degree, uint8_t* coeff) -{ - memset(coeff, 0, degree); - coeff[degree - 1] = 1; - - // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), - // drop the highest term, and store the rest of the coefficients in order of descending powers. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - uint16_t root = 1; - for (uint8_t i = 0; i < degree; i++) - { - // Multiply the current product by (x - r^i) - for (uint8_t j = 0; j != degree; ++j) + BitBuffer::ReedSolomon::ReedSolomon(uint8_t degree) + : degree(degree) + , coeff(new uint8_t[degree]) { - coeff[j] = rs_multiply(coeff[j], root); - if (j + 1 < degree) - coeff[j] ^= coeff[j + 1]; + memset(coeff, 0, degree); + coeff[degree - 1] = 1; + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // drop the highest term, and store the rest of the coefficients in order of descending powers. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint16_t root = 1; + for (uint8_t i = 0; i < degree; i++) + { + // Multiply the current product by (x - r^i) + for (uint8_t j = 0; j != degree; ++j) + { + coeff[j] = Multiply(coeff[j], root); + if (j + 1 < degree) + coeff[j] ^= coeff[j + 1]; + } + root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) + } } - root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) - } -} -static void rs_getRemainder(uint8_t degree, uint8_t* coeff, uint8_t* data, uint8_t length, uint8_t* result, uint8_t stride) -{ - // Compute the remainder by performing polynomial division - for (uint8_t i = 0; i < length; i++) - { - uint8_t factor = data[i] ^ result[0]; - for (uint8_t j = 1; j != degree; ++j) - result[(j - 1) * stride] = result[j * stride]; + void BitBuffer::ReedSolomon::Remainder(uint8_t* data, uint8_t length, uint8_t* result, uint8_t stride) const + { + // Compute the remainder by performing polynomial division + for (uint8_t i = 0; i != length; ++i) + { + uint8_t factor = data[i] ^ result[0]; + for (uint8_t j = 1; j != degree; ++j) + result[(j - 1) * stride] = result[j * stride]; - result[(degree - 1) * stride] = 0; + result[(degree - 1) * stride] = 0; - for (uint8_t j = 0; j != degree; ++j) - result[j * stride] ^= rs_multiply(coeff[j], factor); - } -} + for (uint8_t j = 0; j != degree; ++j) + result[j * stride] ^= Multiply(coeff[j], factor); + } + } -BitBuffer::BitBuffer(uint8_t version, infra::BoundedConstString text, uint8_t ecc) - : version(version) - , moduleCount(numRawDataModulesForVersion[version]) - , capacityBytes(RoundBitsToByte(moduleCount)) - , data(new uint8_t[capacityBytes]) - , result(new uint8_t[capacityBytes]) -{ - std::memset(data, 0, capacityBytes); - std::memset(result, 0, capacityBytes); + uint8_t BitBuffer::ReedSolomon::Multiply(uint8_t x, uint8_t y) const + { + // Russian peasant multiplication + // See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication + uint16_t z = 0; + for (int8_t i = 7; i >= 0; i--) + { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + return z; + } - uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1]; + BitBuffer::BitBuffer(uint8_t version, uint8_t ecc) + : version(version) + , ecc(ecc) + , moduleCount(numRawDataModulesForVersion[version]) + , capacityBytes(RoundBitsToByte(moduleCount)) + , data(new uint8_t[capacityBytes]) + , result(new uint8_t[capacityBytes]) + , reedSolomon(BlockEccLen(version, ecc)) + { + std::memset(data, 0, capacityBytes); + std::memset(result, 0, capacityBytes); + } - // Place the data code words into the buffer - EncodeDataCodewords(text); + void BitBuffer::Generate(infra::BoundedConstString text) + { + uint16_t dataCapacity = moduleCount / 8 - numErrorCorrectionCodewords[ecc][version - 1]; - // Add terminator and pad up to a byte if applicable - uint32_t padding = std::min((dataCapacity * 8) - bitOffset, 4); + // Place the data code words into the buffer + EncodeDataCodewords(text); - Append(0, padding); - Append(0, (8 - bitOffset % 8) % 8); + // Add terminator and pad up to a byte if applicable + uint32_t padding = std::min((dataCapacity * 8) - bitOffset, 4); - // Pad with alternate bytes until data capacity is reached - for (uint8_t padByte = 0xEC; bitOffset != dataCapacity * 8; padByte ^= 0xEC ^ 0x11) - Append(padByte, 8); + Append(0, padding); + Append(0, (8 - bitOffset % 8) % 8); - PerformErrorCorrection(ecc); -} + // Pad with alternate bytes until data capacity is reached + for (uint8_t padByte = 0xEC; bitOffset != dataCapacity * 8; padByte ^= 0xEC ^ 0x11) + Append(padByte, 8); -uint16_t BitBuffer::Length() const -{ - return bitOffset; -} + PerformErrorCorrection(ecc); + } -bool BitBuffer::Bit(uint16_t index) const -{ - return ((result[index >> 3] >> (7 - (index & 7))) & 1) != 0; -} + uint16_t BitBuffer::Length() const + { + return bitOffset; + } -void BitBuffer::Append(uint32_t val, uint8_t length) -{ - for (int8_t i = length - 1; i >= 0; --i, ++bitOffset) - data[bitOffset >> 3] |= ((val >> i) & 1) << (7 - (bitOffset & 7)); -} + bool BitBuffer::Bit(uint16_t index) const + { + return ((result[index >> 3] >> (7 - (index & 7))) & 1) != 0; + } -void BitBuffer::EncodeDataCodewords(infra::BoundedConstString text) -{ + void BitBuffer::Append(uint32_t val, uint8_t length) + { + for (int8_t i = length - 1; i >= 0; --i, ++bitOffset) + data[bitOffset >> 3] |= ((val >> i) & 1) << (7 - (bitOffset & 7)); + } + + void BitBuffer::EncodeDataCodewords(infra::BoundedConstString text) + { #define MODE_NUMERIC 0 #define MODE_ALPHANUMERIC 1 #define MODE_BYTE 2 - if (isNumeric(text)) - { - Append(1 << MODE_NUMERIC, 4); - Append(text.size(), getModeBits(version, MODE_NUMERIC)); - - uint16_t accumData = 0; - uint8_t accumCount = 0; - for (auto c : text) - { - accumData = accumData * 10 + (c - '0'); - ++accumCount; - if (accumCount == 3) + if (isNumeric(text)) { - Append(accumData, 10); - accumData = 0; - accumCount = 0; + Append(1 << MODE_NUMERIC, 4); + Append(text.size(), getModeBits(version, MODE_NUMERIC)); + + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (auto c : text) + { + accumData = accumData * 10 + (c - '0'); + ++accumCount; + if (accumCount == 3) + { + Append(accumData, 10); + accumData = 0; + accumCount = 0; + } + } + + // 1 or 2 digits remaining + if (accumCount > 0) + Append(accumData, accumCount * 3 + 1); } - } + else if (isAlphanumeric(text)) + { + Append(1 << MODE_ALPHANUMERIC, 4); + Append(text.size(), getModeBits(version, MODE_ALPHANUMERIC)); - // 1 or 2 digits remaining - if (accumCount > 0) - Append(accumData, accumCount * 3 + 1); - } - else if (isAlphanumeric(text)) - { - Append(1 << MODE_ALPHANUMERIC, 4); - Append(text.size(), getModeBits(version, MODE_ALPHANUMERIC)); + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (auto c : text) + { + accumData = accumData * 45 + getAlphanumeric(c); + ++accumCount; + if (accumCount == 2) + { + Append(accumData, 11); + accumData = 0; + accumCount = 0; + } + } - uint16_t accumData = 0; - uint8_t accumCount = 0; - for (auto c : text) - { - accumData = accumData * 45 + getAlphanumeric(c); - ++accumCount; - if (accumCount == 2) + if (accumCount == 1) + Append(accumData, 6); + } + else { - Append(accumData, 11); - accumData = 0; - accumCount = 0; + Append(1 << MODE_BYTE, 4); + Append(text.size(), getModeBits(version, MODE_BYTE)); + for (auto c : text) + Append(c, 8); } } - if (accumCount == 1) - Append(accumData, 6); - } - else - { - Append(1 << MODE_BYTE, 4); - Append(text.size(), getModeBits(version, MODE_BYTE)); - for (auto c : text) - Append(c, 8); - } -} - -void BitBuffer::PerformErrorCorrection(uint8_t ecc) -{ - // See: http://www.thonky.com/qr-code-tutorial/structure-final-message - uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1]; - uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1]; - - uint8_t blockEccLen = totalEcc / numBlocks; - uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks; - uint8_t shortBlockLen = moduleCount / 8 / numBlocks; + void BitBuffer::PerformErrorCorrection(uint8_t ecc) + { + // See: http://www.thonky.com/qr-code-tutorial/structure-final-message + uint8_t numBlocks = numErrorCorrectionBlocks[ecc][version - 1]; + uint16_t totalEcc = numErrorCorrectionCodewords[ecc][version - 1]; - uint8_t shortDataBlockLen = shortBlockLen - blockEccLen; + uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks; + uint8_t shortBlockLen = moduleCount / 8 / numBlocks; - uint8_t* coeff = AllocOnStack(blockEccLen); - std::memset(coeff, 0, blockEccLen); - rs_init(blockEccLen, coeff); + uint8_t shortDataBlockLen = shortBlockLen - BlockEccLen(version, ecc); - uint16_t offset = 0; - uint8_t* dataBytes = data; + uint16_t offset = 0; + uint8_t* dataBytes = data; - // Interleave all short blocks - for (uint8_t i = 0; i != shortDataBlockLen; ++i) - { - uint16_t index = i; - uint8_t stride = shortDataBlockLen; - for (uint8_t blockNum = 0; blockNum != numBlocks; ++blockNum) - { - result[offset++] = dataBytes[index]; + // Interleave all short blocks + for (uint8_t i = 0; i != shortDataBlockLen; ++i) + { + uint16_t index = i; + uint8_t stride = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum != numBlocks; ++blockNum) + { + result[offset++] = dataBytes[index]; - if (blockNum == numShortBlocks) - ++stride; + if (blockNum == numShortBlocks) + ++stride; - index += stride; - } - } + index += stride; + } + } - // Version less than 5 only have short blocks - { - // Interleave long blocks - uint16_t index = shortDataBlockLen * (numShortBlocks + 1); - uint8_t stride = shortDataBlockLen; - for (uint8_t blockNum = 0; blockNum != numBlocks - numShortBlocks; ++blockNum) - { - result[offset++] = dataBytes[index]; + // Version less than 5 only have short blocks + { + // Interleave long blocks + uint16_t index = shortDataBlockLen * (numShortBlocks + 1); + uint8_t stride = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum != numBlocks - numShortBlocks; ++blockNum) + { + result[offset++] = dataBytes[index]; - if (blockNum == 0) - ++stride; + if (blockNum == 0) + ++stride; - index += stride; - } - } + index += stride; + } + } - // Add all ecc blocks, interleaved - uint8_t blockSize = shortDataBlockLen; - for (uint8_t blockNum = 0; blockNum != numBlocks; ++blockNum) - { + // Add all ecc blocks, interleaved + uint8_t blockSize = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum != numBlocks; ++blockNum) + { - if (blockNum == numShortBlocks) - ++blockSize; + if (blockNum == numShortBlocks) + ++blockSize; - rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks); - dataBytes += blockSize; - } + reedSolomon.Remainder(dataBytes, blockSize, &result[offset + blockNum], numBlocks); + dataBytes += blockSize; + } - bitOffset = moduleCount; -} + bitOffset = moduleCount; + } -BitBucket::BitBucket(uint8_t size) - : buffer(new uint8_t[QRCode::GridSizeInBytes(size)]) - , bitmap(infra::ByteRange(buffer, buffer + QRCode::GridSizeInBytes(size)), infra::Vector(size, size), infra::PixelFormat::blackandwhite) -{ - memset(buffer, 0, QRCode::GridSizeInBytes(size)); -} + BitBucket::BitBucket(uint8_t size, infra::ByteRange buffer) + : bitmap(buffer, infra::Vector(size, size), infra::PixelFormat::blackandwhite) + {} -void BitBucket::Set(infra::Point position, bool on) -{ - bitmap.SetBlackAndWhitePixel(position, on); -} + void BitBucket::Set(infra::Point position, bool on) + { + bitmap.SetBlackAndWhitePixel(position, on); + } -bool BitBucket::Get(infra::Point position) const -{ - return bitmap.BlackAndWhitePixel(position); -} + bool BitBucket::Get(infra::Point position) const + { + return bitmap.BlackAndWhitePixel(position); + } -void BitBucket::Invert(infra::Point position, bool invert) -{ - if (invert) - Set(position, !Get(position)); -} + void BitBucket::Invert(infra::Point position, bool invert) + { + if (invert) + Set(position, !Get(position)); + } #define PENALTY_N1 3 #define PENALTY_N2 3 #define PENALTY_N3 40 #define PENALTY_N4 10 -// Calculates and returns the penalty score based on state of this QR Code's current modules. -// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. -uint32_t BitBucket::PenaltyScore() const -{ - uint32_t result = 0; + // Calculates and returns the penalty score based on state of this QR Code's current modules. + // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. + uint32_t BitBucket::PenaltyScore() const + { + uint32_t result = 0; - uint8_t size = bitmap.size.deltaX; + uint8_t size = bitmap.size.deltaX; - // Adjacent modules in row having same color - for (uint8_t y = 0; y < size; ++y) - { - bool colorX = Get(infra::Point(0, y)); - for (uint8_t x = 1, runX = 1; x != size; ++x) - { - bool cx = Get(infra::Point(x, y)); - if (cx != colorX) - { - colorX = cx; - runX = 1; - } - else + // Adjacent modules in row having same color + for (uint8_t y = 0; y < size; ++y) { - ++runX; - if (runX == 5) - result += PENALTY_N1; - else if (runX > 5) - ++result; + bool colorX = Get(infra::Point(0, y)); + for (uint8_t x = 1, runX = 1; x != size; ++x) + { + bool cx = Get(infra::Point(x, y)); + if (cx != colorX) + { + colorX = cx; + runX = 1; + } + else + { + ++runX; + if (runX == 5) + result += PENALTY_N1; + else if (runX > 5) + ++result; + } + } } - } - } - // Adjacent modules in column having same color - for (uint8_t x = 0; x != size; ++x) - { - bool colorY = Get(infra::Point(x, 0)); - for (uint8_t y = 1, runY = 1; y != size; ++y) - { - bool cy = Get(infra::Point(x, y)); - if (cy != colorY) - { - colorY = cy; - runY = 1; - } - else + // Adjacent modules in column having same color + for (uint8_t x = 0; x != size; ++x) { - ++runY; - if (runY == 5) - result += PENALTY_N1; - else if (runY > 5) - ++result; + bool colorY = Get(infra::Point(x, 0)); + for (uint8_t y = 1, runY = 1; y != size; ++y) + { + bool cy = Get(infra::Point(x, y)); + if (cy != colorY) + { + colorY = cy; + runY = 1; + } + else + { + ++runY; + if (runY == 5) + result += PENALTY_N1; + else if (runY > 5) + ++result; + } + } } - } - } - uint16_t black = 0; - for (uint8_t y = 0; y != size; ++y) - { - uint16_t bitsRow = 0, bitsCol = 0; - for (uint8_t x = 0; x != size; ++x) - { - bool color = Get(infra::Point(x, y)); - - // 2*2 blocks of modules having same color - if (x > 0 && y > 0) + uint16_t black = 0; + for (uint8_t y = 0; y != size; ++y) { - bool colorUL = Get(infra::Point(x - 1, y - 1)); - bool colorUR = Get(infra::Point(x, y - 1)); - bool colorL = Get(infra::Point(x - 1, y)); - if (color == colorUL && color == colorUR && color == colorL) - result += PENALTY_N2; + uint16_t bitsRow = 0, bitsCol = 0; + for (uint8_t x = 0; x != size; ++x) + { + bool color = Get(infra::Point(x, y)); + + // 2*2 blocks of modules having same color + if (x > 0 && y > 0) + { + bool colorUL = Get(infra::Point(x - 1, y - 1)); + bool colorUR = Get(infra::Point(x, y - 1)); + bool colorL = Get(infra::Point(x - 1, y)); + if (color == colorUL && color == colorUR && color == colorL) + result += PENALTY_N2; + } + + // Finder-like pattern in rows and columns + bitsRow = ((bitsRow << 1) & 0x7FF) | static_cast(color); + bitsCol = ((bitsCol << 1) & 0x7FF) | static_cast(Get(infra::Point(y, x))); + + // Needs 11 bits accumulated + if (x >= 10) + { + if (bitsRow == 0x05D || bitsRow == 0x5D0) + result += PENALTY_N3; + + if (bitsCol == 0x05D || bitsCol == 0x5D0) + result += PENALTY_N3; + } + + // Balance of black and white modules + if (color) + ++black; + } } - // Finder-like pattern in rows and columns - bitsRow = ((bitsRow << 1) & 0x7FF) | static_cast(color); - bitsCol = ((bitsCol << 1) & 0x7FF) | static_cast(Get(infra::Point(y, x))); + // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% + uint16_t total = size * size; + for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) + result += PENALTY_N4; - // Needs 11 bits accumulated - if (x >= 10) - { - if (bitsRow == 0x05D || bitsRow == 0x5D0) - result += PENALTY_N3; - - if (bitsCol == 0x05D || bitsCol == 0x5D0) - result += PENALTY_N3; - } - - // Balance of black and white modules - if (color) - ++black; + return result; } - } - - // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% - uint16_t total = size * size; - for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) - result += PENALTY_N4; - - return result; -} -// We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits) -// The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc) -static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); - -QRCode::QRCodeGenerator::QRCodeGenerator(BitBucket& modules, uint8_t version, Ecc ecc, infra::BoundedConstString text) - : version(version) - , size(version * 4 + 17) - , ecc((ECC_FORMAT_BITS >> (2 * static_cast(ecc))) & 0x03) - , modules(modules) - , isFunction(size) - , codewords(version, text, this->ecc) - , alignCount(version / 7 + 2) - , alignPosition(new uint8_t[alignCount]) -{ - std::memset(alignPosition, 0, alignCount); - - // Draw function patterns, draw all codewords, do masking - DrawFunctionPatterns(); - DrawCodewords(); + // We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits) + // The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc) + static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); + + detail::QRCodeGenerator::QRCodeGenerator(BitBucket& modules, BitBucket& isFunction, uint8_t version, QRCodeEcc ecc) + : version(version) + , ecc((ECC_FORMAT_BITS >> (2 * static_cast(ecc))) & 0x03) + , modules(modules) + , isFunction(isFunction) + , codewords(version, this->ecc) + , alignCount(version / 7 + 2) + , alignPosition(new uint8_t[alignCount]) + {} + + void QRCodeGenerator::Generate(infra::BoundedConstString text) + { + codewords.Generate(text); + std::memset(alignPosition, 0, alignCount); - // Find the best (lowest penalty) mask - uint8_t mask = BestMask(); + // Draw function patterns, draw all codewords, do masking + DrawFunctionPatterns(); + DrawCodewords(); - // Overwrite old format bits - DrawFormatBits(mask); + // Find the best (lowest penalty) mask + uint8_t mask = BestMask(); - // Apply the final choice of mask - ApplyMask(mask); -} + // Overwrite old format bits + DrawFormatBits(mask); -uint8_t QRCode::QRCodeGenerator::BestMask() -{ - uint8_t mask = 0; - - int32_t minPenalty = INT32_MAX; - for (uint8_t i = 0; i != 8; ++i) - { - DrawFormatBits(i); - ApplyMask(i); - int penalty = modules.PenaltyScore(); - if (penalty < minPenalty) - { - mask = i; - minPenalty = penalty; + // Apply the final choice of mask + ApplyMask(mask); } - ApplyMask(i); // Undoes the mask due to XOR - } - - return mask; -} -// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical -// properties, calling ApplyMask(m) twice with the same value is equivalent to no change at all. -// This means it is possible to apply a mask, undo it, and try another mask. Note that a final -// well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). -void QRCode::QRCodeGenerator::ApplyMask(uint8_t mask) -{ - uint8_t size = modules.bitmap.size.deltaX; + uint8_t detail::QRCodeGenerator::BestMask() + { + uint8_t mask = 0; - for (auto position : infra::RowFirstPoints(infra::Region(infra::Point(), modules.bitmap.size))) - { - if (isFunction.Get(position)) - continue; + int32_t minPenalty = INT32_MAX; + for (uint8_t i = 0; i != 8; ++i) + { + DrawFormatBits(i); + ApplyMask(i); + int penalty = modules.PenaltyScore(); + if (penalty < minPenalty) + { + mask = i; + minPenalty = penalty; + } + ApplyMask(i); // Undoes the mask due to XOR + } - bool invert = 0; - switch (mask) - { - case 0: - invert = (position.x + position.y) % 2 == 0; - break; - case 1: - invert = position.y % 2 == 0; - break; - case 2: - invert = position.x % 3 == 0; - break; - case 3: - invert = (position.x + position.y) % 3 == 0; - break; - case 4: - invert = (position.x / 3 + position.y / 2) % 2 == 0; - break; - case 5: - invert = position.x * position.y % 2 + position.x * position.y % 3 == 0; - break; - case 6: - invert = (position.x * position.y % 2 + position.x * position.y % 3) % 2 == 0; - break; - case 7: - invert = ((position.x + position.y) % 2 + position.x * position.y % 3) % 2 == 0; - break; + return mask; } - modules.Invert(position, invert); - } -} - -void QRCode::QRCodeGenerator::DrawFunctionPatterns() -{ - uint8_t size = modules.bitmap.size.deltaX; + // XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical + // properties, calling ApplyMask(m) twice with the same value is equivalent to no change at all. + // This means it is possible to apply a mask, undo it, and try another mask. Note that a final + // well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). + void detail::QRCodeGenerator::ApplyMask(uint8_t mask) + { + uint8_t size = modules.bitmap.size.deltaX; - // Draw the horizontal and vertical timing patterns - for (uint8_t i = 0; i != size; ++i) - { - SetFunctionModule(infra::Point(6, i), i % 2 == 0); - SetFunctionModule(infra::Point(i, 6), i % 2 == 0); - } + for (auto position : infra::RowFirstPoints(infra::Region(infra::Point(), modules.bitmap.size))) + { + if (isFunction.Get(position)) + continue; - // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) - DrawFinderPattern(infra::Point(3, 3)); - DrawFinderPattern(infra::Point(size - 4, 3)); - DrawFinderPattern(infra::Point(3, size - 4)); + bool invert = 0; + switch (mask) + { + case 0: + invert = (position.x + position.y) % 2 == 0; + break; + case 1: + invert = position.y % 2 == 0; + break; + case 2: + invert = position.x % 3 == 0; + break; + case 3: + invert = (position.x + position.y) % 3 == 0; + break; + case 4: + invert = (position.x / 3 + position.y / 2) % 2 == 0; + break; + case 5: + invert = position.x * position.y % 2 + position.x * position.y % 3 == 0; + break; + case 6: + invert = (position.x * position.y % 2 + position.x * position.y % 3) % 2 == 0; + break; + case 7: + invert = ((position.x + position.y) % 2 + position.x * position.y % 3) % 2 == 0; + break; + } - if (version > 1) - { - // Draw the numerous alignment patterns + modules.Invert(position, invert); + } + } - uint8_t step; - if (version != 32) - step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; - else - step = 26; + void detail::QRCodeGenerator::DrawFunctionPatterns() + { + uint8_t size = modules.bitmap.size.deltaX; - uint8_t alignPositionIndex = alignCount - 1; + // Draw the horizontal and vertical timing patterns + for (uint8_t i = 0; i != size; ++i) + { + SetFunctionModule(infra::Point(6, i), i % 2 == 0); + SetFunctionModule(infra::Point(i, 6), i % 2 == 0); + } - alignPosition[0] = 6; + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + DrawFinderPattern(infra::Point(3, 3)); + DrawFinderPattern(infra::Point(size - 4, 3)); + DrawFinderPattern(infra::Point(3, size - 4)); - uint8_t size = version * 4 + 17; - for (uint8_t i = 0, pos = size - 7; i != alignCount - 1; ++i, pos -= step) - alignPosition[alignPositionIndex--] = pos; + if (version > 1) + { + // Draw the numerous alignment patterns - for (uint8_t i = 0; i != alignCount; ++i) - for (uint8_t j = 0; j != alignCount; ++j) - if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) - continue; // Skip the three finder corners + uint8_t step; + if (version != 32) + step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; else - DrawAlignmentPattern(infra::Point(alignPosition[i], alignPosition[j])); - } + step = 26; - // Draw configuration data - DrawFormatBits(0); // Dummy mask value; overwritten later in the constructor - DrawVersion(); -} + uint8_t alignPositionIndex = alignCount - 1; -// Draws two copies of the version bits (with its own error correction code), -// based on this object's version field (which only has an effect for 7 <= version <= 40). -void QRCode::QRCodeGenerator::DrawVersion() -{ - int8_t size = modules.bitmap.size.deltaX; + alignPosition[0] = 6; - if (version < 7) - return; + uint8_t size = version * 4 + 17; + for (uint8_t i = 0, pos = size - 7; i != alignCount - 1; ++i, pos -= step) + alignPosition[alignPositionIndex--] = pos; - // Calculate error correction code and pack bits - uint32_t rem = version; // version is uint6, in the range [7, 40] - for (uint8_t i = 0; i != 12; ++i) - rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + for (uint8_t i = 0; i != alignCount; ++i) + for (uint8_t j = 0; j != alignCount; ++j) + if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) + continue; // Skip the three finder corners + else + DrawAlignmentPattern(infra::Point(alignPosition[i], alignPosition[j])); + } - uint32_t data = version << 12 | rem; + // Draw configuration data + DrawFormatBits(0); // Dummy mask value; overwritten later in the constructor + DrawVersion(); + } - // Draw two copies - for (uint8_t i = 0; i != 18; ++i) - { - bool bit = ((data >> i) & 1) != 0; - uint8_t a = size - 11 + i % 3; - uint8_t b = i / 3; - SetFunctionModule(infra::Point(a, b), bit); - SetFunctionModule(infra::Point(b, a), bit); - } -} + // Draws two copies of the version bits (with its own error correction code), + // based on this object's version field (which only has an effect for 7 <= version <= 40). + void detail::QRCodeGenerator::DrawVersion() + { + int8_t size = modules.bitmap.size.deltaX; -// Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). -void QRCode::QRCodeGenerator::DrawFinderPattern(infra::Point position) -{ - auto bitmapRegion = infra::Region(infra::Point(), modules.bitmap.size); - auto finderRegion = infra::Region(infra::Point(-4, -4), infra::Point(5, 5)) >> (position - infra::Point()); + if (version < 7) + return; - for (auto i : infra::RowFirstPoints(infra::Intersection(finderRegion, bitmapRegion))) - { - auto dist = infra::ChebyshevDistance(i, position); - SetFunctionModule(i, dist != 2 && dist != 4); - } -} + // Calculate error correction code and pack bits + uint32_t rem = version; // version is uint6, in the range [7, 40] + for (uint8_t i = 0; i != 12; ++i) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); -// Draws a 5*5 alignment pattern, with the center module at (x, y). -void QRCode::QRCodeGenerator::DrawAlignmentPattern(infra::Point position) -{ - auto alignmentRegion = infra::Region(infra::Point(-2, -2), infra::Point(3, 3)) >> (position - infra::Point()); + uint32_t data = version << 12 | rem; - for (auto i : infra::RowFirstPoints(alignmentRegion)) - SetFunctionModule(i, infra::ChebyshevDistance(i, position) != 1); -} + // Draw two copies + for (uint8_t i = 0; i != 18; ++i) + { + bool bit = ((data >> i) & 1) != 0; + uint8_t a = size - 11 + i % 3; + uint8_t b = i / 3; + SetFunctionModule(infra::Point(a, b), bit); + SetFunctionModule(infra::Point(b, a), bit); + } + } -// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire -// data area of this QR Code symbol. Function modules need to be marked off before this is called. -void QRCode::QRCodeGenerator::DrawCodewords() -{ - uint8_t size = modules.bitmap.size.deltaX; + // Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). + void detail::QRCodeGenerator::DrawFinderPattern(infra::Point position) + { + auto bitmapRegion = infra::Region(infra::Point(), modules.bitmap.size); + auto finderRegion = infra::Region(infra::Point(-4, -4), infra::Point(5, 5)) >> (position - infra::Point()); - // Bit index into the data - uint32_t i = 0; + for (auto i : infra::RowFirstPoints(infra::Intersection(finderRegion, bitmapRegion))) + { + auto dist = infra::ChebyshevDistance(i, position); + SetFunctionModule(i, dist != 2 && dist != 4); + } + } - // Do the funny zigzag scan - for (int16_t right = size - 1; right >= 1; right -= 2) - { // Index of right column in each column pair - if (right == 6) - right = 5; + // Draws a 5*5 alignment pattern, with the center module at (x, y). + void detail::QRCodeGenerator::DrawAlignmentPattern(infra::Point position) + { + auto alignmentRegion = infra::Region(infra::Point(-2, -2), infra::Point(3, 3)) >> (position - infra::Point()); - for (uint8_t vert = 0; vert != size; ++vert) - { // Vertical counter - for (int j = 0; j != 2; ++j) - { - uint8_t x = right - j; // Actual x coordinate - bool upwards = ((right & 2) == 0) ^ (x < 6); - uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate - auto position = infra::Point(x, y); - if (!isFunction.Get(position)) - { - modules.Set(position, codewords.Bit(i)); - ++i; + for (auto i : infra::RowFirstPoints(alignmentRegion)) + SetFunctionModule(i, infra::ChebyshevDistance(i, position) != 1); + } - if (i == codewords.Length()) - return; + // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire + // data area of this QR Code symbol. Function modules need to be marked off before this is called. + void detail::QRCodeGenerator::DrawCodewords() + { + uint8_t size = modules.bitmap.size.deltaX; + + // Bit index into the data + uint32_t i = 0; + + // Do the funny zigzag scan + for (int16_t right = size - 1; right >= 1; right -= 2) + { // Index of right column in each column pair + if (right == 6) + right = 5; + + for (uint8_t vert = 0; vert != size; ++vert) + { // Vertical counter + for (int j = 0; j != 2; ++j) + { + uint8_t x = right - j; // Actual x coordinate + bool upwards = ((right & 2) == 0) ^ (x < 6); + uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate + auto position = infra::Point(x, y); + if (!isFunction.Get(position)) + { + modules.Set(position, codewords.Bit(i)); + ++i; + + if (i == codewords.Length()) + return; + } + // If there are any remainder bits (0 to 7), they are already + // set to 0/false/white when the grid of modules was initialized + } } - // If there are any remainder bits (0 to 7), they are already - // set to 0/false/white when the grid of modules was initialized } } - } -} -// Draws two copies of the format bits (with its own error correction code) -// based on the given mask and this object's error correction level field. -void QRCode::QRCodeGenerator::DrawFormatBits(uint8_t mask) -{ - uint8_t size = modules.bitmap.size.deltaX; - - // Calculate error correction code and pack bits - uint32_t data = ecc << 3 | mask; // ecc is uint2, mask is uint3 - uint32_t rem = data; - for (int i = 0; i != 10; ++i) - rem = (rem << 1) ^ ((rem >> 9) * 0x537); + // Draws two copies of the format bits (with its own error correction code) + // based on the given mask and this object's error correction level field. + void detail::QRCodeGenerator::DrawFormatBits(uint8_t mask) + { + uint8_t size = modules.bitmap.size.deltaX; - data = data << 10 | rem; - data ^= 0x5412; // uint15 + // Calculate error correction code and pack bits + uint32_t data = ecc << 3 | mask; // ecc is uint2, mask is uint3 + uint32_t rem = data; + for (int i = 0; i != 10; ++i) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); - // Draw first copy - for (uint8_t i = 0; i != 6; ++i) - SetFunctionModule(infra::Point(8, i), ((data >> i) & 1) != 0); + data = data << 10 | rem; + data ^= 0x5412; // uint15 - SetFunctionModule(infra::Point(8, 7), ((data >> 6) & 1) != 0); - SetFunctionModule(infra::Point(8, 8), ((data >> 7) & 1) != 0); - SetFunctionModule(infra::Point(7, 8), ((data >> 8) & 1) != 0); + // Draw first copy + for (uint8_t i = 0; i != 6; ++i) + SetFunctionModule(infra::Point(8, i), ((data >> i) & 1) != 0); - for (int8_t i = 9; i != 15; ++i) - SetFunctionModule(infra::Point(14 - i, 8), ((data >> i) & 1) != 0); + SetFunctionModule(infra::Point(8, 7), ((data >> 6) & 1) != 0); + SetFunctionModule(infra::Point(8, 8), ((data >> 7) & 1) != 0); + SetFunctionModule(infra::Point(7, 8), ((data >> 8) & 1) != 0); - // Draw second copy - for (int8_t i = 0; i != 8; ++i) - SetFunctionModule(infra::Point(size - 1 - i, 8), ((data >> i) & 1) != 0); + for (int8_t i = 9; i != 15; ++i) + SetFunctionModule(infra::Point(14 - i, 8), ((data >> i) & 1) != 0); - for (int8_t i = 8; i != 15; ++i) - SetFunctionModule(infra::Point(8, size - 15 + i), ((data >> i) & 1) != 0); + // Draw second copy + for (int8_t i = 0; i != 8; ++i) + SetFunctionModule(infra::Point(size - 1 - i, 8), ((data >> i) & 1) != 0); - SetFunctionModule(infra::Point(8, size - 8), true); -} - -void QRCode::QRCodeGenerator::SetFunctionModule(infra::Point position, bool on) -{ - modules.Set(position, on); - isFunction.Set(position, true); -} + for (int8_t i = 8; i != 15; ++i) + SetFunctionModule(infra::Point(8, size - 15 + i), ((data >> i) & 1) != 0); -QRCode::QRCode(uint8_t version, Ecc ecc, infra::BoundedConstString text) - : modules(version * 4 + 17) -{ - QRCodeGenerator generator(modules, version, ecc, text); -} + SetFunctionModule(infra::Point(8, size - 8), true); + } -const infra::Bitmap& QRCode::GetBitmap() const -{ - return modules.bitmap; + void detail::QRCodeGenerator::SetFunctionModule(infra::Point position, bool on) + { + modules.Set(position, on); + isFunction.Set(position, true); + } + } } diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QRCode.hpp index c45ccfa..5fdb5a3 100644 --- a/preview/interfaces/QRCode.hpp +++ b/preview/interfaces/QRCode.hpp @@ -41,71 +41,9 @@ #include "preview/interfaces/Bitmap.hpp" #include -class BitBucket +namespace services { -public: - BitBucket(uint8_t size); - - void Set(infra::Point position, bool on); - bool Get(infra::Point position) const; - void Invert(infra::Point position, bool invert); - - uint32_t PenaltyScore() const; - -public: - std::uint8_t* buffer; - infra::Bitmap bitmap; -}; - -class BitBuffer -{ -private: - static constexpr std::array numRawDataModulesForVersion{ - 0, 208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523, - 7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587, - 19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648 - }; - - static constexpr uint16_t RoundBitsToByte(uint16_t bits) - { - return (bits + 7) / 8; - } - -public: - BitBuffer(uint8_t version, infra::BoundedConstString text, uint8_t ecc); - - uint16_t Length() const; - bool Bit(uint16_t index) const; - -private: - void Append(uint32_t val, uint8_t length); - void EncodeDataCodewords(infra::BoundedConstString text); - void PerformErrorCorrection(uint8_t ecc); - -private: - uint8_t version; - uint16_t moduleCount; - uint16_t bitOffset = 0; - uint16_t capacityBytes; - uint8_t* data; - uint8_t* result; -}; - -class QRCode -{ -public: - static constexpr uint16_t GridSizeInBytes(uint8_t bits) - { - return (bits * bits + 7) / 8; - } - - static constexpr uint16_t BufferSize(uint8_t version) - { - return GridSizeInBytes(4 * version + 17); - } - -public: - enum class Ecc : uint8_t + enum class QRCodeEcc : uint8_t { low, medium, @@ -113,45 +51,205 @@ class QRCode high }; -public: - QRCode(uint8_t version, Ecc ecc, infra::BoundedConstString text); - - const infra::Bitmap& GetBitmap() const; + namespace detail + { + static constexpr uint16_t GridSizeInBytes(uint8_t bits) + { + return (bits * bits + 7) / 8; + } + + static constexpr uint16_t BufferSize(uint8_t version) + { + return GridSizeInBytes(4 * version + 17); + } + + class BitBucket + { + public: + template + struct ForVersion; + + BitBucket(uint8_t size, infra::ByteRange buffer); + + void Set(infra::Point position, bool on); + bool Get(infra::Point position) const; + void Invert(infra::Point position, bool invert); + + uint32_t PenaltyScore() const; + + public: + infra::Bitmap bitmap; + }; + + template + struct BitBucket::ForVersion + : BitBucket + { + ForVersion(); + + static constexpr uint8_t size = Version * 4 + 17; + std::array buffer{}; + }; + + class BitBuffer + { + private: + static constexpr std::array numRawDataModulesForVersion{ + 0, 208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523, + 7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587, + 19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648 + }; + + static constexpr auto numErrorCorrectionCodewords = []() + { + std::array, 4> result{ { + // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + { 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372 }, // Medium + { 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750 }, // Low + { 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430 }, // High + { 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040 }, // Quartile + } }; + + return result; + }(); + + static constexpr auto numErrorCorrectionBlocks = []() + { + const std::array, 4> result{ { + // Version: (note that index 0 is for padding, and is set to an illegal value) + // 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + { 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49 }, // Medium + { 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25 }, // Low + { 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81 }, // High + { 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68 }, // Quartile + } }; + + return result; + }(); + + static constexpr uint16_t RoundBitsToByte(uint16_t bits) + { + return (bits + 7) / 8; + } + + static constexpr uint8_t BlockEccLen(uint8_t version, uint8_t ecc) + { + uint8_t numBlocks = numErrorCorrectionBlocks[ecc][version - 1]; + uint16_t totalEcc = numErrorCorrectionCodewords[ecc][version - 1]; + return totalEcc / numBlocks; + } + + public: + BitBuffer(uint8_t version, uint8_t ecc); + + void Generate(infra::BoundedConstString text); + + uint16_t Length() const; + bool Bit(uint16_t index) const; + + private: + void Append(uint32_t val, uint8_t length); + void EncodeDataCodewords(infra::BoundedConstString text); + void PerformErrorCorrection(uint8_t ecc); + + private: + class ReedSolomon + { + public: + ReedSolomon(uint8_t degree); + + void Remainder(uint8_t* data, uint8_t length, uint8_t* result, uint8_t stride) const; + + private: + uint8_t Multiply(uint8_t x, uint8_t y) const; + + private: + uint8_t degree; + uint8_t* coeff; + }; + + private: + uint8_t version; + uint8_t ecc; + uint16_t moduleCount; + uint16_t bitOffset = 0; + uint16_t capacityBytes; + uint8_t* data; + uint8_t* result; + ReedSolomon reedSolomon; + }; + + class QRCodeGenerator + { + public: + template + struct ForVersionAndEcc; + + QRCodeGenerator(BitBucket& modules, BitBucket& isFunction, uint8_t version, QRCodeEcc ecc); + + void Generate(infra::BoundedConstString text); + + private: + uint8_t BestMask(); + + void ApplyMask(uint8_t mask); + void DrawFunctionPatterns(); + void DrawVersion(); + void DrawFinderPattern(infra::Point position); + void DrawAlignmentPattern(infra::Point position); + void DrawCodewords(); + void DrawFormatBits(uint8_t mask); + void SetFunctionModule(infra::Point position, bool on); + + private: + uint8_t version; + uint8_t ecc; + + BitBucket& modules; + BitBucket& isFunction; + + BitBuffer codewords; + + uint8_t alignCount; + uint8_t* alignPosition; + }; + } -private: - class QRCodeGenerator + template + class QRCode { public: - QRCodeGenerator(BitBucket& modules, uint8_t version, Ecc ecc, infra::BoundedConstString text); + QRCode(infra::BoundedConstString text); - private: - uint8_t BestMask(); + const infra::Bitmap& GetBitmap() const; - void ApplyMask(uint8_t mask); - void DrawFunctionPatterns(); - void DrawVersion(); - void DrawFinderPattern(infra::Point position); - void DrawAlignmentPattern(infra::Point position); - void DrawCodewords(); - void DrawFormatBits(uint8_t mask); - void SetFunctionModule(infra::Point position, bool on); - - private: - uint8_t version; - uint8_t size; - uint8_t ecc; + public: + detail::BitBucket::ForVersion modules; + }; - BitBucket& modules; - BitBucket isFunction; + //// Implementation //// - BitBuffer codewords; + namespace detail + { + template + BitBucket::ForVersion::ForVersion() + : BitBucket(size, buffer) + {} + } - uint8_t alignCount; - uint8_t* alignPosition; - }; + template + QRCode::QRCode(infra::BoundedConstString text) + { + detail::BitBucket::ForVersion isFunction; -public: - BitBucket modules; -}; + detail::QRCodeGenerator generator(modules, isFunction, Version, Ecc); + generator.Generate(text); + } + template + const infra::Bitmap& QRCode::GetBitmap() const + { + return modules.bitmap; + } +} #endif From a22f8d725d1f2214a73c8028906bcc2aba2eb4e6 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Thu, 12 Sep 2024 15:46:59 +0200 Subject: [PATCH 07/22] Refactor --- preview/interfaces/QRCode.cpp | 78 +++++++++++++++----------------- preview/interfaces/QRCode.hpp | 84 ++++++++++++++++++++++++----------- 2 files changed, 94 insertions(+), 68 deletions(-) diff --git a/preview/interfaces/QRCode.cpp b/preview/interfaces/QRCode.cpp index b33e85d..e1fe104 100644 --- a/preview/interfaces/QRCode.cpp +++ b/preview/interfaces/QRCode.cpp @@ -118,24 +118,22 @@ namespace services return result; } - BitBuffer::ReedSolomon::ReedSolomon(uint8_t degree) - : degree(degree) - , coeff(new uint8_t[degree]) + BitBuffer::ReedSolomon::ReedSolomon(infra::ByteRange coeff) + : coeff(coeff) { - memset(coeff, 0, degree); - coeff[degree - 1] = 1; + coeff.back() = 1; - // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{coeff.size()-1}), // drop the highest term, and store the rest of the coefficients in order of descending powers. // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). uint16_t root = 1; - for (uint8_t i = 0; i < degree; i++) + for (uint8_t i = 0; i < coeff.size(); i++) { // Multiply the current product by (x - r^i) - for (uint8_t j = 0; j != degree; ++j) + for (uint8_t j = 0; j != coeff.size(); ++j) { coeff[j] = Multiply(coeff[j], root); - if (j + 1 < degree) + if (j + 1 < coeff.size()) coeff[j] ^= coeff[j + 1]; } root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) @@ -148,12 +146,12 @@ namespace services for (uint8_t i = 0; i != length; ++i) { uint8_t factor = data[i] ^ result[0]; - for (uint8_t j = 1; j != degree; ++j) + for (uint8_t j = 1; j != coeff.size(); ++j) result[(j - 1) * stride] = result[j * stride]; - result[(degree - 1) * stride] = 0; + result[(coeff.size() - 1) * stride] = 0; - for (uint8_t j = 0; j != degree; ++j) + for (uint8_t j = 0; j != coeff.size(); ++j) result[j * stride] ^= Multiply(coeff[j], factor); } } @@ -171,22 +169,18 @@ namespace services return z; } - BitBuffer::BitBuffer(uint8_t version, uint8_t ecc) + BitBuffer::BitBuffer(infra::ByteRange data, infra::ByteRange result, infra::ByteRange coeff, uint8_t version, QRCodeEcc ecc) : version(version) , ecc(ecc) , moduleCount(numRawDataModulesForVersion[version]) - , capacityBytes(RoundBitsToByte(moduleCount)) - , data(new uint8_t[capacityBytes]) - , result(new uint8_t[capacityBytes]) - , reedSolomon(BlockEccLen(version, ecc)) - { - std::memset(data, 0, capacityBytes); - std::memset(result, 0, capacityBytes); - } + , data(data) + , result(result) + , reedSolomon(coeff) + {} void BitBuffer::Generate(infra::BoundedConstString text) { - uint16_t dataCapacity = moduleCount / 8 - numErrorCorrectionCodewords[ecc][version - 1]; + uint16_t dataCapacity = moduleCount / 8 - numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; // Place the data code words into the buffer EncodeDataCodewords(text); @@ -280,11 +274,11 @@ namespace services } } - void BitBuffer::PerformErrorCorrection(uint8_t ecc) + void BitBuffer::PerformErrorCorrection(QRCodeEcc ecc) { // See: http://www.thonky.com/qr-code-tutorial/structure-final-message - uint8_t numBlocks = numErrorCorrectionBlocks[ecc][version - 1]; - uint16_t totalEcc = numErrorCorrectionCodewords[ecc][version - 1]; + uint8_t numBlocks = numErrorCorrectionBlocks[static_cast(ecc)][version - 1]; + uint16_t totalEcc = numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks; uint8_t shortBlockLen = moduleCount / 8 / numBlocks; @@ -292,7 +286,7 @@ namespace services uint8_t shortDataBlockLen = shortBlockLen - BlockEccLen(version, ecc); uint16_t offset = 0; - uint8_t* dataBytes = data; + uint8_t* dataBytes = data.begin(); // Interleave all short blocks for (uint8_t i = 0; i != shortDataBlockLen; ++i) @@ -466,24 +460,18 @@ namespace services return result; } - // We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits) - // The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc) - static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); - - detail::QRCodeGenerator::QRCodeGenerator(BitBucket& modules, BitBucket& isFunction, uint8_t version, QRCodeEcc ecc) + detail::QRCodeGenerator::QRCodeGenerator(BitBucket& modules, BitBucket& isFunction, BitBuffer& codewords, infra::ByteRange alignPosition, uint8_t version, QRCodeEcc ecc) : version(version) - , ecc((ECC_FORMAT_BITS >> (2 * static_cast(ecc))) & 0x03) + , ecc(ecc) , modules(modules) , isFunction(isFunction) - , codewords(version, this->ecc) - , alignCount(version / 7 + 2) - , alignPosition(new uint8_t[alignCount]) + , codewords(codewords) + , alignPosition(alignPosition) {} void QRCodeGenerator::Generate(infra::BoundedConstString text) { codewords.Generate(text); - std::memset(alignPosition, 0, alignCount); // Draw function patterns, draw all codewords, do masking DrawFunctionPatterns(); @@ -588,21 +576,21 @@ namespace services uint8_t step; if (version != 32) - step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; + step = (version * 4 + alignPosition.size() * 2 + 1) / (2 * alignPosition.size() - 2) * 2; else step = 26; - uint8_t alignPositionIndex = alignCount - 1; + uint8_t alignPositionIndex = alignPosition.size() - 1; alignPosition[0] = 6; uint8_t size = version * 4 + 17; - for (uint8_t i = 0, pos = size - 7; i != alignCount - 1; ++i, pos -= step) + for (uint8_t i = 0, pos = size - 7; i != alignPosition.size() - 1; ++i, pos -= step) alignPosition[alignPositionIndex--] = pos; - for (uint8_t i = 0; i != alignCount; ++i) - for (uint8_t j = 0; j != alignCount; ++j) - if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) + for (uint8_t i = 0; i != alignPosition.size(); ++i) + for (uint8_t j = 0; j != alignPosition.size(); ++j) + if ((i == 0 && j == 0) || (i == 0 && j == alignPosition.size() - 1) || (i == alignPosition.size() - 1 && j == 0)) continue; // Skip the three finder corners else DrawAlignmentPattern(infra::Point(alignPosition[i], alignPosition[j])); @@ -704,10 +692,14 @@ namespace services // based on the given mask and this object's error correction level field. void detail::QRCodeGenerator::DrawFormatBits(uint8_t mask) { + static constexpr std::array eccFormatBits{ + 0x01, 0x00, 0x03, 0x02 + }; + uint8_t size = modules.bitmap.size.deltaX; // Calculate error correction code and pack bits - uint32_t data = ecc << 3 | mask; // ecc is uint2, mask is uint3 + uint32_t data = eccFormatBits[static_cast(ecc)] << 3 | mask; // ecc is uint2, mask is uint3 uint32_t rem = data; for (int i = 0; i != 10; ++i) rem = (rem << 1) ^ ((rem >> 9) * 0x537); diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QRCode.hpp index 5fdb5a3..6b03881 100644 --- a/preview/interfaces/QRCode.hpp +++ b/preview/interfaces/QRCode.hpp @@ -104,10 +104,10 @@ namespace services { std::array, 4> result{ { // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - { 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372 }, // Medium { 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750 }, // Low - { 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430 }, // High + { 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372 }, // Medium { 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040 }, // Quartile + { 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430 }, // High } }; return result; @@ -118,10 +118,10 @@ namespace services const std::array, 4> result{ { // Version: (note that index 0 is for padding, and is set to an illegal value) // 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - { 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49 }, // Medium { 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25 }, // Low - { 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81 }, // High + { 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49 }, // Medium { 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68 }, // Quartile + { 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81 }, // High } }; return result; @@ -132,15 +132,18 @@ namespace services return (bits + 7) / 8; } - static constexpr uint8_t BlockEccLen(uint8_t version, uint8_t ecc) + static constexpr uint8_t BlockEccLen(uint8_t version, QRCodeEcc ecc) { - uint8_t numBlocks = numErrorCorrectionBlocks[ecc][version - 1]; - uint16_t totalEcc = numErrorCorrectionCodewords[ecc][version - 1]; + uint8_t numBlocks = numErrorCorrectionBlocks[static_cast(ecc)][version - 1]; + uint16_t totalEcc = numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; return totalEcc / numBlocks; } public: - BitBuffer(uint8_t version, uint8_t ecc); + template + struct ForVersionAndEcc; + + BitBuffer(infra::ByteRange data, infra::ByteRange result, infra::ByteRange coeff, uint8_t version, QRCodeEcc ecc); void Generate(infra::BoundedConstString text); @@ -150,13 +153,13 @@ namespace services private: void Append(uint32_t val, uint8_t length); void EncodeDataCodewords(infra::BoundedConstString text); - void PerformErrorCorrection(uint8_t ecc); + void PerformErrorCorrection(QRCodeEcc ecc); private: class ReedSolomon { public: - ReedSolomon(uint8_t degree); + ReedSolomon(infra::ByteRange coeff); void Remainder(uint8_t* data, uint8_t length, uint8_t* result, uint8_t stride) const; @@ -164,28 +167,39 @@ namespace services uint8_t Multiply(uint8_t x, uint8_t y) const; private: - uint8_t degree; - uint8_t* coeff; + infra::ByteRange coeff; }; private: uint8_t version; - uint8_t ecc; + QRCodeEcc ecc; uint16_t moduleCount; uint16_t bitOffset = 0; - uint16_t capacityBytes; - uint8_t* data; - uint8_t* result; + infra::ByteRange data; + infra::ByteRange result; ReedSolomon reedSolomon; }; + template + struct BitBuffer::ForVersionAndEcc + { + ForVersionAndEcc(); + + static constexpr uint16_t moduleCount = numRawDataModulesForVersion[Version]; + std::array data{}; + std::array result{}; + std::array coeff{}; + + BitBuffer buffer; + }; + class QRCodeGenerator { public: - template + template struct ForVersionAndEcc; - QRCodeGenerator(BitBucket& modules, BitBucket& isFunction, uint8_t version, QRCodeEcc ecc); + QRCodeGenerator(BitBucket& modules, BitBucket& isFunction, BitBuffer& codewords, infra::ByteRange alignPosition, uint8_t version, QRCodeEcc ecc); void Generate(infra::BoundedConstString text); @@ -203,15 +217,25 @@ namespace services private: uint8_t version; - uint8_t ecc; + QRCodeEcc ecc; BitBucket& modules; BitBucket& isFunction; - BitBuffer codewords; + BitBuffer& codewords; - uint8_t alignCount; - uint8_t* alignPosition; + infra::ByteRange alignPosition; + }; + + template + struct QRCodeGenerator::ForVersionAndEcc + : QRCodeGenerator + { + ForVersionAndEcc(BitBucket& modules); + + detail::BitBucket::ForVersion isFunction; + detail::BitBuffer::ForVersionAndEcc codewords; + std::array alignPosition; }; } @@ -235,14 +259,23 @@ namespace services BitBucket::ForVersion::ForVersion() : BitBucket(size, buffer) {} + + + template + BitBuffer::ForVersionAndEcc::ForVersionAndEcc() + : buffer(data, result, coeff, Version, Ecc) + {} + + template + QRCodeGenerator::ForVersionAndEcc::ForVersionAndEcc(BitBucket& modules) + : QRCodeGenerator(modules, isFunction, codewords.buffer, alignPosition, Version, Ecc) + {} } template QRCode::QRCode(infra::BoundedConstString text) { - detail::BitBucket::ForVersion isFunction; - - detail::QRCodeGenerator generator(modules, isFunction, Version, Ecc); + detail::QRCodeGenerator::ForVersionAndEcc generator(modules); generator.Generate(text); } @@ -252,4 +285,5 @@ namespace services return modules.bitmap; } } + #endif From 56d9dd2240dff7a9daad5cd6d2edc3475f086a7a Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Fri, 13 Sep 2024 17:07:13 +0200 Subject: [PATCH 08/22] Refactor --- preview/interfaces/Bitmap.cpp | 5 +++++ preview/interfaces/Bitmap.hpp | 2 ++ preview/interfaces/QRCode.cpp | 4 ++-- preview/interfaces/QRCode.hpp | 42 +++++++++++++++++++---------------- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/preview/interfaces/Bitmap.cpp b/preview/interfaces/Bitmap.cpp index a9f274e..d139de0 100644 --- a/preview/interfaces/Bitmap.cpp +++ b/preview/interfaces/Bitmap.cpp @@ -13,6 +13,11 @@ namespace infra assert(buffer.size() == BufferSize(size.deltaX, size.deltaY, pixelFormat)); } + void Bitmap::Clear() + { + std::fill(buffer.begin(), buffer.end(), 0); + } + const uint8_t* Bitmap::BufferAddress(infra::Point position) const { assert(pixelFormat != PixelFormat::blackandwhite); diff --git a/preview/interfaces/Bitmap.hpp b/preview/interfaces/Bitmap.hpp index cc77e59..350ae14 100644 --- a/preview/interfaces/Bitmap.hpp +++ b/preview/interfaces/Bitmap.hpp @@ -20,6 +20,8 @@ namespace infra Bitmap(infra::ByteRange buffer, infra::Vector size, PixelFormat pixelFormat); + void Clear(); + const uint8_t* BufferAddress(infra::Point position) const; uint8_t* BufferAddress(infra::Point position); void SetBlackAndWhitePixel(infra::Point position, bool pixel); diff --git a/preview/interfaces/QRCode.cpp b/preview/interfaces/QRCode.cpp index e1fe104..19d3a49 100644 --- a/preview/interfaces/QRCode.cpp +++ b/preview/interfaces/QRCode.cpp @@ -335,8 +335,8 @@ namespace services bitOffset = moduleCount; } - BitBucket::BitBucket(uint8_t size, infra::ByteRange buffer) - : bitmap(buffer, infra::Vector(size, size), infra::PixelFormat::blackandwhite) + BitBucket::BitBucket(infra::Bitmap& bitmap) + : bitmap(bitmap) {} void BitBucket::Set(infra::Point position, bool on) diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QRCode.hpp index 6b03881..7c6ae4d 100644 --- a/preview/interfaces/QRCode.hpp +++ b/preview/interfaces/QRCode.hpp @@ -51,6 +51,19 @@ namespace services high }; + template + class QRCode + { + public: + QRCode(infra::BoundedConstString text); + + const infra::Bitmap& GetBitmap() const; + + public: + static constexpr uint8_t size = Version * 4 + 17; + infra::Bitmap::BlackAndWhite bitmap; + }; + namespace detail { static constexpr uint16_t GridSizeInBytes(uint8_t bits) @@ -69,7 +82,7 @@ namespace services template struct ForVersion; - BitBucket(uint8_t size, infra::ByteRange buffer); + BitBucket(infra::Bitmap& bitmap); void Set(infra::Point position, bool on); bool Get(infra::Point position) const; @@ -78,7 +91,7 @@ namespace services uint32_t PenaltyScore() const; public: - infra::Bitmap bitmap; + infra::Bitmap& bitmap; }; template @@ -88,7 +101,7 @@ namespace services ForVersion(); static constexpr uint8_t size = Version * 4 + 17; - std::array buffer{}; + infra::Bitmap::BlackAndWhite bitmap; }; class BitBuffer @@ -239,27 +252,16 @@ namespace services }; } - template - class QRCode - { - public: - QRCode(infra::BoundedConstString text); - - const infra::Bitmap& GetBitmap() const; - - public: - detail::BitBucket::ForVersion modules; - }; - //// Implementation //// namespace detail { template BitBucket::ForVersion::ForVersion() - : BitBucket(size, buffer) - {} - + : BitBucket(bitmap) + { + bitmap.Clear(); + } template BitBuffer::ForVersionAndEcc::ForVersionAndEcc() @@ -275,6 +277,8 @@ namespace services template QRCode::QRCode(infra::BoundedConstString text) { + bitmap.Clear(); + detail::BitBucket modules(bitmap); detail::QRCodeGenerator::ForVersionAndEcc generator(modules); generator.Generate(text); } @@ -282,7 +286,7 @@ namespace services template const infra::Bitmap& QRCode::GetBitmap() const { - return modules.bitmap; + return bitmap; } } From 200ab03f3363722a6cd950cdef00cddc52338431 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Fri, 13 Sep 2024 17:30:12 +0200 Subject: [PATCH 09/22] Refactor --- preview/interfaces/QRCode.cpp | 111 +++++++++------------------------- preview/interfaces/QRCode.hpp | 77 ++++++----------------- 2 files changed, 47 insertions(+), 141 deletions(-) diff --git a/preview/interfaces/QRCode.cpp b/preview/interfaces/QRCode.cpp index 19d3a49..b716f5f 100644 --- a/preview/interfaces/QRCode.cpp +++ b/preview/interfaces/QRCode.cpp @@ -1,39 +1,4 @@ -/** - * The MIT License (MIT) - * - * This library is written and maintained by Richard Moore. - * Major parts were derived from Project Nayuki's library. - * - * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) - * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * Special thanks to Nayuki (https://www.nayuki.io/) from which this library was - * heavily inspired and compared against. - * - * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp - */ - -#include "qrcode.hpp" +#include "preview/interfaces/QRCode.hpp" #include #include #include @@ -182,7 +147,6 @@ namespace services { uint16_t dataCapacity = moduleCount / 8 - numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; - // Place the data code words into the buffer EncodeDataCodewords(text); // Add terminator and pad up to a byte if applicable @@ -335,24 +299,10 @@ namespace services bitOffset = moduleCount; } - BitBucket::BitBucket(infra::Bitmap& bitmap) - : bitmap(bitmap) - {} - - void BitBucket::Set(infra::Point position, bool on) - { - bitmap.SetBlackAndWhitePixel(position, on); - } - - bool BitBucket::Get(infra::Point position) const - { - return bitmap.BlackAndWhitePixel(position); - } - - void BitBucket::Invert(infra::Point position, bool invert) + void Invert(infra::Bitmap& bitmap, infra::Point position, bool invert) { if (invert) - Set(position, !Get(position)); + bitmap.SetBlackAndWhitePixel(position, !bitmap.BlackAndWhitePixel(position)); } #define PENALTY_N1 3 @@ -362,7 +312,7 @@ namespace services // Calculates and returns the penalty score based on state of this QR Code's current modules. // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. - uint32_t BitBucket::PenaltyScore() const + uint32_t PenaltyScore(const infra::Bitmap& bitmap) { uint32_t result = 0; @@ -371,10 +321,10 @@ namespace services // Adjacent modules in row having same color for (uint8_t y = 0; y < size; ++y) { - bool colorX = Get(infra::Point(0, y)); + bool colorX = bitmap.BlackAndWhitePixel(infra::Point(0, y)); for (uint8_t x = 1, runX = 1; x != size; ++x) { - bool cx = Get(infra::Point(x, y)); + bool cx = bitmap.BlackAndWhitePixel(infra::Point(x, y)); if (cx != colorX) { colorX = cx; @@ -394,10 +344,10 @@ namespace services // Adjacent modules in column having same color for (uint8_t x = 0; x != size; ++x) { - bool colorY = Get(infra::Point(x, 0)); + bool colorY = bitmap.BlackAndWhitePixel(infra::Point(x, 0)); for (uint8_t y = 1, runY = 1; y != size; ++y) { - bool cy = Get(infra::Point(x, y)); + bool cy = bitmap.BlackAndWhitePixel(infra::Point(x, y)); if (cy != colorY) { colorY = cy; @@ -420,21 +370,21 @@ namespace services uint16_t bitsRow = 0, bitsCol = 0; for (uint8_t x = 0; x != size; ++x) { - bool color = Get(infra::Point(x, y)); + bool color = bitmap.BlackAndWhitePixel(infra::Point(x, y)); // 2*2 blocks of modules having same color if (x > 0 && y > 0) { - bool colorUL = Get(infra::Point(x - 1, y - 1)); - bool colorUR = Get(infra::Point(x, y - 1)); - bool colorL = Get(infra::Point(x - 1, y)); + bool colorUL = bitmap.BlackAndWhitePixel(infra::Point(x - 1, y - 1)); + bool colorUR = bitmap.BlackAndWhitePixel(infra::Point(x, y - 1)); + bool colorL = bitmap.BlackAndWhitePixel(infra::Point(x - 1, y)); if (color == colorUL && color == colorUR && color == colorL) result += PENALTY_N2; } // Finder-like pattern in rows and columns bitsRow = ((bitsRow << 1) & 0x7FF) | static_cast(color); - bitsCol = ((bitsCol << 1) & 0x7FF) | static_cast(Get(infra::Point(y, x))); + bitsCol = ((bitsCol << 1) & 0x7FF) | static_cast(bitmap.BlackAndWhitePixel(infra::Point(y, x))); // Needs 11 bits accumulated if (x >= 10) @@ -460,7 +410,7 @@ namespace services return result; } - detail::QRCodeGenerator::QRCodeGenerator(BitBucket& modules, BitBucket& isFunction, BitBuffer& codewords, infra::ByteRange alignPosition, uint8_t version, QRCodeEcc ecc) + detail::QRCodeGenerator::QRCodeGenerator(infra::Bitmap& modules, infra::Bitmap& isFunction, BitBuffer& codewords, infra::ByteRange alignPosition, uint8_t version, QRCodeEcc ecc) : version(version) , ecc(ecc) , modules(modules) @@ -496,7 +446,7 @@ namespace services { DrawFormatBits(i); ApplyMask(i); - int penalty = modules.PenaltyScore(); + int penalty = PenaltyScore(modules); if (penalty < minPenalty) { mask = i; @@ -514,11 +464,11 @@ namespace services // well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). void detail::QRCodeGenerator::ApplyMask(uint8_t mask) { - uint8_t size = modules.bitmap.size.deltaX; + uint8_t size = modules.size.deltaX; - for (auto position : infra::RowFirstPoints(infra::Region(infra::Point(), modules.bitmap.size))) + for (auto position : infra::RowFirstPoints(infra::Region(infra::Point(), modules.size))) { - if (isFunction.Get(position)) + if (isFunction.BlackAndWhitePixel(position)) continue; bool invert = 0; @@ -550,13 +500,13 @@ namespace services break; } - modules.Invert(position, invert); + Invert(modules, position, invert); } } void detail::QRCodeGenerator::DrawFunctionPatterns() { - uint8_t size = modules.bitmap.size.deltaX; + uint8_t size = modules.size.deltaX; // Draw the horizontal and vertical timing patterns for (uint8_t i = 0; i != size; ++i) @@ -590,9 +540,8 @@ namespace services for (uint8_t i = 0; i != alignPosition.size(); ++i) for (uint8_t j = 0; j != alignPosition.size(); ++j) - if ((i == 0 && j == 0) || (i == 0 && j == alignPosition.size() - 1) || (i == alignPosition.size() - 1 && j == 0)) - continue; // Skip the three finder corners - else + // Skip the three finder corners + if (!(i == 0 && j == 0) && !(i == 0 && j == alignPosition.size() - 1) && !(i == alignPosition.size() - 1 && j == 0)) DrawAlignmentPattern(infra::Point(alignPosition[i], alignPosition[j])); } @@ -605,7 +554,7 @@ namespace services // based on this object's version field (which only has an effect for 7 <= version <= 40). void detail::QRCodeGenerator::DrawVersion() { - int8_t size = modules.bitmap.size.deltaX; + int8_t size = modules.size.deltaX; if (version < 7) return; @@ -631,7 +580,7 @@ namespace services // Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). void detail::QRCodeGenerator::DrawFinderPattern(infra::Point position) { - auto bitmapRegion = infra::Region(infra::Point(), modules.bitmap.size); + auto bitmapRegion = infra::Region(infra::Point(), modules.size); auto finderRegion = infra::Region(infra::Point(-4, -4), infra::Point(5, 5)) >> (position - infra::Point()); for (auto i : infra::RowFirstPoints(infra::Intersection(finderRegion, bitmapRegion))) @@ -654,7 +603,7 @@ namespace services // data area of this QR Code symbol. Function modules need to be marked off before this is called. void detail::QRCodeGenerator::DrawCodewords() { - uint8_t size = modules.bitmap.size.deltaX; + uint8_t size = modules.size.deltaX; // Bit index into the data uint32_t i = 0; @@ -673,9 +622,9 @@ namespace services bool upwards = ((right & 2) == 0) ^ (x < 6); uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate auto position = infra::Point(x, y); - if (!isFunction.Get(position)) + if (!isFunction.BlackAndWhitePixel(position)) { - modules.Set(position, codewords.Bit(i)); + modules.SetBlackAndWhitePixel(position, codewords.Bit(i)); ++i; if (i == codewords.Length()) @@ -696,7 +645,7 @@ namespace services 0x01, 0x00, 0x03, 0x02 }; - uint8_t size = modules.bitmap.size.deltaX; + uint8_t size = modules.size.deltaX; // Calculate error correction code and pack bits uint32_t data = eccFormatBits[static_cast(ecc)] << 3 | mask; // ecc is uint2, mask is uint3 @@ -730,8 +679,8 @@ namespace services void detail::QRCodeGenerator::SetFunctionModule(infra::Point position, bool on) { - modules.Set(position, on); - isFunction.Set(position, true); + modules.SetBlackAndWhitePixel(position, on); + isFunction.SetBlackAndWhitePixel(position, true); } } } diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QRCode.hpp index 7c6ae4d..6585324 100644 --- a/preview/interfaces/QRCode.hpp +++ b/preview/interfaces/QRCode.hpp @@ -33,8 +33,8 @@ * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp */ -#ifndef QR_CODE_HPP -#define QR_CODE_HPP +#ifndef PREVIEW_QR_CODE_HPP +#define PREVIEW_QR_CODE_HPP #include "infra/util/BoundedString.hpp" #include "infra/util/ByteRange.hpp" @@ -59,51 +59,14 @@ namespace services const infra::Bitmap& GetBitmap() const; - public: - static constexpr uint8_t size = Version * 4 + 17; - infra::Bitmap::BlackAndWhite bitmap; + using Bitmap = infra::Bitmap::BlackAndWhite; + + private: + Bitmap bitmap; }; namespace detail { - static constexpr uint16_t GridSizeInBytes(uint8_t bits) - { - return (bits * bits + 7) / 8; - } - - static constexpr uint16_t BufferSize(uint8_t version) - { - return GridSizeInBytes(4 * version + 17); - } - - class BitBucket - { - public: - template - struct ForVersion; - - BitBucket(infra::Bitmap& bitmap); - - void Set(infra::Point position, bool on); - bool Get(infra::Point position) const; - void Invert(infra::Point position, bool invert); - - uint32_t PenaltyScore() const; - - public: - infra::Bitmap& bitmap; - }; - - template - struct BitBucket::ForVersion - : BitBucket - { - ForVersion(); - - static constexpr uint8_t size = Version * 4 + 17; - infra::Bitmap::BlackAndWhite bitmap; - }; - class BitBuffer { private: @@ -212,7 +175,7 @@ namespace services template struct ForVersionAndEcc; - QRCodeGenerator(BitBucket& modules, BitBucket& isFunction, BitBuffer& codewords, infra::ByteRange alignPosition, uint8_t version, QRCodeEcc ecc); + QRCodeGenerator(infra::Bitmap& modules, infra::Bitmap& isFunction, BitBuffer& codewords, infra::ByteRange alignPosition, uint8_t version, QRCodeEcc ecc); void Generate(infra::BoundedConstString text); @@ -232,8 +195,8 @@ namespace services uint8_t version; QRCodeEcc ecc; - BitBucket& modules; - BitBucket& isFunction; + infra::Bitmap& modules; + infra::Bitmap& isFunction; BitBuffer& codewords; @@ -244,10 +207,10 @@ namespace services struct QRCodeGenerator::ForVersionAndEcc : QRCodeGenerator { - ForVersionAndEcc(BitBucket& modules); + ForVersionAndEcc(infra::Bitmap& modules); - detail::BitBucket::ForVersion isFunction; - detail::BitBuffer::ForVersionAndEcc codewords; + typename QRCode::Bitmap isFunction; + BitBuffer::ForVersionAndEcc codewords; std::array alignPosition; }; } @@ -256,30 +219,24 @@ namespace services namespace detail { - template - BitBucket::ForVersion::ForVersion() - : BitBucket(bitmap) - { - bitmap.Clear(); - } - template BitBuffer::ForVersionAndEcc::ForVersionAndEcc() : buffer(data, result, coeff, Version, Ecc) {} template - QRCodeGenerator::ForVersionAndEcc::ForVersionAndEcc(BitBucket& modules) + QRCodeGenerator::ForVersionAndEcc::ForVersionAndEcc(infra::Bitmap& modules) : QRCodeGenerator(modules, isFunction, codewords.buffer, alignPosition, Version, Ecc) - {} + { + isFunction.Clear(); + } } template QRCode::QRCode(infra::BoundedConstString text) { bitmap.Clear(); - detail::BitBucket modules(bitmap); - detail::QRCodeGenerator::ForVersionAndEcc generator(modules); + detail::QRCodeGenerator::ForVersionAndEcc generator(bitmap); generator.Generate(text); } From 8c2e104f42853416bd9358b0df7785f29ae853fd Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Sun, 15 Sep 2024 07:37:05 +0200 Subject: [PATCH 10/22] Move copyright notice to notice file --- NOTICE | 28 ++++++++++++++++++++++++++++ preview/interfaces/QRCode.hpp | 35 ----------------------------------- 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/NOTICE b/NOTICE index 8022063..167d795 100644 --- a/NOTICE +++ b/NOTICE @@ -24,3 +24,31 @@ Notice file for preview/fonts/FreeSans*.cpp CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Notice file for preview/interfaces/QRCode.cpp/hpp + + The MIT License (MIT) + + This library is written and maintained by Richard Moore. + Major parts were derived from Project Nayuki's library. + + Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) + Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QRCode.hpp index 6585324..aa120d2 100644 --- a/preview/interfaces/QRCode.hpp +++ b/preview/interfaces/QRCode.hpp @@ -1,38 +1,3 @@ -/** - * The MIT License (MIT) - * - * This library is written and maintained by Richard Moore. - * Major parts were derived from Project Nayuki's library. - * - * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) - * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * Special thanks to Nayuki (https://www.nayuki.io/) from which this library was - * heavily inspired and compared against. - * - * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp - */ - #ifndef PREVIEW_QR_CODE_HPP #define PREVIEW_QR_CODE_HPP From 8fc7e3a69bcddafda017b2ce7247f1262ccbf5b7 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Sun, 15 Sep 2024 08:34:34 +0200 Subject: [PATCH 11/22] Refactor --- examples/clicking_scrolling/MainWin.cpp | 2 +- preview/interfaces/QRCode.cpp | 545 ++++++++++++------------ preview/interfaces/QRCode.hpp | 42 +- 3 files changed, 312 insertions(+), 277 deletions(-) diff --git a/examples/clicking_scrolling/MainWin.cpp b/examples/clicking_scrolling/MainWin.cpp index 7cc5855..2233235 100644 --- a/examples/clicking_scrolling/MainWin.cpp +++ b/examples/clicking_scrolling/MainWin.cpp @@ -17,7 +17,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine infra::LowPowerEventDispatcher::WithSize<50> eventDispatcher(lowPowerStrategy); services::QRCode<3, services::QRCodeEcc::low> qrcode("HELLO WORLD"); - services::ViewBitmap viewBitmap(qrcode.GetBitmap()); + services::ViewBitmap viewBitmap(qrcode); hal::DirectDisplaySdl display(infra::Vector(480, 272)); services::ViewPainterDirectDisplay painter(display); diff --git a/preview/interfaces/QRCode.cpp b/preview/interfaces/QRCode.cpp index b716f5f..555ea2c 100644 --- a/preview/interfaces/QRCode.cpp +++ b/preview/interfaces/QRCode.cpp @@ -1,139 +1,11 @@ #include "preview/interfaces/QRCode.hpp" #include -#include -#include -#include +#include namespace services { namespace detail { - static int8_t getAlphanumeric(char c) - { - if (c >= '0' && c <= '9') - return (c - '0'); - if (c >= 'A' && c <= 'Z') - return (c - 'A' + 10); - - switch (c) - { - case ' ': - return 36; - case '$': - return 37; - case '%': - return 38; - case '*': - return 39; - case '+': - return 40; - case '-': - return 41; - case '.': - return 42; - case '/': - return 43; - case ':': - return 44; - default: - return -1; - } - } - - static bool isAlphanumeric(infra::BoundedConstString text) - { - for (auto c : text) - if (getAlphanumeric(c) == -1) - return false; - - return true; - } - - static bool isNumeric(infra::BoundedConstString text) - { - for (auto c : text) - if (c < '0' || c > '9') - return false; - - return true; - } - - // We store the following tightly packed (less 8) in modeInfo - // <=9 <=26 <= 40 - // NUMERIC ( 10, 12, 14); - // ALPHANUMERIC ( 9, 11, 13); - // BYTE ( 8, 16, 16); - static char getModeBits(uint8_t version, uint8_t mode) - { - // Note: We use 15 instead of 16; since 15 doesn't exist and we cannot store 16 (8 + 8) in 3 bits - // hex(int("".join(reversed([('00' + bin(x - 8)[2:])[-3:] for x in [10, 9, 8, 12, 11, 15, 14, 13, 15]])), 2)) - unsigned int modeInfo = 0x7bbb80a; - - if (version > 9) - modeInfo >>= 9; - - if (version > 26) - modeInfo >>= 9; - - char result = 8 + ((modeInfo >> (3 * mode)) & 0x07); - - if (result == 15) - result = 16; - - return result; - } - - BitBuffer::ReedSolomon::ReedSolomon(infra::ByteRange coeff) - : coeff(coeff) - { - coeff.back() = 1; - - // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{coeff.size()-1}), - // drop the highest term, and store the rest of the coefficients in order of descending powers. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - uint16_t root = 1; - for (uint8_t i = 0; i < coeff.size(); i++) - { - // Multiply the current product by (x - r^i) - for (uint8_t j = 0; j != coeff.size(); ++j) - { - coeff[j] = Multiply(coeff[j], root); - if (j + 1 < coeff.size()) - coeff[j] ^= coeff[j + 1]; - } - root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) - } - } - - void BitBuffer::ReedSolomon::Remainder(uint8_t* data, uint8_t length, uint8_t* result, uint8_t stride) const - { - // Compute the remainder by performing polynomial division - for (uint8_t i = 0; i != length; ++i) - { - uint8_t factor = data[i] ^ result[0]; - for (uint8_t j = 1; j != coeff.size(); ++j) - result[(j - 1) * stride] = result[j * stride]; - - result[(coeff.size() - 1) * stride] = 0; - - for (uint8_t j = 0; j != coeff.size(); ++j) - result[j * stride] ^= Multiply(coeff[j], factor); - } - } - - uint8_t BitBuffer::ReedSolomon::Multiply(uint8_t x, uint8_t y) const - { - // Russian peasant multiplication - // See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication - uint16_t z = 0; - for (int8_t i = 7; i >= 0; i--) - { - z = (z << 1) ^ ((z >> 7) * 0x11D); - z ^= ((y >> i) & 1) * x; - } - return z; - } - BitBuffer::BitBuffer(infra::ByteRange data, infra::ByteRange result, infra::ByteRange coeff, uint8_t version, QRCodeEcc ecc) : version(version) , ecc(ecc) @@ -180,62 +52,67 @@ namespace services void BitBuffer::EncodeDataCodewords(infra::BoundedConstString text) { -#define MODE_NUMERIC 0 -#define MODE_ALPHANUMERIC 1 -#define MODE_BYTE 2 + if (IsNumeric(text)) + EncodeNumeric(text); + else if (IsAlphanumeric(text)) + EncodeAlphanumeric(text); + else + EncodeBinary(text); + } - if (isNumeric(text)) - { - Append(1 << MODE_NUMERIC, 4); - Append(text.size(), getModeBits(version, MODE_NUMERIC)); + void BitBuffer::EncodeNumeric(infra::BoundedConstString text) + { + Append(1 << numeric, 4); + Append(text.size(), ModeBitsNumeric()); - uint16_t accumData = 0; - uint8_t accumCount = 0; - for (auto c : text) + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (auto c : text) + { + accumData = accumData * 10 + (c - '0'); + ++accumCount; + if (accumCount == 3) { - accumData = accumData * 10 + (c - '0'); - ++accumCount; - if (accumCount == 3) - { - Append(accumData, 10); - accumData = 0; - accumCount = 0; - } + Append(accumData, 10); + accumData = 0; + accumCount = 0; } - - // 1 or 2 digits remaining - if (accumCount > 0) - Append(accumData, accumCount * 3 + 1); } - else if (isAlphanumeric(text)) - { - Append(1 << MODE_ALPHANUMERIC, 4); - Append(text.size(), getModeBits(version, MODE_ALPHANUMERIC)); - uint16_t accumData = 0; - uint8_t accumCount = 0; - for (auto c : text) - { - accumData = accumData * 45 + getAlphanumeric(c); - ++accumCount; - if (accumCount == 2) - { - Append(accumData, 11); - accumData = 0; - accumCount = 0; - } - } + // 1 or 2 digits remaining + if (accumCount > 0) + Append(accumData, accumCount * 3 + 1); + } - if (accumCount == 1) - Append(accumData, 6); - } - else + void BitBuffer::EncodeAlphanumeric(infra::BoundedConstString text) + { + Append(1 << alphanumeric, 4); + Append(text.size(), ModeBitsAlphanumeric()); + + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (auto c : text) { - Append(1 << MODE_BYTE, 4); - Append(text.size(), getModeBits(version, MODE_BYTE)); - for (auto c : text) - Append(c, 8); + accumData = accumData * 45 + GetAlphanumeric(c); + ++accumCount; + if (accumCount == 2) + { + Append(accumData, 11); + accumData = 0; + accumCount = 0; + } } + + if (accumCount == 1) + Append(accumData, 6); + } + + void BitBuffer::EncodeBinary(infra::BoundedConstString text) + { + Append(1 << latin1, 4); + Append(text.size(), ModeBitsLatin1()); + for (auto c : text) + Append(c, 8); } void BitBuffer::PerformErrorCorrection(QRCodeEcc ecc) @@ -268,20 +145,17 @@ namespace services } } - // Version less than 5 only have short blocks + // Interleave long blocks (versions less than 5 only have short blocks) + uint16_t index = shortDataBlockLen * (numShortBlocks + 1); + uint8_t stride = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum != numBlocks - numShortBlocks; ++blockNum) { - // Interleave long blocks - uint16_t index = shortDataBlockLen * (numShortBlocks + 1); - uint8_t stride = shortDataBlockLen; - for (uint8_t blockNum = 0; blockNum != numBlocks - numShortBlocks; ++blockNum) - { - result[offset++] = dataBytes[index]; + result[offset++] = dataBytes[index]; - if (blockNum == 0) - ++stride; + if (blockNum == 0) + ++stride; - index += stride; - } + index += stride; } // Add all ecc blocks, interleaved @@ -299,76 +173,200 @@ namespace services bitOffset = moduleCount; } - void Invert(infra::Bitmap& bitmap, infra::Point position, bool invert) + uint8_t BitBuffer::ModeBitsNumeric() const { - if (invert) - bitmap.SetBlackAndWhitePixel(position, !bitmap.BlackAndWhitePixel(position)); + if (version <= 9) + return 10; + if (version <= 26) + return 12; + return 14; } -#define PENALTY_N1 3 -#define PENALTY_N2 3 -#define PENALTY_N3 40 -#define PENALTY_N4 10 + uint8_t BitBuffer::ModeBitsAlphanumeric() const + { + if (version <= 9) + return 9; + if (version <= 26) + return 11; + return 13; + } - // Calculates and returns the penalty score based on state of this QR Code's current modules. - // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. - uint32_t PenaltyScore(const infra::Bitmap& bitmap) + uint8_t BitBuffer::ModeBitsLatin1() const + { + if (version <= 9) + return 8; + if (version <= 26) + return 16; + return 16; + } + + int8_t BitBuffer::GetAlphanumeric(char c) + { + if (c >= '0' && c <= '9') + return (c - '0'); + if (c >= 'A' && c <= 'Z') + return (c - 'A' + 10); + + switch (c) + { + case ' ': + return 36; + case '$': + return 37; + case '%': + return 38; + case '*': + return 39; + case '+': + return 40; + case '-': + return 41; + case '.': + return 42; + case '/': + return 43; + case ':': + return 44; + default: + return -1; + } + } + + bool BitBuffer::IsAlphanumeric(infra::BoundedConstString text) { - uint32_t result = 0; + for (auto c : text) + if (GetAlphanumeric(c) == -1) + return false; + + return true; + } + + bool BitBuffer::IsNumeric(infra::BoundedConstString text) + { + for (auto c : text) + if (c < '0' || c > '9') + return false; - uint8_t size = bitmap.size.deltaX; + return true; + } - // Adjacent modules in row having same color - for (uint8_t y = 0; y < size; ++y) + BitBuffer::ReedSolomon::ReedSolomon(infra::ByteRange coeff) + : coeff(coeff) + { + coeff.back() = 1; + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{coeff.size()-1}), + // drop the highest term, and store the rest of the coefficients in order of descending powers. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint16_t root = 1; + for (uint8_t i = 0; i < coeff.size(); i++) { - bool colorX = bitmap.BlackAndWhitePixel(infra::Point(0, y)); - for (uint8_t x = 1, runX = 1; x != size; ++x) + // Multiply the current product by (x - r^i) + for (uint8_t j = 0; j != coeff.size(); ++j) { - bool cx = bitmap.BlackAndWhitePixel(infra::Point(x, y)); - if (cx != colorX) + coeff[j] = Multiply(coeff[j], root); + if (j + 1 < coeff.size()) + coeff[j] ^= coeff[j + 1]; + } + root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) + } + } + + void BitBuffer::ReedSolomon::Remainder(uint8_t* data, uint8_t length, uint8_t* result, uint8_t stride) const + { + // Compute the remainder by performing polynomial division + for (uint8_t i = 0; i != length; ++i) + { + uint8_t factor = data[i] ^ result[0]; + for (uint8_t j = 1; j != coeff.size(); ++j) + result[(j - 1) * stride] = result[j * stride]; + + result[(coeff.size() - 1) * stride] = 0; + + for (uint8_t j = 0; j != coeff.size(); ++j) + result[j * stride] ^= Multiply(coeff[j], factor); + } + } + + uint8_t BitBuffer::ReedSolomon::Multiply(uint8_t x, uint8_t y) const + { + // Russian peasant multiplication + // See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication + uint16_t z = 0; + for (int8_t i = 7; i >= 0; i--) + { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + return z; + } + + uint32_t PenaltyScoreRowRuns(const infra::Bitmap& bitmap) + { + uint32_t penalty = 0; + + for (uint8_t y = 0; y != bitmap.size.deltaY; ++y) + { + bool runColour = bitmap.BlackAndWhitePixel(infra::Point(0, y)); + uint8_t run = 1; + for (uint8_t x = 1; x != bitmap.size.deltaX; ++x) + { + bool colour = bitmap.BlackAndWhitePixel(infra::Point(x, y)); + if (colour != runColour) { - colorX = cx; - runX = 1; + runColour = colour; + run = 1; } else { - ++runX; - if (runX == 5) - result += PENALTY_N1; - else if (runX > 5) - ++result; + ++run; + if (run == 5) + penalty += 3; + else if (run > 5) + ++penalty; } } } - // Adjacent modules in column having same color - for (uint8_t x = 0; x != size; ++x) + return penalty; + } + + uint32_t PenaltyScoreColumnRuns(const infra::Bitmap& bitmap) + { + uint32_t penalty = 0; + + for (uint8_t x = 0; x != bitmap.size.deltaX; ++x) { - bool colorY = bitmap.BlackAndWhitePixel(infra::Point(x, 0)); - for (uint8_t y = 1, runY = 1; y != size; ++y) + bool runColour = bitmap.BlackAndWhitePixel(infra::Point(x, 0)); + uint8_t run = 1; + for (uint8_t y = 1; y != bitmap.size.deltaY; ++y) { - bool cy = bitmap.BlackAndWhitePixel(infra::Point(x, y)); - if (cy != colorY) + bool colour = bitmap.BlackAndWhitePixel(infra::Point(x, y)); + if (colour != runColour) { - colorY = cy; - runY = 1; + runColour = colour; + run = 1; } else { - ++runY; - if (runY == 5) - result += PENALTY_N1; - else if (runY > 5) - ++result; + ++run; + if (run == 5) + penalty += 3; + else if (run > 5) + ++penalty; } } } - uint16_t black = 0; - for (uint8_t y = 0; y != size; ++y) - { - uint16_t bitsRow = 0, bitsCol = 0; - for (uint8_t x = 0; x != size; ++x) + return penalty; + } + + uint32_t PenaltyScoreBlocks(const infra::Bitmap& bitmap) + { + uint32_t penalty = 0; + + for (uint8_t y = 0; y != bitmap.size.deltaY; ++y) + for (uint8_t x = 0; x != bitmap.size.deltaX; ++x) { bool color = bitmap.BlackAndWhitePixel(infra::Point(x, y)); @@ -379,38 +377,68 @@ namespace services bool colorUR = bitmap.BlackAndWhitePixel(infra::Point(x, y - 1)); bool colorL = bitmap.BlackAndWhitePixel(infra::Point(x - 1, y)); if (color == colorUL && color == colorUR && color == colorL) - result += PENALTY_N2; + penalty += 3; } + } + + return penalty; + } + uint32_t PenaltyScoreFinderLike(const infra::Bitmap& bitmap) + { + uint32_t penalty = 0; + + for (uint8_t y = 0; y != bitmap.size.deltaY; ++y) + { + uint16_t bitsRow = 0; + uint16_t bitsCol = 0; + for (uint8_t x = 0; x != bitmap.size.deltaX; ++x) + { // Finder-like pattern in rows and columns - bitsRow = ((bitsRow << 1) & 0x7FF) | static_cast(color); + bitsRow = ((bitsRow << 1) & 0x7FF) | static_cast(bitmap.BlackAndWhitePixel(infra::Point(x, y))); bitsCol = ((bitsCol << 1) & 0x7FF) | static_cast(bitmap.BlackAndWhitePixel(infra::Point(y, x))); // Needs 11 bits accumulated if (x >= 10) { if (bitsRow == 0x05D || bitsRow == 0x5D0) - result += PENALTY_N3; + penalty += 40; if (bitsCol == 0x05D || bitsCol == 0x5D0) - result += PENALTY_N3; + penalty += 40; } - - // Balance of black and white modules - if (color) - ++black; } } + return penalty; + } + + uint32_t PenaltyScoreBalance(const infra::Bitmap& bitmap) + { + uint32_t penalty = 0; + + uint16_t numDark = 0; + for (uint8_t y = 0; y != bitmap.size.deltaY; ++y) + for (uint8_t x = 0; x != bitmap.size.deltaX; ++x) + if (bitmap.BlackAndWhitePixel(infra::Point(x, y))); + ++numDark; + // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% - uint16_t total = size * size; - for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) - result += PENALTY_N4; + uint16_t total = bitmap.size.deltaX * bitmap.size.deltaY; + for (uint16_t k = 0; numDark * 20 < (9 - k) * total || numDark * 20 > (11 + k) * total; ++k) + penalty += 10; + + return penalty; + } - return result; + // Calculates and returns the penalty score based on the state of this QR Code's current modules. + // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. + uint32_t PenaltyScore(const infra::Bitmap& bitmap) + { + return PenaltyScoreRowRuns(bitmap) + PenaltyScoreColumnRuns(bitmap) + PenaltyScoreBlocks(bitmap) + PenaltyScoreFinderLike(bitmap) + PenaltyScoreBalance(bitmap); } - detail::QRCodeGenerator::QRCodeGenerator(infra::Bitmap& modules, infra::Bitmap& isFunction, BitBuffer& codewords, infra::ByteRange alignPosition, uint8_t version, QRCodeEcc ecc) + QRCodeGenerator::QRCodeGenerator(infra::Bitmap& modules, infra::Bitmap& isFunction, BitBuffer& codewords, infra::ByteRange alignPosition, uint8_t version, QRCodeEcc ecc) : version(version) , ecc(ecc) , modules(modules) @@ -423,25 +451,19 @@ namespace services { codewords.Generate(text); - // Draw function patterns, draw all codewords, do masking DrawFunctionPatterns(); DrawCodewords(); - // Find the best (lowest penalty) mask uint8_t mask = BestMask(); - - // Overwrite old format bits DrawFormatBits(mask); - - // Apply the final choice of mask ApplyMask(mask); } - uint8_t detail::QRCodeGenerator::BestMask() + uint8_t QRCodeGenerator::BestMask() { uint8_t mask = 0; - int32_t minPenalty = INT32_MAX; + int32_t minPenalty = std::numeric_limits::max(); for (uint8_t i = 0; i != 8; ++i) { DrawFormatBits(i); @@ -452,7 +474,7 @@ namespace services mask = i; minPenalty = penalty; } - ApplyMask(i); // Undoes the mask due to XOR + ApplyMask(i); // Removes the mask due to XOR } return mask; @@ -462,7 +484,7 @@ namespace services // properties, calling ApplyMask(m) twice with the same value is equivalent to no change at all. // This means it is possible to apply a mask, undo it, and try another mask. Note that a final // well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). - void detail::QRCodeGenerator::ApplyMask(uint8_t mask) + void QRCodeGenerator::ApplyMask(uint8_t mask) { uint8_t size = modules.size.deltaX; @@ -500,11 +522,12 @@ namespace services break; } - Invert(modules, position, invert); + if (invert) + modules.SetBlackAndWhitePixel(position, !modules.BlackAndWhitePixel(position)); } } - void detail::QRCodeGenerator::DrawFunctionPatterns() + void QRCodeGenerator::DrawFunctionPatterns() { uint8_t size = modules.size.deltaX; @@ -552,7 +575,7 @@ namespace services // Draws two copies of the version bits (with its own error correction code), // based on this object's version field (which only has an effect for 7 <= version <= 40). - void detail::QRCodeGenerator::DrawVersion() + void QRCodeGenerator::DrawVersion() { int8_t size = modules.size.deltaX; @@ -578,7 +601,7 @@ namespace services } // Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). - void detail::QRCodeGenerator::DrawFinderPattern(infra::Point position) + void QRCodeGenerator::DrawFinderPattern(infra::Point position) { auto bitmapRegion = infra::Region(infra::Point(), modules.size); auto finderRegion = infra::Region(infra::Point(-4, -4), infra::Point(5, 5)) >> (position - infra::Point()); @@ -591,7 +614,7 @@ namespace services } // Draws a 5*5 alignment pattern, with the center module at (x, y). - void detail::QRCodeGenerator::DrawAlignmentPattern(infra::Point position) + void QRCodeGenerator::DrawAlignmentPattern(infra::Point position) { auto alignmentRegion = infra::Region(infra::Point(-2, -2), infra::Point(3, 3)) >> (position - infra::Point()); @@ -601,7 +624,7 @@ namespace services // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire // data area of this QR Code symbol. Function modules need to be marked off before this is called. - void detail::QRCodeGenerator::DrawCodewords() + void QRCodeGenerator::DrawCodewords() { uint8_t size = modules.size.deltaX; @@ -639,7 +662,7 @@ namespace services // Draws two copies of the format bits (with its own error correction code) // based on the given mask and this object's error correction level field. - void detail::QRCodeGenerator::DrawFormatBits(uint8_t mask) + void QRCodeGenerator::DrawFormatBits(uint8_t mask) { static constexpr std::array eccFormatBits{ 0x01, 0x00, 0x03, 0x02 @@ -677,7 +700,7 @@ namespace services SetFunctionModule(infra::Point(8, size - 8), true); } - void detail::QRCodeGenerator::SetFunctionModule(infra::Point position, bool on) + void QRCodeGenerator::SetFunctionModule(infra::Point position, bool on) { modules.SetBlackAndWhitePixel(position, on); isFunction.SetBlackAndWhitePixel(position, true); diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QRCode.hpp index aa120d2..c7f7bd5 100644 --- a/preview/interfaces/QRCode.hpp +++ b/preview/interfaces/QRCode.hpp @@ -4,6 +4,7 @@ #include "infra/util/BoundedString.hpp" #include "infra/util/ByteRange.hpp" #include "preview/interfaces/Bitmap.hpp" +#include #include namespace services @@ -17,17 +18,14 @@ namespace services }; template - class QRCode + struct QRCode + : infra::Bitmap::BlackAndWhite { - public: QRCode(infra::BoundedConstString text); - const infra::Bitmap& GetBitmap() const; + void Update(infra::BoundedConstString text); - using Bitmap = infra::Bitmap::BlackAndWhite; - - private: - Bitmap bitmap; + using BitmapType = infra::Bitmap::BlackAndWhite; }; namespace detail @@ -57,8 +55,6 @@ namespace services static constexpr auto numErrorCorrectionBlocks = []() { const std::array, 4> result{ { - // Version: (note that index 0 is for padding, and is set to an illegal value) - // 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level { 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25 }, // Low { 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49 }, // Medium { 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68 }, // Quartile @@ -91,11 +87,27 @@ namespace services uint16_t Length() const; bool Bit(uint16_t index) const; + private: + static constexpr uint8_t numeric = 0; + static constexpr uint8_t alphanumeric = 1; + static constexpr uint8_t latin1 = 2; + private: void Append(uint32_t val, uint8_t length); void EncodeDataCodewords(infra::BoundedConstString text); + void EncodeNumeric(infra::BoundedConstString text); + void EncodeAlphanumeric(infra::BoundedConstString text); + void EncodeBinary(infra::BoundedConstString text); void PerformErrorCorrection(QRCodeEcc ecc); + uint8_t ModeBitsNumeric() const; + uint8_t ModeBitsAlphanumeric() const; + uint8_t ModeBitsLatin1() const; + + static int8_t GetAlphanumeric(char c); + static bool IsAlphanumeric(infra::BoundedConstString text); + static bool IsNumeric(infra::BoundedConstString text); + private: class ReedSolomon { @@ -174,7 +186,7 @@ namespace services { ForVersionAndEcc(infra::Bitmap& modules); - typename QRCode::Bitmap isFunction; + typename QRCode::BitmapType isFunction; BitBuffer::ForVersionAndEcc codewords; std::array alignPosition; }; @@ -200,15 +212,15 @@ namespace services template QRCode::QRCode(infra::BoundedConstString text) { - bitmap.Clear(); - detail::QRCodeGenerator::ForVersionAndEcc generator(bitmap); - generator.Generate(text); + Update(text); } template - const infra::Bitmap& QRCode::GetBitmap() const + void QRCode::Update(infra::BoundedConstString text) { - return bitmap; + Clear(); + detail::QRCodeGenerator::ForVersionAndEcc generator(*this); + generator.Generate(text); } } From 52de198881ede2d4730f6e717ea1a3fb0e215fc5 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Thu, 26 Sep 2024 09:29:13 +0200 Subject: [PATCH 12/22] Add max sizes and error checking --- preview/interfaces/QRCode.cpp | 41 ++++------------ preview/interfaces/QRCode.hpp | 89 +++++++++++++++++++++++++++++++++-- 2 files changed, 93 insertions(+), 37 deletions(-) diff --git a/preview/interfaces/QRCode.cpp b/preview/interfaces/QRCode.cpp index 555ea2c..abdc6d5 100644 --- a/preview/interfaces/QRCode.cpp +++ b/preview/interfaces/QRCode.cpp @@ -1,5 +1,6 @@ #include "preview/interfaces/QRCode.hpp" #include +#include #include namespace services @@ -57,13 +58,14 @@ namespace services else if (IsAlphanumeric(text)) EncodeAlphanumeric(text); else - EncodeBinary(text); + EncodeLatin1(text); } void BitBuffer::EncodeNumeric(infra::BoundedConstString text) { + assert(text.size() <= MaxSizeNumeric(version, ecc)); Append(1 << numeric, 4); - Append(text.size(), ModeBitsNumeric()); + Append(text.size(), ModeBitsNumeric(version)); uint16_t accumData = 0; uint8_t accumCount = 0; @@ -86,8 +88,9 @@ namespace services void BitBuffer::EncodeAlphanumeric(infra::BoundedConstString text) { + assert(text.size() <= MaxSizeAlphanumeric(version, ecc)); Append(1 << alphanumeric, 4); - Append(text.size(), ModeBitsAlphanumeric()); + Append(text.size(), ModeBitsAlphanumeric(version)); uint16_t accumData = 0; uint8_t accumCount = 0; @@ -107,10 +110,11 @@ namespace services Append(accumData, 6); } - void BitBuffer::EncodeBinary(infra::BoundedConstString text) + void BitBuffer::EncodeLatin1(infra::BoundedConstString text) { + assert(text.size() <= MaxSizeLatin1(version, ecc)); Append(1 << latin1, 4); - Append(text.size(), ModeBitsLatin1()); + Append(text.size(), ModeBitsLatin1(version)); for (auto c : text) Append(c, 8); } @@ -173,33 +177,6 @@ namespace services bitOffset = moduleCount; } - uint8_t BitBuffer::ModeBitsNumeric() const - { - if (version <= 9) - return 10; - if (version <= 26) - return 12; - return 14; - } - - uint8_t BitBuffer::ModeBitsAlphanumeric() const - { - if (version <= 9) - return 9; - if (version <= 26) - return 11; - return 13; - } - - uint8_t BitBuffer::ModeBitsLatin1() const - { - if (version <= 9) - return 8; - if (version <= 26) - return 16; - return 16; - } - int8_t BitBuffer::GetAlphanumeric(char c) { if (c >= '0' && c <= '9') diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QRCode.hpp index c7f7bd5..02267bd 100644 --- a/preview/interfaces/QRCode.hpp +++ b/preview/interfaces/QRCode.hpp @@ -25,6 +25,10 @@ namespace services void Update(infra::BoundedConstString text); + static constexpr std::size_t MaxSizeNumeric(); + static constexpr std::size_t MaxSizeAlphanumeric(); + static constexpr std::size_t MaxSizeLatin1(); + using BitmapType = infra::Bitmap::BlackAndWhite; }; @@ -76,6 +80,67 @@ namespace services return totalEcc / numBlocks; } + static constexpr uint8_t BitBuffer::ModeBitsNumeric(uint8_t version) + { + if (version <= 9) + return 10; + if (version <= 26) + return 12; + return 14; + } + + static constexpr uint8_t BitBuffer::ModeBitsAlphanumeric(uint8_t version) + { + if (version <= 9) + return 9; + if (version <= 26) + return 11; + return 13; + } + + static constexpr uint8_t BitBuffer::ModeBitsLatin1(uint8_t version) + { + if (version <= 9) + return 8; + if (version <= 26) + return 16; + return 16; + } + + static constexpr std::size_t MaxSizeNumeric(uint8_t version, QRCodeEcc ecc) + { + uint16_t moduleCount = numRawDataModulesForVersion[version]; + uint16_t dataCapacityInBytes = moduleCount / 8 - numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; + uint16_t digitBits = dataCapacityInBytes * 8 - 4 - ModeBitsNumeric(version); + + uint16_t blocksOf3 = digitBits / 10; + digitBits -= blocksOf3 * 10; + + return blocksOf3 * 3 + (digitBits >= 7 ? 2 : digitBits >= 4 ? 1 + : 0); + } + + static constexpr std::size_t MaxSizeAlphanumeric(uint8_t version, QRCodeEcc ecc) + { + uint16_t moduleCount = numRawDataModulesForVersion[version]; + uint16_t dataCapacityInBytes = moduleCount / 8 - numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; + uint16_t digitBits = dataCapacityInBytes * 8 - 4 - ModeBitsAlphanumeric(version); + + uint16_t blocksOf2 = digitBits / 11; + digitBits -= blocksOf2 * 11; + + return blocksOf2 * 2 + digitBits / 6; + } + + static constexpr std::size_t MaxSizeLatin1(uint8_t version, QRCodeEcc ecc) + { + uint16_t moduleCount = numRawDataModulesForVersion[version]; + uint16_t dataCapacityInBytes = moduleCount / 8 - numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; + uint16_t digitBits = dataCapacityInBytes * 8 - 4 - ModeBitsLatin1(version); + + return digitBits / 8; + } + public: template struct ForVersionAndEcc; @@ -97,13 +162,9 @@ namespace services void EncodeDataCodewords(infra::BoundedConstString text); void EncodeNumeric(infra::BoundedConstString text); void EncodeAlphanumeric(infra::BoundedConstString text); - void EncodeBinary(infra::BoundedConstString text); + void EncodeLatin1(infra::BoundedConstString text); void PerformErrorCorrection(QRCodeEcc ecc); - uint8_t ModeBitsNumeric() const; - uint8_t ModeBitsAlphanumeric() const; - uint8_t ModeBitsLatin1() const; - static int8_t GetAlphanumeric(char c); static bool IsAlphanumeric(infra::BoundedConstString text); static bool IsNumeric(infra::BoundedConstString text); @@ -222,6 +283,24 @@ namespace services detail::QRCodeGenerator::ForVersionAndEcc generator(*this); generator.Generate(text); } + + template + constexpr std::size_t QRCode::MaxSizeNumeric() + { + return BitBuffer::MaxSizeNumeric(Version, Ecc); + } + + template + constexpr std::size_t QRCode::MaxSizeAlphanumeric() + { + return BitBuffer::MaxSizeAlphanumeric(Version, Ecc); + } + + template + constexpr std::size_t QRCode::MaxSizeLatin1() + { + return BitBuffer::MaxSizeLatin1(Version, Ecc); + } } #endif From 6cedc3265b1f4975f62cd321783626ddbcbbac72 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Thu, 26 Sep 2024 09:46:51 +0200 Subject: [PATCH 13/22] Improve naming and examples --- examples/CMakeLists.txt | 1 + examples/clicking_scrolling/MainWin.cpp | 8 +- examples/qr_code/CMakeLists.txt | 29 ++++++++ examples/qr_code/MainStm32f746.cpp | 71 ++++++++++++++++++ examples/qr_code/MainWin.cpp | 31 ++++++++ preview/interfaces/CMakeLists.txt | 4 +- preview/interfaces/QRCode.cpp | 62 ++++++++-------- preview/interfaces/QRCode.hpp | 98 ++++++++++++------------- 8 files changed, 215 insertions(+), 89 deletions(-) create mode 100644 examples/qr_code/CMakeLists.txt create mode 100644 examples/qr_code/MainStm32f746.cpp create mode 100644 examples/qr_code/MainWin.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 52bce91..541a0fc 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(clicking_scrolling) +add_subdirectory(qr_code) diff --git a/examples/clicking_scrolling/MainWin.cpp b/examples/clicking_scrolling/MainWin.cpp index 2233235..7de27d4 100644 --- a/examples/clicking_scrolling/MainWin.cpp +++ b/examples/clicking_scrolling/MainWin.cpp @@ -1,13 +1,11 @@ #include "examples/clicking_scrolling/TouchViewClickingScrolling.hpp" #include "hal/generic/TimerServiceGeneric.hpp" #include "infra/event/LowPowerEventDispatcher.hpp" -#include "preview/interfaces/QRCode.hpp" #include "preview/interfaces/ViewPainterDirectDisplay.hpp" #include "preview/interfaces/ViewRepainter.hpp" #include "preview/sdl/DirectDisplaySdl.hpp" #include "preview/sdl/LowPowerStrategySdl.hpp" #include "preview/sdl/SdlTouchInteractor.hpp" -#include "preview/views/ViewBitmap.hpp" #include int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) @@ -16,17 +14,13 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine services::LowPowerStrategySdl lowPowerStrategy(timerService); infra::LowPowerEventDispatcher::WithSize<50> eventDispatcher(lowPowerStrategy); - services::QRCode<3, services::QRCodeEcc::low> qrcode("HELLO WORLD"); - services::ViewBitmap viewBitmap(qrcode); - hal::DirectDisplaySdl display(infra::Vector(480, 272)); services::ViewPainterDirectDisplay painter(display); application::TouchViewClickingScrolling touchView; services::SdlTouchInteractor touchInteractor(lowPowerStrategy, touchView); - services::ViewRepainterPaintWhenDirty repainter(painter, viewBitmap); - viewBitmap.ResetLayout(display.Size()); + services::ViewRepainterPaintWhenDirty repainter(painter, touchView.GetView()); touchView.GetView().ResetLayout(display.Size()); eventDispatcher.Run(); diff --git a/examples/qr_code/CMakeLists.txt b/examples/qr_code/CMakeLists.txt new file mode 100644 index 0000000..7632571 --- /dev/null +++ b/examples/qr_code/CMakeLists.txt @@ -0,0 +1,29 @@ +add_executable(examples.qr_code) +emil_build_for(examples.qr_code HOST Windows TARGET_MCU stm32f746 PREREQUISITE_BOOL PREVIEW_BUILD_EXAMPLES) + +set_target_properties(examples.qr_code PROPERTIES WIN32_EXECUTABLE $) +target_compile_definitions(examples.qr_code PUBLIC NOMINMAX) + +target_include_directories(examples.qr_code PUBLIC + "$" + "$" +) + +target_sources(examples.qr_code PRIVATE + $<$:MainWin.cpp> + $<$:MainStm32f746.cpp> +) + +target_link_libraries(examples.qr_code PUBLIC + $<$:hal.generic> + $<$:preview.sdl> + $<$:hal_st.stm32fxxx> + $<$:preview.stm32fxxx> + preview.touch + preview.views +) + +if(TARGET_MCU STREQUAL stm32f746) + halst_target_default_linker_scripts(examples.qr_code) + halst_target_default_init(examples.qr_code) +endif() diff --git a/examples/qr_code/MainStm32f746.cpp b/examples/qr_code/MainStm32f746.cpp new file mode 100644 index 0000000..819a04c --- /dev/null +++ b/examples/qr_code/MainStm32f746.cpp @@ -0,0 +1,71 @@ +#include "examples/clicking_scrolling/TouchViewClickingScrolling.hpp" +#include "generated/stm32fxxx/PinoutTableDefault.hpp" +#include "hal_st/stm32fxxx/DefaultClockDiscoveryF746G.hpp" +#include "hal_st/stm32fxxx/GpioStm.hpp" +#include "hal_st/stm32fxxx/I2cStm.hpp" +#include "hal_st/stm32fxxx/SdRamStm.hpp" +#include "hal_st/stm32fxxx/SystemTickTimerService.hpp" +#include "infra/event/EventDispatcherWithWeakPtr.hpp" +#include "preview/interfaces/ViewPainterDoubleBufferDisplay.hpp" +#include "preview/interfaces/ViewRepainter.hpp" +#include "preview/stm32fxxx/BitmapPainterStm.hpp" +#include "preview/stm32fxxx/LcdStm.hpp" +#include "preview/touch/TouchFt5x06.hpp" +#include "services/util/DebugLed.hpp" + +unsigned int hse_value = 25000000; + +namespace main_ +{ + struct Lcd + { + hal::MultiGpioPinStm sdRamPins{ hal::stm32f7discoveryFmcPins }; + hal::SdRamStm sdRam{ sdRamPins, hal::stm32f7discoverySdRamConfig }; + + hal::MultiGpioPinStm lcdPins{ hal::stm32f7discoveryLcdPins }; + hal::GpioPinStm displayEnable{ hal::Port::I, 12 }; + hal::GpioPinStm backlightEnable{ hal::Port::K, 3 }; + uint32_t bufferSize{ infra::Bitmap::BufferSize(hal::stm32f7discoveryLcdConfig.width, hal::stm32f7discoveryLcdConfig.height, hal::stm32f7discoveryLcdConfig.pixelFormat) }; + infra::ByteRange lcdBuffer0{ infra::Head(sdRam.Memory(), bufferSize) }; + infra::ByteRange lcdBuffer1{ infra::Head(infra::DiscardHead(sdRam.Memory(), bufferSize), bufferSize) }; + infra::Bitmap bitmap0{ lcdBuffer0, infra::Vector(480, 272), infra::PixelFormat::rgb565 }; + infra::Bitmap bitmap1{ lcdBuffer1, infra::Vector(480, 272), infra::PixelFormat::rgb565 }; + hal::LcdStmDoubleBuffer display{ lcdPins, displayEnable, backlightEnable, lcdBuffer0, lcdBuffer1, hal::stm32f7discoveryLcdConfig }; + }; + + struct Touch + { + Touch(services::TouchRecipient& touchRecipient) + : touch(i2c, lcdInt, touchRecipient) + {} + + hal::GpioPinStm scl{ hal::Port::H, 7 }; + hal::GpioPinStm sda{ hal::Port::H, 8 }; + hal::I2cStm i2c{ 3, scl, sda }; + hal::GpioPinStm lcdInt{ hal::Port::I, 13 }; + services::TouchFt5x06OnTouchRecipient touch; + }; +} + +int main() +{ + HAL_Init(); + ConfigureDefaultClockDiscoveryF746G(); + + static hal::InterruptTable::WithStorage<128> interruptTable; + static infra::EventDispatcherWithWeakPtr::WithSize<50> eventDispatcher; + static hal::GpioStm gpio(hal::pinoutTableDefaultStm); + static hal::SystemTickTimerService systemTick; + + static application::TouchViewClickingScrolling touchView; + static main_::Touch touch(touchView); + + static main_::Lcd lcd; + static hal::BitmapPainterStm bitmapPainter; + static services::ViewPainterDoubleBufferDisplay painter(lcd.display, bitmapPainter); + static services::ViewRepainterPaintWhenDirty repainter(painter, touchView.GetView()); + touchView.GetView().ResetLayout(lcd.display.DisplaySize()); + + eventDispatcher.Run(); + __builtin_unreachable(); +} diff --git a/examples/qr_code/MainWin.cpp b/examples/qr_code/MainWin.cpp new file mode 100644 index 0000000..6f83924 --- /dev/null +++ b/examples/qr_code/MainWin.cpp @@ -0,0 +1,31 @@ +#include "examples/clicking_scrolling/TouchViewClickingScrolling.hpp" +#include "hal/generic/TimerServiceGeneric.hpp" +#include "infra/event/LowPowerEventDispatcher.hpp" +#include "preview/interfaces/QRCode.hpp" +#include "preview/interfaces/ViewPainterDirectDisplay.hpp" +#include "preview/interfaces/ViewRepainter.hpp" +#include "preview/sdl/DirectDisplaySdl.hpp" +#include "preview/sdl/LowPowerStrategySdl.hpp" +#include "preview/sdl/SdlTouchInteractor.hpp" +#include "preview/views/ViewBitmap.hpp" +#include + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + hal::TimerServiceGeneric timerService; + services::LowPowerStrategySdl lowPowerStrategy(timerService); + infra::LowPowerEventDispatcher::WithSize<50> eventDispatcher(lowPowerStrategy); + + services::QrCode<4, services::QrCodeEcc::low> qrcode("https://github.com/philips-software/amp-preview/pull/191"); + services::ViewBitmap viewBitmap(qrcode); + + hal::DirectDisplaySdl display(infra::Vector(480, 272)); + services::ViewPainterDirectDisplay painter(display); + + services::ViewRepainterPaintWhenDirty repainter(painter, viewBitmap); + viewBitmap.ResetLayout(display.Size()); + + eventDispatcher.Run(); + + return 0; +} diff --git a/preview/interfaces/CMakeLists.txt b/preview/interfaces/CMakeLists.txt index fdfc9e2..097a326 100644 --- a/preview/interfaces/CMakeLists.txt +++ b/preview/interfaces/CMakeLists.txt @@ -35,8 +35,8 @@ target_sources(preview.interfaces PRIVATE Geometry.cpp Geometry.hpp MultiBufferDisplay.hpp - QRCode.cpp - QRCode.hpp + QrCode.cpp + QrCode.hpp View.cpp View.hpp ViewOverlay.cpp diff --git a/preview/interfaces/QRCode.cpp b/preview/interfaces/QRCode.cpp index abdc6d5..6d0906a 100644 --- a/preview/interfaces/QRCode.cpp +++ b/preview/interfaces/QRCode.cpp @@ -7,7 +7,7 @@ namespace services { namespace detail { - BitBuffer::BitBuffer(infra::ByteRange data, infra::ByteRange result, infra::ByteRange coeff, uint8_t version, QRCodeEcc ecc) + TextEncoder::TextEncoder(infra::ByteRange data, infra::ByteRange result, infra::ByteRange coeff, uint8_t version, QrCodeEcc ecc) : version(version) , ecc(ecc) , moduleCount(numRawDataModulesForVersion[version]) @@ -16,7 +16,7 @@ namespace services , reedSolomon(coeff) {} - void BitBuffer::Generate(infra::BoundedConstString text) + void TextEncoder::Encode(infra::BoundedConstString text) { uint16_t dataCapacity = moduleCount / 8 - numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; @@ -35,23 +35,23 @@ namespace services PerformErrorCorrection(ecc); } - uint16_t BitBuffer::Length() const + uint16_t TextEncoder::Length() const { return bitOffset; } - bool BitBuffer::Bit(uint16_t index) const + bool TextEncoder::Bit(uint16_t index) const { return ((result[index >> 3] >> (7 - (index & 7))) & 1) != 0; } - void BitBuffer::Append(uint32_t val, uint8_t length) + void TextEncoder::Append(uint32_t val, uint8_t length) { for (int8_t i = length - 1; i >= 0; --i, ++bitOffset) data[bitOffset >> 3] |= ((val >> i) & 1) << (7 - (bitOffset & 7)); } - void BitBuffer::EncodeDataCodewords(infra::BoundedConstString text) + void TextEncoder::EncodeDataCodewords(infra::BoundedConstString text) { if (IsNumeric(text)) EncodeNumeric(text); @@ -61,7 +61,7 @@ namespace services EncodeLatin1(text); } - void BitBuffer::EncodeNumeric(infra::BoundedConstString text) + void TextEncoder::EncodeNumeric(infra::BoundedConstString text) { assert(text.size() <= MaxSizeNumeric(version, ecc)); Append(1 << numeric, 4); @@ -86,7 +86,7 @@ namespace services Append(accumData, accumCount * 3 + 1); } - void BitBuffer::EncodeAlphanumeric(infra::BoundedConstString text) + void TextEncoder::EncodeAlphanumeric(infra::BoundedConstString text) { assert(text.size() <= MaxSizeAlphanumeric(version, ecc)); Append(1 << alphanumeric, 4); @@ -110,7 +110,7 @@ namespace services Append(accumData, 6); } - void BitBuffer::EncodeLatin1(infra::BoundedConstString text) + void TextEncoder::EncodeLatin1(infra::BoundedConstString text) { assert(text.size() <= MaxSizeLatin1(version, ecc)); Append(1 << latin1, 4); @@ -119,7 +119,7 @@ namespace services Append(c, 8); } - void BitBuffer::PerformErrorCorrection(QRCodeEcc ecc) + void TextEncoder::PerformErrorCorrection(QrCodeEcc ecc) { // See: http://www.thonky.com/qr-code-tutorial/structure-final-message uint8_t numBlocks = numErrorCorrectionBlocks[static_cast(ecc)][version - 1]; @@ -177,7 +177,7 @@ namespace services bitOffset = moduleCount; } - int8_t BitBuffer::GetAlphanumeric(char c) + int8_t TextEncoder::GetAlphanumeric(char c) { if (c >= '0' && c <= '9') return (c - '0'); @@ -209,7 +209,7 @@ namespace services } } - bool BitBuffer::IsAlphanumeric(infra::BoundedConstString text) + bool TextEncoder::IsAlphanumeric(infra::BoundedConstString text) { for (auto c : text) if (GetAlphanumeric(c) == -1) @@ -218,7 +218,7 @@ namespace services return true; } - bool BitBuffer::IsNumeric(infra::BoundedConstString text) + bool TextEncoder::IsNumeric(infra::BoundedConstString text) { for (auto c : text) if (c < '0' || c > '9') @@ -227,7 +227,7 @@ namespace services return true; } - BitBuffer::ReedSolomon::ReedSolomon(infra::ByteRange coeff) + TextEncoder::ReedSolomon::ReedSolomon(infra::ByteRange coeff) : coeff(coeff) { coeff.back() = 1; @@ -249,7 +249,7 @@ namespace services } } - void BitBuffer::ReedSolomon::Remainder(uint8_t* data, uint8_t length, uint8_t* result, uint8_t stride) const + void TextEncoder::ReedSolomon::Remainder(uint8_t* data, uint8_t length, uint8_t* result, uint8_t stride) const { // Compute the remainder by performing polynomial division for (uint8_t i = 0; i != length; ++i) @@ -265,7 +265,7 @@ namespace services } } - uint8_t BitBuffer::ReedSolomon::Multiply(uint8_t x, uint8_t y) const + uint8_t TextEncoder::ReedSolomon::Multiply(uint8_t x, uint8_t y) const { // Russian peasant multiplication // See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication @@ -415,18 +415,18 @@ namespace services return PenaltyScoreRowRuns(bitmap) + PenaltyScoreColumnRuns(bitmap) + PenaltyScoreBlocks(bitmap) + PenaltyScoreFinderLike(bitmap) + PenaltyScoreBalance(bitmap); } - QRCodeGenerator::QRCodeGenerator(infra::Bitmap& modules, infra::Bitmap& isFunction, BitBuffer& codewords, infra::ByteRange alignPosition, uint8_t version, QRCodeEcc ecc) + QrCodeGenerator::QrCodeGenerator(infra::Bitmap& modules, infra::Bitmap& isFunction, TextEncoder& encoder, infra::ByteRange alignPosition, uint8_t version, QrCodeEcc ecc) : version(version) , ecc(ecc) , modules(modules) , isFunction(isFunction) - , codewords(codewords) + , encoder(encoder) , alignPosition(alignPosition) {} - void QRCodeGenerator::Generate(infra::BoundedConstString text) + void QrCodeGenerator::Generate(infra::BoundedConstString text) { - codewords.Generate(text); + encoder.Encode(text); DrawFunctionPatterns(); DrawCodewords(); @@ -436,7 +436,7 @@ namespace services ApplyMask(mask); } - uint8_t QRCodeGenerator::BestMask() + uint8_t QrCodeGenerator::BestMask() { uint8_t mask = 0; @@ -461,7 +461,7 @@ namespace services // properties, calling ApplyMask(m) twice with the same value is equivalent to no change at all. // This means it is possible to apply a mask, undo it, and try another mask. Note that a final // well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). - void QRCodeGenerator::ApplyMask(uint8_t mask) + void QrCodeGenerator::ApplyMask(uint8_t mask) { uint8_t size = modules.size.deltaX; @@ -504,7 +504,7 @@ namespace services } } - void QRCodeGenerator::DrawFunctionPatterns() + void QrCodeGenerator::DrawFunctionPatterns() { uint8_t size = modules.size.deltaX; @@ -552,7 +552,7 @@ namespace services // Draws two copies of the version bits (with its own error correction code), // based on this object's version field (which only has an effect for 7 <= version <= 40). - void QRCodeGenerator::DrawVersion() + void QrCodeGenerator::DrawVersion() { int8_t size = modules.size.deltaX; @@ -578,7 +578,7 @@ namespace services } // Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). - void QRCodeGenerator::DrawFinderPattern(infra::Point position) + void QrCodeGenerator::DrawFinderPattern(infra::Point position) { auto bitmapRegion = infra::Region(infra::Point(), modules.size); auto finderRegion = infra::Region(infra::Point(-4, -4), infra::Point(5, 5)) >> (position - infra::Point()); @@ -591,7 +591,7 @@ namespace services } // Draws a 5*5 alignment pattern, with the center module at (x, y). - void QRCodeGenerator::DrawAlignmentPattern(infra::Point position) + void QrCodeGenerator::DrawAlignmentPattern(infra::Point position) { auto alignmentRegion = infra::Region(infra::Point(-2, -2), infra::Point(3, 3)) >> (position - infra::Point()); @@ -601,7 +601,7 @@ namespace services // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire // data area of this QR Code symbol. Function modules need to be marked off before this is called. - void QRCodeGenerator::DrawCodewords() + void QrCodeGenerator::DrawCodewords() { uint8_t size = modules.size.deltaX; @@ -624,10 +624,10 @@ namespace services auto position = infra::Point(x, y); if (!isFunction.BlackAndWhitePixel(position)) { - modules.SetBlackAndWhitePixel(position, codewords.Bit(i)); + modules.SetBlackAndWhitePixel(position, encoder.Bit(i)); ++i; - if (i == codewords.Length()) + if (i == encoder.Length()) return; } // If there are any remainder bits (0 to 7), they are already @@ -639,7 +639,7 @@ namespace services // Draws two copies of the format bits (with its own error correction code) // based on the given mask and this object's error correction level field. - void QRCodeGenerator::DrawFormatBits(uint8_t mask) + void QrCodeGenerator::DrawFormatBits(uint8_t mask) { static constexpr std::array eccFormatBits{ 0x01, 0x00, 0x03, 0x02 @@ -677,7 +677,7 @@ namespace services SetFunctionModule(infra::Point(8, size - 8), true); } - void QRCodeGenerator::SetFunctionModule(infra::Point position, bool on) + void QrCodeGenerator::SetFunctionModule(infra::Point position, bool on) { modules.SetBlackAndWhitePixel(position, on); isFunction.SetBlackAndWhitePixel(position, true); diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QRCode.hpp index 02267bd..600644a 100644 --- a/preview/interfaces/QRCode.hpp +++ b/preview/interfaces/QRCode.hpp @@ -9,7 +9,7 @@ namespace services { - enum class QRCodeEcc : uint8_t + enum class QrCodeEcc : uint8_t { low, medium, @@ -17,11 +17,11 @@ namespace services high }; - template - struct QRCode + template + struct QrCode : infra::Bitmap::BlackAndWhite { - QRCode(infra::BoundedConstString text); + QrCode(infra::BoundedConstString text); void Update(infra::BoundedConstString text); @@ -34,7 +34,7 @@ namespace services namespace detail { - class BitBuffer + class TextEncoder { private: static constexpr std::array numRawDataModulesForVersion{ @@ -73,14 +73,14 @@ namespace services return (bits + 7) / 8; } - static constexpr uint8_t BlockEccLen(uint8_t version, QRCodeEcc ecc) + static constexpr uint8_t BlockEccLen(uint8_t version, QrCodeEcc ecc) { uint8_t numBlocks = numErrorCorrectionBlocks[static_cast(ecc)][version - 1]; uint16_t totalEcc = numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; return totalEcc / numBlocks; } - static constexpr uint8_t BitBuffer::ModeBitsNumeric(uint8_t version) + static constexpr uint8_t ModeBitsNumeric(uint8_t version) { if (version <= 9) return 10; @@ -89,7 +89,7 @@ namespace services return 14; } - static constexpr uint8_t BitBuffer::ModeBitsAlphanumeric(uint8_t version) + static constexpr uint8_t ModeBitsAlphanumeric(uint8_t version) { if (version <= 9) return 9; @@ -98,7 +98,7 @@ namespace services return 13; } - static constexpr uint8_t BitBuffer::ModeBitsLatin1(uint8_t version) + static constexpr uint8_t ModeBitsLatin1(uint8_t version) { if (version <= 9) return 8; @@ -107,7 +107,7 @@ namespace services return 16; } - static constexpr std::size_t MaxSizeNumeric(uint8_t version, QRCodeEcc ecc) + static constexpr std::size_t MaxSizeNumeric(uint8_t version, QrCodeEcc ecc) { uint16_t moduleCount = numRawDataModulesForVersion[version]; uint16_t dataCapacityInBytes = moduleCount / 8 - numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; @@ -120,7 +120,7 @@ namespace services : 0); } - static constexpr std::size_t MaxSizeAlphanumeric(uint8_t version, QRCodeEcc ecc) + static constexpr std::size_t MaxSizeAlphanumeric(uint8_t version, QrCodeEcc ecc) { uint16_t moduleCount = numRawDataModulesForVersion[version]; uint16_t dataCapacityInBytes = moduleCount / 8 - numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; @@ -132,7 +132,7 @@ namespace services return blocksOf2 * 2 + digitBits / 6; } - static constexpr std::size_t MaxSizeLatin1(uint8_t version, QRCodeEcc ecc) + static constexpr std::size_t MaxSizeLatin1(uint8_t version, QrCodeEcc ecc) { uint16_t moduleCount = numRawDataModulesForVersion[version]; uint16_t dataCapacityInBytes = moduleCount / 8 - numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; @@ -142,12 +142,12 @@ namespace services } public: - template + template struct ForVersionAndEcc; - BitBuffer(infra::ByteRange data, infra::ByteRange result, infra::ByteRange coeff, uint8_t version, QRCodeEcc ecc); + TextEncoder(infra::ByteRange data, infra::ByteRange result, infra::ByteRange coeff, uint8_t version, QrCodeEcc ecc); - void Generate(infra::BoundedConstString text); + void Encode(infra::BoundedConstString text); uint16_t Length() const; bool Bit(uint16_t index) const; @@ -163,7 +163,7 @@ namespace services void EncodeNumeric(infra::BoundedConstString text); void EncodeAlphanumeric(infra::BoundedConstString text); void EncodeLatin1(infra::BoundedConstString text); - void PerformErrorCorrection(QRCodeEcc ecc); + void PerformErrorCorrection(QrCodeEcc ecc); static int8_t GetAlphanumeric(char c); static bool IsAlphanumeric(infra::BoundedConstString text); @@ -186,7 +186,7 @@ namespace services private: uint8_t version; - QRCodeEcc ecc; + QrCodeEcc ecc; uint16_t moduleCount; uint16_t bitOffset = 0; infra::ByteRange data; @@ -194,8 +194,8 @@ namespace services ReedSolomon reedSolomon; }; - template - struct BitBuffer::ForVersionAndEcc + template + struct TextEncoder::ForVersionAndEcc { ForVersionAndEcc(); @@ -204,16 +204,16 @@ namespace services std::array result{}; std::array coeff{}; - BitBuffer buffer; + TextEncoder buffer; }; - class QRCodeGenerator + class QrCodeGenerator { public: - template + template struct ForVersionAndEcc; - QRCodeGenerator(infra::Bitmap& modules, infra::Bitmap& isFunction, BitBuffer& codewords, infra::ByteRange alignPosition, uint8_t version, QRCodeEcc ecc); + QrCodeGenerator(infra::Bitmap& modules, infra::Bitmap& isFunction, TextEncoder& encoder, infra::ByteRange alignPosition, uint8_t version, QrCodeEcc ecc); void Generate(infra::BoundedConstString text); @@ -231,24 +231,24 @@ namespace services private: uint8_t version; - QRCodeEcc ecc; + QrCodeEcc ecc; infra::Bitmap& modules; infra::Bitmap& isFunction; - BitBuffer& codewords; + TextEncoder& encoder; infra::ByteRange alignPosition; }; - template - struct QRCodeGenerator::ForVersionAndEcc - : QRCodeGenerator + template + struct QrCodeGenerator::ForVersionAndEcc + : QrCodeGenerator { ForVersionAndEcc(infra::Bitmap& modules); - typename QRCode::BitmapType isFunction; - BitBuffer::ForVersionAndEcc codewords; + typename QrCode::BitmapType isFunction; + TextEncoder::ForVersionAndEcc encoder; std::array alignPosition; }; } @@ -257,49 +257,49 @@ namespace services namespace detail { - template - BitBuffer::ForVersionAndEcc::ForVersionAndEcc() + template + TextEncoder::ForVersionAndEcc::ForVersionAndEcc() : buffer(data, result, coeff, Version, Ecc) {} - template - QRCodeGenerator::ForVersionAndEcc::ForVersionAndEcc(infra::Bitmap& modules) - : QRCodeGenerator(modules, isFunction, codewords.buffer, alignPosition, Version, Ecc) + template + QrCodeGenerator::ForVersionAndEcc::ForVersionAndEcc(infra::Bitmap& modules) + : QrCodeGenerator(modules, isFunction, encoder.buffer, alignPosition, Version, Ecc) { isFunction.Clear(); } } - template - QRCode::QRCode(infra::BoundedConstString text) + template + QrCode::QrCode(infra::BoundedConstString text) { Update(text); } - template - void QRCode::Update(infra::BoundedConstString text) + template + void QrCode::Update(infra::BoundedConstString text) { Clear(); - detail::QRCodeGenerator::ForVersionAndEcc generator(*this); + detail::QrCodeGenerator::ForVersionAndEcc generator(*this); generator.Generate(text); } - template - constexpr std::size_t QRCode::MaxSizeNumeric() + template + constexpr std::size_t QrCode::MaxSizeNumeric() { - return BitBuffer::MaxSizeNumeric(Version, Ecc); + return TextEncoder::MaxSizeNumeric(Version, Ecc); } - template - constexpr std::size_t QRCode::MaxSizeAlphanumeric() + template + constexpr std::size_t QrCode::MaxSizeAlphanumeric() { - return BitBuffer::MaxSizeAlphanumeric(Version, Ecc); + return TextEncoder::MaxSizeAlphanumeric(Version, Ecc); } - template - constexpr std::size_t QRCode::MaxSizeLatin1() + template + constexpr std::size_t QrCode::MaxSizeLatin1() { - return BitBuffer::MaxSizeLatin1(Version, Ecc); + return TextEncoder::MaxSizeLatin1(Version, Ecc); } } From c299db66760c6e8a781b7eb787f428835649ec92 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Thu, 26 Sep 2024 09:50:28 +0200 Subject: [PATCH 14/22] Update examples/qr_code/MainStm32f746 --- examples/qr_code/MainStm32f746.cpp | 28 ++++++++-------------------- examples/qr_code/MainWin.cpp | 1 - 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/examples/qr_code/MainStm32f746.cpp b/examples/qr_code/MainStm32f746.cpp index 819a04c..70e7bca 100644 --- a/examples/qr_code/MainStm32f746.cpp +++ b/examples/qr_code/MainStm32f746.cpp @@ -1,4 +1,3 @@ -#include "examples/clicking_scrolling/TouchViewClickingScrolling.hpp" #include "generated/stm32fxxx/PinoutTableDefault.hpp" #include "hal_st/stm32fxxx/DefaultClockDiscoveryF746G.hpp" #include "hal_st/stm32fxxx/GpioStm.hpp" @@ -6,11 +5,12 @@ #include "hal_st/stm32fxxx/SdRamStm.hpp" #include "hal_st/stm32fxxx/SystemTickTimerService.hpp" #include "infra/event/EventDispatcherWithWeakPtr.hpp" +#include "preview/interfaces/QRCode.hpp" #include "preview/interfaces/ViewPainterDoubleBufferDisplay.hpp" #include "preview/interfaces/ViewRepainter.hpp" #include "preview/stm32fxxx/BitmapPainterStm.hpp" #include "preview/stm32fxxx/LcdStm.hpp" -#include "preview/touch/TouchFt5x06.hpp" +#include "preview/views/ViewBitmap.hpp" #include "services/util/DebugLed.hpp" unsigned int hse_value = 25000000; @@ -32,19 +32,6 @@ namespace main_ infra::Bitmap bitmap1{ lcdBuffer1, infra::Vector(480, 272), infra::PixelFormat::rgb565 }; hal::LcdStmDoubleBuffer display{ lcdPins, displayEnable, backlightEnable, lcdBuffer0, lcdBuffer1, hal::stm32f7discoveryLcdConfig }; }; - - struct Touch - { - Touch(services::TouchRecipient& touchRecipient) - : touch(i2c, lcdInt, touchRecipient) - {} - - hal::GpioPinStm scl{ hal::Port::H, 7 }; - hal::GpioPinStm sda{ hal::Port::H, 8 }; - hal::I2cStm i2c{ 3, scl, sda }; - hal::GpioPinStm lcdInt{ hal::Port::I, 13 }; - services::TouchFt5x06OnTouchRecipient touch; - }; } int main() @@ -57,14 +44,15 @@ int main() static hal::GpioStm gpio(hal::pinoutTableDefaultStm); static hal::SystemTickTimerService systemTick; - static application::TouchViewClickingScrolling touchView; - static main_::Touch touch(touchView); - static main_::Lcd lcd; static hal::BitmapPainterStm bitmapPainter; static services::ViewPainterDoubleBufferDisplay painter(lcd.display, bitmapPainter); - static services::ViewRepainterPaintWhenDirty repainter(painter, touchView.GetView()); - touchView.GetView().ResetLayout(lcd.display.DisplaySize()); + + services::QrCode<3, services::QrCodeEcc::low> qrcode("https://github.com/philips-software/amp-preview"); + services::ViewBitmap viewBitmap(qrcode); + + static services::ViewRepainterPaintWhenDirty repainter(painter, viewBitmap); + viewBitmap.ResetLayout(lcd.display.DisplaySize()); eventDispatcher.Run(); __builtin_unreachable(); diff --git a/examples/qr_code/MainWin.cpp b/examples/qr_code/MainWin.cpp index 6f83924..9e96453 100644 --- a/examples/qr_code/MainWin.cpp +++ b/examples/qr_code/MainWin.cpp @@ -1,4 +1,3 @@ -#include "examples/clicking_scrolling/TouchViewClickingScrolling.hpp" #include "hal/generic/TimerServiceGeneric.hpp" #include "infra/event/LowPowerEventDispatcher.hpp" #include "preview/interfaces/QRCode.hpp" From 78049e6fbe1dd67d6e4492ebc06f3ba44863bfb2 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Thu, 26 Sep 2024 09:58:13 +0200 Subject: [PATCH 15/22] Change casing of QrCode --- preview/interfaces/{QRCode.cpp => QrCode.cpp} | 0 preview/interfaces/{QRCode.hpp => QrCode.hpp} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename preview/interfaces/{QRCode.cpp => QrCode.cpp} (100%) rename preview/interfaces/{QRCode.hpp => QrCode.hpp} (100%) diff --git a/preview/interfaces/QRCode.cpp b/preview/interfaces/QrCode.cpp similarity index 100% rename from preview/interfaces/QRCode.cpp rename to preview/interfaces/QrCode.cpp diff --git a/preview/interfaces/QRCode.hpp b/preview/interfaces/QrCode.hpp similarity index 100% rename from preview/interfaces/QRCode.hpp rename to preview/interfaces/QrCode.hpp From 1b0e9be0b7dfef70bb60ae2bd81004003dc19bcf Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Thu, 26 Sep 2024 10:15:15 +0200 Subject: [PATCH 16/22] Small fixes --- examples/qr_code/CMakeLists.txt | 6 +++++- preview/interfaces/QrCode.cpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/qr_code/CMakeLists.txt b/examples/qr_code/CMakeLists.txt index 7632571..a8d7e7e 100644 --- a/examples/qr_code/CMakeLists.txt +++ b/examples/qr_code/CMakeLists.txt @@ -1,4 +1,8 @@ -add_executable(examples.qr_code) +if (EMIL_BUILD_WIN OR TARGET_MCU STREQUAL stm32f746) + add_executable(examples.qr_code) +else() + add_library(examples.qr_code INTERFACE) # Other platforms have no sources +endif() emil_build_for(examples.qr_code HOST Windows TARGET_MCU stm32f746 PREREQUISITE_BOOL PREVIEW_BUILD_EXAMPLES) set_target_properties(examples.qr_code PROPERTIES WIN32_EXECUTABLE $) diff --git a/preview/interfaces/QrCode.cpp b/preview/interfaces/QrCode.cpp index 6d0906a..b29a4cb 100644 --- a/preview/interfaces/QrCode.cpp +++ b/preview/interfaces/QrCode.cpp @@ -397,7 +397,7 @@ namespace services uint16_t numDark = 0; for (uint8_t y = 0; y != bitmap.size.deltaY; ++y) for (uint8_t x = 0; x != bitmap.size.deltaX; ++x) - if (bitmap.BlackAndWhitePixel(infra::Point(x, y))); + if (bitmap.BlackAndWhitePixel(infra::Point(x, y))) ++numDark; // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% From 60fff601645eef0d08f7a27fc7039bfd783b1701 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Thu, 26 Sep 2024 10:17:14 +0200 Subject: [PATCH 17/22] Small fixes --- examples/qr_code/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/qr_code/CMakeLists.txt b/examples/qr_code/CMakeLists.txt index a8d7e7e..661bf46 100644 --- a/examples/qr_code/CMakeLists.txt +++ b/examples/qr_code/CMakeLists.txt @@ -6,7 +6,9 @@ endif() emil_build_for(examples.qr_code HOST Windows TARGET_MCU stm32f746 PREREQUISITE_BOOL PREVIEW_BUILD_EXAMPLES) set_target_properties(examples.qr_code PROPERTIES WIN32_EXECUTABLE $) -target_compile_definitions(examples.qr_code PUBLIC NOMINMAX) +if (EMIL_BUILD_WIN) + target_compile_definitions(examples.qr_code PUBLIC NOMINMAX) +endif() target_include_directories(examples.qr_code PUBLIC "$" From dd63a5903c453c1e0d759a23aed25a5eca50b502 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Thu, 26 Sep 2024 10:21:11 +0200 Subject: [PATCH 18/22] Small fixes --- examples/qr_code/CMakeLists.txt | 52 ++++++++++++++++----------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/examples/qr_code/CMakeLists.txt b/examples/qr_code/CMakeLists.txt index 661bf46..a55fdd3 100644 --- a/examples/qr_code/CMakeLists.txt +++ b/examples/qr_code/CMakeLists.txt @@ -1,35 +1,33 @@ if (EMIL_BUILD_WIN OR TARGET_MCU STREQUAL stm32f746) add_executable(examples.qr_code) -else() - add_library(examples.qr_code INTERFACE) # Other platforms have no sources -endif() -emil_build_for(examples.qr_code HOST Windows TARGET_MCU stm32f746 PREREQUISITE_BOOL PREVIEW_BUILD_EXAMPLES) + emil_build_for(examples.qr_code HOST Windows TARGET_MCU stm32f746 PREREQUISITE_BOOL PREVIEW_BUILD_EXAMPLES) -set_target_properties(examples.qr_code PROPERTIES WIN32_EXECUTABLE $) -if (EMIL_BUILD_WIN) - target_compile_definitions(examples.qr_code PUBLIC NOMINMAX) -endif() + set_target_properties(examples.qr_code PROPERTIES WIN32_EXECUTABLE $) + if (EMIL_BUILD_WIN) + target_compile_definitions(examples.qr_code PUBLIC NOMINMAX) + endif() -target_include_directories(examples.qr_code PUBLIC - "$" - "$" -) + target_include_directories(examples.qr_code PUBLIC + "$" + "$" + ) -target_sources(examples.qr_code PRIVATE - $<$:MainWin.cpp> - $<$:MainStm32f746.cpp> -) + target_sources(examples.qr_code PRIVATE + $<$:MainWin.cpp> + $<$:MainStm32f746.cpp> + ) -target_link_libraries(examples.qr_code PUBLIC - $<$:hal.generic> - $<$:preview.sdl> - $<$:hal_st.stm32fxxx> - $<$:preview.stm32fxxx> - preview.touch - preview.views -) + target_link_libraries(examples.qr_code PUBLIC + $<$:hal.generic> + $<$:preview.sdl> + $<$:hal_st.stm32fxxx> + $<$:preview.stm32fxxx> + preview.touch + preview.views + ) -if(TARGET_MCU STREQUAL stm32f746) - halst_target_default_linker_scripts(examples.qr_code) - halst_target_default_init(examples.qr_code) + if(TARGET_MCU STREQUAL stm32f746) + halst_target_default_linker_scripts(examples.qr_code) + halst_target_default_init(examples.qr_code) + endif() endif() From 45d4b551a478d206692849e2768a35bb58ec8187 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Thu, 26 Sep 2024 10:29:00 +0200 Subject: [PATCH 19/22] Fix casing of QrCode --- NOTICE | 2 +- examples/qr_code/MainStm32f746.cpp | 2 +- examples/qr_code/MainWin.cpp | 2 +- preview/interfaces/QrCode.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/NOTICE b/NOTICE index 167d795..d6b6a9f 100644 --- a/NOTICE +++ b/NOTICE @@ -25,7 +25,7 @@ Notice file for preview/fonts/FreeSans*.cpp ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -Notice file for preview/interfaces/QRCode.cpp/hpp +Notice file for preview/interfaces/QrCode.cpp/hpp The MIT License (MIT) diff --git a/examples/qr_code/MainStm32f746.cpp b/examples/qr_code/MainStm32f746.cpp index 70e7bca..8602992 100644 --- a/examples/qr_code/MainStm32f746.cpp +++ b/examples/qr_code/MainStm32f746.cpp @@ -5,7 +5,7 @@ #include "hal_st/stm32fxxx/SdRamStm.hpp" #include "hal_st/stm32fxxx/SystemTickTimerService.hpp" #include "infra/event/EventDispatcherWithWeakPtr.hpp" -#include "preview/interfaces/QRCode.hpp" +#include "preview/interfaces/QrCode.hpp" #include "preview/interfaces/ViewPainterDoubleBufferDisplay.hpp" #include "preview/interfaces/ViewRepainter.hpp" #include "preview/stm32fxxx/BitmapPainterStm.hpp" diff --git a/examples/qr_code/MainWin.cpp b/examples/qr_code/MainWin.cpp index 9e96453..9b38ccb 100644 --- a/examples/qr_code/MainWin.cpp +++ b/examples/qr_code/MainWin.cpp @@ -1,6 +1,6 @@ #include "hal/generic/TimerServiceGeneric.hpp" #include "infra/event/LowPowerEventDispatcher.hpp" -#include "preview/interfaces/QRCode.hpp" +#include "preview/interfaces/QrCode.hpp" #include "preview/interfaces/ViewPainterDirectDisplay.hpp" #include "preview/interfaces/ViewRepainter.hpp" #include "preview/sdl/DirectDisplaySdl.hpp" diff --git a/preview/interfaces/QrCode.cpp b/preview/interfaces/QrCode.cpp index b29a4cb..121f917 100644 --- a/preview/interfaces/QrCode.cpp +++ b/preview/interfaces/QrCode.cpp @@ -1,4 +1,4 @@ -#include "preview/interfaces/QRCode.hpp" +#include "preview/interfaces/QrCode.hpp" #include #include #include From 71d3ce96d2c3f1c048ae6c22de01abbe80f5e92e Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Thu, 26 Sep 2024 10:33:23 +0200 Subject: [PATCH 20/22] namespace fix --- preview/interfaces/QrCode.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/preview/interfaces/QrCode.hpp b/preview/interfaces/QrCode.hpp index 600644a..1684860 100644 --- a/preview/interfaces/QrCode.hpp +++ b/preview/interfaces/QrCode.hpp @@ -287,19 +287,19 @@ namespace services template constexpr std::size_t QrCode::MaxSizeNumeric() { - return TextEncoder::MaxSizeNumeric(Version, Ecc); + return detail::TextEncoder::MaxSizeNumeric(Version, Ecc); } template constexpr std::size_t QrCode::MaxSizeAlphanumeric() { - return TextEncoder::MaxSizeAlphanumeric(Version, Ecc); + return detail::TextEncoder::MaxSizeAlphanumeric(Version, Ecc); } template constexpr std::size_t QrCode::MaxSizeLatin1() { - return TextEncoder::MaxSizeLatin1(Version, Ecc); + return detail::TextEncoder::MaxSizeLatin1(Version, Ecc); } } From de79119768618cfb208262ae53ae6d67ad111a78 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Thu, 26 Sep 2024 10:43:59 +0200 Subject: [PATCH 21/22] Fix argument-dependent lookup issue --- preview/interfaces/QrCode.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/preview/interfaces/QrCode.hpp b/preview/interfaces/QrCode.hpp index 1684860..a12c343 100644 --- a/preview/interfaces/QrCode.hpp +++ b/preview/interfaces/QrCode.hpp @@ -279,7 +279,7 @@ namespace services template void QrCode::Update(infra::BoundedConstString text) { - Clear(); + this->Clear(); detail::QrCodeGenerator::ForVersionAndEcc generator(*this); generator.Generate(text); } From f2eaa10505dc2608646746dcb6d2563b13238b56 Mon Sep 17 00:00:00 2001 From: Richard Peters Date: Thu, 26 Sep 2024 10:59:04 +0200 Subject: [PATCH 22/22] Fix sonar issues --- preview/interfaces/QrCode.cpp | 2 +- preview/interfaces/QrCode.hpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/preview/interfaces/QrCode.cpp b/preview/interfaces/QrCode.cpp index 121f917..88bd7e6 100644 --- a/preview/interfaces/QrCode.cpp +++ b/preview/interfaces/QrCode.cpp @@ -236,7 +236,7 @@ namespace services // drop the highest term, and store the rest of the coefficients in order of descending powers. // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). uint16_t root = 1; - for (uint8_t i = 0; i < coeff.size(); i++) + for (uint8_t i = 0; i != coeff.size(); ++i) { // Multiply the current product by (x - r^i) for (uint8_t j = 0; j != coeff.size(); ++j) diff --git a/preview/interfaces/QrCode.hpp b/preview/interfaces/QrCode.hpp index a12c343..31edfe2 100644 --- a/preview/interfaces/QrCode.hpp +++ b/preview/interfaces/QrCode.hpp @@ -107,6 +107,7 @@ namespace services return 16; } + public: static constexpr std::size_t MaxSizeNumeric(uint8_t version, QrCodeEcc ecc) { uint16_t moduleCount = numRawDataModulesForVersion[version];