可立即使用的代码
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,以及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个字节的数据,分别为每个通道一个字节。 GetPixel和SetPixel函数需要相应地重新设计或删除。
使用上述类的好处
- 仅操纵数据而不需要内存分配是不必要的。对原始数据进行的更改立即应用于位图。
- 没有其他需要管理的对象。这像
Bitmap
一样实现了IDisposable
。
- 它不需要一个
unsafe
块。
需要考虑的问题
- 固定内存无法移动。这是这种内存访问工作的必需副作用。这会降低垃圾收集器的效率(MSDN文章)。只有在需要性能的位图中才执行此操作,并确保在完成后释放它们以使内存取消固定。
通过Graphics
对象访问
因为Bitmap
属性实际上是.NETBitmap
对象,所以可以使用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.