Someone Said it Was Impossible: Hue Shift in Pixel Shader 2.0 (EasyPainter, Silverlight)

Feb 9, 2010

I read somewhere online that Hue changes can't be done in pixel shader 2.0, due to limitation of 64 instructions per slot.

Here's the sample that proves otherwise:

Download source code

Indeed the RGB-to-HSL-to-RGB conversion takes about 100 instructions in its typical implementation. PS 2.0 which is the shader model supported by Silverlight 3 only allows for 64 arithmetic instructions, as outlined in this comparison between pixel shaders on Wikipedia

How can we optimize it?

Optimizing pixel shader instruction slots is nice - in a typical C# world, you'd be adding if() statements to make your code run faster like this:

    if ( HSV.y != 0 )
       QUAD_REAL var_h = HSV.x * 6;
       QUAD_REAL var_i = floor(var_h);   // Or ... var_i = floor( var_h )
       QUAD_REAL var_1 = HSV.z * (1.0 - HSV.y);
       QUAD_REAL var_2 = HSV.z * (1.0 - HSV.y * (var_h-var_i));
       QUAD_REAL var_3 = HSV.z * (1.0 - HSV.y * (1-(var_h-var_i)));
       if      (var_i == 0) { RGB = QUAD_REAL3(HSV.z, var_3, var_1); }
       else if (var_i == 1) { RGB = QUAD_REAL3(var_2, HSV.z, var_1); }
       else if (var_i == 2) { RGB = QUAD_REAL3(var_1, HSV.z, var_3); }
       else if (var_i == 3) { RGB = QUAD_REAL3(var_1, var_2, HSV.z); }
       else if (var_i == 4) { RGB = QUAD_REAL3(var_3, var_1, HSV.z); }
       else                 { RGB = QUAD_REAL3(HSV.z, var_1, var_2); }
   }

Not with pixel shaders. If you look carefully at the bold if statement, removing it does not change the program logic. It just takes an extra instruction slot. In reality, I think the pixel shader code will run with the same speed with or without the if() (not 100% sure so correct me if needed).

With this knowledge, I decided to do these optimizations:

1. Instead of HSL-to-RGB, use HSV-to-RGB. The reference NVidia Shader Library implementation (source code here) of HSV-RGB-HSV takes ~70 or so slots.

2. Combine the min_channel() and max_channel() functions into 1 - saves a couple if() statements

3. Take out the if (x < 0) (x += 1) checks in the RGB-HSV function, and execute them once instead of twice, after the hue is modified.

4. Remove the "obsolete" if()-s like the one above

I was very happy to see that it just fit in the 64-instruction slot of PS 2.0! Note that it hits the limit and more complex Hue stuff may need further optimizations! :) If you do so, please let me know! Anyway hue tricks that don't use more slots are OK.

Here's the complete Shazzam-friendly source of the .fx file (also included in the sample project source above).

