qr_generate.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "qr/qr_generate.h"
  8. #include "base/assertion.h"
  9. #if __has_include(<qrcodegen.hpp>)
  10. #include <qrcodegen.hpp>
  11. #else
  12. #include <QrCode.hpp>
  13. #endif
  14. #include <QtGui/QPainter>
  15. #include <string>
  16. namespace Qr {
  17. namespace {
  18. using namespace qrcodegen;
  19. [[nodiscard]] int ReplaceElements(const Data &data) {
  20. const auto elements = [&] {
  21. switch (data.redundancy) {
  22. case Redundancy::Low: return data.size / 5;
  23. case Redundancy::Medium: return data.size / 4;
  24. case Redundancy::Quartile: return data.size / 3;
  25. case Redundancy::High: return data.size / 2;
  26. }
  27. Unexpected("Redundancy value in Qr::ReplaceElements.");
  28. }();
  29. const auto close = (data.redundancy != Redundancy::Quartile);
  30. const auto shift = (data.size - elements) % 2;
  31. return elements + (close ? -1 : 1) * shift;
  32. }
  33. [[nodiscard]] QrCode::Ecc RedundancyToEcc(Redundancy redundancy) {
  34. switch (redundancy) {
  35. case Redundancy::Low: return QrCode::Ecc::LOW;
  36. case Redundancy::Medium: return QrCode::Ecc::MEDIUM;
  37. case Redundancy::Quartile: return QrCode::Ecc::QUARTILE;
  38. case Redundancy::High: return QrCode::Ecc::HIGH;
  39. }
  40. Unexpected("Redundancy value in Qr::RedundancyToEcc.");
  41. }
  42. } // namespace
  43. Data Encode(const QString &text, Redundancy redundancy) {
  44. Expects(!text.isEmpty());
  45. auto result = Data();
  46. result.redundancy = redundancy;
  47. const auto utf8 = text.toStdString();
  48. const auto qr = QrCode::encodeText(
  49. utf8.c_str(),
  50. RedundancyToEcc(redundancy));
  51. result.size = qr.getSize();
  52. Assert(result.size > 0);
  53. result.values.reserve(result.size * result.size);
  54. for (auto row = 0; row != result.size; ++row) {
  55. for (auto column = 0; column != result.size; ++column) {
  56. result.values.push_back(qr.getModule(row, column));
  57. }
  58. }
  59. return result;
  60. }
  61. void PrepareForRound(QPainter &p) {
  62. p.setRenderHints(QPainter::Antialiasing
  63. | QPainter::SmoothPixmapTransform
  64. | QPainter::TextAntialiasing);
  65. p.setPen(Qt::NoPen);
  66. }
  67. QImage GenerateSingle(int size, QColor bg, QColor color) {
  68. auto result = QImage(size, size, QImage::Format_ARGB32_Premultiplied);
  69. result.fill(bg);
  70. {
  71. auto p = QPainter(&result);
  72. p.setCompositionMode(QPainter::CompositionMode_Source);
  73. PrepareForRound(p);
  74. p.setBrush(color);
  75. p.drawRoundedRect(
  76. QRect{ 0, 0, size, size },
  77. size / 2.,
  78. size / 2.);
  79. }
  80. return result;
  81. }
  82. int ReplaceSize(const Data &data, int pixel) {
  83. return ReplaceElements(data) * pixel;
  84. }
  85. QImage Generate(const Data &data, int pixel, QColor fg, QColor bg) {
  86. Expects(data.size > 0);
  87. Expects(data.values.size() == data.size * data.size);
  88. const auto replaceElements = ReplaceElements(data);
  89. const auto replaceFrom = (data.size - replaceElements) / 2;
  90. const auto replaceTill = (data.size - replaceFrom);
  91. const auto black = GenerateSingle(pixel, bg, fg);
  92. const auto white = GenerateSingle(pixel, fg, bg);
  93. const auto value = [&](int row, int column) {
  94. return (row >= 0)
  95. && (row < data.size)
  96. && (column >= 0)
  97. && (column < data.size)
  98. && (row < replaceFrom
  99. || row >= replaceTill
  100. || column < replaceFrom
  101. || column >= replaceTill)
  102. && data.values[row * data.size + column];
  103. };
  104. const auto blackFull = [&](int row, int column) {
  105. return (value(row - 1, column) && value(row + 1, column))
  106. || (value(row, column - 1) && value(row, column + 1));
  107. };
  108. const auto whiteCorner = [&](int row, int column, int dx, int dy) {
  109. return !value(row + dy, column)
  110. || !value(row, column + dx)
  111. || !value(row + dy, column + dx);
  112. };
  113. const auto whiteFull = [&](int row, int column) {
  114. return whiteCorner(row, column, -1, -1)
  115. && whiteCorner(row, column, 1, -1)
  116. && whiteCorner(row, column, 1, 1)
  117. && whiteCorner(row, column, -1, 1);
  118. };
  119. auto result = QImage(
  120. data.size * pixel,
  121. data.size * pixel,
  122. QImage::Format_ARGB32_Premultiplied);
  123. result.fill(bg);
  124. {
  125. auto p = QPainter(&result);
  126. p.setCompositionMode(QPainter::CompositionMode_Source);
  127. const auto skip = pixel - pixel / 2;
  128. const auto brect = [&](int x, int y, int width, int height) {
  129. p.fillRect(x, y, width, height, fg);
  130. };
  131. const auto wrect = [&](int x, int y, int width, int height) {
  132. p.fillRect(x, y, width, height, bg);
  133. };
  134. const auto large = [&](int x, int y) {
  135. p.setBrush(fg);
  136. p.drawRoundedRect(
  137. QRect{ x, y, pixel * 7, pixel * 7 },
  138. pixel * 2.,
  139. pixel * 2.);
  140. p.setBrush(bg);
  141. p.drawRoundedRect(
  142. QRect{ x + pixel, y + pixel, pixel * 5, pixel * 5 },
  143. pixel * 1.5,
  144. pixel * 1.5);
  145. p.setBrush(fg);
  146. p.drawRoundedRect(
  147. QRect{ x + pixel * 2, y + pixel * 2, pixel * 3, pixel * 3 },
  148. pixel,
  149. pixel);
  150. };
  151. for (auto row = 0; row != data.size; ++row) {
  152. for (auto column = 0; column != data.size; ++column) {
  153. if ((row < 7 && (column < 7 || column >= data.size - 7))
  154. || (column < 7 && (row < 7 || row >= data.size - 7))) {
  155. continue;
  156. }
  157. const auto x = column * pixel;
  158. const auto y = row * pixel;
  159. if (value(row, column)) {
  160. if (blackFull(row, column)) {
  161. brect(x, y, pixel, pixel);
  162. } else {
  163. p.drawImage(x, y, black);
  164. if (value(row - 1, column)) {
  165. brect(x, y, pixel, pixel / 2);
  166. } else if (value(row + 1, column)) {
  167. brect(x, y + skip, pixel, pixel / 2);
  168. }
  169. if (value(row, column - 1)) {
  170. brect(x, y, pixel / 2, pixel);
  171. } else if (value(row, column + 1)) {
  172. brect(x + skip, y, pixel / 2, pixel);
  173. }
  174. }
  175. } else if (whiteFull(row, column)) {
  176. wrect(x, y, pixel, pixel);
  177. } else {
  178. p.drawImage(x, y, white);
  179. if (whiteCorner(row, column, -1, -1)
  180. && whiteCorner(row, column, 1, -1)) {
  181. wrect(x, y, pixel, pixel / 2);
  182. } else if (whiteCorner(row, column, -1, 1)
  183. && whiteCorner(row, column, 1, 1)) {
  184. wrect(x, y + skip, pixel, pixel / 2);
  185. }
  186. if (whiteCorner(row, column, -1, -1)
  187. && whiteCorner(row, column, -1, 1)) {
  188. wrect(x, y, pixel / 2, pixel);
  189. } else if (whiteCorner(row, column, 1, -1)
  190. && whiteCorner(row, column, 1, 1)) {
  191. wrect(x + skip, y, pixel / 2, pixel);
  192. }
  193. if (whiteCorner(row, column, -1, -1)) {
  194. wrect(x, y, pixel / 2, pixel / 2);
  195. }
  196. if (whiteCorner(row, column, 1, -1)) {
  197. wrect(x + skip, y, pixel / 2, pixel / 2);
  198. }
  199. if (whiteCorner(row, column, 1, 1)) {
  200. wrect(x + skip, y + skip, pixel / 2, pixel / 2);
  201. }
  202. if (whiteCorner(row, column, -1, 1)) {
  203. wrect(x, y + skip, pixel / 2, pixel / 2);
  204. }
  205. }
  206. }
  207. }
  208. PrepareForRound(p);
  209. large(0, 0);
  210. large((data.size - 7) * pixel, 0);
  211. large(0, (data.size - 7) * pixel);
  212. }
  213. return result;
  214. }
  215. QImage ReplaceCenter(QImage qr, const QImage &center) {
  216. {
  217. auto p = QPainter(&qr);
  218. const auto x = (qr.width() - center.width()) / 2;
  219. const auto y = (qr.height() - center.height()) / 2;
  220. p.drawImage(x, y, center);
  221. }
  222. return qr;
  223. }
  224. } // namespace Qr