【问题标题】:Turning int array into BMP in C#在 C# 中将 int 数组转换为 BMP
【发布时间】:2018-03-02 04:17:23
【问题描述】:

在 C# 中将灰度整数数组 (int32[,]) 转换为 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)pixels[x, y] 但不起作用

【问题讨论】:

  • 你的代码在哪里?

标签: c# visual-studio bitmap


【解决方案1】:

我实际上是wrote a universally usable BuildImagefunction here on SO to build an image out of a byte array,但当然,您不是从字节数组开始,而是从二维Int32 数组开始。绕过它的简单方法就是提前对其进行改造。

您的字节为整数数组是一件相当奇怪的事情。如果这是从灰度图像中读取的,我宁愿假设这是 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更改为只是填充。

【讨论】:

  • 数组是 ASCOM Telescope.ImageArray 属性的输出。它是 32 位,因为 CCD 传感器可以是 32 位,不幸的是我的是 8 位。顺便说一句,您的代码似乎运行良好,感谢您的贡献。
  • 很高兴我能帮上忙。人们通常不太容易理解索引彩色图像的概念。那么,我假设那些 32 位的只是更准确呢?这实际上使处理 8 位的更容易; .Net System.Drawing 类无法处理更高质量的灰度,因此您最终不得不将其划分为 8 位。
【解决方案2】:

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

    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;
         });
    }

仍然不确定为什么用Format8bppIndexed 代替Format24bppRgb 不起作用。有什么线索吗?

【讨论】:

  • Format8bit 需要调色板。你需要专门用 0 到 255 的灰度值填充它。
  • 另外,很明显,你只需要填一个字节,而不是三个,所以你只需要填一个row[x] = grayShade8bit;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-06
  • 2013-05-02
  • 1970-01-01
  • 2021-07-31
  • 1970-01-01
  • 2014-05-18
  • 1970-01-01
相关资源
最近更新 更多