目录

C#(WPF)-图片相关数据类型之间转换

常见的图片数据类型:

  • **byte[ ]:**字节数组,可能有两种情况:1.将图片文件读取到内存后得到的字节数组,包括图片文件的文件头等 。2.图片的像素数据,比如 RGB 数据。
  • **Stream:**数据流,比如 MemoryStreamFileStream等,一般和字节数组可以轻易地转换。
  • **Bitmap:**WinForm 中的图片数据类型(基于 GDI +),命名空间是 System.Drawing
  • **BitmapImage:**WPF中的图片数据类型,命名空间是 System.Windows.Media.Imaging,常用于 Image 控件的 Source 属性 (是 ImageSource 类型)
  • **BitmapSoure:**WPF中的图片数据类型,命名空间是 System.Windows.Media,是 BitmapImage 的基类。
  • 其它一些来自第三方库的图片类型。

将图片文件路径转为 BitmapImage

如果知道图片的链接(可以是本地链接或网址),并且想让 Image 控件显示这个图片:

var image = new Image();
image.Source = new BitmapImage(new Uri(@"path\to\image.jpg",UriKind.RelativeOrAbsolute));

不需要指定图片的格式,因为 BitmapImageBitmapDecoder 都会自动进行处理。

对于大多数常见的图片格式(如JPG,PNG,BMP,GIF,TIFF,WebP,HEIC,AVIF等),这几种工作方式都能正常工作。

如果没有图片的路径,只有它被读进内存后的数据类型,那么就需要下面的几种方式。

Bitmap 转为 BitmapImage

System.Drawing.Bitmap 和 System.Windows.Media.Imageing.BitmapImage 是两个常见的图片数据类型。

System.Drawing.Bitmap 是 WinDForms 中的类型(GDI+)。

System.Windows.Media.Imageing.BitmapImage 是 WPF 的类型。

转换方法:

using System.Drawing;
using System.Windows.Media.Imaging;

static BitmapImage ConvertBitmapToBitmapImage(Bitmap bitmap)
{
    using var stream = new MemoryStream();
    
    bitmap.Save(stream, ImageFormat.Png);
    stream.Position = 0;
    
    var bitmapImage = new BitmapImage();
    bitmapImage.BeginInit();
    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
    bitmapIamge.StreamSource = stream;
    bitmapImage.EndInit();
    bitmapImage.Freeze(); // (可选)冻结图片,提高性能和线程安全性
    return bitmapImage;
}

字节数组转为 ImageSource

1.如果字节数组只是读进内存的图片文件数据,比如一个本地的JPG、PNG、BMP等格式的文件:

using System.IO;
using System.Windows.Media.Imaging;

static ImageSource ConvertByteArrayToImageSource(byte[] bytes)
{
    using var stream = new MemoryStream(bytes);
    var bitmapImage = new BitmapImage();
    bitmapIamge.BeginInit();
    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
    bitmapImage.StreamSource = stream;
    bitmapImage.EndInit();
    bitmapImage.Freeze();
    return bitmapImage();
}

还有一种更简单的方式:直接使用 BitmapDecoder 类:

using System.Windows.Media.Imaging;

static ImageSource ConvertByteArrayToIamgeSource(byte[] bytes)
{
    using var stream = new MemoryStream(bytes);
    
    return BitmapDecoder
        .Create(stream, BitmapCreateOptions.PreservePixelFormat,BitmapCacheOption.OnLoad)
        .Frames[0];
    
}

2.如果字节数组是图片的像素数据,比如从左上到右下的逐行 RGB 数据,那么会麻烦一些,而且需要有办法知道图片的宽高等信息:

using System.Windows.Media;
using System.Windows.Media.Imaging;

static ImageSource BgrByteArrayToImageSource(byte[] array, int width, int height, int channel = 3, int? stride = null)
{
    var bmp = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgr24, null);
    stride ??= ((width * channel + 3) / 4) * 4;
    bmp.WritePixels(new Int32Rect(0, 0, width, height), array, stride.Value, 0);
    bmp.Freeze();
    return bmp;
}

BitmapSource 转为 BitmapImage

这两个类有继承关系,BitmapImage 继承自 BitmapSource

但一般我们仍然需要进行一个"转换",因为通常的使用场景是,我们从 WPF 提供的剪贴板 API 中获取一个 BitmapSource,但经过简单的处理,将它转为 BitmapImage 从而添加给 Image 控件。

using System.Windows.Media.Imaging;

static BitmapImage ConvertBitmapSourceToBitmapImage(BitmapSource bitmapSource)
{
    var bitmapImage = new BitmapImage();
    using var stream = new MemoryStream();
    BitmapEncoder encoder = new BmpBitmapEncoder(); // 一般情况下,剪贴板中的图片数据是 BMP 格式的,而非 PNG 格式
    encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
    encoder.Save(stream);
    stream.Position = 0;
    bitmapImage.BeginInit();
    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
    bitmapImage.StreamSource = stream;
    bitmapImage.EndInit();
    bitmapImage.Freeze();
    return bitmapImage;
}

Emgu.CV.Image 转为 BitmapImage

BitmapImage 支持绝大多数常见的图片格式。但如果现在我们有一个不常见的格式,比如 JP2(JPEG 2000)格式,那么 BitmapImage 就无法直接处理了。这时候我们可以使用 Emgu.CV 库,它是 OpenCV 的 .NET 封装,支持更多的图片格式。下面给出一种方式:

var filename = @"path\to\image.jp2";

var mat = new Image<Bgr, Byte>(filename);
var bytes = mat.ToJpegData();
using var stream = new MemoryStream(bytes);

var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
bitmap.Freeze();

var control = new Image();
control.Source = bitmap;

control.Dump();

总结

对于大多数情况,我们都要先将数据转为持有常见图像类型的 Stream,然后再创建 BitmapImage,最后将其赋给 Image 控件。