diff --git a/src/lib/aztecbarcode.cpp b/src/lib/aztecbarcode.cpp index 8144e14..e2166d2 100644 --- a/src/lib/aztecbarcode.cpp +++ b/src/lib/aztecbarcode.cpp @@ -1,792 +1,792 @@ /* Copyright (c) 2017 Volker Krause 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 "aztecbarcode.h" #include "bitvector_p.h" #include "reedsolomon_p.h" #include "prison_debug.h" #include #include #include #include #include // see https://en.wikipedia.org/wiki/Aztec_Code for encoding tables, magic numbers, etc using namespace Prison; enum { FullMaxSize = 151, FullRadius = 74, FullGridInterval = 16, FullModeMessageSize = 40, FullLayerCount = 32, CompactMaxSize = 27, CompactRadius = 13, CompactModeMessageSize = 28, CompactLayerCount = 4 }; AztecBarcode::AztecBarcode() = default; AztecBarcode::~AztecBarcode() = default; // encoding properties depending on layer count struct aztec_layer_property_t { uint8_t layer; uint8_t codeWordSize; uint16_t gf; }; static const aztec_layer_property_t aztec_layer_properties[] = { { 2, 6, ReedSolomon::GF64 }, { 8, 8, ReedSolomon::GF256 }, { 22, 10, ReedSolomon::GF1024 }, { 32, 12, ReedSolomon::GF4096 } }; // amounts of bits in an Aztec code depending on layer count static int aztecCompactDataBits(int layer) { return (88 + 16 * layer) * layer; } static int aztecFullDataBits(int layer) { return (112 + 16 * layer) * layer; } QImage AztecBarcode::paintImage(const QSizeF& size) { const auto inputData = aztecEncode(data().toLatin1()); int layerCount = 0; int codewordCount = 0; int availableBits = 0; int stuffSize = 0; // extra bits added during bit stuffing, which might make us overun the available size bool compactMode = false; BitVector encodedData; do { layerCount = 0; // find the smallest layout we can put the data in, while leaving 23% for error correction for (auto i = 1; i <= FullLayerCount; ++i) { if (aztecFullDataBits(i) * 0.77 > (inputData.size() + stuffSize)) { layerCount = i; break; } } for (auto i = 1; i <= CompactLayerCount; ++i) { if (aztecCompactDataBits(i) * 0.77 > (inputData.size() + stuffSize)) { layerCount = i; compactMode = true; break; } } if (layerCount == 0) { qCWarning(Log) << "data too large for Aztec code" << inputData.size(); return {}; } // determine code word size const auto propIt = std::lower_bound(aztec_layer_properties, aztec_layer_properties + 4, layerCount, [](const aztec_layer_property_t &lhs, int rhs) { return lhs.layer < rhs; }); // bit stuffing auto stuffedData = bitStuffAndPad(inputData, (*propIt).codeWordSize); stuffSize = stuffedData.size() - inputData.size(); availableBits = compactMode ? aztecCompactDataBits(layerCount) : aztecFullDataBits(layerCount); codewordCount = stuffedData.size() / (*propIt).codeWordSize; const auto rsWordCount = availableBits / (*propIt).codeWordSize - codewordCount; // compute error correction ReedSolomon rs((*propIt).gf, rsWordCount); const auto rsData = rs.encode(stuffedData); // pad with leading 0 bits to align to code word boundaries encodedData.reserve(availableBits); if (int diff = availableBits - stuffedData.size() - rsData.size()) { encodedData.appendMSB(0, diff); } encodedData.append(stuffedData); encodedData.append(rsData); // try again in the rare case that we overrun the available bits due to bit stuffing and padding } while (encodedData.size() > availableBits); // determine mode message BitVector modeMsg; if (compactMode) { modeMsg.appendMSB(layerCount - 1, 2); modeMsg.appendMSB(codewordCount - 1, 6); ReedSolomon rs(ReedSolomon::GF16, 5); modeMsg.append(rs.encode(modeMsg)); } else { modeMsg.appendMSB(layerCount - 1, 5); modeMsg.appendMSB(codewordCount - 1, 11); ReedSolomon rs(ReedSolomon::GF16, 6); modeMsg.append(rs.encode(modeMsg)); } // render the result if (compactMode) { QImage img(CompactMaxSize, CompactMaxSize, QImage::Format_RGB32); img.fill(backgroundColor()); paintCompactGrid(&img); paintCompactData(&img, encodedData, layerCount); paintCompactModeMessage(&img, modeMsg); - return cropAndScaleCompact(&img, layerCount, std::max(size.width(), size.height())); + return cropAndScaleCompact(&img, layerCount, std::min(size.width(), size.height())); } else { QImage img(FullMaxSize, FullMaxSize, QImage::Format_RGB32); img.fill(backgroundColor()); paintFullGrid(&img); paintFullData(&img, encodedData, layerCount); paintFullModeMessage(&img, modeMsg); - return cropAndScaleFull(&img, layerCount, std::max(size.width(), size.height())); + return cropAndScaleFull(&img, layerCount, std::min(size.width(), size.height())); } } // code points and encoding modes for each of the first 127 ASCII characters, the rest is encoded in Binary mode enum Mode { NoMode, Upper, Lower, Mixed, Punct, Digit, Binary, MODE_COUNT, Special }; enum SpecialChar { Space, CarriageReturn, Comma, Dot, SPECIAL_CHAR_COUNT }; struct aztec_code_t { uint8_t code; uint8_t mode; }; static const aztec_code_t aztec_code_table[] = { { 0, Binary }, // 0 { 2, Mixed }, { 3, Mixed }, { 4, Mixed }, { 5, Mixed }, { 6, Mixed }, { 7, Mixed }, { 8, Mixed }, // 7 BEL \a { 9, Mixed }, { 10, Mixed }, { 11, Mixed }, // 10 LF / ^J { 12, Mixed }, { 13, Mixed }, { CarriageReturn, Special }, // 13 CR / ^M - but also 1 Punct { 14, Binary }, { 15, Binary }, { 16, Binary }, { 17, Binary }, { 18, Binary }, { 19, Binary }, { 20, Binary }, // 20 ^T { 21, Binary }, { 22, Binary }, { 23, Binary }, { 24, Binary }, { 25, Binary }, { 26, Binary }, { 15, Mixed }, // 27 ^[ { 16, Mixed }, { 17, Mixed }, { 18, Mixed }, // 30 ^^ { 19, Mixed }, { Space, Special }, // 32 SP { 6, Punct }, { 7, Punct }, { 8, Punct }, // 35 # { 9, Punct }, { 10, Punct }, { 11, Punct }, { 12, Punct }, { 13, Punct }, // 40 ( { 14, Punct }, { 15, Punct }, { 16, Punct }, // 43 + { Comma, Special }, // 44 , { 18, Punct }, // 45 - { Dot, Special }, // 46 . { 20, Punct }, // 47 / { 2, Digit }, // 48 0 { 3, Digit }, { 4, Digit }, { 5, Digit }, { 6, Digit }, { 7, Digit }, { 8, Digit }, { 9, Digit }, { 10, Digit }, { 11, Digit }, // 57 9 { 21, Punct }, // 58 : { 22, Punct }, // 59 ; { 23, Punct }, // 60 < { 24, Punct }, { 25, Punct }, // 62 > { 26, Punct }, // 63 ? { 20, Mixed }, // 64 @ { 2, Upper }, // 65 A { 3, Upper }, { 4, Upper }, { 5, Upper }, { 6, Upper }, { 7, Upper }, { 8, Upper }, { 9, Upper }, { 10, Upper }, { 11, Upper }, { 12, Upper }, { 13, Upper }, { 14, Upper }, { 15, Upper }, { 16, Upper }, { 17, Upper }, { 18, Upper }, { 19, Upper }, { 20, Upper }, { 21, Upper }, { 22, Upper }, { 23, Upper }, { 24, Upper }, { 25, Upper }, { 26, Upper }, { 27, Upper }, // 90 Z { 27, Punct }, // 91 [ { 21, Mixed }, // 92 backslash { 28, Punct }, // 93 ] { 22, Mixed }, // 94 ^ { 23, Mixed }, // 95 _ { 24, Mixed }, // 96 ` { 2, Lower }, // 97 a { 3, Lower }, { 4, Lower }, { 5, Lower }, { 6, Lower }, { 7, Lower }, { 8, Lower }, { 9, Lower }, { 10, Lower }, { 11, Lower }, { 12, Lower }, { 13, Lower }, { 14, Lower }, { 15, Lower }, { 16, Lower }, { 17, Lower }, { 18, Lower }, { 19, Lower }, { 20, Lower }, { 21, Lower }, { 22, Lower }, { 23, Lower }, { 24, Lower }, { 25, Lower }, { 26, Lower }, { 27, Lower }, // 122 z { 29, Punct }, // 123 { { 25, Mixed }, // 124 | { 30, Punct }, // 125 } { 26, Mixed }, // 126 ~ { 27, Mixed } // 127 DEL ^? }; Q_STATIC_ASSERT(sizeof(aztec_code_table) == 256); static const struct { uint8_t c1; uint8_t c2; aztec_code_t sym; } aztec_code_double_symbols[] = { { '\r', '\n', { 2, Punct } }, // CR LF { '.', ' ', { 3, Punct } }, // . SP { ',', ' ', { 4, Punct } }, // , SP { ':', ' ', { 5, Punct } } // : SP }; static const int aztec_code_double_symbols_count = sizeof(aztec_code_double_symbols) / sizeof(aztec_code_double_symbols[0]); static const int aztec_code_size[] = { 0, 5, 5, 5, 5, 4, 8 }; Q_STATIC_ASSERT(sizeof(aztec_code_size) / sizeof(int) == MODE_COUNT); // codes for ambigious characters, ie. those that can be encoded in multiple modes static const aztec_code_t aztec_special_chars[SPECIAL_CHAR_COUNT][MODE_COUNT - 1] = { /* NoMode Upper Lower Mixed Punct Digit */ { { 0, NoMode }, { 1, Upper }, { 1, Lower }, { 1, Mixed }, { 1, Upper }, { 1, Digit } }, /* SP */ { { 0, NoMode }, { 1, Punct }, { 1, Punct }, { 14, Mixed }, { 1, Punct }, { 1, Punct } }, /* CR */ { { 0, NoMode }, { 17, Punct }, { 17, Punct }, { 17, Punct }, { 17, Punct }, { 12, Digit } }, /* Comma */ { { 0, NoMode }, { 19, Punct }, { 19, Punct }, { 19, Punct }, { 19, Punct }, { 13, Digit } }, /* Dot */ }; // shift code table, source mode -> target mode // NoMode indicates shift is not available, use latch instead static const aztec_code_t aztec_shift_codes[MODE_COUNT - 1][MODE_COUNT - 1] = { /* NoMode Upper Lower Mixed Punct Digit */ { { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode } }, { { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, Punct }, { 0, NoMode } }, { { 0, NoMode }, { 28, Upper }, { 0, NoMode }, { 0, NoMode }, { 0, Punct }, { 0, NoMode } }, { { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, Punct }, { 0, NoMode } }, { { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode } }, { { 0, NoMode }, { 15, Upper }, { 0, NoMode }, { 0, NoMode }, { 0, Punct }, { 0, NoMode } } }; // latch code table, source mode -> target mode static const aztec_code_t aztec_latch_codes[MODE_COUNT - 1][MODE_COUNT] = { /* NoMode Upper Lower Mixed Punct Digit Binary */ { { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode }, { 0, NoMode } }, { { 0, NoMode }, { 0, NoMode }, { 28, Lower }, { 29, Mixed }, { 29, Mixed }, { 30, Digit }, { 31, Binary } }, { { 0, NoMode }, { 30, Digit }, { 0, NoMode }, { 29, Mixed }, { 29, Mixed }, { 30, Digit }, { 31, Binary } }, { { 0, NoMode }, { 29, Upper }, { 28, Lower }, { 0, NoMode }, { 30, Punct }, { 28, Lower }, { 31, Binary } }, { { 0, NoMode }, { 31, Upper }, { 31, Upper }, { 31, Upper }, { 0, NoMode }, { 31, Upper }, { 31, Upper } }, { { 0, NoMode }, { 14, Upper }, { 14, Upper }, { 14, Upper }, { 14, Upper }, { 0, NoMode }, { 14, Upper } } }; static Mode aztecCodeLatchTo(Mode currentMode, Mode targetMode, BitVector *v) { if (currentMode == targetMode) { return targetMode; } const auto latchCode = aztec_latch_codes[currentMode][targetMode]; qCDebug(Log) << "latch" << latchCode.code << aztec_code_size[currentMode]; v->appendMSB(latchCode.code, aztec_code_size[currentMode]); return static_cast(latchCode.mode); } static void aztecEncodeBinary(std::vector::iterator &it, const std::vector::iterator &end, BitVector *v) { // determine length of the binary sequence const auto binEndIt = std::find_if(it, end, [](aztec_code_t sym) { return sym.mode != Binary; }); const auto length = std::distance(it, binEndIt); // write length field qCDebug(Log) << "binary length" << length; if (length < 32) { v->appendMSB(length, 5); } else { v->appendMSB(0, 5); v->appendMSB(length - 31, 11); } // write data for (; it != binEndIt; ++it) { qCDebug(Log) << "binary data" << (*it).code; v->appendMSB((*it).code, 8); } } static void aztecEncodeResolveAmbigious(Mode currentMode, const std::vector::iterator &begin, const std::vector::iterator &end) { Q_ASSERT(begin != end); Q_ASSERT(currentMode != (*begin).mode); Q_ASSERT((*begin).mode == Special); // forward search auto it = begin; for (; it != end && (*it).mode == Special; ++it) { if (aztec_special_chars[(*it).code][currentMode].mode == currentMode) { qCDebug(Log) << "special resolved to current mode by forward search"; (*it).mode = aztec_special_chars[(*it).code][currentMode].mode; (*it).code = aztec_special_chars[(*it).code][currentMode].code; } } // backward search auto backIt = it; while (std::distance(begin, backIt) >= 1 && it != end) { --backIt; if ((*backIt).mode == Special && aztec_special_chars[(*backIt).code][(*it).mode].mode == (*it).mode) { qCDebug(Log) << "special resolved by backward search"; (*backIt).mode = aztec_special_chars[(*backIt).code][(*it).mode].mode; (*backIt).code = aztec_special_chars[(*backIt).code][(*it).mode].code; } else { break; } } // pick one if we still have an ambigious symbol if ((*begin).mode != Special) { return; } (*begin).mode = aztec_special_chars[(*begin).code][currentMode].mode; (*begin).code = aztec_special_chars[(*begin).code][currentMode].code; it = begin + 1; if (it != end && (*it).mode == Special) { aztecEncodeResolveAmbigious(static_cast((*begin).mode), it, end); } } static Mode aztecNextMode(Mode currentMode, const std::vector::iterator &nextSym, const std::vector::iterator &end, bool &tryShift) { Q_ASSERT(currentMode != (*nextSym).mode); Q_ASSERT(nextSym != end); Q_ASSERT((*nextSym).mode != Special); auto it = nextSym; ++it; if (it != end && (*it).mode == Special) { aztecEncodeResolveAmbigious(static_cast((*nextSym).mode), it, end); } if ((it == end || (*it).mode == currentMode) && std::distance(nextSym, it) == 1) { tryShift = true; } qCDebug(Log) << currentMode << (*nextSym).mode << tryShift << std::distance(nextSym, it); return static_cast((*nextSym).mode); } BitVector AztecBarcode::aztecEncode(const QByteArray &data) const { // phase one: translate single and double chars to code points std::vector codes; codes.reserve(data.size()); for (int i = 0; i < data.size(); ++i) { const uint8_t c1 = data.at(i); // double char codes if (i < data.size() - 1) { const uint8_t c2 = data.at(i + 1); bool found = false; for (int j = 0; j < aztec_code_double_symbols_count; ++j) { const auto dblCode = aztec_code_double_symbols[j]; if (dblCode.c1 != c1 || dblCode.c2 != c2) { continue; } codes.push_back(dblCode.sym); ++i; found = true; } if (found) { continue; } } // > 127 binary-only range if (c1 > 127) { codes.push_back({c1, Binary}); // encodable single ASCII character } else { codes.push_back(aztec_code_table[c1]); } } // phase two: insert shift and latch codes, translate to bit stream Mode currentMode = Upper; BitVector result; for (auto it = codes.begin(); it != codes.end();) { if ((*it).mode == Binary) { auto newMode = aztecCodeLatchTo(currentMode, Binary, &result); while (newMode != Binary) { currentMode = newMode; newMode = aztecCodeLatchTo(currentMode, Binary, &result); } aztecEncodeBinary(it, codes.end(), &result); continue; } // resolve special codes if ((*it).mode == Special) { aztecEncodeResolveAmbigious(currentMode, it, codes.end()); } // deal with mode changes Mode nextMode = currentMode; if ((*it).mode != currentMode) { bool tryShift = false; const auto newMode = aztecNextMode(currentMode, it, codes.end(), tryShift); // shift to new mode if desired and possible if (tryShift && aztec_shift_codes[currentMode][newMode].mode != NoMode) { qCDebug(Log) << "shift" << aztec_shift_codes[currentMode][newMode].code << aztec_code_size[currentMode]; result.appendMSB(aztec_shift_codes[currentMode][newMode].code, aztec_code_size[currentMode]); currentMode = newMode; } // latch to new mode while (currentMode != newMode && newMode != NoMode && currentMode != NoMode) { currentMode = aztecCodeLatchTo(currentMode, newMode, &result); nextMode = currentMode; } } qCDebug(Log) << (*it).code << aztec_code_size[currentMode]; result.appendMSB((*it).code, aztec_code_size[currentMode]); ++it; currentMode = nextMode; } return result; } BitVector AztecBarcode::bitStuffAndPad(const BitVector& input, int codeWordSize) const { BitVector res; res.reserve(input.size()); // bit stuff codewords with leading codeWordSize 0/1 bits int i = 0; while(i < input.size() - (codeWordSize - 1)) { int v = input.valueAtMSB(i, codeWordSize - 1); res.appendMSB(v, codeWordSize - 1); i += codeWordSize - 1; if (v == 0) { res.appendBit(true); } else if (v == (1 << (codeWordSize - 1)) - 1) { res.appendBit(false); } else { res.appendBit(input.at(i++)); } } while (i < input.size()) { res.appendBit(input.at(i++)); } // pad to nearest code word boundary while (res.size() % codeWordSize) { res.appendBit(true); } return res; } void AztecBarcode::paintFullGrid(QImage *img) const { QPainter p(img); p.translate(img->width() / 2, img->height() / 2); // alignment grids QPen pen(foregroundColor()); pen.setDashPattern({1, 1}); p.setPen(pen); for (int i = 0; i < img->width() / 2; i += FullGridInterval) { p.drawLine(-i, -FullRadius, -i, FullRadius); p.drawLine( i, -FullRadius, i, FullRadius); p.drawLine(-FullRadius, -i, FullRadius, -i); p.drawLine(-FullRadius, i, FullRadius, i); } // bullseye background p.setBrush(backgroundColor()); p.setPen(Qt::NoPen); p.drawRect(-7, -7, 14, 14); // bullseye p.setBrush(Qt::NoBrush); p.setPen(foregroundColor()); p.drawPoint(0, 0); p.drawRect(-2, -2, 4, 4); p.drawRect(-4, -4, 8, 8); p.drawRect(-6, -6, 12, 12); // bullseye orientation marker p.drawRect(-7, -7, 1, 1); p.drawRect(7, -7, 0, 1); p.drawPoint(7, 6); } static const int aztecFullLayerOffset[] = { // 0 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 66, 64, 62, 60, 57, 55, 53, 51, 49, 47, 45, 42, 40, 38, 36, 34, 32, 30, 28, 25, 23, 21, 19, 17, 15, 13, 10, 8, 6, 4, 2, 0 }; void AztecBarcode::paintFullData(QImage* img, const BitVector &data, int layerCount) const { QPainter p(img); p.setPen(foregroundColor()); auto it = data.begin(); for (int layer = layerCount - 1; layer >= 0; --layer) { const auto x1 = aztecFullLayerOffset[layer]; const auto y1 = x1; const auto gridInMiddle = (x1 - FullRadius) % FullGridInterval == 0; const auto x2 = gridInMiddle ? x1 + 2 : x1 + 1; const auto segmentLength = FullMaxSize - 2 * y1 - 2 - (gridInMiddle ? 1 : 0); for (int rotation = 0; rotation < 4; ++rotation) { p.resetTransform(); p.translate(img->width() / 2, img->height() / 2); p.rotate(-90 * rotation); p.translate(-img->width() / 2, -img->height() / 2); for (int i = 0; it != data.end(); ++i, ++it) { const auto x = (i % 2 == 0) ? x1 : x2; auto y = i / 2 + y1; if (((y - FullRadius - 1) % FullGridInterval) == 0) { // skip grid lines ++y; i += 2; } if (y >= y1 + segmentLength) { break; } if (*it) { p.drawPoint(x, y); } } } } } void AztecBarcode::paintFullModeMessage(QImage *img, const BitVector &modeData) const { Q_ASSERT(modeData.size() == FullModeMessageSize); QPainter p(img); p.setPen(foregroundColor()); auto it = modeData.begin(); for (int rotation = 0; rotation < 4; ++rotation) { p.resetTransform(); p.translate(img->width() / 2, img->height() / 2); p.rotate(90 * rotation); for (int i = -5; i <= 5; ++i) { if (i == 0) { // skip grid line continue; } if (*it) { p.drawPoint(i, -7); } ++it; } } } QImage AztecBarcode::cropAndScaleFull(QImage *img, int layerCount, int size) { const auto offset = aztecFullLayerOffset[layerCount - 1]; const auto minSize = FullMaxSize - 2 * offset; setMinimumSize(QSizeF(minSize, minSize) * 4); // *4 taken from what QR does const int scale = std::max(1, size / minSize); QImage out(minSize * scale, minSize * scale, img->format()); QPainter p(&out); p.setRenderHint(QPainter::SmoothPixmapTransform, false); const auto srcRect = img->rect().adjusted(offset, offset, -offset, -offset); p.drawImage(out.rect(), *img, srcRect); return out; } void AztecBarcode::paintCompactGrid(QImage *img) const { QPainter p(img); p.translate(img->width() / 2, img->height() / 2); // bullseye p.setPen(foregroundColor()); p.drawPoint(0, 0); p.drawRect(-2, -2, 4, 4); p.drawRect(-4, -4, 8, 8); // bullseye orientation marker p.drawRect(-5, -5, 1, 1); p.drawRect(5, -5, 0, 1); p.drawPoint(5, 4); } static const int aztecCompactLayerOffset[] = { 6, 4, 2, 0 }; void AztecBarcode::paintCompactData(QImage *img, const BitVector &data, int layerCount) const { QPainter p(img); p.setPen(foregroundColor()); auto it = data.begin(); for (int layer = layerCount - 1; layer >= 0; --layer) { const auto x1 = aztecCompactLayerOffset[layer]; const auto y1 = x1; const auto x2 = x1 + 1; const auto segmentLength = CompactMaxSize - 2 * y1 - 2; for (int rotation = 0; rotation < 4; ++rotation) { p.resetTransform(); p.translate(img->width() / 2, img->height() / 2); p.rotate(-90 * rotation); p.translate(-img->width() / 2, -img->height() / 2); for (int i = 0; it != data.end(); ++i, ++it) { const auto x = (i % 2 == 0) ? x1 : x2; auto y = i / 2 + y1; if (y >= y1 + segmentLength) { break; } if (*it) { p.drawPoint(x, y); } } } } } void AztecBarcode::paintCompactModeMessage(QImage *img, const BitVector &modeData) const { Q_ASSERT(modeData.size() == CompactModeMessageSize); QPainter p(img); p.setPen(foregroundColor()); auto it = modeData.begin(); for (int rotation = 0; rotation < 4; ++rotation) { p.resetTransform(); p.translate(img->width() / 2, img->height() / 2); p.rotate(90 * rotation); for (int i = -3; i <= 3; ++i) { if (*it) { p.drawPoint(i, -5); } ++it; } } } QImage AztecBarcode::cropAndScaleCompact(QImage *img, int layerCount, int size) { const auto offset = aztecCompactLayerOffset[layerCount - 1]; const auto minSize = CompactMaxSize - 2 * offset; setMinimumSize(QSizeF(minSize, minSize) * 4); // *4 taken from what QR does const int scale = std::max(1, size / minSize); QImage out(minSize * scale, minSize * scale, img->format()); QPainter p(&out); p.setRenderHint(QPainter::SmoothPixmapTransform, false); const auto srcRect = img->rect().adjusted(offset, offset, -offset, -offset); p.drawImage(out.rect(), *img, srcRect); return out; }