File indexing completed on 2024-05-12 04:01:31

0001 /*
0002     SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: MIT
0005 */
0006 
0007 #include "aztecbarcode_p.h"
0008 #include "barcodeutil_p.h"
0009 #include "bitvector_p.h"
0010 #include "prison_debug.h"
0011 #include "reedsolomon_p.h"
0012 
0013 #include <QImage>
0014 #include <QPainter>
0015 
0016 #include <algorithm>
0017 #include <vector>
0018 
0019 // see https://en.wikipedia.org/wiki/Aztec_Code for encoding tables, magic numbers, etc
0020 
0021 using namespace Prison;
0022 
0023 enum {
0024     FullMaxSize = 151,
0025     FullRadius = 74,
0026     FullGridInterval = 16,
0027     FullModeMessageSize = 40,
0028     FullLayerCount = 32,
0029 
0030     CompactMaxSize = 27,
0031     CompactRadius = 13,
0032     CompactModeMessageSize = 28,
0033     CompactLayerCount = 4,
0034 };
0035 
0036 AztecBarcode::AztecBarcode()
0037     : AbstractBarcodePrivate(Barcode::TwoDimensions)
0038 {
0039 }
0040 AztecBarcode::~AztecBarcode() = default;
0041 
0042 // encoding properties depending on layer count
0043 struct aztec_layer_property_t {
0044     uint8_t layer;
0045     uint8_t codeWordSize;
0046     uint16_t gf;
0047 };
0048 
0049 static const aztec_layer_property_t aztec_layer_properties[] = {{2, 6, ReedSolomon::GF64},
0050                                                                 {8, 8, ReedSolomon::GF256},
0051                                                                 {22, 10, ReedSolomon::GF1024},
0052                                                                 {32, 12, ReedSolomon::GF4096}};
0053 
0054 // amounts of bits in an Aztec code depending on layer count
0055 static int aztecCompactDataBits(int layer)
0056 {
0057     return (88 + 16 * layer) * layer;
0058 }
0059 
0060 static int aztecFullDataBits(int layer)
0061 {
0062     return (112 + 16 * layer) * layer;
0063 }
0064 
0065 QImage AztecBarcode::paintImage()
0066 {
0067     const auto inputData = aztecEncode(BarCodeUtil::asLatin1ByteArray(m_data));
0068 
0069     int layerCount = 0;
0070     int codewordCount = 0;
0071     int availableBits = 0;
0072     int stuffSize = 0; // extra bits added during bit stuffing, which might make us overrun the available size
0073     bool compactMode = false;
0074     BitVector encodedData;
0075 
0076     do {
0077         layerCount = 0;
0078         // find the smallest layout we can put the data in, while leaving 23% for error correction
0079         for (auto i = 1; i <= FullLayerCount; ++i) {
0080             if (aztecFullDataBits(i) * 0.77 > (inputData.size() + stuffSize)) {
0081                 layerCount = i;
0082                 break;
0083             }
0084         }
0085         for (auto i = 1; i <= CompactLayerCount; ++i) {
0086             if (aztecCompactDataBits(i) * 0.77 > (inputData.size() + stuffSize)) {
0087                 layerCount = i;
0088                 compactMode = true;
0089                 break;
0090             }
0091         }
0092         if (layerCount == 0) {
0093             qCWarning(Log) << "data too large for Aztec code" << inputData.size();
0094             return {};
0095         }
0096 
0097         // determine code word size
0098         const auto propIt = std::lower_bound(aztec_layer_properties, aztec_layer_properties + 4, layerCount, [](const aztec_layer_property_t &lhs, int rhs) {
0099             return lhs.layer < rhs;
0100         });
0101 
0102         // bit stuffing
0103         auto stuffedData = bitStuffAndPad(inputData, (*propIt).codeWordSize);
0104         stuffSize = stuffedData.size() - inputData.size();
0105 
0106         availableBits = compactMode ? aztecCompactDataBits(layerCount) : aztecFullDataBits(layerCount);
0107         codewordCount = stuffedData.size() / (*propIt).codeWordSize;
0108         const auto rsWordCount = availableBits / (*propIt).codeWordSize - codewordCount;
0109 
0110         // compute error correction
0111         ReedSolomon rs((*propIt).gf, rsWordCount);
0112         const auto rsData = rs.encode(stuffedData);
0113 
0114         // pad with leading 0 bits to align to code word boundaries
0115         encodedData.reserve(availableBits);
0116         if (int diff = availableBits - stuffedData.size() - rsData.size()) {
0117             encodedData.appendMSB(0, diff);
0118         }
0119         encodedData.append(stuffedData);
0120         encodedData.append(rsData);
0121 
0122         // try again in the rare case that we overrun the available bits due to bit stuffing and padding
0123     } while (encodedData.size() > availableBits);
0124 
0125     // determine mode message
0126     BitVector modeMsg;
0127     if (compactMode) {
0128         modeMsg.appendMSB(layerCount - 1, 2);
0129         modeMsg.appendMSB(codewordCount - 1, 6);
0130         ReedSolomon rs(ReedSolomon::GF16, 5);
0131         modeMsg.append(rs.encode(modeMsg));
0132     } else {
0133         modeMsg.appendMSB(layerCount - 1, 5);
0134         modeMsg.appendMSB(codewordCount - 1, 11);
0135         ReedSolomon rs(ReedSolomon::GF16, 6);
0136         modeMsg.append(rs.encode(modeMsg));
0137     }
0138 
0139     // render the result
0140     if (compactMode) {
0141         QImage img(CompactMaxSize, CompactMaxSize, QImage::Format_RGB32);
0142         img.fill(m_background);
0143         paintCompactGrid(&img);
0144         paintCompactData(&img, encodedData, layerCount);
0145         paintCompactModeMessage(&img, modeMsg);
0146         return cropAndScaleCompact(&img, layerCount);
0147     } else {
0148         QImage img(FullMaxSize, FullMaxSize, QImage::Format_RGB32);
0149         img.fill(m_background);
0150         paintFullGrid(&img);
0151         paintFullData(&img, encodedData, layerCount);
0152         paintFullModeMessage(&img, modeMsg);
0153         return cropAndScaleFull(&img, layerCount);
0154     }
0155 }
0156 
0157 // code points and encoding modes for each of the first 127 ASCII characters, the rest is encoded in Binary mode
0158 enum Mode {
0159     NoMode,
0160     Upper,
0161     Lower,
0162     Mixed,
0163     Punct,
0164     Digit,
0165     Binary,
0166     MODE_COUNT,
0167     Special,
0168 };
0169 
0170 enum SpecialChar {
0171     Space,
0172     CarriageReturn,
0173     Comma,
0174     Dot,
0175     SPECIAL_CHAR_COUNT,
0176 };
0177 
0178 struct aztec_code_t {
0179     uint8_t code;
0180     uint8_t mode;
0181 };
0182 
0183 static const aztec_code_t aztec_code_table[] = {
0184     {0, Binary}, // 0
0185     {2, Mixed},       {3, Mixed},       {4, Mixed},
0186     {5, Mixed},       {6, Mixed},       {7, Mixed},
0187     {8, Mixed}, // 7 BEL \a
0188     {9, Mixed},       {10, Mixed},      {11, Mixed}, // 10 LF / ^J
0189     {12, Mixed},      {13, Mixed},      {CarriageReturn, Special}, // 13 CR / ^M - but also 1 Punct
0190     {14, Binary},     {15, Binary},     {16, Binary},
0191     {17, Binary},     {18, Binary},     {19, Binary},
0192     {20, Binary}, // 20 ^T
0193     {21, Binary},     {22, Binary},     {23, Binary},
0194     {24, Binary},     {25, Binary},     {26, Binary},
0195     {15, Mixed}, // 27 ^[
0196     {16, Mixed},      {17, Mixed},      {18, Mixed}, // 30 ^^
0197     {19, Mixed},      {Space, Special}, // 32 SP
0198     {6, Punct},       {7, Punct},       {8, Punct}, // 35 #
0199     {9, Punct},       {10, Punct},      {11, Punct},
0200     {12, Punct},      {13, Punct}, // 40 (
0201     {14, Punct},      {15, Punct},      {16, Punct}, // 43 +
0202     {Comma, Special}, // 44 ,
0203     {18, Punct}, // 45 -
0204     {Dot, Special}, // 46 .
0205     {20, Punct}, // 47 /
0206     {2, Digit}, // 48 0
0207     {3, Digit},       {4, Digit},       {5, Digit},
0208     {6, Digit},       {7, Digit},       {8, Digit},
0209     {9, Digit},       {10, Digit},      {11, Digit}, // 57 9
0210     {21, Punct}, // 58 :
0211     {22, Punct}, // 59 ;
0212     {23, Punct}, // 60 <
0213     {24, Punct},      {25, Punct}, // 62 >
0214     {26, Punct}, // 63 ?
0215     {20, Mixed}, // 64 @
0216     {2, Upper}, // 65 A
0217     {3, Upper},       {4, Upper},       {5, Upper},
0218     {6, Upper},       {7, Upper},       {8, Upper},
0219     {9, Upper},       {10, Upper},      {11, Upper},
0220     {12, Upper},      {13, Upper},      {14, Upper},
0221     {15, Upper},      {16, Upper},      {17, Upper},
0222     {18, Upper},      {19, Upper},      {20, Upper},
0223     {21, Upper},      {22, Upper},      {23, Upper},
0224     {24, Upper},      {25, Upper},      {26, Upper},
0225     {27, Upper}, // 90 Z
0226     {27, Punct}, // 91 [
0227     {21, Mixed}, // 92 backslash
0228     {28, Punct}, // 93 ]
0229     {22, Mixed}, // 94 ^
0230     {23, Mixed}, // 95 _
0231     {24, Mixed}, // 96 `
0232     {2, Lower}, // 97 a
0233     {3, Lower},       {4, Lower},       {5, Lower},
0234     {6, Lower},       {7, Lower},       {8, Lower},
0235     {9, Lower},       {10, Lower},      {11, Lower},
0236     {12, Lower},      {13, Lower},      {14, Lower},
0237     {15, Lower},      {16, Lower},      {17, Lower},
0238     {18, Lower},      {19, Lower},      {20, Lower},
0239     {21, Lower},      {22, Lower},      {23, Lower},
0240     {24, Lower},      {25, Lower},      {26, Lower},
0241     {27, Lower}, // 122 z
0242     {29, Punct}, // 123 {
0243     {25, Mixed}, // 124 |
0244     {30, Punct}, // 125 }
0245     {26, Mixed}, // 126 ~
0246     {27, Mixed} // 127 DEL ^?
0247 };
0248 Q_STATIC_ASSERT(sizeof(aztec_code_table) == 256);
0249 
0250 static const struct {
0251     uint8_t c1;
0252     uint8_t c2;
0253     aztec_code_t sym;
0254 } aztec_code_double_symbols[] = {
0255     {'\r', '\n', {2, Punct}}, // CR LF
0256     {'.', ' ', {3, Punct}}, // . SP
0257     {',', ' ', {4, Punct}}, // , SP
0258     {':', ' ', {5, Punct}} // : SP
0259 };
0260 
0261 static const int aztec_code_size[] = {0, 5, 5, 5, 5, 4, 8};
0262 Q_STATIC_ASSERT(sizeof(aztec_code_size) / sizeof(int) == MODE_COUNT);
0263 
0264 // codes for ambiguous characters, ie. those that can be encoded in multiple modes
0265 static const aztec_code_t aztec_special_chars[SPECIAL_CHAR_COUNT][MODE_COUNT] = {
0266     /*   NoMode      Upper      Lower        Mixed        Punct      Digit     Binary  */
0267     {{0, NoMode}, {1, Upper}, {1, Lower}, {1, Mixed}, {1, Upper}, {1, Digit}, {0, NoMode}}, /* SP */
0268     {{0, NoMode}, {1, Punct}, {1, Punct}, {14, Mixed}, {1, Punct}, {1, Punct}, {0, NoMode}}, /* CR */
0269     {{0, NoMode}, {17, Punct}, {17, Punct}, {17, Punct}, {17, Punct}, {12, Digit}, {0, NoMode}}, /* Comma */
0270     {{0, NoMode}, {19, Punct}, {19, Punct}, {19, Punct}, {19, Punct}, {13, Digit}, {0, NoMode}}, /* Dot */
0271 };
0272 
0273 // shift code table, source mode -> target mode
0274 // NoMode indicates shift is not available, use latch instead
0275 static const aztec_code_t aztec_shift_codes[MODE_COUNT - 1][MODE_COUNT - 1] = {
0276     /*     NoMode         Upper           Lower         Mixed          Punct          Digit   */
0277     {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}},
0278     {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}},
0279     {{0, NoMode}, {28, Upper}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}},
0280     {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}},
0281     {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}},
0282     {{0, NoMode}, {15, Upper}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}}};
0283 
0284 // latch code table, source mode -> target mode
0285 static const aztec_code_t aztec_latch_codes[MODE_COUNT - 1][MODE_COUNT] = {
0286     /*     NoMode         Upper           Lower         Mixed          Punct          Digit          Binary */
0287     {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}},
0288     {{0, NoMode}, {0, NoMode}, {28, Lower}, {29, Mixed}, {29, Mixed}, {30, Digit}, {31, Binary}},
0289     {{0, NoMode}, {30, Digit}, {0, NoMode}, {29, Mixed}, {29, Mixed}, {30, Digit}, {31, Binary}},
0290     {{0, NoMode}, {29, Upper}, {28, Lower}, {0, NoMode}, {30, Punct}, {28, Lower}, {31, Binary}},
0291     {{0, NoMode}, {31, Upper}, {31, Upper}, {31, Upper}, {0, NoMode}, {31, Upper}, {31, Upper}},
0292     {{0, NoMode}, {14, Upper}, {14, Upper}, {14, Upper}, {14, Upper}, {0, NoMode}, {14, Upper}}};
0293 
0294 static Mode aztecCodeLatchTo(Mode currentMode, Mode targetMode, BitVector *v)
0295 {
0296     if (currentMode == targetMode) {
0297         return targetMode;
0298     }
0299     const auto latchCode = aztec_latch_codes[currentMode][targetMode];
0300     qCDebug(Log) << "latch" << latchCode.code << aztec_code_size[currentMode];
0301     v->appendMSB(latchCode.code, aztec_code_size[currentMode]);
0302     return static_cast<Mode>(latchCode.mode);
0303 }
0304 
0305 static void aztecEncodeBinary(std::vector<aztec_code_t>::iterator &it, const std::vector<aztec_code_t>::iterator &end, BitVector *v)
0306 {
0307     // determine length of the binary sequence
0308     const auto binEndIt = std::find_if(it, end, [](aztec_code_t sym) {
0309         return sym.mode != Binary;
0310     });
0311     const auto length = std::distance(it, binEndIt);
0312 
0313     // write length field
0314     qCDebug(Log) << "binary length" << length;
0315     if (length < 32) {
0316         v->appendMSB(length, 5);
0317     } else {
0318         v->appendMSB(0, 5);
0319         v->appendMSB(length - 31, 11);
0320     }
0321 
0322     // write data
0323     for (; it != binEndIt; ++it) {
0324         qCDebug(Log) << "binary data" << (*it).code;
0325         v->appendMSB((*it).code, 8);
0326     }
0327 }
0328 
0329 static void aztecEncodeResolveAmbigious(Mode currentMode, const std::vector<aztec_code_t>::iterator &begin, const std::vector<aztec_code_t>::iterator &end)
0330 {
0331     Q_ASSERT(begin != end);
0332     Q_ASSERT(currentMode != (*begin).mode);
0333     Q_ASSERT((*begin).mode == Special);
0334 
0335     // forward search
0336     auto it = begin;
0337     for (; it != end && (*it).mode == Special; ++it) {
0338         if (aztec_special_chars[(*it).code][currentMode].mode == currentMode) {
0339             qCDebug(Log) << "special resolved to current mode by forward search";
0340             (*it).mode = aztec_special_chars[(*it).code][currentMode].mode;
0341             (*it).code = aztec_special_chars[(*it).code][currentMode].code;
0342         }
0343     }
0344 
0345     // backward search
0346     auto backIt = it;
0347     while (std::distance(begin, backIt) >= 1 && it != end) {
0348         --backIt;
0349         if ((*backIt).mode == Special && aztec_special_chars[(*backIt).code][(*it).mode].mode == (*it).mode) {
0350             qCDebug(Log) << "special resolved by backward search";
0351             (*backIt).mode = aztec_special_chars[(*backIt).code][(*it).mode].mode;
0352             (*backIt).code = aztec_special_chars[(*backIt).code][(*it).mode].code;
0353         } else {
0354             break;
0355         }
0356     }
0357 
0358     // pick one if we still have an ambiguous symbol
0359     if ((*begin).mode != Special) {
0360         return;
0361     }
0362     (*begin).mode = aztec_special_chars[(*begin).code][currentMode].mode;
0363     (*begin).code = aztec_special_chars[(*begin).code][currentMode].code;
0364     it = begin + 1;
0365     if (it != end && (*it).mode == Special) {
0366         aztecEncodeResolveAmbigious(static_cast<Mode>((*begin).mode), it, end);
0367     }
0368 }
0369 
0370 static Mode aztecNextMode(Mode currentMode, const std::vector<aztec_code_t>::iterator &nextSym, const std::vector<aztec_code_t>::iterator &end, bool &tryShift)
0371 {
0372     Q_ASSERT(currentMode != (*nextSym).mode);
0373     Q_ASSERT(nextSym != end);
0374     Q_ASSERT((*nextSym).mode != Special);
0375     auto it = nextSym;
0376     ++it;
0377     if (it != end && (*it).mode == Special) {
0378         aztecEncodeResolveAmbigious(static_cast<Mode>((*nextSym).mode), it, end);
0379     }
0380 
0381     if ((it == end || (*it).mode == currentMode) && std::distance(nextSym, it) == 1) {
0382         tryShift = true;
0383     }
0384 
0385     qCDebug(Log) << currentMode << (*nextSym).mode << tryShift << std::distance(nextSym, it);
0386     return static_cast<Mode>((*nextSym).mode);
0387 }
0388 
0389 BitVector AztecBarcode::aztecEncode(const QByteArray &data) const
0390 {
0391     // phase one: translate single and double chars to code points
0392     std::vector<aztec_code_t> codes;
0393     codes.reserve(data.size());
0394     for (int i = 0; i < data.size(); ++i) {
0395         const uint8_t c1 = data.at(i);
0396         // double char codes
0397         if (i < data.size() - 1) {
0398             const uint8_t c2 = data.at(i + 1);
0399             bool found = false;
0400             for (const auto &dblCode : aztec_code_double_symbols) {
0401                 if (dblCode.c1 != c1 || dblCode.c2 != c2) {
0402                     continue;
0403                 }
0404                 codes.push_back(dblCode.sym);
0405                 ++i;
0406                 found = true;
0407             }
0408             if (found) {
0409                 continue;
0410             }
0411         }
0412 
0413         // > 127 binary-only range
0414         if (c1 > 127) {
0415             codes.push_back({c1, Binary});
0416             // encodable single ASCII character
0417         } else {
0418             codes.push_back(aztec_code_table[c1]);
0419         }
0420     }
0421 
0422     // phase two: insert shift and latch codes, translate to bit stream
0423     Mode currentMode = Upper;
0424     BitVector result;
0425     for (auto it = codes.begin(); it != codes.end();) {
0426         if ((*it).mode == Binary) {
0427             auto newMode = aztecCodeLatchTo(currentMode, Binary, &result);
0428             while (newMode != Binary) {
0429                 currentMode = newMode;
0430                 newMode = aztecCodeLatchTo(currentMode, Binary, &result);
0431             }
0432             aztecEncodeBinary(it, codes.end(), &result);
0433             continue;
0434         }
0435         // resolve special codes
0436         if ((*it).mode == Special) {
0437             aztecEncodeResolveAmbigious(currentMode, it, codes.end());
0438         }
0439 
0440         // deal with mode changes
0441         Mode nextMode = currentMode;
0442         if ((*it).mode != currentMode) {
0443             bool tryShift = false;
0444             const auto newMode = aztecNextMode(currentMode, it, codes.end(), tryShift);
0445 
0446             // shift to new mode if desired and possible
0447             if (tryShift && aztec_shift_codes[currentMode][newMode].mode != NoMode) {
0448                 qCDebug(Log) << "shift" << aztec_shift_codes[currentMode][newMode].code << aztec_code_size[currentMode];
0449                 result.appendMSB(aztec_shift_codes[currentMode][newMode].code, aztec_code_size[currentMode]);
0450                 currentMode = newMode;
0451             }
0452 
0453             // latch to new mode
0454             while (currentMode != newMode && newMode != NoMode && currentMode != NoMode) {
0455                 currentMode = aztecCodeLatchTo(currentMode, newMode, &result);
0456                 nextMode = currentMode;
0457             }
0458         }
0459 
0460         qCDebug(Log) << (*it).code << aztec_code_size[currentMode];
0461         result.appendMSB((*it).code, aztec_code_size[currentMode]);
0462         ++it;
0463 
0464         currentMode = nextMode;
0465     }
0466 
0467     return result;
0468 }
0469 
0470 BitVector AztecBarcode::bitStuffAndPad(const BitVector &input, int codeWordSize) const
0471 {
0472     BitVector res;
0473     res.reserve(input.size());
0474 
0475     // bit stuff codewords with leading codeWordSize 0/1 bits
0476     int i = 0;
0477     while (i < input.size() - (codeWordSize - 1)) {
0478         int v = input.valueAtMSB(i, codeWordSize - 1);
0479         res.appendMSB(v, codeWordSize - 1);
0480         i += codeWordSize - 1;
0481         if (v == 0) {
0482             res.appendBit(true);
0483         } else if (v == (1 << (codeWordSize - 1)) - 1) {
0484             res.appendBit(false);
0485         } else {
0486             res.appendBit(input.at(i++));
0487         }
0488     }
0489     while (i < input.size()) {
0490         res.appendBit(input.at(i++));
0491     }
0492 
0493     // check if we are code word aligned already
0494     const auto trailingBits = res.size() % codeWordSize;
0495     if (!trailingBits) { // nothing to pad
0496         return res;
0497     }
0498 
0499     // pad with ones to nearest code word boundary
0500     // last bit has to be zero if we'd otherwise would have all ones though
0501     bool allOnes = true;
0502     for (int i = res.size() - trailingBits; i < res.size(); ++i) {
0503         allOnes &= res.at(i);
0504     }
0505     while (res.size() % codeWordSize) {
0506         if ((res.size() % codeWordSize) == (codeWordSize - 1)) {
0507             res.appendBit(allOnes ? false : true);
0508         } else {
0509             res.appendBit(true);
0510         }
0511     }
0512 
0513     return res;
0514 }
0515 
0516 void AztecBarcode::paintFullGrid(QImage *img) const
0517 {
0518     QPainter p(img);
0519     p.translate(img->width() / 2, img->height() / 2);
0520 
0521     // alignment grids
0522     QPen pen(m_foreground);
0523     pen.setDashPattern({1, 1});
0524     p.setPen(pen);
0525     for (int i = 0; i < img->width() / 2; i += FullGridInterval) {
0526         p.drawLine(-i, -FullRadius, -i, FullRadius);
0527         p.drawLine(i, -FullRadius, i, FullRadius);
0528         p.drawLine(-FullRadius, -i, FullRadius, -i);
0529         p.drawLine(-FullRadius, i, FullRadius, i);
0530     }
0531 
0532     // bullseye background
0533     p.setBrush(m_background);
0534     p.setPen(Qt::NoPen);
0535     p.drawRect(-7, -7, 14, 14);
0536 
0537     // bullseye
0538     p.setBrush(Qt::NoBrush);
0539     p.setPen(m_foreground);
0540     p.drawPoint(0, 0);
0541     p.drawRect(-2, -2, 4, 4);
0542     p.drawRect(-4, -4, 8, 8);
0543     p.drawRect(-6, -6, 12, 12);
0544 
0545     // bullseye orientation marker
0546     p.drawRect(-7, -7, 1, 1);
0547     p.drawRect(7, -7, 0, 1);
0548     p.drawPoint(7, 6);
0549 }
0550 
0551 static const int aztecFullLayerOffset[] = {
0552     //   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
0553     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};
0554 
0555 void AztecBarcode::paintFullData(QImage *img, const BitVector &data, int layerCount) const
0556 {
0557     QPainter p(img);
0558     p.setPen(m_foreground);
0559 
0560     auto it = data.begin();
0561     for (int layer = layerCount - 1; layer >= 0; --layer) {
0562         const auto x1 = aztecFullLayerOffset[layer];
0563         const auto y1 = x1;
0564         const auto gridInMiddle = (x1 - FullRadius) % FullGridInterval == 0;
0565         const auto x2 = gridInMiddle ? x1 + 2 : x1 + 1;
0566         const auto segmentLength = FullMaxSize - 2 * y1 - 2 - (gridInMiddle ? 1 : 0);
0567 
0568         for (int rotation = 0; rotation < 4; ++rotation) {
0569             p.resetTransform();
0570             p.translate(img->width() / 2, img->height() / 2);
0571             p.rotate(-90 * rotation);
0572             p.translate(-img->width() / 2, -img->height() / 2);
0573 
0574             for (int i = 0; it != data.end(); ++i, ++it) {
0575                 const auto x = (i % 2 == 0) ? x1 : x2;
0576                 auto y = i / 2 + y1;
0577                 if (((y - FullRadius - 1) % FullGridInterval) == 0) { // skip grid lines
0578                     ++y;
0579                     i += 2;
0580                 }
0581                 if (y >= y1 + segmentLength) {
0582                     break;
0583                 }
0584                 if (*it) {
0585                     p.drawPoint(x, y);
0586                 }
0587             }
0588         }
0589     }
0590 }
0591 
0592 void AztecBarcode::paintFullModeMessage(QImage *img, const BitVector &modeData) const
0593 {
0594     Q_ASSERT(modeData.size() == FullModeMessageSize);
0595 
0596     QPainter p(img);
0597     p.setPen(m_foreground);
0598 
0599     auto it = modeData.begin();
0600     for (int rotation = 0; rotation < 4; ++rotation) {
0601         p.resetTransform();
0602         p.translate(img->width() / 2, img->height() / 2);
0603         p.rotate(90 * rotation);
0604 
0605         for (int i = -5; i <= 5; ++i) {
0606             if (i == 0) { // skip grid line
0607                 continue;
0608             }
0609             if (*it) {
0610                 p.drawPoint(i, -7);
0611             }
0612             ++it;
0613         }
0614     }
0615 }
0616 
0617 QImage AztecBarcode::cropAndScaleFull(QImage *img, int layerCount)
0618 {
0619     const auto offset = aztecFullLayerOffset[layerCount - 1];
0620     const auto minSize = FullMaxSize - 2 * offset;
0621 
0622     QImage out(minSize, minSize, img->format());
0623     QPainter p(&out);
0624     p.setRenderHint(QPainter::SmoothPixmapTransform, false);
0625     const auto srcRect = img->rect().adjusted(offset, offset, -offset, -offset);
0626     p.drawImage(out.rect(), *img, srcRect);
0627     return out;
0628 }
0629 
0630 void AztecBarcode::paintCompactGrid(QImage *img) const
0631 {
0632     QPainter p(img);
0633     p.translate(img->width() / 2, img->height() / 2);
0634 
0635     // bullseye
0636     p.setPen(m_foreground);
0637     p.drawPoint(0, 0);
0638     p.drawRect(-2, -2, 4, 4);
0639     p.drawRect(-4, -4, 8, 8);
0640 
0641     // bullseye orientation marker
0642     p.drawRect(-5, -5, 1, 1);
0643     p.drawRect(5, -5, 0, 1);
0644     p.drawPoint(5, 4);
0645 }
0646 
0647 static const int aztecCompactLayerOffset[] = {6, 4, 2, 0};
0648 
0649 void AztecBarcode::paintCompactData(QImage *img, const BitVector &data, int layerCount) const
0650 {
0651     QPainter p(img);
0652     p.setPen(m_foreground);
0653 
0654     auto it = data.begin();
0655     for (int layer = layerCount - 1; layer >= 0; --layer) {
0656         const auto x1 = aztecCompactLayerOffset[layer];
0657         const auto y1 = x1;
0658         const auto x2 = x1 + 1;
0659         const auto segmentLength = CompactMaxSize - 2 * y1 - 2;
0660 
0661         for (int rotation = 0; rotation < 4; ++rotation) {
0662             p.resetTransform();
0663             p.translate(img->width() / 2, img->height() / 2);
0664             p.rotate(-90 * rotation);
0665             p.translate(-img->width() / 2, -img->height() / 2);
0666 
0667             for (int i = 0; it != data.end(); ++i, ++it) {
0668                 const auto x = (i % 2 == 0) ? x1 : x2;
0669                 auto y = i / 2 + y1;
0670                 if (y >= y1 + segmentLength) {
0671                     break;
0672                 }
0673                 if (*it) {
0674                     p.drawPoint(x, y);
0675                 }
0676             }
0677         }
0678     }
0679 }
0680 
0681 void AztecBarcode::paintCompactModeMessage(QImage *img, const BitVector &modeData) const
0682 {
0683     Q_ASSERT(modeData.size() == CompactModeMessageSize);
0684 
0685     QPainter p(img);
0686     p.setPen(m_foreground);
0687 
0688     auto it = modeData.begin();
0689     for (int rotation = 0; rotation < 4; ++rotation) {
0690         p.resetTransform();
0691         p.translate(img->width() / 2, img->height() / 2);
0692         p.rotate(90 * rotation);
0693 
0694         for (int i = -3; i <= 3; ++i) {
0695             if (*it) {
0696                 p.drawPoint(i, -5);
0697             }
0698             ++it;
0699         }
0700     }
0701 }
0702 
0703 QImage AztecBarcode::cropAndScaleCompact(QImage *img, int layerCount)
0704 {
0705     const auto offset = aztecCompactLayerOffset[layerCount - 1];
0706     const auto minSize = CompactMaxSize - 2 * offset;
0707 
0708     QImage out(minSize, minSize, img->format());
0709     QPainter p(&out);
0710     p.setRenderHint(QPainter::SmoothPixmapTransform, false);
0711     const auto srcRect = img->rect().adjusted(offset, offset, -offset, -offset);
0712     p.drawImage(out.rect(), *img, srcRect);
0713     return out;
0714 }