/// <summary>Hue shift</summary>
/// <minValue>0</minValue>
/// <maxValue>1</maxValue>
/// <defaultValue>0</defaultValue>
float HueShift : register(c0);
sampler2D Samp : register(S0);
#define QUAD_REAL float
#define QUAD_REAL3 float3
QUAD_REAL3 rgb_to_hsv_no_clip(QUAD_REAL3 RGB)
{
    QUAD_REAL3 HSV;
   
 float minChannel, maxChannel;
 if (RGB.x > RGB.y) {
  maxChannel = RGB.x;
  minChannel = RGB.y;
 }
 else {
  maxChannel = RGB.y;
  minChannel = RGB.x;
 }
 
 if (RGB.z > maxChannel) maxChannel = RGB.z;
 if (RGB.z < minChannel) minChannel = RGB.z;
   
    HSV.xy = 0;
    HSV.z = maxChannel;
    QUAD_REAL delta = maxChannel - minChannel;             //Delta RGB value
    if (delta != 0) {                    // If gray, leave H & S at zero
       HSV.y = delta / HSV.z;
       QUAD_REAL3 delRGB;
       delRGB = (HSV.zzz - RGB + 3*delta) / (6.0*delta);
       if      ( RGB.x == HSV.z ) HSV.x = delRGB.z - delRGB.y;
       else if ( RGB.y == HSV.z ) HSV.x = ( 1.0/3.0) + delRGB.x - delRGB.z;
       else if ( RGB.z == HSV.z ) HSV.x = ( 2.0/3.0) + delRGB.y - delRGB.x;
    }
    return (HSV);
}
QUAD_REAL3 hsv_to_rgb(QUAD_REAL3 HSV)
{
    QUAD_REAL3 RGB = HSV.z;
    //if ( HSV.y != 0 ) { // we don't really need this since it just adds an obsolete instruction slot
       QUAD_REAL var_h = HSV.x * 6;
       QUAD_REAL var_i = floor(var_h);   // Or ... var_i = floor( var_h )
       QUAD_REAL var_1 = HSV.z * (1.0 - HSV.y);
       QUAD_REAL var_2 = HSV.z * (1.0 - HSV.y * (var_h-var_i));
       QUAD_REAL var_3 = HSV.z * (1.0 - HSV.y * (1-(var_h-var_i)));
       if      (var_i == 0) { RGB = QUAD_REAL3(HSV.z, var_3, var_1); }
       else if (var_i == 1) { RGB = QUAD_REAL3(var_2, HSV.z, var_1); }
       else if (var_i == 2) { RGB = QUAD_REAL3(var_1, HSV.z, var_3); }
       else if (var_i == 3) { RGB = QUAD_REAL3(var_1, var_2, HSV.z); }
       else if (var_i == 4) { RGB = QUAD_REAL3(var_3, var_1, HSV.z); }
       else                 { RGB = QUAD_REAL3(HSV.z, var_1, var_2); }
   //}
   return (RGB);
}
float4 main(float2 uv : TEXCOORD) : COLOR
{
 float4 col = tex2D(Samp, uv);
 float3 hsv = rgb_to_hsv_no_clip(col.xyz);
    hsv.x+=HueShift;
    //if ( hsv.x < 0.0 ) { hsv.x += 1.0; }
    if ( hsv.x > 1.0 ) { hsv.x -= 1.0; }
    return float4(hsv_to_rgb(hsv),col.w);
}
 
btw, Visual Studio 2010 RC is out for MSDN subsribers (public tomorrow) and I'm going to publish all samples in VS 2010 from now on :)

Hope you like it!

      

Comments

2/11/2010 6:55:28 PM #

Cory Plotts

This is awesome!

Don't know if it was me (www.cplotts.com/.../) that you are thinking of (in relation to saying it was impossible in PS 2.0), but I'm stoked to see this. Can't wait to play with it.

Cory Plotts | Reply

2/11/2010 6:58:35 PM #

Cory Plotts

By the way, I tried to contact you via Twitter (I'm @cplotts) when you released EasyPainter ... but you may want to somehow incorporate my blend mode library.

The library has a full set of blend modes (for both WPF and Silverlight). The Silverlight version was missing the Hue, Saturation, Color, and Luminosity effects ... but I'm going to try your HLSL above out ... and if it works, will incorporate those as well into my blend mode library.

Again, very nice.

Cory Plotts | Reply

2/12/2010 7:16:24 AM #

nokola

Thanks Cory!
Yes it was youSmile I didn't want to point fingers but it found its way back Smile
Oh yes! Great job on the Blend Mode library! It's very nice you figured out that shaders also use premultiplied alpha! I didn't know - learned it from your blog just now.
The library looks very robust and solid. I know it takes (to me at least) a lot of work to do something like that, so thanks for making it!
What license is it under? Is it MS-PL or something else?

btw, if you're going to incorporate the HSL stuff into your library, my shader still needs some optimization. I think it's just at the limit of 64 instructions now..it would be great to see it go with even less! One idea that I haven't tried: maybe replacing the if()-s with arithmetic instructions to get the mix/max can save a little bit.
Again, thanks for making hte Blend mode library - it's great!

nokola | Reply

2/12/2010 5:40:36 PM #

Cory Plotts

Heh heh ... I didn't quite say it was impossible ... but it was definitely out of reach for me given my only basic knowledge of HLSL and time on hand. But, it is great that you proved me wrong and figured out a way. A great example of the wider community being beneficial to everyone involved.

The library did take longer than I care to admit. Smile But, I definitely learned a lot in the process.

I haven't licensed it yet, but I probably should. The intent would be Ms-PL, I think. I'm all for sharing ... but I should probably put some copyright info in it. In fact, given your question, I'm going to do just that.

I am definitely going to play with and try to incorporate your HSL stuff ... if you optimize it further ... please let me know.

I am glad you like the library, and would be thrilled if you incorporate it into EasyPainter. I think a lot of people would love that! There are some limitations with it, but it is pretty easy to blend two images of the same dimensions. I haven't yet figured out a way to do the BackgroundEffectBehavior for Silverlight (but then I really haven't put much time on it).

