// 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);
}

