如何从字节数组创建位图?

99

我搜索了所有关于字节数组的问题,但总是失败。我从未编写过C#代码,我是这方面的新手。您能帮助我如何从字节数组创建图像文件吗?

这是我的函数,它将字节存储在名为imageData的数组中。

public void imageReady( byte[] imageData, int fWidth, int fHeight))
6个回答

140

你需要将那些 字节 放入一个 MemoryStream 中:

Bitmap bmp;
using (var ms = new MemoryStream(imageData))
{
    bmp = new Bitmap(ms);
}

使用Bitmap(Stream stream)构造函数的重载。

更新:请注意,根据文档和我一直在阅读的源代码,在以下情况下会抛出ArgumentException

stream does not contain image data or is null.
-or-
stream contains a PNG image file with a single dimension greater than 65,535 pixels.

1
那么仅仅编写 bmp.Save(c:\image.jpg); 就足够了吗? - goGud
3
我收到了与@mario的答案相同的错误消息:在System.Drawing.dll中发生了未处理的类型为“System.ArgumentException”的异常,附加信息:参数无效。 - goGud
1
@goGud 也许你应该向我们展示一下从图像创建字节数组的代码。 - Mario S
13
不可以!不要处理这个数据流!从Bitmap(Stream)中得知:"必须在Bitmap对象的生命周期内保持该流处于打开状态。" - piedar
4
正如 @piedar 指出的那样,在位图被销毁之后才应该关闭/释放流。然而,如果你正在使用 MemoryStream,那么你不必担心关闭它,因为 MemoryStream 在被销毁时实际上并不会执行任何操作。 System.Drawing.ImageConverter.ConvertFrom 方法利用了这个事实,所以似乎可以做出这样的推断。因此,只需使用 var bmp = new Bitmap(new MemoryStream(imageData)); 即可。 - Steven Doggart
显示剩余13条评论

35
谢谢你的帮助。我觉得提供的所有答案都可能有效。然而,我的ByteArray包含原始字节。所以那些解决方案对我的代码没有起作用。
不过,我找到了一个解决办法。也许它能帮助其他遇到同样问题的人。
static byte[] PadLines(byte[] bytes, int rows, int columns) {
   int currentStride = columns; // 3
   int newStride = columns;  // 4
   byte[] newBytes = new byte[newStride * rows];
   for (int i = 0; i < rows; i++)
       Buffer.BlockCopy(bytes, currentStride * i, newBytes, newStride * i, currentStride);
   return newBytes;
 }

 int columns = imageWidth;
 int rows = imageHeight;
 int stride = columns;
 byte[] newbytes = PadLines(imageData, rows, columns);

 Bitmap im = new Bitmap(columns, rows, stride, 
          PixelFormat.Format8bppIndexed, 
          Marshal.UnsafeAddrOfPinnedArrayElement(newbytes, 0));

 im.Save("C:\\Users\\musa\\Documents\\Hobby\\image21.bmp");

这适用于8位256 bpp(Format8bppIndexed)。如果您的图像具有不同的格式,您应该更改PixelFormat
我现在还有一个颜色问题。一旦解决了这个问题,我会为其他用户编辑我的答案。
*PS = 我对步幅值不确定,但对于8位来说,它应该等于列数。
此外,这个函数对我有效...它将8位灰度图像复制到32位布局中。
public void SaveBitmap(string fileName, int width, int height, byte[] imageData)
        {
            
            byte[] data = new byte[width * height * 4];

            int o = 0;

            for (int i = 0; i < width * height; i++)
            {
                byte value = imageData[i];

                
                data[o++] = value;
                data[o++] = value;
                data[o++] = value;
                data[o++] = 0; 
            }

            unsafe
            {
                fixed (byte* ptr = data)
                {
                    
                    using (Bitmap image = new Bitmap(width, height, width * 4,
                                PixelFormat.Format32bppRgb, new IntPtr(ptr)))
                    {
                        
                        image.Save(Path.ChangeExtension(fileName, ".jpg"));
                    }
                }
            }
        }

5
Hi goGud,您不需要填充数据,“Format32bppRgb”每个像素使用4个字节(或者如名称所示每个像素使用32位,8位等于1个字节)。实际上,“Format32bppRgb”是RGBX格式,而您的数据似乎是存储为“RGB”,因此每个像素为3个字节。尝试使用“Format24bppRGB”,这将是每个像素3个字节,而您的步幅将是宽度×3。最后值得一提的是IntPtr部分,如果您想使位图持久存在,您将需要保留一个单独的引用。建议查看此帖子:https://dev59.com/-2w15IYBdhLWcg3wLIo2 - chrispepper1989
内部位图的宽度可以被四整除。步幅通常是位深度乘以宽度,但如果不能被四整除,则进行填充。 - John Lord
当我尝试绘制它时,出现了以下错误::无法从具有索引像素格式的图像创建Graphics对象。 (参数'image') 在System.Drawing.Graphics.FromImage(Image image)处 - john k

