Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
245 lines
6.9 KiB
C++
245 lines
6.9 KiB
C++
// This file is part of Desktop App Toolkit,
|
|
// a set of libraries for developing nice desktop applications.
|
|
//
|
|
// For license and copyright information please follow this link:
|
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
|
//
|
|
#include "qr/qr_generate.h"
|
|
|
|
#include "base/assertion.h"
|
|
|
|
#if __has_include(<qrcodegen.hpp>)
|
|
#include <qrcodegen.hpp>
|
|
#else
|
|
#include <QrCode.hpp>
|
|
#endif
|
|
|
|
#include <QtGui/QPainter>
|
|
#include <string>
|
|
|
|
namespace Qr {
|
|
namespace {
|
|
|
|
using namespace qrcodegen;
|
|
|
|
[[nodiscard]] int ReplaceElements(const Data &data) {
|
|
const auto elements = [&] {
|
|
switch (data.redundancy) {
|
|
case Redundancy::Low: return data.size / 5;
|
|
case Redundancy::Medium: return data.size / 4;
|
|
case Redundancy::Quartile: return data.size / 3;
|
|
case Redundancy::High: return data.size / 2;
|
|
}
|
|
Unexpected("Redundancy value in Qr::ReplaceElements.");
|
|
}();
|
|
const auto close = (data.redundancy != Redundancy::Quartile);
|
|
const auto shift = (data.size - elements) % 2;
|
|
return elements + (close ? -1 : 1) * shift;
|
|
}
|
|
|
|
[[nodiscard]] QrCode::Ecc RedundancyToEcc(Redundancy redundancy) {
|
|
switch (redundancy) {
|
|
case Redundancy::Low: return QrCode::Ecc::LOW;
|
|
case Redundancy::Medium: return QrCode::Ecc::MEDIUM;
|
|
case Redundancy::Quartile: return QrCode::Ecc::QUARTILE;
|
|
case Redundancy::High: return QrCode::Ecc::HIGH;
|
|
}
|
|
Unexpected("Redundancy value in Qr::RedundancyToEcc.");
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Data Encode(const QString &text, Redundancy redundancy) {
|
|
Expects(!text.isEmpty());
|
|
|
|
auto result = Data();
|
|
result.redundancy = redundancy;
|
|
const auto utf8 = text.toStdString();
|
|
const auto qr = QrCode::encodeText(
|
|
utf8.c_str(),
|
|
RedundancyToEcc(redundancy));
|
|
result.size = qr.getSize();
|
|
Assert(result.size > 0);
|
|
|
|
result.values.reserve(result.size * result.size);
|
|
for (auto row = 0; row != result.size; ++row) {
|
|
for (auto column = 0; column != result.size; ++column) {
|
|
result.values.push_back(qr.getModule(row, column));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void PrepareForRound(QPainter &p) {
|
|
p.setRenderHints(QPainter::Antialiasing
|
|
| QPainter::SmoothPixmapTransform
|
|
| QPainter::TextAntialiasing);
|
|
p.setPen(Qt::NoPen);
|
|
}
|
|
|
|
QImage GenerateSingle(int size, QColor bg, QColor color) {
|
|
auto result = QImage(size, size, QImage::Format_ARGB32_Premultiplied);
|
|
result.fill(bg);
|
|
{
|
|
auto p = QPainter(&result);
|
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
PrepareForRound(p);
|
|
p.setBrush(color);
|
|
p.drawRoundedRect(
|
|
QRect{ 0, 0, size, size },
|
|
size / 2.,
|
|
size / 2.);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int ReplaceSize(const Data &data, int pixel) {
|
|
return ReplaceElements(data) * pixel;
|
|
}
|
|
|
|
QImage Generate(const Data &data, int pixel, QColor fg, QColor bg) {
|
|
Expects(data.size > 0);
|
|
Expects(data.values.size() == data.size * data.size);
|
|
|
|
const auto replaceElements = ReplaceElements(data);
|
|
const auto replaceFrom = (data.size - replaceElements) / 2;
|
|
const auto replaceTill = (data.size - replaceFrom);
|
|
const auto black = GenerateSingle(pixel, bg, fg);
|
|
const auto white = GenerateSingle(pixel, fg, bg);
|
|
const auto value = [&](int row, int column) {
|
|
return (row >= 0)
|
|
&& (row < data.size)
|
|
&& (column >= 0)
|
|
&& (column < data.size)
|
|
&& (row < replaceFrom
|
|
|| row >= replaceTill
|
|
|| column < replaceFrom
|
|
|| column >= replaceTill)
|
|
&& data.values[row * data.size + column];
|
|
};
|
|
const auto blackFull = [&](int row, int column) {
|
|
return (value(row - 1, column) && value(row + 1, column))
|
|
|| (value(row, column - 1) && value(row, column + 1));
|
|
};
|
|
const auto whiteCorner = [&](int row, int column, int dx, int dy) {
|
|
return !value(row + dy, column)
|
|
|| !value(row, column + dx)
|
|
|| !value(row + dy, column + dx);
|
|
};
|
|
const auto whiteFull = [&](int row, int column) {
|
|
return whiteCorner(row, column, -1, -1)
|
|
&& whiteCorner(row, column, 1, -1)
|
|
&& whiteCorner(row, column, 1, 1)
|
|
&& whiteCorner(row, column, -1, 1);
|
|
};
|
|
auto result = QImage(
|
|
data.size * pixel,
|
|
data.size * pixel,
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
result.fill(bg);
|
|
{
|
|
auto p = QPainter(&result);
|
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
const auto skip = pixel - pixel / 2;
|
|
const auto brect = [&](int x, int y, int width, int height) {
|
|
p.fillRect(x, y, width, height, fg);
|
|
};
|
|
const auto wrect = [&](int x, int y, int width, int height) {
|
|
p.fillRect(x, y, width, height, bg);
|
|
};
|
|
const auto large = [&](int x, int y) {
|
|
p.setBrush(fg);
|
|
p.drawRoundedRect(
|
|
QRect{ x, y, pixel * 7, pixel * 7 },
|
|
pixel * 2.,
|
|
pixel * 2.);
|
|
p.setBrush(bg);
|
|
p.drawRoundedRect(
|
|
QRect{ x + pixel, y + pixel, pixel * 5, pixel * 5 },
|
|
pixel * 1.5,
|
|
pixel * 1.5);
|
|
p.setBrush(fg);
|
|
p.drawRoundedRect(
|
|
QRect{ x + pixel * 2, y + pixel * 2, pixel * 3, pixel * 3 },
|
|
pixel,
|
|
pixel);
|
|
|
|
};
|
|
for (auto row = 0; row != data.size; ++row) {
|
|
for (auto column = 0; column != data.size; ++column) {
|
|
if ((row < 7 && (column < 7 || column >= data.size - 7))
|
|
|| (column < 7 && (row < 7 || row >= data.size - 7))) {
|
|
continue;
|
|
}
|
|
const auto x = column * pixel;
|
|
const auto y = row * pixel;
|
|
if (value(row, column)) {
|
|
if (blackFull(row, column)) {
|
|
brect(x, y, pixel, pixel);
|
|
} else {
|
|
p.drawImage(x, y, black);
|
|
if (value(row - 1, column)) {
|
|
brect(x, y, pixel, pixel / 2);
|
|
} else if (value(row + 1, column)) {
|
|
brect(x, y + skip, pixel, pixel / 2);
|
|
}
|
|
if (value(row, column - 1)) {
|
|
brect(x, y, pixel / 2, pixel);
|
|
} else if (value(row, column + 1)) {
|
|
brect(x + skip, y, pixel / 2, pixel);
|
|
}
|
|
}
|
|
} else if (whiteFull(row, column)) {
|
|
wrect(x, y, pixel, pixel);
|
|
} else {
|
|
p.drawImage(x, y, white);
|
|
if (whiteCorner(row, column, -1, -1)
|
|
&& whiteCorner(row, column, 1, -1)) {
|
|
wrect(x, y, pixel, pixel / 2);
|
|
} else if (whiteCorner(row, column, -1, 1)
|
|
&& whiteCorner(row, column, 1, 1)) {
|
|
wrect(x, y + skip, pixel, pixel / 2);
|
|
}
|
|
if (whiteCorner(row, column, -1, -1)
|
|
&& whiteCorner(row, column, -1, 1)) {
|
|
wrect(x, y, pixel / 2, pixel);
|
|
} else if (whiteCorner(row, column, 1, -1)
|
|
&& whiteCorner(row, column, 1, 1)) {
|
|
wrect(x + skip, y, pixel / 2, pixel);
|
|
}
|
|
if (whiteCorner(row, column, -1, -1)) {
|
|
wrect(x, y, pixel / 2, pixel / 2);
|
|
}
|
|
if (whiteCorner(row, column, 1, -1)) {
|
|
wrect(x + skip, y, pixel / 2, pixel / 2);
|
|
}
|
|
if (whiteCorner(row, column, 1, 1)) {
|
|
wrect(x + skip, y + skip, pixel / 2, pixel / 2);
|
|
}
|
|
if (whiteCorner(row, column, -1, 1)) {
|
|
wrect(x, y + skip, pixel / 2, pixel / 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PrepareForRound(p);
|
|
large(0, 0);
|
|
large((data.size - 7) * pixel, 0);
|
|
large(0, (data.size - 7) * pixel);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QImage ReplaceCenter(QImage qr, const QImage ¢er) {
|
|
{
|
|
auto p = QPainter(&qr);
|
|
const auto x = (qr.width() - center.width()) / 2;
|
|
const auto y = (qr.height() - center.height()) / 2;
|
|
p.drawImage(x, y, center);
|
|
}
|
|
return qr;
|
|
}
|
|
|
|
} // namespace Qr
|