Silverlight Image Panning - silverlight

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;
}

Related

How to grayscale an entire WPF application

A user requested a grayscale version of my WPF application. The application supports different color themes and has text and images. Obviously, one way to this is to create a grayscale theme. But in my mind, it might be better to add a toggle that grayscales the current theme on demand. This got me to wondering if there is a way to grayscale an entire WPF application. I looked at pixel shaders briefly but it doesn't appear I can apply one globally to an app. Looking for suggestions on how to do this.
While a dedicated theme would be the best choice, it's a tremendous amount of work.
Since it's a single user ask - you may want to try a pixel shader and gather some feedback from the user and see if it works. If not, you can look into doing a dedicated theme.
Here is a pixel shader that works really well with different colors, contrasts, and brightness of colors. It uses the HSP color model, which you can learn about here:
http://alienryderflex.com/hsp.html
sampler2D implicitInput : register(s0);
float factor : register(c0);
/// <summary>The brightness offset.</summary>
/// <minValue>-1</minValue>
/// <maxValue>1</maxValue>
/// <defaultValue>0</defaultValue>
float brightness : register(c1);
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 pixelColor = tex2D(implicitInput, uv);
pixelColor.rgb /= pixelColor.a;
// Apply brightness.
pixelColor.rgb += brightness;
// Return final pixel color.
pixelColor.rgb *= pixelColor.a;
float4 color = pixelColor;
float pr = .299;
float pg = .587;
float pb = .114;
float gray = sqrt(color.r * color.r * pr + color.g * color.g * pg + color.b * color.b * pb);
float4 result;
result.r = (color.r - gray) * factor + gray;
result.g = (color.g - gray) * factor + gray;
result.b = (color.b - gray) * factor + gray;
result.a = color.a;
return result;
}
You will need to compile the shader to a .ps file.
Here is the wrapper class:
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Effects;
namespace Shaders
{
/// <summary>
/// Represents a grayscale pixel shader effect using the HSP method.
/// </summary>
public class GrayscaleHspEffect : ShaderEffect
{
/// <summary>
/// Identifies the Input property.
/// </summary>
public static readonly DependencyProperty InputProperty = RegisterPixelShaderSamplerProperty("Input", typeof(GrayscaleHspEffect), 0);
/// <summary>
/// Identifies the Factor property.
/// </summary>
public static readonly DependencyProperty FactorProperty = DependencyProperty.Register("Factor", typeof(double), typeof(GrayscaleHspEffect), new UIPropertyMetadata(0D, PixelShaderConstantCallback(0)));
/// <summary>
/// Identifies the Brightness property.
/// </summary>
public static readonly DependencyProperty BrightnessProperty = DependencyProperty.Register("Brightness", typeof(double), typeof(GrayscaleHspEffect), new UIPropertyMetadata(0D, PixelShaderConstantCallback(1)));
/// <summary>
/// Creates a new instance of the <see cref="GrayscaleHspEffect"/> class.
/// </summary>
public GrayscaleHspEffect()
{
var pixelShader = new PixelShader();
pixelShader.UriSource = new Uri("/Shaders;component/Effects/GrayscaleHspEffect.ps", UriKind.Relative);
PixelShader = pixelShader;
UpdateShaderValue(InputProperty);
UpdateShaderValue(FactorProperty);
UpdateShaderValue(BrightnessProperty);
}
/// <summary>
/// Gets or sets the <see cref="Brush"/> used as input for the shader.
/// </summary>
public Brush Input
{
get => ((Brush)(GetValue(InputProperty)));
set => SetValue(InputProperty, value);
}
/// <summary>
/// Gets or sets the factor used in the shader.
/// </summary>
public double Factor
{
get => ((double)(GetValue(FactorProperty)));
set => SetValue(FactorProperty, value);
}
/// <summary>
/// Gets or sets the brightness of the effect.
/// </summary>
public double Brightness
{
get => ((double)(GetValue(BrightnessProperty)));
set => SetValue(BrightnessProperty, value);
}
}
}

Child control handling touch event affects multi-point manipulation

