Dessiner dans la fenêtre

Après avoir vu comment manipuler des fenêtres, nous allons maintenant dessiner dedans.

Gestion du rendu

Il nous faut nous occuper du rendu de la fenêtre. Pour cela, nous devons créer un renderer. Il s’agit d’une structure (SDL_Renderer). Chaque renderer est associé à une fenêtre et nous permet de dessiner dans celle-ci. C’est de cette manière que nous allons modifier ce qu’il y a dans la fenêtre.

Créer un renderer

Pour créer un renderer, il faut utiliser la fonction SDL_CreateRenderer. Son prototype :

SDL_Renderer* SDL_CreateRenderer(SDL_Window* window,
                                 int         index,
                                 Uint32      flags)

Elle prend ces paramètres :

  • window correspond à la fenêtre à laquelle nous voulons que notre renderer soit associé ;
  • index correspond au pilote à utiliser pour gérer le renderer (on lui donne généralement la valeur -1 qui permet de choisir le premier qui correspond) ;
  • flags correspond à une liste de drapeaux.

Les différents drapeaux possibles sont :

drapeaux

Description

SDL_RENDERER_SOFTWARE

Le renderer est logiciel, le rendu sera effectué par le CPU et les données seront stockées en mémoire vive.

SDL_RENDERER_ACCELERATED

Le renderer utilise l’accélération matérielle. Les données sont en mémoire vidéo, plus rapide que la mémoire vive.

SDL_RENDERER_PRESENTVSYNC

La mise à jour de la fenêtre de rendu est synchronisé avec la fréquence de rafraîchissement de l’écran.

La plupart du temps, nous voulons un rendu avec l’accélération matérielle (utilisant la carte graphique), mais dans le cas où celle-ci n’est pas disponible on peut utiliser un rendu logiciel (c’est donc une solution de repli). Synchroniser le rendu avec la fréquence de rafraîchissement de l’écran peut être une bonne idée, mais la plupart du temps, on préfèrera le gérer nous-mêmes.

Nous pouvons également passer 0 comme argument, dans ce cas, la SDL essaye de fournir un renderer qui utilise l’accélération matérielle.

La fonction SDL_CreateRenderer retourne un pointeur sur SDL_Renderer, pointeur que nous devons récupérer car nous en aurons besoin dès que nous voudrons dessiner un peu. Si la création du renderer a échoué, elle retourne NULL. Il ne faut donc pas oublier de tester la valeur retournée. C’est là qu’intervient le renderer logiciel, on l’utilise généralement si on ne peut pas utiliser l’accélération matérielle. On devrait donc fonctionner de la manière suivante.

  1. On essaye de créer un renderer avec le drapeau SDL_RENDERER_ACCELERATED.
  2. Si la création du renderer a échoué on essaye avec le drapeau SDL_RENDERER_SOFTWARE.
  3. Si aucun n’a marché, on quitte le programme.

En fait, la SDL fait ceci automatiquement quand l’index passé à SDL_CreateRenderer vaut -1.

Nous nous contenterons donc d’essayer le drapeau SDL_RENDERER_ACCELERATED. La fonction SDL_RendererInfo nous permet d’obtenir des informations sur un renderer et permet donc de connaître ses drapeaux après l’avoir créé, mais nous n’allons pas nous en soucier ici.

Détruire le renderer

Désolé, mais nous allons encore une fois gérer nos ressources puisqu’il faut détruire le renderer créé. Pour cela, nous allons utiliser la fonction SDL_DestroyRenderer. Son prototype :

void SDL_DestroyRenderer(SDL_Renderer* renderer)

Elle prend en paramètre le renderer qu’il faut détruire et ne retourne rien. Grâce à ça, nous pouvons faire notre premier code avec un renderer :

#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;
    int statut = EXIT_FAILURE;

    if(0 != SDL_Init(SDL_INIT_VIDEO))
    {
        fprintf(stderr, "Erreur SDL_Init : %s", SDL_GetError());
        goto Quit;
    }
    window = SDL_CreateWindow("SDL2", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                              640, 480, SDL_WINDOW_SHOWN);
    if(NULL == window)
    {
        fprintf(stderr, "Erreur SDL_CreateWindow : %s", SDL_GetError());
        goto Quit;
    }
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if(NULL == renderer)
    {
        fprintf(stderr, "Erreur SDL_CreateRenderer : %s", SDL_GetError());
        goto Quit;
    }
    
    statut = EXIT_SUCCESS;
    SDL_Delay(3000);

