File indexing completed on 2024-05-12 15:49:08
0001 /* 0002 SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: MIT 0005 */ 0006 0007 #include "aztecbarcode.h" 0008 #include "bitvector_p.h" 0009 #include "prison_debug.h" 0010 #include "reedsolomon_p.h" 0011 0012 #include <QImage> 0013 #include <QPainter> 0014 0015 #include <algorithm> 0016 #include <vector> 0017 0018 // see https://en.wikipedia.org/wiki/Aztec_Code for encoding tables, magic numbers, etc 0019 0020 using namespace Prison; 0021 0022 enum { 0023 FullMaxSize = 151, 0024 FullRadius = 74, 0025 FullGridInterval = 16, 0026 FullModeMessageSize = 40, 0027 FullLayerCount = 32, 0028 0029 CompactMaxSize = 27, 0030 CompactRadius = 13, 0031 CompactModeMessageSize = 28, 0032 CompactLayerCount = 4, 0033 }; 0034 0035 AztecBarcode::AztecBarcode() 0036 : AbstractBarcode(AbstractBarcode::TwoDimensions) 0037 { 0038 } 0039 AztecBarcode::~AztecBarcode() = default; 0040 0041 // encoding properties depending on layer count 0042 struct aztec_layer_property_t { 0043 uint8_t layer; 0044 uint8_t codeWordSize; 0045 uint16_t gf; 0046 }; 0047 0048 static const aztec_layer_property_t aztec_layer_properties[] = {{2, 6, ReedSolomon::GF64}, 0049 {8, 8, ReedSolomon::GF256}, 0050 {22, 10, ReedSolomon::GF1024}, 0051 {32, 12, ReedSolomon::GF4096}}; 0052 0053 // amounts of bits in an Aztec code depending on layer count 0054 static int aztecCompactDataBits(int layer) 0055 { 0056 return (88 + 16 * layer) * layer; 0057 } 0058 0059 static int aztecFullDataBits(int layer) 0060 { 0061 return (112 + 16 * layer) * layer; 0062 } 0063 0064 QImage AztecBarcode::paintImage(const QSizeF &size) 0065 { 0066 Q_UNUSED(size); 0067 const auto inputData = aztecEncode(data().isEmpty() ? byteArrayData() : data().toLatin1()); 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(backgroundColor()); 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(backgroundColor()); 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(foregroundColor()); 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(backgroundColor()); 0534 p.setPen(Qt::NoPen); 0535 p.drawRect(-7, -7, 14, 14); 0536 0537 // bullseye 0538 p.setBrush(Qt::NoBrush); 0539 p.setPen(foregroundColor()); 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(foregroundColor()); 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(foregroundColor()); 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(foregroundColor()); 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(foregroundColor()); 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(foregroundColor()); 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 }