Tutorial SDL 2 en Español - Capítulo 3

Capítulo 3: Mostrar una Imagen en SDL 2.

En este tema vamos a mostrar una imagen utilizando SDL 2. En el capítulo anterior creamos una ventana, ahora vamos a ponerle una imagen. También, nosotros colocamos todo el código dentro de la función main porque era un programa pequeño, pero lo recomendable es dividir el código en diferentes secciones más pequeñas a las que llamaremos desde la función main. De esta forma se puede reutilizar el código y depurar más fácilmente. Normalmente en un juego real se realiza de esta forma.

Primeramente debemos declarar las funciones de inicialización, carga de medios y cerrado en el principio del código, precisamente después de los archivos de inclusión.
//Declaración de la funciones
bool Inicializar();     // Inicialización de SDL y creación de Ventanas
bool cargarMedios();    // Carga los medios
void cerrar();          // Libera los medios y cierra SDL
La declaración de las funciones se realiza para advertirle al compilador que se utilizarán funciones en el código y de esta forma asignarle la memoria correspondiente a dichas funciones. Es como declarar variables, la diferencia es que se declaran funciones. La declaración de las funciones se escriben cerca del inicio, después de los archivos de encabezado de inclusión. Para declarar funciones simplemente escriba el "esqueleto" de la función, es decir, el tipo de valor que va a devolver, luego el nombre de la función, dentro de los paréntesis los argumentos de la función y luego de cerrar el paréntesis coloque el punto y coma. No se colocan las llaves, ya que no vamos a colocar ningún código en la declaración de la función.
Las funciones que vamos a utilizar son bool Inicializar();, bool cargarMedios(); y void cerrar();. La primera se utilizará para inicializar SDL y crear las ventanas que necesitaremos. La segunda función la usaremos  para cargar la imagen que se querrá mostrar, y la tercera función la emplearemos para liberar los recursos, cerrar SDL y cerrar la ventana.
Luego de esto, vamos a declarar las variables utilizadas en la función, esta vez las declararemos fuera de la función main, es decir, de forma global (como variables globales) ya que vamos a utilizarlas en otras funciones fuera del main.
//Inicialización de la ventana, la superficie y la imagen
SDL_Window* Ventana      = NULL; //La ventana donde se renderizará la imagen
SDL_Surface* Superficie  = NULL; //La superficie que contendrá la ventana
SDL_Surface* bmpImagen   = NULL; //La imagen que se mostrará en la superficie
Aquí inicializamos las variables de forma global (como comenté anteriormente) que se utilizarán para la ventana, la superficie que contendrá la ventana y ahora declaramos otra superficie donde se cargará la imagen que queremos mostrar llamada bmpImagen. Aunque no se recomienda utilizar mucho las variables globales, ya que complican el tratamiento del código y su depuración, lo hago así para simplificar el código ya que es un proyecto pequeño, ya queda en cuenta del programador de cómo debe usar los recursos del sistema y colocar sus variables.
Seguimos en lo que vamos, hemos creado una superficie para cargar la imagen que queremos mostrar, la superficie es un tipo de datos de imagen que contiene un conjunto de pixeles junto con la información necesarias para mostrar una imagen, la acción de mostrar una imagen se llama renderizar. Para renderizar las imáneges en una superficie, SDL utiliza la renderización por software, o lo que es lo mismo, utiliza procesos del CPU para renderizar las imágenes. Se puede renderizar las imágenes por hardware, o sea, utilizando la GPU de una tarjeta de video, pero esto se enseñará en otro tema, porque que es un poco más complicado, y por el momento quiero enseñarles esto de forma más fácil.
Hay que tener en cuenta que las superficies están declaradas como punteros, por que es la forma más fácil y eficiente de administrar la memoria del sistema. Supongamos que queremos mostrar un tablero de ajedrez en un juego, de esta forma solamente cargamos dos imágenes, un cuadro de color claro y otro de color oscuro, lo que hacemos es reutilizar el cuadro claro y luego el cuadro oscuro repetidamente para así mostrar el tablero de ajedrez, solamente utilizando dos imágenes, esto ayuda a conservar memoria, imagina cargar todos y cada una de las imágenes de los cuadros en memoria, esto lentizará el CPU y utilizará mucha memoria. Recuerda inicializar siempre tus punteros, éstos se deben establecer en NULL inmediatamente después de inicializarlos, ya que si no se realiza así, contendrán "datos basura", es decir, contendrán lo que había en la memoria en ese momento y esto puede generar errores dificiles de resolver.
Ahora veremos el código de la primera función que declaramos, es decir, Inicializar:
bool Inicializar(){

    //Bandera de inicialización es correcta
    bool correcto = true;

    //Inicializa el subsistema de Video
    if(SDL_Init(SDL_INIT_VIDEO) < 0){
        printf("ERROR: No se pudo inicializar SDL, Error SDL: %s\n", SDL_GetError());
        correcto = false;
    }
    else{
        //Se crea la ventana principal
        Ventana = SDL_CreateWindow("Tutorial SDL 2", 50, 50, 640, 480, SDL_WINDOW_SHOWN);
        if(Ventana == NULL){
            printf("ERROR: No se pudo crear la ventana, SDL_Error: %s\n", SDL_GetError());
            correcto = false;
        }
        else{
            //Se crea la superficie para la ventana principal
            Superficie = SDL_GetWindowSurface(Ventana);
        }
    }

    //Salimos de la función
    return correcto;
}
Como podemos observar, la función bool Inicializar() retorna un valor booleano, es decir, dos estados true o false y no contiene argumentos. La función devuelve true si finaliza correctamente, devuelve false cuando hay algún error. Para esto la bandera correcto nos será útil. Una bandera es una variable o algún bit de alguna variable, utilizado para establecer y mostrar algún estado, en este caso si hubo algún error o no. Luego inicializamos SDL y el subsistema de video, (observe que después de la función prinft que reporta el error, cambiamos la bandera correcto a false), ahora creamos la ventana y le asignamos la superficie. Todo esto en el capítulo anterior lo habíamos hecho en la función main, aquí lo hacemos en esta función. Ahora veremos la siguiente función.
bool cargarMedios(){
    //Bandera de carga correcta
    bool correcto = true;

    //Carga la imagen que se mostrará
    bmpImagen = SDL_LoadBMP("mapaBits.bmp");
    if( bmpImagen == NULL){
        printf("No se pudo cargar la imagen %s SDL Error: %s\n", "mapaBits.bmp", SDL_GetError());
        correcto = false;
    }
    //Salimos de la función
    return correcto;
}
Esta función devuelve un valor booleano y no posee argumentos. También usa una bandera correcto que se inicializará con el valor true, si en el camino encuentra algún error, entonces se le cambiará el valor a false. Para cargar la imagen desde un archivo en la superficie bmpImagen utilizamos la función SDL_LoadBMP, esta función tiene un parámetro de cadena donde va la ubicación y el nombre del archivo. Si todo resulta correcto la imagen se almacenará en bmpImagen.
Seguimos entonces con la explicación de la tercera función que habíamos declarado.
void cerrar(){
    //Liberamos la superficie utilizada
    SDL_FreeSurface(bmpImagen);
    bmpImagen = NULL;

    //Destruimos la Ventana para liberar recursos.
    SDL_DestroyWindow(Ventana);

    //Quitamos el subsistema de SDL.
    SDL_Quit();
}
En esta función simplemente liberaremos recursos y cerraremos las ventanas y SDL. Observe la llamada a la función SDL_FreeSurface(bmpImagen);, esta función libera los recursos utilizados por la superficie donde almacenamos la imagen. Las demás funciones fueron explicadas en el tema anterior. Ya que hemos establecido las funciones necesarias de la aplicación, necesitamos llamarlas, y como la función main es la que siempre se ejecuta al iniciar, vamos a llamarlas desde esta función como se ve en el siguiente código:
int main(int argc, char* args[]){
    //Inicializa SDL y crea las ventanas
    if(!Inicializar()){
        printf("No se pudo inicializar\n");
    }
    else{
        //Carga los medios
        if(!cargarMedios()){
            printf("No se pudo cargar los medios\n");
        }
        else{
            //Aplicamos la imagen en la superficie de la ventana
            SDL_BlitSurface(bmpImagen, NULL, Superficie, NULL);

            //Actualizamos la superficie de la ventana
            SDL_UpdateWindowSurface(Ventana);

            //Esperamos 5 segundos
            SDL_Delay(5000);
        }
    }

    //Liberamos los recursos y cerramos SDL
    cerrar();

    //Salimos de main
    return 0;
}
Observamos que dentro de la función main llamamos primero a la función Inicializar(), como ella devuelve un valor booleano, la verificamos con el if y mostramos un mensaje de error si no se ejecuta correctamente, de lo contrario se ejecutará el código que está en el else. Luego, llamamos la función cargarMedios(). Aquí podemos ver la función SDL_BlitSurface, esta función se encarga de volcar una superficie en otra, con esto volcamos la superficie de la imagen en la superificie que está en la ventana. Esta función posee 4 parámetros, el primero es la superficie "fuente", o sea la superficie que se quiere volcar, el segundo parámetro es su área, como queremos volcar toda el área, lo colocamos NULL, el tercer y cuarto parámetro es la superficie "destino" y su área, respectivamente, es decir la superficie donde va destinada el "volcado" o donde se desea copiar la imagen. Luego de esto, debemos actualizar la superficie destino para que los cambios se muestren, para eso utilizamos la función SDL_UpdateWindowSurface. Esta función actualiza las superficies para que se puedan mostrar los cambios realizados. Y por último vamos a mostrar la imagen algunos segundos, esto lo hacemos con la función SDL_Delay. Ahora para terminar llamamos a la función cerrar que apaga SDL y libera los recursos de las ventanas y las superficies utilizadas, luego salimos con return que le indica al sistema operativo que el programa finalizó.
El código fuente debe quedar de la siguiente forma:
//Encabezados de inclusión utilizado en nuestros programas
#include <SDL.h>
#include <stdio.h>