Cory Plotts | Reply

2/12/2010 6:02:02 PM #

nokola

Yes, I agree - it was like in the "broken telephone" game, when people pass stuff they heard around Smile

btw, there is a hue/sat color transform with even less instructions. Not sure how to apply it to blending library yet: it doesn't convert the colors back and forth but transforms them directly. If it works I'll post it online

I'll definitely need something like your library for EasyPainter. Have to figure out a user-friendly way to integrate it though. For example, I want to figure out more user-friendly names for the blending modes (even though they are the same as in photoshop). The names should relate better to what the blend mode does. Similarly to how the "motion blur" was renamed to "Fast Motion" and "Edge Detect" was modified a bit and named "Hollow Outline". Also I'm wondering if adding layer support will be too much for "Easy". Any feedback is welcome! Smile

nokola | Reply

2/16/2010 1:14:52 AM #

Cory Plotts

Hey, just tried your HLSL for the HueEffect ... I'm still over the boundary when I include the HLSL for opacity. I'm at 70 arithmetic instructions versus the 64 max for PS 2.0.

If I delete the code for opacity, it all compiles and is fine.

Any thoughts on how to optimize this further? I don't quite understand your suggestions on replacing the if(s).

Following is the main method for the HueEffect so you can see exactly what I'm talking about:

float4 main(float2 uv : TEXCOORD) : COLOR
{
  float4 inputColor;
  inputColor = tex2D(input, uv);

  float4 blendColor;
  blendColor = tex2D(blend, uv);


  float4 resultColor;
  resultColor.a = inputColor.a;

//  blendColor.rgb = clamp(blendColor.rgb / blendColor.a, 0, 1);


  resultColor.rgb = BlendHue(inputColor.rgb, blendColor.rgb);


//  resultColor.rgb = resultColor.rgb * blendColor.a;
//  resultColor.rgb = (1 - blendColor.a) * inputColor.rgb + resultColor.rgb;

  return resultColor;
}

Cory Plotts | Reply

2/16/2010 6:59:27 PM #

nokola

First, sorry for the vagueness:

I remember there was a way to replace an if() with a series of arithmetic instructions, but on a second thought, it will not help with the instruction count probably.

You could try OJ's suggestion (see below in the comments) and use min() max()
Or, we could store the premultiplication info in a 1x256 texture and do a texture lookup instead of few arithmetic instructions. I'm working on something like that (using texture lookup to replace calculations) - will publish it when done...

One other though - maybe there's an entirely different way to do it (e.g. using matrices as Boris did to shift the hue here: www.theVisualDeveloper.com). Not sure, but some matrix "stuff" Smile might be usable for doing hue blending without having to go from RGB to HSV and back.

nokola | Reply

2/17/2010 3:28:10 PM #

Cory Plotts

Thanks for the thoughts ... I'll keep working on it and let you know if I have any success.

Cory Plotts United States | Reply

2/17/2010 8:58:46 AM #

Nokola

does this help? (not sure how many instructions will be saved, and if it will be 100% correct...)
float4 main(float2 uv : TEXCOORD) : COLOR
{
  float4 inputColor;
  inputColor = tex2D(input, uv);

  float4 blendColor;
  blendColor = tex2D(blend, uv);
  float a = blendColor.a;

  float4 resultColor;
  resultColor.a = inputColor.a;

  blendColor = blendColor / blendColor.a;

  resultColor.rgb = BlendHue(inputColor.rgb, blendColor.rgb);

  resultColor.rgb = resultColor.rgb * a;
  resultColor.rgb = (1 - a) * inputColor.rgb + resultColor.rgb;


  return resultColor;
}

