/*
# License Terms for Academy Color Encoding System Components #

Academy Color Encoding System (ACES) software and tools are provided by the
Academy under the following terms and conditions: A worldwide, royalty-free,
non-exclusive right to copy, modify, create derivatives, and use, in source and
binary forms, is hereby granted, subject to acceptance of this license.

Copyright © 2015 Academy of Motion Picture Arts and Sciences (A.M.P.A.S.).
Portions contributed by others as indicated. All rights reserved.

Performance of any of the aforementioned acts indicates acceptance to be bound
by the following terms and conditions:

* Copies of source code, in whole or in part, must retain the above copyright
notice, this list of conditions and the Disclaimer of Warranty.

* Use in binary form must retain the above copyright notice, this list of
conditions and the Disclaimer of Warranty in the documentation and/or other
materials provided with the distribution.

* Nothing in this license shall be deemed to grant any rights to trademarks,
copyrights, patents, trade secrets or any other intellectual property of
A.M.P.A.S. or any contributors, except as expressly stated herein.

* Neither the name "A.M.P.A.S." nor the name of any other contributors to this
software may be used to endorse or promote products derivative of or based on
this software without express prior written permission of A.M.P.A.S. or the
contributors, as appropriate.

This license shall be construed pursuant to the laws of the State of
California, and any disputes related thereto shall be subject to the
jurisdiction of the courts therein.

Disclaimer of Warranty: THIS SOFTWARE IS PROVIDED BY A.M.P.A.S. AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL A.M.P.A.S., OR ANY
CONTRIBUTORS OR DISTRIBUTORS, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, RESITUTIONARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE ACADEMY SPECIFICALLY
DISCLAIMS ANY REPRESENTATIONS OR WARRANTIES WHATSOEVER RELATED TO PATENT OR
OTHER INTELLECTUAL PROPERTY RIGHTS IN THE ACADEMY COLOR ENCODING SYSTEM, OR
APPLICATIONS THEREOF, HELD BY PARTIES OTHER THAN A.M.P.A.S.,WHETHER DISCLOSED OR
UNDISCLOSED.
*/

#include "/lib/aces/splines.glsl"
#include "/lib/aces/transforms.glsl"

#define acesRRTExposureBias 1.00    //[0.50 0.55 0.60 0.65 0.70 0.75 0.80 0.85 0.90 0.95 1.00 1.05 1.10 1.15 1.20 1.25 1.30 1.35 1.40 1.45 1.50]
#define acesRRTGammaLift 1.00       //[0.50 0.55 0.60 0.65 0.70 0.75 0.80 0.85 0.90 0.95 1.00 1.05 1.10 1.15 1.20 1.25 1.30 1.35 1.40 1.45 1.50]
#define acesRRTGlowGainOffset 0.0   //[-0.20 -0.18 -0.16 -0.14 -0.12 -0.10 -0.08 -0.06 -0.04 -0.02 0.0 0.02 0.04 0.06 0.08 0.10 0.12 0.14 0.16 0.18 0.20]
#define acesRRTSatOffset 0.0    //[-0.20 -0.18 -0.16 -0.14 -0.12 -0.10 -0.08 -0.06 -0.04 -0.02 0.0 0.02 0.04 0.06 0.08 0.10 0.12 0.14 0.16 0.18 0.20]
#define acesODTSatOffset 0.0    //[-0.20 -0.18 -0.16 -0.14 -0.12 -0.10 -0.08 -0.06 -0.04 -0.02 0.0 0.02 0.04 0.06 0.08 0.10 0.12 0.14 0.16 0.18 0.20]

// #if (GRADING_STYLE == 0)
// 	//FILMIC
// 	const float dimSurroundGamma = 0.9211; //default 0.9811

// 	const float rrtGlowGain     = 0.25 + acesRRTGlowGainOffset;     //default 0.05
// 	const float rrtGlowMid      = 0.08;     //default 0.08

// 	const float rrtRedScale     = 0.9;     //default 0.82
// 	const float rrtRedPrivot    = 0.03;     //default 0.03
// 	const float rrtRedHue       = 0.0;      //default 0.0
// 	const float rrtRedWidth     = 135.0;    //default 135.0

// 	const float rrtSatFactor    = 0.97 + acesRRTSatOffset;     //default 0.96
// 	const float odtSatFactor    = 0.96 + acesODTSatOffset;      //default 0.93

