将位图转换为字节数组

293

在使用C#时,将Windows Bitmap 转换成 byte[],有比保存到临时文件并使用 FileStream 读取结果更好的方法吗?

10个回答

514
有几种方法。 ImageConverter
public static byte[] ImageToByte(Image img)
{
    ImageConverter converter = new ImageConverter();
    return (byte[])converter.ConvertTo(img, typeof(byte[]));
}

这个方案很方便,因为它不需要大量的代码。

内存流

public static byte[] ImageToByte2(Image img)
{
    using (var stream = new MemoryStream())
    {
        img.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
        return stream.ToArray();
    }
}

这个方法与你的做法相同,只是将文件保存到内存中而不是磁盘。尽管需要更多的代码,但你可以选择ImageFormat选项,并且可以轻松地在保存到内存或磁盘之间进行修改。

来源:http://www.vcskicks.com/image-to-byte.php


2
您还可以使用bitmap.Lockbits和Marshal.Copy进行简单快速的复制,而无需任何中间的内存流等。 - deegee
8
请注意,“ImageConverter” 方法将图像保存为 Png 格式,导致文件非常大。 - skolima
8
使用“using”时,无需关闭流。 - I Stand With Russia
2
有人能给我更多关于这个数组结构的信息吗?它是以 x = 0 开始,循环遍历每个 y,然后递增 x 吗?然后像这样存储:[0,0,1,1,1,1,0,0,1,1]? - Raymond
1
ImageConverter isn't .net standard you might use MemoryStream - Alexandre
显示剩余4条评论

119

使用MemoryStream可能会有所帮助。你可以将它放在扩展方法中:

public static class ImageExtensions
{
    public static byte[] ToByteArray(this Image image, ImageFormat format)
    {
        using(MemoryStream ms = new MemoryStream())
        {
            image.Save(ms, format);
            return ms.ToArray();
        }
    }
}

你可以像这样使用它:

var image = new Bitmap(10, 10);
// Draw your image
byte[] arr = image.ToByteArray(ImageFormat.Bmp);

对于prestomanifto的答案,我在ImageConverter方面持有部分不同的看法。不要使用ImageConverter。虽然技术上没有问题,但它使用对象的装箱/拆箱让我想起了.NET框架的旧时黑暗地带,并且在图像处理中使用它并不理想(至少将其转换为byte[]是过度设计),特别是考虑到以下事项。

我查看了.Net框架中使用的ImageConverter代码,它在内部使用几乎与我上面提供的代码完全相同的代码。它创建一个新的MemoryStream,以您提供的任何格式保存Bitmap,并返回数组。 通过使用MemoryStream跳过创建ImageConverter类的额外开销。


很好,就这样!虽然我想你会想要处理MemoryStream,不过可以更新一下吗? - Jeremy McGee
我已经更新了我的答案,并讨论了为什么不要使用ImageConverter,就像你选择的答案建议的那样,还添加了处理的内容。 - Christopher Currens
很好的扩展方法使用,我喜欢它! - Dude Pascalou
3
对于您研究ImageConverter并汇报结果,我给予+1的肯定。但我不认为您发现的内容支持“不要使用ImageConverter”的说法。实际上,它提供了有用的服务,例如将字节数组转换为图像,可以设置图像分辨率(dpi)等。而且,“创建ImageConverter类的额外开销”可能是可以忽略的,并且只需要执行一次,无论您使用它多少次。 - RenniePet
1
我发现ImageConverter也很有用,因为它可以自动确定图像的类型 - 例如,如果您有一个图像的字节数组,但不知道格式 - 您可以尝试读取标头并从那里获取提示,但是...使用(Image img =(Image)myImgConverter.ConvertFrom(byteImage)),然后检查img.PixelFormat等就更容易了。- 当然,这取决于您想要做什么。 - hello_earth
显示剩余4条评论

59

您也可以直接使用Marshal.Copy来复制位图数据,不需要使用中间的内存流等,这样可以快速地进行内存复制。这应该适用于24位和32位位图。

public static byte[] BitmapToByteArray(Bitmap bitmap)
{

    BitmapData bmpdata = null;

    try
    {
        bmpdata = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
        int numbytes = bmpdata.Stride * bitmap.Height;
        byte[] bytedata = new byte[numbytes];
        IntPtr ptr = bmpdata.Scan0;

        Marshal.Copy(ptr, bytedata, 0, numbytes);

        return bytedata;
    }
    finally
    {
        if (bmpdata != null)
            bitmap.UnlockBits(bmpdata);
    }

}

.