Quit:
    if(NULL != renderer)
        SDL_DestroyRenderer(renderer);
    if(NULL != window)
        SDL_DestroyWindow(window);
    SDL_Quit();
    return statut;
}

Dans l’ordre, nous initialisons la SDL, puis nous créons la fenêtre et enfin nous créons le renderer. Nous faisons les destructions dans l’autre sens, le renderer, la fenêtre et enfin nous terminons en quittant la SDL. Sans oublier bien sûr de vérifier le retour de nos fonctions.

Créer le renderer et la fenêtre en même temps

Nous avons lié le renderer à une fenêtre et avons dit que chaque renderer était associé à une fenêtre. Ce serait donc bien de pouvoir créer le renderer et la fenêtre en même temps. Tant mieux, la SDL a une fonction qui permet de faire ça. Il s’agit de la fonction SDL_CreateWindowAndRender. Son prototype :

int SDL_CreateWindowAndRenderer(int            width,
                                int            height,
                                Uint32         window_flags,
                                SDL_Window**   window,
                                SDL_Renderer** renderer)

Juste en voyant son prototype, nous devrions comprendre comment elle marche. Elle prend en paramètre :

  • la largeur de la fenêtre ;
  • la hauteur de la fenêtre ;
  • les drapeaux de la fenêtre (vus dans le chapitre précédent ;
  • un double pointeur sur SDL_Window ;
  • un double pointeur sur SDL_Renderer.

Les deux doubles pointeurs sont compréhensibles : la fonction doit modifier la valeur des deux pointeurs pour qu’ils pointent sur la fenêtre et le renderer créé, or, pour modifier la valeur d’une variable dans une fonction, il faut lui passer l’adresse de cette variable, c’est-à-dire des doubles pointeurs dans notre cas. Aucun drapeau n’est demandé pour le renderer, la fonction essaye de créer un renderer avec le drapeau SDL_RENDERER_ACCELERATED.

La fonction retourne un entier. Il s’agit de :

  • 0 si tout s’est bien passé ;
  • -1 en cas d’erreur.

On peut comme d’habitude récupérer l’erreur en question avec la fonction SDL_GetError.

Des rectangles et des couleurs

Faisons maintenant, un aparté sur quelque chose qui nous sera très, mais vraiment très utile par la suite.

La base de tout, le point

La base de l’affichage d’un ordinateur est le pixel. Et un pixel c’est un point, un petit point d’un écran. Il paraît donc logique que la SDL puisse représenter un point.

Mais comment représenter un point ?

Comme en mathématiques, on représente un point avec son abscisse et son ordonnée. Ainsi, un point est représenté en SDL par une structure qui a pour champ ces deux composantes. Il s’agit de la structure SDL_Point. Ses champs :

  • x représente l’abscisse du point ;
  • y représente l’ordonnée du point.

Le point de coordonnées (0, 0) est en haut à gauche, et si les abscisses augmentent en allant vers la droite, les ordonnés, elles, augmentent en allant vers le bas.

Le système de coordonnées de la SDL.
Le système de coordonnées de la SDL.

On crée donc un point de cette manière :

SDL_Point point = {0, 0} /* Le point (0, 0) */

On complète la base, le rectangle

On a dit que la base de tout était le pixel, mais en fait, avec la SDL, ce que l’on peut vraiment considérer comme base est plutôt le rectangle. On pourrait se dire qu’un rectangle n’est qu’une amélioration du point et que le point reste donc la base, mais on utilise le rectangle vraiment beaucoup plus que le point.

Comment pouvons-nous définir un rectangle ?

On peut répondre plusieurs choses :

  • on peut définir un rectangle grâce à quatre points (ses quatre coins) ;
  • on peut définir un rectangle grâce à deux points (deux coins opposés) ;
  • on peut définir un rectangle grâce à deux droites perpendiculaires ;
  • plusieurs autres solutions.

Pour faire son rectangle la SDL a choisi une autre solution. Afin de simplifier les choses, son rectangle a toujours ses côtés parallèles aux axes. Ce rectangle est représenté grâce à la structure SDL_Rect. Ses champs :

  • x représente l’abscisse du coin en haut à gauche du rectangle ;
  • y représente l’ordonnée du coin en haut à gauche du rectangle ;
  • w représente la largeur du rectangle ;
  • h représente la hauteur du rectangle.

Comme nous pouvons le voir, ces quatre variables permettent bien de définir entièrement le rectangle. Par exemple, il permet de savoir si un point appartient à un rectangle. Écrivons le code qui permet de le savoir :

SDL_bool test(SDL_Point point, SDL_Rect rect)
{
    if(point.x >= rect.x && point.x <= (rect.x + rect.w) &&
        point.y >= rect.y && point.y <= (rect.y + rect.h))
        return SDL_TRUE;
    else
        return SDL_FALSE;
}

Le code est assez simple à comprendre :

  • on vérifie que l’abscisse du point est plus grande que l’abscisse du point en haut à gauche du rectangle ;
  • on vérifie que son abscisse est plus petite que l’abscisse du point en haut à gauche du rectangle plus la largeur du rectangle ;
  • on fait la même chose pour l’ordonnée.
Ici, le point rouge n'est pas dans le rectangle.
Ici, le point rouge n’est pas dans le rectangle.

Notons que ce que l’on vient de coder s’appelle une fonction de collision.

Des fonctions utiles

On vient d’écrire une fonction qui permet de tester si un point est dans un rectangle. On voudrait peut-être aussi écrire une fonction pour savoir si deux rectangles se touchent ou encore pour avoir l’intersection de deux rectangles.

Quoi ? Mais, on ne va pas écrire tout ça ?

Heureusement, on n’a pas à les écrire. La SDL a pensé à nous et offre plusieurs fonctions de collisions. Voyons en quelques-unes.

Commençons par la fonction qui permet de savoir si deux rectangles se touchent (ce dont on parlait précédemment). Il s’agit de la fonction SDL_HasIntersection. Son prototype :

SDL_bool SDL_HasIntersection(const SDL_Rect* A,
                             const SDL_Rect* B)

Elle prend en paramètre deux pointeurs sur SDL_Rect, c’est-à-dire deux pointeurs sur les deux rectangles sur lesquelles doit porter la collision et renvoie un SDL_bool qui vaut SDL_TRUE s’ils se touchent et SDL_FALSE sinon.

En fait, nous pouvons même obtenir le rectangle qui correspond à l’intersection grâce à la fonction SDL_IntersectRect. Son prototype :

SDL_bool SDL_IntersectRect(const SDL_Rect* A,
                           const SDL_Rect* B,
                           SDL_Rect*       result)

Elle agit exactement comme la fonction SDL_HasIntersection : elle prend en paramètre les pointeurs sur deux rectangles à tester et retourne SDL_TRUE s’il y a intersection et SDL_FALSE sinon. Cependant, elle prend un troisième argument qui correspond au rectangle résultant de l’intersection.

Pour finir, voyons une dernière fonction. Elle permet de tester si un point est dans un rectangle (en fait, c’est la fonction que l’on a codé tout à l’heure). Il s’agit de la fonction SDL_PointInRect. Son prototype :

SDL_bool SDL_PointInRect(const SDL_Point* p,
                         const SDL_Rect*  r)

Nous ne sommes pas surpris en apprenant qu’elle prend en paramètre un pointeur sur SDL_Point et un autre sur SDL_Rect et renvoie SDL_TRUE si le point est dans le rectangle et SDL_FALSE sinon.

Et nous renvoie à la page de la documentation de la SDL à propos des rectangles pour y voir les autres fonctions à ce propos (oui oui, nous renvoyons beaucoup à la documentation).

Comme entraînement pour nous familiariser avec les rectangles et les points, nous pouvons essayer de réécrire toutes ces fonctions.

Les couleurs

Le système de couleurs de la SDL est le même que celui utilisé sur les ordinateurs, c’est-à-dire qu’il se base sur 3 nombres entiers dont la valeur est comprise entre 0 et 255. Ces trois valeurs correspondent aux composantes rouge, verte et bleue de la couleur qu’on veut représenter (on parle de système RGB pour Red-Green-Blue) et permettent de représenter toutes les autres couleurs. Ainsi, chaque couleur est représentée par :

  • sa quantité de rouge ;
  • sa quantité de vert ;
  • sa quantité de bleu.

Notons que ce système s’appelle la synthèse additive.

La synthèse additive.
La synthèse additive.

On obtient par exemple un jaune particulier avec le triplet (255, 255, 0) (le jaune est un mélange de rouge et de vert).

À ces trois valeurs, on ajoute parfois une quatrième qui correspond à la composante alpha de la couleur. Cette composante ne change pas la couleur mais permet de gérer la transparence, 0 correspondant à de la transparence totale et 255 à de l’opacité totale.

La plupart des fonctions de la SDL nous demanderons les 3 valeurs (ou les 4 pour certaines). De plus, la SDL dispose d’une structure pour représenter une couleur. Il s’agit de la structure SDL_Color. Ses champs :

  • r est un entier entre 0 et 255 et représente la composante rouge de la couleur ;
  • g est un entier entre 0 et 255 et représente la composante verte de la couleur ;
  • b est un entier entre 0 et 255 et représente la composante bleue de la couleur ;
  • a est un entier entre 0 et 255 et représente la composante alpha de la couleur.

Cette structure est intéressante dans le cas où on manipule plusieurs couleurs en même temps. Par exemple, supposons que l’on ait une fonction colorier qui prend en paramètre les quatre composantes d’une couleur et colorie la fenêtre. Si on veut la colorier en bleu, on utilisera ce code.

colorier(0, 0, 255, 255);

Là ça va, pas de difficulté. Par contre, si on veut colorier en bleu, puis en orange, puis en rose fuchsia, il vaudrait mieux faire ainsi.

SDL_Color bleu = {0, 0, 255, 255};
SDL_Color orange = {255, 127, 40, 255};
SDL_Color rose;

colorier(bleu.r, bleu.g, bleu.b, bleu.a)
/* On fait pareil pour les autres */

Ou encore mieux : on pourrait faire une fonction qui prend en paramètre un SDL_Color et se charge d’appeler la fonction colorier ou bien modifier directement la fonction colorier afin qu’elle utilise un SDL_Color comme paramètre.

Des dessins

Le principe

Le principe des renderer de la SDL est vraiment très simple. Voyons un exemple.

#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>


int main(int argc, char *argv[])
{
    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;
    int statut = EXIT_FAILURE;
    SDL_Color orange = {255, 127, 40, 255};
    
    /* Initialisation, création de la fenêtre et du renderer. */
    if(0 != SDL_Init(SDL_INIT_VIDEO))
    {
        fprintf(stderr, "Erreur SDL_Init : %s", SDL_GetError());
        goto Quit;
    }
    window = SDL_CreateWindow("SDL2", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                              640, 480, SDL_WINDOW_SHOWN);
    if(NULL == window)
    {
        fprintf(stderr, "Erreur SDL_CreateWindow : %s", SDL_GetError());
        goto Quit;
    }
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if(NULL == renderer)
    {
        fprintf(stderr, "Erreur SDL_CreateRenderer : %s", SDL_GetError());
        goto Quit;
    }
    
    /* C’est à partir de maintenant que ça se passe. */
    if(0 != SDL_SetRenderDrawColor(renderer, orange.r, orange.g, orange.b, orange.a))
    {
        fprintf(stderr, "Erreur SDL_SetRenderDrawColor : %s", SDL_GetError());
        goto Quit;
    }
    
    if(0 != SDL_RenderClear(renderer))
    {
        fprintf(stderr, "Erreur SDL_SetRenderDrawColor : %s", SDL_GetError());
        goto Quit;
    }
    
    SDL_Delay(500);
    SDL_RenderPresent(renderer);
    SDL_Delay(500);
    
    statut = EXIT_SUCCESS;

Quit:
    if(NULL != renderer)
        SDL_DestroyRenderer(renderer);
    if(NULL != window)
        SDL_DestroyWindow(window);
    SDL_Quit();
    return statut;
}

Ce programme change la couleur de la fenêtre en orange avant de se fermer. Expliquons le pas à pas. Bien sûr, nous n’allons pas revenir sur les initialisations.

Choisir la couleur de dessin

Pour commencer, nous choisissons une « couleur de travail » pour le renderer. En fait, il faut voir le renderer comme un outil de dessin ; nous choisissons quelle couleur utiliser avec cet outil. Pour cela, nous utilisons la fonction SDL_SetRendererDrawColor. Son prototype :

int SDL_SetRenderDrawColor(SDL_Renderer* renderer,
                           Uint8         r,
                           Uint8         g,
                           Uint8         b,
                           Uint8         a)

Elle prend en paramètre le renderer et les composantes de la couleur en question. Elle renvoie comme d’habitude 0 si tout s’est bien passé et une valeur négative en cas d’erreur. Nous devrions donc savoir l’utiliser. La couleur que l’on utilise pour dessiner sera celle-là jusqu’au prochain changement de couleur.

Dans notre code d’exemple, nous choisissons l’orange comme « couleur de travail » grâce à ces lignes.

if(0 != SDL_SetRenderDrawColor(renderer, orange.r, orange.g, orange.b, orange.a))
{
    fprintf(stderr, "Erreur SDL_SetRenderDrawColor : %s", SDL_GetError());
    goto Quit;
}

Changer la couleur

Ensuite, il nous faut nettoyer le renderer, c’est-à-dire l’effacer entièrement en le « peignant » de la couleur souhaitée (celle que l’on utilise actuellement). On le fait à l’aide de la fonction SDL_RenderClear. Son prototype :

int SDL_RenderClear(SDL_Renderer* renderer)

Elle prend en paramètre le renderer qui doit être nettoyé et renvoie 0 en cas de succès et une valeur négative sinon.

C’est donc cette partie de notre code d’exemple.

if(0 != SDL_RenderClear(renderer))
{
    fprintf(stderr, "Erreur SDL_SetRenderDrawColor : %s", SDL_GetError());
    goto Quit;
}

Mise à jour de l’affichage

Pourtant, même après avoir fait tout ça, notre fenêtre n’est toujours pas en orange. En effet, on a modifié le renderer, mais on n’a pas mis à jour l’écran. La mise à jour de l’écran à partir du renderer se fait avec la fonction SDL_RenderPresent. Son prototype :

void SDL_RenderPresent(SDL_Renderer* renderer)

Elle prend en paramètre le renderer à mettre à jour et ne retourne rien.

Donc, la mise à jour de l’écran se fait avec cette ligne.

SDL_RenderPresent(renderer);

Nous pouvons remarquer que nous avons placé un SDL_Delay avant cette ligne. En fait, sans cela, notre fenêtre passe instantanément à l’orange. Le SDL_Delay nous permet de ne pas changer la couleur de la fenêtre tout de suite. C’est grâce à cela que l’on voit le passage à l’orange.

Pour avoir un meilleur code, on pourrait créer une fonction pour changer la couleur de la fenêtre, pour avoir un code de ce genre avec une fonction réutilisable.

int setWindowColor(SDL_Renderer *renderer, SDL_Color color)
{
    if(SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a) < 0)
        return -1;
    if(SDL_RenderClear(renderer) < 0)
        return -1;
    return 0;  
}

