2010-05-19 16 views
9

Ho guardato in giro un sacco e gli unici metodi che ho trovato per la creazione di un Texture2D da una bitmap sono:Esiste un'alternativa veloce alla creazione di Texture2D da un oggetto Bitmap 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); 
} 

e

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

Dove rgbValues ​​è un array di byte che contiene le bitmap di dati pixel nel formato ARGB a 32 bit.

La mia domanda è: ci sono approcci più veloci che posso provare?

Sto scrivendo un editor di mappe che deve essere letto in immagini in formato personalizzato (tessere mappa) e convertirle in trame Texture2D da visualizzare. La versione precedente dell'editor, che era un'implementazione C++, convertiva le immagini prima in bitmap e poi in trame da disegnare usando DirectX. Ho tentato lo stesso approccio qui, tuttavia entrambi gli approcci di cui sopra sono significativamente troppo lenti. Per caricare in memoria tutte le trame richieste per una mappa impiega per il primo approccio ~ 250 secondi e per il secondo approccio ~ 110 secondi su un computer con specifiche ragionevoli (per confronto, il codice C++ impiega circa 5 secondi). Se c'è un metodo per modificare i dati di una texture direttamente (come ad esempio con il metodo LockBits della classe Bitmap), allora sarei in grado di convertire le immagini con formato personalizzato direttamente in un Texture2D e, si spera di risparmiare tempo di elaborazione.

Qualsiasi aiuto sarebbe molto apprezzato.

Grazie

risposta

9

Vuoi LockBits? Ottieni LockBits.

Nella mia implementazione ho passato in GraphicsDevice dal chiamante in modo da rendere questo metodo generico e statico.

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

Non dovrebbe essere "dal chiamante" anziché "callee"? Dagli scopi dell'utente di questo codice, è questa funzione che è il chiamato. – drxzcl

+0

Grazie per la risposta, tuttavia nella mia domanda iniziale intendevo che avevo provato questo approccio - utilizzando SetData(). Ho eseguito ulteriori analisi e per creare Bitmap utilizzando LockBits sono stati necessari 2,7 secondi. Quando ho aggiunto a quel codice, per ogni bitmap generata: Texture2D tx = new Texture2D (dispositivo, bmp.Width, bmp.Height, 0, TextureUsage.None, SurfaceFormat.Color); Ha aumentato il tempo richiesto per 74 secondi !! Solo per creare una trama vuota per ogni bitmap, e nemmeno riempirla. Questo tipo di tempo di caricamento non è accettabile per un gioco 2D. Non pensavo che XNA sarebbe stata così lenta. :/ –

+0

@Ranieri, grazie - risolto! – bufferz

3

ho trovato ho dovuto specificare il PixelFormat come .Format32bppArgb quando si utilizza LockBits come lei suggerisce per afferrare immagini webcam.

 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

hanno cambiato il formato da BGRA a RGBA in XNA 4.0, in modo che il metodo dà strani colori, i canali rosso e blu deve essere acceso. Ecco un metodo che ho scritto che è super veloce! (carica trame da 1500x 256x256 pixel in circa 3 secondi).

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

Bello! Ma non dovresti modificare i dati bitmap da quando lo hai bloccato con ReadOnly? Potresti semplicemente copiarlo prima di fare la conversione. O ancora più semplice, basta fare la conversione e copiare in un solo passaggio! (Assegna a imgData [i] invece di byteData [i], quindi non hai affatto bisogno della copia!) – Cameron

+0

Oh, grazie, funziona per me. –

+1

+1 Questo è utile, sebbene (come suggerito da @Cameron) sarebbe meglio se non si cambiasse la bitmap originale, ma invece si lavorasse direttamente sull'array 'imgData' (non si dovrebbe nemmeno usare' non sicuro' in quel caso). – Groo

1

Quando ho letto questa domanda, ho pensato che era SetData prestazione che era il limite. Tuttavia leggendo i commenti di OP in risposta superiore, sembra essere l'assegnazione di una molto di grande Texture2D di.

In alternativa, chiedere a un pool di Texture2D di, che si alloca come necessario, tornare alla piscina quando non più necessari.

La prima volta che ciascun file di trama è necessario (o in un "pre-caricamento" all'inizio del processo, a seconda di dove si desidera il ritardo), caricare ciascun file in un array byte[]. (Conservare quei byte[] array in un LRU cache - se non si è sicuri di avere abbastanza memoria per tenerli tutti in giro tutto il tempo.) Poi, quando hai bisogno di una di quelle strutture, afferrare una delle strutture della piscina, (l'assegnazione di una nuova uno, se nessuna delle dimensioni appropriate è disponibile), SetData dal tuo array di byte - viola, hai una texture.

[Ho dimenticato dettagli importanti, come la necessità di associare una trama a un dispositivo specifico, ma è possibile determinare qualsiasi necessità dai parametri ai metodi che si stanno chiamando. Il punto che sto facendo è di ridurre al minimo le chiamate al costruttore di Texture2D, specialmente se hai molte trame grandi.]

Se sei davvero fantasioso e hai a che fare con trame di dimensioni diverse, puoi anche applicare LRU Cache principi per la piscina. In particolare, traccia il numero totale di byte di oggetti "liberi" contenuti nel tuo pool. Se il totale supera una soglia impostata (magari combinata con il numero totale di oggetti "liberi"), quindi alla richiesta successiva, elimina gli oggetti del pool libero più vecchi (della dimensione sbagliata o altri parametri errati), per rimanere al di sotto del limite consentito soglia dello spazio della cache "sprecato".

BTW, si potrebbe andare bene semplicemente rintracciare la soglia e buttare via tutti gli oggetti liberi quando viene superata la soglia. Il rovescio della medaglia è un singhiozzo momentaneo la prossima volta che assegni un mucchio di nuove trame - che puoi migliorare se hai informazioni su quali dimensioni dovresti tenere in giro. Se questo non è abbastanza buono, allora hai bisogno di LRU.

+0

Sebbene siano passati 8 anni, apprezzo che tu abbia scritto una risposta alla mia domanda. Penso che il tuo approccio potrebbe funzionare bene se la chiamata al costruttore di Texture2D è il collo di bottiglia. –

Problemi correlati