Programmation avec la SDL/Les surfaces

Un livre de Wikilivres.
En travauxlink={{{link}}}

Cette page est en travaux. Tant que cet avis n'aura pas disparu, veuillez en considérer le plan et le contenu encore incomplets, temporaires et sujets à caution. Si vous souhaitez participer, il vous est recommandé de consulter sa page de discussion au préalable, où des informations peuvent être données sur l'avancement des travaux.

Programmation avec la SDL
Sommaire
L' affichage vidéo
L'essentiel
Approfondissement
La gestion des évènements
Annexes
Modifier ce modèle

Dans les chapitres précédents, nous avons plusieurs fois rencontré les surfaces sans pour autant expliquer ce qu'elles étaient vraiment. SDL_Surface est un type qui nous vient de la première version de la SDL qui conserve encore quelques utilités. C'est l'équivalent des textures que nous utilisons maintenant sauf qu'elle ne disposent pas de l'accélération matérielle et qu'elles n'ont pas autant de possibilité de dessin que les textures. Elles ont néanmoins un avantage, bien que nous ne l'exploiterons pas dans ce livre : elles peuvent être modifiées pixel par pixel beaucoup plus facilement que les textures.

Créer une surface[modifier | modifier le wikicode]

La création d'une surface se fait d'une manière semblable à la création du renderer. Une surface est stockée dans une structure de type SDL_Surface. Comme avec le renderer, nous devons donc en premier lieu déclarer un pointeur de surface, car nous ne manipuleront presque que des pointeurs de surface pour des raisons d'optimisation. En effet, comme nous l'avons dit dans le chapitre sur le renderer, le programme serait alourdi par des transferts de structures contenant beaucoup d'informations telles que SDL_Surface ou SDL_Renderer. Nous préférerons donc l'utilisation de pointeur afin d'éviter des transferts de données inutiles.

Comme pour le renderer, déclarer le pointeur de SDL_Surface ne suffit pas. Il faut encore créer la surface à l'aide de la fonction SDL_CreateRGBSurface.

SDL_Surface* SDL_CreateRGBSurface (Uint32 flags, int width, int height, int depth,
                                   Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask);

Comme nous l'indique le prototype, on obtient bien un pointeur de surface à la sortie de la fonction. Elle renvoie NULL en cas d'erreur. Examinons plutôt les arguments :

  • flags : avec la SDL 2.0, on ne les utilise plus, on envoie donc la valeur 0;
  • width : la largeur de la surface en pixel;
  • height : la hauteur de la surface en pixel;
  • depth : le nombre de bit par pixel. On utilisera le plus souvent des pixels stockés sur 32 bits.
  • RGBAmask : les masks permettant à la SDL de savoir comment extraire la couleur depuis le pixel stocké. Nous les étudions de manière plus approfondie dans le chapitre sur les couleurs.

Même si vous ne savez pas exactement à quoi correspondent les masks, voici deux manières de créer une surface fonctionnelle. Concrètement, vous n'avez pas vraiment besoin d'en savoir beaucoup plus.

Si vous n'avez pas besoin de la transparence :

SDL_Surface* surface = NULL;

//La transparence n'est pas activée
surface = SDL_CreateRGBSurface (0, 100, 100, 32, 0, 0, 0, 0);

if(surface == NULL)
{
    printf("Erreur lors de la creation de la surface : %s", SDL_GetError());
    return EXIT_FAILURE;
}

Si vous avez besoin de la transparence :

//La transparence est activée

SDL_Surface* surface = NULL;
Uint32 rmask, gmask, bmask, amask; // Déclaration des masks

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
    rmask = 0xff000000;
    gmask = 0x00ff0000;
    bmask = 0x0000ff00;
    amask = 0x000000ff;
#else
    rmask = 0x000000ff;
    gmask = 0x0000ff00;
    bmask = 0x00ff0000;
    amask = 0xff000000;
#endif

surface = SDL_CreateRGBSurface(0, width, height, 32, rmask, gmask, bmask, amask);
if(surface == NULL)
{
    printf("Erreur lors de la creation de la surface : %s", SDL_GetError());
    return EXIT_FAILURE;
}

Vous savez à présent créer une surface. Mais comme toujours avec la SDL, lorsqu'on crée quelque chose, il faut le détruire lorsqu'on n'en a pas besoin.

void SDL_FreeSurface(SDL_Surface* surface);

Pour chaque surface créée, vous devez avoir un "FreeSurface" correspondant. Cette étape est très importante car sinon, vous risquez d'encombrer la mémoire vive de votre ordinateur. Et, dans le cas où vous créez un grand nombre de surface, oublier de libérer celle-ci pourrait amener au plantage de votre programme et vous aurez alors beaucoup de mal pour comprendre d'où vient le bug.

