You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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:

  1. 从原生相机获取的图像数据可直接创建WriteableBitmap,保留原始位深度;
  2. 读取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 01:03:32