// RGB to RGB conversion, includes chromatic adaptation transform
// All coordinates are based on the CIE 1931 2° chromaticity diagram

//!HOOK OUTPUT
//!BIND HOOKED
//!DESC gamut mapping (clip)

// You can use custom chromaticity here.
// Example: BT.709 with a D93 white point: Chromaticity(BT709.r, BT709.g, BT709.b, D93)
// You can also define custom coordinates: Chromaticity(vec2(0.7347, 0.2653), BT709.g, BT709.b, D65)

#define from    BT2020
#define to      BT709

// White points of standard illuminants
// https://en.wikipedia.org/wiki/Standard_illuminant#White_points_of_standard_illuminants

const vec2 A    = vec2(0.44757, 0.40745);
const vec2 B    = vec2(0.34842, 0.35161);
const vec2 C    = vec2(0.31006, 0.31616);
const vec2 D50  = vec2(0.34567, 0.35850);
const vec2 D55  = vec2(0.33242, 0.34743);
const vec2 D65  = vec2(0.31271, 0.32902);
const vec2 D75  = vec2(0.29902, 0.31485);
const vec2 D93  = vec2(0.28315, 0.29711);
const vec2 E    = vec2(1.0/3.0, 1.0/3.0);
const vec2 F2   = vec2(0.37208, 0.37529);
const vec2 F7   = vec2(0.31292, 0.32933);
const vec2 F11  = vec2(0.38052, 0.37713);
const vec2 DCI  = vec2(0.31400, 0.35100);
// It is also known as D60
const vec2 ACES = vec2(0.32168, 0.33767);
// Colour Matching Between OLED and CRT
// https://www.sony.jp/products/catalog/FUN_WhitePaper_OLED_ColorMatching_V1_00.pdf
const vec2 BRAVIA = vec2(0.3067, 0.318);

// https://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D
vec2 CIE_D(float T) {
    // Compensate for the loss caused by the accuracy difference between the old and new standards
    // c2 = 1.4387768775039337
    // https://en.wikipedia.org/wiki/Planckian_locus#Planckian_locus_in_the_XYZ_color_space
    T = (T * 1.4388) / 1.438;

    // This formula is applicable to temperatures ranging from 4000K to 25000K
    T = clamp(T, 4000.0, 25000.0);

    float t1 = 1000.0 / T;
    float t2 = t1 * t1;
    float t3 = t1 * t2;

    float x =
        T <= 7000.0
            ? 0.244063 + 0.09911 * t1 + 2.9678 * t2 - 4.607 * t3
            : 0.23704 + 0.24748 * t1 + 1.9018 * t2 - 2.0064 * t3;

    // Daylight locus
    float y = -0.275 + 2.87 * x - 3.0 * x * x;

    return vec2(x, y);
}

// https://en.wikipedia.org/wiki/Planckian_locus#Approximation
vec2 Kang(float T) {
    // This formula is applicable to temperatures ranging from 1667K to 25000K
    T = clamp(T, 1667.0, 25000.0);

    float t1 = 1000.0 / T;
    float t2 = t1 * t1;
    float t3 = t1 * t2;

    float x =
        T <= 4000.0
            ? -0.2661239 * t3 - 0.234358 * t2 + 0.8776956 * t1 + 0.17991
            : -3.0258469 * t3 + 2.1070379 * t2 + 0.2226347 * t1 + 0.24039;

    float x2 = x * x;
    float x3 = x2 * x;

    float y =
        T <= 2222.0
            ? -1.1063814 * x3 - 1.3481102 * x2 - 2.18555832 * x - 0.20219683
            : T <= 4000.0
            ? -0.9549476 * x3 - 1.37418593 * x2 - 2.09137015 * x - 0.16748867
            : 3.081758 * x3 - 5.8733867 * x2 + 3.75112997 * x - 0.37001483;

    return vec2(x, y);
}

// Chromaticities
// https://www.itu.int/rec/T-REC-H.273
// https://github.com/colour-science/colour/tree/develop/colour/models/rgb/datasets

struct Chromaticity {
    vec2 r, g, b, w;
};

