File indexing completed on 2024-04-28 07:39:31
0001 /* 0002 SPDX-FileCopyrightText: 2010 Daniel Laidig <laidig@kde.org> 0003 SPDX-License-Identifier: GPL-2.0-or-later 0004 */ 0005 0006 #include "themedbackgroundrenderer.h" 0007 0008 #include "settings/kgametheme/kgametheme.h" 0009 #include <QDebug> 0010 #include <QStandardPaths> 0011 0012 #include <QApplication> 0013 #include <QMargins> 0014 #include <QPainter> 0015 #include <QPalette> 0016 #include <QRegularExpression> 0017 #include <QtConcurrentRun> 0018 0019 using namespace Practice; 0020 0021 ThemedBackgroundRenderer::ThemedBackgroundRenderer(QObject *parent, const QString &cacheFilename) 0022 : QObject(parent) 0023 , m_haveCache(true) 0024 , m_queuedRequest(false) 0025 , m_isFastScaledRender(true) 0026 { 0027 m_theme = new KGameTheme(); 0028 m_cache.setSaveFilename(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + '/' + cacheFilename); 0029 m_timer.setSingleShot(true); 0030 m_timer.setInterval(1000); 0031 connect(&m_timer, &QTimer::timeout, this, &ThemedBackgroundRenderer::updateBackgroundTimeout); 0032 connect(&m_watcher, &QFutureWatcherBase::finished, this, &ThemedBackgroundRenderer::renderingFinished); 0033 } 0034 0035 ThemedBackgroundRenderer::~ThemedBackgroundRenderer() 0036 { 0037 if (m_future.isRunning()) { 0038 qDebug() << "Waiting for rendering to finish"; 0039 m_future.waitForFinished(); 0040 } 0041 m_cache.saveCache(); 0042 delete m_theme; 0043 } 0044 0045 void ThemedBackgroundRenderer::setTheme(const QString &theme) 0046 { 0047 if (!m_theme->load(theme)) { 0048 qDebug() << "could not load theme" << theme; 0049 } 0050 m_renderer.load(m_theme->graphics()); 0051 m_cache.setFilenames(QStringList(m_theme->graphics()) << m_theme->path()); 0052 m_haveCache = !m_cache.isEmpty(); 0053 m_lastScaledRenderRects.clear(); 0054 m_lastFullRenderRects.clear(); 0055 m_rectMappings.clear(); 0056 } 0057 0058 void ThemedBackgroundRenderer::clearRects() 0059 { 0060 m_rects.clear(); 0061 m_rectMappings.clear(); 0062 } 0063 0064 void ThemedBackgroundRenderer::addRect(const QString &name, const QRect &rect) 0065 { 0066 m_rects.append(QPair<QString, QRect>(name, rect)); 0067 if (!m_rectMappings.contains(name)) { 0068 QString mapped = m_theme->property("X-Parley-" + name); 0069 m_rectMappings[name] = mapped.isEmpty() ? name : mapped; 0070 } 0071 } 0072 0073 QPixmap ThemedBackgroundRenderer::getScaledBackground() 0074 { 0075 if (m_rects.isEmpty() || m_rects[0].second.isEmpty()) { 0076 return QPixmap(); 0077 } 0078 if (m_future.isRunning() || m_future.resultCount()) { 0079 return QPixmap(); 0080 } 0081 if (m_cache.isEmpty()) { 0082 m_timer.start(0); 0083 return QPixmap(); 0084 } 0085 if (m_lastScaledRenderRects == m_rects) { 0086 // we already renderered an image with that exact sizing, no need to waste resources on it again 0087 return QPixmap(); 0088 } 0089 0090 QFutureWatcher<QImage> watcher; 0091 m_future = QtConcurrent::run([this]() { 0092 return renderBackground(true); 0093 }); 0094 watcher.setFuture(m_future); 0095 watcher.waitForFinished(); 0096 0097 QPixmap result = QPixmap::fromImage(m_future.result()); 0098 m_future = QFuture<QImage>(); 0099 m_lastScaledRenderRects = m_rects; 0100 return result; 0101 } 0102 0103 QColor ThemedBackgroundRenderer::fontColor(const QString &context, const QColor &fallback) 0104 { 0105 QString text = m_theme->property("X-Parley-Font-Color-" + context).toLower(); 0106 if (text.length() == 6 && text.contains(QRegularExpression(QStringLiteral("[0-9a-f]{6}")))) { 0107 return QColor(text.mid(0, 2).toInt(nullptr, 16), text.mid(2, 2).toInt(nullptr, 16), text.mid(4, 2).toInt(nullptr, 16)); 0108 } 0109 0110 return fallback; 0111 } 0112 0113 void ThemedBackgroundRenderer::updateBackground() 0114 { 0115 if (m_rects.isEmpty() || m_rects[0].second.isEmpty()) { 0116 return; 0117 } 0118 m_timer.start(); 0119 } 0120 0121 void ThemedBackgroundRenderer::updateBackgroundTimeout() 0122 { 0123 bool fastScale = false; 0124 if (m_future.isRunning()) { 0125 m_timer.start(); // restart the timer again 0126 return; 0127 } 0128 if (m_lastFullRenderRects == m_rects && m_lastScaledRenderRects == m_rects) { 0129 // we already renderered an image with that exact sizing, no need to waste resources on it again 0130 return; 0131 } 0132 m_future = QtConcurrent::run([this, fastScale]() { 0133 return renderBackground(fastScale); 0134 }); 0135 m_watcher.setFuture(m_future); 0136 m_lastFullRenderRects = m_rects; 0137 } 0138 0139 void ThemedBackgroundRenderer::renderingFinished() 0140 { 0141 if (!m_future.resultCount()) { 0142 // qDebug() << "there is no image!"; 0143 return; 0144 } 0145 Q_EMIT backgroundChanged(QPixmap::fromImage(m_future.result())); 0146 m_future = QFuture<QImage>(); 0147 } 0148 0149 QSizeF ThemedBackgroundRenderer::getSizeForId(const QString &id) 0150 { 0151 if (!m_renderer.elementExists(id)) 0152 return QSizeF(); 0153 return m_renderer.boundsOnElement(id).size(); 0154 } 0155 0156 QRectF ThemedBackgroundRenderer::getRectForId(const QString &id) 0157 { 0158 if (!m_renderer.elementExists(id)) 0159 return QRectF(); 0160 return m_renderer.boundsOnElement(id); 0161 } 0162 0163 QPixmap ThemedBackgroundRenderer::getPixmapForId(const QString &id, QSize size) 0164 { 0165 if (!m_renderer.elementExists(id)) 0166 return QPixmap(); 0167 QRectF itemRect = m_renderer.boundsOnElement(id); 0168 if (itemRect.isNull()) 0169 return QPixmap(); 0170 if (size.isEmpty()) 0171 size = itemRect.size().toSize(); 0172 0173 if (m_cache.imageSize(id) != size) { 0174 QImage image(size, QImage::Format_ARGB32_Premultiplied); 0175 image.fill(QColor(Qt::transparent).rgba()); 0176 QPainter p(&image); 0177 m_renderer.render(&p, id, QRectF(QPointF(0, 0), size)); 0178 m_cache.updateImage(id, image); 0179 return QPixmap::fromImage(image); 0180 } else { 0181 return QPixmap::fromImage(m_cache.getImage(id)); 0182 } 0183 } 0184 0185 QMargins ThemedBackgroundRenderer::contentMargins() 0186 { 0187 QString rect; 0188 if (!m_rects.empty()) { 0189 rect = m_rects.at(0).first; 0190 } 0191 if (m_rectMappings.contains(rect)) { 0192 rect = m_rectMappings.value(rect); 0193 } 0194 QMargins margins; 0195 if (m_renderer.elementExists(rect + "-border-topleft")) 0196 margins.setTop(m_renderer.boundsOnElement(rect + "-border-topleft").toAlignedRect().height()); 0197 if (m_renderer.elementExists(rect + "-border-bottomleft")) 0198 margins.setBottom(m_renderer.boundsOnElement(rect + "-border-bottomleft").toAlignedRect().height()); 0199 if (m_renderer.elementExists(rect + "-border-topleft")) 0200 margins.setLeft(m_renderer.boundsOnElement(rect + "-border-topleft").toAlignedRect().width()); 0201 if (m_renderer.elementExists(rect + "-border-topright")) 0202 margins.setRight(m_renderer.boundsOnElement(rect + "-border-topright").toAlignedRect().width()); 0203 return margins; 0204 } 0205 0206 QImage ThemedBackgroundRenderer::renderBackground(bool fastScale) 0207 { 0208 m_isFastScaledRender = false; 0209 0210 QImage image(m_rects[0].second.size(), QImage::Format_ARGB32_Premultiplied); 0211 image.fill(QColor(Qt::transparent).rgba()); 0212 QPainter p(&image); 0213 0214 for (QPair<QString, QRect> rect : qAsConst(m_rects)) { 0215 if (!m_rects.isEmpty() && rect == m_rects[0]) { 0216 QMargins margins = contentMargins(); 0217 rect.second = 0218 QRect(QPoint(margins.left(), margins.top()), rect.second.size() - QSize(margins.right() + margins.left(), margins.bottom() + margins.top())); 0219 } 0220 renderRect(rect.first, rect.second, &p, fastScale); 0221 } 0222 0223 // qDebug() << "image rendered, time:" << t.elapsed(); 0224 return image; 0225 } 0226 0227 void ThemedBackgroundRenderer::renderRect(const QString &name, const QRect &rect, QPainter *p, bool fastScale) 0228 { 0229 renderItem(name, QStringLiteral("center"), rect, p, fastScale, Rect, Qt::IgnoreAspectRatio, Center, Centered, true); 0230 renderItem(name, QStringLiteral("center-ratio"), rect, p, fastScale, Rect, Qt::IgnoreAspectRatio, Center, Centered, true); 0231 renderItem(name, QStringLiteral("center-noscale"), rect, p, fastScale, NoScale, Qt::IgnoreAspectRatio, Center, Centered, true); 0232 0233 renderItem(name, QStringLiteral("border-topleft"), rect, p, fastScale, NoScale, Qt::IgnoreAspectRatio, Top, Corner, false); 0234 renderItem(name, QStringLiteral("border-topright"), rect, p, fastScale, NoScale, Qt::IgnoreAspectRatio, Right, Corner, false); 0235 renderItem(name, QStringLiteral("border-bottomleft"), rect, p, fastScale, NoScale, Qt::IgnoreAspectRatio, Left, Corner, false); 0236 renderItem(name, QStringLiteral("border-bottomright"), rect, p, fastScale, NoScale, Qt::IgnoreAspectRatio, Bottom, Corner, false); 0237 0238 QStringList edges; 0239 edges << QStringLiteral("top") << QStringLiteral("bottom") << QStringLiteral("left") << QStringLiteral("right"); 0240 for (const QString &edge : qAsConst(edges)) { 0241 ScaleBase scaleBase; 0242 Edge alignEdge; 0243 if (edge == QLatin1String("top")) { 0244 alignEdge = Top; 0245 scaleBase = Horizontal; 0246 } else if (edge == QLatin1String("bottom")) { 0247 alignEdge = Bottom; 0248 scaleBase = Horizontal; 0249 } else if (edge == QLatin1String("right")) { 0250 alignEdge = Right; 0251 scaleBase = Vertical; 0252 } else { 0253 alignEdge = Left; 0254 scaleBase = Vertical; 0255 } 0256 for (int inside = 1; inside >= 0; inside--) { 0257 renderItem(name, 0258 QString(inside ? "inside" : "border") + '-' + edge, 0259 rect, 0260 p, 0261 fastScale, 0262 scaleBase, 0263 Qt::IgnoreAspectRatio, 0264 alignEdge, 0265 Centered, 0266 inside); 0267 renderItem(name, 0268 QString(inside ? "inside" : "border") + '-' + edge + "-ratio", 0269 rect, 0270 p, 0271 fastScale, 0272 scaleBase, 0273 Qt::KeepAspectRatio, 0274 alignEdge, 0275 Centered, 0276 inside); 0277 renderItem(name, 0278 QString(inside ? "inside" : "border") + '-' + edge + "-noscale", 0279 rect, 0280 p, 0281 fastScale, 0282 NoScale, 0283 Qt::IgnoreAspectRatio, 0284 alignEdge, 0285 Centered, 0286 inside); 0287 renderItem(name, 0288 QString(inside ? "inside" : "border") + '-' + edge + "-repeat", 0289 rect, 0290 p, 0291 fastScale, 0292 scaleBase, 0293 Qt::IgnoreAspectRatio, 0294 alignEdge, 0295 Repeated, 0296 inside); 0297 renderItem(name, 0298 QString(inside ? "inside" : "border") + '-' + edge + '-' + (scaleBase == Vertical ? "top" : "left"), 0299 rect, 0300 p, 0301 fastScale, 0302 NoScale, 0303 Qt::IgnoreAspectRatio, 0304 alignEdge, 0305 LeftTop, 0306 inside); 0307 renderItem(name, 0308 QString(inside ? "inside" : "border") + '-' + edge + '-' + (scaleBase == Vertical ? "bottom" : "right"), 0309 rect, 0310 p, 0311 fastScale, 0312 NoScale, 0313 Qt::IgnoreAspectRatio, 0314 alignEdge, 0315 RightBottom, 0316 inside); 0317 } 0318 } 0319 } 0320 0321 void ThemedBackgroundRenderer::renderItem(const QString &idBase, 0322 const QString &idSuffix, 0323 const QRect &rect, 0324 QPainter *p, 0325 bool fastScale, 0326 ScaleBase scaleBase, 0327 Qt::AspectRatioMode aspectRatio, 0328 Edge edge, 0329 Align align, 0330 bool inside) 0331 { 0332 // the id without the mapping, which we need to use for caching 0333 // (otherwise, images could share a place in the cache which makes it useless if they have different sizes) 0334 QString id = idBase + '-' + idSuffix; 0335 // the id according to the mapping specified in the desktop file 0336 QString mappedId = m_rectMappings.contains(idBase) ? m_rectMappings.value(idBase) + '-' + idSuffix : id; 0337 0338 if (!m_renderer.elementExists(mappedId)) 0339 return; 0340 QRectF itemRectF = m_renderer.boundsOnElement(mappedId); 0341 if (itemRectF.isNull() || rect.isNull()) 0342 return; 0343 0344 // qDebug() << "draw item" << id; 0345 // qDebug() << "original item rect:" << itemRect << m_renderer.boundsOnElement(id); 0346 QRect itemRect = scaleRect(itemRectF, rect, scaleBase, aspectRatio); 0347 // qDebug() << "scaled" << itemRect; 0348 itemRect = alignRect(itemRect, rect, edge, align, inside); 0349 // qDebug() << "aligned" << itemRect; 0350 0351 QImage image; 0352 if (m_cache.imageSize(id) == itemRect.size()) { 0353 // qDebug() << "found in cache:" << id; 0354 image = m_cache.getImage(id); 0355 } else if (fastScale && !m_cache.imageSize(id).isEmpty()) { 0356 // qDebug() << "FAST SCALE for:" << id; 0357 image = m_cache.getImage(id).scaled(itemRect.size(), Qt::IgnoreAspectRatio, Qt::FastTransformation); 0358 m_isFastScaledRender = true; 0359 } else { 0360 // qDebug() << "NOT IN CACHE, render svg:" << id; 0361 image = QImage(itemRect.size(), QImage::Format_ARGB32_Premultiplied); 0362 image.fill(QColor(Qt::transparent).rgba()); 0363 QPainter painter(&image); 0364 if (align == Repeated) { 0365 QImage tile(itemRectF.toRect().size(), QImage::Format_ARGB32_Premultiplied); 0366 tile.fill(QColor(Qt::transparent).rgba()); 0367 QPainter tilePainter(&tile); 0368 m_renderer.render(&tilePainter, mappedId, QRect(QPoint(0, 0), tile.size())); 0369 painter.fillRect(image.rect(), QBrush(tile)); 0370 } else if (aspectRatio == Qt::KeepAspectRatioByExpanding) { 0371 m_renderer.render(&painter, mappedId, QRect(QPoint(0, 0), itemRect.size())); 0372 painter.end(); 0373 QRect croppedRect = rect; 0374 croppedRect.moveCenter(itemRect.center()); 0375 image = image.copy(croppedRect); 0376 } else { 0377 m_renderer.render(&painter, mappedId, QRect(QPoint(0, 0), itemRect.size())); 0378 } 0379 m_cache.updateImage(id, image); 0380 m_haveCache = true; 0381 } 0382 p->drawImage(itemRect.topLeft(), image); 0383 } 0384 0385 QRect ThemedBackgroundRenderer::scaleRect(QRectF itemRect, const QRect &baseRect, ScaleBase scaleBase, Qt::AspectRatioMode aspectRatio) 0386 { 0387 qreal verticalFactor = 0; 0388 qreal horizontalFactor = 0; 0389 switch (scaleBase) { 0390 case NoScale: 0391 return itemRect.toRect(); 0392 case Horizontal: 0393 switch (aspectRatio) { 0394 case Qt::IgnoreAspectRatio: 0395 itemRect.setWidth(baseRect.width()); 0396 return itemRect.toRect(); 0397 case Qt::KeepAspectRatio: 0398 horizontalFactor = baseRect.width() / itemRect.width(); 0399 itemRect.setWidth(baseRect.width()); 0400 itemRect.setHeight(itemRect.height() * horizontalFactor); 0401 return itemRect.toRect(); 0402 case Qt::KeepAspectRatioByExpanding: 0403 qWarning() << "KeepAspectRatioByExpanding only works for the center"; 0404 return itemRect.toRect(); 0405 } 0406 break; 0407 case Vertical: 0408 switch (aspectRatio) { 0409 case Qt::IgnoreAspectRatio: 0410 itemRect.setHeight(baseRect.height()); 0411 return itemRect.toRect(); 0412 case Qt::KeepAspectRatio: 0413 verticalFactor = baseRect.height() / itemRect.height(); 0414 itemRect.setHeight(baseRect.height()); 0415 itemRect.setWidth(itemRect.width() * verticalFactor); 0416 return itemRect.toRect(); 0417 case Qt::KeepAspectRatioByExpanding: 0418 qWarning() << "KeepAspectRatioByExpanding only works for the center"; 0419 return itemRect.toRect(); 0420 } 0421 break; 0422 case Rect: 0423 switch (aspectRatio) { 0424 case Qt::IgnoreAspectRatio: 0425 itemRect.setWidth(baseRect.width()); 0426 itemRect.setHeight(baseRect.height()); 0427 return itemRect.toRect(); 0428 case Qt::KeepAspectRatio: 0429 horizontalFactor = baseRect.width() / itemRect.width(); 0430 verticalFactor = baseRect.height() / itemRect.height(); 0431 if (verticalFactor < horizontalFactor) { 0432 itemRect.setHeight(baseRect.height()); 0433 itemRect.setWidth(itemRect.width() * verticalFactor); 0434 } else { 0435 itemRect.setWidth(baseRect.width()); 0436 itemRect.setHeight(itemRect.height() * horizontalFactor); 0437 } 0438 return itemRect.toRect(); 0439 case Qt::KeepAspectRatioByExpanding: 0440 horizontalFactor = baseRect.width() / itemRect.width(); 0441 verticalFactor = baseRect.height() / itemRect.height(); 0442 if (verticalFactor > horizontalFactor) { 0443 itemRect.setHeight(baseRect.height()); 0444 itemRect.setWidth(itemRect.width() * verticalFactor); 0445 } else { 0446 itemRect.setWidth(baseRect.width()); 0447 itemRect.setHeight(itemRect.height() * horizontalFactor); 0448 } 0449 return itemRect.toRect(); 0450 } 0451 break; 0452 } 0453 // qDebug() << "unhandled scaling option"; 0454 return itemRect.toRect(); 0455 } 0456 0457 QRect ThemedBackgroundRenderer::alignRect(QRect itemRect, const QRect &baseRect, Edge edge, Align align, bool inside) 0458 { 0459 if (edge == Center) { 0460 int x = baseRect.x() + (baseRect.width() - itemRect.width()) / 2; 0461 int y = baseRect.y() + (baseRect.height() - itemRect.height()) / 2; 0462 itemRect.moveTo(x, y); 0463 return itemRect; 0464 } 0465 0466 if (edge == Top || edge == Bottom) { 0467 // set x coordinate 0468 int x = 0; 0469 switch (align) { 0470 case Corner: 0471 if (edge == Top) { 0472 x = baseRect.x() - itemRect.width(); 0473 } else { 0474 x = baseRect.x() + baseRect.width(); 0475 } 0476 break; 0477 case LeftTop: 0478 x = baseRect.x(); 0479 break; 0480 case Centered: 0481 case Repeated: 0482 x = baseRect.x() + (baseRect.width() - itemRect.width()) / 2; 0483 break; 0484 case RightBottom: 0485 x = baseRect.x() + baseRect.width() - itemRect.width(); 0486 break; 0487 } 0488 // set y coordinate 0489 int y = baseRect.y(); 0490 if (edge == Bottom) { 0491 y += baseRect.height() - itemRect.height(); 0492 } 0493 if ((!inside) && edge == Top) { 0494 y -= itemRect.height(); 0495 } else if (!inside) { 0496 y += itemRect.height(); 0497 } 0498 itemRect.moveTo(x, y); 0499 return itemRect; 0500 } else if (edge == Left || edge == Right) { 0501 // set y coordinate 0502 int y = 0; 0503 switch (align) { 0504 case Corner: 0505 if (edge == Right) { 0506 y = baseRect.y() - itemRect.height(); 0507 } else { 0508 y = baseRect.y() + baseRect.height(); 0509 } 0510 break; 0511 case LeftTop: 0512 y = baseRect.y(); 0513 break; 0514 case Centered: 0515 case Repeated: 0516 y = baseRect.y() + (baseRect.height() - itemRect.height()) / 2; 0517 break; 0518 case RightBottom: 0519 y = baseRect.y() + baseRect.height() - itemRect.height(); 0520 break; 0521 } 0522 // set x coordinate 0523 int x = baseRect.x(); 0524 if (edge == Right) { 0525 x += baseRect.width() - itemRect.width(); 0526 } 0527 if ((!inside) && edge == Left) { 0528 x -= itemRect.width(); 0529 } else if (!inside) { 0530 x += itemRect.width(); 0531 } 0532 itemRect.moveTo(x, y); 0533 return itemRect; 0534 } 0535 // qDebug() << "unhandled alignment option"; 0536 return itemRect; 0537 } 0538 0539 #include "moc_themedbackgroundrenderer.cpp"