
/*
   SPDX-FileCopyrightText: 2003-2007 Clarence Dang <dang@kde.org>
   SPDX-FileCopyrightText: 2011 Martin Koller <kollix@aon.at>

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

#define DEBUG_KP_EFFECT_REDUCE_COLORS 0

#include "imagelib/effects/kpEffectReduceColors.h"

#include "kpLogCategories.h"

//---------------------------------------------------------------------

static QImage::Format DepthToFormat(int depth)
{
    // These values are QImage's supported depths.
    switch (depth) {
    case 1:
        // (can be MSB instead, I suppose)
        return QImage::Format_MonoLSB;

    case 8:
        return QImage::Format_Indexed8;

    case 16:
        return QImage::Format_ARGB4444_Premultiplied;

    case 24:
        return QImage::Format_ARGB6666_Premultiplied;

    case 32:
        return QImage::Format_ARGB32_Premultiplied;

    default:
        Q_ASSERT(!"unknown depth");
        return QImage::Format_Invalid;
    }
}

//---------------------------------------------------------------------

// public static
QImage kpEffectReduceColors::convertImageDepth(const QImage &image, int depth, bool dither)
{
#if DEBUG_KP_EFFECT_REDUCE_COLORS
    qCDebug(kpLogImagelib) << "kpeffectreducecolors.cpp:ConvertImageDepth() changing image (w=" << image.width() << ",h=" << image.height() << ") depth from "
                           << image.depth() << " to " << depth << " (dither=" << dither << ")" << endl;
#endif

    if (image.isNull()) {
        return image;
    }

    if (depth == image.depth()) {
        return image;
    }

#if DEBUG_KP_EFFECT_REDUCE_COLORS && 0
    for (int y = 0; y < image.height(); y++) {
        for (int x = 0; x < image.width(); x++) {
            fprintf(stderr, " %08X", image.pixel(x, y));
        }
        fprintf(stderr, "\n");
    }
#endif

    // Hack around Qt's braindead QImage::convertToFormat(QImage::Format_MonoLSB, ...)
    // (with dithering off) which produces pathetic results with an image that
    // only has 2 colors - sometimes it just gives a completely black
    // result (try yellow and white as input).  Instead, we simply preserve
    // the 2 colors.
    //
    // One use case is resaving a "color monochrome" image (<= 2 colors but
    // not necessarily black & white).
    if (depth == 1 && !dither) {
#if DEBUG_KP_EFFECT_REDUCE_COLORS
        qCDebug(kpLogImagelib) << "\tinvoking convert-to-depth 1 hack";
#endif
        QRgb color0 = 0, color1 = 0;
        bool color0Valid = false, color1Valid = false;

        bool moreThan2Colors = false;

        QImage monoImage(image.width(), image.height(), QImage::Format_MonoLSB);
        monoImage.setColorCount(2);
#if DEBUG_KP_EFFECT_REDUCE_COLORS
        qCDebug(kpLogImagelib) << "\t\tinitialising output image w=" << monoImage.width() << ",h=" << monoImage.height() << ",d=" << monoImage.depth();
#endif
        for (int y = 0; y < image.height(); y++) {
            for (int x = 0; x < image.width(); x++) {
                // (this can be transparent)
                QRgb imagePixel = image.pixel(x, y);

                if (color0Valid && imagePixel == color0) {
                    monoImage.setPixel(x, y, 0);
                } else if (color1Valid && imagePixel == color1) {
                    monoImage.setPixel(x, y, 1);
                } else if (!color0Valid) {
                    color0 = imagePixel;
                    color0Valid = true;
                    monoImage.setPixel(x, y, 0);
#if DEBUG_KP_EFFECT_REDUCE_COLORS
                    qCDebug(kpLogImagelib) << "\t\t\tcolor0=" << (int *)color0 << " at x=" << x << ",y=" << y;
#endif
                } else if (!color1Valid) {
                    color1 = imagePixel;
                    color1Valid = true;
                    monoImage.setPixel(x, y, 1);
#if DEBUG_KP_EFFECT_REDUCE_COLORS
                    qCDebug(kpLogImagelib) << "\t\t\tcolor1=" << (int *)color1 << " at x=" << x << ",y=" << y;
#endif
                } else {
#if DEBUG_KP_EFFECT_REDUCE_COLORS
                    qCDebug(kpLogImagelib) << "\t\t\timagePixel=" << (int *)imagePixel << " at x=" << x << ",y=" << y << " moreThan2Colors - abort hack";
#endif
                    moreThan2Colors = true;

                    // Dijkstra, this is clearer than double break'ing or
                    // a check in both loops
                    goto exit_loop;
                }
            }
        }
    exit_loop:

        if (!moreThan2Colors) {
            monoImage.setColor(0, color0Valid ? color0 : 0xFFFFFF);
            monoImage.setColor(1, color1Valid ? color1 : 0x000000);
            return monoImage;
        }
    }

    QImage retImage = image.convertToFormat(::DepthToFormat(depth),
                                            Qt::AutoColor | (dither ? Qt::DiffuseDither : Qt::ThresholdDither) | Qt::ThresholdAlphaDither
                                                | (dither ? Qt::PreferDither : Qt::AvoidDither));
#if DEBUG_KP_EFFECT_REDUCE_COLORS
    qCDebug(kpLogImagelib) << "\tformat: before=" << image.format() << "after=" << retImage.format();
#endif

#if DEBUG_KP_EFFECT_REDUCE_COLORS && 0
    qCDebug(kpLogImagelib) << "After color reduction:";
    for (int y = 0; y < image.height(); y++) {
        for (int x = 0; x < image.width(); x++) {
            fprintf(stderr, " %08X", image.pixel(x, y));
        }
        fprintf(stderr, "\n");
    }
#endif

    return retImage;
}

//---------------------------------------------------------------------

// public static
void kpEffectReduceColors::applyEffect(QImage *destPtr, int depth, bool dither)
{
    if (!destPtr) {
        return;
    }

    // You can't "reduce" to 32-bit since it's the highest depth.
    if (depth != 1 && depth != 8) {
        return;
    }

    *destPtr = convertImageDepth(*destPtr, depth, dither);

    // internally we always use QImage::Format_ARGB32_Premultiplied and
    // this effect is just an "effect" in that it changes the image (the look) somehow
    // When one wants a different depth on the file, then he needs to save the image
    // in that depth
    destPtr->convertTo(QImage::Format_ARGB32_Premultiplied);
}

//---------------------------------------------------------------------

QImage kpEffectReduceColors::applyEffect(const QImage &pm, int depth, bool dither)
{
    QImage ret = pm;
    applyEffect(&ret, depth, dither);
    return ret;
}

//---------------------------------------------------------------------