// ITU-R BT.2020, ITU-R BT.2100
const Chromaticity BT2020 = Chromaticity(
    vec2(0.708, 0.292),
    vec2(0.170, 0.797),
    vec2(0.131, 0.046),
    D65
);

// ITU-R BT.709, IEC 61966-2-1 (sRGB)
const Chromaticity BT709 = Chromaticity(
    vec2(0.64, 0.33),
    vec2(0.30, 0.60),
    vec2(0.15, 0.06),
    D65
);

// ITU-R BT.601 (525 lines), SMPTE ST 240
const Chromaticity BT601_525 = Chromaticity(
    vec2(0.630, 0.340),
    vec2(0.310, 0.595),
    vec2(0.155, 0.070),
    D65
);

// ITU-R BT.601 (625 lines), BT.470 (B/G), EBU 3213-E
const Chromaticity BT601_625 = Chromaticity(
    vec2(0.64, 0.33),
    vec2(0.29, 0.60),
    vec2(0.15, 0.06),
    D65
);

// ITU-R BT.470 (M)
const Chromaticity BT470m = Chromaticity(
    vec2(0.67, 0.33),
    vec2(0.21, 0.71),
    vec2(0.14, 0.08),
    C
);

// P3-DCI (Theater)
const Chromaticity P3DCI = Chromaticity(
    vec2(0.680, 0.320),
    vec2(0.265, 0.690),
    vec2(0.150, 0.060),
    DCI
);

// P3-D65 (Display)
const Chromaticity P3D65 = Chromaticity(
    P3DCI.r,
    P3DCI.g,
    P3DCI.b,
    D65
);

// P3-D60 (ACES Cinema)
const Chromaticity P3D60 = Chromaticity(
    P3DCI.r,
    P3DCI.g,
    P3DCI.b,
    ACES
);

// ITU-T H.273 (Generic film)
const Chromaticity H273_8 = Chromaticity(
    vec2(0.681, 0.319),
    vec2(0.243, 0.692),
    vec2(0.145, 0.049),
    C
);

// ITU-T H.273 (No corresponding industry specification identified)
const Chromaticity H273_22 = Chromaticity(
    vec2(0.630, 0.340),
    vec2(0.295, 0.605),
    vec2(0.155, 0.077),
    D65
);

// CIE RGB (CIE 1931 color space)
const Chromaticity CIERGB = Chromaticity(
    vec2(0.73474284, 0.26525716),
    vec2(0.27377903, 0.7174777),
    vec2(0.16655563, 0.00891073),
    E
);

// CIE XYZ (CIE 1931 color space)
const Chromaticity XYZ = Chromaticity(
    vec2(1.0, 0.0),
    vec2(0.0, 1.0),
    vec2(0.0, 0.0),
    E
);

// CIE XYZ (CIE 1931 color space, D65 whitepoint)
const Chromaticity XYZD65 = Chromaticity(
    XYZ.r,
    XYZ.g,
    XYZ.b,
    D65
);

// CIE XYZ (CIE 1931 color space, D50 whitepoint)
const Chromaticity XYZD50 = Chromaticity(
    XYZ.r,
    XYZ.g,
    XYZ.b,
    D50
);

// Grayscale, Monochrome
const Chromaticity GRAY = Chromaticity(
    vec2(0.0, 1.0),
    vec2(0.0, 1.0),
    vec2(0.0, 1.0),
    E
);

// Adobe RGB (1998)
const Chromaticity AdobeRGB = Chromaticity(
    vec2(0.64, 0.33),
    vec2(0.21, 0.71),
    vec2(0.15, 0.06),
    D65
);

// Adobe Wide Gamut RGB
const Chromaticity AdobeWideGamutRGB = Chromaticity(
    vec2(0.7347, 0.2653),
    vec2(0.1152, 0.8264),
    vec2(0.1566, 0.0177),
    D50
);

// ROMM (ProPhoto RGB)
const Chromaticity ROMM = Chromaticity(
    vec2(0.734699, 0.265301),
    vec2(0.159597, 0.840403),
    vec2(0.036598, 0.000105),
    D50
);

// AP0 (ACES 2065-1)
const Chromaticity AP0 = Chromaticity(
    vec2(0.7347,  0.2653),
    vec2(0.0000,  1.0000),
    vec2(0.0001, -0.0770),
    ACES
);

