1 /*
2 SPDX-FileCopyrightText: 2009 Daniel Laidig <d.laidig@gmx.de>
3 SPDX-License-Identifier: GPL-2.0-or-later
4 */
5
6 #include "imagewidget.h"
7 #include <config-parley.h>
8
9 #include <QPaintEngine>
10 #include <QPainter>
11 #include <QTimeLine>
12 #include <QTimer>
13
14 #include <QDebug>
15
16 #if defined(Q_WS_X11)
17 #include <QX11Info>
18 #include <X11/Xlib.h>
19 #include <X11/extensions/Xrender.h>
20 #undef KeyPress
21 #undef FocusOut
22 #endif
23
24 using namespace Practice;
25
26 // The functions centerPixmaps() and transition() are copied from kdelibs/plasma/paintutils.cpp, revision 1133527
27 // License: LGPLv2+
28 // SPDX-FileCopyrightText: 2005 Aaron Seigo <aseigo@kde.org>
29 // SPDX-FileCopyrightText: 2008 Andrew Lake <jamboarder@yahoo.com>
30 // Don't just modify the code here, if there are issues they should probably also be fixed in libplasma.
31
centerPixmaps(QPixmap & from,QPixmap & to)32 void centerPixmaps(QPixmap &from, QPixmap &to)
33 {
34 if (from.size() == to.size() && from.hasAlphaChannel() && to.hasAlphaChannel()) {
35 return;
36 }
37 QRect fromRect(from.rect());
38 QRect toRect(to.rect());
39
40 QRect actualRect = QRect(QPoint(0, 0), fromRect.size().expandedTo(toRect.size()));
41 fromRect.moveCenter(actualRect.center());
42 toRect.moveCenter(actualRect.center());
43
44 if (from.size() != actualRect.size() || !from.hasAlphaChannel()) {
45 QPixmap result(actualRect.size());
46 result.fill(Qt::transparent);
47 QPainter p(&result);
48 p.setCompositionMode(QPainter::CompositionMode_Source);
49 p.drawPixmap(fromRect.topLeft(), from);
50 p.end();
51 from = result;
52 }
53
54 if (to.size() != actualRect.size() || !to.hasAlphaChannel()) {
55 QPixmap result(actualRect.size());
56 result.fill(Qt::transparent);
57 QPainter p(&result);
58 p.setCompositionMode(QPainter::CompositionMode_Source);
59 p.drawPixmap(toRect.topLeft(), to);
60 p.end();
61 to = result;
62 }
63 }
64
transition(const QPixmap & from,const QPixmap & to,qreal amount)65 QPixmap transition(const QPixmap &from, const QPixmap &to, qreal amount)
66 {
67 if (from.isNull() && to.isNull()) {
68 return from;
69 }
70
71 QPixmap startPixmap(from);
72 QPixmap targetPixmap(to);
73
74 if (from.size() != to.size() || !from.hasAlphaChannel() || !to.hasAlphaChannel()) {
75 centerPixmaps(startPixmap, targetPixmap);
76 }
77
78 // paint to in the center of from
79 QRect toRect = to.rect();
80
81 QColor color;
82 color.setAlphaF(amount);
83
84 // If the native paint engine supports Porter/Duff compositing and CompositionMode_Plus
85 QPaintEngine *paintEngine = from.paintEngine();
86 if (paintEngine && paintEngine->hasFeature(QPaintEngine::PorterDuff) && paintEngine->hasFeature(QPaintEngine::BlendModes)) {
87 QPainter p;
88 p.begin(&targetPixmap);
89 p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
90 p.fillRect(targetPixmap.rect(), color);
91 p.end();
92
93 p.begin(&startPixmap);
94 p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
95 p.fillRect(startPixmap.rect(), color);
96 p.setCompositionMode(QPainter::CompositionMode_Plus);
97 p.drawPixmap(toRect.topLeft(), targetPixmap);
98 p.end();
99
100 return startPixmap;
101 }
102 #if defined(Q_WS_X11)
103 // We have Xrender support
104 else if (paintEngine && paintEngine->hasFeature(QPaintEngine::PorterDuff)) {
105 // QX11PaintEngine doesn't implement CompositionMode_Plus in Qt 4.3,
106 // which we need to be able to do a transition from one pixmap to
107 // another.
108 //
109 // In order to avoid the overhead of converting the pixmaps to images
110 // and doing the operation entirely in software, this function has a
111 // specialized path for X11 that uses Xrender directly to do the
112 // transition. This operation can be fully accelerated in HW.
113 //
114 // This specialization can be removed when QX11PaintEngine supports
115 // CompositionMode_Plus.
116 QPixmap source(targetPixmap), destination(startPixmap);
117
118 source.detach();
119 destination.detach();
120
121 Display *dpy = QX11Info::display();
122
123 XRenderPictFormat *format = XRenderFindStandardFormat(dpy, PictStandardA8);
124 XRenderPictureAttributes pa;
125 pa.repeat = 1; // RepeatNormal
126
127 // Create a 1x1 8 bit repeating alpha picture
128 Pixmap pixmap = XCreatePixmap(dpy, destination.handle(), 1, 1, 8);
129 Picture alpha = XRenderCreatePicture(dpy, pixmap, format, CPRepeat, &pa);
130 XFreePixmap(dpy, pixmap);
131
132 // Fill the alpha picture with the opacity value
133 XRenderColor xcolor;
134 xcolor.alpha = quint16(0xffff * amount);
135 XRenderFillRectangle(dpy, PictOpSrc, alpha, &xcolor, 0, 0, 1, 1);
136
137 // Reduce the alpha of the destination with 1 - opacity
138 XRenderComposite(dpy, PictOpOutReverse, alpha, None, destination.x11PictureHandle(), 0, 0, 0, 0, 0, 0, destination.width(), destination.height());
139
140 // Add source * opacity to the destination
141 XRenderComposite(dpy,
142 PictOpAdd,
143 source.x11PictureHandle(),
144 alpha,
145 destination.x11PictureHandle(),
146 toRect.x(),
147 toRect.y(),
148 0,
149 0,
150 0,
151 0,
152 destination.width(),
153 destination.height());
154
155 XRenderFreePicture(dpy, alpha);
156 return destination;
157 }
158 #endif
159 else {
160 // Fall back to using QRasterPaintEngine to do the transition.
161 QImage under = startPixmap.toImage();
162 QImage over = targetPixmap.toImage();
163
164 QPainter p;
165 p.begin(&over);
166 p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
167 p.fillRect(over.rect(), color);
168 p.end();
169
170 p.begin(&under);
171 p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
172 p.fillRect(under.rect(), color);
173 p.setCompositionMode(QPainter::CompositionMode_Plus);
174 p.drawImage(toRect.topLeft(), over);
175 p.end();
176
177 return QPixmap::fromImage(under);
178 }
179 }
180
ImageWidget(QWidget * parent)181 ImageWidget::ImageWidget(QWidget *parent)
182 : QWidget(parent)
183 {
184 m_scaleTimer = new QTimer(this);
185 m_scaleTimer->setSingleShot(true);
186 m_scaleTimer->setInterval(500);
187
188 m_animation = new QTimeLine(300, this);
189
190 m_scaledPixmapOutOfDate = false;
191 connect(m_scaleTimer, SIGNAL(timeout()), this, SLOT(scalePixmap()));
192 connect(m_animation, SIGNAL(valueChanged(qreal)), this, SLOT(update()));
193 connect(m_animation, &QTimeLine::finished, this, &ImageWidget::animationFinished);
194 }
195
setPixmap(const QPixmap & pixmap)196 void ImageWidget::setPixmap(const QPixmap &pixmap)
197 {
198 // qDebug() << "set new pixmap, size:" << pixmap.size();
199 if (m_animation->state() == QTimeLine::Running) {
200 m_scaledPixmap = transition(m_animationPixmap, m_scaledPixmap, m_animation->currentValue());
201 m_animation->stop();
202 animationFinished();
203 }
204
205 m_animationPixmap = m_scaledPixmap;
206 m_originalPixmap = pixmap;
207 m_scaledPixmap = QPixmap();
208 m_scaledBackupPixmap = QPixmap();
209 m_scaledPixmapOutOfDate = true;
210 if (!m_scaling) {
211 m_scaledPixmap = pixmap;
212 }
213 scalePixmap(true);
214 if (m_fading) {
215 m_animation->start();
216 }
217 update();
218 }
219
setScalingEnabled(bool scaling,bool onlyDownscaling)220 void ImageWidget::setScalingEnabled(bool scaling, bool onlyDownscaling)
221 {
222 m_scaling = scaling;
223 m_onlyDownscaling = onlyDownscaling;
224 }
225
setKeepAspectRatio(Qt::AspectRatioMode mode)226 void ImageWidget::setKeepAspectRatio(Qt::AspectRatioMode mode)
227 {
228 m_keepAspectRatio = mode;
229 }
230
setFadingEnabled(bool fading)231 void ImageWidget::setFadingEnabled(bool fading)
232 {
233 m_fading = fading;
234 }
235
setAlignment(Qt::Alignment alignment)236 void ImageWidget::setAlignment(Qt::Alignment alignment)
237 {
238 m_alignment = alignment;
239 }
240
paintEvent(QPaintEvent * e)241 void ImageWidget::paintEvent(QPaintEvent *e)
242 {
243 QWidget::paintEvent(e);
244 QPainter painter(this);
245 if (m_scaling && m_scaledPixmapOutOfDate) {
246 m_scaleTimer->start();
247 scalePixmap(false);
248 }
249 QPixmap pm = m_scaledPixmap;
250 if (m_animation->state() == QTimeLine::Running) {
251 pm = transition(m_animationPixmap, m_scaledPixmap, m_animation->currentValue());
252 }
253
254 int x = (size().width() - pm.width()) / 2;
255 if (m_alignment.testFlag(Qt::AlignLeft)) {
256 x = 0;
257 } else if (m_alignment.testFlag(Qt::AlignRight)) {
258 x = size().width() - pm.width();
259 }
260 int y = (size().height() - pm.height()) / 2;
261 if (m_alignment.testFlag(Qt::AlignTop)) {
262 y = 0;
263 } else if (m_alignment.testFlag(Qt::AlignBottom)) {
264 y = size().height() - pm.height();
265 }
266 painter.drawPixmap(x, y, pm);
267 }
268
resizeEvent(QResizeEvent * e)269 void ImageWidget::resizeEvent(QResizeEvent *e)
270 {
271 if (!m_scaledPixmapOutOfDate) {
272 m_scaledBackupPixmap = m_scaledPixmap;
273 }
274 // stop animations when resizing
275 if (m_animation->state() == QTimeLine::Running) {
276 m_animation->stop();
277 animationFinished();
278 }
279 m_scaledPixmapOutOfDate = true;
280 QWidget::resizeEvent(e);
281 emit sizeChanged();
282 }
283
scalePixmap(bool smooth)284 void ImageWidget::scalePixmap(bool smooth)
285 {
286 bool scaleUp = m_originalPixmap.width() <= size().width() && m_originalPixmap.height() <= size().height();
287 if ((m_onlyDownscaling && scaleUp) || m_originalPixmap.size() == size()) {
288 // qDebug() << "no need to scale pixmap";
289 m_scaledPixmapOutOfDate = false;
290 m_scaledPixmap = m_originalPixmap;
291 m_scaledBackupPixmap = QPixmap();
292 } else if (smooth) {
293 // qDebug() << "smooth scaling to" << size();
294 if (m_originalPixmap.isNull() || size().isEmpty()) {
295 m_scaledPixmapOutOfDate = false;
296 m_scaledPixmap = QPixmap();
297 update();
298 return;
299 }
300 m_scaledPixmap = m_originalPixmap.scaled(size(), m_keepAspectRatio, Qt::SmoothTransformation);
301 m_scaledBackupPixmap = QPixmap();
302 m_scaledPixmapOutOfDate = false;
303 update();
304 } else {
305 // qDebug() << "fast scaling to" << size();
306 // Try to find out if it makes sense to use the scaled backup pixmap.
307 // If the scaled backup gets too small, we use the original image.
308 float ratio = 0;
309 if (!size().isEmpty()) {
310 ratio = qMin(float(m_scaledBackupPixmap.width()) / size().width(), float(m_scaledBackupPixmap.height()) / size().height());
311 }
312 if (ratio > 0.4 && !m_scaledBackupPixmap.isNull()) {
313 m_scaledPixmap = m_scaledBackupPixmap.scaled(size(), m_keepAspectRatio, Qt::FastTransformation);
314 } else {
315 if (m_originalPixmap.isNull() || size().isEmpty()) {
316 m_scaledPixmap = QPixmap();
317 return;
318 }
319 // use the original pixmap
320 m_scaledPixmap = m_originalPixmap.scaled(size(), m_keepAspectRatio, Qt::FastTransformation);
321 m_scaledBackupPixmap = m_scaledPixmap;
322 }
323 m_scaledPixmapOutOfDate = true;
324 }
325 }
326
animationFinished()327 void ImageWidget::animationFinished()
328 {
329 m_animationPixmap = QPixmap();
330 }
331