立即可用的代码
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 SetPixel(int x, int y, Color colour)
{
int index = x + (y * Width);
int col = colour.ToArgb();
Bits[index] = col;
}
public Color GetPixel(int x, int y)
{
int index = x + (y * Width);
int col = Bits[index];
Color result = Color.FromArgb(col);
return result;
}
public void Dispose()
{
if (Disposed) return;
Disposed = true;
Bitmap.Dispose();
BitsHandle.Free();
}
}
不需要LockBits 或SetPixel。使用上述类直接访问位图数据。
使用这个类,可以将原始位图数据设置为 32 位数据。请注意,它是 PARGB,它是预乘 alpha。请参阅Alpha Compositing on Wikipedia 了解有关其工作原理的更多信息,并参阅examples on the MSDN article for 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 个字节的数据,每个通道一个。 GetPixel 和 SetPixel 函数将需要相应地重新设计或删除。
使用上述类的好处
- 仅用于操作数据的内存分配是不必要的;对原始数据所做的更改会立即应用于位图。
- 没有其他需要管理的对象。这实现了
IDisposable,就像Bitmap。
- 它不需要
unsafe 块。
注意事项
- 无法移动固定内存。为了使这种内存访问起作用,这是必需的副作用。这会降低垃圾收集器 (MSDN Article) 的效率。仅对需要性能的位图执行此操作,并确保在完成后
Dispose 以取消固定内存。
通过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));
}
性能对比
这个问题是关于性能的,所以这里有一个表格,应该显示答案中提出的三种不同方法之间的相对性能。这是使用基于 .NET Standard 2 的应用程序和 NUnit 完成的。
* Time to fill the entire bitmap with red pixels *
- Not including the time to create and dispose the bitmap
- Best out of 100 runs taken
- Lower is better
- Time is measured in Stopwatch ticks to emphasize magnitude rather than actual time elapsed
- Tests were performed on an Intel Core i7-4790 based workstation
Bitmap size
Method 4x4 16x16 64x64 256x256 1024x1024 4096x4096
DirectBitmap <1 2 28 668 8219 178639
LockBits 2 3 33 670 9612 197115
SetPixel 45 371 5920 97477 1563171 25811013
* Test details *
- LockBits test: Bitmap.LockBits is only called once and the benchmark
includes Bitmap.UnlockBits. It is expected that this
is the absolute best case, adding more lock/unlock calls
will increase the time required to complete the operation.