File indexing completed on 2024-05-12 05:47:31
0001 // krazy:excludeall=copyright (email of Maxim is missing) 0002 /* 0003 This file is a part of the KDE project 0004 0005 SPDX-FileCopyrightText: 2006 Zack Rusin <zack@kde.org> 0006 SPDX-FileCopyrightText: 2006-2007, 2008 Fredrik Höglund <fredrik@kde.org> 0007 0008 The stack blur algorithm was invented by Mario Klingemann <mario@quasimondo.com> 0009 0010 This implementation is based on the version in Anti-Grain Geometry Version 2.4, 0011 SPDX-FileCopyrightText: 2002-2005 Maxim Shemanarev (http://www.antigrain.com) 0012 0013 SPDX-License-Identifier: BSD-2-Clause 0014 */ 0015 0016 #include "kpixmapmodifier.h" 0017 0018 #include <QGuiApplication> 0019 #include <QImage> 0020 #include <QPainter> 0021 0022 static const quint32 stackBlur8Mul[255] = { 0023 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 0024 292, 273, 512, 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 0025 302, 292, 282, 273, 265, 512, 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 320, 312, 305, 298, 291, 284, 278, 0026 271, 265, 259, 507, 496, 485, 475, 465, 456, 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 312, 0027 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405, 399, 0028 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 0029 274, 271, 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 0030 404, 400, 396, 392, 388, 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 332, 329, 326, 323, 320, 318, 315, 312, 0031 310, 307, 304, 302, 299, 297, 294, 292, 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259}; 0032 0033 static const quint32 stackBlur8Shr[255] = { 0034 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 0035 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 0036 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 0037 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 0038 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 0039 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0040 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24}; 0041 0042 static void blurHorizontal(QImage &image, unsigned int *stack, int div, int radius) 0043 { 0044 int stackindex; 0045 int stackstart; 0046 0047 quint32 *const pixels = reinterpret_cast<quint32 *>(image.bits()); 0048 quint32 pixel; 0049 0050 int w = image.width(); 0051 int h = image.height(); 0052 int wm = w - 1; 0053 0054 unsigned int mulSum = stackBlur8Mul[radius]; 0055 unsigned int shrSum = stackBlur8Shr[radius]; 0056 0057 unsigned int sum, sumIn, sumOut; 0058 0059 for (int y = 0; y < h; y++) { 0060 sum = 0; 0061 sumIn = 0; 0062 sumOut = 0; 0063 0064 const int yw = y * w; 0065 pixel = pixels[yw]; 0066 for (int i = 0; i <= radius; i++) { 0067 stack[i] = qAlpha(pixel); 0068 0069 sum += stack[i] * (i + 1); 0070 sumOut += stack[i]; 0071 } 0072 0073 for (int i = 1; i <= radius; i++) { 0074 pixel = pixels[yw + qMin(i, wm)]; 0075 0076 unsigned int *stackpix = &stack[i + radius]; 0077 *stackpix = qAlpha(pixel); 0078 0079 sum += *stackpix * (radius + 1 - i); 0080 sumIn += *stackpix; 0081 } 0082 0083 stackindex = radius; 0084 for (int x = 0, i = yw; x < w; x++) { 0085 pixels[i++] = (((sum * mulSum) >> shrSum) << 24) & 0xff000000; 0086 0087 sum -= sumOut; 0088 0089 stackstart = stackindex + div - radius; 0090 if (stackstart >= div) { 0091 stackstart -= div; 0092 } 0093 0094 unsigned int *stackpix = &stack[stackstart]; 0095 0096 sumOut -= *stackpix; 0097 0098 pixel = pixels[yw + qMin(x + radius + 1, wm)]; 0099 0100 *stackpix = qAlpha(pixel); 0101 0102 sumIn += *stackpix; 0103 sum += sumIn; 0104 0105 if (++stackindex >= div) { 0106 stackindex = 0; 0107 } 0108 0109 stackpix = &stack[stackindex]; 0110 0111 sumOut += *stackpix; 0112 sumIn -= *stackpix; 0113 } 0114 } 0115 } 0116 0117 static void blurVertical(QImage &image, unsigned int *stack, int div, int radius) 0118 { 0119 int stackindex; 0120 int stackstart; 0121 0122 quint32 *const pixels = reinterpret_cast<quint32 *>(image.bits()); 0123 quint32 pixel; 0124 0125 int w = image.width(); 0126 int h = image.height(); 0127 int hm = h - 1; 0128 0129 int mul_sum = stackBlur8Mul[radius]; 0130 int shr_sum = stackBlur8Shr[radius]; 0131 0132 unsigned int sum, sumIn, sumOut; 0133 0134 for (int x = 0; x < w; x++) { 0135 sum = 0; 0136 sumIn = 0; 0137 sumOut = 0; 0138 0139 pixel = pixels[x]; 0140 for (int i = 0; i <= radius; i++) { 0141 stack[i] = qAlpha(pixel); 0142 0143 sum += stack[i] * (i + 1); 0144 sumOut += stack[i]; 0145 } 0146 0147 for (int i = 1; i <= radius; i++) { 0148 pixel = pixels[qMin(i, hm) * w + x]; 0149 0150 unsigned int *stackpix = &stack[i + radius]; 0151 *stackpix = qAlpha(pixel); 0152 0153 sum += *stackpix * (radius + 1 - i); 0154 sumIn += *stackpix; 0155 } 0156 0157 stackindex = radius; 0158 for (int y = 0, i = x; y < h; y++, i += w) { 0159 pixels[i] = (((sum * mul_sum) >> shr_sum) << 24) & 0xff000000; 0160 0161 sum -= sumOut; 0162 0163 stackstart = stackindex + div - radius; 0164 if (stackstart >= div) 0165 stackstart -= div; 0166 0167 unsigned int *stackpix = &stack[stackstart]; 0168 0169 sumOut -= *stackpix; 0170 0171 pixel = pixels[qMin(y + radius + 1, hm) * w + x]; 0172 0173 *stackpix = qAlpha(pixel); 0174 0175 sumIn += *stackpix; 0176 sum += sumIn; 0177 0178 if (++stackindex >= div) { 0179 stackindex = 0; 0180 } 0181 0182 stackpix = &stack[stackindex]; 0183 0184 sumOut += *stackpix; 0185 sumIn -= *stackpix; 0186 } 0187 } 0188 } 0189 0190 static void stackBlur(QImage &image, float radius) 0191 { 0192 radius = qRound(radius); 0193 0194 int div = int(radius * 2) + 1; 0195 unsigned int *stack = new unsigned int[div]; 0196 0197 blurHorizontal(image, stack, div, radius); 0198 blurVertical(image, stack, div, radius); 0199 0200 delete[] stack; 0201 } 0202 0203 static void shadowBlur(QImage &image, float radius, const QColor &color) 0204 { 0205 if (radius < 0) { 0206 return; 0207 } 0208 0209 if (radius > 0) { 0210 stackBlur(image, radius); 0211 } 0212 0213 // Correct the color and opacity of the shadow 0214 QPainter p(&image); 0215 p.setCompositionMode(QPainter::CompositionMode_SourceIn); 0216 p.fillRect(image.rect(), color); 0217 } 0218 0219 namespace 0220 { 0221 /** Helper class for drawing frames for KPixmapModifier::applyFrame(). */ 0222 class TileSet 0223 { 0224 public: 0225 enum { LeftMargin = 3, TopMargin = 2, RightMargin = 3, BottomMargin = 4 }; 0226 0227 enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide, RightSide, BottomLeftCorner, BottomSide, BottomRightCorner, NumTiles }; 0228 0229 TileSet() 0230 { 0231 QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied); 0232 0233 QPainter p(&image); 0234 p.setCompositionMode(QPainter::CompositionMode_Source); 0235 p.fillRect(image.rect(), Qt::transparent); 0236 p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black); 0237 p.end(); 0238 0239 shadowBlur(image, 3, Qt::black); 0240 0241 QPixmap pixmap = QPixmap::fromImage(image); 0242 m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8); 0243 m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8); 0244 m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8); 0245 m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8); 0246 m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8); 0247 m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8); 0248 m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8); 0249 m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8); 0250 } 0251 0252 void paint(QPainter *p, const QRect &r) 0253 { 0254 p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]); 0255 if (r.width() - 16 > 0) { 0256 p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]); 0257 } 0258 p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]); 0259 if (r.height() - 16 > 0) { 0260 p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]); 0261 p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]); 0262 } 0263 p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]); 0264 if (r.width() - 16 > 0) { 0265 p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]); 0266 } 0267 p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]); 0268 0269 const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1, -(RightMargin + 1), -(BottomMargin + 1)); 0270 p->fillRect(contentRect, Qt::transparent); 0271 } 0272 0273 QPixmap m_tiles[NumTiles]; 0274 }; 0275 } 0276 0277 void KPixmapModifier::scale(QPixmap &pixmap, const QSize &scaledSize) 0278 { 0279 if (scaledSize.isEmpty() || pixmap.isNull()) { 0280 pixmap = QPixmap(); 0281 return; 0282 } 0283 qreal dpr = pixmap.devicePixelRatio(); 0284 pixmap = pixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); 0285 pixmap.setDevicePixelRatio(dpr); 0286 } 0287 0288 void KPixmapModifier::applyFrame(QPixmap &icon, const QSize &scaledSize) 0289 { 0290 if (icon.isNull()) { 0291 icon = QPixmap(scaledSize); 0292 icon.fill(Qt::transparent); 0293 return; 0294 } 0295 0296 static TileSet tileSet; 0297 qreal dpr = qApp->devicePixelRatio(); 0298 0299 // Resize the icon to the maximum size minus the space required for the frame 0300 const QSize size(scaledSize.width() - TileSet::LeftMargin - TileSet::RightMargin, scaledSize.height() - TileSet::TopMargin - TileSet::BottomMargin); 0301 scale(icon, size * dpr); 0302 icon.setDevicePixelRatio(dpr); 0303 0304 QPixmap framedIcon(icon.size().width() + (TileSet::LeftMargin + TileSet::RightMargin) * dpr, 0305 icon.size().height() + (TileSet::TopMargin + TileSet::BottomMargin) * dpr); 0306 framedIcon.setDevicePixelRatio(dpr); 0307 framedIcon.fill(Qt::transparent); 0308 0309 QPainter painter; 0310 painter.begin(&framedIcon); 0311 painter.setCompositionMode(QPainter::CompositionMode_Source); 0312 tileSet.paint(&painter, QRect(QPoint(0, 0), framedIcon.size() / dpr)); 0313 painter.setCompositionMode(QPainter::CompositionMode_SourceOver); 0314 painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon); 0315 0316 icon = framedIcon; 0317 } 0318 0319 QSize KPixmapModifier::sizeInsideFrame(const QSize &frameSize) 0320 { 0321 return QSize(frameSize.width() - TileSet::LeftMargin - TileSet::RightMargin, frameSize.height() - TileSet::TopMargin - TileSet::BottomMargin); 0322 }