2010-05-19 11 views
9

Ich habe viel herum geschaut und die einzigen Methoden, die ich für die Schaffung eines Texture2D aus einer Bitmap gefunden haben, sind:Gibt es eine schnelle Alternative zum Erstellen einer Texture2D aus einem Bitmap-Objekt in XNA?

using (MemoryStream s = new MemoryStream()) 
{ 
    bmp.Save(s, System.Drawing.Imaging.ImageFormat.Png); 
    s.Seek(0, SeekOrigin.Begin); 
    Texture2D tx = Texture2D.FromFile(device, s); 
} 

und

Texture2D tx = new Texture2D(device, bmp.Width, bmp.Height, 
         0, TextureUsage.None, SurfaceFormat.Color); 
tx.SetData<byte>(rgbValues, 0, rgbValues.Length, SetDataOptions.NoOverwrite); 

Wo rgbValues ​​ein Byte-Array die Bitmap enthält, Pixeldaten im 32-Bit-ARGB-Format.

Meine Frage ist, gibt es schnellere Ansätze, die ich ausprobieren kann?

Ich schreibe einen Karteneditor, der Bilder im benutzerdefinierten Format (Kartenkacheln) einlesen und in Texture2D-Texturen konvertieren muss, um sie anzuzeigen. Die vorherige Version des Editors, die eine C++ - Implementierung war, wandelte die Bilder zuerst in Bitmaps und dann in Texturen um, die mit DirectX gezeichnet wurden. Ich habe den gleichen Ansatz hier versucht, jedoch sind beide der oben genannten Ansätze deutlich zu langsam. Um alle für eine Karte erforderlichen Texturen in den Speicher zu laden, werden für den ersten Ansatz etwa 250 Sekunden und für den zweiten Ansatz etwa 110 Sekunden auf einem Computer mit vernünftiger Spezifikation benötigt (zum Vergleich benötigte C++ - Code etwa 5 Sekunden). Wenn es eine Methode gibt, die Daten einer Textur direkt zu bearbeiten (zB mit der LockBits-Methode der Bitmap-Klasse), wäre ich in der Lage, die Bilder im benutzerdefinierten Format direkt in eine Texture2D zu konvertieren und hoffentlich Verarbeitungszeit zu sparen.

Jede Hilfe würde sehr geschätzt werden.

Dank

Antwort

9

Sie LockBits wollen? Sie erhalten LockBits.

In meiner Implementierung habe ich das GraphicsDevice vom Aufrufer übergeben, damit ich diese Methode generisch und statisch machen konnte.

public static Texture2D GetTexture2DFromBitmap(GraphicsDevice device, Bitmap bitmap) 
{ 
    Texture2D tex = new Texture2D(device, bitmap.Width, bitmap.Height, 1, TextureUsage.None, SurfaceFormat.Color); 

    BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat); 

    int bufferSize = data.Height * data.Stride; 

    //create data buffer 
    byte[] bytes = new byte[bufferSize];  

    // copy bitmap data into buffer 
    Marshal.Copy(data.Scan0, bytes, 0, bytes.Length); 

    // copy our buffer to the texture 
    tex.SetData(bytes); 

    // unlock the bitmap data 
    bitmap.UnlockBits(data); 

    return tex; 
} 
+1

Sollte das nicht "vom Anrufer" statt "Angerufener" sein? Aus den Zwecken des Benutzers dieses Codes ist diese Funktion der Angerufene. – drxzcl

+0

Danke für deine Antwort, aber ich meinte in meiner ursprünglichen Frage, dass ich diesen Ansatz versucht habe - mit SetData(). Ich habe eine weitere Analyse durchgeführt, und um die Bitmaps mit LockBits zu erstellen, brauchte ich 2,7 Sekunden. Wenn ich diesen Code für jede erzeugte Bitmap hinzugefügt habe: Texture2D tx = new Texture2D (Gerät, bmp.Width, bmp.Height, 0, TextureUsage.None, SurfaceFormat.Color); Es dauerte die Zeit bis 74 Sekunden !! Nur um eine leere Textur für jede Bitmap zu erstellen und nicht einmal zu füllen. Diese Ladezeit ist für ein 2D-Spiel nicht akzeptabel. Ich hätte nicht gedacht, dass XNA so langsam wäre. :/ –

+0

@Ranieri, danke - behoben! – bufferz

3

Ich fand ich das Pixelformat als .Format32bppArgb angeben hatte, als LockBits verwenden, wie Sie Webcam-Bilder greifen vorschlagen.

 BitmapData bmd = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), 
      System.Drawing.Imaging.ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 
     int bufferSize = bmd.Height * bmd.Stride; 
     //create data buffer 
     byte[] bytes = new byte[bufferSize]; 
     // copy bitmap data into buffer 
     Marshal.Copy(bmd.Scan0, bytes, 0, bytes.Length); 

     // copy our buffer to the texture 
     Texture2D t2d = new Texture2D(_graphics.GraphicsDevice, bmp.Width, bmp.Height, 1, TextureUsage.None, SurfaceFormat.Color); 
     t2d.SetData<byte>(bytes); 
     // unlock the bitmap data 
     bmp.UnlockBits(bmd); 
     return t2d; 
7