int main(int argc, char *argv[])
{
    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;
    SDL_Color orange = {255, 127, 40, 255};

    /* On fait toutes nos initialisations ici */

    setWindowColor(renderer, orange); /* La réussite ou non de la fonction ne nous intéresse pas */
    SDL_Delay(1000);

    /* On libère toutes nos ressources ici et on fait notre return */ 
}

Dans ce dernier code, la réussite de la fonction setWindowColor ne nous intéresse pas. Ce sera souvent le cas lorsque nous allons dessiner. L’échec d’une fonction de dessin ne provoquera pas de crash, vérifier qu’elle a réussi n’est donc pas fondamental (à moins bien sûr que notre programme ait absolument besoin de sa réussite).


Finalement, dessiner revient à suivre deux étapes.

  1. On dessine à l’aide du renderer.
  2. On met à jour l’écran avec le renderer.

C’est ce que l’on fait tout le long d’un programme. On dessine et on met à jour, on redessine, on remet à jour…

L’étape 2 se fait très simplement à l’aide de la fonction SDL_RenderPresent.

La première étape peut se décomposer en plusieurs étapes (choix d’une couleur de travail, et utilisation de cette couleur pour dessiner). Dans notre exemple, nous avons changé la couleur de l’écran, mais il y a plusieurs manières de dessiner :

  • on peut changer la couleur de fond de la fenêtre ;
  • on peut afficher un point ;
  • on peut afficher une image.
  • etc.

