float4x4 brightnessMatrix( float brightness )
{
    return float4x4(1, 0, 0, 0,
					0, 1, 0, 0,
					0, 0, 1, 0,
					brightness, brightness, brightness, 1 );
}

float4x4 contrastMatrix( float contrast )
{
	float t = ( 1.0 - contrast ) / 2.0;
    
    return float4x4(contrast, 0, 0, 0,
					0, contrast, 0, 0,
					0, 0, contrast, 0,
					t, t, t, 1 );

}

float4x4 saturationMatrix( float saturation )
{
    float3 luminance = float3( 0.3086, 0.6094, 0.0820 );
    
    float oneMinusSat = 1.0 - saturation;
    
    float3 red = float3( luminance.x * oneMinusSat, luminance.x * oneMinusSat, luminance.x * oneMinusSat );
    red+= float3( saturation, 0, 0 );
    
    float3 green = float3( luminance.y * oneMinusSat, luminance.y * oneMinusSat, luminance.y * oneMinusSat );
    green += float3( 0, saturation, 0 );
    
    float3 blue = float3( luminance.z * oneMinusSat, luminance.z * oneMinusSat, luminance.z * oneMinusSat );
    blue += float3( 0, 0, saturation );
    
    return float4x4(red,     0,
					green,   0,
					blue,    0,
					0, 0, 0, 1 );
}

float3 Brightness(float3 color, float brightness, float contrast)
{
	const float saturation = 1.0;
	float4 res;

    res = mul(
       		float4(color, 1.0),
			mul(
				mul(
					brightnessMatrix( brightness ),
					contrastMatrix( contrast )
				),
				saturationMatrix( saturation )
			)
			);
	
	return res.rgb;
}

/////////////////////////////////////////////////////////////////////////////////////

float3 RGBToHCV( in float3 RGB )
{
    float4 P         = ( RGB.g < RGB.b ) ? float4( RGB.bg, -1.0f, 2.0f/3.0f ) : float4( RGB.gb, 0.0f, -1.0f/3.0f );
    float4 Q1        = ( RGB.r < P.x ) ? float4( P.xyw, RGB.r ) : float4( RGB.r, P.yzx );
    float C          = Q1.x - min( Q1.w, Q1.y );
    float H          = abs(( Q1.w - Q1.y ) / ( 6.0f * C + 0.000001f ) + Q1.z );
    return float3( H, C, Q1.x );
}

float3 RGBToHSL( in float3 RGB )
{
    RGB.xyz          = max( RGB.xyz, 0.000001f );
    float3 HCV       = RGBToHCV(RGB);
    float L          = HCV.z - HCV.y * 0.5f;
    float S          = HCV.y / ( 1.0f - abs( L * 2.0f - 1.0f ) + 0.000001f);
    return float3( HCV.x, S, L );
} 

float curve( float x, float k )
{
    float s = sign( x - 0.5f );
    float o = ( 1.0f + s ) / 2.0f;
    return o - 0.5f * s * pow( 2.0f * ( o - s * x ), k );
}

float3 ProcessBW( float3 col, float r, float y, float g, float c, float b, float m )
{
    float3 hsl         = RGBToHSL( col.xyz );
    float lum          = 1.0f - hsl.z;
		
	float curve_str=4.000000;
		
    float weight_r     = curve( max( 1.0f - abs(  hsl.x               * 6.0f ), 0.0f ), curve_str ) +
                         curve( max( 1.0f - abs(( hsl.x - 1.0f      ) * 6.0f ), 0.0f ), curve_str );
    float weight_y     = curve( max( 1.0f - abs(( hsl.x - 0.166667f ) * 6.0f ), 0.0f ), curve_str );
    float weight_g     = curve( max( 1.0f - abs(( hsl.x - 0.333333f ) * 6.0f ), 0.0f ), curve_str );
    float weight_c     = curve( max( 1.0f - abs(( hsl.x - 0.5f      ) * 6.0f ), 0.0f ), curve_str );
    float weight_b     = curve( max( 1.0f - abs(( hsl.x - 0.666667f ) * 6.0f ), 0.0f ), curve_str );
    float weight_m     = curve( max( 1.0f - abs(( hsl.x - 0.833333f ) * 6.0f ), 0.0f ), curve_str );

    float sat          = hsl.y * ( 1.0f - hsl.y ) + hsl.y;
    float ret          = hsl.z;
    ret                += ( hsl.z * ( weight_r * r ) * sat * lum );
    ret                += ( hsl.z * ( weight_y * y ) * sat * lum );
    ret                += ( hsl.z * ( weight_g * g ) * sat * lum );
    ret                += ( hsl.z * ( weight_c * c ) * sat * lum );
    ret                += ( hsl.z * ( weight_b * b ) * sat * lum );
    ret                += ( hsl.z * ( weight_m * m ) * sat * lum );

    return saturate( ret );
}

