Prison

aztecbarcode.cpp
1/*
2 SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: MIT
5*/
6
7#include "aztecbarcode_p.h"
8#include "barcodeutil_p.h"
9#include "bitvector_p.h"
10#include "prison_debug.h"
11#include "reedsolomon_p.h"
12
13#include <QImage>
14#include <QPainter>
15
16#include <algorithm>
17#include <vector>
18
19// see https://en.wikipedia.org/wiki/Aztec_Code for encoding tables, magic numbers, etc
20
21using namespace Prison;
22
23enum {
24 FullMaxSize = 151,
25 FullRadius = 74,
26 FullGridInterval = 16,
27 FullModeMessageSize = 40,
28 FullLayerCount = 32,
29
30 CompactMaxSize = 27,
31 CompactRadius = 13,
32 CompactModeMessageSize = 28,
33 CompactLayerCount = 4,
34};
35
36AztecBarcode::AztecBarcode()
37 : AbstractBarcodePrivate(Barcode::TwoDimensions)
38{
39}
40AztecBarcode::~AztecBarcode() = default;
41
42// encoding properties depending on layer count
43struct aztec_layer_property_t {
44 uint8_t layer;
45 uint8_t codeWordSize;
46 uint16_t gf;
47};
48
49static const aztec_layer_property_t aztec_layer_properties[] = {{2, 6, ReedSolomon::GF64},
50 {8, 8, ReedSolomon::GF256},
51 {22, 10, ReedSolomon::GF1024},
52 {32, 12, ReedSolomon::GF4096}};
53
54// amounts of bits in an Aztec code depending on layer count
55static int aztecCompactDataBits(int layer)
56{
57 return (88 + 16 * layer) * layer;
58}
59
60static int aztecFullDataBits(int layer)
61{
62 return (112 + 16 * layer) * layer;
63}
64
65QImage AztecBarcode::paintImage()
66{
67 const auto inputData = aztecEncode(BarCodeUtil::asLatin1ByteArray(m_data));
68
69 int layerCount = 0;
70 int codewordCount = 0;
71 int availableBits = 0;
72 int stuffSize = 0; // extra bits added during bit stuffing, which might make us overrun the available size
73 bool compactMode = false;
74 BitVector encodedData;
75
76 do {
77 layerCount = 0;
78 // find the smallest layout we can put the data in, while leaving 23% for error correction
79 for (auto i = 1; i <= FullLayerCount; ++i) {
80 if (aztecFullDataBits(i) * 0.77 > (inputData.size() + stuffSize)) {
81 layerCount = i;
82 break;
83 }
84 }
85 for (auto i = 1; i <= CompactLayerCount; ++i) {
86 if (aztecCompactDataBits(i) * 0.77 > (inputData.size() + stuffSize)) {
87 layerCount = i;
88 compactMode = true;
89 break;
90 }
91 }
92 if (layerCount == 0) {
93 qCWarning(Log) << "data too large for Aztec code" << inputData.size();
94 return {};
95 }
96
97 // determine code word size
98 const auto propIt = std::lower_bound(aztec_layer_properties, aztec_layer_properties + 4, layerCount, [](const aztec_layer_property_t &lhs, int rhs) {
99 return lhs.layer < rhs;
100 });
101
102 // bit stuffing
103 auto stuffedData = bitStuffAndPad(inputData, (*propIt).codeWordSize);
104 stuffSize = stuffedData.size() - inputData.size();
105
106 availableBits = compactMode ? aztecCompactDataBits(layerCount) : aztecFullDataBits(layerCount);
107 codewordCount = stuffedData.size() / (*propIt).codeWordSize;
108 const auto rsWordCount = availableBits / (*propIt).codeWordSize - codewordCount;
109
110 // compute error correction
111 ReedSolomon rs((*propIt).gf, rsWordCount);
112 const auto rsData = rs.encode(stuffedData);
113
114 // pad with leading 0 bits to align to code word boundaries
115 encodedData.reserve(availableBits);
116 if (int diff = availableBits - stuffedData.size() - rsData.size()) {
117 encodedData.appendMSB(0, diff);
118 }
119 encodedData.append(stuffedData);
120 encodedData.append(rsData);
121
122 // try again in the rare case that we overrun the available bits due to bit stuffing and padding
123 } while (encodedData.size() > availableBits);
124
125 // determine mode message
126 BitVector modeMsg;
127 if (compactMode) {
128 modeMsg.appendMSB(layerCount - 1, 2);
129 modeMsg.appendMSB(codewordCount - 1, 6);
130 ReedSolomon rs(ReedSolomon::GF16, 5);
131 modeMsg.append(rs.encode(modeMsg));
132 } else {
133 modeMsg.appendMSB(layerCount - 1, 5);
134 modeMsg.appendMSB(codewordCount - 1, 11);
135 ReedSolomon rs(ReedSolomon::GF16, 6);
136 modeMsg.append(rs.encode(modeMsg));
137 }
138
139 // render the result
140 if (compactMode) {
141 QImage img(CompactMaxSize, CompactMaxSize, QImage::Format_RGB32);
142 img.fill(m_background);
143 paintCompactGrid(&img);
144 paintCompactData(&img, encodedData, layerCount);
145 paintCompactModeMessage(&img, modeMsg);
146 return cropAndScaleCompact(&img, layerCount);
147 } else {
148 QImage img(FullMaxSize, FullMaxSize, QImage::Format_RGB32);
149 img.fill(m_background);
150 paintFullGrid(&img);
151 paintFullData(&img, encodedData, layerCount);
152 paintFullModeMessage(&img, modeMsg);
153 return cropAndScaleFull(&img, layerCount);
154 }
155}
156
157// code points and encoding modes for each of the first 127 ASCII characters, the rest is encoded in Binary mode
158enum Mode {
159 NoMode,
160 Upper,
161 Lower,
162 Mixed,
163 Punct,
164 Digit,
165 Binary,
166 MODE_COUNT,
167 Special,
168};
169
170enum SpecialChar {
171 Space,
172 CarriageReturn,
173 Comma,
174 Dot,
175 SPECIAL_CHAR_COUNT,
176};
177
178struct aztec_code_t {
179 uint8_t code;
180 uint8_t mode;
181};
182
183static const aztec_code_t aztec_code_table[] = {
184 {0, Binary}, // 0
185 {2, Mixed}, {3, Mixed}, {4, Mixed},
186 {5, Mixed}, {6, Mixed}, {7, Mixed},
187 {8, Mixed}, // 7 BEL \a
188 {9, Mixed}, {10, Mixed}, {11, Mixed}, // 10 LF / ^J
189 {12, Mixed}, {13, Mixed}, {CarriageReturn, Special}, // 13 CR / ^M - but also 1 Punct
190 {14, Binary}, {15, Binary}, {16, Binary},
191 {17, Binary}, {18, Binary}, {19, Binary},
192 {20, Binary}, // 20 ^T
193 {21, Binary}, {22, Binary}, {23, Binary},
194 {24, Binary}, {25, Binary}, {26, Binary},
195 {15, Mixed}, // 27 ^[
196 {16, Mixed}, {17, Mixed}, {18, Mixed}, // 30 ^^
197 {19, Mixed}, {Space, Special}, // 32 SP
198 {6, Punct}, {7, Punct}, {8, Punct}, // 35 #
199 {9, Punct}, {10, Punct}, {11, Punct},
200 {12, Punct}, {13, Punct}, // 40 (
201 {14, Punct}, {15, Punct}, {16, Punct}, // 43 +
202 {Comma, Special}, // 44 ,
203 {18, Punct}, // 45 -
204 {Dot, Special}, // 46 .
205 {20, Punct}, // 47 /
206 {2, Digit}, // 48 0
207 {3, Digit}, {4, Digit}, {5, Digit},
208 {6, Digit}, {7, Digit}, {8, Digit},
209 {9, Digit}, {10, Digit}, {11, Digit}, // 57 9
210 {21, Punct}, // 58 :
211 {22, Punct}, // 59 ;
212 {23, Punct}, // 60 <
213 {24, Punct}, {25, Punct}, // 62 >
214 {26, Punct}, // 63 ?
215 {20, Mixed}, // 64 @
216 {2, Upper}, // 65 A
217 {3, Upper}, {4, Upper}, {5, Upper},
218 {6, Upper}, {7, Upper}, {8, Upper},
219 {9, Upper}, {10, Upper}, {11, Upper},
220 {12, Upper}, {13, Upper}, {14, Upper},
221 {15, Upper}, {16, Upper}, {17, Upper},
222 {18, Upper}, {19, Upper}, {20, Upper},
223 {21, Upper}, {22, Upper}, {23, Upper},
224 {24, Upper}, {25, Upper}, {26, Upper},
225 {27, Upper}, // 90 Z
226 {27, Punct}, // 91 [
227 {21, Mixed}, // 92 backslash
228 {28, Punct}, // 93 ]
229 {22, Mixed}, // 94 ^
230 {23, Mixed}, // 95 _
231 {24, Mixed}, // 96 `
232 {2, Lower}, // 97 a
233 {3, Lower}, {4, Lower}, {5, Lower},
234 {6, Lower}, {7, Lower}, {8, Lower},
235 {9, Lower}, {10, Lower}, {11, Lower},
236 {12, Lower}, {13, Lower}, {14, Lower},
237 {15, Lower}, {16, Lower}, {17, Lower},
238 {18, Lower}, {19, Lower}, {20, Lower},
239 {21, Lower}, {22, Lower}, {23, Lower},
240 {24, Lower}, {25, Lower}, {26, Lower},
241 {27, Lower}, // 122 z
242 {29, Punct}, // 123 {
243 {25, Mixed}, // 124 |
244 {30, Punct}, // 125 }
245 {26, Mixed}, // 126 ~
246 {27, Mixed} // 127 DEL ^?
247};
248Q_STATIC_ASSERT(sizeof(aztec_code_table) == 256);
249
250static const struct {
251 uint8_t c1;
252 uint8_t c2;
253 aztec_code_t sym;
254} aztec_code_double_symbols[] = {
255 {'\r', '\n', {2, Punct}}, // CR LF
256 {'.', ' ', {3, Punct}}, // . SP
257 {',', ' ', {4, Punct}}, // , SP
258 {':', ' ', {5, Punct}} // : SP
259};
260
261static const int aztec_code_size[] = {0, 5, 5, 5, 5, 4, 8};
262Q_STATIC_ASSERT(sizeof(aztec_code_size) / sizeof(int) == MODE_COUNT);
263
264// codes for ambiguous characters, ie. those that can be encoded in multiple modes
265static const aztec_code_t aztec_special_chars[SPECIAL_CHAR_COUNT][MODE_COUNT] = {
266 /* NoMode Upper Lower Mixed Punct Digit Binary */
267 {{0, NoMode}, {1, Upper}, {1, Lower}, {1, Mixed}, {1, Upper}, {1, Digit}, {0, NoMode}}, /* SP */
268 {{0, NoMode}, {1, Punct}, {1, Punct}, {14, Mixed}, {1, Punct}, {1, Punct}, {0, NoMode}}, /* CR */
269 {{0, NoMode}, {17, Punct}, {17, Punct}, {17, Punct}, {17, Punct}, {12, Digit}, {0, NoMode}}, /* Comma */
270 {{0, NoMode}, {19, Punct}, {19, Punct}, {19, Punct}, {19, Punct}, {13, Digit}, {0, NoMode}}, /* Dot */
271};
272
273// shift code table, source mode -> target mode
274// NoMode indicates shift is not available, use latch instead
275static const aztec_code_t aztec_shift_codes[MODE_COUNT - 1][MODE_COUNT - 1] = {
276 /* NoMode Upper Lower Mixed Punct Digit */
277 {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}},
278 {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}},
279 {{0, NoMode}, {28, Upper}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}},
280 {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}},
281 {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}},
282 {{0, NoMode}, {15, Upper}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}}};
283
284// latch code table, source mode -> target mode
285static const aztec_code_t aztec_latch_codes[MODE_COUNT - 1][MODE_COUNT] = {
286 /* NoMode Upper Lower Mixed Punct Digit Binary */
287 {{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}},
288 {{0, NoMode}, {0, NoMode}, {28, Lower}, {29, Mixed}, {29, Mixed}, {30, Digit}, {31, Binary}},
289 {{0, NoMode}, {30, Digit}, {0, NoMode}, {29, Mixed}, {29, Mixed}, {30, Digit}, {31, Binary}},
290 {{0, NoMode}, {29, Upper}, {28, Lower}, {0, NoMode}, {30, Punct}, {28, Lower}, {31, Binary}},
291 {{0, NoMode}, {31, Upper}, {31, Upper}, {31, Upper}, {0, NoMode}, {31, Upper}, {31, Upper}},
292 {{0, NoMode}, {14, Upper}, {14, Upper}, {14, Upper}, {14, Upper}, {0, NoMode}, {14, Upper}}};
293
294static Mode aztecCodeLatchTo(Mode currentMode, Mode targetMode, BitVector *v)
295{
296 if (currentMode == targetMode) {
297 return targetMode;
298 }
299 const auto latchCode = aztec_latch_codes[currentMode][targetMode];
300 qCDebug(Log) << "latch" << latchCode.code << aztec_code_size[currentMode];
301 v->appendMSB(latchCode.code, aztec_code_size[currentMode]);
302 return static_cast<Mode>(latchCode.mode);
303}
304
305static void aztecEncodeBinary(std::vector<aztec_code_t>::iterator &it, const std::vector<aztec_code_t>::iterator &end, BitVector *v)
306{
307 // determine length of the binary sequence
308 const auto binEndIt = std::find_if(it, end, [](aztec_code_t sym) {
309 return sym.mode != Binary;
310 });
311 const auto length = std::distance(it, binEndIt);
312
313 // write length field
314 qCDebug(Log) << "binary length" << length;
315 if (length < 32) {
316 v->appendMSB(length, 5);
317 } else {
318 v->appendMSB(0, 5);
319 v->appendMSB(length - 31, 11);
320 }
321
322 // write data
323 for (; it != binEndIt; ++it) {
324 qCDebug(Log) << "binary data" << (*it).code;
325 v->appendMSB((*it).code, 8);
326 }
327}
328
329static void aztecEncodeResolveAmbigious(Mode currentMode, const std::vector<aztec_code_t>::iterator &begin, const std::vector<aztec_code_t>::iterator &end)
330{
331 Q_ASSERT(begin != end);
332 Q_ASSERT(currentMode != (*begin).mode);
333 Q_ASSERT((*begin).mode == Special);
334
335 // forward search
336 auto it = begin;
337 for (; it != end && (*it).mode == Special; ++it) {
338 if (aztec_special_chars[(*it).code][currentMode].mode == currentMode) {
339 qCDebug(Log) << "special resolved to current mode by forward search";
340 (*it).mode = aztec_special_chars[(*it).code][currentMode].mode;
341 (*it).code = aztec_special_chars[(*it).code][currentMode].code;
342 }
343 }
344
345 // backward search
346 auto backIt = it;
347 while (std::distance(begin, backIt) >= 1 && it != end) {
348 --backIt;
349 if ((*backIt).mode == Special && aztec_special_chars[(*backIt).code][(*it).mode].mode == (*it).mode) {
350 qCDebug(Log) << "special resolved by backward search";
351 (*backIt).mode = aztec_special_chars[(*backIt).code][(*it).mode].mode;
352 (*backIt).code = aztec_special_chars[(*backIt).code][(*it).mode].code;
353 } else {
354 break;
355 }
356 }
357
358 // pick one if we still have an ambiguous symbol
359 if ((*begin).mode != Special) {
360 return;
361 }
362 (*begin).mode = aztec_special_chars[(*begin).code][currentMode].mode;
363 (*begin).code = aztec_special_chars[(*begin).code][currentMode].code;
364 it = begin + 1;
365 if (it != end && (*it).mode == Special) {
366 aztecEncodeResolveAmbigious(static_cast<Mode>((*begin).mode), it, end);
367 }
368}
369
370static Mode aztecNextMode(Mode currentMode, const std::vector<aztec_code_t>::iterator &nextSym, const std::vector<aztec_code_t>::iterator &end, bool &tryShift)
371{
372 Q_ASSERT(currentMode != (*nextSym).mode);
373 Q_ASSERT(nextSym != end);
374 Q_ASSERT((*nextSym).mode != Special);
375 auto it = nextSym;
376 ++it;
377 if (it != end && (*it).mode == Special) {
378 aztecEncodeResolveAmbigious(static_cast<Mode>((*nextSym).mode), it, end);
379 }
380
381 if ((it == end || (*it).mode == currentMode) && std::distance(nextSym, it) == 1) {
382 tryShift = true;
383 }
384
385 qCDebug(Log) << currentMode << (*nextSym).mode << tryShift << std::distance(nextSym, it);
386 return static_cast<Mode>((*nextSym).mode);
387}
388
389BitVector AztecBarcode::aztecEncode(const QByteArray &data) const
390{
391 // phase one: translate single and double chars to code points
392 std::vector<aztec_code_t> codes;
393 codes.reserve(data.size());
394 for (int i = 0; i < data.size(); ++i) {
395 const uint8_t c1 = data.at(i);
396 // double char codes
397 if (i < data.size() - 1) {
398 const uint8_t c2 = data.at(i + 1);
399 bool found = false;
400 for (const auto &dblCode : aztec_code_double_symbols) {
401 if (dblCode.c1 != c1 || dblCode.c2 != c2) {
402 continue;
403 }
404 codes.push_back(dblCode.sym);
405 ++i;
406 found = true;
407 }
408 if (found) {
409 continue;
410 }
411 }
412
413 // > 127 binary-only range
414 if (c1 > 127) {
415 codes.push_back({c1, Binary});
416 // encodable single ASCII character
417 } else {
418 codes.push_back(aztec_code_table[c1]);
419 }
420 }
421
422 // phase two: insert shift and latch codes, translate to bit stream
423 Mode currentMode = Upper;
424 BitVector result;
425 for (auto it = codes.begin(); it != codes.end();) {
426 if ((*it).mode == Binary) {
427 auto newMode = aztecCodeLatchTo(currentMode, Binary, &result);
428 while (newMode != Binary) {
429 currentMode = newMode;
430 newMode = aztecCodeLatchTo(currentMode, Binary, &result);
431 }
432 aztecEncodeBinary(it, codes.end(), &result);
433 continue;
434 }
435 // resolve special codes
436 if ((*it).mode == Special) {
437 aztecEncodeResolveAmbigious(currentMode, it, codes.end());
438 }
439
440 // deal with mode changes
441 Mode nextMode = currentMode;
442 if ((*it).mode != currentMode) {
443 bool tryShift = false;
444 const auto newMode = aztecNextMode(currentMode, it, codes.end(), tryShift);
445
446 // shift to new mode if desired and possible
447 if (tryShift && aztec_shift_codes[currentMode][newMode].mode != NoMode) {
448 qCDebug(Log) << "shift" << aztec_shift_codes[currentMode][newMode].code << aztec_code_size[currentMode];
449 result.appendMSB(aztec_shift_codes[currentMode][newMode].code, aztec_code_size[currentMode]);
450 currentMode = newMode;
451 }
452
453 // latch to new mode
454 while (currentMode != newMode && newMode != NoMode && currentMode != NoMode) {
455 currentMode = aztecCodeLatchTo(currentMode, newMode, &result);
456 nextMode = currentMode;
457 }
458 }
459
460 qCDebug(Log) << (*it).code << aztec_code_size[currentMode];
461 result.appendMSB((*it).code, aztec_code_size[currentMode]);
462 ++it;
463
464 currentMode = nextMode;
465 }
466
467 return result;
468}
469
470BitVector AztecBarcode::bitStuffAndPad(const BitVector &input, int codeWordSize) const
471{
472 BitVector res;
473 res.reserve(input.size());
474
475 // bit stuff codewords with leading codeWordSize 0/1 bits
476 int i = 0;
477 while (i < input.size() - (codeWordSize - 1)) {
478 int v = input.valueAtMSB(i, codeWordSize - 1);
479 res.appendMSB(v, codeWordSize - 1);
480 i += codeWordSize - 1;
481 if (v == 0) {
482 res.appendBit(true);
483 } else if (v == (1 << (codeWordSize - 1)) - 1) {
484 res.appendBit(false);
485 } else {
486 res.appendBit(input.at(i++));
487 }
488 }
489 while (i < input.size()) {
490 res.appendBit(input.at(i++));
491 }
492
493 // check if we are code word aligned already
494 const auto trailingBits = res.size() % codeWordSize;
495 if (!trailingBits) { // nothing to pad
496 return res;
497 }
498
499 // pad with ones to nearest code word boundary
500 // last bit has to be zero if we'd otherwise would have all ones though
501 bool allOnes = true;
502 for (int i = res.size() - trailingBits; i < res.size(); ++i) {
503 allOnes &= res.at(i);
504 }
505 while (res.size() % codeWordSize) {
506 if ((res.size() % codeWordSize) == (codeWordSize - 1)) {
507 res.appendBit(allOnes ? false : true);
508 } else {
509 res.appendBit(true);
510 }
511 }
512
513 return res;
514}
515
516void AztecBarcode::paintFullGrid(QImage *img) const
517{
518 QPainter p(img);
519 p.translate(img->width() / 2, img->height() / 2);
520
521 // alignment grids
522 QPen pen(m_foreground);
523 pen.setDashPattern({1, 1});
524 p.setPen(pen);
525 for (int i = 0; i < img->width() / 2; i += FullGridInterval) {
526 p.drawLine(-i, -FullRadius, -i, FullRadius);
527 p.drawLine(i, -FullRadius, i, FullRadius);
528 p.drawLine(-FullRadius, -i, FullRadius, -i);
529 p.drawLine(-FullRadius, i, FullRadius, i);
530 }
531
532 // bullseye background
533 p.setBrush(m_background);
534 p.setPen(Qt::NoPen);
535 p.drawRect(-7, -7, 14, 14);
536
537 // bullseye
538 p.setBrush(Qt::NoBrush);
539 p.setPen(m_foreground);
540 p.drawPoint(0, 0);
541 p.drawRect(-2, -2, 4, 4);
542 p.drawRect(-4, -4, 8, 8);
543 p.drawRect(-6, -6, 12, 12);
544
545 // bullseye orientation marker
546 p.drawRect(-7, -7, 1, 1);
547 p.drawRect(7, -7, 0, 1);
548 p.drawPoint(7, 6);
549}
550
551static const int aztecFullLayerOffset[] = {
552 // 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
553 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};
554
555void AztecBarcode::paintFullData(QImage *img, const BitVector &data, int layerCount) const
556{
557 QPainter p(img);
558 p.setPen(m_foreground);
559
560 auto it = data.begin();
561 for (int layer = layerCount - 1; layer >= 0; --layer) {
562 const auto x1 = aztecFullLayerOffset[layer];
563 const auto y1 = x1;
564 const auto gridInMiddle = (x1 - FullRadius) % FullGridInterval == 0;
565 const auto x2 = gridInMiddle ? x1 + 2 : x1 + 1;
566 const auto segmentLength = FullMaxSize - 2 * y1 - 2 - (gridInMiddle ? 1 : 0);
567
568 for (int rotation = 0; rotation < 4; ++rotation) {
569 p.resetTransform();
570 p.translate(img->width() / 2, img->height() / 2);
571 p.rotate(-90 * rotation);
572 p.translate(-img->width() / 2, -img->height() / 2);
573
574 for (int i = 0; it != data.end(); ++i, ++it) {
575 const auto x = (i % 2 == 0) ? x1 : x2;
576 auto y = i / 2 + y1;
577 if (((y - FullRadius - 1) % FullGridInterval) == 0) { // skip grid lines
578 ++y;
579 i += 2;
580 }
581 if (y >= y1 + segmentLength) {
582 break;
583 }
584 if (*it) {
585 p.drawPoint(x, y);
586 }
587 }
588 }
589 }
590}
591
592void AztecBarcode::paintFullModeMessage(QImage *img, const BitVector &modeData) const
593{
594 Q_ASSERT(modeData.size() == FullModeMessageSize);
595
596 QPainter p(img);
597 p.setPen(m_foreground);
598
599 auto it = modeData.begin();
600 for (int rotation = 0; rotation < 4; ++rotation) {
601 p.resetTransform();
602 p.translate(img->width() / 2, img->height() / 2);
603 p.rotate(90 * rotation);
604
605 for (int i = -5; i <= 5; ++i) {
606 if (i == 0) { // skip grid line
607 continue;
608 }
609 if (*it) {
610 p.drawPoint(i, -7);
611 }
612 ++it;
613 }
614 }
615}
616
617QImage AztecBarcode::cropAndScaleFull(QImage *img, int layerCount)
618{
619 const auto offset = aztecFullLayerOffset[layerCount - 1];
620 const auto minSize = FullMaxSize - 2 * offset;
621
622 QImage out(minSize, minSize, img->format());
623 QPainter p(&out);
624 p.setRenderHint(QPainter::SmoothPixmapTransform, false);
625 const auto srcRect = img->rect().adjusted(offset, offset, -offset, -offset);
626 p.drawImage(out.rect(), *img, srcRect);
627 return out;
628}
629
630void AztecBarcode::paintCompactGrid(QImage *img) const
631{
632 QPainter p(img);
633 p.translate(img->width() / 2, img->height() / 2);
634
635 // bullseye
636 p.setPen(m_foreground);
637 p.drawPoint(0, 0);
638 p.drawRect(-2, -2, 4, 4);
639 p.drawRect(-4, -4, 8, 8);
640
641 // bullseye orientation marker
642 p.drawRect(-5, -5, 1, 1);
643 p.drawRect(5, -5, 0, 1);
644 p.drawPoint(5, 4);
645}
646
647static const int aztecCompactLayerOffset[] = {6, 4, 2, 0};
648
649void AztecBarcode::paintCompactData(QImage *img, const BitVector &data, int layerCount) const
650{
651 QPainter p(img);
652 p.setPen(m_foreground);
653
654 auto it = data.begin();
655 for (int layer = layerCount - 1; layer >= 0; --layer) {
656 const auto x1 = aztecCompactLayerOffset[layer];
657 const auto y1 = x1;
658 const auto x2 = x1 + 1;
659 const auto segmentLength = CompactMaxSize - 2 * y1 - 2;
660
661 for (int rotation = 0; rotation < 4; ++rotation) {
662 p.resetTransform();
663 p.translate(img->width() / 2, img->height() / 2);
664 p.rotate(-90 * rotation);
665 p.translate(-img->width() / 2, -img->height() / 2);
666
667 for (int i = 0; it != data.end(); ++i, ++it) {
668 const auto x = (i % 2 == 0) ? x1 : x2;
669 auto y = i / 2 + y1;
670 if (y >= y1 + segmentLength) {
671 break;
672 }
673 if (*it) {
674 p.drawPoint(x, y);
675 }
676 }
677 }
678 }
679}
680
681void AztecBarcode::paintCompactModeMessage(QImage *img, const BitVector &modeData) const
682{
683 Q_ASSERT(modeData.size() == CompactModeMessageSize);
684
685 QPainter p(img);
686 p.setPen(m_foreground);
687
688 auto it = modeData.begin();
689 for (int rotation = 0; rotation < 4; ++rotation) {
690 p.resetTransform();
691 p.translate(img->width() / 2, img->height() / 2);
692 p.rotate(90 * rotation);
693
694 for (int i = -3; i <= 3; ++i) {
695 if (*it) {
696 p.drawPoint(i, -5);
697 }
698 ++it;
699 }
700 }
701}
702
703QImage AztecBarcode::cropAndScaleCompact(QImage *img, int layerCount)
704{
705 const auto offset = aztecCompactLayerOffset[layerCount - 1];
706 const auto minSize = CompactMaxSize - 2 * offset;
707
708 QImage out(minSize, minSize, img->format());
709 QPainter p(&out);
710 p.setRenderHint(QPainter::SmoothPixmapTransform, false);
711 const auto srcRect = img->rect().adjusted(offset, offset, -offset, -offset);
712 p.drawImage(out.rect(), *img, srcRect);
713 return out;
714}
A barcode generator for a fixed barcode format.
Definition barcode.h:40
const QList< QKeySequence > & begin()
const QList< QKeySequence > & end()
Provides classes and methods for generating barcodes.
Definition barcode.h:24
char at(qsizetype i) const const
qsizetype size() const const
Format format() const const
int height() const const
QRect rect() const const
int width() const const
SmoothPixmapTransform
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:16:07 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.