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 }