float3 BlackandWhite(float3 color)
{
    color.xyz         = saturate( color.xyz );
        
    float red;  float yellow; float green;
    float cyan; float blue;   float magenta;

    red      = -0.400000;
    yellow   = 2.000001;
    green    = 3.000000;
    cyan     = 0.000000;
    blue     = -0.600000;
    magenta  = -0.200000;

    color.xyz         = ProcessBW( color.xyz, red, yellow, green, cyan, blue, magenta );

    return color;
}
	
/////////////////////////////////////////////////////////////////////////////////////

float3 LevelsPass(float3 color)
{
	const float3 InputColor = color;
	float3 OutputColor = InputColor;
	
	float3 InputBlackPoint=float3(0.000000,0.000000,0.000000);
	float3 InputWhitePoint=float3(1.000000,1.000000,1.000000);
	float3 OutputBlackPoint=float3(0.000000,0.000000,0.000000);
	float3 OutputWhitePoint=float3(1.000000,1.000000,1.000000);
	float3 InputGamma=float3(3.920000,1.000000,7.467000);
	OutputColor = pow(abs(((InputColor) - InputBlackPoint)/(InputWhitePoint - InputBlackPoint)), InputGamma) * (OutputWhitePoint - OutputBlackPoint) + OutputBlackPoint;
	  
	return OutputColor;
}
	
//////////////////////////////////////////////////////////////////////////////////////

#define permONE     1.0f / 256.0f
#define permHALF    0.5f * permONE
#define permTexSize 256

float discreteNoise(float rand) {
    return float(int(rand * 3.0) * 64) / 256.0;
}

float4 rnm( float2 tc, float t ) 
{
	float grainAdjust=1.000000;
		
    float noise       = sin( dot( tc, float2( 12.9898, 78.233 ))) * ( 43758.5453 + t );
    float noiseR      = frac( noise * grainAdjust ) * 2.0 - 1.0;
    float noiseG      = frac( noise * 1.2154 * grainAdjust ) * 2.0 - 1.0; 
    float noiseB      = frac( noise * 1.3453 * grainAdjust ) * 2.0 - 1.0;
    float noiseA      = frac( noise * 1.3647 * grainAdjust ) * 2.0 - 1.0;
    return float4( noiseR, noiseG, noiseB, noiseA );
}
	
float4 samplerPermTex(float2 uv)
{
	float4 gen = rnm(uv, 13.14381);
	gen.x = discreteNoise(gen.x);
	gen.y = discreteNoise(gen.y);
	gen.z = discreteNoise(gen.z);
	return gen;
}

float fade( float t )
{
    return t * t * t * ( t * ( t * 6.0 - 15.0 ) + 10.0 );
}

