mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
--------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> Co-authored-by: ebbit1q <ebbit1q@gmail.com>
391 lines
13 KiB
C++
391 lines
13 KiB
C++
#include "pixel_map_generator.h"
|
|
|
|
#include <QApplication>
|
|
#include <QDomDocument>
|
|
#include <QFile>
|
|
#include <QPainter>
|
|
#include <QPalette>
|
|
#include <QSvgRenderer>
|
|
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
|
|
|
#define DEFAULT_COLOR_UNREGISTERED "#32c8ec";
|
|
#define DEFAULT_COLOR_REGISTERED "#5ed900";
|
|
#define DEFAULT_COLOR_MODERATOR_LEFT "#ffffff";
|
|
#define DEFAULT_COLOR_MODERATOR_RIGHT "#000000";
|
|
#define DEFAULT_COLOR_ADMIN "#ff2701";
|
|
|
|
/**
|
|
* Loads in an svg from file and scales it without affecting image quality.
|
|
*
|
|
* @param svgPath The path to the svg file, with file extension.
|
|
* @param size The desired size of the pixmap.
|
|
* @param expandOnly If true, then keep the size of the initial pixmap to at least the svg size.
|
|
*
|
|
* @return The svg loaded into a Pixmap with the given size, or an empty Pixmap if the loading failed.
|
|
*/
|
|
static QPixmap loadSvg(const QString &svgPath, const QSize &size, bool expandOnly = false)
|
|
{
|
|
QSvgRenderer svgRenderer(svgPath);
|
|
|
|
if (!svgRenderer.isValid()) {
|
|
qCWarning(PixelMapGeneratorLog) << "Failed to load" << svgPath;
|
|
return {};
|
|
}
|
|
|
|
// If expandOnly, make sure the pixmap is at least as large as the svg, so that we don't lose any detail.
|
|
// QIcon.pixmap(size) will automatically scale down the image, but it won't scale it up.
|
|
QSize pixmapSize = expandOnly ? svgRenderer.defaultSize().expandedTo(size) : size;
|
|
QPixmap pix(pixmapSize);
|
|
pix.fill(Qt::transparent);
|
|
|
|
QPainter pixPainter(&pix);
|
|
svgRenderer.render(&pixPainter);
|
|
|
|
// Converting the pixmap to a QIcon and back is the easiest way to scale down a svg without affecting image quality
|
|
if (expandOnly) {
|
|
return QIcon(pix).pixmap(size);
|
|
}
|
|
|
|
return pix;
|
|
}
|
|
|
|
/**
|
|
* Try to load path image from non-SVG formats, otherwise fall back to SVG.
|
|
* This is to allow custom themes to support non-SVG format type overrides, since SVG requires custom loading.
|
|
* @param path The path to the file, with no file extension. File formats will be automatically detected.
|
|
* @param size The desired size of the pixmap.
|
|
* @param expandOnly If true, then keep the size of the initial pixmap to at least the size (Only relevant if SVG).
|
|
*
|
|
* @return The loaded image into a Pixmap with the given size, or an empty Pixmap if the loading failed.
|
|
*/
|
|
static QPixmap tryLoadImage(const QString &path, const QSize &size, bool expandOnly = false)
|
|
{
|
|
const auto formats = {"png", "jpg"};
|
|
|
|
QPixmap returnPixmap;
|
|
for (const auto &format : formats) {
|
|
if (returnPixmap.load(path, format)) {
|
|
return returnPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
}
|
|
}
|
|
|
|
return loadSvg(path + ".svg", size, expandOnly);
|
|
}
|
|
|
|
QMap<QString, QPixmap> PhasePixmapGenerator::pmCache;
|
|
|
|
QPixmap PhasePixmapGenerator::generatePixmap(int height, QString name)
|
|
{
|
|
QString key = name + QString::number(height);
|
|
if (pmCache.contains(key))
|
|
return pmCache.value(key);
|
|
|
|
QPixmap pixmap = tryLoadImage("theme:phases/" + name, QSize(height, height));
|
|
|
|
pmCache.insert(key, pixmap);
|
|
return pixmap;
|
|
}
|
|
|
|
QMap<QString, QPixmap> CounterPixmapGenerator::pmCache;
|
|
|
|
QPixmap CounterPixmapGenerator::generatePixmap(int height, QString name, bool highlight)
|
|
{
|
|
// The colorless counter is named "x" by the server but the file is named "general.svg"
|
|
if (name == "x") {
|
|
name = "general";
|
|
}
|
|
|
|
if (highlight)
|
|
name.append("_highlight");
|
|
QString key = name + QString::number(height);
|
|
if (pmCache.contains(key))
|
|
return pmCache.value(key);
|
|
|
|
QPixmap pixmap = tryLoadImage("theme:counters/" + name, QSize(height, height));
|
|
|
|
// fall back to colorless counter if the name can't be found
|
|
if (pixmap.isNull()) {
|
|
name = "general";
|
|
if (highlight)
|
|
name.append("_highlight");
|
|
pixmap = tryLoadImage("theme:counters/" + name, QSize(height, height));
|
|
}
|
|
|
|
pmCache.insert(key, pixmap);
|
|
return pixmap;
|
|
}
|
|
|
|
QPixmap PingPixmapGenerator::generatePixmap(int size, int value, int max)
|
|
{
|
|
int key = size * 1000000 + max * 1000 + value;
|
|
if (pmCache.contains(key))
|
|
return pmCache.value(key);
|
|
|
|
QPixmap pixmap(size, size);
|
|
pixmap.fill(Qt::transparent);
|
|
QPainter painter(&pixmap);
|
|
QColor color;
|
|
if ((max == -1) || (value == -1))
|
|
color = Qt::black;
|
|
else
|
|
color.setHsv(120 * (1.0 - ((double)value / max)), 255, 255);
|
|
|
|
QRadialGradient g(QPointF((double)pixmap.width() / 2, (double)pixmap.height() / 2),
|
|
qMin(pixmap.width(), pixmap.height()) / 2.0);
|
|
g.setColorAt(0, color);
|
|
g.setColorAt(1, Qt::transparent);
|
|
painter.fillRect(0, 0, pixmap.width(), pixmap.height(), QBrush(g));
|
|
|
|
pmCache.insert(key, pixmap);
|
|
|
|
return pixmap;
|
|
}
|
|
|
|
QMap<int, QPixmap> PingPixmapGenerator::pmCache;
|
|
|
|
QPixmap CountryPixmapGenerator::generatePixmap(int height, const QString &countryCode)
|
|
{
|
|
if (countryCode.size() != 2)
|
|
return QPixmap();
|
|
QString key = countryCode + QString::number(height);
|
|
if (pmCache.contains(key))
|
|
return pmCache.value(key);
|
|
|
|
int width = height * 2;
|
|
QPixmap pixmap = tryLoadImage("theme:countries/" + countryCode.toLower(), QSize(width, height), true);
|
|
|
|
QPainter painter(&pixmap);
|
|
painter.setPen(Qt::black);
|
|
|
|
// width/height offset was determined through trial-and-error
|
|
#ifdef Q_OS_MACOS
|
|
painter.drawRect(0, 0, pixmap.width() / 2, pixmap.height() / 2);
|
|
#else
|
|
painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1);
|
|
#endif
|
|
|
|
pmCache.insert(key, pixmap);
|
|
return pixmap;
|
|
}
|
|
|
|
QMap<QString, QPixmap> CountryPixmapGenerator::pmCache;
|
|
|
|
/**
|
|
* Updates tags in the svg
|
|
*
|
|
* @param elem The svg
|
|
* @param tagName tag with attribute to update
|
|
* @param attrName attribute to be updated
|
|
* @param idName id that the tag has to match
|
|
* @param attrValue the value to update the attribute to
|
|
*/
|
|
static void setAttrRecur(QDomElement &elem,
|
|
const QString &tagName,
|
|
const QString &attrName,
|
|
const QString &idName,
|
|
const QString &attrValue);
|
|
|
|
void setAttrRecur(QDomElement &elem,
|
|
const QString &tagName,
|
|
const QString &attrName,
|
|
const QString &idName,
|
|
const QString &attrValue)
|
|
{
|
|
if (elem.tagName().compare(tagName) == 0) {
|
|
if (elem.attribute("id").compare(idName) == 0) {
|
|
elem.setAttribute(attrName, attrValue);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < elem.childNodes().count(); i++) {
|
|
if (!elem.childNodes().at(i).isElement()) {
|
|
continue;
|
|
}
|
|
auto docElem = elem.childNodes().at(i).toElement();
|
|
setAttrRecur(docElem, tagName, attrName, idName, attrValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads the usericon svg and fills in its colors.
|
|
* The image is kept as a QIcon to preserve the image quality.
|
|
*
|
|
* Call icon.pixmap(w, h) in order to convert this icon into a pixmap with the given dimensions.
|
|
* Avoid scaling the pixmap in other ways, as that destroys image quality.
|
|
*
|
|
* @param minSize If the dimensions of the source svg is smaller than this, then it will be scaled up to this size
|
|
*/
|
|
static QIcon loadAndColorSvg(const QString &iconPath,
|
|
const QString &colorLeft,
|
|
const std::optional<QString> &colorRight,
|
|
const int minSize)
|
|
{
|
|
QFile file(iconPath);
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
qCWarning(PixelMapGeneratorLog) << "Unable to open" << iconPath;
|
|
return {};
|
|
}
|
|
|
|
const auto &baData = file.readAll();
|
|
QDomDocument doc;
|
|
doc.setContent(baData);
|
|
|
|
auto docElem = doc.documentElement();
|
|
|
|
setAttrRecur(docElem, "path", "fill", "left", colorLeft);
|
|
if (colorRight.has_value()) {
|
|
setAttrRecur(docElem, "path", "fill", "right", colorRight.value());
|
|
}
|
|
|
|
QSvgRenderer svgRenderer(doc.toByteArray());
|
|
|
|
QPixmap pix(svgRenderer.defaultSize().expandedTo(QSize(minSize, minSize)));
|
|
pix.fill(Qt::transparent);
|
|
|
|
QPainter pixPainter(&pix);
|
|
svgRenderer.render(&pixPainter);
|
|
|
|
return QIcon(pix);
|
|
}
|
|
|
|
QPixmap UserLevelPixmapGenerator::generatePixmap(int height,
|
|
UserLevelFlags userLevel,
|
|
ServerInfo_User::PawnColorsOverride pawnColorsOverride,
|
|
bool isBuddy,
|
|
const QString &privLevel)
|
|
{
|
|
return generateIcon(height, userLevel, pawnColorsOverride, isBuddy, privLevel).pixmap(height, height);
|
|
}
|
|
|
|
QIcon UserLevelPixmapGenerator::generateIcon(int minHeight,
|
|
UserLevelFlags userLevel,
|
|
ServerInfo_User::PawnColorsOverride pawnColorsOverride,
|
|
bool isBuddy,
|
|
const QString &privLevel)
|
|
{
|
|
std::optional<QString> colorLeft = std::nullopt;
|
|
if (pawnColorsOverride.has_left_side()) {
|
|
colorLeft = QString::fromStdString(pawnColorsOverride.left_side());
|
|
}
|
|
|
|
std::optional<QString> colorRight = std::nullopt;
|
|
if (pawnColorsOverride.has_right_side()) {
|
|
colorRight = QString::fromStdString(pawnColorsOverride.right_side());
|
|
}
|
|
|
|
QString key = QString::number(minHeight * 10000) + ":" + static_cast<short>(userLevel) + ":" +
|
|
static_cast<short>(isBuddy) + ":" + privLevel.toLower() + ":" + colorLeft.value_or("") + ":" +
|
|
colorRight.value_or("");
|
|
|
|
if (iconCache.contains(key)) {
|
|
return iconCache.value(key);
|
|
}
|
|
|
|
QIcon icon = colorLeft.has_value()
|
|
? generateIconWithColorOverride(minHeight, isBuddy, userLevel, privLevel, colorLeft, colorRight)
|
|
: generateIconDefault(minHeight, userLevel, isBuddy, privLevel);
|
|
|
|
iconCache.insert(key, icon);
|
|
return icon;
|
|
}
|
|
|
|
static QString getIconType(const bool isBuddy, const UserLevelFlags &userLevelFlags, const QString &privLevel)
|
|
{
|
|
if (isBuddy) {
|
|
return "star";
|
|
}
|
|
|
|
if (userLevelFlags.testFlag(ServerInfo_User_UserLevelFlag_IsJudge)) {
|
|
return "pawn_judge";
|
|
}
|
|
|
|
if (!privLevel.isEmpty() && privLevel.toLower() != "none") {
|
|
return QString("pawn_%1").arg(privLevel.toLower());
|
|
}
|
|
|
|
return "pawn";
|
|
}
|
|
|
|
QIcon UserLevelPixmapGenerator::generateIconDefault(int height,
|
|
UserLevelFlags userLevel,
|
|
bool isBuddy,
|
|
const QString &privLevel)
|
|
{
|
|
const auto &iconType = getIconType(isBuddy, userLevel, privLevel);
|
|
|
|
QString arity = "single";
|
|
QString colorLeft;
|
|
std::optional<QString> colorRight = std::nullopt;
|
|
|
|
if (userLevel.testFlag(ServerInfo_User::IsAdmin)) {
|
|
colorLeft = DEFAULT_COLOR_ADMIN;
|
|
} else if (userLevel.testFlag(ServerInfo_User::IsModerator)) {
|
|
colorLeft = DEFAULT_COLOR_MODERATOR_LEFT;
|
|
colorRight = DEFAULT_COLOR_MODERATOR_RIGHT;
|
|
arity = "double";
|
|
} else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) {
|
|
colorLeft = DEFAULT_COLOR_REGISTERED;
|
|
} else {
|
|
colorLeft = DEFAULT_COLOR_UNREGISTERED;
|
|
}
|
|
|
|
const auto &iconPath = QString("theme:usericons/%1_%2.svg").arg(iconType, arity);
|
|
return loadAndColorSvg(iconPath, colorLeft, colorRight, height);
|
|
}
|
|
|
|
QIcon UserLevelPixmapGenerator::generateIconWithColorOverride(int height,
|
|
bool isBuddy,
|
|
const UserLevelFlags &userLevelFlags,
|
|
const QString &privLevel,
|
|
const std::optional<QString> &colorLeft,
|
|
const std::optional<QString> &colorRight)
|
|
{
|
|
const auto &iconType = getIconType(isBuddy, userLevelFlags, privLevel);
|
|
const QString &arity = colorRight.has_value() ? "double" : "single";
|
|
const auto &iconPath = QString("theme:usericons/%1_%2.svg").arg(iconType, arity);
|
|
return loadAndColorSvg(iconPath, colorLeft.value(), colorRight, height);
|
|
}
|
|
|
|
QMap<QString, QIcon> UserLevelPixmapGenerator::iconCache;
|
|
|
|
QPixmap LockPixmapGenerator::generatePixmap(int height)
|
|
{
|
|
|
|
int key = height;
|
|
if (pmCache.contains(key))
|
|
return pmCache.value(key);
|
|
|
|
QPixmap pixmap = tryLoadImage("theme:icons/lock", QSize(height, height), true);
|
|
pmCache.insert(key, pixmap);
|
|
return pixmap;
|
|
}
|
|
|
|
QMap<int, QPixmap> LockPixmapGenerator::pmCache;
|
|
|
|
QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded)
|
|
{
|
|
QString key = QString::number(expanded) + ":" + QString::number(height);
|
|
if (pmCache.contains(key))
|
|
return pmCache.value(key);
|
|
|
|
QString name = expanded ? "dropdown_expanded" : "dropdown_collapsed";
|
|
QPixmap pixmap = tryLoadImage("theme:icons/" + name, QSize(height, height), true);
|
|
|
|
pmCache.insert(key, pixmap);
|
|
return pixmap;
|
|
}
|
|
|
|
QMap<QString, QPixmap> DropdownIconPixmapGenerator::pmCache;
|
|
|
|
QPixmap loadColorAdjustedPixmap(const QString &name)
|
|
{
|
|
if (qApp->palette().windowText().color().lightness() > 200) {
|
|
QImage img(name);
|
|
img.invertPixels();
|
|
QPixmap result;
|
|
result.convertFromImage(img);
|
|
return result;
|
|
} else {
|
|
return QPixmap(name);
|
|
}
|
|
}
|