// krazy:excludeall=copyright (email of Maxim is missing)
/*
    This file is a part of the KDE project

    SPDX-FileCopyrightText: 2006 Zack Rusin <zack@kde.org>
    SPDX-FileCopyrightText: 2006-2007, 2008 Fredrik Höglund <fredrik@kde.org>

    The stack blur algorithm was invented by Mario Klingemann <mario@quasimondo.com>

    This implementation is based on the version in Anti-Grain Geometry Version 2.4,
    SPDX-FileCopyrightText: 2002-2005 Maxim Shemanarev (http://www.antigrain.com)

    SPDX-License-Identifier: BSD-2-Clause
*/

#include "kpixmapmodifier.h"

#include <QGuiApplication>
#include <QImage>
#include <QPainter>

static const quint32 stackBlur8Mul[255] = {
    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,
    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,
    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,
    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,
    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,
    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,
    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,
    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,
    310, 307, 304, 302, 299, 297, 294, 292, 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259};

static const quint32 stackBlur8Shr[255] = {
    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,
    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,
    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,
    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,
    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,
    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,
    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};

static void blurHorizontal(QImage &image, unsigned int *stack, int div, int radius)
{
    int stackindex;
    int stackstart;

    quint32 *const pixels = reinterpret_cast<quint32 *>(image.bits());
    quint32 pixel;

    int w = image.width();
    int h = image.height();
    int wm = w - 1;

    unsigned int mulSum = stackBlur8Mul[radius];
    unsigned int shrSum = stackBlur8Shr[radius];

    unsigned int sum, sumIn, sumOut;

    for (int y = 0; y < h; y++) {
        sum = 0;
        sumIn = 0;
        sumOut = 0;

        const int yw = y * w;
        pixel = pixels[yw];
        for (int i = 0; i <= radius; i++) {
            stack[i] = qAlpha(pixel);

            sum += stack[i] * (i + 1);
            sumOut += stack[i];
        }

        for (int i = 1; i <= radius; i++) {
            pixel = pixels[yw + qMin(i, wm)];

            unsigned int *stackpix = &stack[i + radius];
            *stackpix = qAlpha(pixel);

            sum += *stackpix * (radius + 1 - i);
            sumIn += *stackpix;
        }

        stackindex = radius;
        for (int x = 0, i = yw; x < w; x++) {
            pixels[i++] = (((sum * mulSum) >> shrSum) << 24) & 0xff000000;

            sum -= sumOut;

            stackstart = stackindex + div - radius;
            if (stackstart >= div) {
                stackstart -= div;
            }

            unsigned int *stackpix = &stack[stackstart];

            sumOut -= *stackpix;

            pixel = pixels[yw + qMin(x + radius + 1, wm)];

            *stackpix = qAlpha(pixel);

            sumIn += *stackpix;
            sum += sumIn;

            if (++stackindex >= div) {
                stackindex = 0;
            }

            stackpix = &stack[stackindex];

            sumOut += *stackpix;
            sumIn -= *stackpix;
        }
    }
}

static void blurVertical(QImage &image, unsigned int *stack, int div, int radius)
{
    int stackindex;
    int stackstart;

    quint32 *const pixels = reinterpret_cast<quint32 *>(image.bits());
    quint32 pixel;

    int w = image.width();
    int h = image.height();
    int hm = h - 1;

    int mul_sum = stackBlur8Mul[radius];
    int shr_sum = stackBlur8Shr[radius];

    unsigned int sum, sumIn, sumOut;

    for (int x = 0; x < w; x++) {
        sum = 0;
        sumIn = 0;
        sumOut = 0;

        pixel = pixels[x];
        for (int i = 0; i <= radius; i++) {
            stack[i] = qAlpha(pixel);

            sum += stack[i] * (i + 1);
            sumOut += stack[i];
        }

        for (int i = 1; i <= radius; i++) {
            pixel = pixels[qMin(i, hm) * w + x];

            unsigned int *stackpix = &stack[i + radius];
            *stackpix = qAlpha(pixel);

            sum += *stackpix * (radius + 1 - i);
            sumIn += *stackpix;
        }

        stackindex = radius;
        for (int y = 0, i = x; y < h; y++, i += w) {
            pixels[i] = (((sum * mul_sum) >> shr_sum) << 24) & 0xff000000;

            sum -= sumOut;

            stackstart = stackindex + div - radius;
            if (stackstart >= div)
                stackstart -= div;

            unsigned int *stackpix = &stack[stackstart];

            sumOut -= *stackpix;

            pixel = pixels[qMin(y + radius + 1, hm) * w + x];

            *stackpix = qAlpha(pixel);

            sumIn += *stackpix;
            sum += sumIn;

            if (++stackindex >= div) {
                stackindex = 0;
            }

            stackpix = &stack[stackindex];

            sumOut += *stackpix;
            sumIn -= *stackpix;
        }
    }
}

