在C#中将int数组转换为BMP

时间:2018-03-01 09:19:52

标签: c# visual-studio bitmap

我在将整个灰度数组的int(int32 [,])转换为C#中的BMP格式时遇到了问题。 我尝试在阵列中循环以在BMP中设置像素颜色,它确实有效,但最终变得非常慢并且实际上无法使用。 我做了很多谷歌搜索,但我找不到我的问题的答案。 我需要实时将该图像放在PictureBox中,因此该方法需要快速。

相关讨论here

编辑:数组的深度为8位,但存储为int32

Edit2:刚刚找到这段代码

    private unsafe Task<Bitmap> BitmapFromArray(Int32[,] pixels, int width, int height)
    {
        return Task.Run(() =>
         {
             Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
             BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
             for (int y = 0; y < height; y++)
             {
                 byte* row = (byte*)bitmapData.Scan0 + bitmapData.Stride * y;
                 for (int x = 0; x < width; x++)
                 {
                     byte grayShade8bit = (byte)(pixels[x, y] >> 4);
                     row[x * 3 + 0] = grayShade8bit;
                     row[x * 3 + 1] = grayShade8bit;
                     row[x * 3 + 2] = grayShade8bit;
                 }
             }
             bitmap.UnlockBits(bitmapData);
             return bitmap;
         });
    }

似乎工作得足够快但图像几乎是黑色的。如果我移除相机的顶部,图像应该是完全白色的,但它只显示一个非常深的灰色。我猜它将像素值解释为32位,而不是8位。然后尝试投射(ushort)像素[x,y],但没有工作

2 个答案:

答案 0 :(得分:2)

我实际上wrote a universally usable BuildImagefunction here on SO to build an image out of a byte array,但当然,你不是从字节数组开始,而是从一个二维Int32数组开始。解决它的简单方法就是提前改变它。

你的byte-as-integer数组是一个相当奇怪的事情。如果从灰度图像中读取这个,我宁愿假设这是32位ARGB数据,而你只是使用每个值的最低分量(这将是蓝色的),但是如果将值降低4位产生了一致的黑暗价值观我倾向于接受你的话;否则下一个颜色成分(绿色)的位会渗入,也会产生明亮的颜色。

无论如何,除了沉思和猜测,这是我的实际答案。

您可能认为每个值在倾注到8位图像时只是亮度,但这实际上是假的System.Drawing像素格式中没有特定类型表示8位灰度,而8位图像调色,这意味着图像上的每个值都指的是调色板。因此,要实际制作一个8位灰度图像,其中您的字节值指示像素的亮度,您需要明确定义一个调色板,其中调色板上0到255的索引包含来自(0,0的灰色) 0)到(255,255,255)。当然,这很容易生成。

此代码将您的数组转换为8位图像。它使用前面提到的BuildImage函数。请注意,该函数使用 no 不安全代码。 Marshal.Copy的使用意味着永远不会直接处理原始指针,从而使代码得到完全管理。

public static Bitmap FromTwoDimIntArrayGray(Int32[,] data)
{
    // Transform 2-dimensional Int32 array to 1-byte-per-pixel byte array
    Int32 width = data.GetLength(0);
    Int32 height = data.GetLength(1);
    Int32 byteIndex = 0;
    Byte[] dataBytes = new Byte[height * width];
    for (Int32 y = 0; y < height; y++)
    {
        for (Int32 x = 0; x < width; x++)
        {
            // logical AND to be 100% sure the int32 value fits inside
            // the byte even if it contains more data (like, full ARGB).
            dataBytes[byteIndex] = (Byte)(((UInt32)data[x, y]) & 0xFF);
            // More efficient than multiplying
            byteIndex++;
        }
    }
    // generate palette
    Color[] palette = new Color[256];
    for (Int32 b = 0; i < 256; b++)
        palette[b] = Color.FromArgb(b, b, b);
    // Build image
    return BuildImage(dataBytes, width, height, width, PixelFormat.Format8bppIndexed, palette, null);
}

注意,即使整数完整的ARGB值,上面的代码仍然可以完全相同;如果你只使用整数内四个字节中的最低字节,就像我说的那样,那只是整个ARGB整数的蓝色分量。如果图像是灰度图像,则所有三个颜色分量应该相同,因此您仍然会得到相同的结果。

假设您发现自己使用的是同一种字节数组,其中整数实际上包含完整的32bpp ARGB数据,您必须移出所有四个字节值,并且不会生成灰色调色板,但除此之外,代码将非常相似。只是,每X次迭代处理4个字节。

    public static Bitmap fromTwoDimIntArrayGray(Int32[,] data)
    {
        Int32 width = data.GetLength(0);
        Int32 height = data.GetLength(1);
        Int32 stride = width * 4;
        Int32 byteIndex = 0;
        Byte[] dataBytes = new Byte[height * stride];
        for (Int32 y = 0; y < height; y++)
        {
            for (Int32 x = 0; x < width; x++)
            {
                // UInt32 0xAARRGGBB = Byte[] { BB, GG, RR, AA }
                UInt32 val = (UInt32)data[x, y];
                // This code clears out everything but a specific part of the value
                // and then shifts the remaining piece down to the lowest byte
                dataBytes[byteIndex + 0] = (Byte)(val & 0x000000FF); // B
                dataBytes[byteIndex + 1] = (Byte)((val & 0x0000FF00) >> 08); // G
                dataBytes[byteIndex + 2] = (Byte)((val & 0x00FF0000) >> 16); // R
                dataBytes[byteIndex + 3] = (Byte)((val & 0xFF000000) >> 24); // A
                // More efficient than multiplying
                byteIndex+=4;
            }
        }
        return BuildImage(dataBytes, width, height, stride, PixelFormat.Format32bppArgb, null, null);
    }

当然,如果你想要这个没有透明度,你可以像你一样使用三个字节,或者只是在最后一次调用PixelFormat.Format32bppArgb时更改PixelFormat.Format32bppRgb,这会更改第四个字符的含义从alpha到填充的字节。

答案 1 :(得分:0)

解决了(必须删除四位移位):

    private unsafe Task<Bitmap> BitmapFromArray(Int32[,] pixels, int width, int height)
    {
        return Task.Run(() =>
         {
             Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
             BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
             for (int y = 0; y < height; y++)
             {
                 byte* row = (byte*)bitmapData.Scan0 + bitmapData.Stride * y;
                 for (int x = 0; x < width; x++)
                 {
                     byte grayShade8bit = (byte)(pixels[x, y]);
                     row[x * 3 + 0] = grayShade8bit;
                     row[x * 3 + 1] = grayShade8bit;
                     row[x * 3 + 2] = grayShade8bit;
                 }
             }
             bitmap.UnlockBits(bitmapData);
             return bitmap;
         });
    }

仍然不确定为什么用Format24bppRgb取代Format8bppIndexed并不起作用。任何线索?