]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/private/kpixmapmodifier.cpp
review comments
[dolphin.git] / src / kitemviews / private / kpixmapmodifier.cpp
1 // krazy:excludeall=copyright (email of Maxim is missing)
2 /*
3 This file is a part of the KDE project
4
5 Copyright © 2006 Zack Rusin <zack@kde.org>
6 Copyright © 2006-2007, 2008 Fredrik Höglund <fredrik@kde.org>
7
8 The stack blur algorithm was invented by Mario Klingemann <mario@quasimondo.com>
9
10 This implementation is based on the version in Anti-Grain Geometry Version 2.4,
11 Copyright © 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
12
13 Redistribution and use in source and binary forms, with or without
14 modification, are permitted provided that the following conditions
15 are met:
16
17 1. Redistributions of source code must retain the above copyright
18 notice, this list of conditions and the following disclaimer.
19 2. Redistributions in binary form must reproduce the above copyright
20 notice, this list of conditions and the following disclaimer in the
21 documentation and/or other materials provided with the distribution.
22
23 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
24 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
27 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 */
34
35 #include "kpixmapmodifier.h"
36
37 #include <QImage>
38 #include <QPainter>
39 #include <QPixmap>
40 #include <QSize>
41 #include <QGuiApplication>
42
43
44 #include <config-X11.h> // for HAVE_XRENDER
45 #if defined(Q_WS_X11) && defined(HAVE_XRENDER)
46 # include <QX11Info>
47 # include <X11/Xlib.h>
48 # include <X11/extensions/Xrender.h>
49 #endif
50
51 static const quint32 stackBlur8Mul[255] =
52 {
53 512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,
54 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,
55 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,
56 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,
57 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,
58 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,
59 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,
60 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,
61 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,
62 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,
63 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,
64 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,
65 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,
66 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,
67 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,
68 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259
69 };
70
71 static const quint32 stackBlur8Shr[255] =
72 {
73 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
74 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
75 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
76 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
77 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
78 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
79 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
80 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
81 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
82 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
83 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
84 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
85 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
86 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
87 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
88 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24
89 };
90
91 static void blurHorizontal(QImage& image, unsigned int* stack, int div, int radius)
92 {
93 int stackindex;
94 int stackstart;
95
96 quint32 * const pixels = reinterpret_cast<quint32 *>(image.bits());
97 quint32 pixel;
98
99 int w = image.width();
100 int h = image.height();
101 int wm = w - 1;
102
103 unsigned int mulSum = stackBlur8Mul[radius];
104 unsigned int shrSum = stackBlur8Shr[radius];
105
106 unsigned int sum, sumIn, sumOut;
107
108 for (int y = 0; y < h; y++) {
109 sum = 0;
110 sumIn = 0;
111 sumOut = 0;
112
113 const int yw = y * w;
114 pixel = pixels[yw];
115 for (int i = 0; i <= radius; i++) {
116 stack[i] = qAlpha(pixel);
117
118 sum += stack[i] * (i + 1);
119 sumOut += stack[i];
120 }
121
122 for (int i = 1; i <= radius; i++) {
123 pixel = pixels[yw + qMin(i, wm)];
124
125 unsigned int* stackpix = &stack[i + radius];
126 *stackpix = qAlpha(pixel);
127
128 sum += *stackpix * (radius + 1 - i);
129 sumIn += *stackpix;
130 }
131
132 stackindex = radius;
133 for (int x = 0, i = yw; x < w; x++) {
134 pixels[i++] = (((sum * mulSum) >> shrSum) << 24) & 0xff000000;
135
136 sum -= sumOut;
137
138 stackstart = stackindex + div - radius;
139 if (stackstart >= div) {
140 stackstart -= div;
141 }
142
143 unsigned int* stackpix = &stack[stackstart];
144
145 sumOut -= *stackpix;
146
147 pixel = pixels[yw + qMin(x + radius + 1, wm)];
148
149 *stackpix = qAlpha(pixel);
150
151 sumIn += *stackpix;
152 sum += sumIn;
153
154 if (++stackindex >= div) {
155 stackindex = 0;
156 }
157
158 stackpix = &stack[stackindex];
159
160 sumOut += *stackpix;
161 sumIn -= *stackpix;
162 }
163 }
164 }
165
166 static void blurVertical(QImage& image, unsigned int* stack, int div, int radius)
167 {
168 int stackindex;
169 int stackstart;
170
171 quint32 * const pixels = reinterpret_cast<quint32 *>(image.bits());
172 quint32 pixel;
173
174 int w = image.width();
175 int h = image.height();
176 int hm = h - 1;
177
178 int mul_sum = stackBlur8Mul[radius];
179 int shr_sum = stackBlur8Shr[radius];
180
181 unsigned int sum, sumIn, sumOut;
182
183 for (int x = 0; x < w; x++) {
184 sum = 0;
185 sumIn = 0;
186 sumOut = 0;
187
188 pixel = pixels[x];
189 for (int i = 0; i <= radius; i++) {
190 stack[i] = qAlpha(pixel);
191
192 sum += stack[i] * (i + 1);
193 sumOut += stack[i];
194 }
195
196 for (int i = 1; i <= radius; i++) {
197 pixel = pixels[qMin(i, hm) * w + x];
198
199 unsigned int* stackpix = &stack[i + radius];
200 *stackpix = qAlpha(pixel);
201
202 sum += *stackpix * (radius + 1 - i);
203 sumIn += *stackpix;
204 }
205
206 stackindex = radius;
207 for (int y = 0, i = x; y < h; y++, i += w) {
208 pixels[i] = (((sum * mul_sum) >> shr_sum) << 24) & 0xff000000;
209
210 sum -= sumOut;
211
212 stackstart = stackindex + div - radius;
213 if (stackstart >= div)
214 stackstart -= div;
215
216 unsigned int* stackpix = &stack[stackstart];
217
218 sumOut -= *stackpix;
219
220 pixel = pixels[qMin(y + radius + 1, hm) * w + x];
221
222 *stackpix = qAlpha(pixel);
223
224 sumIn += *stackpix;
225 sum += sumIn;
226
227 if (++stackindex >= div) {
228 stackindex = 0;
229 }
230
231 stackpix = &stack[stackindex];
232
233 sumOut += *stackpix;
234 sumIn -= *stackpix;
235 }
236 }
237 }
238
239 static void stackBlur(QImage& image, float radius)
240 {
241 radius = qRound(radius);
242
243 int div = int(radius * 2) + 1;
244 unsigned int* stack = new unsigned int[div];
245
246 blurHorizontal(image, stack, div, radius);
247 blurVertical(image, stack, div, radius);
248
249 delete [] stack;
250 }
251
252 static void shadowBlur(QImage& image, float radius, const QColor& color)
253 {
254 if (radius < 0) {
255 return;
256 }
257
258 if (radius > 0) {
259 stackBlur(image, radius);
260 }
261
262 // Correct the color and opacity of the shadow
263 QPainter p(&image);
264 p.setCompositionMode(QPainter::CompositionMode_SourceIn);
265 p.fillRect(image.rect(), color);
266 }
267
268 namespace {
269 /** Helper class for drawing frames for KPixmapModifier::applyFrame(). */
270 class TileSet
271 {
272 public:
273 enum { LeftMargin = 3, TopMargin = 2, RightMargin = 3, BottomMargin = 4 };
274
275 enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide,
276 RightSide, BottomLeftCorner, BottomSide, BottomRightCorner,
277 NumTiles };
278
279 TileSet()
280 {
281 QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied);
282
283 QPainter p(&image);
284 p.setCompositionMode(QPainter::CompositionMode_Source);
285 p.fillRect(image.rect(), Qt::transparent);
286 p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black);
287 p.end();
288
289 shadowBlur(image, 3, Qt::black);
290
291 QPixmap pixmap = QPixmap::fromImage(image);
292 m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8);
293 m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8);
294 m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8);
295 m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8);
296 m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8);
297 m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8);
298 m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8);
299 m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8);
300 }
301
302 void paint(QPainter* p, const QRect& r)
303 {
304 p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]);
305 if (r.width() - 16 > 0) {
306 p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]);
307 }
308 p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]);
309 if (r.height() - 16 > 0) {
310 p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]);
311 p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]);
312 }
313 p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]);
314 if (r.width() - 16 > 0) {
315 p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]);
316 }
317 p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]);
318
319 const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1,
320 -(RightMargin + 1), -(BottomMargin + 1));
321 p->fillRect(contentRect, Qt::transparent);
322 }
323
324 QPixmap m_tiles[NumTiles];
325 };
326 }
327
328 void KPixmapModifier::scale(QPixmap& pixmap, const QSize& scaledSize)
329 {
330 if (scaledSize.isEmpty()) {
331 pixmap = QPixmap();
332 return;
333 }
334
335 #if defined(Q_WS_X11) && defined(HAVE_XRENDER)
336 // Assume that the texture size limit is 2048x2048
337 if ((pixmap.width() <= 2048) && (pixmap.height() <= 2048) && pixmap.x11PictureHandle()) {
338 const QPixmap unscaledPixmap = pixmap.copy(); // Make a deep copy for XRender
339 QSize scaledPixmapSize = pixmap.size();
340 scaledPixmapSize.scale(scaledSize, Qt::KeepAspectRatio);
341
342 const qreal factor = scaledPixmapSize.width() / qreal(unscaledPixmap.width());
343
344 XTransform xform = {{
345 { XDoubleToFixed(1 / factor), 0, 0 },
346 { 0, XDoubleToFixed(1 / factor), 0 },
347 { 0, 0, XDoubleToFixed(1) }
348 }};
349
350 QPixmap scaledPixmap(scaledPixmapSize);
351 scaledPixmap.setDevicePixelRatio(pixmap.devicePixelRatio());
352 scaledPixmap.fill(Qt::transparent);
353
354 Display* dpy = QX11Info::display();
355
356 XRenderPictureAttributes attr;
357 attr.repeat = RepeatPad;
358 XRenderChangePicture(dpy, unscaledPixmap.x11PictureHandle(), CPRepeat, &attr);
359
360 XRenderSetPictureFilter(dpy, unscaledPixmap.x11PictureHandle(), FilterBilinear, 0, 0);
361 XRenderSetPictureTransform(dpy, unscaledPixmap.x11PictureHandle(), &xform);
362 XRenderComposite(dpy, PictOpOver, unscaledPixmap.x11PictureHandle(), None, scaledPixmap.x11PictureHandle(),
363 0, 0, 0, 0, 0, 0, scaledPixmap.width(), scaledPixmap.height());
364 pixmap = scaledPixmap;
365 } else {
366 pixmap = pixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
367 }
368 #else
369 qreal dpr = pixmap.devicePixelRatio();
370 pixmap = pixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
371 pixmap.setDevicePixelRatio(dpr);
372 #endif
373 }
374
375 void KPixmapModifier::applyFrame(QPixmap& icon, const QSize& scaledSize)
376 {
377 static TileSet tileSet;
378 qreal dpr = qApp->devicePixelRatio();
379
380 // Resize the icon to the maximum size minus the space required for the frame
381 const QSize size(scaledSize.width() - TileSet::LeftMargin - TileSet::RightMargin,
382 scaledSize.height() - TileSet::TopMargin - TileSet::BottomMargin);
383 scale(icon, size * dpr);
384 icon.setDevicePixelRatio(dpr);
385
386 QPixmap framedIcon(icon.size().width() + (TileSet::LeftMargin + TileSet::RightMargin) * dpr,
387 icon.size().height() + (TileSet::TopMargin + TileSet::BottomMargin) * dpr);
388 framedIcon.setDevicePixelRatio(dpr);
389 framedIcon.fill(Qt::transparent);
390
391 QPainter painter;
392 painter.begin(&framedIcon);
393 painter.setCompositionMode(QPainter::CompositionMode_Source);
394 tileSet.paint(&painter, QRect(QPoint(0,0), framedIcon.size() / dpr));
395 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
396 painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon);
397
398 icon = framedIcon;
399 }
400
401 QSize KPixmapModifier::sizeInsideFrame(const QSize& frameSize)
402 {
403 return QSize(frameSize.width() - TileSet::LeftMargin - TileSet::RightMargin,
404 frameSize.height() - TileSet::TopMargin - TileSet::BottomMargin);
405 }
406