Programación Gráfica 2D ( II )
|
Documentación de SDL, para el que aún no lo conozca: IntroducciónEn el tutorial anterior comenté qué era una superficie, y puse como crear una cargando sus datos desde un archivo. En este la idea es comentar más en detalle qué son las superficies, cómo se manejan realmente, y todo lo que se puede hacer con ellas. En SDL, un SDL_Surface no es más que una estructura definida así: typedef struct SDL_Surface { Uint32 flags; /* Read-only */
“format” se refiere al formato del pixel, lo comentaré más abajo. “w”, y “h” son el ancho y alto en pixels de la superficie. “pitch” es el ancho en bytes de la “scanline” de la superficie en memoria, esto también lo comentaré más abajo. “pixels” es el buffer en memoria con los datos de la propia imagen. “clip-rect” es un rectángulo de clipping. Sirve para definir un área “bliteable”. Lo comentaré en el próximo tutorial. “refcount” es simplemente un contador de referencias a la superficie, y no lo vamos a usar. Creación de una superficieEn el otro tutorial puse el código para crear una superficie a partir de un archivo: SDL_LoadBMP ("archivo.bmp");
En realidad, cuando creemos una superficie, nos interesa tener más control sobre su formato y sus propiedades. Por eso, al crearlas, usaremos la siguiente función: SDL_Surface *SDL_CreateRGBSurface(Uint32 flags, int width, int height, int bitsPerPixel, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); Esto crea una superficie vacía, del tamaño indicado por width y height, y con las propiedades indicadas en flags. Los parámetros de “bitsPerPixel”, y las máscaras dependen del formato del pixel, y lo comentaré más adelante. De momento, vamos a ver los flags: SDL_SWSURFACE: Hace que la superficie se cree en memoria del sistema De momento los flags que nos interesan son los que se refieren al tipo de memoria en que habitará la superficie. Tipos de MemoriasHasta ahora decía que las superficies están “en la memoria”. Pero en realidad, las superficies pueden crearse en dos tipos de memorias distintas: la memoria del sistema o la memoria de video. La memoria del Sistema (SWSURFACE): La memoria de Video (HWSURFACE): Las dos memorias se comunican a través de un bus (el bus AGP por lo general). Eso quiere decir que si hacemos un blit entre dos superficies, una en cada memoria, los datos tendrán que atravesar este bus, lo que es bastante más lento y puede dar lugar a cuellos de botella. NOTA: A la hora de inicializar la librería, podemos elegir en qué tipo de memoria crearemos el Buffer Primario (con SDL_SetVideoMode). Hay que tener en cuenta que si lo creamos en la memoria del sistema, los datos tendrán que atravesar el bus para llegar hasta la tarjeta gráfica y mostrarse en pantalla, aunque SDL se encarga de que esto sea transparente al programador.
Resumiendo, a la hora de elegir memorias, hay que tener en cuenta: Con esto ya está aclarado (o eso espero), el tema de los flags a la hora de crear la superficie. Recuerdo que los otros dos los veremos en el siguiente tutorial. SDL_Surface *SDL_CreateRGBSurface(Uint32 flags, int width, int height, int bitsPerPixel,Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask);
Formatos de PixelsCuando hablamos del formato del pixel, nos referimos a la manera en que un pixel guarda la información del color. En primer lugar está la “profundidad de color”, o los “bits por pixel” (bpp). Esto es simplemente cuantos bits usamos para representar un pixel, y puede ser 8, 16, 24, o 32. A mas bits, mas rango de colores. Con 8 bits solo tenemos 256 colores. Normalmente en este modo se suele usar una paleta, pero no quiero entrar en eso, porque no lo vamos a usar. Con 16 bits o superior se considera representación “TrueColor”. A partir de aquí, lo que se guarda en el pixel son las componentes primarias del color: rojo, verde, y azul, o RGB(Red, Green, Blue). Algunos formatos añaden también otro componente “alpha” que guarda la opacidad, muy útil para hacer efectos de transparencia. A esos formatos se les suele llamar RGBA. Así que lo que diferencia a unos formatos de otros, a parte de la profundidad, es si tienen o no componente “alpha”, y como reparten los bits entre cada uno de los componentes. Imaginad un formato para una profundidad de 32 bits. Teniendo 4 componentes (RGBA), podemos usar 8 bits para cada uno, entonces el pixel sería así: Y las máscaras: 11111111 00000000 00000000 00000000 = Rmask En hexadecimal: Rmask = 0xff000000;
NOTA: Dependiendo del ordenador, hay memorias donde los bits mas significativos se colocan en las posiciones mas altas (es decir, al reves). Este tipo de memoria es la “Little Endian”, y para saber en que modo estamos trabajando hay que consultar a SDL_BYTEORDER Asi que dependiendo de ese indicador usaremos estas mascaras, o unas donde el rojo está en los bits menos significativos, el alpha en los más, etc... #if SDL_BYTEORDER == SDL_BIG_ENDIAN rmask = 0xff000000; gmask = 0x00ff0000; bmask = 0x0000ff00; amask = 0x000000ff; #else rmask = 0x000000ff; gmask = 0x0000ff00; bmask = 0x00ff0000; amask = 0xff000000; #endif
Toda esta información es la que se guarda en la estructura SDL_PixelFormat, que es un dato más de SDL_Surface. Esto quiere decir que cada superficie puede tener un formato de pixel distinto. Al hacer un blit el sistema automáticamente realiza la “traducción” entre formatos. Pero esa traducción tiene un coste en tiempo, claro, asi que lo mejor es evitarla si es posible.
Bueno, explicado el tema de las máscaras, ya sabemos como crear a pelo una superficie con las propiedades y formato que nos de la gana. Pero es un rollo. Hacer todo el tema de las máscaras para cada formato que podamos usar es un auténtico coñazo. Pensemos un momento, lo que nos interesa es que todas las superficie compartan el mismo formato, ¿y qué formato es ese? Pues el que tenga el Buffer Primario. Cuando inicializamos la librería, se asigna un formato al buffer primario. Ese el formato que tenemos que recuperar y usar para cuando creemos nuestras propias superficies. Para obtener ese formato, usaremos una función llamada SDL_GetVideoInfo (). Esto devuelve una estructura SDL_VideoInfo, con un campo llamado vfmt que es precisamente el formato de pixel del buffer primario. Y ese formato es una estructura que contiene las máscaras que tenemos que usar. ¿Suena raro? XD Si mirais el código no es tan complicado: const SDL_VideoInfo *vi; Las dos primeras lineas las ejecutariamos al principio del programa, y después guardaríamos el formato a lo largo de toda la ejecución, para no tener que llamar a GetVideoInfo cada dos por tres. //Cargamos el archivo en la superficie auxiliar Y esto es todo. Tened en cuenta que en los valores de ancho (width), y alto (height), al crear la superficie estoy usando el ancho y alto de la superficie temporal, que son los del propio archivo.
Destrucción de una superficieIgual que se crean, las superficies se destruyen cuando ya no nos hacen falta, liberando la memoria, para eso usamos SDL_FreeSurface ( ) como en el código anterior. Rellenar una superficie de un colorImaginad que queremos llenar una superficie, completamente, de color rojo turquesa. Para ello SDL nos da una función especial: int SDL_FillRect(SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color); dst es la superficie que vamos a rellenar. Y por último, color es el color que usaremos... vale... y como hacemos que ese “int” signifique “rojo turquesa”? Suponiendo que rojo turquesa en RGB sea por ejemplo (255, 120, 120), el valor que le pasamos a FillRect tiene que ser un entero que en el formato de pixel de la superficie se corresponda con el valor RGB que queremos. Aquí podríamos entrar de nuevo con las máscaras y demás, pero por suerte, tenemos una función que se encarga de eso por nosotros: Uint32 SDL_MapRGB(SDL_PixelFormat *fmt, Uint8 r, Uint8 g, Uint8 b); Esta funcón toma un formato de pixel, y los valores RGB (de 0 a 255) del color que queramos. Lo que devuelve es un entero con el color representado en el formato indicado. Es decir, justo lo que necesitamos: SDL_FillRect (superficie, NULL, SDL_MapRGB(superficie->format, 255, 120, 120)); OJO: Como formato de pixel uso el de la superficie, no el que sacamos de “VideoInfo” que había comentado antes. Esto es porque aunque en nuestro caso serán iguales, no tiene por qué ser siempre así. Y para el que le interese añadir el canal alpha, también hay una función MapRGBA que hace lo mismo con un componente más.
|