字节数组转换为图像

135

我想将一个字节数组转换为图像。

这是从数据库中获取字节数组的代码:

public void Get_Finger_print()
{
    try
    {
        using (SqlConnection thisConnection = new SqlConnection(@"Data Source=" + System.Environment.MachineName + "\\SQLEXPRESS;Initial Catalog=Image_Scanning;Integrated Security=SSPI "))
        {
            thisConnection.Open();
            string query = "select pic from Image_tbl";// where Name='" + name + "'";
            SqlCommand cmd = new SqlCommand(query, thisConnection);
            byte[] image =(byte[]) cmd.ExecuteScalar();
            Image newImage = byteArrayToImage(image);
            Picture.Image = newImage;
            //return image;
        }
    }
    catch (Exception) { }
    //return null;
}

我的转换代码:

public Image byteArrayToImage(byte[] byteArrayIn)
{
    try
    {
        MemoryStream ms = new MemoryStream(byteArrayIn,0,byteArrayIn.Length);
        ms.Write(byteArrayIn, 0, byteArrayIn.Length);
        returnImage = Image.FromStream(ms,true);//Exception occurs here
    }
    catch { }
    return returnImage;
}
当我到达一行带有注释的代码时,就会出现下面的异常:参数无效。 我该如何修复引起此异常的问题?

你检查过查询中的图像字节是否有效吗?你可以使用File.WriteAllBytes("myimage.jpg", byteArrayIn)进行验证。 - Holstebroe
你想要接受其中一个答案吗? 当有人回答我的问题时我该怎么做? - Andrew Morton
14个回答

136
你向内存流写入了两次数据,并且在使用完毕后没有处理该流。此外,你要求图像解码器应用嵌入式颜色校正。
尝试使用以下方法替代:
using (var ms = new MemoryStream(byteArrayIn))
{
    return Image.FromStream(ms);
}

1
在初始化后,您还可以显式地将内存流设置为不可写:new MemoryStream(byteArrayIn, false)。 - Holstebroe
38
这违反了 MSDN 中 Image.FromStream() 的规定,其中写道“您必须在图像的生命周期内保持流处于打开状态。”另请参阅 https://dev59.com/4nA75IYBdhLWcg3wdIp0 - RenniePet

102

也许我少了什么,但对我来说,这个一行代码可以很好地处理包含JPEG文件图像的字节数组。

Image x = (Bitmap)((new ImageConverter()).ConvertFrom(jpegByteArray));

编辑:

请参见此处的更新版本:如何将图像转换为字节数组


2
啊,谢谢!终于有一个好答案了。为什么会有这么多关于内存流的回答呢?它给我带来了很多问题。非常感谢! - Julian50
1
好的答案!这可以用在一个单独的函数中,而所有其他使用MemoryStream的提议都不行(流必须在图像的生命周期内保持打开状态)。 - A_L
这绝对是互联网上关于在C#/.NET中保存和转换图像为字节的最佳答案。 几乎每个其他答案都涉及到MemoryStream,并且荒谬地要求在Image对象的生命周期内保持它们打开,真是让人摇头! - undefined

31
public Image byteArrayToImage(byte[] bytesArr)
{
    using (MemoryStream memstr = new MemoryStream(bytesArr))
    {
        Image img = Image.FromStream(memstr);
        return img;
    }
}

