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
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.
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
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
Luego de esto, vamos a declarar las variables utilizadas en la función, esta vez las declararemos fuera 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
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
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
Ahora veremos el código de la primera función que declaramos, es decir,
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;
}
//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;
}
//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
Seguimos entonces con la explicación de la tercera función que habíamos declarado.
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();
}
//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;
}
//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
El código fuente debe quedar de la siguiente forma:
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;
}
#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