sábado, julio 08, 2006

Texturas a partir de archivos PCX

Para el proyecto que estoy desarrollando me intersaba dar soporte a archivos PCX: es decir crear texturas a partir de archivos PCX.

La clase TextureLoader permite cargar una textura a partir de un archivo gráfico, da soporte a varios formatos (entre ellos .bmp, .jpg y .png como más destacados) pero no da soporte a arhivos PCX.
Así pues intenté ver como podía cargar un archivo PCX y crear una textura Direct3D a partir de él.
Para ello el primer paso fue encontrar alguna librería para leer archivos PCX. Estuve buscando por google a ver si encontraba alguna librería .NET (gratuita) para leer este tipo de archivos. No encontré ninguna pero vi que FreeImage tenia un wrapper de C#, así que me decidí utilizarla. Ya había usado FreeImage en proyectos anteriores (con DirectDraw y C++) y me había gustado así que me pareció una buena idea.

La clase Texture tiene un método llamado fromBitmap, que a partir de un objeto System.Drawing.Bitmap crea el objeto Textura correspondiente. Así que en resumen se trataba de obtener un Bitmap a partir de FreeImage.

FreeImage és una librería que trabaja con varios formatos gráficos, pero internamente los convierte todos al formato DIB (Device Independent Bitmap).

El primer paso era cargar la imagen con FreeImage:

uint handle = FreeImage.Load(FIF.FIF_PCX, System.Windows.Forms.Application.StartupPath + @"\..\..\texture.pcx", 0);

Para pasar la imagen del formato DIB interno de FreeImage al formato Bitmap de .NET se me ocurrió utilizar las funciones de memoria de la propia FreeImage. En concreto lo que quería hacer era:
  1. Guardar la imagen en un bloque de memoria en formato Bitmap
  2. Copiar la información en un array de bytes
  3. Crear un objeto Bitmap inicializado con los datos del array de bytes.
El primer problema que me encontré era que el wrapper de C# para FreeImage estaba incompleto: Las funciones de manejo de memoria de FreeImage no aparecían en el wrapper. Por suerte el wrapper no es nada más que un pequeño archivo .cs con las definiciones de P/Invoke de las funciones de FreeImage, así que pude añadir las definiciones que me faltaban:

[DllImport(dllName, EntryPoint = "FreeImage_OpenMemory")]

public static extern FIMEMORY OpenMemory(IntPtr bits, Int32 size_in_bytes);

[DllImport(dllName, EntryPoint = "FreeImage_CloseMemory")]

public static extern void CloseMemory(FIMEMORY stream);

[DllImport(dllName, EntryPoint = "FreeImage_SaveToMemory")]

public static extern bool SaveToMemory(FIF fif, FIBITMAP dib, FIMEMORY stream, int flags);

[DllImport(dllName, EntryPoint = "FreeImage_AcquireMemory")]

public static extern long AcquireMemory(FIMEMORY stream, ref IntPtr data, ref int size_in_bytes);


Una vez añadido esto en el wrapper (y recompilarlo) usé el siguiente código para obtener un objeto System.Drawing.Bitmap:

uint hmem;
int w, h, stride;
byte[] content;
// Obtenemos un buffer de memoria de FreeImage
hmem = FreeImage.OpenMemory(IntPtr.Zero, 0);
// Grabamos la imagen en el buffer de memoria en formato BMP
bool b = FreeImage.SaveToMemory(FreeImageAPI.FIF.FIF_BMP, dib, hmem, 0);
FreeImage.Unload(dib);
// Obtenemos un puntero (unmanaged) al buffer de memoria de FreeImage
IntPtr ptr = IntPtr.Zero;
int size_in_bytes = 0;
FreeImage.AcquireMemory(hmem, ref ptr, ref size_in_bytes);
content = new byte[size_in_bytes];
// Copiamos el contenido del buffer de memoria unmanaged a un byte[]
Marshal.Copy(ptr, content, 0, size_in_bytes);
// Creamos un MemoryStream que apunte al contenido copiado y creamos el Bitmap
System.IO.MemoryStream ms = new System.IO.MemoryStream(content);
Bitmap bmp = new Bitmap(ms, false);
// Cerramos el buffer de memoria de FreeImage (para evitar memory-leaks)
FreeImage.CloseMemory(hmem);
ms.Close();
// Listos.
return bmp;


En este punto el Bitmap está creado, así que ya sólo queda usar el método fromBitmap de la clase Texture y ya tenemos una textura Direct3D lista para usar.

En mi caso para hacer una prueba rápida cogí el Tutorial #5 de Direct3d (muestra un cilindro texturizado) y lo ejecuté dos veces: una con una textura PCX y otra con un archivo BMP convertido (usé Irfanview) a partir del PCX , y comprobé que en ambos casos el resultado era el mismo.

Nota: Aparte del wrapper "oficial" de FreeImage, que se encuentra en su página oficial, encontré otro wrapper que incluía las definiciones de P/Invoke de todas las funciones de FreeImage que le faltan al wrapper "oficial" además de ofrecer algunas funciones helper adicionales. Este wrapper está en http://hauntedhousesoftware.com/blog/Files/FreeImage.cs

No hay comentarios: