webentwicklung-frage-antwort-db.com.de

Schnellere Alternativen zu SetPixel und GetPixel für Bitmaps für Windows Forms App

Ich versuche mir C # beizubringen und habe aus einer Vielzahl von Quellen gehört, dass die Funktionen erhalten und Setpixel schrecklich langsam sein können. Was sind einige der Alternativen und ist die Leistungsverbesserung wirklich so bedeutend? Danke im Voraus!

Ein Teil meines Codes als Referenz:

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;
}
36
purdoo

Der sofort verwendbare Code

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

Es besteht keine Notwendigkeit für LockBits oder SetPixel. Verwenden Sie die obige Klasse für den direkten Zugriff auf Bitmap-Daten.

Mit dieser Klasse ist es möglich, unformatierte Bitmap-Daten als 32-Bit-Daten festzulegen. Beachten Sie, dass es sich um PARGB handelt, das vormultiplizierte Alpha. In Alpha Compositing auf Wikipedia finden Sie weitere Informationen zur Funktionsweise und in Beispiele im MSDN-Artikel für BLENDFUNCTION erfahren Sie, wie Sie das Alpha richtig berechnen.

Verwenden Sie stattdessen PixelFormat.Format32bppArgb, Wenn die Vorvervielfachung die Dinge überkomplizieren könnte. Ein Leistungstreffer tritt auf, wenn er gezogen wird, da er intern in PixelFormat.Format32bppPArgb Konvertiert wird. Wenn sich das Bild vor dem Zeichnen nicht ändern muss, kann die Arbeit vor der Vorvervielfachung ausgeführt, in einen PixelFormat.Format32bppArgb - Puffer gezeichnet und von dort weiter verwendet werden.

Der Zugriff auf Standard-Member Bitmap erfolgt über die Eigenschaft Bitmap. Auf Bitmap-Daten wird direkt über die Eigenschaft Bits zugegriffen.

Verwenden von byte anstelle von int für unformatierte Pixeldaten

Ändern Sie beide Instanzen von Int32 In byte, und ändern Sie dann diese Zeile:

Bits = new Int32[width * height];

Dazu:

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

Wenn Bytes verwendet werden, lautet das Format Alpha/Rot/Grün/Blau in dieser Reihenfolge. Jedes Pixel benötigt 4 Datenbytes, eines für jeden Kanal. Die Funktionen GetPixel und SetPixel müssen entsprechend überarbeitet oder entfernt werden.

Vorteile für die Verwendung der oben genannten Klasse

  • Eine Speicherzuordnung zum bloßen Manipulieren der Daten ist nicht erforderlich. Änderungen an den Rohdaten werden sofort auf die Bitmap angewendet.
  • Es sind keine zusätzlichen Objekte zu verwalten. Dies implementiert IDisposable genau wie Bitmap.
  • Es ist kein unsafe -Block erforderlich.

Überlegungen

  • Der angeheftete Speicher kann nicht verschoben werden. Dies ist ein erforderlicher Nebeneffekt, damit dieser Speicherzugriff funktioniert. Dies verringert die Effizienz des Garbage Collectors ( MSDN Article ). Tun Sie dies nur mit Bitmaps, bei denen Leistung erforderlich ist, und stellen Sie sicher, dass Sie sie Dispose, wenn Sie fertig sind, damit der Speicher freigegeben werden kann.

Zugriff über das Objekt Graphics

Da es sich bei der Bitmap -Eigenschaft tatsächlich um ein .NET Bitmap -Objekt handelt, können Vorgänge mit der Graphics -Klasse problemlos ausgeführt werden.

var dbm = new DirectBitmap(200, 200);
using (var g = Graphics.FromImage(dbm.Bitmap))
{
    g.DrawRectangle(Pens.Black, new Rectangle(50, 50, 100, 100));
}

Leistungsvergleich

Bei der Frage geht es um die Leistung. In der folgenden Tabelle ist die relative Leistung zwischen den drei in den Antworten vorgeschlagenen Methoden aufgeführt. Dies wurde mit einer .NET Standard 2-basierten Anwendung und NUnit durchgeführt.

* 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.
80
SaxxonPike

Der Grund, warum Bitmap-Vorgänge in C # so langsam sind, ist das Sperren und Entsperren. Bei jeder Operation werden die erforderlichen Bits gesperrt, die Bits manipuliert und anschließend entsperrt.

Sie können die Geschwindigkeit erheblich verbessern, indem Sie die Vorgänge selbst ausführen. Siehe folgendes Beispiel.

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

  }
}
16
Bort

Es ist einige Zeit her, aber ich fand ein Beispiel, das nützlich sein könnte.

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

Dabei ist btm eine Bitmap-Variable.

2
user9015994

Sie können die Bitmap.LockBits-Methode verwenden. Wenn Sie die parallele Taskausführung verwenden möchten, können Sie auch die Parallel-Klasse im System.Threading.Tasks-Namespace verwenden. Die folgenden Links enthalten einige Beispiele und Erklärungen.

1
turgay