// 	const float odtGammaLift    = 0.92;      //Default 1.00

// 	#define WHITE_BALANCE                7500        // [4000 4100 4200 4300 4400 4500 4600 4700 4800 4900 5000 5100 5200 5300 5400 5500 5600 5700 5800 5900 6000 6100 6200 6300 6400 6500 6600 6700 6800 6900 7000 7100 7200 7300 7400 7500 7600 7700 7800 7900 8000 8100 8200 8300 8400 8500 8600 8700 8800 8900 9000 9100 9200 9300 9400 9500 9600 9700 9800 9900 10000 10100 10200 10300 10400 10500 10600 10700 10800 10900 11000 11100 11200 11300 11400 11500 11600 11700 11800 11900 12000]     
// 	#define CONT_MOD                    -0.03        // [-0.4 -0.3 -0.2 -0.15 -0.1 -0.09 -0.08 -0.07 -0.06 -0.05 -0.04 -0.03 -0.02 -0.01 0.0 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0]
// 	#define CONT_MIDPOINT                0.5         // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0]
	
// 	const vec3 colorTint        = vec3(255, 238, 214)/255.0;
// #elif (GRADING_STYLE == 1)
	//NATURAL
	const float dimSurroundGamma = 0.9111; //default 0.9811

	const float rrtGlowGain     = 0.25 + acesRRTGlowGainOffset;     //default 0.05
	const float rrtGlowMid      = 0.08;     //default 0.08

	const float rrtRedScale     = 0.9;     //default 0.82
	const float rrtRedPrivot    = 0.03;     //default 0.03
	const float rrtRedHue       = 0.0;      //default 0.0
	const float rrtRedWidth     = 135.0;    //default 135.0

	const float rrtSatFactor    = 0.93 + acesRRTSatOffset;     //default 0.96
	const float odtSatFactor    = 0.93 + acesODTSatOffset;      //default 0.93

	const float odtGammaLift    = 1.00;      //Default 1.00

	#define COLOUR_GRADING
	#define WHITE_BALANCE                7100        // [4000 4100 4200 4300 4400 4500 4600 4700 4800 4900 5000 5100 5200 5300 5400 5500 5600 5700 5800 5900 6000 6100 6200 6300 6400 6500 6600 6700 6800 6900 7000 7100 7200 7300 7400 7500 7600 7700 7800 7900 8000 8100 8200 8300 8400 8500 8600 8700 8800 8900 9000 9100 9200 9300 9400 9500 9600 9700 9800 9900 10000 10100 10200 10300 10400 10500 10600 10700 10800 10900 11000 11100 11200 11300 11400 11500 11600 11700 11800 11900 12000]     
	#define CONT_MOD                    -0.04        // [-0.4 -0.3 -0.2 -0.15 -0.125 -0.1 -0.09 -0.08 -0.07 -0.06 -0.05 -0.04 -0.03 -0.02 -0.01 0.0 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0]
	#define CONT_MIDPOINT                0.5         // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0]

	const vec3 colorTint        = vec3(255, 255, 255)/255.0;
// #endif

struct ColorCorrection {
	float saturation;
	float vibrance;
	vec3 lum;
	float contrast;
	float contrastMidpoint;

	vec3 gain;
	vec3 lift;
	vec3 InvGamma;
} m;
 
float rgb_2_saturation(vec3 rgb) {
	float minrgb = min(min(rgb.r, rgb.g), rgb.b);
	float maxrgb = max(max(rgb.r, rgb.g), rgb.b);
	
	return (max(maxrgb, 1e-10) - max(minrgb, 1e-10)) / max(maxrgb, 1e-2);
}

float rgb_2_yc(vec3 rgb) { // Converts RGB to a luminance proxy, here called YC. YC is ~ Y + K * Chroma.
	float ycRadiusWeight = 1.75;
	float r = rgb[0]; float g = rgb[1]; float b = rgb[2];
	float chroma = sqrt(b * (b - g) + g * (g - r) + r * (r - b));

	return (b + g + r + ycRadiusWeight * chroma) / 3.0;
}

float glow_fwd(float ycIn, float glowGainIn, float glowMid) {
	float glowGainOut;

	if (ycIn <= 2.0 / 3.0 * glowMid) {
		glowGainOut = glowGainIn;
	} else if ( ycIn >= 2.0 * glowMid) {
		glowGainOut = 0;
	} else {
		glowGainOut = glowGainIn * (glowMid / ycIn - 0.5);
	}

	return glowGainOut;
}

