在.NET环境中,直接操作像素数据是一项常见的需求,尤其是在图像处理、游戏开发和实时图形渲染等领域。然而,传统的GDI+库在实现直接像素操作时存在一些限制,如需要锁定和复制图像数据,这不仅增加了开发复杂度,还可能导致性能瓶颈。本文将介绍一种在.NET中直接操作像素的高效方法,该方法避免了传统方法的不足,提供了一种更简洁、更快速的像素操作手段。
在GDI+中,Bitmap.LockBits()方法被广泛用于直接访问图像的像素数据。然而,这种方法需要锁定图像数据,然后将其复制到一个单独的缓冲区中进行操作,最后再将修改后的数据复制回图像,这一过程不仅效率低下,而且在部分信任的环境中无法使用。
为了绕过这些限制,可以创建一个新的位图,其像素数据存储在预先分配的内存区域中。这样,就可以绕过GDI+的限制,直接操作这些像素数据。此外,还可以让位图使用托管数组作为其像素缓冲区,这样就不需要使用不安全的代码,从而可以在低信任环境中进行像素操作。
Bitmap bitmap;
byte[] bits;
GCHandle handle;
int stride;
int pixelFormatSize;
// 创建步骤
pixelFormatSize = Image.GetPixelFormatSize(format) / 8;
stride = width * pixelFormatSize;
bits = new byte[stride * height];
handle = GCHandle.Alloc(bits, GCHandleType.Pinned);
IntPtr pointer = Marshal.UnsafeAddrOfPinnedArrayElement(bits, 0);
bitmap = new Bitmap(width, height, stride, format, pointer);
上述代码展示了如何创建一个空的位图,其像素数据可以直接作为一个托管字节数组进行编辑。以下是创建过程的详细步骤:
现在可以通过数组编辑位图的像素数据了。但是,为了编辑一个现有的位图,必须将位图数据复制到新创建的空白位图中。为此,使用DrawImageUnscaledAndClipped方法(这只适用于非索引目标像素格式):
Graphics g = Graphics.FromImage(bitmap);
g.DrawImageUnscaledAndClipped(source, new Rectangle(0, 0, source.Width, source.Height));
g.Dispose();
需要注意的是,这种方法“只”复制图像的当前帧,它“不”复制EXIF属性、多帧等。如果需要保留这些信息,可以使用基于数组的Bitmap作为LockBits()分配的工作缓冲区的托管等效物,或者手动将位图属性复制到它上面。
使用托管数组而不是指针,性能上会有损失吗?根据测试,答案是否定的。在测试中,发现数组方法至少比指针方法快10%。
在简单的场景中,可以看到这种方法的应用,例如在文章《队列-线性洪水填充:一种快速的洪水填充算法》中,展示了一个超级快速的Floodfill算法,无论图像有多大,都不会出现栈溢出。会感到惊讶的是,在.NET中进行图像处理可以有多快!