sie das Format von BGRA geändert haben in XNA 4.0, RGBA, so dass Verfahren seltsame Farben gibt, braucht die roten und blauen Kanäle geschaltet werden. Hier ist eine Methode, die ich geschrieben habe, das ist super schnell! (lädt 1500x 256x256 Pixel Texturen in ca. 3 Sekunden).

private Texture2D GetTexture(GraphicsDevice dev, System.Drawing.Bitmap bmp) 
    { 
     int[] imgData = new int[bmp.Width * bmp.Height]; 
     Texture2D texture = new Texture2D(dev, bmp.Width, bmp.Height); 

     unsafe 
     { 
      // lock bitmap 
      System.Drawing.Imaging.BitmapData origdata = 
       bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat); 

      uint* byteData = (uint*)origdata.Scan0; 

      // Switch bgra -> rgba 
      for (int i = 0; i < imgData.Length; i++) 
      { 
       byteData[i] = (byteData[i] & 0x000000ff) << 16 | (byteData[i] & 0x0000FF00) | (byteData[i] & 0x00FF0000) >> 16 | (byteData[i] & 0xFF000000);       
      }     

      // copy data 
      System.Runtime.InteropServices.Marshal.Copy(origdata.Scan0, imgData, 0, bmp.Width * bmp.Height); 

      byteData = null; 

      // unlock bitmap 
      bmp.UnlockBits(origdata); 
     } 

     texture.SetData(imgData); 

     return texture; 
    } 
+2

Schön! Aber sollten Sie die Bitmap-Daten nicht ändern, seit Sie sie mit ReadOnly gesperrt haben? Sie könnten es einfach kopieren und dann die Konvertierung durchführen. Oder noch einfacher, machen Sie einfach die Konvertierung und kopieren Sie in einem Schritt! (Zu imgData [i] anstelle von ByteData [i] zuweisen, dann brauchst du die Kopie überhaupt nicht!) – Cameron

+0

Oh, danke, es funktioniert für mich. –

+1

+1 Dies ist praktisch, obwohl (wie von @Cameron vorgeschlagen) es besser wäre, wenn Sie nicht die ursprüngliche Bitmap ändern würden, sondern stattdessen direkt am 'imgData'-Array gearbeitet hätten (Sie müssten nicht einmal' unsafe' verwenden) In diesem Fall). – Groo

1

Als ich diese Frage zu lesen, nahm ich es war SetData Leistung, die die Grenze war. Wie auch immer er die OP-Kommentare in der oberen Antwort liest, scheint er ein Los von großen Texture2D's zuzuteilen.

Als Alternative können Sie einen Pool von Texture2Ds in Betracht ziehen, die Sie nach Bedarf zuweisen und dann zum Pool zurückkehren, wenn sie nicht mehr benötigt werden.

Wenn die Texturdatei zum ersten Mal benötigt wird (oder in einem "Pre-Load" zu Beginn des Prozesses, je nachdem, wo Sie die Verzögerung wünschen), laden Sie jede Datei in ein byte[] Array. (Speichern Sie diese byte[] Arrays in einem LRU Cache - es sei denn, Sie sind sicher, dass Sie genug Speicher haben, um sie ständig herumzuhalten.) Wenn Sie eine dieser Texturen benötigen, greifen Sie eine der Pool - Texturen zu Wenn Sie keine geeignete Größe haben, SetData von Ihrem Byte-Array - Viola, haben Sie eine Textur.

[Ich habe wichtige Details weggelassen, z. B. die Notwendigkeit, eine Textur mit einem bestimmten Gerät zu verknüpfen - Sie können jedoch alle Anforderungen von den Parametern zu den Methoden, die Sie aufrufen, festlegen. Der Punkt, den ich hinaus will ist, Anrufe an den Texture2D Konstruktor zu minimieren, vor allem wenn Sie viele große Texturen haben.]

Wenn Sie wirklich Lust bekommen, und mit vielen verschiedenen Größen Texturen zu tun haben, Sie auch anwenden können LRU Cache Prinzipien zum Pool. Verfolgen Sie insbesondere die Gesamtzahl der Bytes von "freien" Objekten in Ihrem Pool. Überschreitet diese Summe einen bestimmten Grenzwert (möglicherweise kombiniert mit der Gesamtzahl der "freien" Objekte), wirf bei der nächsten Anfrage die ältesten freien Poolelemente (der falschen Größe oder anderer falscher Parameter) weg, um unter dem zulässigen Wert zu bleiben Schwelle des "verschwendeten" Cachespeichers.

BTW, können Sie einfach die Schwelle verfolgen und alle freie Objekte einfach wegwerfen, wenn der Grenzwert überschritten wird. Der Nachteil ist ein momentaner Schluckauf, wenn Sie das nächste Mal eine Menge neuer Texturen zuweisen - was Sie verbessern können, wenn Sie Informationen darüber haben, welche Größen Sie in der Nähe haben sollten. Wenn das nicht gut genug ist, dann brauchen Sie LRU.

+0

Obwohl es 8 Jahre ist, schätze ich es, dass Sie eine Antwort auf meine Frage geschrieben haben. Ich denke, dass Ihr Ansatz in der Tat gut funktionieren könnte, wenn der Aufruf des Texture2D-Konstruktors der Flaschenhals ist. –