Nokola United States | Reply

2/17/2010 3:35:56 PM #

Cory Plotts

Heh. Sorry for the confusing question ... I showed you the above code ... only so you could see exactly lines of code I needed to comment out, in order to get it compiling to PS 2.0 ... namely, the lines of code needed for handling opacity on the upper layer.

Didn't mean to ask you to work on my code! However, I will take a look at your changes and see if they help. Smile

I was able to get Hue (with opacity) to work by commenting out one of the if checks in the RGB->HSV->RGB code ... however, this didn't work for Saturation. This kind of stumped me (it was 68 versus the limit of 64). I figured that if Hue worked, then Saturation would too, but apparently Saturation needs more instructions. And ... to top it off, the Saturation effect wasn't really working correctly (so maybe the commented if check was causing problems).

Oh, well, I'll continue toying with it.

Cory Plotts United States | Reply

2/17/2010 5:35:23 PM #

nokola

No problem! I'd like to see this working too! btw, with my changes the instructions go even higher!
Anyway I tried it a little bit and here is the <64 instructions version. I haven't tested it so it may have bugs Smile
The optimizations:
1. Split the rgb_to_hsv function in two, since you need either only the x or the yz components
2. Removed the division of the hue by 6, since it gets multiplied by 6 later
3. Took out 3*delta out of the brackets making it 2*delta in rgb_to_hsv_no_clip_xOnly_premul() (this saved the last 2 instructions!)

Hope it works Smile
I'm not sure what the Saturation code is like...


sampler2D input : register(S0);
sampler2D blend : register(S1);

#define QUAD_REAL float
#define QUAD_REAL3 float3

float rgb_to_hsv_no_clip_xOnly_premul(QUAD_REAL3 RGB)
{
  float minChannel, maxChannel;
  if (RGB.x > RGB.y) {
    maxChannel = RGB.x;
    minChannel = RGB.y;
  }
  else {
    maxChannel = RGB.y;
    minChannel = RGB.x;
  }
  
  if (RGB.z > maxChannel) maxChannel = RGB.z;
  if (RGB.z < minChannel) minChannel = RGB.z;
    
    QUAD_REAL delta = maxChannel - minChannel;             //Delta RGB value
    if (delta != 0) {                    // If gray, leave H & S at zero
       QUAD_REAL3 delRGB;
       delRGB = 2*delta + (float3(maxChannel,maxChannel,maxChannel) - RGB) / delta;
       if      ( RGB.x == maxChannel ) return delRGB.z - delRGB.y;
       else if ( RGB.y == maxChannel ) return 2 + delRGB.x - delRGB.z;
       else if ( RGB.z == maxChannel ) return 4 + delRGB.y - delRGB.x;
       else return 0;
    }
    return 0;
}

QUAD_REAL3 rgb_to_hsv_no_clip_yzOnly(QUAD_REAL3 RGB)
{
  float minChannel, maxChannel;
  if (RGB.x > RGB.y) {
    maxChannel = RGB.x;
    minChannel = RGB.y;
  }
  else {
    maxChannel = RGB.y;
    minChannel = RGB.x;
  }
  
  if (RGB.z > maxChannel) maxChannel = RGB.z;
  if (RGB.z < minChannel) minChannel = RGB.z;
    
    QUAD_REAL delta = maxChannel - minChannel;             //Delta RGB value
    return float3(0,delta/maxChannel, maxChannel);
}


QUAD_REAL3 hsv_to_rgb_premulX(QUAD_REAL3 HSV)
{
    QUAD_REAL3 RGB; // no need to assign here, since will be assigned further down = HSV.z;
       QUAD_REAL var_h = HSV.x;
       QUAD_REAL var_i = floor(var_h);   // Or ... var_i = floor( var_h )
       QUAD_REAL var_1 = HSV.z * (1.0 - HSV.y);
       QUAD_REAL var_2 = HSV.z * (1.0 - HSV.y * (var_h-var_i));
       QUAD_REAL var_3 = HSV.z * (1.0 - HSV.y * (1-(var_h-var_i)));
       if      (var_i == 0) { RGB = QUAD_REAL3(HSV.z, var_3, var_1); }
       else if (var_i == 1) { RGB = QUAD_REAL3(var_2, HSV.z, var_1); }
       else if (var_i == 2) { RGB = QUAD_REAL3(var_1, HSV.z, var_3); }
       else if (var_i == 3) { RGB = QUAD_REAL3(var_1, var_2, HSV.z); }
       else if (var_i == 4) { RGB = QUAD_REAL3(var_3, var_1, HSV.z); }
       else                 { RGB = QUAD_REAL3(HSV.z, var_1, var_2); }
   return (RGB);
}

