diff --git a/README.md b/README.md index 3fc59d0..1eb0bab 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ The following example prints a QR code to the Serial Monitor (it likely will not be scannable, but is just for demonstration purposes). ```c -for (uint8 y = 0; y < qrcode.size; y++) { - for (uint8 x = 0; x < qrcode.size; x++) { - if (qrcode_getModule(&qrcode, x, y) { +for (uint8_t y = 0; y < qrcode.size; y++) { + for (uint8_t x = 0; x < qrcode.size; x++) { + if (qrcode_getModule(&qrcode, x, y)) { Serial.print("**"); } else { Serial.print(" "); diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..1dff07f --- /dev/null +++ b/src/Makefile @@ -0,0 +1,44 @@ +# +# Makefile for QR code test program. +# +# The MIT License (MIT) +# +# This library is written and maintained by Richard Moore. +# Major parts were derived from Project Nayuki's library. +# +# Copyright (c) 2025 by Michael R Sweet +# 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. +# + +.POSIX: + +CFLAGS = -Os -g +LDFLAGS = -Os -g +LIBS = -lz +OBJS = qrcode.o testqrcode.o + +all: testqrcode + +testqrcode: $(OBJS) + $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) + +$(OBJS): qrcode.h diff --git a/src/qrcode.c b/src/qrcode.c index 0b441b3..23a1bfe 100755 --- a/src/qrcode.c +++ b/src/qrcode.c @@ -4,6 +4,7 @@ * This library is written and maintained by Richard Moore. * Major parts were derived from Project Nayuki's library. * + * Copyright (c) 2025 Michael R Sweet * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) * @@ -104,10 +105,10 @@ static int abs(int value) { #pragma mark - Mode testing and conversion 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; @@ -119,7 +120,7 @@ static int8_t getAlphanumeric(char c) { case '/': return 43; case ':': return 44; } - + return -1; } @@ -151,18 +152,18 @@ 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 LOCK_VERSION == 0 || LOCK_VERSION > 9 if (version > 9) { modeInfo >>= 9; } #endif - + #if LOCK_VERSION == 0 || LOCK_VERSION > 26 if (version > 26) { modeInfo >>= 9; } #endif - + char result = 8 + ((modeInfo >> (3 * mode)) & 0x07); if (result == 15) { result = 16; } - + return result; } @@ -198,7 +199,7 @@ static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityB bitBuffer->bitOffsetOrWidth = 0; bitBuffer->capacityBytes = capacityBytes; bitBuffer->data = data; - + memset(data, 0, bitBuffer->capacityBytes); } @@ -259,11 +260,11 @@ 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; - + 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; @@ -312,7 +313,7 @@ static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint // 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 @@ -321,32 +322,32 @@ static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ec 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); } @@ -354,23 +355,23 @@ static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ec // 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 LOCK_VERSION != 0 && LOCK_VERSION < 7 return; - + #else 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; @@ -378,12 +379,12 @@ static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t versi setFunctionModule(modules, isFunction, a, b, bit); setFunctionModule(modules, isFunction, b, a, bit); } - + #endif } 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 @@ -391,18 +392,18 @@ static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint 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 LOCK_VERSION == 0 || LOCK_VERSION > 1 if (version > 1) { // Draw the numerous alignment patterns - + uint8_t alignCount = version / 7 + 2; uint8_t step; if (version != 32) { @@ -410,17 +411,17 @@ static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint } else { // C-C-C-Combo breaker! step = 26; } - + uint8_t alignPositionIndex = alignCount - 1; uint8_t alignPosition[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)) { @@ -431,9 +432,9 @@ static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint } } } - + #endif - + // Draw configuration data drawFormatBits(modules, isFunction, ecc, 0); // Dummy mask value; overwritten later in the constructor drawVersion(modules, isFunction, version); @@ -443,19 +444,19 @@ 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) { - + 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 @@ -486,19 +487,19 @@ static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket * // @TODO: This can be optimized by working with the bytes instead of bits. 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) { @@ -509,7 +510,7 @@ 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); @@ -528,7 +529,7 @@ static uint32_t getPenaltyScore(BitBucket *modules) { } } } - + uint16_t black = 0; for (uint8_t y = 0; y < size; y++) { uint16_t bitsRow = 0, bitsCol = 0; @@ -569,7 +570,7 @@ static uint32_t getPenaltyScore(BitBucket *modules) { for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) { result += PENALTY_N4; } - + return result; } @@ -590,7 +591,7 @@ static uint8_t rs_multiply(uint8_t x, uint8_t y) { 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). @@ -609,17 +610,17 @@ static void rs_init(uint8_t degree, uint8_t *coeff) { 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 < degree; i++) { result[] = 0; } //memset(result, 0, degree); - + 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); } @@ -632,7 +633,7 @@ 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) { int8_t mode = MODE_BYTE; - + if (isNumeric((char*)text, length)) { mode = MODE_NUMERIC; bb_appendBits(dataCodewords, 1 << MODE_NUMERIC, 4); @@ -649,12 +650,12 @@ static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, 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); @@ -671,12 +672,12 @@ static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, accumCount = 0; } } - + // 1 character remaining if (accumCount > 0) { bb_appendBits(dataCodewords, accumData, 6); } - + } else { bb_appendBits(dataCodewords, 1 << MODE_BYTE, 4); bb_appendBits(dataCodewords, length, getModeBits(version, MODE_BYTE)); @@ -684,16 +685,16 @@ static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, bb_appendBits(dataCodewords, (char)(text[i]), 8); } } - + //bb_setBits(dataCodewords, length, 4, getModeBits(version, mode)); - + return mode; } static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) { - + // See: http://www.thonky.com/qr-code-tutorial/structure-final-message - + #if LOCK_VERSION == 0 uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1]; uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1]; @@ -703,37 +704,37 @@ static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc]; uint16_t moduleCount = NUM_RAW_DATA_MODULES; #endif - + 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[data->capacityBytes]; memset(result, 0, sizeof(result)); - + uint8_t coeff[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 LOCK_VERSION == 0 || LOCK_VERSION >= 5 if (blockNum == numShortBlocks) { stride++; } #endif index += stride; } } - + // Version less than 5 only have short blocks #if LOCK_VERSION == 0 || LOCK_VERSION >= 5 { @@ -742,24 +743,24 @@ static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data uint8_t stride = shortDataBlockLen; for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) { result[offset++] = dataBytes[index]; - + if (blockNum == 0) { stride++; } index += stride; } } #endif - + // Add all ecc blocks, interleaved uint8_t blockSize = shortDataBlockLen; for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) { - + #if LOCK_VERSION == 0 || LOCK_VERSION >= 5 if (blockNum == numShortBlocks) { blockSize++; } #endif rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks); dataBytes += blockSize; } - + memcpy(data->data, result, data->capacityBytes); data->bitOffsetOrWidth = moduleCount; } @@ -775,17 +776,61 @@ uint16_t qrcode_getBufferSize(uint8_t version) { return bb_getGridSizeBytes(4 * version + 17); } -// @TODO: Return error if data is too big. 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; - + static uint16_t maxlength[40][4] = { + // Max bytes for each ECC and VERSION + { 17, 14, 11, 7 }, + { 32, 26, 20, 14 }, + { 53, 42, 32, 24 }, + { 78, 62, 46, 34 }, + { 106, 84, 60, 44 }, + { 134, 106, 74, 58 }, + { 154, 122, 86, 64 }, + { 192, 152, 108, 84 }, + { 230, 180, 130, 98 }, + { 271, 213, 151, 119 }, + { 321, 251, 177, 137 }, + { 367, 287, 203, 155 }, + { 425, 331, 241, 177 }, + { 458, 362, 258, 194 }, + { 520, 412, 292, 220 }, + { 586, 450, 322, 250 }, + { 644, 504, 364, 280 }, + { 718, 560, 394, 310 }, + { 792, 624, 442, 338 }, + { 858, 666, 482, 382 }, + { 929, 711, 509, 403 }, + { 1003, 779, 565, 439 }, + { 1091, 857, 611, 461 }, + { 1171, 911, 661, 511 }, + { 1273, 997, 715, 535 }, + { 1367, 1059, 751, 593 }, + { 1465, 1125, 805, 625 }, + { 1528, 1190, 868, 658 }, + { 1628, 1264, 908, 698 }, + { 1732, 1370, 982, 742 }, + { 1840, 1452, 1030, 790 }, + { 1952, 1538, 1112, 842 }, + { 2068, 1628, 1168, 898 }, + { 2188, 1722, 1228, 958 }, + { 2303, 1809, 1283, 983 }, + { 2431, 1911, 1351, 1051 }, + { 2563, 1989, 1423, 1093 }, + { 2699, 2099, 1499, 1139 }, + { 2809, 2213, 1579, 1219 }, + { 2953, 2331, 1663, 1273 } + }; + + if (ecc < ECC_LOW || ecc > ECC_HIGH) { return -1; } uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03; - + #if LOCK_VERSION == 0 + if (version == VERSION_AUTO) { + for (version = VERSION_MIN; version <= VERSION_MAX; version ++) { + if (maxlength[version - 1][ecc] >= length) { break; } + } + if (version > VERSION_MAX) { return -1; } + } else if (version < VERSION_MIN || version > VERSION_MAX) { return -1; } uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1]; #else @@ -793,17 +838,25 @@ int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8 uint16_t moduleCount = NUM_RAW_DATA_MODULES; uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits]; #endif - + + if (length > maxlength[version - 1][ecc]) { return -1; } + + uint8_t size = version * 4 + 17; + qrcode->version = version; + qrcode->size = size; + qrcode->ecc = ecc; + qrcode->modules = modules; + struct BitBucket codewords; uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)]; bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes)); - + // 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 = (dataCapacity * 8) - codewords.bitOffsetOrWidth; if (padding > 4) { padding = 4; } @@ -817,16 +870,16 @@ int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8 BitBucket modulesGrid; bb_initGrid(&modulesGrid, modules, size); - + BitBucket isFunctionGrid; uint8_t isFunctionGridBytes[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; @@ -840,12 +893,12 @@ 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); @@ -853,7 +906,9 @@ int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8 } 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)); + size_t length = strlen(data); + if (length > 65535) { return -1; } + return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, (uint16_t)length); } bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) { @@ -862,7 +917,7 @@ bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) { } uint32_t offset = y * qrcode->size + x; - return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; + return (qrcode->modules[offset >> 3] & (128 >> (offset & 0x07))) != 0; } /* @@ -871,6 +926,6 @@ uint8_t qrcode_getHexLength(QRCode *qrcode) { } void qrcode_getHex(QRCode *qrcode, char *result) { - + } */ diff --git a/src/qrcode.h b/src/qrcode.h index 6a5745e..0ec4d7c 100755 --- a/src/qrcode.h +++ b/src/qrcode.h @@ -4,6 +4,7 @@ * This library is written and maintained by Richard Moore. * Major parts were derived from Project Nayuki's library. * + * Copyright (c) 2025 Michael R Sweet * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) * @@ -37,12 +38,7 @@ #ifndef __QRCODE_H_ #define __QRCODE_H_ -#ifndef __cplusplus -typedef unsigned char bool; -static const bool false = 0; -static const bool true = 1; -#endif - +#include #include @@ -66,6 +62,14 @@ static const bool true = 1; #endif +// Version Numbers +#if LOCK_VERSION == 0 +#define VERSION_AUTO 0 +#endif // LOCK_VERSION == 0 +#define VERSION_MIN 1 +#define VERSION_MAX 40 + + typedef struct QRCode { uint8_t version; uint8_t size; diff --git a/src/testqrcode.c b/src/testqrcode.c new file mode 100644 index 0000000..de523ba --- /dev/null +++ b/src/testqrcode.c @@ -0,0 +1,378 @@ +/** + * Test program that generates a SVG QR code using the API. + * + * Usage: + * + * ./testqrcode [-e {low,medium,quartile,high}] [-v VERSION] TEXT >FILENAME.svg + * + * The MIT License (MIT) + * + * This library is written and maintained by Richard Moore. + * Major parts were derived from Project Nayuki's library. + * + * Copyright (c) 2025 by Michael R Sweet + * 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. + */ + +#include +#include +#include +#include "qrcode.h" +#include + + +// Image export constants... +#define QR_SCALE 5 // Nominal size of modules +#define QR_PADDING 4 // White padding around QR code + + +// Local function for PNG output... +static unsigned char *png_add_crc(unsigned char *pngdata, unsigned char *pngptr, unsigned char *pngend); +static unsigned char *png_add_unsigned(unsigned val, unsigned char *pngptr, unsigned char *pngend); + + +// Main entry +int main(int argc, char *argv[]) { + const char *progname; // Program name + int i; // Looping var + uint8_t ecc = ECC_LOW; // Error correction level + uint8_t version = VERSION_AUTO; // Version/size + const char *text = NULL; // Text to encode + QRCode qrcode; // QR code data + uint8_t qrcodeBytes[qrcode_getBufferSize(VERSION_MAX)]; + // QR code buffer + bool makeSVG = false; // Output SVG? + + + // Parse command-line... + if ((progname = strrchr(argv[0], '/')) != NULL) + progname ++; + else + progname = argv[0]; + + for (i = 1; i < argc; i ++) { + if (argv[i][0] == '-') { + for (const char *opt = argv[i] + 1; *opt; opt ++) { + switch (*opt) { + case 'e' : /* -e ECC */ + i ++; + if (i >= argc) { + fprintf(stderr, "%s: Missing error correction level after '-e'.\n", progname); + return 1; + } else if (!strcmp(argv[i], "low")) { + ecc = ECC_LOW; + } else if (!strcmp(argv[i], "medium")) { + ecc = ECC_MEDIUM; + } else if (!strcmp(argv[i], "quartile")) { + ecc = ECC_QUARTILE; + } else if (!strcmp(argv[i], "high")) { + ecc = ECC_HIGH; + } else { + fprintf(stderr, "%s: Bad error correction level '-e %s'.\n", progname, argv[i]); + return 1; + } + break; + + case 'f' : /* -f FORMAT */ + i ++; + if (i >= argc) { + fprintf(stderr, "%s: Missing format after '-f'.\n", progname); + return 1; + } else { + if (!strcmp(argv[i], "svg")) { + makeSVG = true; + } else if (strcmp(argv[i], "png")) { + fprintf(stderr, "%s: Unsupported format '%s'.\n", progname, argv[i]); + return 1; + } + } + break; + + case 'v' : /* -v VERSION */ + i ++; + if (i >= argc) { + fprintf(stderr, "%s: Missing version number after '-v'.\n", progname); + return 1; + } else { + long tempval = strtol(argv[i], NULL, 10); + if (tempval < VERSION_MIN || tempval > VERSION_MAX) { + fprintf(stderr, "%s: Bad version '-v %s'.\n", progname, argv[i]); + return 1; + } + version = (uint8_t)tempval; + } + break; + + default : + fprintf(stderr, "%s: Unknown option '-%c'.\n", progname, *opt); + return 1; + } + } + } else if (text != NULL) { + fprintf(stderr, "%s: Unknown option '%s'.\n", progname, argv[i]); + return 1; + } else { + text = argv[i]; + } + } + + // Verify we have something to generate... + if (text == NULL) { + fprintf(stderr, "Usage: %s [-e ECC] [-v VERSION] TEXT >FILENAME.svg\n", progname); + fputs("Options:\n", stderr); + fputs("-e ECC Specify error correction (low,medium,quartile,high)\n", stderr); + fputs("-e VERSION Specify version/size (1 to 40, default is auto)\n", stderr); + return 1; + } + + // Generate QR code... + if (qrcode_initText(&qrcode, qrcodeBytes, version, ecc, text) < 0) { + fprintf(stderr, "%s: Unable to generate QR code.\n", progname); + return 1; + } + + if (makeSVG) { + // Write SVG to stdout... + printf("\n", (qrcode.size + 2 * QR_PADDING) * QR_SCALE, (qrcode.size + 2 * QR_PADDING) * QR_SCALE); + printf(" \n", (qrcode.size + 2 * QR_PADDING) * QR_SCALE, (qrcode.size + 2 * QR_PADDING) * QR_SCALE); + + for (uint8_t y = 0; y < qrcode.size; y++) { + uint8_t xstart = 0, xcount = 0; + + for (uint8_t x = 0; x < qrcode.size; x++) { + if (qrcode_getModule(&qrcode, x, y)) { + if (xcount == 0) { xstart = x; } + xcount ++; + } else if (xcount > 0) { + printf(" \n", (xstart + QR_PADDING) * QR_SCALE, (y + QR_PADDING) * QR_SCALE, xcount * QR_SCALE, QR_SCALE); + xcount = 0; + } + } + + if (xcount > 0) { + printf(" \n", (xstart + QR_PADDING) * QR_SCALE, (y + QR_PADDING) * QR_SCALE, xcount * QR_SCALE, QR_SCALE); + } + } + + puts(""); + } else { + // Write PNG to stdout... + unsigned char pngbuf[65536], // PNG output buffer + *pngptr = pngbuf, + // Pointer into PNG buffer + *pngend = pngbuf + sizeof(pngbuf), + // Pointer to end of PNG buffer + *pngdata, // Start of PNG chunk data + line[1 + (QR_SCALE * (255 + 2 * QR_PADDING) + 7) / 8], + // PNG bitmap line starting with filter byte + *lineptr, // Pointer into line + bit; // Current bit + unsigned size = QR_SCALE * (qrcode.size + 2 * QR_PADDING), + // Size of image + linelen = (size + 7) / 8, + // Length of a line + x, x0, y, y0, // Looping vars + xoff = (QR_SCALE * QR_PADDING) / 8, + xmod = (QR_SCALE * QR_PADDING) & 7; + int zerr; // ZLIB error code + z_stream zstream; // ZLIB compression stream + + + // Add the PNG file header... + *pngptr++ = 137; + *pngptr++ = 80; + *pngptr++ = 78; + *pngptr++ = 71; + *pngptr++ = 13; + *pngptr++ = 10; + *pngptr++ = 26; + *pngptr++ = 10; + + // Add the IHDR chunk... + pngptr = png_add_unsigned(13, pngptr, pngend); + pngdata = pngptr; + + *pngptr++ = 'I'; + *pngptr++ = 'H'; + *pngptr++ = 'D'; + *pngptr++ = 'R'; + + pngptr = png_add_unsigned(size, pngptr, pngend); + // Width + pngptr = png_add_unsigned(size, pngptr, pngend); + // Height + *pngptr++ = 1; // Bit depth + *pngptr++ = 0; // Color type grayscale + *pngptr++ = 0; // Compression method 0 (deflate) + *pngptr++ = 0; // Filter method 0 (adaptive) + *pngptr++ = 0; // Interlace method 0 (no interlace) + pngptr = png_add_crc(pngdata, pngptr, pngend); + + // Add the IDAT chunk... + pngptr += 4; // Leave room for length + pngdata = pngptr; + + *pngptr++ = 'I'; + *pngptr++ = 'D'; + *pngptr++ = 'A'; + *pngptr++ = 'T'; + + // Initialize zlib compressor... + if ((zerr = deflateInit2(&zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, /*windowBits*/11, /*memLevel*/7, Z_DEFAULT_STRATEGY)) < Z_OK) { + fprintf(stderr, "%s: Unable to create deflate stream (%d).\n", progname, zerr); + return 1; + } + + zstream.next_in = (Bytef *)line; + zstream.next_out = (Bytef *)pngptr; + zstream.avail_out = (uInt)(sizeof(pngbuf) - (pngptr - pngbuf)); + + // All lines start with the "None" (0) filter... + line[0] = 0; + + // Add padding at the top... + memset(line + 1, 0xff, linelen); + for (y = 0; y < (QR_SCALE * QR_PADDING); y ++) { + zstream.next_in = (Bytef *)line; + zstream.avail_in = linelen + 1; + if ((zerr = deflate(&zstream, Z_NO_FLUSH)) < Z_OK) { + fprintf(stderr, "%s: Unable to deflate image (%d).\n", progname, zerr); + return 1; + } + } + + // Add lines from the QR code... + for (y = 0; y < qrcode.size; y ++) { + memset(line + 1, 0xff, linelen); + + for (x = 0, lineptr = line + 1 + xoff, bit = 128 >> xmod; x < qrcode.size; x ++) { + bool qrset = qrcode_getModule(&qrcode, x, y); + + for (x0 = 0; x0 < QR_SCALE; x0 ++) { + if (qrset) { + *lineptr ^= bit; + } + + if (bit == 1) { + lineptr ++; + bit = 128; + } else { + bit = bit / 2; + } + } + } + + for (y0 = 0; y0 < QR_SCALE; y0 ++) { + zstream.next_in = (Bytef *)line; + zstream.avail_in = linelen + 1; + if ((zerr = deflate(&zstream, Z_NO_FLUSH)) < Z_OK) { + fprintf(stderr, "%s: Unable to deflate image (%d).\n", progname, zerr); + return 1; + } + } + } + + // Add padding at the bottom... + memset(line + 1, 0xff, linelen); + for (y = 0; y < (QR_SCALE * QR_PADDING); y ++) { + zstream.next_in = (Bytef *)line; + zstream.avail_in = linelen + 1; + if ((zerr = deflate(&zstream, Z_NO_FLUSH)) < Z_OK) { + fprintf(stderr, "%s: Unable to deflate image (%d).\n", progname, zerr); + return 1; + } + } + + // Finish compression... + zstream.next_in = (Bytef *)line; + zstream.avail_in = 0; + if ((zerr = deflate(&zstream, Z_FINISH)) != Z_STREAM_END) { + fprintf(stderr, "%s: Unable to end image (%d).\n", progname, zerr); + return 1; + } + + pngptr = (unsigned char *)zstream.next_out; + + png_add_unsigned((unsigned)(pngptr - pngdata - 4), pngdata - 4, pngend); + pngptr = png_add_crc(pngdata, pngptr, pngend); + + // Add the IEND chunk... + pngptr = png_add_unsigned(0, pngptr, pngend); + pngdata = pngptr; + + *pngptr++ = 'I'; + *pngptr++ = 'E'; + *pngptr++ = 'N'; + *pngptr++ = 'D'; + + pngptr = png_add_crc(pngdata, pngptr, pngend); + + // Write the PNG file to stdout... + fwrite(pngbuf, (size_t)(pngptr - pngbuf), 1, stdout); + fflush(stdout); + } + + return 0; +} + + +// +// 'png_add_crc()' - Compute and append the chunk data CRC. +// + +static unsigned char * // O - Next byte in output buffer +png_add_crc(unsigned char *pngdata, // I - Pointer to start of chunk data + unsigned char *pngptr, // I - Pointer to end of chunk data (where CRC goes) + unsigned char *pngend) // I - Pointer to end of PNG output buffer +{ + unsigned c; // CRC value + + + c = crc32(0, Z_NULL, 0); + c = crc32(c, pngdata, (uInt)(pngptr - pngdata)); + + // Append the CRC to the buffer... + return (png_add_unsigned(c, pngptr, pngend)); +} + + +// +// 'png_add_unsigned()' - Add a 32-bit unsigned integer to the PNG buffer. +// + +static unsigned char * // O - Next byte in output buffer +png_add_unsigned(unsigned val, // I - Value to append + unsigned char *pngptr, // I - Pointer into output buffer + unsigned char *pngend) // I - Pointer to end of output buffer +{ + // Append the value to the buffer... + if (pngptr < pngend) + *pngptr++ = (val >> 24) & 0xff; + if (pngptr < pngend) + *pngptr++ = (val >> 16) & 0xff; + if (pngptr < pngend) + *pngptr++ = (val >> 8) & 0xff; + if (pngptr < pngend) + *pngptr++ = val & 0xff; + + return (pngptr); +}