Sunday, 4 March 2012

16-bit Grayscale in WPF Pixel Shader BitmapEffect

If you’re looking to use a 16-bit grayscale (Gray16) BitmapSource as an input to a Pixel Shader BitmapEffect – say for brightness / contrast adjustment, you’re likely to hit a problem.
It seems that the 16-bit values are knocked down to 8-bit precision by the time you get the float values in the pixel shader (so a range from 0 to 1, but only 256 distinct values).
But you can instead pack the 16 bits into two of the 8-bit colour channels of an RGB image (an approach suggested by ken_s in the msdn thread Debugging Pixel Shaders for WPF effects.
Create a Bgr32 format WriteableBitmap, and use WritePixels to copy the 16-bit values from a ushort array, with one blank ushort between each pixel. This way the high byte goes into the green channel, the low byte into the blue, and the red and unused channels get zeroed.
In your pixel shader code, you can reconstruct the 16-bit value from the float values passed for green and blue.
float4 main(float2 img : TEXCOORD) : COLOR
{
 float4 gb = tex2D(input, img);
  int high = gb.g * 65280; // Fix by Rick Burke (see comments)
 int low = gb.b * 255;
 int grayVal = high + low;

 // ... 
 // Calculate a 0-1 grayscale value ‘processed’ from grayVal 
 // ... 

 float4 result; 
 result.a = 1; 
 result.r = processed; 
 result.g = processed; 
 result.b = processed; 
 return result; 
}