#version 330 core

/*
** Gamma correction
** Details: http://blog.mouaif.org/2009/01/22/photoshop-gamma-correction-shader/
*/
#define GammaCorrection(color, gamma) pow( color, 1.0 / vec3(gamma))

/*
** Levels control (input (+gamma), output)
** Details: http://blog.mouaif.org/2009/01/28/levels-control-shader/
*/
#define LevelsControlInputRange(color, minInput, maxInput)  min(max(color - vec3(minInput), 0.0) / (vec3(maxInput) - vec3(minInput)), 1.0)
#define LevelsControlInput(color, minInput, gamma, maxInput) GammaCorrection(LevelsControlInputRange(color, minInput, maxInput), gamma)
#define LevelsControlOutputRange(color, minOutput, maxOutput)  mix(vec3(minOutput), vec3(maxOutput), color)
#define LevelsControl(color, minInput, gamma, maxInput, minOutput, maxOutput)   LevelsControlOutputRange(LevelsControlInput(color, minInput, gamma, maxInput), minOutput, maxOutput)

#define ONETHIRD 0.333333
#define TWOTHIRD 0.666666
#define EPSILON  0.000001

out vec4 FragColor;

// from vertex shader (interpolated)
in vec4 vertexColor;
in vec2 vertexUV;

vec4 texcoord;

// image processing specific
uniform sampler2D iChannel0;             // input channel (texture id).
uniform vec3      iResolution;           // viewport resolution (in pixels)
uniform mat4      iTransform;            // UV image transformation

// Image shader uniforms
uniform vec4 color;
uniform vec4 uv;

// Image processing uniforms
uniform float contrast;
uniform float brightness;
uniform float saturation;
uniform vec4  gamma;
uniform vec4  levels;
uniform float hueshift;
uniform vec4  chromakey;
uniform float chromadelta;
uniform float threshold;
uniform float lumakey;
uniform int   nbColors;
uniform int   invert;

// conversion between rgb and YUV
const mat4 RGBtoYUV = mat4(0.257,  0.439, -0.148, 0.0,
                           0.504, -0.368, -0.291, 0.0,
                           0.098, -0.071,  0.439, 0.0,
                           0.0625, 0.500,  0.500, 1.0 );

/*
** Hue, saturation, luminance <=> Red Green Blue
*/

float HueToRGB(float f1, float f2, float hue)
{
    float res;

    hue += mix( -float( hue > 1.0 ), 1.0, float(hue < 0.0) );

    res = mix( f1, mix( clamp( f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0, 0.0, 1.0) , mix( f2,  clamp(f1 + (f2 - f1) * 6.0 * hue, 0.0, 1.0), float((6.0 * hue) < 1.0)),  float((2.0 * hue) < 1.0)), float((3.0 * hue) < 2.0) );

    return res;
}

vec3 HSV2RGB(vec3 hsl)
{
    vec3 rgb;
    float f1, f2;

    f2 = mix( (hsl.z + hsl.y) - (hsl.y * hsl.z), hsl.z * (1.0 + hsl.y), float(hsl.z < 0.5) );

    f1 = 2.0 * hsl.z - f2;

    rgb.r = HueToRGB(f1, f2, hsl.x + ONETHIRD);
    rgb.g = HueToRGB(f1, f2, hsl.x);
    rgb.b = HueToRGB(f1, f2, hsl.x - ONETHIRD);

    rgb =  mix( rgb, vec3(hsl.z), float(hsl.y < EPSILON));

    return rgb;
}

