C# - 用于Windows窗体应用程序的BitPps的SetPixel和GetPixel的更快替代品

pur*_*doo 37 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;
}
Run Code Online (Sandbox Code Playgroud)

Sax*_*ike 82

可立即使用的代码

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

没有必要LockBitsSetPixel.使用上面的类直接访问位图数据.

使用此类,可以将原始位图数据设置为32位数据.请注意,它是PARGB,它是预乘alpha.有关其工作原理的详细信息,请参阅维基百科上的Alpha Compositing,以及有关如何正确计算alpha 的MSDN文章中有关BLENDFUNCTION的示例.

如果预乘可能使事情过于复杂,请PixelFormat.Format32bppArgb改用.在绘制时会发生性能损失,因为它在内部被转换为PixelFormat.Format32bppPArgb.如果图像在绘制之前不必更改,则可以在预乘之前完成工作,绘制到PixelFormat.Format32bppArgb缓冲区,并从那里进一步使用.

Bitmap通过Bitmap酒店可以使用标准会员.使用Bits属性直接访问位图数据.

使用byte而不是int原始像素数据

将两个实例更改Int32byte,然后更改此行:

Bits = new Int32[width * height];
Run Code Online (Sandbox Code Playgroud)

对此:

Bits = new byte[width * height * 4];
Run Code Online (Sandbox Code Playgroud)

使用字节时,格式为Alpha/Red/Green/Blue.每个像素占用4个字节的数据,每个通道一个.GetPixel和SetPixel函数需要相应地重新处理或删除.

使用上述课程的好处

  • 仅仅操纵数据的内存分配是不必要的; 对原始数据所做的更改会立即应用于位图.
  • 没有其他要管理的对象.这实现IDisposable就像Bitmap.
  • 它不需要unsafe块.

注意事项

  • 固定内存无法移动.为了使这种内存访问起作用,这是必需的副作用.这降低了垃圾收集器的效率(MSDN文章).仅在需要性能的位图上执行此操作,并在完成后确保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));
}
Run Code Online (Sandbox Code Playgroud)

性能比较

这个问题询问了性能,所以这里有一个表格,应该显示答案中提出的三种不同方法之间的相对表现.这是使用基于.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.
Run Code Online (Sandbox Code Playgroud)

  • 它可能不是默认提供的,因为它是一个非托管对象(相反,底层数据是非托管的)并且与框架的哲学背道而驰.但是这个版本对于频繁的图像处理肯定更有用. (2认同)
  • 需要从头开始创建DirectBitmap.如果需要从现有位图创建一个,则需要创建具有相同尺寸的DirectBitmap,并使用Graphics对象将其复制. (2认同)
  • 我建议修改此代码以在〜DirectBitmap()的finalize方法中调用`Dispose()`,或提供示例用法,在使用中创建DirectBitmap,使用(DirectBitmap bmp = new DirectBitmap()){...} `块。 (2认同)

Bor*_*ort 16

位图操作在C#中如此慢的原因是由于锁定和解锁.每个操作都将对所需位执行锁定,操作位,然后解锁位.

您可以自己处理操作,从而大大提高速度.请参阅以下示例.

using (var tile = new Bitmap(tilePart.Width, tilePart.Height))
{
  try
  {
      BitmapData srcData = sourceImage.LockBits(tilePart, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
      BitmapData dstData = tile.LockBits(new Rectangle(0, 0, tile.Width, tile.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

      unsafe
      {
          byte* dstPointer = (byte*)dstData.Scan0;
          byte* srcPointer = (byte*)srcData.Scan0;

          for (int i = 0; i < tilePart.Height; i++)
          {
              for (int j = 0; j < tilePart.Width; j++)
              {
                  dstPointer[0] = srcPointer[0]; // Blue
                  dstPointer[1] = srcPointer[1]; // Green
                  dstPointer[2] = srcPointer[2]; // Red
                  dstPointer[3] = srcPointer[3]; // Alpha

                  srcPointer += BytesPerPixel;
                  dstPointer += BytesPerPixel;
              }
              srcPointer += srcStrideOffset + srcTileOffset;
              dstPointer += dstStrideOffset;
          }
      }

      tile.UnlockBits(dstData);
      aSourceImage.UnlockBits(srcData);

      tile.Save(path);
  }
  catch (InvalidOperationException e)
  {

  }
}
Run Code Online (Sandbox Code Playgroud)

  • 这里的答案是错误的.锁定为什么?因为.NET使用垃圾收集器异步释放未使用的内存.释放一块内存后,它会将剩余的内存移动到其他位置,以获得更长的一致空闲内存块.如果垃圾收集器会在您读取像素时将位图移动到另一个位置,那么您将读取无意义的值.所以.NET强制你锁定位图,禁止垃圾收集器移动它.位图数据保留在内存中的同一位置,直到您将其解锁. (6认同)
  • 顺便说一句,这两个步长可以直接从“BitmapData”对象中获取。此代码无法洞察步幅的来源。就此而言,也不清楚“srcTileOffset”是什么。 (3认同)
  • `tilePart.Width` 和 `tilePart.Weight` 非常慢。考虑将它们的结果放在单独的宽度/高度变量中。就我而言,这将 2048x2048 图像的性能提高了 40 倍。 (3认同)

小智 6

已经有一段时间了,但我发现了一个可能有用的例子。

  BitmapData BtmpDt = a.LockBits(new Rectangle(0, 0, btm.Width, btm.Height), ImageLockMode.ReadWrite, btm.PixelFormat);
  IntPtr pointer = BtmDt.Scan0;
  int size = Math.Abs(BtmDt.Stride) * btm.Height;
  byte[] pixels = new byte[size];
  Marshal.Copy(pointer, pixels, 0, size);
  for (int b = 0; b < pixels.Length; b++) 
  {
    pixels[b] = 255; //Do something here 
  }
  Marshal.Copy(pixels, 0, pointer, size);
  btm.UnlockBits(BtmDt);
Run Code Online (Sandbox Code Playgroud)

其中btm是位图变量。