/* This file is part of the KDE project
 * SPDX-FileCopyrightText: 2010 Jan Hambrecht <jaham@gmx.net>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "MorphologyEffect.h"
#include "KoFilterEffectLoadingContext.h"
#include "KoFilterEffectRenderContext.h"
#include "KoViewConverter.h"
#include "KoXmlReader.h"
#include "KoXmlWriter.h"
#include <KLocalizedString>
#include <QImage>
#include <QRect>
#include <cmath>

MorphologyEffect::MorphologyEffect()
    : KoFilterEffect(MorphologyEffectId, i18n("Morphology"))
    , m_radius(0, 0)
    , m_operator(Erode)
{
}

QPointF MorphologyEffect::morphologyRadius() const
{
    return m_radius;
}

void MorphologyEffect::setMorphologyRadius(const QPointF &radius)
{
    m_radius = radius;
}

MorphologyEffect::Operator MorphologyEffect::morphologyOperator() const
{
    return m_operator;
}

void MorphologyEffect::setMorphologyOperator(Operator op)
{
    m_operator = op;
}

QImage MorphologyEffect::processImage(const QImage &image, const KoFilterEffectRenderContext &context) const
{
    QImage result = image;

    QPointF radius = context.toUserSpace(m_radius);

    const int rx = static_cast<int>(ceil(radius.x()));
    const int ry = static_cast<int>(ceil(radius.y()));

    const int w = result.width();
    const int h = result.height();

    // setup mask
    const int maskSize = (1 + 2 * rx) * (1 + 2 * ry);
    int *mask = new int[maskSize];
    int index = 0;
    for (int y = -ry; y <= ry; ++y) {
        for (int x = -rx; x <= rx; ++x) {
            mask[index] = y * w + x;
            index++;
        }
    }

    int dstPixel, srcPixel;
    uchar s0, s1, s2, s3;
    const uchar *src = image.constBits();
    uchar *dst = result.bits();

    const QRect roi = context.filterRegion().toRect();
    const int minX = qMax(rx, roi.left());
    const int maxX = qMin(w - rx, roi.right());
    const int minY = qMax(ry, roi.top());
    const int maxY = qMin(h - ry, roi.bottom());
    const int defValue = m_operator == Erode ? 255 : 0;

    uchar *d = nullptr;

    for (int row = minY; row < maxY; ++row) {
        for (int col = minX; col < maxX; ++col) {
            dstPixel = row * w + col;
            s0 = s1 = s2 = s3 = defValue;
            for (int i = 0; i < maskSize; ++i) {
                srcPixel = dstPixel + mask[i];
                const uchar *s = &src[4 * srcPixel];
                if (m_operator == Erode) {
                    s0 = qMin(s0, s[0]);
                    s1 = qMin(s1, s[1]);
                    s2 = qMin(s2, s[2]);
                    s3 = qMin(s3, s[3]);
                } else {
                    s0 = qMax(s0, s[0]);
                    s1 = qMax(s1, s[1]);
                    s2 = qMax(s2, s[2]);
                    s3 = qMax(s3, s[3]);
                }
            }
            d = &dst[4 * dstPixel];
            d[0] = s0;
            d[1] = s1;
            d[2] = s2;
            d[3] = s3;
        }
    }

    delete[] mask;

    return result;
}

bool MorphologyEffect::load(const KoXmlElement &element, const KoFilterEffectLoadingContext &context)
{
    if (element.tagName() != id())
        return false;

    m_radius = QPointF();
    m_operator = Erode;

    if (element.hasAttribute("radius")) {
        QString radiusStr = element.attribute("radius").trimmed();
        QStringList params = radiusStr.replace(',', ' ').simplified().split(' ');
        switch (params.count()) {
        case 1:
            m_radius.rx() = params[0].toDouble() * 72. / 90.;
            m_radius.ry() = m_radius.x();
            break;
        case 2:
            m_radius.rx() = params[0].toDouble() * 72. / 90.;
            m_radius.ry() = params[1].toDouble() * 72. / 90.;
            break;
        default:
            m_radius = QPointF();
        }
    }

    m_radius = context.convertFilterPrimitiveUnits(m_radius);

    if (element.hasAttribute("operator")) {
        QString op = element.attribute("operator");
        if (op == "dilate")
            m_operator = Dilate;
    }

    return true;
}

void MorphologyEffect::save(KoXmlWriter &writer)
{
    writer.startElement(MorphologyEffectId);

    saveCommonAttributes(writer);

    if (m_operator != Erode)
        writer.addAttribute("operator", "dilate");
    if (!m_radius.isNull()) {
        if (m_radius.x() == m_radius.y()) {
            writer.addAttribute("radius", QString("%1").arg(m_radius.x()));
        } else {
            writer.addAttribute("radius", QString("%1 %2").arg(m_radius.x()).arg(m_radius.y()));
        }
    }

    writer.endElement();
}
