// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * feComponentTransfer filter primitive renderer
 *
 * Authors:
 *   Felipe Corrêa da Silva Sanches <juca@members.fsf.org>
 *   Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
 *
 * Copyright (C) 2007 authors
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <cmath>
#include "display/cairo-templates.h"
#include "display/cairo-utils.h"
#include "display/nr-filter-component-transfer.h"
#include "display/nr-filter-slot.h"

namespace Inkscape {
namespace Filters {

FilterComponentTransfer::FilterComponentTransfer() = default;

FilterComponentTransfer::~FilterComponentTransfer() = default;

struct UnmultiplyAlpha
{
    guint32 operator()(guint32 in)
    {
        EXTRACT_ARGB32(in, a, r, g, b);
        if (a == 0 )
            return in;
        r = unpremul_alpha(r, a);
        g = unpremul_alpha(g, a);
        b = unpremul_alpha(b, a);
        ASSEMBLE_ARGB32(out, a, r, g, b);
        return out;
    }
};

struct MultiplyAlpha
{
    guint32 operator()(guint32 in)
    {
        EXTRACT_ARGB32(in, a, r, g, b);
        r = premul_alpha(r, a);
        g = premul_alpha(g, a);
        b = premul_alpha(b, a);
        ASSEMBLE_ARGB32(out, a, r, g, b);
        return out;
    }
};

struct ComponentTransfer
{
    ComponentTransfer(guint32 color)
        : _shift(color * 8)
        , _mask(0xff << _shift) {}

protected:
    guint32 _shift;
    guint32 _mask;
};

struct ComponentTransferTable : public ComponentTransfer
{
    ComponentTransferTable(guint32 color, std::vector<double> const &values)
        : ComponentTransfer(color)
        , _v(values.size())
    {
        for (unsigned i = 0; i < values.size(); ++i) {
            _v[i] = std::round(std::clamp(values[i], 0.0, 1.0) * 255);
        }
    }
    
    guint32 operator()(guint32 in)
    {
        if (_v.empty()) {
            return in;
        }

        guint32 component = (in & _mask) >> _shift;
        if (_v.size() == 1 || component == 255) {
            component = _v.back();
        } else {
            guint32 k = (_v.size() - 1) * component;
            guint32 dx = k % 255;
            k /= 255;
            component = _v[k] * 255 + (_v[k + 1] - _v[k]) * dx;
            component = (component + 127) / 255;
        }
        return (in & ~_mask) | (component << _shift);
    }
    
private:
    std::vector<guint32> _v;
};

struct ComponentTransferDiscrete : public ComponentTransfer
{
    ComponentTransferDiscrete(guint32 color, std::vector<double> const &values)
        : ComponentTransfer(color)
        , _v(values.size())
    {
        for (unsigned i = 0; i< values.size(); ++i) {
            _v[i] = std::round(std::clamp(values[i], 0.0, 1.0) * 255);
        }
    }
    
    guint32 operator()(guint32 in)
    {
        guint32 component = (in & _mask) >> _shift;
        guint32 k = _v.size() * component / 255;
        if (k == _v.size()) --k;
        component = _v[k];
        return (in & ~_mask) | ((guint32)component << _shift);
    }
    
private:
    std::vector<guint32> _v;
};

struct ComponentTransferLinear : public ComponentTransfer
{
    ComponentTransferLinear(guint32 color, double intercept, double slope)
        : ComponentTransfer(color)
        , _intercept(round(intercept * 255 * 255))
        , _slope(round(slope * 255)) {}
    
    guint32 operator()(guint32 in)
    {
        gint32 component = (in & _mask) >> _shift;

        // TODO: this can probably be reduced to something simpler
        component = pxclamp(_slope * component + _intercept, 0, 255 * 255);
        component = (component + 127) / 255;
        return (in & ~_mask) | (component << _shift);
    }
    
private:
    gint32 _intercept;
    gint32 _slope;
};

struct ComponentTransferGamma : public ComponentTransfer
{
    ComponentTransferGamma(guint32 color, double amplitude, double exponent, double offset)
        : ComponentTransfer(color)
        , _amplitude(amplitude)
        , _exponent(exponent)
        , _offset(offset) {}
    
    guint32 operator()(guint32 in)
    {
        double component = (in & _mask) >> _shift;
        component /= 255.0;
        component = _amplitude * std::pow(component, _exponent) + _offset;
        guint32 cpx = pxclamp(component * 255.0, 0, 255);
        return (in & ~_mask) | (cpx << _shift);
    }

private:
    double _amplitude;
    double _exponent;
    double _offset;
};

void FilterComponentTransfer::render_cairo(FilterSlot &slot) const
{
    cairo_surface_t *input = slot.getcairo(_input);
    cairo_surface_t *out = ink_cairo_surface_create_same_size(input, CAIRO_CONTENT_COLOR_ALPHA);

    // We may need to transform input surface to correct color interpolation space. The input surface
    // might be used as input to another primitive but it is likely that all the primitives in a given
    // filter use the same color interpolation space so we don't copy the input before converting.
    set_cairo_surface_ci(out, color_interpolation);
    set_cairo_surface_ci(input, color_interpolation);

    ink_cairo_surface_blit(input, out);

    // We need to operate on unmultipled by alpha color values otherwise a change in alpha screws
    // up the premultiplied by alpha r, g, b values.
    ink_cairo_surface_filter(out, out, UnmultiplyAlpha());

    // parameters: R = 0, G = 1, B = 2, A = 3
    // Cairo:      R = 2, G = 1, B = 0, A = 3
    // If tableValues is empty, use identity.
    for (unsigned i = 0; i < 4; ++i) {
        guint32 color = 2 - i;
        if (i == 3) color = 3; // alpha

        switch (type[i]) {
        case COMPONENTTRANSFER_TYPE_TABLE:
            if (!tableValues[i].empty()) {
              ink_cairo_surface_filter(out, out, ComponentTransferTable(color, tableValues[i]));
            }
            break;
        case COMPONENTTRANSFER_TYPE_DISCRETE:
            if (!tableValues[i].empty()) {
                ink_cairo_surface_filter(out, out, ComponentTransferDiscrete(color, tableValues[i]));
            }
            break;
        case COMPONENTTRANSFER_TYPE_LINEAR:
            ink_cairo_surface_filter(out, out, ComponentTransferLinear(color, intercept[i], slope[i]));
            break;
        case COMPONENTTRANSFER_TYPE_GAMMA:
            ink_cairo_surface_filter(out, out, ComponentTransferGamma(color, amplitude[i], exponent[i], offset[i]));
            break;
        case COMPONENTTRANSFER_TYPE_ERROR:
        case COMPONENTTRANSFER_TYPE_IDENTITY:
        default:
            break;
        }
    }

    ink_cairo_surface_filter(out, out, MultiplyAlpha());

    slot.set(_output, out);
    cairo_surface_destroy(out);
}

bool FilterComponentTransfer::can_handle_affine(Geom::Affine const &) const
{
    return true;
}

double FilterComponentTransfer::complexity(Geom::Affine const &) const
{
    return 2.0;
}

} // namespace Filters
} // namespace Inkscape

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
