Simple image filters written as HLSL pixel shaders

I had to write some image filtering pixel shaders for some coarse-work. So I thought I will make them available for anyone who might want to write there own image filtering pixel shaders, or just want some quickly without having to worry about the details for now.

The snippets below are in HLSL, and are single pass techniques. Written to compile with pixel shader 2.0. The snippets have “uniform extern texture tex;” declared. The shaders only work with 1024 x 768 resolution textures: a rigid implementation, easily overcame in later shader versions with the use of ddx and ddy intrinsic functions. OK Enough fluffing around, heres the code:

Blurs using a 3×3 filter kernel

// Blurs using a 3x3 filter kernel
float4 BlurFunction3x3(VertexShaderOutput input) : COLOR0
{
  // TOP ROW
  float4 s11 = tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, -1.0f / 768.0f));	// LEFT
  float4 s12 = tex2D(texsampler, input.texcoords + float2(0, -1.0f / 768.0f));				// MIDDLE
  float4 s13 = tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f, -1.0f / 768.0f));	// RIGHT

  // MIDDLE ROW
  float4 s21 = tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, 0));				// LEFT
  float4 col = tex2D(texsampler, input.texcoords);											// DEAD CENTER
  float4 s23 = tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, 0)); 				// RIGHT

  // LAST ROW
  float4 s31 = tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, 1.0f / 768.0f));	// LEFT
  float4 s32 = tex2D(texsampler, input.texcoords + float2(0, 1.0f / 768.0f));					// MIDDLE
  float4 s33 = tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f, 1.0f / 768.0f));	// RIGHT

  // Average the color with surrounding samples
  col = (col + s11 + s12 + s13 + s21 + s23 + s31 + s32 + s33) / 9;
  return col;
}

Blurs using a 5×5 filter kernel – ARG!

// Blurs using a 5x5 filter kernel
float4 BlurFunction5x5(VertexShaderOutput input) : COLOR0
{
  return (
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		-2.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		-1.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(0,					0)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		0)) +

    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		1.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		2.0f / 768.0f))
  ) / 25;
}

Blurs using a 7×7 filter kernel – ARGGGG!!!!

// Blurs using a 7x7 filter kernel
float4 BlurFunction7x7(VertexShaderOutput input) : COLOR0
{
  return (
    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		-3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		-3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		-3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					-3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		-3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		-3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		-3.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		-2.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		-1.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(0,					0)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		0)) +

    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		1.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		2.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		3.0f / 768.0f))
  ) / 49;
}

Outputs edges only using a A 3×3 edge filter kernel

// Outputs edges only using a A 3x3 edge filter kernel
float4 OutlinesFunction3x3(VertexShaderOutput input) : COLOR0
{
  float4 lum = float4(0.30, 0.59, 0.11, 1);

  // TOP ROW
  float s11 = dot(tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, -1.0f / 768.0f)), lum);	// LEFT
  float s12 = dot(tex2D(texsampler, input.texcoords + float2(0, -1.0f / 768.0f)), lum);				// MIDDLE
  float s13 = dot(tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f, -1.0f / 768.0f)), lum);	// RIGHT

  // MIDDLE ROW
  float s21 = dot(tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, 0)), lum);				// LEFT
  // Omit center
  float s23 = dot(tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, 0)), lum); 				// RIGHT

  // LAST ROW
  float s31 = dot(tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, 1.0f / 768.0f)), lum);	// LEFT
  float s32 = dot(tex2D(texsampler, input.texcoords + float2(0, 1.0f / 768.0f)), lum);				// MIDDLE
  float s33 = dot(tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f, 1.0f / 768.0f)), lum);	// RIGHT

  // Filter ... thanks internet 🙂
  float t1 = s13 + s33 + (2 * s23) - s11 - (2 * s21) - s31;
  float t2 = s31 + (2 * s32) + s33 - s11 - (2 * s12) - s13;

  float4 col;

  if (((t1 * t1) + (t2 * t2)) > 0.05) {
  col = float4(0,0,0,1);
  } else {
    col = float4(1,1,1,1);
  }

  return col;
}

A 3×3 emboss filter kernel

// A 3x3 emboss filter kernel
float4 EmbossShaderFunction3x3(VertexShaderOutput input) : COLOR0
{
  float4 s22 = tex2D(texsampler, input.texcoords); // center
  float4 s11 = tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, -1.0f / 768.0f));
  float4 s33 = tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f, 1.0f / 768.0f));

  s11.rgb = (s11.r + s11.g + s11.b);
  s22.rgb = (s22.r + s22.g + s22.b) * -0.5;
  s33.rgb = (s22.r + s22.g + s22.b) * 0.2;

  return (s11 + s22 + s33);
  // return col * (s11 + s22 + s33); // with color 🙂 - but B & W looks a lot nicer
}