float rgb_2_hue(vec3 rgb) { // Returns a geometric hue angle in degrees (0-360) based on RGB values.
	float hue;
	if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) { // For neutral colors, hue is undefined and the function will return a quiet NaN value.
		hue = 0;
	} else {
		hue = (180.0 / PI) * atan(2.0 * rgb[0] - rgb[1] - rgb[2], sqrt(3.0) * (rgb[1] - rgb[2])); // flip due to opengl spec compared to hlsl
	}

	if (hue < 0.0) 
		hue = hue + 360.0;

	return clamp(hue, 0.0, 360.0);
}

float center_hue(float hue, float centerH) {
	float hueCentered = hue - centerH;
	
	if (hueCentered < -180.0) {
		hueCentered += 360.0;
	} else if (hueCentered > 180.0) {
		hueCentered -= 360.0;
	}
	
	return hueCentered;
}

// Transformations between CIE XYZ tristimulus values and CIE x,y 
// chromaticity coordinates
vec3 XYZ_2_xyY( vec3 XYZ ) {
	float divisor = max(XYZ[0] + XYZ[1] + XYZ[2], 1e-10);
	
	vec3 xyY    = XYZ.xyy;
	     xyY.rg = XYZ.rg / divisor;

	return xyY;
}

vec3 xyY_2_XYZ(vec3 xyY) {
	vec3 XYZ   = vec3(0.0);
	     XYZ.r = xyY.r * xyY.b / max(xyY.g, 1e-10);
	     XYZ.g = xyY.b;  
	     XYZ.b = (1.0 - xyY.r - xyY.g) * xyY.b / max(xyY.g, 1e-10);

	return XYZ;
}

float y_linVC(float y, float max, float min) {
    return (y - min) / (max - min);
}

vec3 darkToDimSurround(vec3 linear_cv) {
    vec3 xyz    = linear_cv * AP1_XYZ;
    vec3 xyY    = XYZ_2_xyY(xyz);
        xyY.z   = clamp(xyY.z, 0.0, 65535.0);
        xyY.z   = pow(xyY.z, dimSurroundGamma);
        xyz     = xyY_2_XYZ(xyY);

    return xyz * XYZ_AP1;
}

mat3 ChromaticAdaptation( vec2 src_xy, vec2 dst_xy ) {
	// Von Kries chromatic adaptation 

	// Bradford
	const mat3 ConeResponse = mat3(
		 vec3(0.8951,  0.2664, -0.1614),
		vec3(-0.7502,  1.7135,  0.0367),
		 vec3(0.0389, -0.0685,  1.0296)
	);
	const mat3 InvConeResponse = mat3(
		vec3(0.9869929, -0.1470543,  0.1599627),
		vec3(0.4323053,  0.5183603,  0.0492912),
		vec3(-0.0085287,  0.0400428,  0.9684867)
	);

	vec3 src_XYZ = xyY_2_XYZ( vec3( src_xy, 1 ) );
	vec3 dst_XYZ = xyY_2_XYZ( vec3( dst_xy, 1 ) );

	vec3 src_coneResp = src_XYZ * ConeResponse;
	vec3 dst_coneResp = dst_XYZ *  ConeResponse;

	mat3 VonKriesMat = mat3(
		vec3(dst_coneResp[0] / src_coneResp[0], 0.0, 0.0),
		vec3(0.0, dst_coneResp[1] / src_coneResp[1], 0.0),
		vec3(0.0, 0.0, dst_coneResp[2] / src_coneResp[2])
	);

	return (ConeResponse * VonKriesMat) * InvConeResponse;
}

/*******************************************************************************
 - Color CorrectionUE4 Style
 ******************************************************************************/
 
 // Accurate for 1000K < Temp < 15000K
