C#中支持16位灰度等多像素格式的内存图像存储类查询
我需要对内存缓冲区中的图像进行矩形区域黑色遮罩处理,因此最初在自研API中直接复用Bitmap类搭配ImageFormat.MemoryBmp实现该功能,本地环境运行效果良好,实现代码如下:
public static void MaskBitmap(Bitmap input, Rectangle maskRegion) { var bytesPerPixel = Image.GetPixelFormatSize(input.PixelFormat) / 8; var row = new byte[maskRegion.Width * bytesPerPixel]; var maskData = input.LockBits(maskRegion, ImageLockMode.WriteOnly, input.PixelFormat); for (var i = 0; i < maskData.Height; ++i) { Marshal.Copy(row, 0, maskData.Scan0 + (i * maskData.Stride), row.Length); } input.UnlockBits(maskData); }
但部署到生产环境后发现,执行以下代码会抛出NotImplementedException异常:
var image16 = new Bitmap(512, 512, PixelFormat.Format16bppGrayScale);
最终溯源到问题出自mono的libgdiplus未实现对应逻辑。
因此想咨询:C#中是否有现成的类可以复用,用于存储以下pixelformat类型的图像:
- PixelFormat.Format8bppIndexed
- PixelFormat.Format16bppGrayScale
- PixelFormat.Format24bppRgb
我已知晓GDI+不支持16位图像的保存/展示,我仅需要具备图像式访问能力的内存结构即可。
参考信息:我曾尝试过如下hack方案:
var image = new Bitmap(512,512,PixelFormat.Format24bppRgb); image.Flags = ImageFlags.ColorSpaceGray;
但Flags为只读属性,该方案不可行。
可行解决方案
方案1:使用跨平台图像处理库(低改造成本)
直接使用社区成熟的纯托管图像处理库即可,无需依赖libgdiplus,原生支持你需要的全部像素格式,API逻辑和GDI+接近,原有遮罩逻辑几乎不需要大改:
- 兼容8位索引、16位灰度、24位RGB等常见像素格式
- 提供直接操作内存像素块的能力,性能和GDI+原生实现相当
- 无原生组件依赖,跨Windows/Linux/macOS运行无兼容问题
遮罩逻辑示例:
// 泛型支持所有像素格式,L8对应8位灰度、L16对应16位灰度、Rgb24对应24位RGB public static void MaskImage<TPixel>(Image<TPixel> input, Rectangle maskRegion) where TPixel : unmanaged, IPixel<TPixel> { input.ProcessPixelRows(accessor => { for (int y = maskRegion.Y; y < maskRegion.Bottom; y++) { Span<TPixel> maskRow = accessor.GetRowSpan(y).Slice(maskRegion.X, maskRegion.Width); maskRow.Clear(); // 直接填充对应像素格式的黑色值 } }); }
方案2:自研轻量内存图像结构(零第三方依赖)
如果不想引入外部依赖,可以自行封装极简的内存图像类,完全规避GDI+的兼容问题:
public sealed class MemoryBitmap : IDisposable { public int Width { get; } public int Height { get; } public PixelFormat Format { get; } public int BytesPerPixel { get; } public int Stride { get; } public IntPtr DataPtr { get; } private bool _isDisposed; public MemoryBitmap(int width, int height, PixelFormat format) { Width = width; Height = height; Format = format; BytesPerPixel = Image.GetPixelFormatSize(format) / 8; // 和GDI+规则一致,行宽对齐到4字节 Stride = (width * BytesPerPixel + 3) & ~3; DataPtr = Marshal.AllocHGlobal(Stride * height); // 初始化为全黑 unsafe { new Span<byte>((void*)DataPtr, Stride * height).Clear(); } } // 直接获取指定行的内存切片,方便读写操作 public Span<byte> GetRow(int rowIndex) { if (_isDisposed) throw new ObjectDisposedException(nameof(MemoryBitmap)); if (rowIndex < 0 || rowIndex >= Height) throw new ArgumentOutOfRangeException(nameof(rowIndex)); unsafe { return new Span<byte>((void*)(DataPtr + rowIndex * Stride), Stride); } } public void Dispose() { if (_isDisposed) return; Marshal.FreeHGlobal(DataPtr); _isDisposed = true; } }
该实现完全不依赖GDI+的Bitmap创建逻辑,不会触发libgdiplus的未实现异常,你原有遮罩逻辑只需要稍作调整即可直接复用。
内容的提问来源于stack exchange,提问作者malat
相关产品推荐
相关产品推荐

