1 // krazy:excludeall=copyright (email of Maxim is missing)
3 This file is a part of the KDE project
5 Copyright © 2006 Zack Rusin <zack@kde.org>
6 Copyright © 2006-2007, 2008 Fredrik Höglund <fredrik@kde.org>
8 The stack blur algorithm was invented by Mario Klingemann <mario@quasimondo.com>
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)
13 Redistribution and use in source and binary forms, with or without
14 modification, are permitted provided that the following conditions
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.
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.
35 #include "kpixmapmodifier.h"
41 #include <QGuiApplication>
44 #include <config-X11.h> // for HAVE_XRENDER
45 #if defined(Q_WS_X11) && defined(HAVE_XRENDER)
47 # include <X11/Xlib.h>
48 # include <X11/extensions/Xrender.h>
51 static const quint32 stackBlur8Mul
[255] =
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
71 static const quint32 stackBlur8Shr
[255] =
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
91 static void blurHorizontal(QImage
& image
, unsigned int* stack
, int div
, int radius
)
96 quint32
* const pixels
= reinterpret_cast<quint32
*>(image
.bits());
99 int w
= image
.width();
100 int h
= image
.height();
103 unsigned int mulSum
= stackBlur8Mul
[radius
];
104 unsigned int shrSum
= stackBlur8Shr
[radius
];
106 unsigned int sum
, sumIn
, sumOut
;
108 for (int y
= 0; y
< h
; y
++) {
113 const int yw
= y
* w
;
115 for (int i
= 0; i
<= radius
; i
++) {
116 stack
[i
] = qAlpha(pixel
);
118 sum
+= stack
[i
] * (i
+ 1);
122 for (int i
= 1; i
<= radius
; i
++) {
123 pixel
= pixels
[yw
+ qMin(i
, wm
)];
125 unsigned int* stackpix
= &stack
[i
+ radius
];
126 *stackpix
= qAlpha(pixel
);
128 sum
+= *stackpix
* (radius
+ 1 - i
);
133 for (int x
= 0, i
= yw
; x
< w
; x
++) {
134 pixels
[i
++] = (((sum
* mulSum
) >> shrSum
) << 24) & 0xff000000;
138 stackstart
= stackindex
+ div
- radius
;
139 if (stackstart
>= div
) {
143 unsigned int* stackpix
= &stack
[stackstart
];
147 pixel
= pixels
[yw
+ qMin(x
+ radius
+ 1, wm
)];
149 *stackpix
= qAlpha(pixel
);
154 if (++stackindex
>= div
) {
158 stackpix
= &stack
[stackindex
];
166 static void blurVertical(QImage
& image
, unsigned int* stack
, int div
, int radius
)
171 quint32
* const pixels
= reinterpret_cast<quint32
*>(image
.bits());
174 int w
= image
.width();
175 int h
= image
.height();
178 int mul_sum
= stackBlur8Mul
[radius
];
179 int shr_sum
= stackBlur8Shr
[radius
];
181 unsigned int sum
, sumIn
, sumOut
;
183 for (int x
= 0; x
< w
; x
++) {
189 for (int i
= 0; i
<= radius
; i
++) {
190 stack
[i
] = qAlpha(pixel
);
192 sum
+= stack
[i
] * (i
+ 1);
196 for (int i
= 1; i
<= radius
; i
++) {
197 pixel
= pixels
[qMin(i
, hm
) * w
+ x
];
199 unsigned int* stackpix
= &stack
[i
+ radius
];
200 *stackpix
= qAlpha(pixel
);
202 sum
+= *stackpix
* (radius
+ 1 - i
);
207 for (int y
= 0, i
= x
; y
< h
; y
++, i
+= w
) {
208 pixels
[i
] = (((sum
* mul_sum
) >> shr_sum
) << 24) & 0xff000000;
212 stackstart
= stackindex
+ div
- radius
;
213 if (stackstart
>= div
)
216 unsigned int* stackpix
= &stack
[stackstart
];
220 pixel
= pixels
[qMin(y
+ radius
+ 1, hm
) * w
+ x
];
222 *stackpix
= qAlpha(pixel
);
227 if (++stackindex
>= div
) {
231 stackpix
= &stack
[stackindex
];
239 static void stackBlur(QImage
& image
, float radius
)
241 radius
= qRound(radius
);
243 int div
= int(radius
* 2) + 1;
244 unsigned int* stack
= new unsigned int[div
];
246 blurHorizontal(image
, stack
, div
, radius
);
247 blurVertical(image
, stack
, div
, radius
);
252 static void shadowBlur(QImage
& image
, float radius
, const QColor
& color
)
259 stackBlur(image
, radius
);
262 // Correct the color and opacity of the shadow
264 p
.setCompositionMode(QPainter::CompositionMode_SourceIn
);
265 p
.fillRect(image
.rect(), color
);
269 /** Helper class for drawing frames for KPixmapModifier::applyFrame(). */
273 enum { LeftMargin
= 3, TopMargin
= 2, RightMargin
= 3, BottomMargin
= 4 };
275 enum Tile
{ TopLeftCorner
= 0, TopSide
, TopRightCorner
, LeftSide
,
276 RightSide
, BottomLeftCorner
, BottomSide
, BottomRightCorner
,
281 QImage
image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied
);
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
);
289 shadowBlur(image
, 3, Qt::black
);
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);
302 void paint(QPainter
* p
, const QRect
& r
)
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
]);
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
]);
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
]);
317 p
->drawPixmap(r
.right() - 8 + 1, r
.bottom() - 8 + 1, m_tiles
[BottomRightCorner
]);
319 const QRect contentRect
= r
.adjusted(LeftMargin
+ 1, TopMargin
+ 1,
320 -(RightMargin
+ 1), -(BottomMargin
+ 1));
321 p
->fillRect(contentRect
, Qt::transparent
);
324 QPixmap m_tiles
[NumTiles
];
328 void KPixmapModifier::scale(QPixmap
& pixmap
, const QSize
& scaledSize
)
330 if (scaledSize
.isEmpty()) {
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
);
342 const qreal factor
= scaledPixmapSize
.width() / qreal(unscaledPixmap
.width());
344 XTransform xform
= {{
345 { XDoubleToFixed(1 / factor
), 0, 0 },
346 { 0, XDoubleToFixed(1 / factor
), 0 },
347 { 0, 0, XDoubleToFixed(1) }
350 QPixmap
scaledPixmap(scaledPixmapSize
);
351 scaledPixmap
.setDevicePixelRatio(pixmap
.devicePixelRatio());
352 scaledPixmap
.fill(Qt::transparent
);
354 Display
* dpy
= QX11Info::display();
356 XRenderPictureAttributes attr
;
357 attr
.repeat
= RepeatPad
;
358 XRenderChangePicture(dpy
, unscaledPixmap
.x11PictureHandle(), CPRepeat
, &attr
);
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
;
366 pixmap
= pixmap
.scaled(scaledSize
, Qt::KeepAspectRatio
, Qt::SmoothTransformation
);
369 qreal dpr
= pixmap
.devicePixelRatio();
370 pixmap
= pixmap
.scaled(scaledSize
, Qt::KeepAspectRatio
, Qt::SmoothTransformation
);
371 pixmap
.setDevicePixelRatio(dpr
);
375 void KPixmapModifier::applyFrame(QPixmap
& icon
, const QSize
& scaledSize
)
377 static TileSet tileSet
;
378 qreal dpr
= qApp
->devicePixelRatio();
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
);
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
);
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
);
401 QSize
KPixmapModifier::sizeInsideFrame(const QSize
& frameSize
)
403 return QSize(frameSize
.width() - TileSet::LeftMargin
- TileSet::RightMargin
,
404 frameSize
.height() - TileSet::TopMargin
- TileSet::BottomMargin
);