Toutes ces opérations se font à l’aide du renderer qui est, comme nous l’avons dit, notre outil de dessin.

Dessiner des points et des lignes

Maintenant que nous connaissons la méthode, voyons comment dessiner un point. Pour cela, nous allons choisir notre couleur, puis utiliser la fonction SDL_RenderDrawPoint. Son prototype :

int SDL_RenderDrawPoint(SDL_Renderer* renderer,
                        int           x, 
                        int           y)

Elle prend en paramètre le renderer sur lequel dessiner et les coordonnées du point à dessiner et retourne 0 en cas de succès et une valeur négative en cas d’erreur.

Pour dessiner un point rouge, puis un point bleu et finalement un point vert, nous pourrions écrire ceci.

SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
SDL_RenderDrawPoint(renderer, 50, 50);
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
SDL_RenderDrawPoint(renderer, 100, 100);
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
SDL_RenderDrawPoint(renderer, 150, 150);
SDL_RenderPresent(renderer);

Nous obtiendrons ainsi trois points.

Et si nous avons besoin d’afficher plusieurs points d’un coup (donc de la même couleur), la SDL propose la fonction SDL_RenderDrawPoints. Son prototype :

int SDL_RenderDrawPoints(SDL_Renderer*    renderer,
                         const SDL_Point* points,
                         int              count)