Convertir une surface en texture[modifier | modifier le wikicode]

Lorsque nous utilisons une surface, nous avons presque systématiquement besoin de la convertir par la suite en texture. En effet, c'est nécessaire ne serait-ce que pour afficher une surface. Pour convertir une surface en texture, nous allons utiliser la fonction

SDL_Texture* SDL_CreateTextureFromSurface(SDL_Renderer* renderer, SDL_Surface* surface);

Cette fonction ne présente aucune difficulté majeure : elle prend en paramètre le rendererer correspondant ainsi que la surface à convertir. Voici le code qui, en supposons qu'on a déjà déclaré un renderer et une surface avant, va vous permettre de convertir une surface en texture :

SDL_Texture* maTexture = SDL_CreateTextureFromSurface(renderer, maSurface);
SDL_FreeSurface(maSurface); // Maintenant qu'on a la texture, on n'a plus besoin de la surface

Nous sommes donc à présent en mesure d'afficher une surface en suivant les étapes suivantes :

  • Création de la surface que l'on souhaite afficher.
  • Conversion de la surface en texture avec SDL_CreateTextureFromSurface
  • Affichage de la texture grâce à la fonction SDL_RenderCopy.

Cependant, si vous essayez d'utiliser la fonction SDL_SetRenderTarget sur la texture issue de la conversion de votre surface, vous constaterez qu'elle renvoie une valeur négative. Vous aurez probablement deviné que la texture renvoyée n'a pas un accès de type SDL_TEXTUREACCES_TARGET. Ce n'est pas gênant si vous vous contentez d'afficher la surface mais en revanche, si vous voulez utiliser les fonctions de dessin du renderer, il va falloir ruser. Un moyen de contourner le problème va donc être de coller la texture convertie sur une autre texture qui elle, possède les bons attributs. Voici le code permettant de le faire :

//création du texture tampon ne possédant pas SDL_TEXTUREACCES_TARGET
SDL_Texture *texture_tampon = SDL_CreateTextureFromSurface(renderer, surface);
SDL_Rect dimensions = {0,0,0,0};
SDL_QueryTexture(texture_tampon, NULL, NULL, &dimensions.w, &dimensions.h);

// on crée la texture définitive avec SDL_TEXTUREACCESS_TARGET
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, dim.w, dim.h);

SDL_SetRenderTarget(renderer, texture);
SDL_RenderCopy(renderer, texture_tampon, NULL, &dimensions);
SDL_SetRenderTarget(renderer, NULL);

SDL_DestroyTexture(texture_tampon);  // on n'aura plus besoin de la texture tampon

Modifier une surface[modifier | modifier le wikicode]

Maintenant que nous savons afficher une surface, il est temps de pouvoir la modifier.

Colorier un rectangle[modifier | modifier le wikicode]

La principale fonctionnalité des surfaces consiste à colorier les rectangles. En théorie, il suffirait de cela pour créer n'importe quelle image si on remplit la surface pixel par pixel et c'est d'ailleurs souvent ce que l'on fait lorsqu'on veut créer des formes géométriques tels que des cercles. On utilise pour cela les fonctions

int SDL_FillRect(SDL_Surface* dst, const SDL_Rect* rect, Uint32 color);
int SDL_FillRects(SDL_Surface* dst, const SDL_Rect* rects, Uint32 color);

Comme vous l'aurez probablement deviné, la première fonction remplit un seul rectangle (l'argument rect est un pointeur vers un unique SDL_Rect) et la seconde en remplit autant qu'on veut (rects est un tableau d'éléments de type SDL_Rect). Ainsi, la fonction SDL_Rects permet de remplir les rectangles rects de la surface dst (pour destination) avec la couleur color. Voici par exemple comment colorier une surface entière en rouge :

SDL_Surface *surface = SDL_CreateRGBSurface(0, 100, 100, 32, 0, 0, 0, 0);
SDL_Rect surface_rect = {0, 0, 100, 100};
SDL_FillRect(surface, &surface_rect, SDL_MapRGB(surface->format, 255, 0, 0));

Vous avez sans doute remarqué que l'on a utilisé dans ce code une nouvelle fonction : SDL_MapRGB. Celle-ci demande en argument le format de la couleur puis la valeur en rouge, vert et bleu de la couleur que vous voulez envoyer. Pour le format, envoyez toujours le format de la surface que vous êtes en train de modifier puisqu'il faut que les deux formats correspondent.