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 }