// AP1 (ACEScg, cc, cct, proxy)
const Chromaticity AP1 = Chromaticity(
    vec2(0.713, 0.293),
    vec2(0.165, 0.830),
    vec2(0.128, 0.044),
    ACES
);

// ARRI Wide Gamut 3
const Chromaticity AWG3 = Chromaticity(
    vec2(0.684,   0.313),
    vec2(0.221,   0.848),
    vec2(0.0861, -0.102),
    D65
);

// ARRI Wide Gamut 4
const Chromaticity AWG4 = Chromaticity(
    vec2(0.7347,  0.2653),
    vec2(0.1424,  0.8576),
    vec2(0.0991, -0.0308),
    D65
);

// RED Wide Gamut RGB
const Chromaticity RWG = Chromaticity(
    vec2(0.780308,  0.304253),
    vec2(0.121595,  1.493994),
    vec2(0.095612, -0.084589),
    D65
);

// DaVinci Wide Gamut
const Chromaticity DWG = Chromaticity(
    vec2(0.8000,  0.3130),
    vec2(0.1682,  0.9877),
    vec2(0.0790, -0.1155),
    D65
);

// FilmLight E-Gamut
const Chromaticity EGAMUT = Chromaticity(
    vec2(0.8000,  0.3177),
    vec2(0.1800,  0.9000),
    vec2(0.0650, -0.0805),
    D65
);

// FilmLight E-Gamut 2
const Chromaticity EGAMUT2 = Chromaticity(
    vec2(0.8300,  0.3100),
    vec2(0.1500,  0.9500),
    vec2(0.0650, -0.0805),
    D65
);

// FUJIFILM F-Gamut C
const Chromaticity FGAMUTC = Chromaticity(
    vec2(0.7347,  0.2653),
    vec2(0.0263,  0.9737),
    vec2(0.1173, -0.0224),
    D65
);

// Sony S-Gamut3/S-Gamut
const Chromaticity SGAMUT = Chromaticity(
    vec2(0.73,  0.280),
    vec2(0.14,  0.855),
    vec2(0.10, -0.050),
    D65
);

// Sony S-Gamut.Cine
const Chromaticity SGAMUTCINE = Chromaticity(
    vec2(0.766,  0.275),
    vec2(0.225,  0.800),
    vec2(0.089, -0.087),
    D65
);

// Canon Cinema Gamut
const Chromaticity CINEMA_GAMUT = Chromaticity(
    vec2(0.74,  0.27),
    vec2(0.17,  1.14),
    vec2(0.08, -0.10),
    D65
);

// Panasonic V-Gamut
const Chromaticity VGAMUT = Chromaticity(
    vec2(0.730,  0.28),
    vec2(0.165,  0.84),
    vec2(0.100, -0.03),
    D65
);

// DJI D-Gamut
const Chromaticity DGAMUT = Chromaticity(
    vec2(0.71,  0.31),
    vec2(0.21,  0.88),
    vec2(0.09, -0.08),
    D65
);

// Chromatic adaptation transform
// https://en.wikipedia.org/wiki/LMS_color_space

// It is also known as von Kries
const mat3 HPE = mat3(
     0.40024,  0.70760, -0.08081,
    -0.22630,  1.16532,  0.04570,
     0.00000,  0.00000,  0.91822
);

const mat3 Bradford = mat3(
     0.8951,  0.2664, -0.1614,
    -0.7502,  1.7135,  0.0367,
     0.0389, -0.0685,  1.0296
);

const mat3 CAT97 = mat3(
     0.8562,  0.3372, -0.1934,
    -0.8360,  1.8327,  0.0033,
     0.0357, -0.0469,  1.0112
);

const mat3 CAT02 = mat3(
     0.7328,  0.4296, -0.1624,
    -0.7036,  1.6975,  0.0061,
     0.0030,  0.0136,  0.9834
);

const mat3 CAT16 = mat3(
     0.401288,  0.650173, -0.051461,
    -0.250268,  1.204414,  0.045854,
    -0.002079,  0.048952,  0.953127
);

// Other constants

const mat3 Identity3 = mat3(
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
    0.0, 0.0, 1.0
);

