/* SPDX-FileCopyrightText: 2024 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

#pragma once

/** \file
 * \ingroup imbuf
 */

#include "BLI_compiler_compat.h"
#include "BLI_math_matrix_types.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"

#include <optional>
#include <variant>

#define BCM_CONFIG_FILE "config.ocio"

namespace blender {
struct CurveMapping;

struct ColorManagedColorspaceSettings;
struct ColorManagedDisplaySettings;
struct ColorManagedViewSettings;
class ColormanageProcessor;
struct ID;
struct EnumPropertyItem;
struct ImBuf;
struct ImageFormatData;
struct Main;
struct bContext;

namespace ocio {
class CPUProcessor;
class ColorSpace;
class Config;
class Display;
struct ScopeInfo;
}  // namespace ocio

using ColorManagedConfig = ocio::Config;
using ColorSpace = ocio::ColorSpace;
using ColorManagedDisplay = ocio::Display;

enum ColorManagedDisplaySpace {
  /**
   * Convert to display space for drawing. This will included emulation of the
   * chosen display for an extended sRGB buffer.
   */
  DISPLAY_SPACE_DRAW,
  /**
   * Convert to display space for file output. Note image and video have different
   * conventions for HDR brightness, so there is a distinction.
   */
  DISPLAY_SPACE_IMAGE_OUTPUT,
  DISPLAY_SPACE_VIDEO_OUTPUT,
  /** Convert to display space for inspecting color values as text in the UI. */
  DISPLAY_SPACE_COLOR_INSPECTION,
  /** Convert to space suitable for plotting scopes. */
  DISPLAY_SPACE_SCOPE,
};

enum class ColorManagedFileOutput { Image, Video };

/* -------------------------------------------------------------------- */
/** \name Generic Functions
 * \{ */

ColorManagedConfig &IMB_colormanagement_get_config();

void IMB_colormanagement_check_file_config(Main *bmain);

void IMB_colormanagement_validate_settings(const ColorManagedDisplaySettings *display_settings,
                                           ColorManagedViewSettings *view_settings);

const char *IMB_colormanagement_role_colorspace_name_get(int role);
const char *IMB_colormanagement_srgb_colorspace_name_get();
void IMB_colormanagement_copy_settings(ImBuf *ibuf_src, ImBuf *ibuf_dst);
void IMB_colormanagement_assign_float_colorspace(ImBuf *ibuf, const char *name);
void IMB_colormanagement_assign_byte_colorspace(ImBuf *ibuf, const char *name);

const char *IMB_colormanagement_get_float_colorspace(const ImBuf *ibuf);
const char *IMB_colormanagement_get_byte_colorspace(const ImBuf *ibuf);
const char *IMB_colormanagement_space_from_filepath_rules(const char *filepath);

const ColorSpace *IMB_colormanagement_space_get_named(const char *name);
const ColorSpace *IMB_colormanagement_space_get_named(StringRefNull name);
bool IMB_colormanagement_space_is_data(const ColorSpace *colorspace);

/** Is colorspace the same as the scene linear working space? */
bool IMB_colormanagement_space_is_scene_linear(const ColorSpace *colorspace);
/** Is the colorspace sRGB? */
bool IMB_colormanagement_space_is_srgb(const ColorSpace *colorspace);
/* Is the colorspace scene linear + the sRGB transfer function? */
bool IMB_colormanagement_space_is_scene_linear_srgb(const ColorSpace *colorspace);

bool IMB_colormanagement_space_name_is_data(const char *name);
bool IMB_colormanagement_space_name_is_scene_linear(const char *name);
bool IMB_colormanagement_space_name_is_srgb(const char *name);

/**
 * Get binary ICC profile contents for a color-space.
 * For describing the color-space for standard dynamic range image files.
 */
Vector<char> IMB_colormanagement_space_to_icc_profile(const ColorSpace *colorspace);
/**
 * Get CICP code for color-space.
 * For describing the color-space of videos and high dynamic range image files.
 */
bool IMB_colormanagement_space_to_cicp(const ColorSpace *colorspace,
                                       ColorManagedFileOutput output,
                                       bool rgb_matrix,
                                       int cicp[4]);
const ColorSpace *IMB_colormanagement_space_from_cicp(const int cicp[4],
                                                      ColorManagedFileOutput output);

/**
 * Get identifier for color-spaces that works with multiple OpenColorIO configurations,
 * as defined by the ASWF Color Interop Forum.
 */
StringRefNull IMB_colormanagement_space_get_interop_id(const ColorSpace *colorspace);
const ColorSpace *IMB_colormanagement_space_from_interop_id(StringRefNull interop_id);

BLI_INLINE void IMB_colormanagement_get_luminance_coefficients(float r_rgb[3]);

/**
 * Convert a float RGB triplet to the correct luminance weighted average.
 *
 * Gray-scale, or Luma is a distillation of RGB data values down to a weighted average
 * based on the luminance positions of the red, green, and blue primaries.
 * Given that the internal reference space may be arbitrarily set, any
 * effort to glean the luminance coefficients must be aware of the reference
 * space primaries.
 *
 * See http://wiki.blender.org/index.php/User:Nazg-gul/ColorManagement#Luminance
 */
BLI_INLINE float IMB_colormanagement_get_luminance(const float rgb[3]);
/**
 * Byte equivalent of #IMB_colormanagement_get_luminance().
 */
BLI_INLINE unsigned char IMB_colormanagement_get_luminance_byte(const unsigned char rgb[3]);

/**
 * Conversion between scene linear and other color spaces.
 */
BLI_INLINE void IMB_colormanagement_xyz_to_scene_linear(float scene_linear[3], const float xyz[3]);
BLI_INLINE void IMB_colormanagement_scene_linear_to_xyz(float xyz[3], const float scene_linear[3]);
BLI_INLINE void IMB_colormanagement_rec709_to_scene_linear(float scene_linear[3],
                                                           const float rec709[3]);
BLI_INLINE void IMB_colormanagement_scene_linear_to_rec709(float rec709[3],
                                                           const float scene_linear[3]);
BLI_INLINE void IMB_colormanagement_aces_to_scene_linear(float scene_linear[3],
                                                         const float aces[3]);
BLI_INLINE void IMB_colormanagement_scene_linear_to_aces(float aces[3],
                                                         const float scene_linear[3]);
BLI_INLINE void IMB_colormanagement_acescg_to_scene_linear(float scene_linear[3],
                                                           const float acescg[3]);
BLI_INLINE void IMB_colormanagement_scene_linear_to_acescg(float acescg[3],
                                                           const float scene_linear[3]);
BLI_INLINE void IMB_colormanagement_rec2020_to_scene_linear(float scene_linear[3],
                                                            const float rec2020[3]);
BLI_INLINE void IMB_colormanagement_scene_linear_to_rec2020(float rec2020[3],
                                                            const float scene_linear[3]);
float3x3 IMB_colormanagement_get_xyz_to_scene_linear();
float3x3 IMB_colormanagement_get_scene_linear_to_xyz();

/**
 * Functions for converting between color temperature/tint and RGB white points.
 */
void IMB_colormanagement_get_whitepoint(float temperature, float tint, float whitepoint[3]);
bool IMB_colormanagement_set_whitepoint(const float whitepoint[3],
                                        float &temperature,
                                        float &tint);

/** \} */

/* -------------------------------------------------------------------- */
/** \name Color Space Transformation Functions
 * \{ */

/**
 * Convert a float image buffer from one color space to another.
 */
void IMB_colormanagement_transform_float(float *buffer,
                                         int width,
                                         int height,
                                         int channels,
                                         const char *from_colorspace,
                                         const char *to_colorspace,
                                         bool predivide);
/**
 * Convert a byte image buffer from one color space to another.
 */
void IMB_colormanagement_transform_byte(unsigned char *buffer,
                                        int width,
                                        int height,
                                        int channels,
                                        const char *from_colorspace,
                                        const char *to_colorspace);

/**
 * Convert a byte image buffer into a float buffer, changing the color spaces too.
 */
void IMB_colormanagement_transform_byte_to_float(float *float_buffer,
                                                 const unsigned char *byte_buffer,
                                                 int width,
                                                 int height,
                                                 int channels,
                                                 const char *from_colorspace,
                                                 const char *to_colorspace);
void IMB_colormanagement_transform_v4(float pixel[4],
                                      const char *from_colorspace,
                                      const char *to_colorspace);

/**
 * Convert pixel from specified color space to scene linear space.
 * For performance, use #IMB_colormanagement_colorspace_to_scene_linear
 * when converting an array of pixels.
 */
void IMB_colormanagement_colorspace_to_scene_linear_v3(float pixel[3],
                                                       const ColorSpace *colorspace);
void IMB_colormanagement_colorspace_to_scene_linear_v4(float pixel[4],
                                                       bool predivide,
                                                       const ColorSpace *colorspace);

/**
 * Convert pixel from scene linear space to specified color space.
 * For performance, use #IMB_colormanagement_scene_linear_to_colorspace
 * when converting an array of pixels.
 */
void IMB_colormanagement_scene_linear_to_colorspace_v3(float pixel[3],
                                                       const ColorSpace *colorspace);

/**
 * Converts a (width)x(height) block of float pixels from given color space to
 * scene linear space. This is much higher performance than converting pixels
 * one by one.
 */
void IMB_colormanagement_colorspace_to_scene_linear(float *buffer,
                                                    int width,
                                                    int height,
                                                    int channels,
                                                    const ColorSpace *colorspace,
                                                    bool predivide);

/**
 * Converts a (width)x(height) block of float pixels from scene linear space
 * to given color space. This is much higher performance than converting pixels
 * one by one.
 */
void IMB_colormanagement_scene_linear_to_colorspace(
    float *buffer, int width, int height, int channels, const ColorSpace *colorspace);

void IMB_colormanagement_imbuf_to_byte_texture(unsigned char *out_buffer,
                                               int offset_x,
                                               int offset_y,
                                               int width,
                                               int height,
                                               const ImBuf *ibuf,
                                               bool store_premultiplied);
void IMB_colormanagement_imbuf_to_float_texture(float *out_buffer,
                                                int offset_x,
                                                int offset_y,
                                                int width,
                                                int height,
                                                const ImBuf *ibuf,
                                                bool store_premultiplied);

/**
 * Conversion between color picking role. Typically we would expect such a
 * requirements:
 * - It is approximately perceptually linear, so that the HSV numbers and
 *   the HSV cube/circle have an intuitive distribution.
 * - It has the same gamut as the scene linear color space.
 * - Color picking values 0..1 map to scene linear values in the 0..1 range,
 *   so that picked albedo values are energy conserving.
 */
void IMB_colormanagement_scene_linear_to_color_picking_v3(float color_picking[3],
                                                          const float scene_linear[3]);
void IMB_colormanagement_color_picking_to_scene_linear_v3(float scene_linear[3],
                                                          const float color_picking[3]);

/**
 * Conversion between sRGB, for rare cases like hex color or copy/pasting
 * between UI theme and scene linear colors.
 */
BLI_INLINE void IMB_colormanagement_scene_linear_to_srgb_v3(float srgb[3],
                                                            const float scene_linear[3]);
BLI_INLINE void IMB_colormanagement_srgb_to_scene_linear_v3(float scene_linear[3],
                                                            const float srgb[3]);

/**
 * Convert pixel from scene linear to display space using default view
 * used by performance-critical areas such as color-related widgets where we want to reduce
 * amount of per-widget allocations.
 */
void IMB_colormanagement_scene_linear_to_display_v3(
    float pixel[3],
    const ColorManagedDisplay *display,
    ColorManagedDisplaySpace display_space = DISPLAY_SPACE_DRAW);
/**
 * Same as #IMB_colormanagement_scene_linear_to_display_v3,
 * but converts color in opposite direction.
 */
void IMB_colormanagement_display_to_scene_linear_v3(
    float pixel[3],
    const ColorManagedDisplay *display,
    ColorManagedDisplaySpace display_space = DISPLAY_SPACE_DRAW);

void IMB_colormanagement_pixel_to_display_space_v4(
    float result[4],
    const float pixel[4],
    const ColorManagedViewSettings *view_settings,
    const ColorManagedDisplaySettings *display_settings,
    ColorManagedDisplaySpace display_space = DISPLAY_SPACE_DRAW);

/**
 * Prepare image buffer to be saved on disk, applying color management if needed
 * color management would be applied if image is saving as render result and if
 * file format is not expecting float buffer to be in linear space (currently
 * JPEG2000 and TIFF are such formats -- they're storing image as float but
 * file itself stores applied color space).
 *
 * Both byte and float buffers would contain applied color space, and result's
 * float_colorspace would be set to display color space. This should be checked
 * in image format write callback and if float_colorspace is not NULL, no color
 * space transformation should be applied on this buffer.
 */
ImBuf *IMB_colormanagement_imbuf_for_write(ImBuf *ibuf,
                                           bool save_as_render,
                                           bool allocate_result,
                                           const ImageFormatData *image_format);

void IMB_colormanagement_display_settings_from_ctx(
    const bContext *C,
    ColorManagedViewSettings **r_view_settings,
    ColorManagedDisplaySettings **r_display_settings);

void IMB_colormanagement_scene_linear_to_display_buffer(
    uint8_t *display_buffer,
    const float *linear_buffer,
    int width,
    int height,
    const ColorManagedViewSettings *view_settings,
    const ColorManagedDisplaySettings *display_settings);

/** \} */

/* -------------------------------------------------------------------- */
/** \name Display Functions
 * \{ */

int IMB_colormanagement_display_get_named_index(const char *name);
const char *IMB_colormanagement_display_get_indexed_name(int index);
const char *IMB_colormanagement_display_get_default_name();
/**
 * Used by performance-critical pixel processing areas, such as color widgets.
 */
const ColorManagedDisplay *IMB_colormanagement_display_get_named(const char *name);
const char *IMB_colormanagement_display_get_none_name();
const char *IMB_colormanagement_display_get_default_view_transform_name(
    const ColorManagedDisplay *display);

const ColorSpace *IMB_colormangement_display_get_color_space(
    const ColorManagedViewSettings *view_settings,
    const ColorManagedDisplaySettings *display_settings);
bool IMB_colormanagement_display_is_hdr(const ColorManagedDisplaySettings *display_settings,
                                        const char *view_name);
bool IMB_colormanagement_display_is_wide_gamut(const ColorManagedDisplaySettings *display_settings,
                                               const char *view_name);
bool IMB_colormanagement_display_support_emulation(
    const ColorManagedDisplaySettings *display_settings, const char *view_name);

/** Max luminance of the view transform, or 0 if no maximum found. */
int IMB_colormanagement_view_max_nits(const char *display_name, const char *view_name);

/** Get scope display info for waveform/parade/vector-scope. */
ocio::ScopeInfo IMB_colormanagement_get_scope_info(
    const ColorManagedDisplaySettings *display_settings,
    const ColorManagedViewSettings *view_settings);

/** \} */

/* -------------------------------------------------------------------- */
/** \name View Functions
 * \{ */

int IMB_colormanagement_view_get_id_by_name(const char *name);
const char *IMB_colormanagement_view_get_name_by_id(int index);

/** \} */

/* -------------------------------------------------------------------- */
/** \name Look Functions
 * \{ */

int IMB_colormanagement_look_get_named_index(const char *name);
const char *IMB_colormanagement_look_get_indexed_name(int index);
const char *IMB_colormanagement_look_get_default_name();
const char *IMB_colormanagement_look_validate_for_view(const char *view_name,
                                                       const char *look_name);

/** \} */

/* -------------------------------------------------------------------- */
/** \name Color Space Functions
 * \{ */

int IMB_colormanagement_colorspace_get_named_index(const char *name);
const char *IMB_colormanagement_colorspace_get_indexed_name(int index);
const char *IMB_colormanagement_colorspace_get_name(const ColorSpace *colorspace);
const char *IMB_colormanagement_colorspace_get_family(const ColorSpace *colorspace);
const char *IMB_colormanagement_colorspace_get_description(const ColorSpace *colorspace);
const char *IMB_colormanagement_view_get_default_name(const char *display_name);
const char *IMB_colormanagement_view_get_raw_or_default_name(const char *display_name);

void IMB_colormanagement_colorspace_from_ibuf_ftype(
    ColorManagedColorspaceSettings *colorspace_settings, ImBuf *ibuf);

/** \} */

/* -------------------------------------------------------------------- */
/** \name Working Space Functions
 * \{ */

const char *IMB_colormanagement_working_space_get_default();
const char *IMB_colormanagement_working_space_get();

bool IMB_colormanagement_working_space_set_from_name(const char *name);
void IMB_colormanagement_working_space_check(Main *bmain,
                                             bool for_undo,
                                             bool have_editable_assets);

void IMB_colormanagement_working_space_init_default(Main *bmain);
void IMB_colormanagement_working_space_init_startup(Main *bmain);
void IMB_colormanagement_working_space_convert(Main *bmain,
                                               const float3x3 &current_scene_linear_to_xyz,
                                               const float3x3 &new_xyz_to_scene_linear,
                                               bool depsgraph_tag = false,
                                               bool linked_only = false,
                                               bool editable_assets_only = false);
void IMB_colormanagement_working_space_convert(Main *bmain, const Main *reference_bmain);

int IMB_colormanagement_working_space_get_named_index(const char *name);
const char *IMB_colormanagement_working_space_get_indexed_name(int index);
void IMB_colormanagement_working_space_items_add(EnumPropertyItem **items, int *totitem);

/** \} */

/* -------------------------------------------------------------------- */
/** \name RNA Helper Functions
 * \{ */

void IMB_colormanagement_display_items_add(EnumPropertyItem **items, int *totitem);
void IMB_colormanagement_view_items_add(EnumPropertyItem **items,
                                        int *totitem,
                                        const char *display_name);
void IMB_colormanagement_look_items_add(EnumPropertyItem **items,
                                        int *totitem,
                                        const char *view_name);
void IMB_colormanagement_colorspace_items_add(EnumPropertyItem **items, int *totitem);

/** \} */

/* -------------------------------------------------------------------- */
/** \name Pixel Processor Functions
 * \{ */
class ColormanageProcessor : NonCopyable {
  using ProcessorType =
      std::variant<std::shared_ptr<const ocio::CPUProcessor>, const ocio::CPUProcessor *>;