虽然这通常不是一个好主意,但我在Memory Stream之前放置了GC.Collect()。当我将大量的大型图形文件作为字节数组预加载到内存中,然后在查看期间将它们转换为图像时,我会耗尽内存。 - Kayot
2
这并没有为之前发布的回答(https://dev59.com/6Wox5IYBdhLWcg3wf0RP#9174069)增添任何内容,并且存在着同样的问题,即过早地释放了流。 - GSerg

13

我想指出@isaias-b提供的解决方案存在一个错误。

该解决方案假定stride等于行长度。但这并非总是正确的。由于GDI执行的内存对齐,stride可能大于行长度。这必须考虑在内。否则将生成无效的移位图像。每行中的填充字节将被忽略。

stride是单个像素行(扫描线)的宽度,向上舍入到四字节边界。

修复后的代码:

using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

public static class ImageExtensions
{
    public static Image ImageFromRawBgraArray(this byte[] arr, int width, int height, PixelFormat pixelFormat)
    {
        var output = new Bitmap(width, height, pixelFormat);
        var rect = new Rectangle(0, 0, width, height);
        var bmpData = output.LockBits(rect, ImageLockMode.ReadWrite, output.PixelFormat);

        // Row-by-row copy
        var arrRowLength = width * Image.GetPixelFormatSize(output.PixelFormat) / 8;
        var ptr = bmpData.Scan0;
        for (var i = 0; i < height; i++)
        {
            Marshal.Copy(arr, i * arrRowLength, ptr, arrRowLength);
            ptr += bmpData.Stride;
        }

        output.UnlockBits(bmpData);
        return output;
    }
}
为了说明可能会导致什么情况,让我们生成一张101x101的PixelFormat.Format24bppRgb渐变图像:
var width = 101;
var height = 101;
var gradient = new byte[width * height * 3 /* bytes per pixel */];
for (int i = 0, pixel = 0; i < gradient.Length; i++, pixel = i / 3)
{
    var x = pixel % height;
    var y = (pixel - x) / width;
    gradient[i] = (byte)((x / (double)(width - 1) + y / (double)(height - 1)) / 2d * 255);
}

如果我们将整个数组原样复制到bmpData.Scan0指向的地址,那么我们将得到以下图像。图像偏移是因为一部分图像被写入了填充字节,这些填充字节被忽略了。也正因为如此,最后一行是不完整的:

stride ignored

但是,如果我们逐行复制,并通过bmpData.Stride值来偏移目标指针,就可以生成有效的图像:

stride taken into account

步幅也可以是负数:

  

如果步幅是正数,则位图是自上而下的。如果步幅为负,则位图是自下而上的。

但我没有处理过这样的图像,这超出了我的说明范围。

相关答案:C# - RGB Buffer from Bitmap different from C++


10

所有给出的答案都假设字节数组中包含已知文件格式表示的数据,例如:gif、png或jpg。但是我最近遇到了一个问题,尝试将包含线性BGRA信息的byte[]高效地转换为Image对象。以下代码使用Bitmap对象解决了这个问题。

using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
public static class Extensions
{
    public static Image ImageFromRawBgraArray(
        this byte[] arr, int width, int height)
    {
        var output = new Bitmap(width, height);
        var rect = new Rectangle(0, 0, width, height);
        var bmpData = output.LockBits(rect, 
            ImageLockMode.ReadWrite, output.PixelFormat);
        var ptr = bmpData.Scan0;
        Marshal.Copy(arr, 0, ptr, arr.Length);
        output.UnlockBits(bmpData);
        return output;
    }
}

这是一个略微变形的解决方案,它最初发布在这个网站上。


作为MSDN的参考:Bitmap(Int32, Int32):“此构造函数创建一个PixelFormat枚举值为Format32bppArgb的位图。”这意味着byte[0]是蓝色,byte[1]是绿色,byte[2]是红色,byte[3]是alpha,byte[4]是蓝色,以此类推。 - kbridge4096
3
感谢您添加所有必要的“using”。大多数人都会忘记,这会让找到它们变得很麻烦。 - Casey Crookston
1
哇,你真是救了我啊,谢谢! - Vulovic Vukasin

7

简单来说:

Image.FromStream(new MemoryStream(byteArrayIn));

工作正常 :-). - Thulasiram

6
以下是简单的方法,您可以使用图像的FromStream方法来实现这一点,只需记得使用System.Drawing即可。
// using image object not file 
public byte[] imageToByteArray(Image imageIn)
{
    MemoryStream ms = new MemoryStream();
    imageIn.Save(ms,System.Drawing.Imaging.ImageFormat.Gif);
    return ms.ToArray();
}

public Image byteArrayToImage(byte[] byteArrayIn)
{
    MemoryStream ms = new MemoryStream(byteArrayIn);
    Image returnImage = Image.FromStream(ms);
    return returnImage;
}

6

尝试 (更新)

MemoryStream ms = new MemoryStream(byteArrayIn,0,byteArrayIn.Length);
ms.Position = 0; // this is important
returnImage = Image.FromStream(ms,true);

2
在使用相同的字节数组初始化内存流之后,将字节数组写入内存流并没有多大意义。实际上,我不确定MemoryStream是否允许在构造函数中指定的长度之外进行写入。 - Holstebroe

3

一句话描述:

Image bmp = (Bitmap)((new ImageConverter()).ConvertFrom(imageBytes));

2

这受Holstebroe的回答启发,加上这里的评论:从字节数组中获取图像对象

Bitmap newBitmap;
using (MemoryStream memoryStream = new MemoryStream(byteArrayIn))
    using (Image newImage = Image.FromStream(memoryStream))
        newBitmap = new Bitmap(newImage);
return newBitmap;

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