//Declaración de la funciones
bool Inicializar();     // Inicialización de SDL y creación de Ventanas
bool cargarMedios();    // Carga los medios
void cerrar();          // Libera los medios y cierra SDL

//Inicialización de la ventana, la superficie y la imagen
SDL_Window* Ventana      = NULL; //La ventana donde se renderizará la imagen
SDL_Surface* Superficie  = NULL; //La superficie que contendrá la ventana
SDL_Surface* bmpImagen   = NULL; //La imagen que se mostrará en la superficie

bool Inicializar(){

    //Bandera de inicialización es correcta
    bool correcto = true;

    //Inicializa el subsistema de Video
    if(SDL_Init(SDL_INIT_VIDEO) < 0){
        printf("ERROR: No se pudo inicializar SDL, Error SDL: %s\n", SDL_GetError());
        correcto = false;
    }
    else{
        //Se crea la ventana principal
        Ventana = SDL_CreateWindow("Tutorial SDL 2", 50, 50, 640, 480, SDL_WINDOW_SHOWN);
        if(Ventana == NULL){
            printf("ERROR: No se pudo crear la ventana, SDL_Error: %s\n", SDL_GetError());
            correcto = false;
        }
        else{
            //Se crea la superficie para la ventana principal
            Superficie = SDL_GetWindowSurface(Ventana);
        }
    }

    //Salimos de la función
    return correcto;
}

