#include "BuildingIconCache.h" #include #include #include #include #include namespace { // Chip background color for a build button the player cannot afford // (REQ-UI-BUILD-DISABLED). Part of the icon rather than of any widget's palette, so it // lives with the recoloring step. const char* const kGreyFill = "fill=\"#5f636e\""; } // namespace BuildingIconCache::BuildingIconCache(const QString& iconDir) : m_iconDir(iconDir) { } const QByteArray& BuildingIconCache::getSvg(const std::string& iconName) { const std::map::const_iterator cached = m_svgByName.find(iconName); if (cached != m_svgByName.end()) { return cached->second; } // An absent or unreadable file caches an empty byte array so it is not retried; // a missing icon is not an error (REQ-UI-BUILD-ICON). QByteArray svg; QFile file(m_iconDir + "/" + QString::fromStdString(iconName) + ".svg"); if (file.open(QIODevice::ReadOnly)) { svg = file.readAll(); } return m_svgByName.emplace(iconName, std::move(svg)).first->second; } const QByteArray& BuildingIconCache::getGreySvg(const std::string& iconName) { const std::map::const_iterator cached = m_greySvgByName.find(iconName); if (cached != m_greySvgByName.end()) { return cached->second; } // Recolor only the chip background: the first "#rrggbb" fill in the file is the // rounded background rect; the white glyph uses fill="none" and is left alone. const QByteArray& svg = getSvg(iconName); QByteArray greyed = svg; if (!svg.isEmpty()) { QString text = QString::fromUtf8(svg); static const QRegularExpression fillPattern( QStringLiteral("fill=\"#[0-9a-fA-F]{6}\"")); const QRegularExpressionMatch match = fillPattern.match(text); if (match.hasMatch()) { text.replace(match.capturedStart(), match.capturedLength(), QLatin1String(kGreyFill)); } greyed = text.toUtf8(); } return m_greySvgByName.emplace(iconName, std::move(greyed)).first->second; } bool BuildingIconCache::hasIcon(const std::string& iconName) { return !getSvg(iconName).isEmpty(); } QPixmap BuildingIconCache::getChip(const std::string& iconName, int sizePx) { return getPixmap(iconName, getSvg(iconName), sizePx); } QPixmap BuildingIconCache::getGreyChip(const std::string& iconName, int sizePx) { return getPixmap("grey:" + iconName, getGreySvg(iconName), sizePx); } QPixmap BuildingIconCache::getPixmap(const std::string& cacheKey, const QByteArray& svg, int sizePx) { if (sizePx <= 0) { return QPixmap(); } const std::pair key(cacheKey, sizePx); const std::map, QPixmap>::const_iterator cached = m_pixmapCache.find(key); if (cached != m_pixmapCache.end()) { return cached->second; } QPixmap pixmap; if (!svg.isEmpty()) { // Rasterized straight at its on-screen size times the device pixel ratio, so it // stays crisp without a downscale step. const qreal dpr = qApp ? qApp->devicePixelRatio() : 1.0; QSvgRenderer renderer(svg); pixmap = QPixmap(static_cast(sizePx * dpr), static_cast(sizePx * dpr)); pixmap.setDevicePixelRatio(dpr); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing, true); renderer.render(&painter); } return m_pixmapCache.emplace(key, std::move(pixmap)).first->second; }