Bagaimana cara membuat tekstur XNA dari Gambar GDI dengan sumber persegi panjang?

8

Texture2D di XNA hanya bisa menampung begitu banyak. Bergantung pada profil mana yang saya gunakan batasnya adalah 2048 atau 4096 piksel. Sejauh yang saya tahu dari penelitian saya (seseorang mengoreksi saya jika saya salah) Gambar GDI tidak memiliki batasan selain tidak melebihi memori pengguna.

Saya mencari cara yang paling efisien untuk mengambil Gambar GDI dan sumber persegi panjang (dalam batasan ukuran XNA) dan membuat Texture2D baru dari data saat runtime. Proses ini juga harus memperhitungkan alfa pra-berlipat ganda XNA 4.0!

ATAU...

Cara untuk mengambil Texture2D.FromStream dan mengadaptasinya sehingga dibutuhkan sumber persegi panjang juga, karena itu akan lebih baik untuk kebutuhan saya.

Persyaratannya adalah:

  • Tidak ada pipa konten, saya harus dapat memuatnya saat runtime.
  • Gunakan hanya Jangkauan profil jika memungkinkan!
  • Hanya peduli dengan Windows. Jangan khawatir tentang portabilitas.

TL; DR:

Saya memerlukan metode seperti:

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

Atau

Texture2D Texture2DHelper.FromImage(Image, SourceRectangle)

Lebih disukai yang pertama.


Gambar besar:

Mengabaikan persegi panjang sumber sesaat (untuk mempermudah visualisasi), saya telah mencoba memuat seluruh tekstur dalam GDI dan meneruskan data ke Texture2D menggunakan pendekatan brute force:

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

Dan hasilnya sangat lambat. Butuh beberapa kali lebih lama daripada sekadar melakukan:

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

Saya juga berpikir tentang menggunakan Bitmap.LockBits dan Block Copy tetapi melakukan itu akan meninggalkan koreksi alpha pra-dikalikan, dan saya tidak tahu bagaimana menerapkannya setelah salinan.

Saya tergoda untuk berpikir bahwa solusi yang lebih baik adalah bekerja di sekitar Texture2D.FromStream secara langsung ... Saya tidak terlalu akrab dengan cara kerja Stream tetapi saya telah melihat beberapa metode di .NET yang mengambil Stream dan Rectangle. Bagaimana saya mencapai sesuatu seperti itu? Baik metode FromStream yang sama sekali baru atau pembungkus di sekitar Stream yang menyediakan fungsionalitas "Rectangle".

David Gouveia
sumber

Jawaban:

4

OKE, berhasil menemukan solusi untuk ini, gaya GDI! Saya telah membungkusnya dalam kelas pembantu statis:

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

Jika Anda tidak melihat Texture2D.FromFile dihapus dari XNA 4.0 jadi saya mengambil kesempatan untuk mereplikasi perilaku lama ketika tidak melewati sumber persegi panjang.

Dan bagian terbaiknya adalah karena pada akhirnya ia masih bergantung pada Texture2D.FromStream, seluruh fase alfa yang telah diultipultasikan secara otomatis! Berjalan relatif cepat juga. Oke ini tidak benar! Anda masih harus melakukan perhitungan alfa premultiply. Saya hanya tidak ingat dengan baik karena saya sudah memiliki kode untuk melakukannya .

Tetapi masih bertanya-tanya apakah ada cara untuk melakukan ini langsung dari Stream tanpa harus melewati GDI. Pikiran berkelana ke arah mendekorasi Sungai dengan cara tertentu tetapi tidak sampai di mana pun. :)

Dan dari menjelajah melalui kode lama saya, saya menemukan komentar tentang Texture2D.FromStream disadap dan tidak bisa membaca semua format dokumentasi mengatakan itu, jadi saya tetap menggunakan GDI pula. Jadi saya akan tinggal dengan metode ini untuk saat ini.

David Gouveia
sumber