# Hue, saturation, brightness, contrast effect in hlsl

I am new to pixel shader, and I am trying to write a simple brightness, contrast, hue, saturation effect. I have written a shader for it but I doubt that my shader is not providing me correct result, Brightness, contrast, saturation is working fine, problem is with hue. if I apply hue between -1 to 1, it seems to be working fine, but to make things more sharp, I need to apply hue value between -180 and 180, like we can apply hue in Paint.NET.

Here is my code.

// Amount to shift the Hue, range 0 to 6
float Hue;
float Brightness;
float Contrast;
float Saturation;
float Alpha;

sampler Samp : register(S0);

// Converts the rgb value to hsv, where H's range is -1 to 5
float3 rgb_to_hsv(float3 RGB)
{
float r = RGB.x;
float g = RGB.y;
float b = RGB.z;

float minChannel = min(r, min(g, b));
float maxChannel = max(r, max(g, b));

float h = 0;
float s = 0;
float v = maxChannel;

float delta = maxChannel - minChannel;

if (delta != 0)
{
s = delta / v;

if (r == v) h = (g - b) / delta;
else if (g == v) h = 2 + (b - r) / delta;
else if (b == v) h = 4 + (r - g) / delta;
}

return float3(h, s, v);
}

float3 hsv_to_rgb(float3 HSV)
{
float3 RGB = HSV.z;

float h = HSV.x;
float s = HSV.y;
float v = HSV.z;

float i = floor(h);
float f = h - i;

float p = (1.0 - s);
float q = (1.0 - s * f);
float t = (1.0 - s * (1 - f));

if (i == 0) { RGB = float3(1, t, p); }
else if (i == 1) { RGB = float3(q, 1, p); }
else if (i == 2) { RGB = float3(p, 1, t); }
else if (i == 3) { RGB = float3(p, q, 1); }
else if (i == 4) { RGB = float3(t, p, 1); }
else /* i == -1 */ { RGB = float3(1, p, q); }

RGB *= v;

return RGB;
}

float4 mainPS(float2 uv : TEXCOORD) : COLOR
{
float4 col = tex2D(Samp, uv);

float3 hsv = rgb_to_hsv(col.xyz);

hsv.x += Hue;
// Put the hue back to the -1 to 5 range
//if (hsv.x > 5) { hsv.x -= 6.0; }
hsv = hsv_to_rgb(hsv);
float4 newColor = float4(hsv,col.w);

float4 colorWithBrightnessAndContrast = newColor;

colorWithBrightnessAndContrast.rgb /= colorWithBrightnessAndContrast.a;
colorWithBrightnessAndContrast.rgb = colorWithBrightnessAndContrast.rgb + Brightness;
colorWithBrightnessAndContrast.rgb = ((colorWithBrightnessAndContrast.rgb - 0.5f) * max(Contrast + 1.0, 0)) + 0.5f;
colorWithBrightnessAndContrast.rgb *= colorWithBrightnessAndContrast.a;

float greyscale = dot(colorWithBrightnessAndContrast.rgb, float3(0.3, 0.59, 0.11));
colorWithBrightnessAndContrast.rgb = lerp(greyscale, colorWithBrightnessAndContrast.rgb, col.a * (Saturation + 1.0));
return colorWithBrightnessAndContrast;
}

technique TransformTexture {
pass p0 {
}
}


Please If anyone can help me learning what am I doing wrong or any suggestions? Any help will be of great value.

EDIT: Images of the effect at hue 180:

On the left hand side, the effect I got with @teodron answer. On the right hand side, The effect Paint.NET gives and I'm trying to reproduce.

• Please upload some example images. – kaoD May 8 '12 at 12:02
• With the sample image I can still say you're wishing to shift the hue. @teodron answer is not wrong after all. It just dont shift as you expected. I didn't test, but you could try just adding or subtracting hue then normalizing the value(as it is a rotational spectrum...) – Gustavo Maciel May 8 '12 at 13:36
• Assume you have the pixel hue float h in a value between 0 and 359. and you want to shift the hue +180. Just make h += 180, If the value is >= 360, just do h -= 360. If the value is < 0, just do h += 360. Should work or not. I didn't test, but it is what I came in mind. A simple search show's that a Hue value is represented as degrees, so it should repeat at every 360 values. If your Hue value is given as [-1, +1], [0, +1] it should be no different. You can just map the values to [0, 359] or work with them in their current form. – Gustavo Maciel May 8 '12 at 13:51

UPDATE Basic maths: if your input is a variable called X and lies in the [a,b] interval, but you'd like to have it translated to the [c,d] interval, the common sense way of doing that is via a linear/affine transformation (affine operators are functions of the form AoX+B where o is a multiplication like operation - A and X could be matrices or other multidimensional elements).

For a scalar x in [a,b] we have the following transform:

 x <- ((x-a)/(b-a))*(d-c) + c