Elle prend en paramètre le renderer sur lequel dessiner, un tableau de SDL_Point et le nombre de points à dessiner (donc ce nombre doit être plus petit ou égal à la taille de ce tableau). Elle retourne comme d’habitude une valeur négative en cas d’erreur et 0 en cas de succès.

Utilisons la pour dessiner une ligne sur l’écran.

SDL_Point point[640];
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
size_t i = 0;
for(i = 0; i < 640; i++)
{
    point[i].x = i;
    point[i].y = 200;
}
SDL_RenderDrawPoints(renderer, point, 640);
SDL_RenderPresent(renderer);

On a utilisé un tableau de SDL_Point. Le champ y de tous ces points est égal à 200 et le champ x va en croissant, finalement on obtient donc une ligne horizontale d’ordonnée 200.

Et pour dessiner une ligne sur la diagonale de l’écran ?

Il existe plusieurs algorithmes de tracés de segment. Nous pourrions les implémenter et créer une fonction qui trace un segment reliant deux points. Mais ce n’est même pas la peine : la SDL possède déjà une fonction pour tracer un segment, la fonction SDL_RenderDrawLine. Son prototype :

int SDL_RenderDrawLine(SDL_Renderer* renderer,
                       int           x1,
                       int           y1,
                       int           x2,
                       int           y2)