// [Krystek 1985, "An algorithm to calculate correlated colour temperature"]
vec2 PlanckianLocusChromaticity(float Temp) {
	float u = ( 0.860117757f + 1.54118254e-4f * Temp + 1.28641212e-7f * Temp*Temp ) / ( 1.0f + 8.42420235e-4f * Temp + 7.08145163e-7f * Temp*Temp );
	float v = ( 0.317398726f + 4.22806245e-5f * Temp + 4.20481691e-8f * Temp*Temp ) / ( 1.0f - 2.89741816e-5f * Temp + 1.61456053e-7f * Temp*Temp );

	float x = 3.0*u / ( 2.0*u - 8.0*v + 4.0 );
	float y = 2.0*v / ( 2.0*u - 8.0*v + 4.0 );

	return vec2(x, y);
}
 
 vec2 D_IlluminantChromaticity(float Temp) { 
	// Accurate for 4000K < Temp < 25000K
	// in: correlated color temperature
	// out: CIE 1931 chromaticity
	// Correct for revision of Plank's law
	// This makes 6500 == D65
	Temp *= 1.4388 / 1.438;

	float x =	Temp <= 7000 ?
				0.244063 + ( 0.09911e3 + ( 2.9678e6 - 4.6070e9 / Temp ) / Temp ) / Temp :
				0.237040 + ( 0.24748e3 + ( 1.9018e6 - 2.0064e9 / Temp ) / Temp ) / Temp;
	
	float y = -3 * x*x + 2.87 * x - 0.275;

	return vec2(x,y);
}

vec2 PlanckianIsothermal( float Temp, float Tint ) {
	float u = ( 0.860117757f + 1.54118254e-4f * Temp + 1.28641212e-7f * Temp*Temp ) / ( 1.0f + 8.42420235e-4f * Temp + 7.08145163e-7f * Temp*Temp );
	float v = ( 0.317398726f + 4.22806245e-5f * Temp + 4.20481691e-8f * Temp*Temp ) / ( 1.0f - 2.89741816e-5f * Temp + 1.61456053e-7f * Temp*Temp );

	float ud = ( -1.13758118e9f - 1.91615621e6f * Temp - 1.53177f * Temp*Temp ) / pow( 1.41213984e6f + 1189.62f * Temp + Temp*Temp, 2.0 );
	float vd = (  1.97471536e9f - 705674.0f * Temp - 308.607f * Temp*Temp ) / pow( 6.19363586e6f - 179.456f * Temp + Temp*Temp , 2.0); //don't pow2 this

	vec2 uvd = normalize( vec2( u, v ) );

	// Correlated color temperature is meaningful within +/- 0.05
	u += -uvd.y * Tint * 0.05;
	v +=  uvd.x * Tint * 0.05;
	
	float x = 3*u / ( 2*u - 8*v + 4 );
	float y = 2*v / ( 2*u - 8*v + 4 );

	return vec2(x,y);
}

vec3 WhiteBalance(vec3 LinearColor) {
	const float WhiteTemp = float(WHITE_BALANCE);
	const float WhiteTint = 0.0;
	vec2 SrcWhiteDaylight = D_IlluminantChromaticity( WhiteTemp );
	vec2 SrcWhitePlankian = PlanckianLocusChromaticity( WhiteTemp );

	vec2 SrcWhite = WhiteTemp < 4000 ? SrcWhitePlankian : SrcWhiteDaylight;
	const vec2 D65White = vec2(0.31270,  0.32900);

	// Offset along isotherm
	vec2 Isothermal = PlanckianIsothermal( WhiteTemp, WhiteTint ) - SrcWhitePlankian;
	SrcWhite += Isothermal;

	mat3x3 WhiteBalanceMat = ChromaticAdaptation( SrcWhite, D65White );
	WhiteBalanceMat = (sRGB_XYZ * WhiteBalanceMat) * XYZ_sRGB;

	return LinearColor * WhiteBalanceMat * 1.0;
}

vec3 Saturation(vec3 color, ColorCorrection m) {
	float grey = dot(color, m.lum);
	return grey + m.saturation * (color - grey);
}

vec3 Vibrance(vec3 color, ColorCorrection m) {
	float maxColor = max3(color.r, color.g, color.b);
	float minColor = min3(color.r, color.g, color.b);

	float colorSaturation = maxColor - minColor;

	float grey = dot(color, m.lum);
	color = mix(vec3(grey), color, 1.0 + m.vibrance * (1.0 - sign(m.vibrance) * colorSaturation));

	return color;
}

vec3 LiftGammaGain(vec3 v, ColorCorrection m) {
	vec3 lerpV = clamp01(pow(v, m.InvGamma));
	return m.gain * lerpV + m.lift * (1.0 - lerpV);
}