bool cargarMedios(){
    //Bandera de carga correcta
    bool correcto = true;

    //Carga la imagen que se mostrará
    bmpImagen = SDL_LoadBMP("mapaBits.bmp");
    if( bmpImagen == NULL){
        printf("No se pudo cargar la imagen %s SDL Error: %s\n", "mapaBits.bmp", SDL_GetError());
        correcto = false;
    }
    //Salimos de la función
    return correcto;
}

void cerrar(){
    //Liberamos la superficie utilizada
    SDL_FreeSurface(bmpImagen);
    bmpImagen = NULL;

    //Destruimos la Ventana para liberar recursos.
    SDL_DestroyWindow(Ventana);

    //Quitamos el subsistema de SDL.
    SDL_Quit();
}

int main(int argc, char* args[]){
    //Inicializa SDL y crea las ventanas
    if(!Inicializar()){
        printf("No se pudo inicializar\n");
    }
    else{
        //Carga los medios
        if(!cargarMedios()){
            printf("No se pudo cargar los medios\n");
        }
        else{
            //Aplicamos la imagen en la superficie de la ventana
            SDL_BlitSurface(bmpImagen, NULL, Superficie, NULL);

            //Actualizamos la superficie de la ventana
            SDL_UpdateWindowSurface(Ventana);

            //Esperamos 5 segundos
            SDL_Delay(5000);
        }
    }

    //Liberamos los recursos y cerramos SDL
    cerrar();

    //Salimos de main
    return 0;
}
Hasta aquí hemos visto cómo se muestra una imagen con SDL, también vimos como se "vuelca" o se "copia" el contenido de una superficie a otra. Puedes descargar el código fuente completo aquí.

No hay comentarios:

Publicar un comentario