float3 BlendHue(float3 base, float3 blend)
{
  float3 baseHSL = rgb_to_hsv_no_clip_yzOnly(base);
  return hsv_to_rgb_premulX(float3(rgb_to_hsv_no_clip_xOnly_premul(blend), baseHSL.y, baseHSL.z));
}

float4 main(float2 uv : TEXCOORD) : COLOR
{
  float4 inputColor;
  inputColor = tex2D(input, uv);

  float4 blendColor;
  blendColor = tex2D(blend, uv);


  float4 resultColor;
  resultColor.a = inputColor.a;

  blendColor.rgb = saturate(blendColor.rgb / blendColor.a);


  resultColor.rgb = BlendHue(inputColor.rgb, blendColor.rgb);

  resultColor.rgb = resultColor.rgb * blendColor.a;
  resultColor.rgb = (1 - blendColor.a) * inputColor.rgb + resultColor.rgb;

  return resultColor;

}

nokola | Reply

2/18/2010 3:34:16 PM #

Cory Plotts

I quickly tried it ... and it seems to work (it compiles under the limit, but the results are wrong) for Hue, but not for Saturation. The code for the Saturation blend mode follows, in case you want to play with it on your end.

float3 BlendSaturation(float3 base, float3 blend)
{
  float3 baseHSV = rgb_to_hsv_no_clip_yzOnly(base);
  return hsv_to_rgb_premulX(float3(baseHSV.x, rgb_to_hsv_no_clip_yzOnly(blend).y, baseHSV.z));
}

Cory Plotts United States | Reply

2/18/2010 4:36:27 PM #

Nokola

Do you mean it works OK for Hue but has an issue for Saturation? Or does it not work for both Hue and Saturation?

Nokola United States | Reply

2/18/2010 5:21:35 PM #

Cory Plotts

It seems to work okay for Hue ... but it does not seem to work for Saturation.

Cory Plotts United States | Reply

2/18/2010 11:30:23 PM #

nokola

Yes - it doesn't work for saturation.
The rgb_to_hsv_no_clip_yzOnly(base) returns only Y and Z components (the X is ignored), thus the "yzOnly" name
When you use it in the "return ..." line, baseHSV.x has some random value.

I think you'll have to make 2 new functions rgb_to_hsv_xzOnly() (for the base parameter) and rgb_to_hsv_yOnly() (for the blend parameter) and use those. I just cut down the original rgb_to_hsv() to create the new rgb_to_hsv_<something>Only() functions

nokola | Reply

2/19/2010 3:32:03 PM #

Cory Plotts

Ok, I misunderstood. I'll try that.

Cory Plotts United States | Reply

2/25/2010 5:35:05 PM #

Cory Plotts

Finally got around to trying your tricks. I created rgb_to_hsv_no_clip_yOnly, rgb_to_hsv_no_clip_xzOnly_premulX, and hsv_to_rgb_premulX.

With the opacity changes, I'm still over by 3 instructions (67 versus 64). Darn!

Nice ideas though! If you email me, I'll email you back the code. I think others are bored by now by our back and forth. Smile

Cory Plotts United States | Reply

2/12/2010 5:46:30 PM #

Cory Plotts

By the way, absolutely love your site. Very impressed with your work and will definitely be toying around with what you have here. Smile

Cory Plotts | Reply

2/12/2010 6:35:51 PM #

nokola

Thanks! I appreciate it!

nokola | Reply

2/12/2010 4:23:53 AM #

John OBrien

Nice work, I just applied this to the base layer of Bing Maps:
www.soulsolutions.com.au/.../...-Pixel-Shader.aspx

I have been looking at ways to create a pixel shader to allow designers to customise the base imagery from Bing in their custom Silverlight application, this will help significantly.

John OBrien | Reply