21

可以很容易:

var ms = new MemoryStream(imageData);
System.Drawing.Image image = Image.FromStream(ms);

image.Save("c:\\image.jpg");

测试一下:

byte[] imageData;

// Create the byte array.
var originalImage = Image.FromFile(@"C:\original.jpg");
using (var ms = new MemoryStream())
{
    originalImage.Save(ms, ImageFormat.Jpeg);
    imageData = ms.ToArray();
}

// Convert back to image.
using (var ms = new MemoryStream(imageData))
{
    Image image = Image.FromStream(ms);
    image.Save(@"C:\newImage.jpg");
}

1
+1 表示您展示了使用 static 访问器的另一种方法,但是是的,您需要将 MemoryStream 包装在 using 中。 - Mike Perrenoud
1
@goGud 我已经更新了答案。System.Windows.Controls.Image 是用来显示图像的控件。在这里,使用的是 System.Drawing.Image 类。 - Mario S
@goGud 看起来你传入到 Image.FromStream 方法中的字节数组并不是一张有效的图像。 - Mario S
@Mario,它有一个字节数组,其中包含0-99999,我认为我需要使用8位,256 bppi。 - goGud
2
问题在于它不是像问题中那样的原始RGB流。为了检索数据,没有给出任何维度,它保存图像就像任何对象都可以保存一样,其中对象本身已经是一个带有化妆品的图像(例如尺寸像素格式等)。 - Peter
显示剩余3条评论

7
此外,您可以将字节数组简单地转换为位图
var bmp = new Bitmap(new MemoryStream(imgByte));

您也可以直接从文件路径获取Bitmap

Bitmap bmp = new Bitmap(Image.FromFile(filePath));

1
错误 ArgumentException:参数无效。 - john k

2

这对我很有帮助:https://www.tek-tips.com/viewthread.cfm?qid=1264492(参考答案)

我的理解是:

  • 我有一个包含像素数据的字节数组,例如以RGB格式(24位/像素)的形式呈现
  • 我想从这些原始像素数据中创建一个位图

这段代码对我有用:

int width = ...;
int height = ...;
byte[] pixelArray = new byte[] {
  // Creation of the actual data is not in the scope of this answer
};

Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppRgb);

// Create a BitmapData and lock all pixels to be written 
BitmapData bmpData = bmp.LockBits(
                    new Rectangle(0, 0, bmp.Width, bmp.Height),
                    ImageLockMode.WriteOnly, bmp.PixelFormat);
// Copy the data from the byte array into BitmapData.Scan0
Marshal.Copy(pixelArray, 0, bmpData.Scan0, pixelArray.Length);

// Unlock the pixels
bmp.UnlockBits(bmpData);

// Do something with your image, e.g. save it to disc
bmp.Save("c:\\temp\\mybmp.bmp", ImageFormat.Bmp);

0

根据被接受的答案,OP想要将imageData字节数组解释为像素缓冲区,而不是最受欢迎的答案建议的已编码位图流。虽然它能够运行,但其中包含了许多副本,以及调色板问题("现在颜色有问题")。

实际上,我恰好有一个绘画库专门用于此目的(还有其他用途)。平台无关的核心库允许您将任何原始类型数组解释为位图数据:interpret

// Unlike in the accepted answer, no extra buffer allocation or
// array copy happens in the background. Note that we can specify
// a palette for the indexed format so the colors will be interpreted correctly
using var myBitmap = BitmapDataFactory.CreateBitmapData(imageData, new Size(fWidth, fHeight),
    stride: fWidth, // stride is same as width because of the 8bpp pixel format
    pixelFormat: KnownPixelFormat.Format8bppIndexed,
    palette: Palette.Grayscale256());

myBitmap现在是一个IReadWriteBitmapData实例,允许进行许多操作(只需查看可用的扩展方法)。它还提供了一个相当快速的SetPixel方法,它尊重调色板,因此在这种特定情况下将任何颜色转换为灰度。但如果您知道实际的像素格式,还可以使用WriteRaw<T>方法直接访问像素。

如果您使用技术特定的包,例如GDI+WPF,那么您可以将缓冲区转换为已知的位图类型,例如System.Drawing.Bitmap System.Windows.Media.WriteableBitmap
// the accepted answer creates two bitmaps due to the color problems where
// the 2nd one is a 32 bpp image. This solution is much faster, simpler, it avoids
// unnecessary allocations and uses parallel processing internally if possible
var systemBitmap = myBitmap.ToBitmap(); // or ToBitmapAsync, ToWriteableBitmap, etc.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接