Wie erstelle ich eine XNA-Textur aus einem GDI-Bild mit Quellrechteck?

8

Eine Texture2D in XNA kann nur so viel aufnehmen. Je nachdem, welches Profil ich verwende, ist diese Grenze entweder 2048 oder 4096 Pixel breit. Soweit ich anhand meiner Recherchen feststellen konnte (jemand korrigiert mich, wenn ich falsch liege), haben GDI-Bilder keine andere Einschränkung, als den Speicher des Benutzers nicht zu überschreiten.

Ich suche nach der effizientesten Möglichkeit, ein GDI-Image und ein Quellrechteck (innerhalb der Größenbeschränkungen von XNA) zu erstellen und zur Laufzeit aus diesen Daten eine neue Texture2D zu erstellen. Der Prozess muss auch das vormultiplizierte Alpha von XNA 4.0 berücksichtigen!

ODER...

Eine Möglichkeit, Texture2D.FromStream so anzupassen, dass auch ein Quellrechteck verwendet wird, da dies für meine Anforderungen noch besser wäre.

Die Anforderungen sind:

  • Keine Inhaltspipeline, ich muss sie zur Laufzeit laden können.
  • Wenn möglich nur Profil erreichen!
  • Kümmere dich nur um Windows. Sorgen Sie sich nicht um die Portabilität.

TL; DR:

Ich brauche eine Methode wie:

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

Oder

Texture2D Texture2DHelper.FromImage(Image, SourceRectangle)

Am liebsten der erste.


Großes Bild:

Ich habe das Quellrechteck für einen Moment ignoriert (um die Visualisierung zu vereinfachen) und versucht, eine ganze Textur in GDI zu laden und die Daten mithilfe eines Brute-Force-Ansatzes an Texture2D zu übergeben:

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

Und das Ergebnis war sehr langsam. Es hat viele, viele Male länger gedauert als nur:

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

Ich habe auch darüber nachgedacht, Bitmap.LockBits und eine Blockkopie zu verwenden, aber dies würde die vormultiplizierte Alphakorrektur auslassen, und ich weiß nicht, wie ich sie nach der Kopie anwenden soll.

Ich bin versucht zu glauben, dass eine bessere Lösung darin besteht, Texture2D.FromStream direkt zu umgehen ... Ich bin nicht allzu vertraut mit der Funktionsweise von Stream, aber ich habe in .NET einige Methoden gesehen, die einen Stream und ein Rechteck verwenden. Wie würde ich so etwas erreichen? Entweder eine völlig neue FromStream-Methode oder ein Wrapper um einen Stream, der "Rechteck" -Funktionalität bietet.

David Gouveia
quelle

Antworten:

4

OK, es ist mir gelungen, die Lösung für diesen GDI-Stil zu finden! Ich habe es in eine statische Hilfsklasse eingepackt:

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

Falls Sie nicht bemerkt haben, dass Texture2D.FromFile aus XNA 4.0 entfernt wurde, habe ich die Gelegenheit genutzt, das alte Verhalten zu replizieren, wenn kein Quellrechteck übergeben wurde.

Und das Beste daran ist, dass die gesamte vorvervielfachte Alpha-Phase automatisiert ist, da sie am Ende immer noch auf Texture2D.FromStream basiert! Läuft auch relativ schnell. Okay das ist nicht wahr! Sie müssen noch die vormultiplizierten Alpha-Berechnungen durchführen. Ich erinnerte mich einfach nicht gut, da ich bereits Code hatte, um das zu tun .

Aber ich frage mich immer noch, ob es eine Möglichkeit gibt, dies direkt vom Stream aus zu tun, ohne GDI passieren zu müssen. Die Gedanken wanderten irgendwie dazu, den Strom zu dekorieren, kamen aber nirgendwo an. :) :)

Beim Durchsuchen meines alten Codes fand ich einen Kommentar zu Texture2D.FromStream, der fehlerhaft war und nicht alle Formate lesen konnte, die in der Dokumentation angegeben sind. Daher habe ich trotzdem GDI verwendet. Also werde ich vorerst bei dieser Methode bleiben.

David Gouveia
quelle