立即可用的代码
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
or SetPixel
。使用上面的类可以直接访问位图数据。
使用此类,可以将原始位图数据设置为 32 位数据。请注意,它是 PARGB,即预乘 alpha。看维基百科上的 Alpha 合成 https://en.wikipedia.org/wiki/Alpha_compositing有关其工作原理的更多信息以及MSDN 文章中有关 BLENDFUNCTION 的示例 https://msdn.microsoft.com/en-us/library/dd183393(v=vs.85).aspx了解如何正确计算 alpha。
如果预乘可能会使事情变得过于复杂,请使用PixelFormat.Format32bppArgb
反而。当它被绘制时,性能会受到影响,因为它在内部被转换为PixelFormat.Format32bppPArgb
。如果图像在绘制之前不必更改,则可以在预乘之前完成工作,绘制到PixelFormat.Format32bppArgb
缓冲区,并从那里进一步使用。
获取标准Bitmap
成员通过以下方式暴露Bitmap
财产。位图数据可以使用以下命令直接访问Bits
财产。
Using byte
代替int
对于原始像素数据
更改两个实例Int32
to byte
,然后更改这一行:
Bits = new Int32[width * height];
To this:
Bits = new byte[width * height * 4];
当使用字节时,格式依次为 Alpha/Red/Green/Blue。每个像素占用 4 个字节的数据,每个通道一个。 GetPixel 和 SetPixel 函数需要相应地重新设计或删除。
使用上述类的好处
- 仅仅为了操作数据而分配内存是不必要的;对原始数据所做的更改会立即应用于位图。
- 没有其他对象需要管理。这实现了
IDisposable
就像Bitmap
.
- 它不需要
unsafe
block.
注意事项
- 固定内存无法移动。为了使这种内存访问起作用,这是必需的副作用。这降低了垃圾收集器的效率(MSDN 文章 https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.gchandle.addrofpinnedobject(v=vs.100).aspx)。仅对需要性能的位图执行此操作,并确保
Dispose
当你完成后,就可以将它们解开。
通过访问Graphics
object
因为Bitmap
属性实际上是一个.NETBitmap
对象,使用它执行操作很简单Graphics
class.
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.