¿Cómo crear una textura XNA a partir de una imagen GDI con rectángulo de origen?

8

Un Texture2D en XNA solo puede contener tanto. Dependiendo de qué perfil utilizo, ese límite es de 2048 o 4096 píxeles de ancho. Por lo que pude ver en mi investigación (alguien me corrige si estoy equivocado), las imágenes GDI no tienen ninguna otra limitación que no sea exceder la memoria del usuario.

Estoy buscando la forma más eficiente de tomar una imagen GDI y un rectángulo de origen (dentro de las limitaciones de tamaño de XNA) y crear una nueva Texture2D a partir de esos datos en tiempo de ejecución. ¡El proceso también debe tener en cuenta el alfa pre-multiplicado de XNA 4.0!

O...

Una forma de tomar Texture2D.FromStream y adaptarlo para que también tome un rectángulo fuente, ya que eso sería aún mejor para mis necesidades.

Los requisitos son:

  • Sin canalización de contenido, necesito poder cargarlos en tiempo de ejecución.
  • ¡Usando solo el perfil Reach si es posible!
  • Solo se preocupan por Windows. No te preocupes por la portabilidad.

TL; DR:

Necesito un método como:

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

O

Texture2D Texture2DHelper.FromImage(Image, SourceRectangle)

Preferiblemente el primero.


Cuadro grande:

Ignorando el rectángulo de origen por un momento (para hacer que las cosas sean más fáciles de visualizar), he intentado cargar una textura completa en GDI y pasar los datos a un Texture2D usando un enfoque completamente de fuerza 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);
}

Y el resultado fue realmente lento. Tomó muchas más veces que simplemente hacer:

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

También pensé en usar Bitmap.LockBits y Block Copy, pero hacer eso dejaría de lado la corrección alfa pre-multiplicada, y no sé cómo aplicarla después de la copia.

Estoy tentado a pensar que una mejor solución sería trabajar directamente con Texture2D. Desde StreamStream ... No estoy muy familiarizado con el funcionamiento de Stream, pero he visto algunos métodos en .NET que toman un Stream y un Rectángulo. ¿Cómo lograría algo así? Ya sea un método FromStream completamente nuevo o un contenedor alrededor de cualquier Stream que proporcione la funcionalidad "Rectángulo".

David Gouveia
fuente

Respuestas:

4

OK, ¡logré encontrar la solución a esto, al estilo GDI! Lo he envuelto en una clase auxiliar estática:

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

En caso de que no haya notado que Texture2D.FromFile se eliminó de XNA 4.0, aproveché la oportunidad para replicar el comportamiento anterior cuando no se pasó un rectángulo de origen.

Y la mejor parte es que, al final, todavía se basa en Texture2D. Desde Stream, ¡toda la fase alfa premultiplicada está automatizada! Corre relativamente rápido también. Ok, esto no es verdad! Todavía tiene que hacer los cálculos alfa premultiply. Simplemente no lo recordaba bien ya que ya tenía el código para hacerlo .

Pero aún me pregunto si hay una manera de hacer esto directamente desde Stream sin tener que pasar por GDI. La mente vagó hacia la decoración del Stream de alguna manera, pero no llegó a ningún lado. :)

Y al navegar a través de mi antiguo código, encontré un comentario sobre Texture2D. Desde el error de Streaming y la imposibilidad de leer todos los formatos que dice la documentación, así que he estado usando GDI de todos modos. Así que me quedaré con este método por ahora.

David Gouveia
fuente