I want to make a pixel shader for Silverlight which will help me to change the Hue/Saturation/Lightness using a slider.
* Hue slider has values in range: [-180, 180]
* Saturation slider has values in range: [-100, 100]
* Lightness slider has values in range: [-100, 100]
I managed to create a pixel shader which can manipulate the Saturation and Lightness values.
But I can find any algorithm for changing the hue value.
Can anyone provide me an algorithm? Thank you.
Here is my HLSL code:
/// <summary>The brightness offset.</summary>
/// <minValue>-180</minValue>
/// <maxValue>180</maxValue>
/// <defaultValue>0</defaultValue>
float Hue : register(C0);
/// <summary>The saturation offset.</summary>
/// <minValue>-100</minValue>
/// <maxValue>100</maxValue>
/// <defaultValue>0</defaultValue>
float Saturation : register(C1);
/// <summary>The lightness offset.</summary>
/// <minValue>-100</minValue>
/// <maxValue>100</maxValue>
/// <defaultValue>0</defaultValue>
float Lightness : register(C2);
sampler2D input : register(S0);
//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float4 main(float2 uv : TEXCOORD) : COLOR
{
// some vars
float saturation = Saturation / 100 + 1;
float lightness = Lightness / 100;
float3 luminanceWeights = float3(0.299,0.587,0.114);
// input raw pixel
float4 srcPixel = tex2D(input, uv);
// Apply saturation
float luminance = dot(srcPixel, luminanceWeights);
float4 dstPixel = lerp(luminance, srcPixel, saturation);
// Apply lightness
dstPixel.rgb += lightness;
//retain the incoming alpha
dstPixel.a = srcPixel.a;
return dstPixel;
}
With a slightly different input domain but it can be adapted easily:
float Hue : register(C0); // 0..360, default 0
float Saturation : register(C1); // 0..2, default 1
float Luminosity : register(C2); // -1..1, default 0
sampler2D input1 : register(S0);
static float3x3 matrixH =
{
0.8164966f, 0, 0.5352037f,
-0.4082483f, 0.70710677f, 1.0548190f,
-0.4082483f, -0.70710677f, 0.1420281f
};
static float3x3 matrixH2 =
{
0.84630f, -0.37844f, -0.37844f,
-0.37265f, 0.33446f, -1.07975f,
0.57735f, 0.57735f, 0.57735f
};
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 c = tex2D(input1, uv);
float3x3 rotateZ =
{
cos(radians(Hue)), sin(radians(Hue)), 0,
-sin(radians(Hue)), cos(radians(Hue)), 0,
0, 0, 1
};
matrixH = mul(matrixH, rotateZ);
matrixH = mul(matrixH, matrixH2);
float i = 1 - Saturation;
float3x3 matrixS =
{
i*0.3086f+Saturation, i*0.3086f, i*0.3086f,
i*0.6094f, i*0.6094f+Saturation, i*0.6094f,
i*0.0820f, i*0.0820f, i*0.0820f+Saturation
};
matrixH = mul(matrixH, matrixS);
float3 c1 = mul(c, matrixH);
c1 += Luminosity;
return float4(c1, c.a);
}
The conversions between colour spaces are available at EasyRGB, see this post at nokola.com for a Silverlight implementation of a Hue shift. It may be possible to fit hue, saturation and brightness in one PS 2.0 shader if you take the approach mentioned here but I haven't tried.
Here is a great excample how to work with hue.
http://www.silverlightshow.net/news/Hue-Shift-in-Pixel-Shader-2.0-EasyPainter-Silverlight.aspx
Related
Is it possible to have DropShadowEffect to ignore certain colors when rendering shadow? To have sort of masked (color selective) shadow?
My problem is what shadow can be assigned to whole visual element (graph). It looks like this:
And I want
Notice grid lines without shadow (except 0,0 ones). This can be achieved by having 2 synchronized in zooming/offset graphs, one without shadow effect containing grid and another with shadow containing the rest. But I am not very happy about this solution (I predict lots of problems in the future with that solution). So I'd rather prefer to modify DropShadowEffect somehow.
I can create and use ShaderEffect, but I have no knowledge of how to program shaders to have actual shadow effect (if it can be produced by shaders at all).
Perhaps there is much easier way of doing something with DropShadowEffect itself? Anyone?
Edit
I tried to make shader effect:
sampler2D _input : register(s0);
float _width : register(C0);
float _height : register(C1);
float _depth : register(C2); // shadow depth
float4 main(float2 uv : TEXCOORD) : COLOR
{
// get pixel size
float2 pixel = {1 / _width, 1 / _height};
// find color at offset
float2 offset = float2(uv.x - pixel.x * _depth, uv.y - pixel.y * _depth);
float4 color = tex2D(_input, offset);
// convert to gray?
//float gray = dot(color, float4(0.1, 0.1, 0.1, 0));
//color = float4(gray, gray, gray, 1);
// saturate?
//color = saturate(color);
return tex2D(_input, uv) + color;
}
But fail at everything.
Edit
Here is screenshot of graph appearance, which I like (to those, who try to convince me not to do this):
Currently it is achieved by having special Graph which has template
<Border x:Name="PART_Border" BorderThickness="1" BorderBrush="Gray" CornerRadius="4" Background="White">
<Grid>
<Image x:Name="PART_ImageBack" Stretch="None"/>
<Image x:Name="PART_ImageFront" Stretch="None">
<Image.Effect>
<DropShadowEffect Opacity="0.3"/>
</Image.Effect>
</Image>
</Grid>
</Border>
Everything is rendered onto PART_ImageFront (with shadow), while grid is rendered onto PART_ImageBack (without shadow). Performance-wise it is still good.
I have zero experience with pixel shaders, but here's my quick and dirty attempt at a shadow effect that ignores "uncolored" pixels:
sampler2D _input : register(s0);
float _width : register(C0);
float _height : register(C1);
float _depth : register(C2);
float _opacity : register(C3);
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);
}
float4 main(float2 uv : TEXCOORD) : COLOR {
float width = _width; // 512;
float height = _height; // 512;
float depth = _depth; // 3;
float opacity = _opacity; // 0.25;
float2 pixel = { 1 / width, 1 / height };
float2 offset = float2(uv.x - pixel.x * depth, uv.y - pixel.y * depth);
float4 srcColor = tex2D(_input, offset);
float3 srcHsv = rgb_to_hsv(srcColor);
float4 dstColor = tex2D(_input, uv);
// add shadow for colored pixels only
// tweak saturation threshold as necessary
if (srcHsv.y >= 0.1) {
float gray = dot(srcColor, float4(0.1, 0.1, 0.1, 0.0));
float4 multiplier = float4(gray, gray, gray, opacity * srcColor.a);
return dstColor + (float4(0.1, 0.1, 0.1, 1.0) * multiplier);
}
return dstColor;
}
Here it is in action against a (totally legit) chart that I drew in Blend with the pencil tool:
The shader effect is applied on the root panel containing the axes, grid lines, and series lines, and it generates a shadow only for the series lines.
I don't think it's realistic to expect a shader to be able to apply a shadow to the axes and labels while ignoring the grid lines; the antialiasing on the text is bound to intersect the color/saturation range of the grid lines. I think applying the shadow to just the series lines is cleaner and more aesthetically pleasing anyway.
sampler2D input : register(s0);
float4 main(float2 uv : TEXCOORD) : COLOR {
float4 Color;
Color = tex2D( input , uv.xy);
return Color;
}
This is a basic 'do nothing' shader. the line with the text2D call takes the color that normally would be plotted at the current location. (And in this case simply returns it)
Instead of sampling uv.xy you could add an offset vector to uv.xy and return that color. This would shift the entire image in the direction of the offset vector.
You could combine these two:
sample the uv.xy, if set to a visible color plot that color (this will keep all the lines visible at the right location)
if it is transparent, sample a bit to the top left. if it is set to a color you want to have a shadow: return the shadow color.
Step 2. can be changed into: if it is set to a color you do not want to have a shadow, return a transparent color.
The offset and the colors to test and to use as shadow color could be parameters of the effect.
I strongly suggest to play around with Shazzam it will allow you to test your shader and it will generate the C# code for you.
Note that the uv coordinates are are not in pixels but scaled to 0.0 to 1.0.
Addition
A poor man's blur (anti-aliasing) could be obtained by sampling more pixels around the offset and calculating an average of the colors found that should cause a shadow. this will cause more pixels to receive a shadow.
To calculate the shadow color you could simply darken the existing color by multiplying it with a factor in between 0.0 (black) and 1.0 (original color)
By using the average from the blur you can multiply the shadow color again causing the blur to mix with the original color.
More precise (and expensive) would be to translate the rgb values to hls values and use 'official' darkening formulas to determine the shadow color.
I am running to a problem where I have a WPF shader effect (modified from Rene Schulte) to simulate Dot Matrix Display (DMD). Everything works great, but all dots are aliased.
See attached image.
I tried many features within WPF, to bring antialiasing, nothing to do.
in constructor (image within a textbox);
RenderOptions.SetBitmapScalingMode(MarqueeTB, BitmapScalingMode.HighQuality);
RenderOptions.SetEdgeMode(MarqueeTB, EdgeMode.Unspecified);
RenderOptions.SetClearTypeHint(MarqueeTB, ClearTypeHint.Enabled);
I don't think it is my graphic card or Windows config. I did some testing on two PCs, same results; Windows 8.1 and Windows 7.
I have no clue how to proceed. Any help or advises would be welcome.
Thanks in advance, regards,
Shader code:
// Project: Shaders
//
// Description: Mosaic Shader for Coding4Fun.
//
// Changed by: $Author$
// Changed on: $Date$
// Changed in: $Revision$
// Project: $URL$
// Id: $Id$
//
//
// Copyright (c) 2010 Rene Schulte
//
/// <description>Mosaic Shader for Coding4Fun.</description>
/// <summary>The number pixel blocks.</summary>
/// <type>Single</type>
/// <minValue>2</minValue>
/// <maxValue>500</maxValue>
/// <defaultValue>50</defaultValue>
float BlockCount : register(C0);
/// <summary>The rounding of a pixel block.</summary>
/// <type>Single</type>
/// <minValue>0</minValue>
/// <maxValue>1</maxValue>
/// <defaultValue>0.45</defaultValue>
float Max : register(C2);
/// <summary>The aspect ratio of the image.</summary>
/// <type>Single</type>
/// <minValue>0</minValue>
/// <maxValue>10</maxValue>
/// <defaultValue>1</defaultValue>
float AspectRatio : register(C3);
// Sampler
sampler2D input : register(S0);
// Static computed vars for optimization
static float2 BlockCount2 = float2(BlockCount, BlockCount / AspectRatio);
static float2 BlockSize2 = 1.0f / BlockCount2;
// Shader
float4 main(float2 uv : TEXCOORD) : COLOR
{
// Calculate block center
float2 blockPos = floor(uv * BlockCount2);
float2 blockCenter = blockPos * BlockSize2 + BlockSize2 * 0.5;
// Scale coordinates back to original ratio for rounding
float2 uvScaled = float2(uv.x * AspectRatio, uv.y);
float2 blockCenterScaled = float2(blockCenter.x * AspectRatio, blockCenter.y);
// Round the block by testing the distance of the pixel coordinate to the center
float dist = length(uvScaled - blockCenterScaled) * BlockCount2;
if(dist < 0 || dist > Max)
{
return 1;
}
// Sample color at the calculated coordinate
return tex2D(input, blockCenter);
}
Here is a solution & the corrected code accordingly. I am not sure if this is 'the best' way, but it worked. (see antialiasedCircle section, solution from: http://gamedev.stackexchange.com/questions/34582/how-do-i-use-screen-space-derivatives-to-antialias-a-parametric-shape-in-a-pixel )
//
// Project: Dot Matrix Display (DMD) Shader
// Inspired from From Mosaic shader Copyright (c) 2010 Rene Schulte
/// <summary>The number pixel blocks.</summary>
/// <type>Single</type>
/// <minValue>2</minValue>
/// <maxValue>500</maxValue>
/// <defaultValue>34</defaultValue>
float BlockCount : register(C0);
/// <summary>The rounding of a pixel block.</summary>
/// <type>Single</type>
/// <minValue>0</minValue>
/// <maxValue>1</maxValue>
/// <defaultValue>0.45</defaultValue>
float Max : register(C2);
/// <summary>The aspect ratio of the image.</summary>
/// <type>Single</type>
/// <minValue>0</minValue>
/// <maxValue>10</maxValue>
/// <defaultValue>1.55</defaultValue>
float AspectRatio : register(C3);
/// <summary>The monochrome color used to tint the input.</summary>
/// <defaultValue>Yellow</defaultValue>
float4 FilterColor : register(C1);
/// <summary>monochrome.</summary>
/// <defaultValue>1</defaultValue>
float IsMonochrome : register(C4);
// Sampler
sampler2D input : register(S0);
// Static computed vars for optimization
static float2 BlockCount2 = float2(BlockCount, BlockCount / AspectRatio);
static float2 BlockSize2 = 1.0f / BlockCount2;
float4 setMonochrome(float4 color) : COLOR
{
float4 monochrome= color;
if(((int)IsMonochrome) == 1)
{
float3 rgb = color.rgb;
float3 luminance = dot(rgb, float3(0.30, 0.59, 0.11));
monochrome= float4(luminance * FilterColor.rgb, color.a);
}
return monochrome;
}
float4 SetDMD(float2 uv : TEXCOORD, sampler2D samp) : COLOR
{
// Calculate block center
float2 blockPos = floor(uv * BlockCount2);
float2 blockCenter = blockPos * BlockSize2 + BlockSize2 * 0.5;
// Scale coordinates back to original ratio for rounding
float2 uvScaled = float2(uv.x * AspectRatio, uv.y);
float2 blockCenterScaled = float2(blockCenter.x * AspectRatio, blockCenter.y);
// Round the block by testing the distance of the pixel coordinate to the center
float dist = length(uvScaled - blockCenterScaled) * BlockCount2;
float4 insideColor= tex2D(samp, blockCenter);
float4 outsideColor = insideColor;
outsideColor.r = 0;
outsideColor.g = 0;
outsideColor.b = 0;
outsideColor.a = 1;
float distFromEdge = Max - dist; // positive when inside the circle
float thresholdWidth = .22; // a constant you'd tune to get the right level of softness
float antialiasedCircle = saturate((distFromEdge / thresholdWidth) + 0.5);
return lerp(outsideColor, insideColor, antialiasedCircle);
}
// Shader
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 DMD= SetDMD(uv, input);
DMD = setMonochrome(DMD);
return DMD;
}
Here is an image of the successful solution;
Is it layout rounding? Try MarqueeTB.UseLayoutRounding = false;
The documentation does not tell much about this behavior:
Variable Syntax
static Mark a local variable so that it is initialized one time and persists between function calls. If the declaration does not include an initializer, the value is set to zero. A global variable marked static is not visible to an application.
Can you explain why does removing the static modifier from matrices produces unexpected output ?
static float3x3 protanopia ={
0.567f, 0.433f, 0.000f,
0.558f, 0.442f, 0.000f,
0.000f, 0.242f, 0.758f,
};
Normal result with static :
Incorrect without static:
Here's the complete code:
sampler2D input : register(s0);
// new HLSL shader
// modify the comment parameters to reflect your shader parameters
/// <summary>Explain the purpose of this variable.</summary>
/// <minValue>0/minValue>
/// <maxValue>8</maxValue>
/// <defaultValue>0</defaultValue>
float Filter : register(C0);
static float3x3 norm ={
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
};
static float3x3 protanopia ={
0.567f, 0.433f, 0.000f,
0.558f, 0.442f, 0.000f,
0.000f, 0.242f, 0.758f,
};
float4 main(float2 uv : TEXCOORD) : COLOR
{
int filter = (int)abs(Filter);
float3x3 mat;
switch (filter)
{
case 0:
mat = norm;
break;
case 1:
mat=protanopia;
break;
default:
break;
}
float4 color = tex2D( input , uv.xy);
float3 rgb = {
color.x * mat._m00 + color.y * mat._m01 + color.z * mat._m02,
color.x * mat._m10 + color.y * mat._m11 + color.z * mat._m12,
color.x * mat._m20 + color.y * mat._m21 + color.z * mat._m22
};
return float4(rgb,1);
}
You have to manage the memory of non-static variables yourself. Therefore when using static everything works as expected, since the compiler cares about reserving some memory where it can store the filter values. If the static is not present you have to manage the memory yourself - that means you have to retrieve the default value of the variable and copy it by hand to constant buffer for instance.
I am working on silverlight application and I had use the pixel shadder with brightness and contrast which is working fine. so I had tried with panning center x,y but it will not working so how I can add this panning.
My PS file like this.
/// <class>briconEffect</class>
/// <description>An effect that controls brightness and contrast.</description>
//-----------------------------------------------------------------------------------------
// Shader constant register mappings (scalars - float, double, Point, Color, Point3D, etc.)
//-----------------------------------------------------------------------------------------
/// <summary>The brightness offset.</summary>
/// <minValue>-1</minValue>
/// <maxValue>1</maxValue>
/// <defaultValue>0</defaultValue>
float Brightness : register(C0);
/// <summary>The contrast multiplier.</summary>
/// <minValue>0</minValue>
/// <maxValue>2</maxValue>
/// <defaultValue>1.5</defaultValue>
float Contrast : register(C1);
/// <summary>The gamma correction exponent.</summary>
/// <minValue>0.5</minValue>
/// <maxValue>2</maxValue>
/// <defaultValue>0.8</defaultValue>
float Gamma : register(C2);
/// <summary>Change the ratio between the Red channel</summary>
/// <minValue>0/minValue>
/// <maxValue>1</maxValue>
/// <defaultValue>0.5</defaultValue>
float RedRatio : register(C3);
/// <summary>Change the ratio between the Blue channel </summary>
/// <minValue>0/minValue>
/// <maxValue>1</maxValue>
/// <defaultValue>0.5</defaultValue>
float BlueRatio : register(C4);
/// <summary>Change the ratio between the Green channel</summary>
/// <minValue>0/minValue>
/// <maxValue>1</maxValue>
/// <defaultValue>0.5</defaultValue>
float GreenRatio : register(C5);
//--------------------------------------------------------------------------------------
// Sampler Inputs (Brushes, including Texture1)
//--------------------------------------------------------------------------------------
sampler2D Texture1Sampler : register(S0);
//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 pixelColor = tex2D(Texture1Sampler, uv);
pixelColor.rgb = pow(pixelColor.rgb, Gamma);
pixelColor.rgb /= pixelColor.a;
// Apply contrast.
pixelColor.rgb = ((pixelColor.rgb - 0.5f) * max(Contrast, 0)) + 0.5f;
// Apply brightness.
pixelColor.rgb += Brightness;
// Return final pixel color.
pixelColor.rgb *= pixelColor.a;
pixelColor.r = pixelColor.r * (1-RedRatio ) ;
pixelColor.b = pixelColor.b * (1-BlueRatio ) ;
pixelColor.g = pixelColor.g * (1-GreenRatio ) ;
return pixelColor;
}
I am trying to get an HLSL Pixel Shader for Silverlight to work to subtract the background image from a video image. Can anyone suggest a more sophisticated algorithm than I am using because my algorithm isn't doing it correctly?
float Tolerance : register(C1);
SamplerState ImageSampler : register(S0);
SamplerState BackgroundSampler : register(S1);
struct VS_INPUT
{
float4 Position : POSITION;
float4 Diffuse : COLOR0;
float2 UV0 : TEXCOORD0;
float2 UV1 : TEXCOORD1;
};
struct VS_OUTPUT
{
float4 Position : POSITION;
float4 Color : COLOR0;
float2 UV : TEXCOORD0;
};
float4 PS( VS_OUTPUT input ) : SV_Target
{
float4 color = tex2D( ImageSampler, input.UV );
float4 background = tex2D( BackgroundSampler, input.UV);
if (abs(background.r - color.r) <= Tolerance &&
abs(background.g - color.g) <= Tolerance &&
abs(background.b - color.b) <= Tolerance)
{
color.rgba = 0;
}
return color;
}
To see an example of this, you need a computer with a webcam:
Go to the page http://xmldocs.net/alphavideo/background.html
Press [Start Recording].
Move your body out of the the scene and press [Capture Background].
Then move your body back into the scene and use the slider to adjust the Toleance value to the shader.
EDIT
Single pixel isn't useful for such task, because of noise. So algorithm essence should be to measure similarity between pixel blocks. Recipe pseudo-code (based on correlation measurement):
Divide image into N x M grid
For each N,M cell in grid:
correlation = correlation_between(signal_pixels_of(N,M),
background_pixels_of(N,M)
);
if (correlation > threshold)
show_background_cell(N,M)
else
show_signal_cell(N,M)
This is sequential pseudo code, but it could be easily converted to HLSL shader. Simply each pixel detects to which pixel block it belongs, and after that measures correlation between corresponding blocks. And based on that correlation shows or hides current pixel.
Try this approach,
Good Luck !