2
嗨,我使用了这种方法,但是我无法将这个字节数组再次转换为图像。Bitmap bitmap1 = new Bitmap(new MemoryStream(array)); 它抛出了一个无效参数的异常。有什么错误吗? - Howard
1
你好,评论区对我来说太小了,无法发布完整的干净代码。最简单的方法是将数组固定:GCHandle handle = GCHandle.Alloc(bufferarray, GCHandleType.Pinned); 获取数组指针 IntPtr iptr = Marshal.UnsafeAddrOfPinnedArrayElement(bufferarray, 0); 然后使用 IntPtr 创建新的位图:bitmap = new Bitmap(width, height, (width * 4), PixelFormat.Format32bppArgb, iptr); 但是你必须使用适当的位图创建参数来创建你的位图格式。一定要清理:iptr = IntPtr.Zero; handle.Free(); - deegee
4
什么是bug?许多程序员,包括我自己,都使用这种代码风格,而且它运行得很好。 - deegee
6
我建议您查阅位图步幅(bitmap stride)和位图宽度(bitmap width)的区别。这不是我的代码中的 bug,而是 Windows 处理位图的方式。步幅可能会在每行(宽度)末尾包含额外的数据,以便使每行都以 32 位边界结束和开始,以实现内存对齐和性能优化。 - deegee
6
这不是一个错误。http://msdn.microsoft.com/en-us/library/system.drawing.imaging.bitmapdata.stride%28v=vs.110%29.aspx - deegee
显示剩余9条评论

19

图像上没有Save方法。 - Prescott Chartier
@PrescottChartier 上面的例子假设您正在使用从System.Drawing.Image派生的类型(请参见:https://learn.microsoft.com/en-us/dotnet/api/system.drawing.image.save?view=netframework-4.7.2#System_Drawing_Image_Save_System_IO_Stream_System_Drawing_Imaging_ImageFormat_)工作。 - Chris Baxter
是的,我也遇到了“System.Drawing.Image不存在”的问题。所以...不行 :( - Prescott Chartier
您可以看到我在尝试做什么:https://stackoverflow.com/questions/55084620/android-xamarin-retrieve-the-image-stored-in-an-imageview/55084716#55084716 - Prescott Chartier
此解决方案还包含字节数组中的元数据。因此,它可以用于填充MemoryStream并再次创建相同的位图:using (var ms = new MemoryStream(previewBinary)) bitmap = (Bitmap)Image.FromStream(ms); - huha

14

使用MemoryStream代替FileStream,像这样:

MemoryStream ms = new MemoryStream();
bmp.Save (ms, ImageFormat.Jpeg);
byte[] bmpBytes = ms.ToArray();

你可能想要使用 ToArray,而不是 GetBuffer - vcsjones
GetBuffer 返回一个无符号字节数组 (byte array)。 - Jeff Reddy
5
该字节数组可能包含非图像部分的无用数据。根据文档:请注意,缓冲区包含已分配但未使用的字节。例如,如果将字符串“test”写入MemoryStream对象,则从GetBuffer返回的缓冲区的长度为256,而不是4,其中252个字节未使用。要仅获取缓冲区中的数据,请使用ToArray方法。 因此,从GetBuffer返回的字节数组将返回图像和未使用的字节,这可能导致图像损坏。 - vcsjones
这种方法将图像保存为带有默认压缩设置的jpeg格式,这将引入压缩伪影,可能会明显降低您的图像质量。 - Richard Ev
bmp 上没有 Save 方法。 - Prescott Chartier

13

更简单:

return (byte[])System.ComponentModel.TypeDescriptor.GetConverter(pImagen).ConvertTo(pImagen, typeof(byte[]))

3
谢谢 - 运行得很好,但如果您添加了关于它如何工作的解释,答案将会更好。 - Igand

11

尝试以下方法:

MemoryStream stream = new MemoryStream();
Bitmap bitmap = new Bitmap();
bitmap.Save(stream, ImageFormat.Jpeg);

byte[] byteArray = stream.GetBuffer();

请确保使用以下内容:

System.Drawing & using System.Drawing.Imaging;

1
这种方法将图像以默认压缩设置保存为jpeg格式,这将引入压缩伪影,可能会明显降低您的图像质量。 - Richard Ev
位图上没有保存方法。 - Prescott Chartier

8
我相信你可以简单地这样做:

我相信您可以轻松实现:

ImageConverter converter = new ImageConverter();
var bytes = (byte[])converter.ConvertTo(img, typeof(byte[]));

7
MemoryStream ms = new MemoryStream();
yourBitmap.Save(ms, ImageFormat.Bmp);
byte[] bitmapData = ms.ToArray();

-4

非常简单,只需在一行中使用:

byte[] imgdata = File.ReadAllBytes(@"C:\download.png");

1
OP说他对这个选项不感兴趣。 - Mauro Sampietro

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