Elle prend en paramètre le renderer sur lequel dessiner le segment et les coordonnées des deux points à relier. Elle retourne comme d’habitude 0 en cas de succès et une valeur négative en cas d’erreur.

Pour relier les coins haut gauche et bas droit d’une fenêtre en 640x480, nous allons donc faire ceci.

SDL_RenderDrawLine(renderer, 0, 0, 640, 480);

Et là encore, on peut dessiner plusieurs lignes à l’aide de la fonction SDL_RenderDrawLines. Son prototype :

int SDL_RenderDrawLines(SDL_Renderer*    renderer,
                        const SDL_Point* points,
                        int              count)

Elle prend en paramètre le renderer, un tableau de points et la taille de ce tableau. Elle retourne (encore une fois) 0 en cas de succès et une valeur négative en cas d’erreur.

Cette fonction dessine count - 1 segments : elle relie le premier point au second, le second au troisième, le troisième au quatrième, …, l’avant-dernier au dernier. Ainsi, pour dessiner un carré de longueur 20 pixels, nous pourrions faire ceci.

SDL_Point point[5];
point[0].x = 100;
point[0].y = 100;
point[1].x = 200;
point[1].y = 100;
point[2].x = 200;
point[2].y = 200;
point[3].x = 100;
point[3].y = 200;   
point[4].x = 100;
point[4].y = 100;
SDL_RenderDrawLines(renderer, point, 5);
SDL_RenderPresent(renderer);