2/12/2010 7:26:46 AM #

nokola

Thanks! Great job on the bing maps! I like how you can change the Earth to "night mode" Smile btw, I'm planning to make a "shift a specific hue range to another specific hue range" shader too at some point...

nokola | Reply

2/12/2010 9:41:49 AM #

trackback

Social comments and analytics for this post

This post was mentioned on Twitter by SilverlightNews: Someone Said it Was Impossible: Hue Shift in Pixel Shader 2.0 (EasyPainter, Silverlight) - http://snurl.com/uc7cy

uberVU - social comments | Reply

2/15/2010 10:10:30 PM #

Boris

Hi Nokola,
...thanks man for all your contributions and inspiring work.
About a year ago i stumbeled upon the same PS 2.0 limitations when i was trying to build a shader that could alter the HSB of a WPF UI like in photoshop .
I was trying all the possible HSL\HSV conversion algorithms i could find and tried to squeeze them into the 64 instructions. Well i gave up on that one after a while. Luckily i later found some cool matrix stuff which i used to solve the problem.
I quickly stitched together something on my site based on your example. Hope you don't mind. Feel free to use it.

www.theVisualDeveloper.com

The original code had another matrix for contrast adjustment but unfortunately i could not fit it in - again because of the 64 slots. Maybe you have an another cool optimization trick in your sleeve. Smile
And by the way...if you would like to colaborate aka. need some help with UI and stuff, me as a Designer\Developer i am eager to contribute.

Cheers Boris

Boris Germany | Reply

2/16/2010 6:50:53 PM #

nokola

Thanks Boris! I don't mind using the examples. btw, few days ago I found the cool matrix stuff too! (the Color Shift in EasyPainter now has Hue/Sat not just Hue)
I got mine from: social.msdn.microsoft.com/.../ I think it's probably the same as yours with the matrix pre-computed.

It seems to support Contrast, but it looks kind of weird. I decided to keep the original EasyPainter's Brightness/Contrast and use the "matrix shader" just for Hue/Sat. Very nice find of http://www.graficaobscura.com/matrix/index.html!! Smile
I'm working on fixing up some shader stuff now Smile Will compile all the info here post it.

nokola | Reply

2/15/2010 11:21:06 PM #

OJ

Hi there,

Nice shader. To address your point re: including the if() statement, the general rule when it comes to pixel or vertex shaders is to avoid branching whenever possible. This is a generalisation and there are cases when including branches doesn't have an adverse affect on the performance of the shader. But it should be avoided if possible because branching on the GPU is nowhere near as cheap as it is on the CPU.

It's actually better to do the work and then throw it away if it's not needed.

The number of branches in your shader is actually pretty excessive and you can remove most of them by using built-in functions like clamp(), saturate(), min() and max().

Just thought I'd share Smile Good luck!
OJ

OJ Australia | Reply

2/16/2010 6:41:49 PM #

nokola

Thanks OJ! I'll try the min() and max() and see how they compare. Do you happen to know a function or a more effective way to do "if (x < 0) x += 1"? I looked but found just clamp() and saturate() which are equivalent to "if (x < 0) x = 0", making the hue shift lose data..

nokola United States | Reply

2/17/2010 5:39:10 PM #

nokola

Tried the min() and max() - same instruction count as using the if()-s unfortunately. Maybe in some other situation they would have done better...

nokola | Reply

3/23/2010 5:20:51 AM #

Enrique


Thanks for sharing this, very educational for me.

I have a need to create a WriteableBitmap by applying color correction (uhe shift, brightness, contrast, saturation) to another (base) WriteableBitmap. I've made it work using RGB to HSV conversion but this is slow, involving a lot of double math.

My question: Is there a way to sequentially apply pixel shaders like your Hue Shift to a WriteableBitmap and get the result back on another WriteableBitmap? Or are Pixel Shaders only applicable during the final drawing to the screen?

Enrique

Enrique United States | Reply

3/23/2010 5:35:01 AM #

nokola

Hi Enrique,
Nice! Thank you for the comments!

Yes - there is a way to sequentially apply pixel shaders. It is described in this post: nokola.com/.../...loat-Computations-And-More).aspx

