2022-01-02 05:56:55 -05:00
/**
* 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 "qrcodegen.hpp"
#include <stdlib.h>
#include <string.h>
2022-04-18 22:32:43 +02:00
/* #pragma mark - Error Correction Lookup tables */
2022-01-02 05:56:55 -05:00
#if LOCK_VERSION == 0
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
2023-05-19 08:16:05 +12:00
{ 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
2022-01-02 05:56:55 -05:00
};
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
2023-05-19 08:16:05 +12:00
{ 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
2022-01-02 05:56:55 -05:00
};
static const uint16_t NUM_RAW_DATA_MODULES [ 40 ] = {
2023-05-19 08:16:05 +12:00
// 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 };
2022-01-02 05:56:55 -05:00
// @TODO: Put other LOCK_VERSIONS here
#elif LOCK_VERSION == 3
static const int16_t NUM_ERROR_CORRECTION_CODEWORDS [ 4 ] = {
2023-05-19 08:16:05 +12:00
26 , 15 , 44 , 36 };
2022-01-02 05:56:55 -05:00
static const int8_t NUM_ERROR_CORRECTION_BLOCKS [ 4 ] = {
2023-05-19 08:16:05 +12:00
1 , 1 , 2 , 2 };
2022-01-02 05:56:55 -05:00
static const uint16_t NUM_RAW_DATA_MODULES = 567 ;
#else
#error Unsupported LOCK_VERSION (add it...)
#endif
static int max ( int a , int b ) {
2023-05-19 08:16:05 +12:00
if ( a > b ) {
return a ;
}
2022-01-02 05:56:55 -05:00
return b ;
}
/*
static int abs(int value) {
if (value < 0) { return -value; }
return value;
}
*/
2022-04-18 22:32:43 +02:00
/* #pragma mark - Mode testing and conversion */
2022-01-02 05:56:55 -05:00
static int8_t getAlphanumeric ( char c ) {
2023-05-19 08:16:05 +12:00
if ( c >= '0' && c <= '9' ) {
return ( c - '0' );
2022-01-02 05:56:55 -05:00
}
2023-05-19 08:16:05 +12:00
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 ;
}
2022-01-02 05:56:55 -05:00
return - 1 ;
}
2023-05-19 08:16:05 +12:00
static bool isAlphanumeric ( const char * text , uint16_t length ) {
2022-01-02 05:56:55 -05:00
while ( length != 0 ) {
2023-05-19 08:16:05 +12:00
if ( getAlphanumeric ( text [ -- length ]) == - 1 ) {
return false ;
}
2022-01-02 05:56:55 -05:00
}
return true ;
}
2023-05-19 08:16:05 +12:00
static bool isNumeric ( const char * text , uint16_t length ) {
2022-01-02 05:56:55 -05:00
while ( length != 0 ) {
char c = text [ -- length ];
2023-05-19 08:16:05 +12:00
if ( c < '0' || c > '9' ) {
return false ;
}
2022-01-02 05:56:55 -05:00
}
return true ;
}
2022-04-18 22:32:43 +02:00
/* #pragma mark - Counting */
2022-01-02 05:56:55 -05:00
// 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 ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
#if LOCK_VERSION == 0 || LOCK_VERSION > 9
2023-05-19 08:16:05 +12:00
if ( version > 9 ) {
modeInfo >>= 9 ;
}
2022-01-02 05:56:55 -05:00
#endif
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
#if LOCK_VERSION == 0 || LOCK_VERSION > 26
2023-05-19 08:16:05 +12:00
if ( version > 26 ) {
modeInfo >>= 9 ;
}
2022-01-02 05:56:55 -05:00
#endif
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
char result = 8 + (( modeInfo >> ( 3 * mode )) & 0x07 );
2023-05-19 08:16:05 +12:00
if ( result == 15 ) {
result = 16 ;
}
2022-01-02 05:56:55 -05:00
return result ;
}
2022-04-18 22:32:43 +02:00
/* #pragma mark - BitBucket */
2022-01-02 05:56:55 -05:00
typedef struct BitBucket {
uint32_t bitOffsetOrWidth ;
uint16_t capacityBytes ;
2023-05-19 08:16:05 +12:00
uint8_t * data ;
2022-01-02 05:56:55 -05:00
} BitBucket ;
/*
void bb_dump(BitBucket *bitBuffer) {
printf("Buffer: ");
for (uint32_t i = 0; i < bitBuffer->capacityBytes; i++) {
printf("%02x", bitBuffer->data[i]);
if ((i % 4) == 3) { printf(" "); }
}
printf("\n");
}
*/
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 );
}
2023-05-19 08:16:05 +12:00
static void bb_initBuffer ( BitBucket * bitBuffer , uint8_t * data , int32_t capacityBytes ) {
2022-01-02 05:56:55 -05:00
bitBuffer -> bitOffsetOrWidth = 0 ;
bitBuffer -> capacityBytes = capacityBytes ;
bitBuffer -> data = data ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
memset ( data , 0 , bitBuffer -> capacityBytes );
}
2023-05-19 08:16:05 +12:00
static void bb_initGrid ( BitBucket * bitGrid , uint8_t * data , uint8_t size ) {
2022-01-02 05:56:55 -05:00
bitGrid -> bitOffsetOrWidth = size ;
bitGrid -> capacityBytes = bb_getGridSizeBytes ( size );
bitGrid -> data = data ;
memset ( data , 0 , bitGrid -> capacityBytes );
}
2023-05-19 08:16:05 +12:00
static void bb_appendBits ( BitBucket * bitBuffer , uint32_t val , uint8_t length ) {
2022-01-02 05:56:55 -05:00
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 ;
}
/*
void bb_setBits(BitBucket *bitBuffer, uint32_t val, int offset, uint8_t length) {
for (int8_t i = length - 1; i >= 0; i--, offset++) {
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
}
}
*/
2023-05-19 08:16:05 +12:00
static void bb_setBit ( BitBucket * bitGrid , uint8_t x , uint8_t y , bool on ) {
2022-01-02 05:56:55 -05:00
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 ;
}
}
2023-05-19 08:16:05 +12:00
static void bb_invertBit ( BitBucket * bitGrid , uint8_t x , uint8_t y , bool invert ) {
2022-01-02 05:56:55 -05:00
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 ;
}
}
2023-05-19 08:16:05 +12:00
static bool bb_getBit ( BitBucket * bitGrid , uint8_t x , uint8_t y ) {
2022-01-02 05:56:55 -05:00
uint32_t offset = y * bitGrid -> bitOffsetOrWidth + x ;
return ( bitGrid -> data [ offset >> 3 ] & ( 1 << ( 7 - ( offset & 0x07 )))) != 0 ;
}
2022-04-18 22:32:43 +02:00
/* #pragma mark - Drawing Patterns */
2022-01-02 05:56:55 -05:00
// 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.).
2023-05-19 08:16:05 +12:00
static void applyMask ( BitBucket * modules , BitBucket * isFunction , uint8_t mask ) {
2022-01-02 05:56:55 -05:00
uint8_t size = modules -> bitOffsetOrWidth ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
for ( uint8_t y = 0 ; y < size ; y ++ ) {
for ( uint8_t x = 0 ; x < size ; x ++ ) {
2023-05-19 08:16:05 +12:00
if ( bb_getBit ( isFunction , x , y )) {
continue ;
}
2022-01-02 05:56:55 -05:00
bool invert = 0 ;
switch ( mask ) {
2023-05-19 08:16:05 +12:00
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 ;
2022-01-02 05:56:55 -05:00
}
bb_invertBit ( modules , x , y , invert );
}
}
}
2023-05-19 08:16:05 +12:00
static void setFunctionModule ( BitBucket * modules , BitBucket * isFunction , uint8_t x , uint8_t y , bool on ) {
2022-01-02 05:56:55 -05:00
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).
2023-05-19 08:16:05 +12:00
static void drawFinderPattern ( BitBucket * modules , BitBucket * isFunction , uint8_t x , uint8_t y ) {
2022-01-02 05:56:55 -05:00
uint8_t size = modules -> bitOffsetOrWidth ;
for ( int8_t i = - 4 ; i <= 4 ; i ++ ) {
for ( int8_t j = - 4 ; j <= 4 ; j ++ ) {
uint8_t dist = max ( abs ( i ), 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).
2023-05-19 08:16:05 +12:00
static void drawAlignmentPattern ( BitBucket * modules , BitBucket * isFunction , uint8_t x , uint8_t y ) {
2022-01-02 05:56:55 -05:00
for ( int8_t i = - 2 ; i <= 2 ; i ++ ) {
for ( int8_t j = - 2 ; j <= 2 ; j ++ ) {
setFunctionModule ( modules , isFunction , x + j , y + i , max ( abs ( i ), 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.
2023-05-19 08:16:05 +12:00
static void drawFormatBits ( BitBucket * modules , BitBucket * isFunction , uint8_t ecc , uint8_t mask ) {
2022-01-02 05:56:55 -05:00
uint8_t size = modules -> bitOffsetOrWidth ;
// Calculate error correction code and pack bits
uint32_t data = ecc << 3 | mask ; // errCorrLvl is uint2, mask is uint3
uint32_t rem = data ;
for ( int i = 0 ; i < 10 ; i ++ ) {
rem = ( rem << 1 ) ^ (( rem >> 9 ) * 0x537 );
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
data = data << 10 | rem ;
data ^= 0x5412 ; // uint15
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// Draw first copy
for ( uint8_t i = 0 ; i <= 5 ; i ++ ) {
setFunctionModule ( modules , isFunction , 8 , i , (( data >> i ) & 1 ) != 0 );
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
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 );
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
for ( int8_t i = 9 ; i < 15 ; i ++ ) {
setFunctionModule ( modules , isFunction , 14 - i , 8 , (( data >> i ) & 1 ) != 0 );
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// Draw second copy
for ( int8_t i = 0 ; i <= 7 ; i ++ ) {
setFunctionModule ( modules , isFunction , size - 1 - i , 8 , (( data >> i ) & 1 ) != 0 );
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
for ( int8_t i = 8 ; i < 15 ; i ++ ) {
setFunctionModule ( modules , isFunction , 8 , size - 15 + i , (( data >> i ) & 1 ) != 0 );
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
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).
2023-05-19 08:16:05 +12:00
static void drawVersion ( BitBucket * modules , BitBucket * isFunction , uint8_t version ) {
2022-01-02 05:56:55 -05:00
int8_t size = modules -> bitOffsetOrWidth ;
#if LOCK_VERSION != 0 && LOCK_VERSION < 7
return ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
#else
2023-05-19 08:16:05 +12:00
if ( version < 7 ) {
return ;
}
2022-01-02 05:56:55 -05:00
// 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 );
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
uint32_t data = version << 12 | rem ; // uint18
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// 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 );
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
#endif
}
2023-05-19 08:16:05 +12:00
static void drawFunctionPatterns ( BitBucket * modules , BitBucket * isFunction , uint8_t version , uint8_t ecc ) {
2022-01-02 05:56:55 -05:00
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 );
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// 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 );
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
#if LOCK_VERSION == 0 || LOCK_VERSION > 1
if ( version > 1 ) {
// Draw the numerous alignment patterns
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
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
2023-05-19 08:16:05 +12:00
} else { // C-C-C-Combo breaker!
2022-01-02 05:56:55 -05:00
step = 26 ;
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
uint8_t alignPositionIndex = alignCount - 1 ;
uint8_t alignPosition [ alignCount ];
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
alignPosition [ 0 ] = 6 ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
uint8_t size = version * 4 + 17 ;
for ( uint8_t i = 0 , pos = size - 7 ; i < alignCount - 1 ; i ++ , pos -= step ) {
alignPosition [ alignPositionIndex -- ] = pos ;
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
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 ]);
}
}
}
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
#endif
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// 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.
2023-05-19 08:16:05 +12:00
static void drawCodewords ( BitBucket * modules , BitBucket * isFunction , BitBucket * codewords ) {
2022-01-02 05:56:55 -05:00
uint32_t bitLength = codewords -> bitOffsetOrWidth ;
2023-05-19 08:16:05 +12:00
uint8_t * data = codewords -> data ;
2022-01-02 05:56:55 -05:00
uint8_t size = modules -> bitOffsetOrWidth ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// Bit index into the data
uint32_t i = 0 ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// Do the funny zigzag scan
for ( int16_t right = size - 1 ; right >= 1 ; right -= 2 ) { // Index of right column in each column pair
2023-05-19 08:16:05 +12:00
if ( right == 6 ) {
right = 5 ;
}
2022-01-02 05:56:55 -05:00
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
}
}
}
}
2022-04-18 22:32:43 +02:00
/* #pragma mark - Penalty Calculation */
2022-01-02 05:56:55 -05:00
2023-05-19 08:16:05 +12:00
#define PENALTY_N1 3
#define PENALTY_N2 3
#define PENALTY_N3 40
#define PENALTY_N4 10
2022-01-02 05:56:55 -05:00
// 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.
// @TODO: This can be optimized by working with the bytes instead of bits.
2023-05-19 08:16:05 +12:00
static uint32_t getPenaltyScore ( BitBucket * modules ) {
2022-01-02 05:56:55 -05:00
uint32_t result = 0 ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
uint8_t size = modules -> bitOffsetOrWidth ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// 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 ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
} else {
runX ++ ;
if ( runX == 5 ) {
result += PENALTY_N1 ;
} else if ( runX > 5 ) {
result ++ ;
}
}
}
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// 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 ++ ;
}
}
}
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
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 ) | color ;
bitsCol = (( bitsCol << 1 ) & 0x7FF ) | 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
2023-05-19 08:16:05 +12:00
if ( color ) {
black ++ ;
}
2022-01-02 05:56:55 -05:00
}
}
// 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 ;
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
return result ;
}
2022-04-18 22:32:43 +02:00
/* #pragma mark - Reed-Solomon Generator */
2022-01-02 05:56:55 -05:00
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 ;
}
2023-05-19 08:16:05 +12:00
static void rs_init ( uint8_t degree , uint8_t * coeff ) {
2022-01-02 05:56:55 -05:00
memset ( coeff , 0 , degree );
coeff [ degree - 1 ] = 1 ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// 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)
}
}
2023-05-19 08:16:05 +12:00
static void rs_getRemainder ( uint8_t degree , uint8_t * coeff , uint8_t * data , uint8_t length , uint8_t * result , uint8_t stride ) {
2022-01-02 05:56:55 -05:00
// Compute the remainder by performing polynomial division
2023-05-19 08:16:05 +12:00
// for (uint8_t i = 0; i < degree; i++) { result[] = 0; }
// memset(result, 0, degree);
2022-01-02 05:56:55 -05:00
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 ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
for ( uint8_t j = 0 ; j < degree ; j ++ ) {
result [ j * stride ] ^= rs_multiply ( coeff [ j ], factor );
}
}
}
2022-04-18 22:32:43 +02:00
/* #pragma mark - QrCode */
2022-01-02 05:56:55 -05:00
2023-05-19 08:16:05 +12:00
static int8_t encodeDataCodewords ( BitBucket * dataCodewords , const uint8_t * text , uint16_t length , uint8_t version ) {
2022-01-02 05:56:55 -05:00
int8_t mode = MODE_BYTE ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
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 ;
}
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// 1 or 2 digits remaining
if ( accumCount > 0 ) {
bb_appendBits ( dataCodewords , accumData , accumCount * 3 + 1 );
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
} 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 ;
2023-05-19 08:16:05 +12:00
for ( uint16_t i = 0 ; i < length ; i ++ ) {
2022-01-02 05:56:55 -05:00
accumData = accumData * 45 + getAlphanumeric (( char )( text [ i ]));
accumCount ++ ;
if ( accumCount == 2 ) {
bb_appendBits ( dataCodewords , accumData , 11 );
accumData = 0 ;
accumCount = 0 ;
}
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// 1 character remaining
if ( accumCount > 0 ) {
bb_appendBits ( dataCodewords , accumData , 6 );
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
} 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 );
}
}
2023-05-19 08:16:05 +12:00
// bb_setBits(dataCodewords, length, 4, getModeBits(version, mode));
2022-01-02 05:56:55 -05:00
return mode ;
}
2023-05-19 08:16:05 +12:00
static void performErrorCorrection ( uint8_t version , uint8_t ecc , BitBucket * data ) {
2022-01-02 05:56:55 -05:00
// See: http://www.thonky.com/qr-code-tutorial/structure-final-message
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
#if LOCK_VERSION == 0
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 ];
#else
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS [ ecc ];
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS [ ecc ];
uint16_t moduleCount = NUM_RAW_DATA_MODULES ;
#endif
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
uint8_t blockEccLen = totalEcc / numBlocks ;
uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks ;
uint8_t shortBlockLen = moduleCount / 8 / numBlocks ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
uint8_t shortDataBlockLen = shortBlockLen - blockEccLen ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
uint8_t result [ data -> capacityBytes ];
memset ( result , 0 , sizeof ( result ));
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
uint8_t coeff [ blockEccLen ];
rs_init ( blockEccLen , coeff );
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
uint16_t offset = 0 ;
2023-05-19 08:16:05 +12:00
uint8_t * dataBytes = data -> data ;
2022-01-02 05:56:55 -05:00
// 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 ];
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
2023-05-19 08:16:05 +12:00
if ( blockNum == numShortBlocks ) {
stride ++ ;
}
2022-01-02 05:56:55 -05:00
#endif
index += stride ;
}
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// Version less than 5 only have short blocks
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
{
// 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 ];
2023-05-19 08:16:05 +12:00
if ( blockNum == 0 ) {
stride ++ ;
}
2022-01-02 05:56:55 -05:00
index += stride ;
}
}
#endif
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// Add all ecc blocks, interleaved
uint8_t blockSize = shortDataBlockLen ;
for ( uint8_t blockNum = 0 ; blockNum < numBlocks ; blockNum ++ ) {
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
2023-05-19 08:16:05 +12:00
if ( blockNum == numShortBlocks ) {
blockSize ++ ;
}
2022-01-02 05:56:55 -05:00
#endif
rs_getRemainder ( blockEccLen , coeff , dataBytes , blockSize , & result [ offset + blockNum ], numBlocks );
dataBytes += blockSize ;
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
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 );
2022-04-18 22:32:43 +02:00
/* #pragma mark - Public QRCode functions */
2022-01-02 05:56:55 -05:00
uint16_t qrcode_getBufferSize ( uint8_t version ) {
return bb_getGridSizeBytes ( 4 * version + 17 );
}
// @TODO: Return error if data is too big.
2023-05-19 08:16:05 +12:00
int8_t qrcode_initBytes ( QRCode * qrcode , uint8_t * modules , uint8_t version , uint8_t ecc , uint8_t * data , uint16_t length ) {
2022-01-02 05:56:55 -05:00
uint8_t size = version * 4 + 17 ;
qrcode -> version = version ;
qrcode -> size = size ;
qrcode -> ecc = ecc ;
qrcode -> modules = modules ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
uint8_t eccFormatBits = ( ECC_FORMAT_BITS >> ( 2 * ecc )) & 0x03 ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
#if LOCK_VERSION == 0
uint16_t moduleCount = NUM_RAW_DATA_MODULES [ version - 1 ];
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS [ eccFormatBits ][ version - 1 ];
#else
version = LOCK_VERSION ;
uint16_t moduleCount = NUM_RAW_DATA_MODULES ;
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS [ eccFormatBits ];
#endif
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
struct BitBucket codewords ;
uint8_t codewordBytes [ bb_getBufferSizeBytes ( moduleCount )];
bb_initBuffer ( & codewords , codewordBytes , ( int32_t ) sizeof ( codewordBytes ));
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// Place the data code words into the buffer
int8_t mode = encodeDataCodewords ( & codewords , data , length , version );
2023-05-19 08:16:05 +12:00
if ( mode < 0 ) {
return - 1 ;
}
2022-01-02 05:56:55 -05:00
qrcode -> mode = mode ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// Add terminator and pad up to a byte if applicable
uint32_t padding = ( dataCapacity * 8 ) - codewords . bitOffsetOrWidth ;
2023-05-19 08:16:05 +12:00
if ( padding > 4 ) {
padding = 4 ;
}
2022-01-02 05:56:55 -05:00
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 );
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
BitBucket isFunctionGrid ;
uint8_t isFunctionGridBytes [ bb_getGridSizeBytes ( size )];
bb_initGrid ( & isFunctionGrid , isFunctionGridBytes , size );
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// Draw function patterns, draw all codewords, do masking
drawFunctionPatterns ( & modulesGrid , & isFunctionGrid , version , eccFormatBits );
performErrorCorrection ( version , eccFormatBits , & codewords );
drawCodewords ( & modulesGrid , & isFunctionGrid , & codewords );
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// 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
}
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
qrcode -> mask = mask ;
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// Overwrite old format bits
drawFormatBits ( & modulesGrid , & isFunctionGrid , eccFormatBits , mask );
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
// Apply the final choice of mask
applyMask ( & modulesGrid , & isFunctionGrid , mask );
return 0 ;
}
2023-05-19 08:16:05 +12:00
int8_t qrcode_initText ( QRCode * qrcode , uint8_t * modules , uint8_t version , uint8_t ecc , const char * data ) {
2022-01-02 05:56:55 -05:00
return qrcode_initBytes ( qrcode , modules , version , ecc , ( uint8_t * ) data , strlen ( data ));
}
2023-05-19 08:16:05 +12:00
bool qrcode_getModule ( QRCode * qrcode , uint8_t x , uint8_t y ) {
if ( x >= qrcode -> size || y >= qrcode -> size ) {
2022-01-02 05:56:55 -05:00
return false ;
}
uint32_t offset = y * qrcode -> size + x ;
return ( qrcode -> modules [ offset >> 3 ] & ( 1 << ( 7 - ( offset & 0x07 )))) != 0 ;
}
/*
uint8_t qrcode_getHexLength(QRCode *qrcode) {
return ((qrcode->size * qrcode->size) + 7) / 4;
}
void qrcode_getHex(QRCode *qrcode, char *result) {
2023-05-19 08:16:05 +12:00
2022-01-02 05:56:55 -05:00
}
2022-04-18 22:32:43 +02:00
*/