我正在尝试自学 C#,并且从各种来源听说函数 get 和 setpixel 可能非常慢。有哪些替代方案,性能改进真的那么重要吗?提前致谢!
我的一段代码供引用:
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(c.A, f.R, f.G, f.B));
}
}
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 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 位数据。请注意,它是 PARRGB,它是预乘的 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 个字节的数据,每个 channel 一个。 GetPixel 和 SetPixel 函数将需要相应地重新设计或删除。
使用上述类的好处
- 不需要为仅仅操作数据分配内存;对原始数据所做的更改会立即应用于位图。
- 没有其他对象需要管理。这实现了
IDisposable
就像Bitmap
一样。 - 它不需要
unsafe
block 。
注意事项
- 无法移动固定的内存。为了使这种内存访问正常工作,这是一个必需的副作用。这会降低垃圾收集器 (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.
关于C# - 用于 Windows 窗体应用程序位图的 SetPixel 和 GetPixel 的更快替代方案,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24701703/