float LogContrast(float x, const float eps, float logMidpoint, float contrast) {
	float logX = log2(x + eps);
	float adjX = (logX - logMidpoint) / contrast + logMidpoint;

	return max0(exp2(adjX) - eps);
}

vec3 Contrast(vec3 color, ColorCorrection m) {
	const float contrastEpsilon = 1e-5;

	vec3 ret;
	     ret.x = LogContrast(color.x, contrastEpsilon, log2(0.18), m.contrast);
		 ret.y = LogContrast(color.y, contrastEpsilon, log2(0.18), m.contrast);
		 ret.z = LogContrast(color.z, contrastEpsilon, log2(0.18), m.contrast);

	return ret;
}

vec3 srgbToLinear(vec3 srgb) {
    return mix(
        srgb * 0.07739938080495356, // 1.0 / 12.92 = ~0.07739938080495356
        pow(0.947867 * srgb + 0.0521327, vec3(2.4)),
        step(0.04045, srgb)
    );
}

vec3 linearToSrgb(vec3 linear) {
    return mix(
        linear * 12.92,
        pow(linear, vec3(0.416666666667)) * 1.055 - 0.055, // 1.0 / 2.4 = ~0.416666666667
        step(0.0031308, linear)
    );
}

vec3 rrtSweeteners(vec3 aces) {
    /* glow module */
    float saturation    = rgb_2_saturation(aces);
    float yc_in         = rgb_2_yc(aces);
    float s             = sigmoid_shaper((saturation - 0.4) / 0.2);
    float added_glow    = 1.0 + glow_fwd(yc_in, rrtGlowGain * s, rrtGlowMid);
    
        aces   *= added_glow;

    /* red modifier module */
    float hue           = rgb_2_hue(aces);
    float centered_hue  = center_hue(hue, rrtRedHue);
    float hue_weight    = cubic_basis_shaper(centered_hue, rrtRedWidth);

        aces.r += hue_weight * saturation * (rrtRedPrivot - aces.r) * (1.0 - rrtRedScale);

    /* aces to rgb */
        aces    = clamp(aces, 0.0, 65535.0);

        vec3 rgbPre     = aces * AP0_AP1;
            rgbPre      = clamp(rgbPre, 0.0, 65535.0);

    /* global desaturation */
        rgbPre          = mix(vec3(dot(rgbPre, AP1_RGB_Y)), rgbPre , rrtSatFactor);
        //rgbPre          = rgbPre  * rrt_sat_matrix;

        rgbPre    = pow(rgbPre, vec3(0.95 / acesRRTGammaLift));
    
    return rgbPre;
}

/* takes input as academy color and returns oces */
vec3 academyRRT(vec3 aces) {
    vec3 rgbPre         = rrtSweeteners(aces);

    /* apply tonescale in rgb space */
    vec3 rgbPost;
        rgbPost.r       = segmented_spline_c5_fwd(rgbPre.r);
        rgbPost.g       = segmented_spline_c5_fwd(rgbPre.g);
        rgbPost.b       = segmented_spline_c5_fwd(rgbPre.b);

    /* rgb space to oces */
    return rgbPost * AP1_AP0;
}

