Miércoles, Febrero 7, 2007

Cargar texturas con ImageMagick

Cargar texturas en OpenGL implica leer una textura, crear un objeto de textura y subir los texels a la gráfica. Los dos últimos pasos, el de crear y subir la información, no son complicados o por lo menos siempre se hacen igual, pero leer una imagen de disco suele ser un quebradero de cabeza si pretendemos hacerlo a mano. Claro está, no me refiero a leer de disco a secas, me refiero a leer imágenes con formato ya sea jpg, png, svg, etc.

Típicamente en el mundillo de los gráficos desde tiempos inmemoriales se ha venido usando un set de librerías conocido por DevIL ( que inicialmente se llamaba OpenIL aunque por problemas con SGI cambiaron el nombre). Esta librería es además de práctica muy sencilla de utilizar y entona muy bien con openGL por estar basada en una máquina de estados y estar separada en varios niveles de abstracción IL, ILU, ILUT (como GL, GLU, y GLUT).

Pero si queremos usar algo más potente de DevIL podemos usar ImageMagick. Con ImageMagick ganaremos no sólo un cargador, además ganamos un editor, compositor y conversor de imágenes con la ventaja de que ImageMagick probablemente ya viene instalada en las ubuntu/debian/*nix normales ( y si no viene, sólo hay que enseñar a usar convert o cualquiera de las command-line-tools de ImageMagick ).

Veamos un breve, rapidísimo tutorial de cómo cargar una imagen con Imagemagick. Vamos a usar la interfaz “C” de magick-wand, también existe una para C++ (así como para python, perl, php,…) pero implicaría tener que añadir una dependencia extra… y la verdad es que Magick-Wand es suficientemente fácil de usar por sí misma.


#include <wand/magick_wand.h>
int main(...) {
MagickWandGenesis();
MagickWand *wand = NewMagickWand();
// ...
wand = DestroyMagickWand(wand);
MagickWandTerminus();
}

Las llamadas MagickWandGenesis y MagickWandTerminus inicializan y finalizan la librería, una vez hecho con NewMagickWand y DestroyMagickWand podemos crear y liberar varitas (mágicas) para trabajar con imágenes, transformalas, etc… Lo siguiente consiste en cargar la imagen de un fichero o un BLOB de memoria:


// opcion 1: leer de fichero dado un path
MagickBooleanType status = MagickReadImage ( wand, path );
// opcion 2: leer de un puntero a memoria (data) de tamaño conocido (size)
MagickBooleanType status = MagickReadImageBlob( wand, data, size );
if ( status != MagickFalse )
{
}

Las dos llamadas de MagickReadImage[Blob] hacen lo mismo, leen una imagen y además lo hacen identificando el tipo de imagen que es. Esto lo digo concretamente por la versión “blob”, no hay que decirle si es un png, jpg, ppm, svg… Imagemagick lo identificará por la cabecera del fichero, si usáis vuestro propio cargador de ficheros no tenéis más que leer el fichero entero sin preocuparos del tipo de fichero y MagickReadImageBlob intentará leerlo.
También existe la versión MagickReadImageFile que recibe un descriptor de fichero tipo FILE*
nota: en vuestro caso particular sólo hay que usar una de las dos opciones, claro está :)

El resultado es un booleano, si todo a ido bien sólo queda recuperar esa información y subirlo a la textura. En este caso vamos a tener en cuenta si la imagen tiene alfa o no (para ImageMagick es ver si tiene opacidad):


unsigned long width = MagickGetImageWidth( wand );
unsigned long height = MagickGetImageHeight( wand );
unsigned char *data = 0;
MagickFlipImage( wand );
// reservamos para una textura y la activamos
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
if ( MagickGetImageChannelDepth(wand, OpacityChannel) > 1 )
{
// con alfa
data = new unsigned char[width*height*4];
MagickGetImagePixels(wand, 0,0, width, height, "RGBA", CharPixel, data);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
}
else
{
// sin alfa
data = new unsigned char[width*height*3];
MagickGetImagePixels(wand, 0,0, width, height, "RGB", CharPixel, data);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
}
delete[] data;

Este ya es un buen cacho, primero obtenemos ancho y alto de la imagen, hacemos un flip de la imagen (ya que en OpenGL las imágenes empiezan abajo a la izquierda – pitfall 12) y reservamos una textura de OpenGL. Después preguntamos a la varita si la imagen tiene alfa, en realidad le preguntamos si la profundidad del canal de opacidad es mayor que 1, si es así reservamos espacio para guardarnos la información de la imagen en formato RGBA y lo subimos a la textura. Si no tiene alfa pues lo mismo pero en formato RGB. Y como somos muy limpios, al final un delete[] para liberar la memoria temporal donde guardamos la imagen.

Y finalmente el ejemplo más o menos completo del tema… Para compilarlo en linux no hay que olvidar que tenemos un mágico `Wand-config --cppflags --cflags --ldflags --libs` para ayudarnos.


#include <GL/gl.h>
#include<wand/magick_wand.h>
int main(...) {
// inicializamos openGL o lo que haga falta...
GLuint texture = 0; // nuestra textura
MagickWandGenesis();
MagickWand *wand = NewMagickWand();
// opcion 1: leer de fichero dado un path
MagickBooleanType status = MagickReadImage ( wand, path );
// opcion 2: leer de un puntero a memoria (data) de tamaño conocido (size)
MagickBooleanType status = MagickReadImageBlob( wand, data, size );
if ( status != MagickFalse )
{
unsigned long width = MagickGetImageWidth( wand );
unsigned long height = MagickGetImageHeight( wand );
unsigned char *data = 0;
MagickFlipImage( wand );
// reservamos para una textura y la activamos
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
if ( MagickGetImageChannelDepth(wand, OpacityChannel) > 1 )
{
// con alfa
data = new unsigned char[width*height*4];
MagickGetImagePixels(wand, 0,0, width, height, "RGBA", CharPixel, data);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
}
else
{
// sin alfa
data = new unsigned char[width*height*3];
MagickGetImagePixels(wand, 0,0, width, height, "RGB", CharPixel, data);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
}
delete[] data;
}
wand = DestroyMagickWand(wand);
MagickWandTerminus();
}

posteado por PpluX @ 1:12 pm tags:C/C++, OpenGL, Programación, recetas  

Jueves, Noviembre 30, 2006

Depurando con CGDB

Depurar con gdb no es realmente complicado, al principio uno se siente tentado de usar printf’s(C)/std::couts(C++) como método de depuración, pero no!, invertir tiempo en aprender a usar mínimamente gdb es más práctico, más eficiente y además ahorra mucho tiempo. Hay mil manuales sobre gdb, aquí os propongo dos que he encontrado buscando a bote pronto, gdb-quickreference y esta guía, aunque la documentación oficial es genial.

En cualquier caso, ya de puestos, y suponiendo que os habéis mirado algo de gdb, buscando interfaces gráficas para el gdb encontré este que me ha gustado especialmente, cgdb. Es básicamente un gdb, en ncurses, pero con una ventana para ver el código con resaltado de sintaxis y la posibilidad de abrir el tty de la aplicación en otra subventana (para no mezclar la entrada/salida del programa con la linea de comandos del depurador).

Si ya sabes gdb, esto solamente será azúcar para tu debug :)

posteado por PpluX @ 3:55 pm tags:C/C++, Programación  

Domingo, Noviembre 19, 2006

Vim y plantillas

Aunque no se muy bien cómo titular esta entrada, este es un pequeño truco para desarrolladores. Vamos a tomar como ejemplo C++ y que cada uno lo extienda a su caso particular. Casi siempre en un proyecto el esqueleto de una cabecera para una clase y de su fichero cpp es muy similar, tanto que yo siempre acabo por crearme un “Dummy.h” y un “Dummy.cpp”, con esta pinta:

#ifndef _MyProject_DUMMY_HPP_
#define _MyProject_DUMMY_HPP_
#include "Referenced.h"
namespace MyProject {
    class Dummy : public Referenced
    {
    public:
        Dummy();
    protected:
        ~Dummy();
        Dummy & operator= (const Dummy&) { return *this; }
    private:
    };
} // end of MyProject namespace
#endif
//------ y este es el cpp --------------
#include <MyProject/Dummy.h>
namespace MyProject {
} // end of MyProject namespace

El código en si no es lo importante, en lo que hay que fijarse es que si usamos este fichero como plantilla hay mucho “Dummy” que cambiar por el nombre de la nueva clase. Para ello nada más fácil que:

:%s/Dummy/MiClase/g
:%s/DUMMY/MICLASE/g

El primero va bien, el segundo también, pero cuando llevas 15 te hartas de los “:%s”, y aquí viene el truco, dejemos que vim haga su faena:

function Dummy()
    let name = expand("%:t:r")
    execute "%s/DUMMY/".toupper(name)."/g"
    execute "%s/Dummy/".name."/g"
endfunction

Se invoca con “: call Dummy()” y hace toda la faena por ti tomando como nombre a reemplazar el nombre del fichero que estamos editando.

posteado por PpluX @ 12:42 pm tags:Programación, Vim, recetas  

Sábado, Noviembre 18, 2006

Glee con soporte para OpenGL 2.1

Glee es una pequeña librería multiplataforma + cabecera (gl.h) que permite programar siempre con la última versión de la especificación de OpenGL, en este caso la versión 2.1.

Glee cargará por ti dinámicamente aquellas extensiones que soporte tu tarjeta, sólo hay que tener en cuenta que aunque estés progrmando contra el API de OpenGL 2.1, es posible que tu tarjeta NO soporte ciertas llamadas, o ciertos formatos… las comprobaciones de las capabilities de la tarjeta se tienen que seguir haciendo ( en tiempo de ejecución ).

En resumen, el que prueba esta librería no vuelve a complicarse la vida con las extesiones de OpenGL.

Desde: OpenGL.org

posteado por PpluX @ 10:16 am tags:C/C++, OpenGL, Programación  

Lunes, Noviembre 6, 2006

Sorpresa RTTI funciona.

El RTTI(RunTime Type Identification) se puede usar para muchas cosas, lo más típico es invertir la herencia con un dynamic_cast:

    Base *ptr_base = ...;
    Derived *ptr = dynamic_cast<Derived*>(ptr_base);

Aquí las clase Derived hereda de Base, y un dynamic_cast permite saber si un puntero de tipo Base es en realidad de la clase Derived o no. Si devuelve algo distinto de 0 es que la conversión ha tenido éxito.

También se suele usar mucho el Typeid, tyepid( variable ) devuelve una clase que representa el tipo de esa clase, eso permite que los tipos se puedan comparar en tiempo de ejecución, obtener una representación en string del nombre de la clase typeid(variable).name()… etc.

Lo que no tenía tan claro es qué pasa cuando se hace algo como esto:

    Base *ptr = ... ;
    std::cout << typeid(*ptr).name() << std::endl;

¿Cuál es el resultado?
(más…)

posteado por PpluX @ 12:27 pm tags:C/C++, Programación  

Jueves, Agosto 31, 2006

osgLua

osgLua es un wrapper de Lua para osg usando introspección. Lo mejor es que todo se hace al vuelo, dinámicamente se consulta a osgIntrospection según las necesidades. De esta forma si algo cambia en osg no hay que tocar nada de código de osgLua para que funcione. No creo que a nadie le interese demasiado las interioridades… pero algún día se puede plantear cómo funciona.

Puedes bajarlo aquí: http:://svn.pplux.com/lab/osgLua

Update yo quería esperar a tenerlo algo más avanzado, pero ya se ha anunciado oficialmente ;)

posteado por PpluX @ 1:36 pm tags:Lua, OSG, Programación  

Martes, Agosto 29, 2006

Jartá reír (x2)

Lo que me he podido reír al ver esta entrada del Daily WTF (What the fuck!?). Resumiendo ¿ qué pasa si tocas el fichero de licencia de un programa que ha expirado y te sale esta ventana?
WTF!!!!?

Leed la historia completa, no tiene desperdicio.

Update: Y via menéame esta otra, buenísima para empezar la mañana:
sudo

posteado por PpluX @ 7:40 pm tags:Programación, humor  

Jueves, Junio 8, 2006

Sinergia en C/C++

Según la RAE Sinergia es : “Acción de dos o más causas cuyo efecto es superior a la suma de los efectos individuales.”. Vamos que la suma del todo es mayor que el de las partes. Recientemente me he topado (otra vez) con este efecto programando, cómo somos viejos conocidos os lo voy a presentar. Se llama padding y suele venir de serie en las estructuras y clases de C o C++.

Veamos un ejemplo:
(más…)

posteado por PpluX @ 10:50 pm tags:C/C++, Programación  
« Entradas anterioresEntradas siguientes »

Gestionado con WordPress