static void stackBlur(QImage &image, float radius)
{
    radius = qRound(radius);

    int div = int(radius * 2) + 1;
    unsigned int *stack = new unsigned int[div];

    blurHorizontal(image, stack, div, radius);
    blurVertical(image, stack, div, radius);

    delete[] stack;
}

static void shadowBlur(QImage &image, float radius, const QColor &color)
{
    if (radius < 0) {
        return;
    }

    if (radius > 0) {
        stackBlur(image, radius);
    }

    // Correct the color and opacity of the shadow
    QPainter p(&image);
    p.setCompositionMode(QPainter::CompositionMode_SourceIn);
    p.fillRect(image.rect(), color);
}

namespace
{
/** Helper class for drawing frames for KPixmapModifier::applyFrame(). */
class TileSet
{
public:
    enum { LeftMargin = 3, TopMargin = 2, RightMargin = 3, BottomMargin = 4 };

    enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide, RightSide, BottomLeftCorner, BottomSide, BottomRightCorner, NumTiles };

    TileSet()
    {
        QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied);

        QPainter p(&image);
        p.setCompositionMode(QPainter::CompositionMode_Source);
        p.fillRect(image.rect(), Qt::transparent);
        p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black);
        p.end();

        shadowBlur(image, 3, Qt::black);

        QPixmap pixmap = QPixmap::fromImage(image);
        m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8);
        m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8);
        m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8);
        m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8);
        m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8);
        m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8);
        m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8);
        m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8);
    }

    void paint(QPainter *p, const QRect &r)
    {
        p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]);
        if (r.width() - 16 > 0) {
            p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]);
        }
        p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]);
        if (r.height() - 16 > 0) {
            p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]);
            p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]);
        }
        p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]);
        if (r.width() - 16 > 0) {
            p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]);
        }
        p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]);

        const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1, -(RightMargin + 1), -(BottomMargin + 1));
        p->fillRect(contentRect, Qt::transparent);
    }

    QPixmap m_tiles[NumTiles];
};
}

void KPixmapModifier::scale(QPixmap &pixmap, const QSize &scaledSize)
{
    if (scaledSize.isEmpty() || pixmap.isNull()) {
        pixmap = QPixmap();
        return;
    }
    qreal dpr = pixmap.devicePixelRatio();
    pixmap = pixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
    pixmap.setDevicePixelRatio(dpr);
}

void KPixmapModifier::applyFrame(QPixmap &icon, const QSize &scaledSize)
{
    if (icon.isNull()) {
        icon = QPixmap(scaledSize);
        icon.fill(Qt::transparent);
        return;
    }

    static TileSet tileSet;
    qreal dpr = qApp->devicePixelRatio();

    // Resize the icon to the maximum size minus the space required for the frame
    const QSize size(scaledSize.width() - TileSet::LeftMargin - TileSet::RightMargin, scaledSize.height() - TileSet::TopMargin - TileSet::BottomMargin);
    scale(icon, size * dpr);
    icon.setDevicePixelRatio(dpr);

    QPixmap framedIcon(icon.size().width() + (TileSet::LeftMargin + TileSet::RightMargin) * dpr,
                       icon.size().height() + (TileSet::TopMargin + TileSet::BottomMargin) * dpr);
    framedIcon.setDevicePixelRatio(dpr);
    framedIcon.fill(Qt::transparent);

    QPainter painter;
    painter.begin(&framedIcon);
    painter.setCompositionMode(QPainter::CompositionMode_Source);
    tileSet.paint(&painter, QRect(QPoint(0, 0), framedIcon.size() / dpr));
    painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
    painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon);

    icon = framedIcon;
}

QSize KPixmapModifier::sizeInsideFrame(const QSize &frameSize)
{
    return QSize(frameSize.width() - TileSet::LeftMargin - TileSet::RightMargin, frameSize.height() - TileSet::TopMargin - TileSet::BottomMargin);
}