(x-a)/(b-a) takes x from [a,b] into the [0,1] range, *(d-c) scales it to the [0,d-c] interval and adding the c moves it to the [c,d] desired range. Hope my maths are right now..

If you want to go between -180 and 180 degs, you need to linearly shift the hue.. something like this should work with hues in the [-180, 180] interval:

float3 hsv_to_rgb(float3 HSV)
{
float3 RGB = HSV.z;

float h = (HSV.x+180)/60.0; //get a hue in the 0..5 interval from the -180 180 intv
float s = HSV.y;
float v = HSV.z;

float i = floor(h);
float f = h - i;

float p = (1.0 - s);
float q = (1.0 - s * f);
float t = (1.0 - s * (1 - f));

if (i == 0) { RGB = float3(1, t, p); }
else if (i == 1) { RGB = float3(q, 1, p); }
else if (i == 2) { RGB = float3(p, 1, t); }
else if (i == 3) { RGB = float3(p, q, 1); }
else if (i == 4) { RGB = float3(t, p, 1); }
else /* i == -1 */ { RGB = float3(1, p, q); }

RGB *= v;

return RGB;
}

• I might have misunderstood you, but ( (-1,1)+180 )/60 = (179,181)/60 = (2.983, 3.016) Besides, he has no problem mapping (-1,1) to (-180,180), but his mapping is (somehow) not what he expects. Lets wait for example images. – kaoD May 8 '12 at 12:40
• Here is sample image skydrive.live.com/…. The left image is generated by sample provided by teodron and the right one I got from paint.NET, both are using hue value 180, but results are different? That's the problem with my code? – Vibhore Tanwer May 8 '12 at 13:05
• Come on, the answer doesnt deserves a -1. It isn't the desired effect but still a good answer for referring and has valuable info. – Gustavo Maciel May 8 '12 at 13:37
• @VibhoreTanwer Post it as an answer, So it's easier to people with the same question. – Gustavo Maciel May 10 '12 at 13:36
Finally I figured it out.

// Amount to shift the Hue, range 0 to 6
float Hue;
float Brightness;
float Contrast;
float Saturation;

sampler Samp : register(S0);
float3x3 QuaternionToMatrix(float4 quat)
{
float3 cross = quat.yzx * quat.zxy;
float3 square= quat.xyz * quat.xyz;
float3 wimag = quat.w * quat.xyz;

square = square.xyz + square.yzx;

float3 diag = 0.5 - square;
float3 a = (cross + wimag);
float3 b = (cross - wimag);

return float3x3(
2.0 * float3(diag.x, b.z, a.y),
2.0 * float3(a.z, diag.y, b.x),
2.0 * float3(b.y, a.x, diag.z));
}

const float3 lumCoeff = float3(0.2125, 0.7154, 0.0721);

float4 mainPS(float2 uv : TEXCOORD) : COLOR
{
float4 outputColor = tex2D(Samp, uv);
float3 hsv;
float3 intensity;
float3 root3 = float3(0.57735, 0.57735, 0.57735);
float half_angle = 0.5 * radians(Hue); // Hue is radians of 0 tp 360 degree
float4 rot_quat = float4( (root3 * sin(half_angle)), cos(half_angle));
float3x3 rot_Matrix = QuaternionToMatrix(rot_quat);
outputColor.rgb = mul(rot_Matrix, outputColor.rgb);
outputColor.rgb = (outputColor.rgb - 0.5) *(Contrast + 1.0) + 0.5;
outputColor.rgb = outputColor.rgb + Brightness;
intensity = float(dot(outputColor.rgb, lumCoeff));
outputColor.rgb = lerp(intensity, outputColor.rgb, Saturation );

return outputColor;
}
technique TransformTexture {
pass p0 {
}
}


Sorry ahead of time for not posting any code, but your problem might be HSV itself.

Hue is represented as a circle, called a Color Wheel. In HSV, the color wheel is based on color frequencies, evenly divided out across the visual spectrum. In other words, The Sextants your solution assumes exist are based on evenly distributing the 0-359 degrees over this Color Wheel (which is what most Display Monitors and computers assume when it divides all the color channels into 256).

But, take a look at Paint.NET and various other applications, like Photoshop. They don't use a Color Wheel, but what's often called a Painter's Wheel. It's usually a modified Wheel that doesn't evenly distribute the color frequencies.

These differences are usually caused by Display Monitors that have brighter blues and greens than is actually typical of reality. In order to color balance an image like this, it needs to be corrected or the displayed results can't match up with a real world solution.

Simply put, the HSV sextant solution may be the culprit. Try modifying your program to display the full range of the color wheel you're using and compare that to Paint.NET. You really can't compare two images that are operating in a different color space.

(Again, this is just a guess, as I'm trying to solve a similar problem)

• Oh, this has already been answered. Nevermind. :P – Joshua Wall Jun 2 '12 at 1:34
• Joshua, It's OK to add multiple answers! In fact, it's encouraged. It's just more options for someone who's looking in the future. – MichaelHouse Jun 28 '12 at 17:24