C# – 更快的替代SetPixel和GetPixel的Windows窗体应用程序位图
我想教自己的C#,并从各种各样的消息来源得知,函数获取和setpixel可以是非常缓慢。 什么是一些替代scheme,性能改进真的很重要? 提前致谢!
我的代码的一大块供参考:
public static Bitmap Paint(Bitmap _b, Color f) { Bitmap b = new Bitmap(_b); for (int x = 0; x < b.Width; x++) { for (int y = 0; y < b.Height; y++) { Color c = b.GetPixel(x, y); b.SetPixel(x, y, Color.FromArgb(cA, fR, fG, fB)); } } return b; }
即时可用的代码
public class DirectBitmap : IDisposable { public Bitmap Bitmap { get; private set; } public Int32[] Bits { get; private set; } public bool Disposed { get; private set; } public int Height { get; private set; } public int Width { get; private set; } protected GCHandle BitsHandle { get; private set; } public DirectBitmap(int width, int height) { Width = width; Height = height; Bits = new Int32[width * height]; BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned); Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject()); } public void Dispose() { if (Disposed) return; Disposed = true; Bitmap.Dispose(); BitsHandle.Free(); } }
不需要LockBits
或SetPixel
。 使用上述类直接访问位图数据。
使用这个类,可以将原始位图数据设置为32位数据。 请注意,它是预乘alpha的PARGB。 请参阅维基百科上的Alpha合成关于如何工作的更多信息以及MSDN文章中用于BLENDFUNCTION的示例,以了解如何正确计算alpha。
如果预乘可能会使事情复杂化,请改为使用PixelFormat.Format32bppArgb
。 在绘制时发生性能冲突,因为它在内部被转换为PixelFormat.Format32bppPArgb
。 如果图像在绘制之前不需要改变,那么可以在预乘之前完成工作,绘制到PixelFormat.Format32bppArgb
缓冲区,并从那里进一步使用。
对标准Bitmap
成员的访问通过Bitmap
属性公开。 位图数据直接使用Bits
属性进行访问。
对原始像素数据使用byte
而不是int
将两个Int32
实例更改为byte
,然后更改此行:
Bits = new Int32[width * height];
对此:
Bits = new byte[width * height * 4];
使用字节时,格式为Alpha / Red / Green / Blue。 每个像素需要4个字节的数据,每个通道一个。
使用上述类的好处
- 仅仅处理数据的内存分配是不必要的。 对原始数据所做的更改将立即应用于位图。
- 没有其他的对象可以pipe理。 这就像
Bitmap
一样实现了IDisposable
。 - 它不需要一个
unsafe
块。
注意事项
- 固定的内存不能移动。 这是为了使这种内存访问起作用所必需的副作用。 这降低了垃圾收集器的效率( MSDN文章 )。 只需要在需要性能的位图上进行操作,并确保在完成操作后将其解除,以便可以取消固定内存。
通过Graphics
对象访问
由于Bitmap
属性实际上是一个.NET Bitmap
对象,因此使用Graphics
类执行操作非常简单。
var dbm = new DirectBitmap(200, 200); using (var g = Graphics.FromImage(dbm.Bitmap)) { g.DrawRectangle(Pens.Black, new Rectangle(50, 50, 100, 100)); }
C#中位图操作的原因很慢是由于locking和解锁。 每个操作将对所需的位执行locking,操作位,然后解锁位。
您可以通过自己处理操作大大提高速度。 看下面的例子。
using (var tile = new Bitmap(tilePart.Width, tilePart.Height)) { try { BitmapData srcData = sourceImage.LockBits(tilePart, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); BitmapData dstData = tile.LockBits(new Rectangle(0, 0, tile.Width, tile.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); unsafe { byte* dstPointer = (byte*)dstData.Scan0; byte* srcPointer = (byte*)srcData.Scan0; for (int i = 0; i < tilePart.Height; i++) { for (int j = 0; j < tilePart.Width; j++) { dstPointer[0] = srcPointer[0]; // Blue dstPointer[1] = srcPointer[1]; // Green dstPointer[2] = srcPointer[2]; // Red dstPointer[3] = srcPointer[3]; // Alpha srcPointer += BytesPerPixel; dstPointer += BytesPerPixel; } srcPointer += srcStrideOffset + srcTileOffset; dstPointer += dstStrideOffset; } } tile.UnlockBits(dstData); aSourceImage.UnlockBits(srcData); tile.Save(path); } catch (InvalidOperationException e) { } }
您可以使用Bitmap.LockBits方法。 另外如果你想使用并行任务执行,你可以在System.Threading.Tasks命名空间中使用Parallel类。 以下链接有一些样本和解释。