File indexing completed on 2024-04-21 03:51:05

0001 /*
0002     SPDX-FileCopyrightText: 2009 Daniel Laidig <d.laidig@gmx.de>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "imagewidget.h"
0007 #include <config-parley.h>
0008 
0009 #include <QPaintEngine>
0010 #include <QPainter>
0011 #include <QTimeLine>
0012 #include <QTimer>
0013 
0014 #include <QDebug>
0015 
0016 #if defined(Q_WS_X11)
0017 #include <QX11Info>
0018 #include <X11/Xlib.h>
0019 #include <X11/extensions/Xrender.h>
0020 #undef KeyPress
0021 #undef FocusOut
0022 #endif
0023 
0024 using namespace Practice;
0025 
0026 // The functions centerPixmaps() and transition() are copied from kdelibs/plasma/paintutils.cpp, revision 1133527
0027 // License: LGPLv2+
0028 // SPDX-FileCopyrightText: 2005 Aaron Seigo <aseigo@kde.org>
0029 // SPDX-FileCopyrightText: 2008 Andrew Lake <jamboarder@yahoo.com>
0030 // Don't just modify the code here, if there are issues they should probably also be fixed in libplasma.
0031 
0032 void centerPixmaps(QPixmap &from, QPixmap &to)
0033 {
0034     if (from.size() == to.size() && from.hasAlphaChannel() && to.hasAlphaChannel()) {
0035         return;
0036     }
0037     QRect fromRect(from.rect());
0038     QRect toRect(to.rect());
0039 
0040     QRect actualRect = QRect(QPoint(0, 0), fromRect.size().expandedTo(toRect.size()));
0041     fromRect.moveCenter(actualRect.center());
0042     toRect.moveCenter(actualRect.center());
0043 
0044     if (from.size() != actualRect.size() || !from.hasAlphaChannel()) {
0045         QPixmap result(actualRect.size());
0046         result.fill(Qt::transparent);
0047         QPainter p(&result);
0048         p.setCompositionMode(QPainter::CompositionMode_Source);
0049         p.drawPixmap(fromRect.topLeft(), from);
0050         p.end();
0051         from = result;
0052     }
0053 
0054     if (to.size() != actualRect.size() || !to.hasAlphaChannel()) {
0055         QPixmap result(actualRect.size());
0056         result.fill(Qt::transparent);
0057         QPainter p(&result);
0058         p.setCompositionMode(QPainter::CompositionMode_Source);
0059         p.drawPixmap(toRect.topLeft(), to);
0060         p.end();
0061         to = result;
0062     }
0063 }
0064 
0065 QPixmap transition(const QPixmap &from, const QPixmap &to, qreal amount)
0066 {
0067     if (from.isNull() && to.isNull()) {
0068         return from;
0069     }
0070 
0071     QPixmap startPixmap(from);
0072     QPixmap targetPixmap(to);
0073 
0074     if (from.size() != to.size() || !from.hasAlphaChannel() || !to.hasAlphaChannel()) {
0075         centerPixmaps(startPixmap, targetPixmap);
0076     }
0077 
0078     // paint to in the center of from
0079     QRect toRect = to.rect();
0080 
0081     QColor color;
0082     color.setAlphaF(amount);
0083 
0084     // If the native paint engine supports Porter/Duff compositing and CompositionMode_Plus
0085     QPaintEngine *paintEngine = from.paintEngine();
0086     if (paintEngine && paintEngine->hasFeature(QPaintEngine::PorterDuff) && paintEngine->hasFeature(QPaintEngine::BlendModes)) {
0087         QPainter p;
0088         p.begin(&targetPixmap);
0089         p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
0090         p.fillRect(targetPixmap.rect(), color);
0091         p.end();
0092 
0093         p.begin(&startPixmap);
0094         p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
0095         p.fillRect(startPixmap.rect(), color);
0096         p.setCompositionMode(QPainter::CompositionMode_Plus);
0097         p.drawPixmap(toRect.topLeft(), targetPixmap);
0098         p.end();
0099 
0100         return startPixmap;
0101     }
0102 #if defined(Q_WS_X11)
0103     // We have Xrender support
0104     else if (paintEngine && paintEngine->hasFeature(QPaintEngine::PorterDuff)) {
0105         // QX11PaintEngine doesn't implement CompositionMode_Plus in Qt 4.3,
0106         // which we need to be able to do a transition from one pixmap to
0107         // another.
0108         //
0109         // In order to avoid the overhead of converting the pixmaps to images
0110         // and doing the operation entirely in software, this function has a
0111         // specialized path for X11 that uses Xrender directly to do the
0112         // transition. This operation can be fully accelerated in HW.
0113         //
0114         // This specialization can be removed when QX11PaintEngine supports
0115         // CompositionMode_Plus.
0116         QPixmap source(targetPixmap), destination(startPixmap);
0117 
0118         source.detach();
0119         destination.detach();
0120 
0121         Display *dpy = QX11Info::display();
0122 
0123         XRenderPictFormat *format = XRenderFindStandardFormat(dpy, PictStandardA8);
0124         XRenderPictureAttributes pa;
0125         pa.repeat = 1; // RepeatNormal
0126 
0127         // Create a 1x1 8 bit repeating alpha picture
0128         Pixmap pixmap = XCreatePixmap(dpy, destination.handle(), 1, 1, 8);
0129         Picture alpha = XRenderCreatePicture(dpy, pixmap, format, CPRepeat, &pa);
0130         XFreePixmap(dpy, pixmap);
0131 
0132         // Fill the alpha picture with the opacity value
0133         XRenderColor xcolor;
0134         xcolor.alpha = quint16(0xffff * amount);
0135         XRenderFillRectangle(dpy, PictOpSrc, alpha, &xcolor, 0, 0, 1, 1);
0136 
0137         // Reduce the alpha of the destination with 1 - opacity
0138         XRenderComposite(dpy, PictOpOutReverse, alpha, None, destination.x11PictureHandle(), 0, 0, 0, 0, 0, 0, destination.width(), destination.height());
0139 
0140         // Add source * opacity to the destination
0141         XRenderComposite(dpy,
0142                          PictOpAdd,
0143                          source.x11PictureHandle(),
0144                          alpha,
0145                          destination.x11PictureHandle(),
0146                          toRect.x(),
0147                          toRect.y(),
0148                          0,
0149                          0,
0150                          0,
0151                          0,
0152                          destination.width(),
0153                          destination.height());
0154 
0155         XRenderFreePicture(dpy, alpha);
0156         return destination;
0157     }
0158 #endif
0159     else {
0160         // Fall back to using QRasterPaintEngine to do the transition.
0161         QImage under = startPixmap.toImage();
0162         QImage over = targetPixmap.toImage();
0163 
0164         QPainter p;
0165         p.begin(&over);
0166         p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
0167         p.fillRect(over.rect(), color);
0168         p.end();
0169 
0170         p.begin(&under);
0171         p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
0172         p.fillRect(under.rect(), color);
0173         p.setCompositionMode(QPainter::CompositionMode_Plus);
0174         p.drawImage(toRect.topLeft(), over);
0175         p.end();
0176 
0177         return QPixmap::fromImage(under);
0178     }
0179 }
0180 
0181 ImageWidget::ImageWidget(QWidget *parent)
0182     : QWidget(parent)
0183 {
0184     m_scaleTimer = new QTimer(this);
0185     m_scaleTimer->setSingleShot(true);
0186     m_scaleTimer->setInterval(500);
0187 
0188     m_animation = new QTimeLine(300, this);
0189 
0190     m_scaledPixmapOutOfDate = false;
0191     connect(m_scaleTimer, SIGNAL(timeout()), this, SLOT(scalePixmap()));
0192     connect(m_animation, SIGNAL(valueChanged(qreal)), this, SLOT(update()));
0193     connect(m_animation, &QTimeLine::finished, this, &ImageWidget::animationFinished);
0194 }
0195 
0196 void ImageWidget::setPixmap(const QPixmap &pixmap)
0197 {
0198     // qDebug() << "set new pixmap, size:" << pixmap.size();
0199     if (m_animation->state() == QTimeLine::Running) {
0200         m_scaledPixmap = transition(m_animationPixmap, m_scaledPixmap, m_animation->currentValue());
0201         m_animation->stop();
0202         animationFinished();
0203     }
0204 
0205     m_animationPixmap = m_scaledPixmap;
0206     m_originalPixmap = pixmap;
0207     m_scaledPixmap = QPixmap();
0208     m_scaledBackupPixmap = QPixmap();
0209     m_scaledPixmapOutOfDate = true;
0210     if (!m_scaling) {
0211         m_scaledPixmap = pixmap;
0212     }
0213     scalePixmap(true);
0214     if (m_fading) {
0215         m_animation->start();
0216     }
0217     update();
0218 }
0219 
0220 void ImageWidget::setScalingEnabled(bool scaling, bool onlyDownscaling)
0221 {
0222     m_scaling = scaling;
0223     m_onlyDownscaling = onlyDownscaling;
0224 }
0225 
0226 void ImageWidget::setKeepAspectRatio(Qt::AspectRatioMode mode)
0227 {
0228     m_keepAspectRatio = mode;
0229 }
0230 
0231 void ImageWidget::setFadingEnabled(bool fading)
0232 {
0233     m_fading = fading;
0234 }
0235 
0236 void ImageWidget::setAlignment(Qt::Alignment alignment)
0237 {
0238     m_alignment = alignment;
0239 }
0240 
0241 void ImageWidget::paintEvent(QPaintEvent *e)
0242 {
0243     QWidget::paintEvent(e);
0244     QPainter painter(this);
0245     if (m_scaling && m_scaledPixmapOutOfDate) {
0246         m_scaleTimer->start();
0247         scalePixmap(false);
0248     }
0249     QPixmap pm = m_scaledPixmap;
0250     if (m_animation->state() == QTimeLine::Running) {
0251         pm = transition(m_animationPixmap, m_scaledPixmap, m_animation->currentValue());
0252     }
0253 
0254     int x = (size().width() - pm.width()) / 2;
0255     if (m_alignment.testFlag(Qt::AlignLeft)) {
0256         x = 0;
0257     } else if (m_alignment.testFlag(Qt::AlignRight)) {
0258         x = size().width() - pm.width();
0259     }
0260     int y = (size().height() - pm.height()) / 2;
0261     if (m_alignment.testFlag(Qt::AlignTop)) {
0262         y = 0;
0263     } else if (m_alignment.testFlag(Qt::AlignBottom)) {
0264         y = size().height() - pm.height();
0265     }
0266     painter.drawPixmap(x, y, pm);
0267 }
0268 
0269 void ImageWidget::resizeEvent(QResizeEvent *e)
0270 {
0271     if (!m_scaledPixmapOutOfDate) {
0272         m_scaledBackupPixmap = m_scaledPixmap;
0273     }
0274     // stop animations when resizing
0275     if (m_animation->state() == QTimeLine::Running) {
0276         m_animation->stop();
0277         animationFinished();
0278     }
0279     m_scaledPixmapOutOfDate = true;
0280     QWidget::resizeEvent(e);
0281     Q_EMIT sizeChanged();
0282 }
0283 
0284 void ImageWidget::scalePixmap(bool smooth)
0285 {
0286     bool scaleUp = m_originalPixmap.width() <= size().width() && m_originalPixmap.height() <= size().height();
0287     if ((m_onlyDownscaling && scaleUp) || m_originalPixmap.size() == size()) {
0288         // qDebug() << "no need to scale pixmap";
0289         m_scaledPixmapOutOfDate = false;
0290         m_scaledPixmap = m_originalPixmap;
0291         m_scaledBackupPixmap = QPixmap();
0292     } else if (smooth) {
0293         // qDebug() << "smooth scaling to" << size();
0294         if (m_originalPixmap.isNull() || size().isEmpty()) {
0295             m_scaledPixmapOutOfDate = false;
0296             m_scaledPixmap = QPixmap();
0297             update();
0298             return;
0299         }
0300         m_scaledPixmap = m_originalPixmap.scaled(size(), m_keepAspectRatio, Qt::SmoothTransformation);
0301         m_scaledBackupPixmap = QPixmap();
0302         m_scaledPixmapOutOfDate = false;
0303         update();
0304     } else {
0305         // qDebug() << "fast scaling to" << size();
0306         // Try to find out if it makes sense to use the scaled backup pixmap.
0307         // If the scaled backup gets too small, we use the original image.
0308         float ratio = 0;
0309         if (!size().isEmpty()) {
0310             ratio = qMin(float(m_scaledBackupPixmap.width()) / size().width(), float(m_scaledBackupPixmap.height()) / size().height());
0311         }
0312         if (ratio > 0.4 && !m_scaledBackupPixmap.isNull()) {
0313             m_scaledPixmap = m_scaledBackupPixmap.scaled(size(), m_keepAspectRatio, Qt::FastTransformation);
0314         } else {
0315             if (m_originalPixmap.isNull() || size().isEmpty()) {
0316                 m_scaledPixmap = QPixmap();
0317                 return;
0318             }
0319             // use the original pixmap
0320             m_scaledPixmap = m_originalPixmap.scaled(size(), m_keepAspectRatio, Qt::FastTransformation);
0321             m_scaledBackupPixmap = m_scaledPixmap;
0322         }
0323         m_scaledPixmapOutOfDate = true;
0324     }
0325 }
0326 
0327 void ImageWidget::animationFinished()
0328 {
0329     m_animationPixmap = QPixmap();
0330 }
0331 
0332 #include "moc_imagewidget.cpp"