简体   繁体   中英

Loading and displaying a 16 (12) bit grayscale png into a PictureBox

I'm using a framework for some camera hardware called IDS Peak and we are receiving 16 bit grayscale images back from the framework, the framework itself can write the files to disk as PNGs and that's all good and well, but how do I display them in a PictureBox in Winforms?

Windows Bitmap does not support 16 bit grayscale so the following code throws a 'Parameter is not valid.' System.ArgumentException

var image = new Bitmap(width, height, stride, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale, iplImg.Data());

iplImg.Data() here is an IntPtr to the bespoke Image format of the framework.

Considering Windows Bitmap does not support the format, and I can write the files using the framework to PNGs, how can I do one of the following:

  1. Convert to a different object type other than Bitmap to display directly in Winforms without reading from the files.
  2. Load the 16-bit grayscale PNG files into the PictureBox control (or any other control type, it doesn't have to be a PictureBox ).

(1) is preferable as it doesn't require file IO but if (2) is the only possibility that's completely fine as I need to both save and display them anyway but (1) only requires a write operation and not a secondary read.

The files before writing to disc are actually monochrome with 12 bits per pixel, packed.

While it is possible to display 16-bit images, for example by hosting a wpf control in winforms, you probably want to apply a windowing function to reduce the image to 8 bit before display.

So lets use unsafe code and pointers for speed:

            var bitmapData = myBitmap.LockBits(
                new Rectangle(0, 0, myBitmap.Width, myBitmap.Height),
                ImageLockMode.ReadWrite,
                myBitmap.PixelFormat);

            try
            {
                 var ptr= (byte*)bitmapData.Scan0;
                 var stride = bitmapData.Stride;
                 var width = bitmapData.Width;
                 var height= bitmapData.Height;
                 // Conversion Code
            }
            finally
            {
                myBitmap.UnlockBits(bitmapData);
            }

or using wpf image classes, that generally have better 16-bit support:

var myBitmap= new WriteableBitmap(new BitmapImage(new Uri("myBitmap.jpg", UriKind.Relative)));
writeableBitmap.Lock();
try{
     var ptr = (byte*)myBitmap.BackBuffer;
     ...
}
finally
{
      myBitmap.Unlock();
}

To loop over all the pixels you would use a double loop:

for (int y = 0; y < height; y++)
{
    var row = (ushort*)(ptr+ y * stride);
    for (int x = 0; x < width; x++)
    {
        var pixelValue = row[x];
        // Scaling code
    }
}

And to scale the value you could use a linear scaling between the min and max values to the 0-255 range of a byte

var slope = (byte.MaxValue + 1f) / (maxUshortValyue - minUshortValue);
var scaled = (int)(((pixelValue + 0.5f - minUshortValue) * slope)) ;
scaled = scaled > byte.MaxValue ? byte.MaxValue: scaled;
scaled = scaled < 0 ? 0: scaled;
var byteValue = (byte)scaled;

The maxUshortValyue / minUshortValue would either be computed from the max/min value of the image, or configured by the user. You would also need to create a target image in order to write down the result into a target 8-bit grayscale bitmap to be displayed, or write down the same value for each color channel in a color image.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM