在C#中如何正确处理48/64位深度图像(含真灰度)
我在WPF应用中使用Bitmap类及其PNG字节流处理图像,主要涉及文件读写、从原生相机库获取图像并在UI中展示。近期有新需求,需要升级以处理64位深度图像(或48位,无需Alpha通道),但几乎所有操作中Bitmap都会将图像转换回32bppArgb。我已实现深拷贝功能,目前正在研究Image.FromStream(),但由此产生疑问:在C#应用中是否存在正确处理非32bppArgb图像(包括真灰度图像)的方法?还是微软忽略了这一需求?我找到过相关问题,但多是10年前的临时解决方案,期望现在已有正规方法。
编辑:运行于.NET Standard 2.0,主要用于.NET 6.0应用;使用System.Drawing.Common 5.0.2版本(若有需要可升级)。假设nativeImage是相机库中的结构体,需深拷贝以脱离共享内存:
using Bitmap bitmap = new(nativeImage.Width, nativeImage.Height, nativeImage.stride, PixelFormat.Format64bppArgb, nativeImage.DataPtr); using Bitmap copy = bitmap.DeepCopy(); //自定义函数 using MemoryStream stream = new(); copy.Save(stream, ImageFormat.Png); byte[] byteStream = stream.ToArray(); //保存至文件后确认为64位 //以下为测试代码,实际中字节数组可能会多次传递 using MemoryStream ms = new(byteStream); using Image image = Image.FromStream(ms, true); //此处突然变为32bppArgb
深拷贝函数代码:
public static Bitmap DeepCopy(this Image original) { // 复制Bitmap(Image)构造函数代码,但使用原始像素格式而非32bppArgb var result = new Bitmap(original.Width, original.Height, original.PixelFormat); using (Graphics g = Graphics.FromImage(result)) { g.Clear(Color.Transparent); g.DrawImage(original, 0, 0, original.Width, original.Height); } return result; }
1. 深拷贝函数的问题:Graphics.DrawImage导致格式转换
你的DeepCopy方法中使用Graphics.DrawImage是核心问题之一。GDI+的Graphics对象在绘制图像时,会自动将源图像转换为目标设备上下文的默认像素格式(通常是32bppArgb),哪怕你指定了目标Bitmap的像素格式也无法避免。
正确的深拷贝方式应该直接操作像素缓冲区,绕过GDI+的自动转换逻辑:
public static Bitmap DeepCopy(this Bitmap original) { // 创建与源图像同格式的目标Bitmap var result = new Bitmap(original.Width, original.Height, original.PixelFormat); // 锁定源和目标的像素缓冲区 var sourceData = original.LockBits( new Rectangle(0, 0, original.Width, original.Height), ImageLockMode.ReadOnly, original.PixelFormat); var destData = result.LockBits( new Rectangle(0, 0, result.Width, result.Height), ImageLockMode.WriteOnly, result.PixelFormat); try { // 计算缓冲区大小并直接复制像素数据 int bufferSize = sourceData.Stride * sourceData.Height; byte[] pixelBuffer = new byte[bufferSize]; System.Runtime.InteropServices.Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, bufferSize); System.Runtime.InteropServices.Marshal.Copy(pixelBuffer, 0, destData.Scan0, bufferSize); } finally { // 必须解锁缓冲区,避免资源泄漏 original.UnlockBits(sourceData); result.UnlockBits(destData); } return result; }
该方法直接复制原始像素数据,完全保留图像的原始位深度。
2. Image.FromStream的限制:System.Drawing.Common的固有缺陷
Image.FromStream自动转换高深度图像为32bppArgb是GDI+的设计限制——作为老旧的图形技术,GDI+对48/64位等高深度图像的支持非常有限,多数操作都会强制转换为32bppArgb格式。
要正确处理非32bppArgb图像,有两种正规方案:
方案A:使用WPF原生BitmapSource替代System.Drawing.Bitmap
WPF的BitmapSource系列类(如WriteableBitmap、BitmapImage)原生支持多种高深度格式,包括:
PixelFormats.Gray64(64位灰度)PixelFormats.Rgb48(48位RGB,无Alpha)PixelFormats.Rgba64(64位RGBA)
如果你的应用以WPF为主,建议直接切换到WPF的图像处理API:
- 从原生相机获取的图像数据可直接创建
WriteableBitmap,保留原始位深度; - 读取PNG字节流时,设置
BitmapCreateOptions.PreservePixelFormat避免自动转换:
using MemoryStream ms = new MemoryStream(byteStream); BitmapImage bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.CacheOption = BitmapCacheOption.OnLoad; bitmapImage.CreateOptions = BitmapCreateOptions.PreservePixelFormat; bitmapImage.StreamSource = ms; bitmapImage.EndInit(); // bitmapImage.Format会保留原始的64/48位格式
方案B:使用第三方图像处理库
如果必须依赖System.Drawing生态,可使用第三方库绕过GDI+的限制,比如ImageSharp(跨平台,完美支持.NET 6+):
// 使用ImageSharp加载图像,完全保留原始位深度 using var image = Image.Load(byteStream); // image.PixelType会保留原始的64/48位格式,所有操作均不会强制转换
ImageSharp完全重写了图像处理逻辑,没有GDI+的格式限制,支持几乎所有常见的图像位深度。
3. 总结
微软并非忽略高深度图像的需求,而是GDI+(System.Drawing.Common的底层)本身是老旧技术,对高深度图像支持有限。当前的正规解决方案是:
- WPF应用优先使用WPF原生的
BitmapSource系列类; - 跨平台或需要复杂图像处理时,使用ImageSharp等第三方库;
- 避免用GDI+的
Graphics类做像素级操作,改用直接复制缓冲区的方式处理原始像素数据。
内容的提问来源于stack exchange,提问作者Simoris