  ProcessorType cpu_processor_ = nullptr;
  CurveMapping *curve_mapping_ = nullptr;
  bool is_data_result_ = false;

 public:
  ColormanageProcessor() = default;
  ColormanageProcessor(ColormanageProcessor &&other) noexcept;
  ~ColormanageProcessor();
  ColormanageProcessor &operator=(ColormanageProcessor &&other) noexcept;

  static ColormanageProcessor colorspace_processor_new(StringRefNull from_colorspace,
                                                       StringRefNull to_colorspace);
  static ColormanageProcessor colorspace_processor_from_scene_linear_new(
      const ColorSpace &to_colorspace);
  static ColormanageProcessor colorspace_processor_to_scene_linear_new(
      const ColorSpace &from_colorspace);
  static ColormanageProcessor display_processor_new(
      const ColorManagedViewSettings *view_settings,
      const ColorManagedDisplaySettings *display_settings,
      ColorManagedDisplaySpace display_space = DISPLAY_SPACE_DRAW,
      bool inverse = false,
      const char *from_colorspace = nullptr);
  static std::optional<ColormanageProcessor> display_processor_for_imbuf(
      const ImBuf *ibuf,
      const ColorManagedViewSettings *view_settings,
      const ColorManagedDisplaySettings *display_settings,
      ColorManagedDisplaySpace display_space = DISPLAY_SPACE_DRAW);