vec3 odtSRGB_D65(vec3 oces) {
    vec3 rgbPre  = oces * AP0_AP1;

    SegmentedSplineParams_c9 odt_48nit = SegmentedSplineParams_c9(
        float[10](-1.6989700043, -1.6989700043, -1.4779000000, -1.2291000000, -0.8648000000, -0.4480000000, 0.0051800000, 0.4511080334, 0.9113744414, 0.9113744414),
        float[10](0.5154386965, 0.8470437783, 1.1358000000, 1.3802000000, 1.5197000000, 1.5985000000, 1.6467000000, 1.6746091357, 1.6878733390, 1.6878733390),
        vec2(segmented_spline_c5_fwd(0.18 * exp2(-6.5)), 0.02),
        vec2(segmented_spline_c5_fwd(0.18), 4.8),
        vec2(segmented_spline_c5_fwd(0.18 * exp2(6.5)), 48.0),
        0.0,
        0.04
    );

    /* apply tonescale in rgb space */
    vec3 rgbPost;
        rgbPost.r       = segmented_spline_c9_fwd(rgbPre.r, odt_48nit);
        rgbPost.g       = segmented_spline_c9_fwd(rgbPre.g, odt_48nit);
        rgbPost.b       = segmented_spline_c9_fwd(rgbPre.b, odt_48nit);

    /* black and white points for cinema system */
    const float cinema_white    = 48.0;
    const float cinema_black    = 0.02;     //white / 2400.0, default 0.02

    /* scale luminance to linear */
    vec3 linear_cv;
        linear_cv.r     = y_linVC(rgbPost.r, cinema_white, cinema_black);
        linear_cv.g     = y_linVC(rgbPost.g, cinema_white, cinema_black);
        linear_cv.b     = y_linVC(rgbPost.b, cinema_white, cinema_black);

        linear_cv       = darkToDimSurround(linear_cv);

    /* global desaturation */
        linear_cv       = mix(vec3(dot(linear_cv, AP1_RGB_Y)), linear_cv, odtSatFactor);

	/* Added Gamma Correction to allow for color response tuning */
        linear_cv        = pow(linear_cv, vec3(odtGammaLift));

    /* convert to display primary encoding */
    vec3 xyz    = linear_cv * AP1_XYZ;
        xyz     = xyz * D60_D65;

        linear_cv   = xyz * XYZ_sRGB;

    return clamp(linear_cv, 0.0, 1.0);
}

struct acesSplineFitParam {
    float slope;
    float toe;
    float shoulder;
    float blackClip;
    float whiteClip;
};

vec3 ACES_AP1_SRGB_RRT(vec3 AP1) {
        AP1    *= 1.0 * acesRRTExposureBias;
    vec3 ACES   = AP1 * AP1_AP0;
    ACES        = academyRRT(ACES);
    ACES        = odtSRGB_D65(ACES);

    return ACES;
}

