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

2019-05-09 07:04发布

我想自学C#和从各种渠道听说的功能获得和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;
}

Answer 1:

该立即可用的代码

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位数据。 注意,它是PARGB,这是预乘alpha。 见在维基百科上的alpha合成关于如何工作和更多的信息在MSDN文章BLENDFUNCTION的例子来了解如何正确计算阿尔法。

如果左乘可能过于复杂的事情,使用PixelFormat.Format32bppArgb代替。 因为它在内部被转换为当它绘制时出现性能下降, PixelFormat.Format32bppPArgb 。 如果图像没有之前被吸引到改变,工作时可预乘前进行,划了一个PixelFormat.Format32bppArgb缓冲区,并从那里继续使用。

访问标准的Bitmap的成员是通过暴露Bitmap属性。 位图数据使用直接访问Bits属性。

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

的两个实例更改Int32byte ,然后改变这一行:

Bits = new Int32[width * height];

为此:

Bits = new byte[width * height * 4];

当使用字节,格式是α/红/绿/蓝的顺序。 每个像素需要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));
}

性能比较

这个问题询问性能,所以这里应该显示在回答所提出的三种不同方法之间的相对性能的表格。 这是使用.NET标准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.


Answer 2:

究其原因位图操作在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)
  {

  }
}


Answer 3:

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

  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);

BTM为位图的变量。



Answer 4:

您可以使用Bitmap.LockBits方法。 此外,如果你想使用并行任务执行,您可以使用System.Threading.Tasks命名空间中的Parallel类。 以下链接有一些实例和解释。

  • http://csharpexamples.com/fast-image-processing-c/
  • http://msdn.microsoft.com/en-us/library/dd460713%28v=vs.110%29.aspx
  • http://msdn.microsoft.com/tr-tr/library/system.drawing.imaging.bitmapdata%28v=vs.110%29.aspx


文章来源: C# - Faster Alternatives to SetPixel and GetPixel for Bitmaps for Windows Forms App