/*
 * This file is part of libplacebo.
 *
 * libplacebo is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * libplacebo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with libplacebo.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef LIBPLACEBO_GAMUT_MAPPING_H_
#define LIBPLACEBO_GAMUT_MAPPING_H_

#include <libplacebo/common.h>
#include <libplacebo/colorspace.h>

PL_API_BEGIN

struct pl_gamut_map_params;
struct pl_gamut_map_function {
    const char *name;        // Identifier
    const char *description; // Friendly / longer name

    // The gamut-mapping function itself. Iterates over all values in `lut`,
    // and adapts them as needed.
    void (*map)(float *lut, const struct pl_gamut_map_params *params);

    // Returns true if `map` supports both stretching and contracting the
    // gamut. In this case, `map` is always executed, even if the output gamut
    // is larger than the input gamut.
    bool bidirectional;

    // Private data. Unused by libplacebo, but may be accessed by `map`.
    void *priv;
};

struct pl_gamut_map_constants {
    // (Relative) chromaticity protection zone for perceptual mapping [0,1]
    float perceptual_deadzone;

    // Strength of the perceptual saturation mapping component [0,1]
    float perceptual_strength;

    // I vs C curve gamma to use for colorimetric clipping [0,10]
    float colorimetric_gamma;

    // Knee point to use for softclipping methods (perceptual, softclip) [0,1]
    float softclip_knee;

    // Desaturation strength (for softclip only) [0,1]
    float softclip_desat;
};

#define PL_GAMUT_MAP_CONSTANTS    \
    .colorimetric_gamma  = 1.80f, \
    .softclip_knee       = 0.70f, \
    .softclip_desat      = 0.35f, \
    .perceptual_deadzone = 0.30f, \
    .perceptual_strength = 0.80f,

struct pl_gamut_map_params {
    // If `function` is NULL, defaults to `pl_gamut_map_clip`.
    const struct pl_gamut_map_function *function;

    // The desired input/output primaries. This affects the subjective color
    // volume in which the desired mapping shall take place.
    struct pl_raw_primaries input_gamut;
    struct pl_raw_primaries output_gamut;

    // Minimum/maximum luminance (PQ) of the target display. Note that the same
    // value applies to both the input and output, since it's assumed that tone
    // mapping has already happened by this stage. This effectively defines the
    // legal gamut boundary in RGB space.
    //
    // This also defines the I channel value range, for `pl_gamut_map_generate`
    float min_luma;
    float max_luma;

    // Common constants, should be initialized to PL_GAMUT_MAP_CONSTANTS if
    // not intending to override them further.
    struct pl_gamut_map_constants constants;

    // -- LUT generation options (for `pl_gamut_map_generate` only)

    // The size of the resulting LUT, per channel.
    //
    // Note: For quality, it's generally best to increase h > I > C
    int lut_size_I;
    int lut_size_C;
    int lut_size_h;

    // The stride (in number of floats) between elements in the resulting LUT.
    int lut_stride;

    // -- Removed parameters
    float chroma_margin PL_DEPRECATED; // non-functional
};

#define pl_gamut_map_params(...) (&(struct pl_gamut_map_params) {   \
    .constants = { PL_GAMUT_MAP_CONSTANTS },                        \
    __VA_ARGS__                                                     \
})

// Note: Only does pointer equality testing on `function`
PL_API bool pl_gamut_map_params_equal(const struct pl_gamut_map_params *a,
                                      const struct pl_gamut_map_params *b);

// Returns true if the given gamut mapping configuration effectively represents
// a no-op configuration. Gamut mapping can be skipped in this case.
PL_API bool pl_gamut_map_params_noop(const struct pl_gamut_map_params *params);

// Generate a gamut-mapping LUT for a given configuration. LUT samples are
// stored as IPTPQc4 values, but the LUT itself is indexed by IChPQc4,spanning
// the effective range [min_luma, max_luma] × [0, 0.5] × [-pi,pi].
//
// This ordering is designed to keep frequently co-occurring values close in
// memory, while permitting simple wrapping of the 'h' component.
PL_API void pl_gamut_map_generate(float *out, const struct pl_gamut_map_params *params);

// Samples a gamut mapping function for a single IPTPQc4 value. The input
// values are updated in-place.
PL_API void pl_gamut_map_sample(float x[3], const struct pl_gamut_map_params *params);

// Performs no gamut-mapping, just hard clips out-of-range colors per-channel.
PL_API extern const struct pl_gamut_map_function pl_gamut_map_clip;

// Performs a perceptually balanced (saturation) gamut mapping, using a soft
// knee function to preserve in-gamut colors, followed by a final softclip
// operation. This works bidirectionally, meaning it can both compress and
// expand the gamut. Behaves similar to a blend of `saturation` and `softclip`.
PL_API extern const struct pl_gamut_map_function pl_gamut_map_perceptual;

// Performs a perceptually balanced gamut mapping using a soft knee function to
// roll-off clipped regions, and a hue shifting function to preserve saturation.
PL_API extern const struct pl_gamut_map_function pl_gamut_map_softclip;

// Performs relative colorimetric clipping, while maintaining an exponential
// relationship between brightness and chromaticity.
PL_API extern const struct pl_gamut_map_function pl_gamut_map_relative;

// Performs simple RGB->RGB saturation mapping. The input R/G/B channels are
// mapped directly onto the output R/G/B channels. Will never clip, but will
// distort all hues and/or result in a faded look.
PL_API extern const struct pl_gamut_map_function pl_gamut_map_saturation;

// Performs absolute colorimetric clipping. Like pl_gamut_map_relative, but
// does not adapt the white point.
PL_API extern const struct pl_gamut_map_function pl_gamut_map_absolute;

// Performs constant-luminance colorimetric clipping, desaturing colors
// towards white until they're in-range.
PL_API extern const struct pl_gamut_map_function pl_gamut_map_desaturate;

// Uniformly darkens the input slightly to prevent clipping on blown-out
// highlights, then clamps colorimetrically to the input gamut boundary,
// biased slightly to preserve chromaticity over luminance.
PL_API extern const struct pl_gamut_map_function pl_gamut_map_darken;

// Performs no gamut mapping, but simply highlights out-of-gamut pixels.
PL_API extern const struct pl_gamut_map_function pl_gamut_map_highlight;

// Linearly/uniformly desaturates the image in order to bring the entire
// image into the target gamut.
PL_API extern const struct pl_gamut_map_function pl_gamut_map_linear;

// A list of built-in gamut mapping functions, terminated by NULL
PL_API extern const struct pl_gamut_map_function * const pl_gamut_map_functions[];
PL_API extern const int pl_num_gamut_map_functions; // excluding trailing NULL

// Find the gamut mapping function with the given name, or NULL on failure.
PL_API const struct pl_gamut_map_function *pl_find_gamut_map_function(const char *name);

PL_API_END

#endif // LIBPLACEBO_GAMUT_MAPPING_H_
