C# - 用于 Windows 窗体应用程序位图的 SetPixel 和 GetPixel 的更快替代方案

标签 c# drawing gdi+ getpixel

我正在尝试自学 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();
    }
}

不需要 LockBitsSetPixel。使用上述类直接访问位图数据。

使用这个类,可以将原始位图数据设置为 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/

相关文章:

c# - asp.net 日历控件。当前日期 2 天后可选择的日期

java - 在 BufferedImage 中绘制多条线

.net - 在控制台应用程序中处理

c# - 在 C# 中更改 Jpeg 文件的大小

python - 如何在图形中间画轴?

graphics - WIC和GDI+有什么关系?

c++ - 将 Bitmap 转换为 unsigned char vector

javascript - .net 2.0 : getting all keys and values pairs from object, 从 JS 传递过来

c# - 如何从基泛型类派生泛型类?

c# - 关于变量名成员前缀