const mat3 SingularY3 = mat3(
    0.0, 1.0, 0.0,
    0.0, 1.0, 0.0,
    0.0, 1.0, 0.0
);

// http://www.brucelindbloom.com/Eqn_xyY_to_XYZ.html
vec3 xyY_to_XYZ(vec3 xyY) {
    float x = xyY.x;
    float y = xyY.y;
    float Y = xyY.z;

    float multiplier = Y / max(y, 1e-6);

    float z = 1.0 - x - y;
    float X = x * multiplier;
    float Z = z * multiplier;

    return vec3(X, Y, Z);
}

// http://www.brucelindbloom.com/Eqn_XYZ_to_xyY.html
vec3 XYZ_to_xyY(vec3 XYZ) {
    float X = XYZ.x;
    float Y = XYZ.y;
    float Z = XYZ.z;

    float divisor = X + Y + Z;
    if (divisor == 0.0) divisor = 1e-6;

    float x = X / divisor;
    float y = Y / divisor;

    return vec3(x, y, Y);
}

// http://www.brucelindbloom.com/Eqn_RGB_XYZ_Matrix.html
mat3 RGB_to_XYZ(Chromaticity C) {
    if (C == GRAY)
        return Identity3;

    vec3 r = xyY_to_XYZ(vec3(C.r, 1.0));
    vec3 g = xyY_to_XYZ(vec3(C.g, 1.0));
    vec3 b = xyY_to_XYZ(vec3(C.b, 1.0));
    vec3 w = xyY_to_XYZ(vec3(C.w, 1.0));

    mat3 xyz = transpose(mat3(r, g, b));

    vec3 scale = w * inverse(xyz);
    mat3 scale_diag = mat3(
        scale.x, 0.0, 0.0,
        0.0, scale.y, 0.0,
        0.0, 0.0, scale.z
    );

    return scale_diag * xyz;
}

mat3 XYZ_to_RGB(Chromaticity C) {
    if (C == GRAY)
        return SingularY3;

    return inverse(RGB_to_XYZ(C));
}

// http://www.brucelindbloom.com/Eqn_ChromAdapt.html
mat3 adaptation(vec2 w1, vec2 w2, mat3 cat) {
    vec3 src_xyz = xyY_to_XYZ(vec3(w1, 1.0));
    vec3 dst_xyz = xyY_to_XYZ(vec3(w2, 1.0));

    vec3 src_lms = src_xyz * cat;
    vec3 dst_lms = dst_xyz * cat;

    vec3 scale = dst_lms / src_lms;
    mat3 scale_diag = mat3(
        scale.x, 0.0, 0.0,
        0.0, scale.y, 0.0,
        0.0, 0.0, scale.z
    );

    return cat * scale_diag * inverse(cat);
}

// CAM16 uses CAT16 as cat and equal-energy illuminant (E) as wt.
// https://www.researchgate.net/publication/318152296_Comprehensive_color_solutions_CAM16_CAT16_and_CAM16-UCS
// Android uses Bradford as cat and D50 as wt.
// https://android.googlesource.com/platform/frameworks/base/+/master/graphics/java/android/graphics/ColorSpace.java
mat3 adaptation_two_step(vec2 w1, vec2 w2, vec2 wt, mat3 cat) {
    return adaptation(w1, wt, cat) * adaptation(wt, w2, cat);
}

mat3 adaptation_two_step(vec2 w1, vec2 w2) {
    return adaptation_two_step(w1, w2, E, CAT16);
}

mat3 RGB_to_RGB(Chromaticity c1, Chromaticity c2) {
    mat3 m = Identity3;
    if (c1 != c2) {
        m *= RGB_to_XYZ(c1);
        if (c1.w != c2.w) {
            m *= adaptation_two_step(c1.w, c2.w);
        }
        m *= XYZ_to_RGB(c2);
    }
    return m;
}

vec3 RGB_to_RGB(vec3 c, Chromaticity c1, Chromaticity c2) {
    return c * RGB_to_RGB(c1, c2);
}

vec4 hook() {
    vec4 color = HOOKED_tex(HOOKED_pos);

    color.rgb = RGB_to_RGB(color.rgb, from, to);

    return color;
}
