简体   繁体   English

C# - 转换8位或16位灰度原始像素数据

[英]C# - Converting 8-bit or 16-bit grayscale raw pixel data

I need to be able to convert 8-bit or 16-bit grayscale pixel data into a file format that the .NET framework can support. 我需要能够将8位或16位灰度像素数据转换为.NET框架可以支持的文件格式。

The data I have available is the width, height, orientation (bottom-left) and the pixel format as 4096 shades of gray (12-bit resolution) packed in 2 bytes per pixel. 我可用的数据是宽度,高度,方向(左下角)和像素格式为4096灰度(12位分辨率),每像素2个字节。

So for example each pixel ranges from 0 to 4096, and each pixel is 2 bytes. 因此,例如,每个像素的范围从0到4096,并且每个像素是2个字节。

I have already tried using PixelFormat.Format16bppGrayScale with the Bitmap constructor, and it throws a GDI+ exception. 我已经尝试使用PixelFormat.Format16bppGrayScale和Bitmap构造函数,它会引发GDI +异常。 Everything I have read says that this format is not supported and that MSDN is wrong. 我读过的所有内容都说不支持这种格式,并且MSDN错误。

I want to convert this pixel buffer into a .NET Bitmap format (such as Format32bppArgb) with as little image quality loss as possible. 我想将此像素缓冲区转换为.NET位图格式(如Format32bppArgb),尽可能减少图像质量损失。

Anyone know how? 谁知道怎么样?

See the example below, which precomputes a lookup table (LUT) and uses that to convert each pixel. 请参阅下面的示例,该示例预先计算查找表(LUT)并使用它来转换每个像素。 This version covers your 12-bit case; 此版本涵盖您的12位案例; for 8-bit the code is very similar, but it is difficult to generalize across pixel formats. 对于8位代码非常相似,但很难概括像素格式。

A conversion from 12-bit GS to effectively 8-bit GS will lose data. 从12位GS到有效8位GS的转换将丢失数据。 However, you can adjust the LUT table to focus on a smaller range of input values with better contrast (ex. DICOM Window Center/Window Width ). 但是,您可以调整LUT表以聚焦较小范围的输入值,并获得更好的对比度(例如DICOM窗口中心/窗口宽度 )。

class Program
{
    static void Main( string[] args )
    {
        // Test driver - create a Wedge, convert to Bitmap, save to file
        //
        int width = 4095;
        int height = 1200;
        int bits = 12;

        byte[] wedge = Wedge( width, height, bits );

        Bitmap bmp = Convert( wedge, width, height, bits );

        string file = "wedge.png";

        bmp.Save( file );

        Process.Start( file );
    }

    static Bitmap Convert( byte[] input, int width, int height, int bits )
    {
        // Convert byte buffer (2 bytes per pixel) to 32-bit ARGB bitmap

        var bitmap = new Bitmap( width, height, PixelFormat.Format32bppArgb );

        var rect = new Rectangle( 0, 0, width, height );

        var lut = CreateLut( bits );

        var bitmap_data = bitmap.LockBits( rect, ImageLockMode.WriteOnly, bitmap.PixelFormat );

        ConvertCore( width, height, bits, input, bitmap_data, lut );

        bitmap.UnlockBits( bitmap_data );

        return bitmap;
    }

    static unsafe void ConvertCore( int width, int height, int bits, byte[] input, BitmapData output, uint[] lut )
    {
        // Copy pixels from input to output, applying LUT

        ushort mask = (ushort)( ( 1 << bits ) - 1 );

        int in_stride = output.Stride;
        int out_stride = width * 2;

        byte* out_data = (byte*)output.Scan0;

        fixed ( byte* in_data = input )
        {
            for ( int y = 0; y < height; y++ )
            {
                uint* out_row = (uint*)( out_data + ( y * in_stride ) );

                ushort* in_row = (ushort*)( in_data + ( y * out_stride ) );

                for ( int x = 0; x < width; x++ )
                {
                    ushort in_pixel = (ushort)( in_row[ x ] & mask );

                    out_row[ x ] = lut[ in_pixel ];
                }
            }
        }
    }

    static uint[] CreateLut( int bits )
    {
        // Create a linear LUT to convert from grayscale to ARGB

        int max_input = 1 << bits;

        uint[] lut = new uint[ max_input ];

        for ( int i = 0; i < max_input; i++ )
        {
            // map input value to 8-bit range
            //
            byte intensity = (byte)( ( i * 0xFF ) / max_input );

            // create ARGB output value A=255, R=G=B=intensity
            //
            lut[ i ] = (uint)( 0xFF000000L | ( intensity * 0x00010101L ) );
        }

        return lut;
    }

    static byte[] Wedge( int width, int height, int bits )
    {
        // horizontal wedge

        int max = 1 << bits;

        byte[] pixels = new byte[ width * height * 2 ];

        for ( int y = 0; y < height; y++ )
        {
            for ( int x = 0; x < width; x++ )
            {
                int pixel = x % max;

                int addr = ( ( y * width ) + x ) * 2;

                pixels[ addr + 1 ] = (byte)( ( pixel & 0xFF00 ) >> 8 );
                pixels[ addr + 0 ] = (byte)( ( pixel & 0x00FF ) );
            }
        }

        return pixels;
    }
}

Spoof a 16b format & use ColorMatrix to map it correctly before display. 欺骗16b格式并使用ColorMatrix在显示之前正确映射它。

I haven't done performance tests of this approach on Windows, but on other platforms (eg, Android) where I needed efficient memory storage and rapid remapping of different ranges in the 12b or 16b data I've made good use of this technique. 我还没有在Windows上对这种方法进行性能测试,但在其他平台(例如Android)上我需要高效的内存存储并快速重新映射12b或16b数据中的不同范围我已经很好地利用了这种技术。

I tell it my 12/16b grayscale data is really RGB565, so that it's happy serializing, deserializing & other manipulations. 我告诉它我的12 / 16b灰度数据真的是RGB565,所以它很高兴序列化,反序列化和其他操作。 When I need to display I pass it through a ColorMatrix which maps the appropriate window to an 8b grayscale in ARGB8888. 当我需要显示时,我将它传递给ColorMatrix,它将适当的窗口映射到ARGB8888中的8b灰度。

If anyone wants to try this I'll post my mapping algorithm. 如果有人想尝试这个我会发布我的映射算法。

Two possible ways: 两种可能的方式:

  • Use Bitmap constructor pointing to an arbitrary buffer. 使用指向任意缓冲区的Bitmap构造函数 This requires you to keep the buffer around until the Bitmap is disposed, but does prevent unnecessary copying of the bitmap data in memory. 这要求您保留缓冲区直到布置位图,但确实防止在内存中不必要地复制位图数据。
  • LockBits method can be used to get a pointer to the Bitmap's data. LockBits方法可用于获取指向Bitmap数据的指针。 In this case, construct a Bitmap as usual with the desired dimensions and format. 在这种情况下,像往常一样构造一个具有所需尺寸和格式的Bitmap。 Then call LockBits and copy the bitmap data into the buffer. 然后调用LockBits并将位图数据复制到缓冲区中。 This would be slower, but necessary if your data is not in a format that the Bitmap constructor can directly accept and therefore requires some kind of custom conversion. 如果您的数据不是Bitmap构造函数可以直接接受的格式,因此需要某种自定义转换,这会更慢,但是这是必要的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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