  bool is_data_result() const;
  bool is_noop() const;
  void apply_v4(float pixel[4]) const;
  void apply_v4_predivide(float pixel[4]) const;
  void apply_v3(float pixel[3]) const;
  void apply_pixel(float *pixel, int channels) const;
  void apply(float *buffer, int width, int height, int channels, bool predivide) const;
  void apply_byte(unsigned char *buffer, int width, int height, int channels) const;

 private:
  const ocio::CPUProcessor *get_cpu_processor() const
  {
    if (std::holds_alternative<std::shared_ptr<const ocio::CPUProcessor>>(cpu_processor_)) {
      return std::get<std::shared_ptr<const ocio::CPUProcessor>>(cpu_processor_).get();
    }
    return std::get<const ocio::CPUProcessor *>(cpu_processor_);
  }
};

/** \} */

/* -------------------------------------------------------------------- */
/** \name OpenGL Drawing Routines Using GLSL for Color Space Transform
 * \{ */

/**
 * Configures GLSL shader for conversion from scene linear to display space.
 */
bool IMB_colormanagement_setup_glsl_draw(const ColorManagedViewSettings *view_settings,
                                         const ColorManagedDisplaySettings *display_settings,
                                         float dither,
                                         bool predivide);
/**
 * \note Same as IMB_colormanagement_setup_glsl_draw,
 * but display space conversion happens from a specified space.
 *
 * Configures GLSL shader for conversion from specified to
 * display color space
 *
 * Will create appropriate OCIO processor and setup GLSL shader,
 * so further 2D texture usage will use this conversion.
 *
 * When there's no need to apply transform on 2D textures, use
 * IMB_colormanagement_finish_glsl_draw().
 *
 * This is low-level function, use ED_draw_imbuf_ctx if you
 * only need to display given image buffer
 */
bool IMB_colormanagement_setup_glsl_draw_from_space(
    const ColorManagedViewSettings *view_settings,
    const ColorManagedDisplaySettings *display_settings,
    const ColorSpace *from_colorspace,
    float dither,
    bool predivide,
    bool do_overlay_merge,
    ColorManagedDisplaySpace display_space = DISPLAY_SPACE_DRAW,
    float opacity = 1.0f);
/**
 * Same as setup_glsl_draw, but color management settings are guessing from a given context.
 */
bool IMB_colormanagement_setup_glsl_draw_ctx(const bContext *C, float dither, bool predivide);
/**
 * Same as `setup_glsl_draw_from_space`,
 * but color management settings are guessing from a given context.
 */
bool IMB_colormanagement_setup_glsl_draw_from_space_ctx(const bContext *C,
                                                        const ColorSpace *from_colorspace,
                                                        float dither,
                                                        bool predivide);

/**
 * Configures GPU shader for conversion from the given space to scene linear.
 * Drawing happens in the same immediate mode as when GPU_SHADER_3D_IMAGE_COLOR shader is used.
 *
 * Returns true if the GPU shader was successfully bound.
 */
bool IMB_colormanagement_setup_glsl_draw_to_scene_linear(const char *from_colorspace_name,
                                                         bool predivide);

/**
 * Finish GLSL-based display space conversion.
 */
void IMB_colormanagement_finish_glsl_draw();

/** \} */

/* -------------------------------------------------------------------- */
/** \name View Transform
 * \{ */

void IMB_colormanagement_init_untonemapped_view_settings(
    ColorManagedViewSettings *view_settings, const ColorManagedDisplaySettings *display_settings);

/* Roles */
enum {
  COLOR_ROLE_SCENE_LINEAR = 0,
  COLOR_ROLE_COLOR_PICKING,
  COLOR_ROLE_TEXTURE_PAINTING,
  COLOR_ROLE_DEFAULT_SEQUENCER,
  COLOR_ROLE_DEFAULT_BYTE,
  COLOR_ROLE_DEFAULT_FLOAT,
  COLOR_ROLE_ACES_INTERCHANGE,
  COLOR_ROLE_DATA,
};

/** \} */

/* -------------------------------------------------------------------- */
/** \name Rendering Tables
 * \{ */

void IMB_colormanagement_blackbody_temperature_to_rgb(float r_dest[4], float value);
void IMB_colormanagement_blackbody_temperature_to_rgb_table(float *r_table,
                                                            int width,
                                                            float min,
                                                            float max);
void IMB_colormanagement_wavelength_to_rgb(float r_dest[4], float value);
void IMB_colormanagement_wavelength_to_rgb_table(float *r_table, int width);

/** \} */

}  // namespace blender

#include "intern/colormanagement_inline.h"  // IWYU pragma: export