On crée 5 points parce qu’il ne faut pas oublier de relier le dernier point au premier pour « fermer » le carré. Le dernier point est donc le même que le premier.

Dessiner des rectangles

La SDL a aussi des fonctions pour dessiner des rectangles. Voyons pour commencer la fonction SDL_RenderDrawRect. Son prototype :

int SDL_RenderDrawRect(SDL_Renderer*   renderer,
                       const SDL_Rect* rect)

Elle prend en paramètre le renderer sur lequel dessiner et un pointeur sur SDL_Rect qui représente le rectangle à dessiner et retourne 0 en cas de succès et une valeur négative en cas d’erreur. Ainsi, pour dessiner le carré de l’exemple précédent, nous pouvons faire ceci.

SDL_Rect rect = {100, 100, 100, 100};
SDL_RenderDrawRect(renderer, &rect); 
SDL_RenderPresent(renderer);

La fonction SDL_RenderDrawRect dessine un rectangle (c’est-à-dire les contours du rectangle). On peut aussi vouloir dessiner un rectangle plein. Dans ce cas, il nous faut une fonction qui remplira un rectangle de couleur (« fill » en anglais). Dès lors, le nom de la fonction qui fait cette action est toute trouvée. Il s’agit de la fonction SDL_RenderFillRect. Son prototype :

int SDL_RenderFillRect(SDL_Renderer*   renderer,
                       const SDL_Rect* rect)

C’est exactement le même prototype que celui de la fonction SDL_RenderDrawRect. Les paramètres et les retours sont exactement les mêmes.

En changeant le SDL_RenderDrawRect de l’exemple précédent en SDL_RenderFillRect, on obtiendra donc un rectangle plein (avec la couleur que nous aurons choisie bien sûr).

SDL_Rect rect = {100, 100, 100, 100};
SDL_RenderFillRect(renderer, &rect); 
SDL_RenderPresent(renderer);

Le second paramètre de SDL_RenderFillRect (et aussi de SDL_RenderDrawRect), peut être NULL. Dans ce cas, le rectangle qui sera rempli (dont le contour sera dessiné) est le renderer en entier.

Et maintenant, voyons les variantes pour dessiner plusieurs rectangles. Il s’agit (et nous pouvions facilement le deviner) des fonctions SDL_RenderFillRects et SDL_RenderDrawRects. Leurs prototypes ne devraient pas non plus nous surprendre :

int SDL_RenderFillRects(SDL_Renderer*   renderer,
                        const SDL_Rect* rects,
                        int             count)

Et

int SDL_RenderDrawRects(SDL_Renderer*   renderer,
                        const SDL_Rect* rects,
                        int             count)

Comme d’habitude, elles retournent 0 en cas de succès et une valeur négative en cas d’erreur. Elles prennent en paramètre le renderer sur lequel dessiner, un tableau de SDL_Rect et le nombre de rectangles à dessiner.

Essayons par exemple d’utiliser SDL_RenderFillRects pour dessiner un damier. Voici un code possible.

Avec une fenêtre 500x500 (donc des cases 50x50) :

SDL_Rect rect[50];
size_t i = 0;
for(i = 0; i < 50; i++)
{   
    rect[i].w = 50;
    rect[i].h = 50;
    rect[i].x = 100 * (i % 5) + 50 * ((i / 5) % 2);
    rect[i].y = 50 * (i / 5);
}
SDL_RenderFillRects(renderer, rect, 50); 
SDL_RenderPresent(renderer);

Bien sûr, il existe d’autres méthodes pour faire ce travail.


Dans ce chapitre nous n’avons quasiment pas fait de vérifications du retour des fonctions de la SDL. Il ne faut pas oublier de les faire dans notre code. Le plus simple est de créer des fonctions qui se chargent de cela.