vec3 FilmToneMap(vec3 LinearColor) {
	const mat3 AP0_2_sRGB = (AP0_XYZ * D60_D65) * XYZ_sRGB;
	const mat3 AP1_2_sRGB = (AP1_XYZ * D60_D65) * XYZ_sRGB;
	
	const mat3 AP0_2_AP1 = AP0_XYZ * XYZ_AP1;
	const mat3 AP1_2_AP0 = AP1_XYZ * XYZ_AP0;
	
	// vec3 ColorAP1 = LinearColor * AP0_2_AP1;
	// float LumaAP1 = dot( ColorAP1, AP1_RGB_Y );
	
	// vec3 ChromaAP1 = ColorAP1 / LumaAP1;
	
	// float ChromaDistSqr = dot( ChromaAP1 - 1, ChromaAP1 - 1 );
	// float ExpandAmount = ( 1 - exp2( -4 * ChromaDistSqr ) ) * ( 1 - exp2( -4 * Gamut_Expansion * LumaAP1*LumaAP1 ) );
	
	// const mat3 Wide_2_XYZ_MAT = mat3(
	// 	vec3(0.5441691,  0.2395926,  0.1666943),
	// 	vec3(0.2394656,  0.7021530,  0.0583814),
	// 	vec3(-0.0023439,  0.0361834,  1.0552183)
	// );
	
	// const mat3 Wide_2_AP1 = Wide_2_XYZ_MAT * XYZ_AP1;
	// const mat3 ExpandMat = AP1_2_sRGB * Wide_2_AP1;

	// vec3 ColorExpand = ColorAP1 * ExpandMat;
	// ColorAP1 = mix(ColorAP1, ColorExpand, ExpandAmount);

	// const mat3 BlueCorrect = mat3(
	// 	vec3(0.9404372683, -0.0183068787, 0.0778696104),
	// 	vec3(0.0083786969,  0.8286599939, 0.1629613092),
	// 	vec3(0.0005471261, -0.0008833746, 1.0003362486)
	// );
	const mat3 BlueCorrectInv = mat3(
		vec3(1.06318,     0.0233956, -0.0865726),
		vec3(-0.0106337,   1.20632,   -0.19569),
		vec3(-0.000590887, 0.00105248, 0.999538)
	);
	
	// const mat3 BlueCorrectAP1    = (AP1_2_AP0 *  BlueCorrect) * AP0_2_AP1;
	const mat3 BlueCorrectInvAP1 = (AP1_2_AP0 * BlueCorrectInv) * AP0_2_AP1;

	// // Blue correction
	// ColorAP1 = mix(ColorAP1, ColorAP1 *  BlueCorrectAP1, Blue_Correction);
	
	vec3 ColorAP0 = LinearColor * AP1_2_AP0;

	// "Glow" module constants
	const float RRT_GLOW_GAIN = 0.05;
	const float RRT_GLOW_MID = 0.08;

	float saturation = rgb_2_saturation(ColorAP0);
	float ycIn = rgb_2_yc(ColorAP0);
	float s = sigmoid_shaper((saturation - 0.4) * 5.0);
	float addedGlow = 1.0 + glow_fwd(ycIn, RRT_GLOW_GAIN * s, RRT_GLOW_MID) * 3;
	ColorAP0 *= addedGlow;

	// --- Red modifier --- //
	const float RRT_RED_SCALE = 0.96; // 0.99
	const float RRT_RED_PIVOT = 0.01; // 0.22
	const float RRT_RED_HUE   = 0.0; // 0.15
	const float RRT_RED_WIDTH = 135.0; //135.0
	float hue = rgb_2_hue(ColorAP0);
	float centeredHue = center_hue(hue, RRT_RED_HUE);
	float hueWeight = pow(smoothstep(0.0, 1.0, 1.0 - abs(2.0 * centeredHue / RRT_RED_WIDTH)), 2.0);
	
	ColorAP0.r += hueWeight * saturation * (RRT_RED_PIVOT - ColorAP0.r) * (1.0 - RRT_RED_SCALE);
	
	// Use ACEScg primaries as working space
	vec3 WorkingColor = ColorAP0 * AP0_AP1 * 1.2;
	     WorkingColor = max(vec3(0.0), WorkingColor) * 1.1;
	     WorkingColor = mix(vec3(dot(WorkingColor, AP1_RGB_Y)), WorkingColor, 0.96); // Pre desaturate
	
	const float ToeScale      = 1.0 + Black_Clip - Film_Toe;
	const float ShoulderScale = 1.0 + White_Clip - Film_Shoulder;
	

	float ToeMatch = 0.0;
	if(Film_Toe > 0.8) {
		// 0.18 will be on straight segment
		ToeMatch = (1.0 - Film_Toe - Out_Match) / Film_Slope + log10(in_Match);
	} else {
		// 0.18 will be on toe segment
		// Solve for ToeMatch such that input of in_Match gives output of Out_Match.
		const float bt = (Out_Match + Black_Clip) / ToeScale - 1.0;
		ToeMatch = log10(in_Match) - 0.5 * log((1.0 + bt) / (1.0 - bt)) * (ToeScale / Film_Slope);
	}

	float StraightMatch = (1.0 - Film_Toe) / Film_Slope - ToeMatch;
	float ShoulderMatch = Film_Shoulder / Film_Slope - StraightMatch;
	
	vec3 LogColor = log10(WorkingColor);
	vec3 StraightColor = Film_Slope * (LogColor + StraightMatch);
	
	vec3 ToeColor		= (-Black_Clip) + (2.0 * ToeScale) / (1.0 + expf((-2.0 * Film_Slope / ToeScale) * (LogColor - ToeMatch)));
	vec3 ShoulderColor	= (1.0 + White_Clip) - (2.0 * ShoulderScale) / (1.0 + expf(( 2.0 * Film_Slope / ShoulderScale) * (LogColor - ShoulderMatch)));
	
	for(int i = 0; i < 1; ++i) {
		ToeColor[i] = LogColor[i] < ToeMatch ? ToeColor[i] : StraightColor[i];
		ShoulderColor[i] = LogColor[i] > ShoulderMatch ? ShoulderColor[i] : StraightColor[i];
	}

	vec3 t = clamp((LogColor - ToeMatch) / (ShoulderMatch - ToeMatch), 0.0, 1.0);
	     t = ShoulderMatch < ToeMatch ? 1.0 - t : t;
	     t = (3.0 - 2.0 * t) * t * t;
	
	vec3 ToneColor = mix(ToeColor, ShoulderColor, t);
	     ToneColor = mix(vec3(dot(ToneColor, AP1_RGB_Y)), ToneColor, 0.93); // Post desaturate
		 
	ToneColor = mix(ToneColor, ToneColor * BlueCorrectInvAP1, Blue_Correction);

	// Returning positive AP1 values
	return max(vec3(0.0), ToneColor * AP1_2_sRGB);
}