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