Prison

aztecbarcode.cpp
1 /*
2  SPDX-FileCopyrightText: 2017 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: MIT
5 */
6 
7 #include "aztecbarcode.h"
8 #include "bitvector_p.h"
9 #include "prison_debug.h"
10 #include "reedsolomon_p.h"
11 
12 #include <QImage>
13 #include <QPainter>
14 
15 #include <algorithm>
16 #include <vector>
17 
18 // see https://en.wikipedia.org/wiki/Aztec_Code for encoding tables, magic numbers, etc
19 
20 using namespace Prison;
21 
22 enum {
23  FullMaxSize = 151,
24  FullRadius = 74,
25  FullGridInterval = 16,
26  FullModeMessageSize = 40,
27  FullLayerCount = 32,
28 
29  CompactMaxSize = 27,
30  CompactRadius = 13,
31  CompactModeMessageSize = 28,
32  CompactLayerCount = 4,
33 };
34 
35 AztecBarcode::AztecBarcode()
36  : AbstractBarcode(AbstractBarcode::TwoDimensions)
37 {
38 }
39 AztecBarcode::~AztecBarcode() = default;
40 
41 // encoding properties depending on layer count
42 struct aztec_layer_property_t {
43  uint8_t layer;
44  uint8_t codeWordSize;
45  uint16_t gf;
46 };
47 
48 static const aztec_layer_property_t aztec_layer_properties[] = {{2, 6, ReedSolomon::GF64},
49  {8, 8, ReedSolomon::GF256},
50  {22, 10, ReedSolomon::GF1024},
51  {32, 12, ReedSolomon::GF4096}};
52 
53 // amounts of bits in an Aztec code depending on layer count
54 static int aztecCompactDataBits(int layer)
55 {
56  return (88 + 16 * layer) * layer;
57 }
58 
59 static int aztecFullDataBits(int layer)
60 {
61  return (112 + 16 * layer) * layer;
62 }
63 
65 {
66  Q_UNUSED(size);
67  const auto inputData = aztecEncode(data().isEmpty() ? byteArrayData() : data().toLatin1());
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(backgroundColor());
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(backgroundColor());
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
158 enum Mode {
159  NoMode,
160  Upper,
161  Lower,
162  Mixed,
163  Punct,
164  Digit,
165  Binary,
166  MODE_COUNT,
167  Special,
168 };
169 
170 enum SpecialChar {
171  Space,
172  CarriageReturn,
173  Comma,
174  Dot,
175  SPECIAL_CHAR_COUNT,
176 };
177 
178 struct aztec_code_t {
179  uint8_t code;
180  uint8_t mode;
181 };
182 
183 static 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 };
248 Q_STATIC_ASSERT(sizeof(aztec_code_table) == 256);
249 
250 static 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 
261 static const int aztec_code_size[] = {0, 5, 5, 5, 5, 4, 8};
262 Q_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
265 static 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
275 static 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
285 static 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 
294 static 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 
305 static 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 
329 static 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 
370 static 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 
389 BitVector 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 
470 BitVector 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 
516 void 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(foregroundColor());
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(backgroundColor());
534  p.setPen(Qt::NoPen);
535  p.drawRect(-7, -7, 14, 14);
536 
537  // bullseye
538  p.setBrush(Qt::NoBrush);
539  p.setPen(foregroundColor());
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 
551 static 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 
555 void AztecBarcode::paintFullData(QImage *img, const BitVector &data, int layerCount) const
556 {
557  QPainter p(img);
558  p.setPen(foregroundColor());
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 
592 void AztecBarcode::paintFullModeMessage(QImage *img, const BitVector &modeData) const
593 {
594  Q_ASSERT(modeData.size() == FullModeMessageSize);
595 
596  QPainter p(img);
597  p.setPen(foregroundColor());
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 
617 QImage 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 
630 void 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(foregroundColor());
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 
647 static const int aztecCompactLayerOffset[] = {6, 4, 2, 0};
648 
649 void AztecBarcode::paintCompactData(QImage *img, const BitVector &data, int layerCount) const
650 {
651  QPainter p(img);
652  p.setPen(foregroundColor());
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 
681 void AztecBarcode::paintCompactModeMessage(QImage *img, const BitVector &modeData) const
682 {
683  Q_ASSERT(modeData.size() == CompactModeMessageSize);
684 
685  QPainter p(img);
686  p.setPen(foregroundColor());
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 
703 QImage 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 }
SmoothPixmapTransform
QString::iterator begin()
int height() const const
int size() const const
void fill(uint pixelValue)
QImage::Format format() const const
QRect rect() const const
const QList< QKeySequence > & begin()
QString::iterator end()
const QColor & backgroundColor() const
QImage paintImage(const QSizeF &size) override
Doing the actual painting of the image.
base class for barcode generators To add your own barcode generator, subclass this class and reimplem...
QByteArray byteArrayData() const
Binary data encoded in this barcode.
const QChar at(int position) const const
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
const QColor & foregroundColor() const
QString data() const
Textual content encoded in this barcode.
const QList< QKeySequence > & end()
int width() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Fri Dec 1 2023 04:09:13 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.