logo Sign In

Curves (based on G'MIC) and HSV dither hlsl

Author
Time
 (Edited)

Since I thought that the Curves function in G’MIC (GIMP) was very good for colour mapping, I wanted to use it for video in some way. So I created this code that lets you remap RGB and HSV values. It just does straight line interpolation, not curves.

I also have a dithering shader that allows you to increase the mean or standard deviation of the image by adding (pseudo-)random amounts according to a uniform distribution (almost), I suggest using this to deblock or deband. You can also use my crushing filter here to clean up the image afterwards.

sampler s0 : register(s0);
float4 p0 :  register(c0);
float4 p1 :  register(c1);

#define width   (p0[0])
#define height  (p0[1])
#define counter (p0[2])
#define clock   (p0[3])
#define one_over_width  (p1[0])
#define one_over_height (p1[1])


float3 rgb2hsv(float3 c)
{
    float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
    float4 p = lerp(float4(c.bg, K.wz), float4(c.gb, K.xy), step(c.b, c.g));
    float4 q = lerp(float4(p.xyw, c.r), float4(c.r, p.yzx), step(p.x, c.r));
 
    float d = q.x - min(q.w, q.y);
    float e = 1.0e-10;
    return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}


float3 hsv2rgb(float3 c)
{
    float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    float3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * lerp(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
//Source: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl

//Bounds for interpolation//
static float r_x_b[2]={0,1};static float r_y_b[2]={0,1};static float g_x_b[2]={0,1};static float g_y_b[2]={0,1};static float b_x_b[2]={0,1};static float b_y_b[2]={0,1};static float h_x_b[2]={0,1};static float h_y_b[2]={0,1};static float s_x_b[2]={0,1};static float s_y_b[2]={0,1};static float v_x_b[2]={0,1};static float v_y_b[2]={0,1};

#define red_on 0
#define red_points 3

static float2 r[red_points] = {
0, 0,    // row 1; x,y
128,128,
255,255
};

#define green_on 0
#define green_points 3

static float2 g[green_points] = {
0, 0,    // row 1; x,y
128,128,
255,255
};

#define blue_on 0
#define blue_points 3

static float2 b[blue_points] = {
0, 0,    // row 1; x,y
128,128,
255,255
};


static float rotate_hues = 0; //-360 to 360

#define hue_on 1
#define hue_points 7

static float2 h[hue_points] = {
0, 0,    // row 1; x,y
10,10,
120,128,
185,183,
294,302,
302,310,
360,360
};

#define sat_on 1
#define sat_range 100
#define sat_points 3

static float2 s[sat_points] = {
0, 0,    // row 1; x,y
69,80,
100,100
};

#define val_on 1
#define val_range 100
#define val_points 4

static float2 v[val_points]= {
0, 0,    // row 1; x,y
35,34,
77,84,
100,100
};

#define split 1
#define flip_split 0
#define split_position  0.5

#define noWorseWB 0

float red_map(float red){int i=0;int exact=0;[unroll(red_points)]for(i=0;i<red_points;i++){[branch]if(r[i][0]/255==red) {red=r[i][1]/255;exact=1;i=red_points-1;}else{if(r[i][0]/255<red&&r[i][0]/255>=r_x_b[0]){r_x_b[0]=r[i][0]/255;r_y_b[0]=r[i][1]/255;} if(r[i][0]/255<=r_x_b[1]&&red<r[i][0]/255){r_x_b[1]=r[i][0]/255;r_y_b[1]=r[i][1]/255;}}} if(exact==0){red=r_y_b[0]+(red-r_x_b[0])*((r_y_b[1]-r_y_b[0])/(r_x_b[1]-r_x_b[0]));}return red;} 

float green_map(float green){int i=0;int exact=0;[unroll(green_points)]for(i=0;i<green_points;i++){[branch]if(g[i][0]/255==green) {green=g[i][1]/255;exact=1;i=green_points-1;}else{if(g[i][0]/255<green&&g[i][0]/255>=g_x_b[0]){g_x_b[0]=g[i][0]/255;g_y_b[0]=g[i][1]/255;} if(g[i][0]/255<=g_x_b[1]&&green<g[i][0]/255){g_x_b[1]=g[i][0]/255;g_y_b[1]=g[i][1]/255;}}} if(exact==0){green=g_y_b[0]+(green-g_x_b[0])*((g_y_b[1]-g_y_b[0])/(g_x_b[1]-g_x_b[0]));}return green;} 

float blue_map(float blue){int i=0;int exact=0;[unroll(blue_points)]for(i=0;i<blue_points;i++){[branch]if(b[i][0]/255==blue) {blue=b[i][1]/255;exact=1;i=blue_points-1;}else{if(b[i][0]/255<blue&&b[i][0]/255>=b_x_b[0]){b_x_b[0]=b[i][0]/255;b_y_b[0]=b[i][1]/255;} if(b[i][0]/255<=b_x_b[1]&&blue<b[i][0]/255){b_x_b[1]=b[i][0]/255;b_y_b[1]=b[i][1]/255;}}} if(exact==0){blue=b_y_b[0]+(blue-b_x_b[0])*((b_y_b[1]-b_y_b[0])/(b_x_b[1]-b_x_b[0]));}return blue;} 

float hue_rotate(float hue, float deg){deg*=pow(360,-1); float r=hue+deg; float r1=(r<=1)?r:r-1; hue=(r<0)?1+r:r1; return hue;}

float hue_map(float hue){int i=0;int exact=0;[unroll(hue_points)]for(i=0;i<hue_points;i++){[branch]if(h[i][0]/360==hue) {hue=h[i][1]/360;exact=1;i=hue_points-1;}else{if(h[i][0]/360<hue&&h[i][0]/360>=h_x_b[0]){h_x_b[0]=h[i][0]/360;h_y_b[0]=h[i][1]/360;} if(h[i][0]/360<=h_x_b[1]&&hue<h[i][0]/360){h_x_b[1]=h[i][0]/360;h_y_b[1]=h[i][1]/360;}}} if(exact==0){hue=h_y_b[0]+(hue-h_x_b[0])*((h_y_b[1]-h_y_b[0])/(h_x_b[1]-h_x_b[0]));}return hue;} 

float sat_map(float sat){int i=0;int exact=0;[unroll(sat_points)]for(i=0;i<sat_points;i++){[branch]if(s[i][0]/sat_range==sat) {sat=s[i][1]/sat_range;exact=1;i=sat_points-1;}else{if(s[i][0]/sat_range<sat&&s[i][0]/sat_range>=s_x_b[0]){s_x_b[0]=s[i][0]/sat_range;s_y_b[0]=s[i][1]/sat_range;} if(s[i][0]/sat_range<=s_x_b[1]&&sat<s[i][0]/sat_range){s_x_b[1]=s[i][0]/sat_range;s_y_b[1]=s[i][1]/sat_range;}}} if(exact==0){sat=s_y_b[0]+(sat-s_x_b[0])*((s_y_b[1]-s_y_b[0])/(s_x_b[1]-s_x_b[0]));}return sat;} 

float val_map(float val){int i=0;int exact=0;[unroll(val_points)]for(i=0;i<val_points;i++){[branch]if(v[i][0]/val_range==val) {val=v[i][1]/val_range;exact=1;i=val_points-1;}else{if(v[i][0]/val_range<val&&v[i][0]/val_range>=v_x_b[0]){v_x_b[0]=v[i][0]/val_range;v_y_b[0]=v[i][1]/val_range;} if(v[i][0]/val_range<=v_x_b[1]&&val<v[i][0]/val_range){v_x_b[1]=v[i][0]/val_range;v_y_b[1]=v[i][1]/val_range;}}} if(exact==0){val=v_y_b[0]+(val-v_x_b[0])*((v_y_b[1]-v_y_b[0])/(v_x_b[1]-v_x_b[0]));}return val;}

float4 remapper(float4 c0){
float3 orig=c0.rgb;
[branch]if(red_on==1){c0.r=red_map(c0.r);}		//red
[branch]if(green_on==1){c0.g=green_map(c0.g);}	//green
[branch]if(blue_on==1){c0.b=blue_map(c0.b);}	//blue

[branch]if (noWorseWB==1){

float3 origHSV=rgb2hsv(orig.rgb);
float3 newHSV=rgb2hsv(c0.rgb);
float origMin=min(orig.r,min(orig.g,orig.b));
float newMin=min(c0.r,min(c0.g,c0.b));

float ogChoice=origMin*(1-origHSV.y)+pow(1-origMin,2);
float nwChoice=newMin*(1-newHSV.y)+pow(1-newMin,2);

c0.rgb=(ogChoice>nwChoice)?orig.rgb:c0.rgb;
}

float3 colorHSV= rgb2hsv(c0.rgb);

[branch]if(rotate_hues!=0){
	colorHSV.x=hue_rotate(colorHSV.x,rotate_hues);
	c0.rgb=hsv2rgb(colorHSV); colorHSV= rgb2hsv(c0.rgb); //hue rotate
}

[branch]if(hue_on==1){
colorHSV.x=hue_map(colorHSV.x);
c0.rgb=hsv2rgb(colorHSV); 
colorHSV= rgb2hsv(c0.rgb); //hue
	}

[branch]if(sat_on==1){
colorHSV.y=sat_map(colorHSV.y); 
c0.rgb=hsv2rgb(colorHSV); 
colorHSV= rgb2hsv(c0.rgb); //sat
}

[branch]if(val_on==1){
colorHSV.z=val_map(colorHSV.z);
c0.rgb=hsv2rgb(colorHSV);
colorHSV= rgb2hsv(c0.rgb); //val
}

	return c0;


}


float4 main(float2 tex : TEXCOORD0) : COLOR {

float4 c0=tex2D(s0, tex);

float Split=split;
float Split_position=split_position;
float Flip_split=flip_split;

float4 c1=remapper(c0);

float4 c2=(tex.x>=Split_position*Split)?c1:c0;
float4 c3=(tex.x<=Split_position*Split)?c1:c0;

float4 c4=(Flip_split*Split==1)?c3:c2;

float divLine = abs(tex.x - Split_position) < one_over_width;
c4 =(Split==0)?c4: c4*(1.0 - divLine); //invert divline

return c4;
}

Here’s the grey dither:

sampler s0 : register(s0);
float4 p0 :  register(c0);
float4 p1 :  register(c1);

#define width   (p0[0])
#define height  (p0[1])
#define counter (p0[2])
#define clock   (p0[3])
#define one_over_width  (p1[0])
#define one_over_height (p1[1])

static float d_x_b[2]={0,1};static float d_y_b[2]={0,1};
 
#define dither_points 7

static float2 d[dither_points] = {
0,0,
1,0,
64.90915,63.75,
131.4898,127.5,
194.1036,191.25,
254,255,
255,255
};

float dither_map(float dither){int i=0;int exact=0;[unroll(dither_points)]for(i=0;i<dither_points;i++){[branch]if(d[i][0]/255==dither) {dither=d[i][1]/255;exact=1;i=dither_points-1;}else{if(d[i][0]/255<dither&&d[i][0]/255>=d_x_b[0]){d_x_b[0]=d[i][0]/255;d_y_b[0]=d[i][1]/255;} if(d[i][0]/255<=d_x_b[1]&&dither<d[i][0]/255){d_x_b[1]=d[i][0]/255;d_y_b[1]=d[i][1]/255;}}} if(exact==0){dither=d_y_b[0]+(dither-d_x_b[0])*((d_y_b[1]-d_y_b[0])/(d_x_b[1]-d_x_b[0]));}return dither;} 


float random( float2 p )
{
// We need irrationals for pseudo randomness.
// Most (all?) known transcendental numbers will (generally) work.
const float2 r = float2(
23.1406926327792690,  // e^pi (Gelfond's constant)
 2.6651441426902251); // 2^sqrt(2) (Gelfond-Schneider constant)

float t=frac(acos(p.x/p.y)+sin(p.x)*r.y+cos(p.y)*r.x+p.x*p.y*r.y);

t= frac((800*cos(t/20)+1400)*t);  
t= frac(pow( frac((0.01*t+sin(500*t*t))+tan(t*500)*500),2));

float rMap =3.98;
float tOld=t;
int i=0;

[unroll(100)]for (i=0;i<100;i++){
tOld=rMap*tOld*(1-tOld);
}
 
 float w = frac(10000*tOld+0.597*tOld);


return dither_map(w);

}

float grey_dither(float color,float2 tex,float rnd,float sdv, float gamma){

float rand=random(float2((tex.x+width*one_over_height)*color,(tex.y+height*one_over_width)*color));
float randm=rnd*-1*((rand*-4)+1); // averages to color + rnd

color=(rnd!=0)?color+(randm/255):color;

float sAB=sdv*sqrt(12)*0.5;
randm=sAB*(2*rand-1)*-1;
color=(sdv!=0)?color+(randm/255):color;

float colorSc=color*2;
randm=(colorSc<0.5)?(pow(abs(0.5*colorSc),gamma)-color)*-1*((rand*-4)+1):((1-(0.5*pow(abs(2-colorSc),gamma)))-color)*-1*((rand*-4)+1);

color=(gamma!=1)?color+randm:color;

return color;

}

#define greyDither_Amnt 0

#define greyDither_Sdv 0

#define greyDither_Scurve 1

#define dark_Dither 1 //0 or 1

#define dark_Dither_Pwr 0 //0-1



#define split 0

#define flip_split 0

#define split_position 0.5


float4 main(float2 tex : TEXCOORD0) : COLOR 
{ 
	float4 c0 = tex2D(s0, tex);
	float c0Max=max(c0.r,max(c0.g,c0.b));
	float4 c1 = c0;
	float Split=split;
	float Split_position=split_position;
	float Flip_split=flip_split;
	float greyDitherScurve=greyDither_Scurve;
	float greyDitherSdv=greyDither_Sdv;
	float greyDitherAmnt=greyDither_Amnt;
	float Dark_dither=dark_Dither;
	float Dark_dither_pwr=dark_Dither_Pwr;

c1.rgb =saturate(grey_dither(c0Max,tex,greyDitherAmnt,greyDitherSdv,greyDitherScurve)*(c1.rgb/c0Max));

	float c1Max=max(c1.r,max(c1.g,c1.b));

c1.rgb=(Dark_dither==1)?lerp(c1.rgb,c0.rgb,pow(c1Max,1-Dark_dither_pwr)):c1.rgb;

float4 c2=(tex.x>=Split_position*Split)?c1:c0;
float4 c3=(tex.x<=Split_position*Split)?c1:c0;

float4 c4=(Flip_split*Split==1)?c3:c2;

float divLine = abs(tex.x - Split_position) < one_over_width;
c4 =(Split==0)?c4: c4*(1.0 - divLine); //invert divline

return c4;

}