I have a UserControl that must respond to TouchUp events and this sits within a Viewbox which needs to be panned and scaled with pinch manipulation. Touch events on the control are handled fine. However pinch manipulations only scale the ViewPort if both pinch points are contained entirely within either the user control or the Viewport space around it. If the pinch straddles the user control boundary then the ManipulationDelta loses one of the points and reports a scale of (1,1).
If I remove IsManipulationEnabled="True" from the control handling the TouchUp event then the scaling works but the touch event doesn’t fire.
What can I do to retain the manipulation across the ViewPort whilst also handling the touch event in the user control?
Test Solution
<Window x:Class="TouchTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Touch Test"
Height="400"
Width="700"
ManipulationDelta="OnManipulationDelta"
ManipulationStarting="OnManipulationStarting">
<Grid Background="Transparent"
IsManipulationEnabled="True">
<Viewbox x:Name="Viewbox"
Stretch="Uniform">
<Viewbox.RenderTransform>
<MatrixTransform/>
</Viewbox.RenderTransform>
<Grid Width="800"
Height="800"
Background="LightGreen"
IsManipulationEnabled="True"
TouchUp="OnTouchUp">
<TextBlock x:Name="TimeTextBlock"
FontSize="100"
TextAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</Viewbox>
<TextBlock x:Name="ScaleTextBlock"
FontSize="10"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"/>
</Grid>
</Window>
Handlers in code-behind:
private void OnTouchUp(object sender, TouchEventArgs e)
{
TimeTextBlock.Text = DateTime.Now.ToString("H:mm:ss.fff");
}
private void OnManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
e.ManipulationContainer = this;
}
private void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
if (Viewbox == null)
{
return;
}
ManipulationDelta delta = e.DeltaManipulation;
ScaleTextBlock.Text = $"Delta Scale: {delta.Scale}";
MatrixTransform transform = Viewbox.RenderTransform as MatrixTransform;
if (transform == null)
{
return;
}
Matrix matrix = transform.Matrix;
Point position = ((FrameworkElement)e.ManipulationContainer).TranslatePoint(e.ManipulationOrigin, Viewbox);
position = matrix.Transform(position);
matrix = MatrixTransformations.ScaleAtPoint(matrix, delta.Scale.X, delta.Scale.Y, position);
matrix = MatrixTransformations.PreventNegativeScaling(matrix);
matrix = MatrixTransformations.Translate(matrix, delta.Translation);
matrix = MatrixTransformations.ConstrainOffset(Viewbox.RenderSize, matrix);
transform.Matrix = matrix;
}
Supporting class:
public static class MatrixTransformations
{
/// <summary>
/// Prevent the transformation from being offset beyond the given size rectangle.
/// </summary>
/// <param name="size"></param>
/// <param name="matrix"></param>
/// <returns></returns>
public static Matrix ConstrainOffset(Size size, Matrix matrix)
{
double distanceBetweenViewRightEdgeAndActualWindowRight = size.Width * matrix.M11 - size.Width + matrix.OffsetX;
double distanceBetweenViewBottomEdgeAndActualWindowBottom = size.Height * matrix.M22 - size.Height + matrix.OffsetY;
if (distanceBetweenViewRightEdgeAndActualWindowRight < 0)
{
// Moved in the x-axis too far left. Snap back to limit
matrix.OffsetX -= distanceBetweenViewRightEdgeAndActualWindowRight;
}
if (distanceBetweenViewBottomEdgeAndActualWindowBottom < 0)
{
// Moved in the x-axis too far left. Snap back to limit
matrix.OffsetY -= distanceBetweenViewBottomEdgeAndActualWindowBottom;
}
// Prevent positive offset
matrix.OffsetX = Math.Min(0.0, matrix.OffsetX);
matrix.OffsetY = Math.Min(0.0, matrix.OffsetY);
return matrix;
}
/// <summary>
/// Prevent the transformation from performing a negative scale.
/// </summary>
/// <param name="matrix"></param>
/// <returns></returns>
public static Matrix PreventNegativeScaling(Matrix matrix)
{
matrix.M11 = Math.Max(1.0, matrix.M11);
matrix.M22 = Math.Max(1.0, matrix.M22);
return matrix;
}
/// <summary>
/// Translate the matrix by the given vector to providing panning.
/// </summary>
/// <param name="matrix"></param>
/// <param name="vector"></param>
/// <returns></returns>
public static Matrix Translate(Matrix matrix, Vector vector)
{
matrix.Translate(vector.X, vector.Y);
return matrix;
}
/// <summary>
/// Scale the matrix by the given X/Y factors centered at the given point.
/// </summary>
/// <param name="matrix"></param>
/// <param name="scaleX"></param>
/// <param name="scaleY"></param>
/// <param name="point"></param>
/// <returns></returns>
public static Matrix ScaleAtPoint(Matrix matrix, double scaleX, double scaleY, Point point)
{
matrix.ScaleAt(scaleX, scaleY, point.X, point.Y);
return matrix;
}
}
So, I'm not a wpf programmer. But have a suggestion/workaround which could possibly work for you.
You could code the thing as follows:
set IsManipulationEnabled="True" (in this case OnTouchUp isn't fired for the grid colored in LightGreen)
Set OnTouchUp to fire on either Viewbox x:Name="Viewbox" or the Grid above this Viewbox (rather than for the 800x800 Grid)
So now OnTouchUp would be fired whenever you touch anywhere in the Viewbox (not just inside the LightGreen area)
When OnTouchUp is now fired, just check if the co-ordinates are in the region of LightGreen box. If YES-> update the time, if no, leave the time as it is.
I understand this is a workaround. Still posted an answer, in case it could prove useful.
i am not sure the sample you post reflect totally your code...but what i see : you do not manage the ManipulationCompleted and LostMouseCapture. Also you do not make any MouseCapture() MouseRelease() so when the manipulation outbound the window you loose it....search "mouse capture" on this repo, you will see even if no manipulation event that this is quite complicated....https://github.com/TheCamel/ArchX/search?utf8=%E2%9C%93&q=mouse+capture&type=

WPF Shader Effect - antialiasing not showing

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;

XNA Arrays and drawing textures?

I am completely new to coding and have only been practicing for a few weeks and i have been assigned a task in which seems simple has hit a stumbling block
i have 4 sprites on screen drawn but i have to every time the game starts the sprites have to be random chosen between 1 sprite or the other as well out of the 2 sprites there must be at least one of each sprite on screen.
my tutor suggested that i use an array to store the textures and then code it so it randomly picks which one to draw each time
namespace GamesProgrammingAssement1
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
KeyboardState keys;
KeyboardState oldKeys;
GamePadState Pad1;
GamePadState oldPad1;
Texture2D gnome;
Texture2D troll;
Rectangle sprRect1;
Rectangle sprRect2;
Rectangle sprRect3;
Rectangle sprRect4;
SpriteFont Run;
SpriteFont Score;
int scoreNum = 0;
int runNum = 0;
Vector2 scorePos;
Vector2 runPos;
Texture2D[] sprite = new Texture2D[2];
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
sprRect1 = new Rectangle(375, 100, 64, 64);
sprRect2 = new Rectangle(375, 300, 64, 64);
sprRect3 = new Rectangle(225, 200, 64, 64);
sprRect4 = new Rectangle(525, 200, 64, 64);
scorePos = new Vector2(5, 400);
runPos = new Vector2(5, 425);
sprite[0] = gnome;
sprite[1] = troll;
base.Initialize();
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
gnome = Content.Load<Texture2D>("Gnome");
troll = Content.Load<Texture2D>("Troll");
Score = Content.Load<SpriteFont>("Score");
Run = Content.Load<SpriteFont>("Run");
}
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// all content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
KeyboardState keys = Keyboard.GetState();
KeyboardState oldkeys = keys;
if (keys.IsKeyDown(Keys.Escape)) this.Exit();
// TODO: Add your update logic here
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(gnome,sprRect1,Color.White);
spriteBatch.Draw(troll, sprRect2,Color.White);
spriteBatch.Draw(troll, sprRect3, Color.White);
spriteBatch.Draw(troll, sprRect4, Color.White);
spriteBatch.DrawString(Score, "SCORE : "+ scoreNum, scorePos, Color.Black);
spriteBatch.DrawString(Run, "RUN OF TROLL : " + runNum, runPos, Color.Black);
spriteBatch.End();
base.Draw(gameTime);
}
}
}
any help would be great because i dont know if im storing the arrays properly or if im doing random right
I see you are storing your Textures in an Array. I do not see any code for Random in your sample.
That being said, consider the following function:
Random rand = new Random();
private Texture2D GetRandomTexture()
{
return sprite[rand.Next(0, 2)];
}
Calling this function, will return a random texture contained in your "sprite" array. Plugging this function call in your draw could resemble something like this:
spriteBatch.Draw(GetRandomTexture(), sprRect1, Color.White);
Since you declared that you are a beginner, I tried not to go too much into details. You should, however, considering looking into creating a new class "Sprite" that would contain the Texture2D, Position, Rectangle values of each of your sprites. Then instead of storing an array of Textures, you would be able of storing an array (or list) of Sprite objects.
You want to choose four sprites, therefore you should use an array (or similar structure) which can contain four elements:
Texture2D[] sprites = new Texture2D[4];
When initializing the Array there are three possible situations regarding how many of each sprite will exist:
1-3 (one troll, three gnomes)
2-2 (two of each)
3-1 (three trolls, one gnome)
So firstly, you have to pick one of these distributions:
var rnd = new Random();
var trolls = rnd.Next(1, 3);
var gnomes = 4 - trolls;
Then, you can fill the array:
for(int i = 0; i < 4; ++i)
{
if(gnomes == 0)
{
//choose the troll
sprites[i] = troll;
--trolls;
}
else if(trolls == 0)
{
//choose the gnome
sprites[i] = gnome;
--gnomes;
}
else
{
//choose randomly
if(rnd.Next(2) < 1)
{
sprites[i] = troll;
--trolls;
}
else
{
sprites[i] = gnome;
--gnomes;
}
}
}
You draw them like
spriteBatch.Draw(sprites[0], sprRect1,Color.White);
spriteBatch.Draw(sprites[1], , sprRect2,Color.White);
spriteBatch.Draw(sprites[2], , sprRect3, Color.White);
spriteBatch.Draw(sprites[3], , sprRect4, Color.White);

HLSL hue change algorithm

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

Resources