Dans ce chapitre, nous allons traiter des textures. Maintenant que nous savons comment nous occuper du rendu de notre fenêtre, il nous faut voir comment faire des dessins plus évolués et c’est le but des textures. Pour commencer, nous pouvons voir les textures comme de simples paquets de pixels qu’on va coller sur la fenêtres. Par exemple, nous pourrions charger les pixels d’une image dans une texture et ensuite coller cette texture sur l’écran c’est-à-dire afficher l’image.
Les textures
Généralités sur les textures
Une texture est une structure SDL_Texture
. Nous avons dit qu’on pouvait le voir comme un paquet de pixels (un rectangle plus précisément). Ce paquet de pixels, on pourra l’afficher, le modifier, etc.
Créer une texture
Voyons maintenant comment créer une texture. Cette opération se fait à l’aide de la fonction SDL_CreateTexture
. Son prototype :
SDL_Texture* SDL_CreateTexture(SDL_Renderer* renderer,
Uint32 format,
int access,
int w,
int h)
Elle prend en paramètre :
renderer
est le renderer auquel on veut que notre texture soit associée.format
correspond au format de pixel (il en existe plusieurs donnés dans l’énumérationSDL_PixelFormatEnum
). Nous allons généralement choisir la valeurSDL_PIXELFORMAT_RGBA8888
qui correspond à la représentation que nous connaissons avec 4 chiffres entre 0 et 255.access
correspond aux restrictions d’accès de notre structure. On peut lui donner trois valeurs (voir l’énumérationSDL_TextureAccess
).w
eth
correspondent à la largeur (width) et à la hauteur (height) de la texture (qui est, rappelons le, une sorte de rectangle de pixels).
Les trois valeurs possibles pour access
sont celles-ci.
valeur | Description | |
---|---|---|
| La texture est rarement modifiée | |
| La texture est souvent modifiée | |
| La texture peut être utilisée comme cible de rendu (comme un renderer) |
Celles que nous allons utiliser ici sont SDL_TEXTUREACCESS_TARGET
et SDL_TEXTUREACCESS_STREAMING
.
La fonction SDL_CreateTexture
renvoie un pointeur sur SDL_Texture
. Si la création de la texture échoue, ce pointeur vaut NULL
, sinon il pointe sur la texture créée (il ne faudra donc pas oublier de récupérer et tester la valeur retournée).
Détruire une texture
Nous pouvions le deviner. Il faut détruire la texture une fois qu’on a fini de l’utiliser. Cette action se fait avec la fonction SDL_DestroyTexture
dont le prototype est :
void SDL_DestroyTexture(SDL_Texture* texture)
Comme d’habitude, la fonction de destruction ne renvoie rien et prend en paramètre la texture à détruire. On peut maintenant écrire notre code.
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_Texture *texture = NULL;
int statut = EXIT_FAILURE;
if(0 != SDL_Init(SDL_INIT_VIDEO))
{
fprintf(stderr, "Erreur SDL_Init : %s", SDL_GetError());
goto Quit;
}
if(0 != SDL_CreateWindowAndRenderer(640, 480, SDL_WINDOW_SHOWN, &window, &renderer))
{
fprintf(stderr, "Erreur SDL_CreateWindowAndRenderer : %s", SDL_GetError());
goto Quit;
}
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_TARGET, 200, 200);
if(NULL == texture)
{
fprintf(stderr, "Erreur SDL_CreateTexture : %s", SDL_GetError());
goto Quit;
}
statut = EXIT_SUCCESS;
SDL_Delay(3000);
Quit:
if(NULL != texture)
SDL_DestroyTexture(texture);
if(NULL != renderer)
SDL_DestroyRenderer(renderer);
if(NULL != window)
SDL_DestroyWindow(window);
SDL_Quit();
return statut;
}
Notons que nous pourrions faire une fonction qui se chargerait de toutes les initialisations et des créations nécessaires.
Dessiner sur une texture
Nous avons précédemment dit que si l’on utilisait la valeur SDL_TEXTUREACCESS_TARGET
, c’était pour pouvoir utiliser notre texture comme un renderer. Pour être plus précis, c’est pour pouvoir utiliser notre texture comme cible (« target ») de rendu. En fait, nous allons utiliser les fonctions de dessin vu au chapitre précédent, mais la cible du rendu ne sera plus le renderer mais la texture. Pour changer la cible de rendu, nous allons utiliser la fonction SDL_SetRenderTarget
. Son prototype :
int SDL_SetRenderTarget(SDL_Renderer* renderer,
SDL_Texture* texture)
Elle prend en paramètre un renderer et une texture. Elle retourne 0
en cas de succès et une valeur négative en cas d’erreur (une erreur peut par exemple être que la texture passée en paramètre n’a pas été créée avec le paramètre SDL_TEXTUREACCESS_TARGET
).
Après avoir appelé cette fonction, toutes les fonctions de dessin modifiant le renderer modifieront la texture passée en paramètre. Ainsi, pour dessiner sur notre texture, nous allons :
- Appeler
SDL_SetRenderTarget
pour que notre texture soit la cible de rendu ; - Faire nos dessins ;
- Remettre le renderer en tant que cible de rendu.
Pour faire la troisième étape, il suffit d’appeler la fonction SDL_SetRenderTarget
en lui passant NULL
comme second argument.
Par exemple, dessinons un carré sur une texture.
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCES_TARGET, 200, 200);
SDL_Rect rect = {50, 50, 100, 100};
SDL_SetRenderDrawColor(renderer, 150, 0, 150, 255); /* On dessine en violet */
SDL_SetRenderTarget(renderer, texture); /* On va dessiner sur la texture */
SDL_RenderFillRect(renderer, &rect);
SDL_SetRenderTarget(renderer, NULL);
En essayant ce code, on se rend compte que même après avoir mis à jour le renderer, la fenêtre garde la même couleur, cela veut bien dire que le dessin ne s’est pas fait sur elle. Nous verrons bientôt comment afficher une texture et ainsi afficher les modifications effectuées.
Notons qu’il existe une fonction SDL_GetRenderTarget
qui permet d’obtenir la cible de rendu (donc de savoir si les dessins se font sur le renderer ou sur une texture). Son prototype :
SDL_Texture* SDL_GetRenderTarget(SDL_Renderer* renderer)
Elle prend en paramètre un renderer et retourne un pointeur sur SDL_Texture
qui correspond à la texture qui est la cible de rendu. Si la cible de rendu est le renderer lui-même, la fonction renverra NULL
. Cette fonction peut être utile dans le cas où on veut, dans une fonction, obtenir l’adresse de la cible du rendu (ce n’est donc pas la peine de la passer en paramètre) ou encore si on a plusieurs textures et qu’on ne sait pas laquelle est la cible du rendu. Pour le moment, elle ne nous sera pas utile.
Afficher une texture
Afficher une texture consiste à copier la texture sur le renderer puis à mettre à jour le renderer. Ainsi, on verra bien la texture à l’écran. La copie de la texture se fait avec la fonction SDL_RenderCopy
dont le prototype est :
int SDL_RenderCopy(SDL_Renderer* renderer,
SDL_Texture* texture,
const SDL_Rect* srcrect,
const SDL_Rect* dstrect)
Elle prend en paramètre :
- le renderer sur lequel doit être fait la copie ;
- la texture à copier ;
- un pointeur sur un rectangle qui correspond à la partie de la texture à copier (en passant
NULL
, on copie toute la texture) ; - un pointeur sur un rectangle qui correspond à l’endroit du renderer où doit être copié la texture (en passant
NULL
, la texture remplira tout le renderer).
La fonction retourne 0 en cas de succès et une valeur négative en cas d’erreur.
Faisons quelques tests pour voir comment elle fonctionne (on supposera avoir une texture de longueur et de hauteur 50
et un renderer associé à une fenêtre de longueur 600
et de hauteur 480
).
SDL_Rect dst = {0, 0, 50, 50};
SDL_RenderCopy(renderer, texture, NULL, &dst);
Ce cas est le plus simple. On copie toute la texture dans un rectangle de même dimension qui est placé dans le coin en haut à gauche du renderer.
SDL_Rect dst = {0, 0, 20, 30};
SDL_Rect src = {10, 10, 20, 30};
SDL_RenderCopy(renderer, texture, &src, &dst);
Ici, on ne copie qu’une partie de la texture. On copie cette partie dans un rectangle qui a les même dimensions et qui est placé en haut à gauche.
SDL_Rect dst = {0, 0, 40, 60};
SDL_Rect src = {10, 10, 20, 30};
SDL_RenderCopy(renderer, texture, &src, &dst);
Ici, nous copions une partie de la texture, mais nous la copions dans un rectangle deux fois plus grand. Résultat : ce qui est affiché est la texture redimensionnée (étirée) pour remplir le rectangle de destination. C’est toujours ce qui se passera, la partie de la texture à afficher sera redimensionnée pour remplir le rectangle de destination. Voyons un dernier exemple.
SDL_RenderCopy(renderer, texture, NULL, NULL);
Ici, on copie toute la texture, et on veut qu’elle remplisse tout le renderer. Notre texture sera donc redimensionnée pour remplir le renderer.
La vraie texture n’est pas redimensionnée. Il s’agit d’un redimensionnement à la volée, au moment de la copie. Notre variable texture
n’est pas modifiée.
Tout ce que nous venons de dire à propos du redimensionnement implique que pour afficher une texture dans ses vraies dimensions, il faut connaître ses dimensions. Si nous ne les avons pas, il est possible de les récupérer avec la fonction SDL_QueryTexture
comme nous allons le faire dans le code qui suit.
SDL_Rect dst = {0, 0, 0, 0};
SDL_QueryTexture(texture, NULL, NULL, &dst.w, &dst.h);
SDL_RenderCopy(renderer, texture, NULL, &dst);
Comme nous aurions pu le deviner grâce au code précédent, le prototype de SDL_QueryTexture
est :
int SDL_QueryTexture(SDL_Texture* texture,
Uint32* format,
int* access,
int* w,
int* h)
Elle renvoie 0 en cas de succès et une valeur négative en cas d’erreur et prend en paramètre la texture dont on veut les paramètres et quatre pointeurs qui seront remplis avec, dans l’ordre, le format d’accès de la texture, son type d’accès, sa largeur et sa hauteur.
Dans le code suivant, nous avons passé format
et access
à NULL
car leur valeur ne nous intéresse pas. Celui-ci crée une surface, dessine dessus (fond bleu et rectangle rouge dans le coin en bas à droite) et la colle à l’écran.
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_Texture *texture = NULL;
int statut = EXIT_FAILURE;
SDL_Rect rect = {100, 100, 100, 100}, dst = {0, 0, 0, 0};
SDL_Color rouge = {255, 0, 0, 255}, bleu = {0, 0, 255, 255};
if(0 != SDL_Init(SDL_INIT_VIDEO))
{
fprintf(stderr, "Erreur SDL_Init : %s", SDL_GetError());
goto Quit;
}
if(0 != SDL_CreateWindowAndRenderer(640, 480, SDL_WINDOW_SHOWN, &window, &renderer))
{
fprintf(stderr, "Erreur SDL_CreateWindowAndRenderer : %s", SDL_GetError());
goto Quit;
}
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_TARGET, 200, 200);
if(NULL == texture)
{
fprintf(stderr, "Erreur SDL_CreateTexture : %s", SDL_GetError());
goto Quit;
}
SDL_SetRenderTarget(renderer, texture);
/* La texture est la cible de rendu, maintenant, on dessine sur la texture. */
SDL_SetRenderDrawColor(renderer, bleu.r, bleu.g, bleu.b, bleu.a);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, rouge.r, rouge.g, rouge.b, rouge.a);
SDL_RenderFillRect(renderer, &rect); /* On dessine un rectangle rouge sur la texture. */
SDL_SetRenderTarget(renderer, NULL); /* Le renderer est la cible de rendu. */
/* On récupère les dimensions de la texture, on la copie sur le renderer
et on met à jour l’écran. */
SDL_QueryTexture(texture, NULL, NULL, &dst.w, &dst.h);
SDL_RenderCopy(renderer, texture, NULL, &dst);
SDL_RenderPresent(renderer);
statut = EXIT_SUCCESS;
SDL_Delay(3000);
Quit:
if(NULL != texture)
SDL_DestroyTexture(texture);
if(NULL != renderer)
SDL_DestroyRenderer(renderer);
if(NULL != window)
SDL_DestroyWindow(window);
SDL_Quit();
return statut;
}
Les surfaces
Un vestige de la SDL 1
Nous allons maintenant parler des surfaces. Les surfaces sont l’équivalent des textures dans les anciennes versions de la SDL. On représente une surface avec le type SDL_Surface
. La plus grande différence entre les surfaces et les textures est que les textures sont gérées par le GPU et les surfaces par le CPU. Cela donne quelques avantages aux textures qui sont ainsi affichées plus rapidement que ne l’étaient les surfaces et qui peuvent être redimensionnés à l’affichage (comme nous l’avons vu précédemment).
Les surfaces restent cependant utiles. Par exemple, elles peuvent être modifiées pixel par pixel plus facilement que les textures. Mais là où elles nous seront utiles, c’est pour gérer les images.
Créer une surface
La création de surface se fait avec la fonction SDL_CreateRGBSurface
. Son prototype :
SDL_Surface* SDL_CreateRGBSurface(Uint32 flags,
int width,
int height,
int depth,
Uint32 Rmask,
Uint32 Gmask,
Uint32 Bmask,
Uint32 Amask)
Elle prend en paramètre :
- une liste de drapeaux (ce paramètre doit être à 0) ;
- la largeur de la surface ;
- la hauteur de la surface ;
- le nombre de bits par pixels (en général, on travaille avec 32 bits par pixels) ;
- les quatre derniers paramètres permettent à la SDL de savoir comment extraire la couleur de chaque pixel. Nous allons passer la valeur 0 pour ces quatre paramètres.
La fonction retourne un pointeur sur SDL_Surface
qui vaudra NULL
en cas d’échec de la fonction et qui dans le cas contraire pointe sur la surface créée.
Détruire une surface
Comme les autres ressources, il nous faut détruire chaque surface créée. La libération des données se fait avec la fonction SDL_FreeSurface
dont le prototype est :
void SDL_FreeSurface(SDL_Surface* surface)
Elle ne renvoie rien et prend en paramètre la surface à libérer.
Finalement, avec le code qui suit, on crée une surface de longueur 300
et de hauteur 200
.
/* Le début de notre code */
SDL_Surface *surface = NULL;
surface = SDL_CreateRGBSurface(0, 300, 200, 32, 0, 0, 0, 0);
if(NULL == surface)
{
fprintf(stderr, "Erreur SDL_CreateRGBSurface : %s", SDL_GetError());
goto Quit;
}
SDL_FreeSurface(surface);
/* La fin de notre code */
Opérations sur les surfaces
Colorer une surface
La SDL nous offre des fonctions pour dessiner sur des surfaces, mais il y en a beaucoup moins que celles pour dessiner sur les textures. En fait, il n’y en a qu’une seule. Il s’agit de la fonction SDL_FillRect
qui nous permet, comme son nom l’indique, de « remplir » un rectangle avec une couleur. Son prototype :
int SDL_FillRect(SDL_Surface* dst,
const SDL_Rect* rect,
Uint32 color)
Elle prend en paramètre la surface qui doit être remplie, un pointeur sur SDL_Rect
qui représente la partie de la surface à remplir (en passant NULL
, on demande à remplir toute la surface) et la couleur voulue. Elle retourne 0
en cas de succès et une valeur négative en cas d’erreur.
Notons que la couleur passée en paramètre doit avoir le même format que la surface. Nous devons donc transformer le triplet RGB
représentant notre nombre. Pour ce faire, nous allons utiliser la fonction SDL_MapRGB
. Son prototype :
Uint32 SDL_MapRGB(const SDL_PixelFormat* format,
Uint8 r,
Uint8 g,
Uint8 b)
Elle prend en paramètre un format de pixel et les trois composantes d’une couleur et retourne un pixel de cette couleur dans le format voulu.
Le format d’une surface est obtenu avec le champ format
de cette surface.
Par exemple, pour colorer une surface en rouge, nous pouvons utiliser le code suivant.
SDL_Surface *surface;
surface = SDL_CreateRGBSurface(0, 100, 100, 32, 0, 0, 0, 0);
SDL_FillRect(surface, NULL, SDL_MapRGB(s->format, 255, 0, 0));
Coller une surface sur une autre
Une autre opération possible sur les surfaces est le « blit ». Cette opération consiste à copier une surface (ou une partie d’une surface) sur une autre surface. On peut donc la voir comme l’équivalent de SDL_RenderCopy
. Le blit s’effectue grâce à la fonction SDL_BlitSurface
. Son prototype :
int SDL_BlitSurface(SDL_Surface* src,
const SDL_Rect* srcrect,
SDL_Surface* dst,
SDL_Rect* dstrect)
Ses différents paramètres sont :
src
, la surface source est la surface qui sera copiée ;srcrect
est le rectangle source, c’est-à-dire la partie de la surface source qui sera copiée ;dst
est la surface de destination, celle sur laquelle sera copiée la source ;dstrect
est le rectangle de destination, celui où sera copiée la source.
Notons que cette fonction ne fait pas de redimensionnement à la volée, les champs h
et w
de dstrect
n’ont aucune incidence sur la copie.
Une fenêtre avec une icône
La SDL nous permet de donner une icône à notre programme grâce à la fonction SDL_SetWindowIcon
. Son prototype est :
void SDL_SetWindowIcon(SDL_Window* window,
SDL_Surface* icon)
Elle prend en paramètre la fenêtre dont on veut changer l’icône et une surface qui correspond à l’icône que l’on veut donner à la fenêtre. Cette fonction ne retourne rien. Par exemple, ici, on va faire une icône composée de quatre carrés.
/* Cette structure permet de représenter un carré par sa couleur et un SDL_Rect. */
struct carre {
SDL_Rect rect;
Uint32 couleur;
};
int main(int argc, char *argv[])
{
SDL_Window *window;
SDL_Surface *surface;
size_t i;
SDL_Init(SDL_INIT_VIDEO);
window = SDL_CreateWindow("icone", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
800, 600, SDL_WINDOW_RESIZABLE);
surface = SDL_CreateRGBSurface(0, 32, 32, 32, 0, 0, 0, 0);
/* On crée quatre carré pour notre icône. */
struct carre carre[4] = {
{ { 4, 4, 10, 10 }, SDL_MapRGB(surface->format, 0, 0, 0) }, /* Noir */
{ { 4, 18, 10, 10 }, SDL_MapRGB(surface->format, 0, 0, 255) }, /* Bleu */
{ { 18, 4, 10, 10 }, SDL_MapRGB(surface->format, 0, 255, 0) }, /* Vert */
{ { 18, 18, 10, 10 }, SDL_MapRGB(surface->format, 255, 255, 255) } /* Blanc */
};
/* On remplit notre surface grâce à nos carrés. */
for(i = 0; i < 4; i++)
SDL_FillRect(surface, &carre[i].rect, carre[i].couleur);
SDL_SetWindowIcon(window, surface);
SDL_Delay(2000);
/* Libérations */
return 0;
}
Passer de la texture à la surface
Tout ça, c’est très bien, mais on n’a toujours pas vu comment afficher une surface. En fait, on ne peut pas afficher directement une surface. Il nous faut passer par les textures. Il nous faut donc transformer notre surface en texture, puis afficher la texture obtenue. Le passage de la surface à la texture se fait avec la fonction SDL_CreateTextureFromSurface
dont le prototype est le suivant.
SDL_Texture* SDL_CreateTextureFromSurface(SDL_Renderer* renderer,
SDL_Surface* surface)
Elle prend en paramètre la surface qui nous permettra de créer la texture et le renderer auquel la texture créée doit être associée et retourne cette texture ou NULL
en cas d’erreur.
Après avoir créé une texture à partir d’une surface, il ne faut pas oublier de libérer cette surface. Pour obtenir une texture à partir d’une surface on peut donc faire ceci.
texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
if(NULL == texture)
{
fprintf(stderr, "Erreur SDL_CreateTextureFromSurface : %s", SDL_GetError());
goto Quit;
}
Bien sûr, nous pouvons libérer la surface à la fin de notre programme, mais autant la libérer dès que l’on n’en a plus besoin.
Les images
Charger une image
Afficher une image est une opération simple en théorie : une image est un paquet de pixels, on lit ce paquet de pixels, on le met dans une texture et on affiche cette texture. Nous pourrions faire une fonction qui prend en paramètre le chemin d’une image et retourne la texture associée à cette image. Heureusement, ce n’est pas la peine, les images sont gérées par la SDL.
Seul le format BMP
est géré nativement par la SDL.
Pour charger une image, nous allons utiliser la fonction SDL_LoadBMP
. Son prototype :
SDL_Surface* SDL_LoadBMP(const char* file)
Elle prend en paramètre le chemin (relatif ou absolu) de l’image à charger et retourne une surface contenant cet objet ou NULL
en cas d’erreur. Si nous avons parlé des surfaces, c’est parce que nous les utilisons ensuite pour les images.
On peut donc obtenir une surface contenant l’image grâce à SDL_LoadBMP
. Il nous suffit de créer une texture à partir de cette image, et on peut ensuite l’afficher. Sans oublier de libérer la surface.
Il est conseillé de toujours vérifier le retour de SDL_LoadBMP
. En effet, si nous ne le faisons pas, nous risquons ensuite de faire des opérations sur un pointeur nul, ce qui causera des problèmes.
On peut alors écrire un code de ce genre pour charger une image dans une texture.
SDL_Surface *tmp = NULL;
SDL_Texture *texture = NULL;
tmp = SDL_LoadBMP("test.bmp");
if(NULL == tmp)
{
fprintf(stderr, "Erreur SDL_LoadBMP : %s", SDL_GetError());
goto Quit;
}
texture = SDL_CreateTextureFromSurface(renderer, tmp);
SDL_FreeSurface(tmp); /* On libère la surface, on n’en a plus besoin */
if(NULL == texture)
{
fprintf(stderr, "Erreur SDL_CreateTextureFromSurface : %s", SDL_GetError());
goto Quit;
}
Ici, on aurait aussi pu libérer la surface à la fin en même temps que nos autres libérations, mais autant la libérer directement et ne pas occuper de la mémoire inutilement. Imaginons un code dans lequel nous chargerions plusieurs dizaines d’images. Il vaudrait mieux que chaque surface soit libérée aussitôt qu’elle n’a plus d’utilité.
Dessiner sur l’image
Selon la documentation, le type d’accès de la texture créée est SDL_TEXTUREACCESS_STATIC
. On ne pourra donc pas modifier cette texture en dessinant dedans à moins de faire en sorte d’obtenir une texture avec le bon type d’accès. Pour cela, il nous suffit de copier notre texture dans une nouvelle texture qui, elle, a le bon type d’accès. Cela va donc se faire de cette manière.
- Charger notre image.
- Créer une texture à partir de notre image.
- Créer une autre texture de même dimension que la surface.
- Placer cette dernière texture en tant que cible de rendu.
- Copier la première texture sur la deuxième (avec
SDL_RenderCopy)
.
Bien sûr, il faudra faire les libérations adéquates au bon moment et ne pas oublier de vérifier les valeurs retournées par les fonctions à risque. On se retrouve donc avec ce code.
SDL_Surface *surface = NULL;
SDL_Texture *texture, *tmp = NULL;
surface = SDL_LoadBMP("test.bmp");
if(NULL == surface)
{
fprintf(stderr, "Erreur SDL_LoadBMP : %s", SDL_GetError());
goto Quit;
}
tmp = SDL_CreateTextureFromSurface(renderer, surface);
if(NULL == tmp)
{
fprintf(stderr, "Erreur SDL_CreateTextureFromSurface : %s", SDL_GetError());
goto Quit;
}
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_TARGET, surface->w, surface->h);
if(NULL == texture)
{
fprintf(stderr, "Erreur SDL_CreateTextureFromSurface : %s", SDL_GetError());
goto Quit;
}
SDL_SetRenderTarget(renderer, texture); /* La cible de rendu est maintenant texture. */
SDL_RenderCopy(renderer, tmp, NULL, NULL); /* On copie tmp sur texture */
SDL_DestroyTexture(tmp);
SDL_FreeSurface(surface);
SDL_SetRenderTarget(renderer, NULL); /* La cible de rendu est de nouveau le renderer. */
/* On peut maintenant dessiner sur notre texture */
Se faire des fonctions
Ici, notre but sera de pouvoir écrire au minimum un code de ce genre.
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv)
{
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_Texture *image = NULL;
int statut = EXIT_FAILURE;
SDL_Color blanc = {255, 255, 255, 255};
if(0 != init(&window, &renderer, 640, 480)) /* ecrire cette fonction */
goto Quit;
image = loadImage("image.bmp", renderer); /* ecrire cette fonction*/
if(NULL == image)
goto Quit;
statut = EXIT_SUCCESS;
setWindowColor(renderer, blanc); /* ecrire cette fonction */
SDL_RenderPresent(renderer);
SDL_Delay(3000);
Quit:
if(NULL != image)
SDL_DestroyTexture(image);
if(NULL != renderer)
SDL_DestroyRenderer(renderer);
if(NULL != window)
SDL_DestroyWindow(window);
SDL_Quit();
return statut;
}
Écrivons les fonctions. Commençons par la fonction init
. Pour que les pointeurs passés en paramètre soient modifiés, il faut lui passer en paramètre des doubles pointeurs. On écrit donc cette fonction.
int init(SDL_Window **window, SDL_Renderer **renderer, int w, int h)
{
if(0 != SDL_Init(SDL_INIT_VIDEO))
{
fprintf(stderr, "Erreur SDL_Init : %s", SDL_GetError());
return -1;
}
if(0 != SDL_CreateWindowAndRenderer(w, h, SDL_WINDOW_SHOWN, window, renderer))
{
fprintf(stderr, "Erreur SDL_CreateWindowAndRenderer : %s", SDL_GetError());
return -1;
}
return 0;
}
La fonction loadImage
doit charger une image et renvoyer la texture correspondante. Elle doit donc charger l’image dans une surface, convertir cette surface en texture et renvoyer cette texture sans oublier de vérifier les retours des fonctions employées.
SDL_Texture *loadImage(const char path[], SDL_Renderer *renderer)
{
SDL_Surface *tmp = NULL;
SDL_Texture *texture = NULL;
tmp = SDL_LoadBMP(path);
if(NULL == tmp)
{
fprintf(stderr, "Erreur SDL_LoadBMP : %s", SDL_GetError());
return NULL;
}
texture = SDL_CreateTextureFromSurface(renderer, tmp);
SDL_FreeSurface(tmp);
if(NULL == texture)
{
fprintf(stderr, "Erreur SDL_CreateTextureFromSurface : %s", SDL_GetError());
return NULL;
}
return texture;
}
On aurait pu faire la fonction loadImage
pour que la texture renvoyée ait un accès SDL_TEXTUREACCESS_TARGET
. La fonction setWindowColor
a déjà été écrite dans le chapitre précédent.
On pourrait même rendre ces fonctions plus personnalisables et en faire plus.
La manière de charger des images nous montre bien que les surfaces restent utiles et ne sont pas totalement dépréciées. Pour en savoir plus à leur propos, consultons la page de la documentation de la SDL à propos des surfaces.
Avec ce chapitre, nous sommes maintenant capable de dessiner sur la fenêtre, de charger des images et de les afficher. Au prochain chapitre, nous verrons une autre manière de modifier les textures et les surfaces en changeant la couleur des pixels que l’on veut.