EasyPainter (http://nokola.com/easypainter) uses the same "trick" to apply effects (all effects are pixel shader based).
I'll post and blog about the source code in EasyPainter that applies the effects (it's a little bit more complex since it also takes in account selection)

Hope that helps!
Nokola

nokola United States | Reply

3/23/2010 5:56:40 AM #

nokola

forgot to mention: In order to achinese "apply shader to writeablebitmap and get the result in another writeablebitmap": You can assign the WriteableBitmap to Image.Source, then draw the image on another writeablebitmap by setting the Image.Effect to the effect you want.

nokola United States | Reply

3/23/2010 6:45:37 AM #

Enrique


Thanks much, I will give this a try.

Enrique

Enrique United States | Reply

5/20/2010 8:37:21 PM #

Pieter Voloshyn

Hi,
I am coordinator of Thumba (http://www.thumba.net/), an image editor in Silverlight as well as EasyPainter, and I really liked the algorithm you implemented. I did a little different, I implemented the concept of color matrix (something I had seen long ago on a project in C++) and saved me many HLSL instructions. If you want I can give you more details of how I did it.
Regards,
Pieter Voloshyn

Pieter Voloshyn Brazil | Reply

6/4/2010 5:18:35 PM #

nokola

Hi Pieter,

I know thumba.net- it's very nice and I like all the effects in it. If you can publish the color matrix approach, it would be great!
I have a color matrix shader for the same thing too, but didn't have time to publish the sample yet (I wanted to do non-linear shifts for more special hue effects)

nokola United States | Reply

6/29/2010 8:51:14 PM #

Pieter Voloshyn

Hi,
You can access my HSL adjustment implementation in the link below:
http://thumba.net/samples/Hsl.rar
Regards,
Pieter Voloshyn

Pieter Voloshyn Brazil | Reply

7/14/2010 12:53:19 AM #

nokola

Thanks! What is the license of the source?

nokola | Reply

7/25/2010 9:32:07 PM #

Pieter Voloshyn

No license is required. You can use any way you want. Since the original implementation wasn't me who did.
I saw your new project, Fantasia, and I thought excellent the brushes you did!
Congratulations!

Pieter Voloshyn Brazil | Reply

6/19/2010 8:11:55 AM #

Neil

Cool effect!  Here's a version that uses 59 instructions based on your code and the code at (http://www.cs.rit.edu/~ncs/color/t_convert.html).  You'll need to change the input to the shader to the range 0 to 6.

// Amount to shift the Hue, range 0 to 6
float HueShift : register(c0);

sampler2D 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 main(float2 uv : TEXCOORD) : COLOR
{
    float4 col = tex2D(Samp, uv);
    
    float3 hsv = rgb_to_hsv(col.xyz);
    
    hsv.x += HueShift;
    
    // Put the hue back to the -1 to 5 range
    if (hsv.x > 5) { hsv.x -= 6.0; }
    
    return float4(hsv_to_rgb(hsv), col.w);
}

Neil United States | Reply

6/19/2010 8:24:59 AM #

Neil

Actually, the 'if (b == v)' in rgb_to_hsv is unnecessary so you can get it down to 56 instructions:


// Amount to shift the Hue, range 0 to 6
float HueShift : register(c0);

sampler2D 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  /* 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 main(float2 uv : TEXCOORD) : COLOR
{
    float4 col = tex2D(Samp, uv);
    
    float3 hsv = rgb_to_hsv(col.xyz);
    
    hsv.x += HueShift;
    
    // Put the hue back to the -1, 5 range
    if (hsv.x > 5) { hsv.x -= 6.0; }
    
    return float4(hsv_to_rgb(hsv), col.w);
}

Neil United States | Reply

7/14/2010 12:56:36 AM #

nokola

Thanks! Unfortunately, I'm not sure how would you change the input in the range of 0 to 6 for Silverlight, since the shader works with 0..1 by default..

nokola | Reply

Add comment




  Country flag

biuquote
  • Comment
  • Preview
Loading



nokola.com | Terms | Log in

Recent

About the author

Happy & enjoying life. Software enthusiast.
The opinions I express here and on nokola.com are mine and not my employeer's (Microsoft).
This is the official blog of nokola.com. You can find Silverlight samples, coding stuff, and hopefully other interesting things here.