Come creare una trama XNA da un'immagine GDI con rettangolo di origine?


8

Un Texture2D in XNA può contenere solo così tanto. A seconda del profilo che uso quel limite è largo 2048 o 4096 pixel. Per quanto ne so dalla mia ricerca (qualcuno mi corregga se sbaglio) Le immagini GDI non hanno limiti se non quello di non superare la memoria dell'utente.

Sto cercando il modo più efficiente per acquisire un'immagine GDI e un rettangolo di origine (entro i limiti delle dimensioni di XNA) e creare un nuovo Texture2D da quei dati in fase di esecuzione. Il processo deve anche tenere conto dell'alfa pre-moltiplicata di XNA 4.0!

O...

Un modo per prendere Texture2D.FromStream e adattarlo in modo che prenda anche un rettangolo di origine, poiché sarebbe ancora meglio per le mie esigenze.

I requisiti sono:

  • Nessuna pipeline di contenuti, devo essere in grado di caricarli in fase di esecuzione.
  • Utilizzando solo il profilo Reach, se possibile!
  • Cura solo Windows. Non preoccuparti della portabilità.

TL; DR:

Ho bisogno di un metodo come:

Texture2D Texture2DHelper.FromStream(GraphicsDevice, Stream, SourceRectangle);

O

Texture2D Texture2DHelper.FromImage(Image, SourceRectangle)

Preferibilmente il primo.


Quadro generale:

Ignorando per un momento il rettangolo di origine (per rendere le cose più facili da visualizzare), ho provato a caricare un'intera trama in GDI e passare i dati a Texture2D usando un approccio completamente a forza bruta:

using (System.Drawing.Image image = System.Drawing.Image.FromFile(path))
{
    int w = image.Width;
    int h = image.Height;
    System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(image);
    uint[] data = new uint[w * h];
    for (int i=0; i!=bitmap.Width; ++i)
    {
        for (int j=0; j!=bitmap.Height; ++j)
        {
            System.Drawing.Color pixel = bitmap.GetPixel(i, j);
            Microsoft.Xna.Framework.Color color = Color.FromNonPremultiplied(pixel.R, pixel.G, pixel.B, pixel.A);
            data[i + j * w] = color.PackedValue;
        }
    }
    _texture = new Texture2D(device, w, h);
    _texture.SetData(data);
}

E il risultato è stato davvero lento. Ci è voluto molto più tempo del semplice fare:

Texture2D.FromStream(GraphicsDevice, new FileStream(path, FileMode.Open));

Ho anche pensato di usare Bitmap.LockBits e una copia di blocco, ma farlo escluderebbe la correzione alfa pre-moltiplicata e non so come applicarla dopo la copia.

Sono tentato di pensare che una soluzione migliore sarebbe quella di aggirare direttamente Texture2D.FromStream ... Non ho troppa familiarità con il funzionamento di Stream ma ho visto alcuni metodi in .NET che utilizzano Stream e Rectangle. Come potrei ottenere qualcosa del genere? Un metodo FromStream completamente nuovo o un wrapper attorno a qualsiasi Stream che fornisce funzionalità "Rectangle".

Risposte:


4

OK, sono riuscito a trovare la soluzione a questo, in stile GDI! L'ho avvolto in una classe di supporto statica:

public static class TextureLoader
{
    public static Texture2D FromFile(GraphicsDevice device, string path, Microsoft.Xna.Framework.Rectangle? sourceRectangle = null)
    {
        // XNA 4.0 removed FromFile in favor of FromStream
        // So if we don't pass a source rectangle just delegate to FromStream
        if(sourceRectangle == null)
        {
            return Texture2D.FromStream(device, new FileStream(path, FileMode.Open));
        }
        else
        {
            // If we passed in a source rectangle convert it to System.Drawing.Rectangle
            Microsoft.Xna.Framework.Rectangle xnaRectangle = sourceRectangle.Value;
            System.Drawing.Rectangle gdiRectangle = new System.Drawing.Rectangle(xnaRectangle.X, xnaRectangle.Y, xnaRectangle.Width, xnaRectangle.Height);

            // Start by loading the entire image
            Image image = Image.FromFile(path);

            // Create an empty bitmap to contain our crop
            Bitmap bitmap = new Bitmap(gdiRectangle.Width, gdiRectangle.Height, image.PixelFormat);

            // Draw the cropped image region to the bitmap
            Graphics graphics = Graphics.FromImage(bitmap);
            graphics.DrawImage(image, new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), gdiRectangle.X, gdiRectangle.Y, gdiRectangle.Width, gdiRectangle.Height, GraphicsUnit.Pixel);

            // Save the bitmap into a memory stream
            MemoryStream stream = new MemoryStream();
            bitmap.Save(stream, ImageFormat.Png);

            // Finally create the Texture2D from stream
            Texture2D texture = Texture2D.FromStream(device, stream);

            // Clean up
            stream.Dispose();
            graphics.Dispose();
            bitmap.Dispose();
            image.Dispose();

            return texture;
        }
    }
}

Nel caso in cui non ti accorgessi che Texture2D.FromFile è stato rimosso da XNA 4.0, quindi ho colto l'occasione per replicare il vecchio comportamento quando non è stato passato un rettangolo di origine.

E la parte migliore è che alla fine si basa ancora su Texture2D.FromStream, l'intera fase alfa premoltiplicata è automatizzata! Funziona anche relativamente veloce. Va bene questo non è vero! Devi ancora fare i calcoli alfa premoltiplicati. Non mi ricordavo bene poiché avevo già il codice per farlo .

Ma mi chiedo ancora se c'è un modo per farlo direttamente dallo Stream senza dover passare attraverso GDI. La mente vagava verso la decorazione dello Stream in qualche modo, ma non arrivò da nessuna parte. :)

E sfogliando il mio vecchio codice ho trovato un commento su Texture2D.FromStream che veniva intercettato e non potevo leggere tutti i formati che la documentazione dice di fare, quindi ho usato GDI comunque. Quindi starò con questo metodo per ora.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.