float pnoise3D( float3 p, float t, float grainSize )
{   		
	float3 pi         = permONE * floor( p ) + permHALF;
	pi.xy             *= permTexSize;
	pi.xy             = round(( pi.xy - permHALF ) / grainSize ) * grainSize;
	pi.xy             /= permTexSize;
	float3 pf         = frac( p );
	// Noise contributions from (x=0, y=0), z=0 and z=1
	float perm00      = rnm( pi.xy, t ).x;
	float3 grad000    = samplerPermTex(float2( perm00, pi.z )).xyz * 4.0 - 1.0;
	float n000        = dot( grad000, pf );
	float3 grad001    = samplerPermTex(float2( perm00, pi.z + permONE )).xyz * 4.0 - 1.0;
	float n001        = dot( grad001, pf - float3( 0.0, 0.0, 1.0 ));
	// Noise contributions from (x=0, y=1), z=0 and z=1
	float perm01      = rnm( pi.xy + float2( 0.0, permONE ), t ).y ;
	float3  grad010   = samplerPermTex(float2( perm01, pi.z )).xyz * 4.0 - 1.0;
	float n010        = dot( grad010, pf - float3( 0.0, 1.0, 0.0 ));
	float3  grad011   = samplerPermTex(float2( perm01, pi.z + permONE )).xyz * 4.0 - 1.0;
	float n011        = dot( grad011, pf - float3( 0.0, 1.0, 1.0 ));
	// Noise contributions from (x=1, y=0), z=0 and z=1
	float perm10      = rnm( pi.xy + float2( permONE, 0.0 ), t ).z ;
	float3  grad100   = samplerPermTex(float2( perm10, pi.z )).xyz * 4.0 - 1.0;
	float n100        = dot( grad100, pf - float3( 1.0, 0.0, 0.0 ));
	float3  grad101   = samplerPermTex(float2( perm10, pi.z + permONE )).xyz * 4.0 - 1.0;
	float n101        = dot( grad101, pf - float3( 1.0, 0.0, 1.0 ));
	// Noise contributions from (x=1, y=1), z=0 and z=1
	float perm11      = rnm( pi.xy + float2( permONE, permONE ), t ).w ;
	float3  grad110   = samplerPermTex(float2( perm11, pi.z )).xyz * 4.0 - 1.0;
	float n110        = dot( grad110, pf - float3( 1.0, 1.0, 0.0 ));
	float3  grad111   = samplerPermTex(float2( perm11, pi.z + permONE )).xyz * 4.0 - 1.0;
	float n111        = dot( grad111, pf - float3( 1.0, 1.0, 1.0 ));
	// Blend contributions along x
	float4 n_x        = lerp( float4( n000, n001, n010, n011 ), float4( n100, n101, n110, n111 ), fade( pf.x ));
	// Blend contributions along y
	float2 n_xy       = lerp( n_x.xy, n_x.zw, fade( pf.y ));
	// Blend contributions along z
	float n_xyz       = lerp( n_xy.x, n_xy.y, fade( pf.z ));
	// We're done, return the final noise value
	return n_xyz;
}

float3 doGrain(float3 color, float2 texcoord,
	float grainSize,
	float grainAmount,
	float grainIntensity,
	float grainColor,
	float grainIntHigh,
	float grainIntLow,
	float grainDensity)
{
    float timer         = timers.x % 1000.0f;
    float2 uv         = texcoord.xy * float2( screen_res.x, screen_res.y );
    float3 noise      = pnoise3D( float3( uv.xy, 1 ), timer, grainSize );
    noise.y           = pnoise3D( float3( uv.xy, 2 ), timer, grainSize );
    noise.z           = pnoise3D( float3( uv.xy, 3 ), timer, grainSize );		
		
    // Old, practically does the same as grainAmount below
    // Added back on request
    noise.xyz         *= grainIntensity;

	// Noise saturation
    noise.xyz         = lerp( dot( noise.xyz, 1.0f ), noise.xyz, grainColor );
		
	// Control noise density
    noise.xyz         = pow( abs( noise.xyz ), max( 11.0f - grainDensity, 0.1f )) * sign( noise.xyz );

    // Mixing options
    float lum         = dot( color.xyz, 0.333333f ); // Just using average here
    noise.xyz         = lerp( noise.xyz * grainIntLow, noise.xyz * grainIntHigh, fade( lum )); // Noise adjustments based on average intensity
    color.xyz         = lerp( color.xyz, color.xyz + ( noise.xyz ), grainAmount );
    return float3( color.xyz );
}
	
float3 Grain2(float3 color, float2 texcoord)
{
	float grainSize = 2.0;
	float grainAmount=0.023000 * 0.00004;
	float grainIntensity=1.000000;
	float grainColor=0.000000;
	float grainIntHigh=0.000000;
	float grainIntLow=1.000000;
	float grainDensity=0.000000;
		
	return doGrain(color, texcoord,
		grainSize,
		grainAmount,
		grainIntensity,
		grainColor,
		grainIntHigh,
		grainIntLow,
		grainDensity);
}

//////////////////////////////////////////////////////////////////////////////////////

float3 Grain1(float3 color, float2 texcoord)
{
	float grainSize=1.0;
	float grainAmount=0.500000;
	float grainIntensity=0.300000;
	float grainColor=0.000000;
	float grainIntHigh=0.000000;
	float grainIntLow=0.500000;
	float grainDensity=10.000000;
		
	return doGrain(color, texcoord,
		grainSize,
		grainAmount,
		grainIntensity,
		grainColor,
		grainIntHigh,
		grainIntLow,
		grainDensity);
}