vec3 RGB2HSV( vec3 color )
{
    vec3 hsl = vec3(0.0); // init to 0

    float fmin = min(min(color.r, color.g), color.b);    //Min. value of RGB
    float fmax = max(max(color.r, color.g), color.b);    //Max. value of RGB
    float delta = fmax - fmin + EPSILON;             //Delta RGB value

    vec3 deltaRGB = ( ( vec3(fmax) - color ) / 6.0  + vec3(delta) / 2.0 ) / delta ;

    hsl.z = (fmax + fmin) / 2.0; // Luminance

    hsl.y = delta / ( EPSILON + mix( 2.0 - fmax - fmin, fmax + fmin, float(hsl.z < 0.5)) );

    hsl.x = mix( hsl.x, TWOTHIRD + deltaRGB.g - deltaRGB.r, float(color.b == fmax));
    hsl.x = mix( hsl.x, ONETHIRD + deltaRGB.r - deltaRGB.b, float(color.g == fmax));
    hsl.x = mix( hsl.x, deltaRGB.b - deltaRGB.g,  float(color.r == fmax));

    hsl.x += mix( - float( hsl.x > 1.0 ), 1.0, float(hsl.x < 0.0) );

    hsl = mix ( hsl, vec3(-1.0, 0.0, hsl.z), float(delta<EPSILON) );

    return hsl;
}

float alphachromakey(vec3 color, vec3 colorKey, float delta)
{
   // magic values
   vec2 tol = vec2(0.5, 0.4*delta);

   //convert from RGB to YCvCr/YUV
   vec4 keyYuv = RGBtoYUV * vec4(colorKey, 1.0);
   vec4 yuv = RGBtoYUV * vec4(color, 1.0);

   // color distance in the UV (CbCr, PbPr) plane
   vec2 dif = yuv.yz - keyYuv.yz;
   float d = sqrt(dot(dif, dif));

   // tolerance clamping
   return max( (1.0 - step(d, tol.y)), step(tol.x, d) * (d - tol.x) / (tol.y - tol.x));
}


void main(void)
{
    // adjust UV
    texcoord = iTransform * vec4(vertexUV.x, vertexUV.y, 0.0, 1.0);

    vec4 texcolor = texture(iChannel0, texcoord.xy);

    // deal with alpha separately
    float alpha = clamp(texcolor.a * color.a, 0.0, 1.0);

    // read color
    vec3 transformedRGB = texcolor.rgb;

    // chromakey
    alpha -= mix( 0.0, 1.0 - alphachromakey( transformedRGB, chromakey.rgb, chromadelta), float(chromadelta > 0.0001) );

    // brightness and contrast transformation
    transformedRGB = mix(vec3(0.62), transformedRGB, contrast + 1.0) + brightness;

    // RGB invert
    transformedRGB = vec3(float(invert==1)) + ( transformedRGB * vec3(1.0 - 2.0 * float(invert==1)) );

    // Convert to HSL
    vec3 transformedHSL = RGB2HSV( transformedRGB );

    // Luminance invert
    transformedHSL.z = float(invert==2) +  transformedHSL.z * (1.0 - 2.0 * float(invert==2) );

    // perform hue shift
    transformedHSL.x = transformedHSL.x + hueshift;

    // Saturation
    transformedHSL.y *= saturation + 1.0;

    // perform reduction of colors
    transformedHSL = mix( transformedHSL, floor(transformedHSL * vec3(nbColors)) / vec3(nbColors-1),  float( nbColors > 0 ) );

    // luma key
    alpha -= mix( 0.0, step( transformedHSL.z, lumakey ), float(lumakey > EPSILON));
    alpha = clamp(alpha, 0.0, 1.0);

    // level threshold
    transformedHSL = mix( transformedHSL, vec3(0.0, 0.0, 0.95 - step( transformedHSL.z, threshold )), float(threshold > EPSILON));

    // after operations on HSL, convert back to RGB
    transformedRGB = HSV2RGB(transformedHSL);

    // apply gamma correction
    transformedRGB = LevelsControl(transformedRGB, levels.x, gamma.rgb * gamma.a, levels.y, levels.z, levels.w);

    // apply base color and alpha for final fragment color
    FragColor = vec4(transformedRGB * vertexColor.rgb * color.rgb * alpha, alpha);

}


