// Copyright (c) 2016-2018, bacondither
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer
//    in this position and unchanged.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, 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.

// Colourfulness - version 2018-11-12
// EXPECTS FULL RANGE GAMMA LIGHT

#include "ReShadeUI.fxh"

uniform float colourfulness < __UNIFORM_SLIDER_FLOAT1
	ui_min = -1.0; ui_max = 2.0;
	ui_tooltip = "Degree of colourfulness, 0 = neutral";
	ui_step = 0.01;
> = 0.4;

uniform float lim_luma < __UNIFORM_SLIDER_FLOAT1
	ui_min = 0.1; ui_max = 1.0;
	ui_tooltip = "Lower values allows for more change near clipping";
	ui_step = 0.01;
> = 0.7;

uniform bool enable_dither <
	ui_tooltip = "Enables dithering, avoids introducing banding in gradients";
	ui_category = "Dither";
> = false;

uniform bool col_noise <
	ui_tooltip = "Coloured dither noise, lower subjective noise level";
	ui_category = "Dither";
> = true;

uniform float backbuffer_bits <
	ui_min = 1.0; ui_max = 32.0;
	ui_tooltip = "Backbuffer bith depth, most likely 8 or 10 bits";
	ui_category = "Dither";
> = 8.0;

//-------------------------------------------------------------------------------------------------
#ifndef fast_luma
	#define fast_luma 1 // Rapid approx of sRGB gamma, small difference in quality
#endif

#ifndef temporal_dither
	#define temporal_dither 0 // Dither changes with every frame
#endif
//-------------------------------------------------------------------------------------------------

#include "ReShade.fxh"

#if (temporal_dither == 1)
	uniform int rnd < source = "random"; min = 0; max = 1000; >;
#endif

// Sigmoid function, sign(v)*pow(pow(abs(v), -2) + pow(s, -2), 1.0/-2)
#define soft_lim(v,s)  ( (v*s)*rcp(sqrt(s*s + v*v)) )

// Weighted power mean, p = 0.5
#define wpmean(a,b,w)  ( pow(abs(w)*sqrt(abs(a)) + abs(1-w)*sqrt(abs(b)), 2) )

// Max/Min RGB components
#define maxRGB(c)      ( max((c).r, max((c).g, (c).b)) )
#define minRGB(c)      ( min((c).r, min((c).g, (c).b)) )

// Mean of Rec. 709 & 601 luma coefficients
#define lumacoeff        float3(0.2558, 0.6511, 0.0931)

float3 Colourfulness(float4 vpos : SV_Position, float2 tex : TEXCOORD) : SV_Target
{
	#if (fast_luma == 1)
		float3 c0  = tex2D(ReShade::BackBuffer, tex).rgb;
		float luma = sqrt(dot(saturate(c0*abs(c0)), lumacoeff));
		c0 = saturate(c0);
	#else // Better approx of sRGB gamma
		float3 c0  = saturate(tex2D(ReShade::BackBuffer, tex).rgb);
		float luma = pow(dot(pow(c0 + 0.06, 2.4), lumacoeff), 1.0/2.4) - 0.06;
	#endif

	// Calc colour saturation change
	float3 diff_luma = c0 - luma;
	float3 c_diff = diff_luma*(colourfulness + 1) - diff_luma;

	if (colourfulness > 0.0)
	{
		// 120% of c_diff clamped to max visible range + overshoot
		float3 rlc_diff = clamp((c_diff*1.2) + c0, -0.0001, 1.0001) - c0;

		// Calc max saturation-increase without altering RGB ratios
		float poslim = (1.0002 - luma)/(abs(maxRGB(diff_luma)) + 0.0001);
		float neglim = (luma + 0.0002)/(abs(minRGB(diff_luma)) + 0.0001);

		float3 diffmax = diff_luma*min(min(poslim, neglim), 32) - diff_luma;

		// Soft limit diff
		c_diff = soft_lim( c_diff, max(wpmean(diffmax, rlc_diff, lim_luma), 1e-7) );
	}

	if (enable_dither == true)
	{
		// Interleaved gradient noise by Jorge Jimenez
		const float3 magic = float3(0.06711056, 0.00583715, 52.9829189);
		#if (temporal_dither == 1)
			float xy_magic = (vpos.x + rnd)*magic.x + (vpos.y + rnd)*magic.y;
		#else
			float xy_magic = vpos.x*magic.x + vpos.y*magic.y;
		#endif
		float noise = (frac(magic.z*frac(xy_magic)) - 0.5)/(exp2(backbuffer_bits) - 1);
		c_diff += col_noise == true ? float3(-noise, noise, -noise) : noise;
	}

	return saturate(c0 + c_diff);
}

technique Colourfulness
{
	pass
	{
		VertexShader = PostProcessVS;
		PixelShader  = Colourfulness;
	}
}
