Files
tdesktop/Telegram/lib_qr/qr/qr_generate.cpp
allhaileris afb81b8278
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
init
2026-02-16 15:50:16 +03:00

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 &center) {
{
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