9 thoughts on “Simple image filters written as HLSL pixel shaders

  1. Hi Novak,

    Thanks for this useful post.

    I came from WPF (Windows Presentation Foundation) environment. I was searching on how to write a pixel shader effect on converting an image to a line art sketch. But because I am new to pixel shaders I don’t know how to do that. As I saw the second image is like the effect that I am searching for. But I don’t know how to use it. In WPF we can wrire hlsl(.fx) and then we can compile it by DirectX then we can use the generated .ps file directly in C# to apply the effect on the picture. As my little information says we should have a main method to be used by PixelShader_2.0. Something like this

    sampler2D implicitInputSampler : register(S0);
    float4 main(float2 uv : TEXCOORD) : COLOR
    {
    float4 color = tex2D( implicitInputSampler, uv );
    float4 inverted_color = 1 – color;
    inverted_color.a = color.a;
    inverted_color.rgb *= inverted_color.a;
    return inverted_color;
    }

    Could you please help me how to rewrite your work in this format.

    Thanks a lot for time and consideration.

    Regards,
    Ali Daneshmandi

    • Ahh its been ages since i’ve looked at Microsoft stuff let alone 3d programming. I have never heard of WPF simply because I’ve been developing on linux and mac for the last couple of years. I have never used the register function before (in your first line) – is that accessing a special register on the graphics card that points to the first texture sampler? I’m guessing so… Here is my stab of what I think you need, which is probably too late now:

      sampler2D implicitInputSampler : register(S0);
      float4 main(float2 uv : TEXCOORD) : COLOR
      {
         float4 lum = float4(0.30, 0.59, 0.11, 1);  
      
         // TOP ROW  
         float s11 = dot(tex2D(implicitInputSampler, uv + float2(-1.0f / 1024.0f, -1.0f / 768.0f)), lum);   // LEFT  
         float s12 = dot(tex2D(implicitInputSampler, uv + float2(0, -1.0f / 768.0f)), lum);             // MIDDLE  
         float s13 = dot(tex2D(implicitInputSampler, uv + float2(1.0f / 1024.0f, -1.0f / 768.0f)), lum);    // RIGHT  
         
        // MIDDLE ROW  
         float s21 = dot(tex2D(implicitInputSampler, uv + float2(-1.0f / 1024.0f, 0)), lum);                // LEFT  
         // Omit center  
         float s23 = dot(tex2D(implicitInputSampler, uv + float2(-1.0f / 1024.0f, 0)), lum);                // RIGHT  
         
         // LAST ROW  
         float s31 = dot(tex2D(implicitInputSampler, uv + float2(-1.0f / 1024.0f, 1.0f / 768.0f)), lum);    // LEFT  
         float s32 = dot(tex2D(implicitInputSampler, uv + float2(0, 1.0f / 768.0f)), lum);              // MIDDLE  
         float s33 = dot(tex2D(implicitInputSampler, uv + float2(1.0f / 1024.0f, 1.0f / 768.0f)), lum); // RIGHT  
      
         float t1 = s13 + s33 + (2 * s23) - s11 - (2 * s21) - s31;  
         float t2 = s31 + (2 * s32) + s33 - s11 - (2 * s12) - s13;  
        
         float4 col;  
         
         if (((t1 * t1) + (t2 * t2)) > 0.05) {  
         col = float4(0,0,0,1);  
         } else {  
           col = float4(1,1,1,1);  
         }  
        
         return col;  
      }
      
      // You probably wont to add something like this at the end of the *.fx file to complete it:
      technique OutlinesTechnique  
       {  
         pass Pass1  
         {  
           VertexShader = null;
           PixelShader = compile ps_2_0 main();
         }  
       }  
      

      Note:
      This is untested, just my guess of what you might need. Also remember my examples are for a fixed resolution… which can be extended to support any resolution with a bit of effort (and possibly a higher pixel shader specification than 2.0).

      Hope that helped…

  2. Dear Novak,

    Thank you very much for the response. This is exactly what I need. I just changed the ‘&gt’ to ‘>’ and ignored the compilation part and test it in a pixelshader utility (http://shazzam-tool.com/) and it works well.

    Anyway, WPF is a very cool UI technology by Microsoft which enables us create better and more effective UX. WPF is part of the .Net Framework 3.0 and 3.5. In WPF Service Pack 1 Microsoft added pixel shader support into WPF. Moreover, there is also another technology called Silverlight which is something such as flash which run on PC and Mac inside and outside of a browser (SilverLight 3.0). In Silverlight 3.0 which is now in beta we can use pixel shader, too.

    Thanks again for your help.

    Regards,
    Ali daneshmandi

    • No worries, glad I could help. Wow the .NET framework has come a long way in the last 2 years, shesh. Pixel shaders in web browsers, flip thats awesome! Soon we will use web browsers for everything lol!

    • Not sure I have not kept up with later pixel shader versions. If there is some sort of variable which you can query to obtain the texture dimensions then you can perform a straight forward substitution: replacing the hard coded dimensions with the texture dimensions….

  3. Pingback: XNA 4.0 City Environment Car Driving Simulation | Stahuj cz filmy a MP3 zdarma

Leave a reply to brooknovak Cancel reply