Pour le moment nous avons une fenêtre avec un fond noir. C'est la fenêtre de base.
Ce qu'on veut faire, c'est la remplir, c'est-à-dire "dessiner" dedans.
La SDL, je vous l'ai dit dans le chapitre précédent, est une bibliothèque bas niveau. Cela veut dire qu'elle ne nous propose que des fonctions de base, très simples.
Et en effet, la seule forme que la SDL nous permet de dessiner, c'est le rectangle ! Tout ce que vous allez faire, c'est assembler des rectangles dans la fenêtre. On appelle ces rectangles
des surfaces.
La surface, c'est la brique de base de la SDL.
Votre première surface : l'écran
Dans tout programme SDL, il y a au moins une surface que l'on appelle généralement
ecran (ou
screen en anglais). C'est une surface qui correspond à toute la fenêtre, c'est-à-dire toute la zone noire de la fenêtre que vous voyez.
Dans notre code source, chaque surface sera mémorisée dans une variable de type
SDL_Surface. Oui, c'est un type de variable créé par la SDL (une structure en l'occurence).
Comme la première surface que nous devons créer est l'écran, allons-y :
Code : C | SDL_Surface *ecran = NULL;
|
Vous remarquerez que je crée un pointeur. Pourquoi je fais ça ? Parce que c'est la SDL qui va allouer de l'espace en mémoire pour notre surface. Une surface n'a en effet pas toujours la même taille, et la SDL est obligée de faire une allocation dynamique pour nous (ici ça dépendra de la taille de la fenêtre que vous avez ouverte).
Je ne vous l'ai pas dit tout à l'heure, mais la fonction SDL_SetVideoMode renvoie une valeur ! Elle renvoie un pointeur sur la surface de l'écran qu'elle a créée en mémoire pour nous.
Cool, on va donc pouvoir récupérer ce pointeur dans
ecran :
Code : C | ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
|
Notre pointeur peut valoir :
- NULL : ecran vaut NULL si la SDL_SetVideoMode n'a pas réussi à charger le mode vidéo demandé. Cela arrive si vous demandez une trop grande résolution ou un trop grand nombre de couleurs que ne supporte pas votre ordinateur.
- Une autre valeur : si la valeur est différente de NULL, c'est que la SDL a pu allouer la surface en mémoire, donc que tout est bon !
Il serait bien ici de gérer les erreurs, comme on a appris à le faire tout à l'heure pour l'initialisation de la SDL. Voici donc notre main avec la gestion de l'erreur pour SDL_SetVideoMode :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | int main(int argc, char *argv[])
{
SDL_Surface *ecran = NULL; // Le pointeur qui va stocker la surface de l'écran
SDL_Init(SDL_INIT_VIDEO);
ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE); // On tente d'ouvrir une fenêtre
if (ecran == NULL) // Si l'ouverture a échoué, on écrit l'erreur et on arrête
{
fprintf(stderr, "Impossible de charger le mode vidéo : %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
SDL_WM_SetCaption("Ma super fenêtre SDL !", NULL);
pause();
SDL_Quit();
return EXIT_SUCCESS;
}
|
Le message que nous laissera SDL_GetError() nous sera très utile pour savoir ce qui a planté.
Petite anecdote : une fois je me suis planté en voulant faire du plein écran. Au lieu de demander une résolution de 1024*768, j'ai écrit 10244*768. Je ne comprenais pas au départ pourquoi ça ne voulait pas charger, car je ne voyais pas le double 4 dans mon code (oui je devais être un peu fatigué

).
Un petit coup d'oeil au fichier stderr.txt, et j'ai tout de suite compris que c'était ma résolution qui avait été rejetée (tiens comme c'est curieux

)
Colorer une surface
Il n'y a pas 36 façons de remplir une surface... En fait, il y en a 2 :
- Soit vous remplissez la surface avec une couleur unie
- Soit vous remplissez la surface en chargeant une image
Il est aussi possible de dessiner pixel par pixel dans la surface mais c'est assez compliqué, nous ne le verrons pas ici.
Nous allons dans un premier temps voir comment remplir une surface avec une couleur unie. Dans le chapitre suivant, nous apprendrons à charger une image.
La fonction qui permet de colorer une surface avec une couleur unie s'appelle
SDL_FillRect (FillRect = "remplir rectangle" en anglais). Elle prend 3 paramètres. Dans l'ordre :
- Un pointeur sur la surface dans laquelle on doit dessiner (par exemple ecran).
- La partie de la surface qui doit être remplie. Si vous voulez remplir toute la surface (et c'est ce qu'on veut faire), envoyez NULL.
- La couleur à utiliser pour remplir la surface.
En résumé :
Code : C | SDL_FillRect(surface, NULL, couleur);
|
La gestion des couleurs en SDL
En SDL, une couleur est stockée dans un nombre de type Uint32.
Si c'est un nombre, pourquoi ne pas avoir utilisé le type de variable int ou long tout simplement ?
La SDL est une bibliothèque multiplateforme. Or, comme vous le savez maintenant, la taille occupée par un int ou un long peut varier selon votre OS. Pour s'assurer que le nombre occupera toujours la même taille en mémoire, la SDL a donc "inventé" des types pour stocker des entiers qui ont la même taille sur tous les systèmes.
Il y a par exemple :
- Uint32 : un entier de longueur 32 bits (soit 4 octets, quand on sait que 1 octet = 8 bits
)
- Uint16 : un entier codé sur 16 bits (2 octets)
- Uint8 : un entier codé sur 8 bits (1 octet)
La SDL ne fait qu'un simple typedef qui changera selon l'OS que vous utilisez. Regardez de plus près le fichier SDL_types.h si vous êtes curieux.
On ne va pas s'attarder là-dessus plus longtemps car les détails de tout cela ne sont pas importants. Vous avez juste besoin de retenir que Uint32 est un type qui stocke un entier, comme un int.
Bon ok, mais comment je sais quel nombre je dois mettre pour la couleur verte, azur, gris foncé, jaune pâle etc. ?
Il existe une fonction qui sert à ça :
SDL_MapRGB. Elle prend 4 paramètres :
- Le format des couleurs. Ce format dépend du nombre de bits / pixel que vous avez demandé avec SDL_SetVideoMode. Vous pouvez le récupérer, il est stocké dans la sous-variable ecran->format
- La quantité de rouge de la couleur
- La quantité de vert de la couleur
- La quantité de bleu de la couleur
Certains d'entre vous ne le savent peut-être pas, alors expliquons ce bazar. Toute couleur sur un ordinateur est construite à partir de 3 couleurs de base : le rouge, le vert et le bleu.
Chaque quantité peut varier de 0 (pas de couleur) à 255 (toute la couleur).
Donc, si on écrit :
Code : C | SDL_MapRGB(ecran->format, 255, 0, 0)
|
... on crée une couleur rouge. Il n'y a pas de vert ni de bleu.
Autre exemple, si on écrit :
Code : C | SDL_MapRGB(ecran->format, 0, 0, 255)
|
... cette fois c'est une couleur bleue.
Code : C | SDL_MapRGB(ecran->format, 255, 255, 255)
|
... là il s'agit d'une couleur blanche (toutes les couleurs s'additionnent). Si vous voulez du noir, il faut mettre 0, 0, 0.
On ne peut que mettre du rouge, du vert, du bleu, du noir et du blanc ?

Non, c'est à vous de combiner intelligemment les quantités de couleurs

Pour vous aider, ouvrez par exemple le logiciel Paint. Allez dans le menu "Couleurs / Modifier les couleurs". Cliquez sur le bouton "Définir les couleurs personnalisées".
Là, choisissez la couleur qui vous intéresse :
Les composantes de la couleur sont affichées en bas à droite. Ici, ce bleu-vert que j'ai sélectionné se crée à l'aide de 17 de rouge, 206 de vert et 112 de bleu
Coloration de l'écran
La fonction SDL_MapRGB renvoie un Uint32 qui correspond à la couleur demandée.
On peut donc créer une variable bleuVert qui contiendra le code de la couleur bleu-vert :
Code : C | Uint32 bleuVert = SDL_MapRGB(ecran->format, 17, 206, 112);
|
Toutefois, ce n'est pas toujours la peine de passer par une variable pour stocker la couleur (à moins que vous en ayez besoin très souvent dans votre programme).
Vous pouvez tout simplement envoyer directement la couleur donnée par SDL_MapRGB à SDL_FillRect.
Si on veut remplir notre écran de bleu-vert, on peut donc écrire :
Code : C | SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 17, 206, 112));
|
On combine 2 fonctions, mais ça ne pose aucun problème au langage C
Mise à jour de l'écran
Nous y sommes presque.
Toutefois il manque encore une petite chose : demander la mise à jour de l'écran. En effet, l'instruction SDL_FillRect colorie bien l'écran mais cela ne se fait que dans la mémoire. Il faut ensuite
demander à l'ordinateur de mettre à jour l'écran avec les nouvelles données.
Pour cela, on va utiliser la fonction
SDL_Flip, dont nous reparlerons plus longuement plus tard dans le cours.
Cette fonction prend un paramètre : l'écran
Code : C | SDL_Flip(ecran); /* Mise à jour de l'écran */
|
On résume !
Voici une fonction main() qui crée une fenêtre avec un fond bleu-vert :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | int main(int argc, char *argv[])
{
SDL_Surface *ecran = NULL;
SDL_Init(SDL_INIT_VIDEO);
ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
SDL_WM_SetCaption("Ma super fenêtre SDL !", NULL);
// Coloration de la surface ecran en bleu-vert
SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 17, 206, 112));
SDL_Flip(ecran); /* Mise à jour de l'écran avec sa nouvelle couleur */
pause();
SDL_Quit();
return EXIT_SUCCESS;
}
|
Résultat :
Dessiner une nouvelle surface à l'écran
Bon, c'est bien, mais ne nous arrêtons pas en si bon chemin

Pour le moment on n'a qu'une seule surface, c'est l'écran. On aimerait pouvoir dessiner dessus, c'est-à-dire "coller" des surfaces avec une autre couleur par-dessus.
Pour commencer, nous allons avoir besoin de créer une autre variable de type SDL_Surface pour cette nouvelle surface :
Code : C | SDL_Surface *rectangle = NULL;
|
Nous devons ensuite demander à la SDL de nous allouer de l'espace en mémoire pour cette nouvelle surface.
Pour l'écran, nous avons utilisé SDL_SetVideoMode. Toutefois, cette fonction ne marche que pour l'écran (la surface globale), on ne va pas recréer une fenêtre pour chaque rectangle que l'on veut dessiner
Il existe donc une autre fonction pour créer une surface : SDL_CreateRGBSurface. C'est celle que nous utiliserons à chaque fois que nous voulons créer une surface unie comme ici.
Cette fonction prend... beaucoup de paramètres

(8 !) D'ailleurs, peu d'entre eux nous intéressent pour l'instant, donc je vais éviter de vous détailler ceux qui ne nous serviront pas de suite.
Comme en C nous sommes obligés d'indiquer tous les paramètres (les paramètres facultatifs n'existent qu'en C++), nous enverrons la valeur 0 quand le paramètre ne nous intéresse pas.
Regardons de plus près les 4 premiers paramètres, les plus intéressants :
- Une liste de flags (des options). Vous avez le choix entre :
- SDL_HWSURFACE : la surface sera chargée en mémoire vidéo. Il y a moins d'espace dans cette mémoire que dans la mémoire système (quoique, avec les cartes 3D qu'on sort de nos jours...), mais cette mémoire est plus optimisée et accélérée.
- SDL_SWSURFACE : la surface sera chargée en mémoire système où il y a beaucoup de place, mais cela obligera votre processeur à faire plus de calculs. Si vous aviez chargé la surface en mémoire vidéo, c'est la carte 3D qui aurait fait la plupart des calculs.
- La largeur de la surface (en pixels)
- La hauteur de la surface (en pixels)
- Le nombre de couleurs (en bits / pixel)
Voici donc comment on alloue notre nouvelle surface en mémoire :
Code : C | rectangle = SDL_CreateRGBSurface(SDL_HWSURFACE, 220, 180, 32, 0, 0, 0, 0);
|
Les 4 derniers paramètres sont mis à 0 comme je vous l'ai dit car ils ne nous intéressent pas.
Comme notre surface a été allouée manuellement, il faudra penser à la libérer de la mémoire avec la fonction SDL_FreeSurface(), à utiliser juste avant SDL_Quit() :
Code : C | SDL_FreeSurface(rectangle);
SDL_Quit();
|
La surface ecran n'a pas besoin d'être libérée avec SDL_FreeSurface(), cela est fait lors de SDL_Quit().
On peut maintenant colorer notre nouvelle surface en la remplissant par exemple de blanc :
Code : C | SDL_FillRect(rectangle, NULL, SDL_MapRGB(ecran->format, 255, 255, 255));
|
Coller la surface à l'écran
Allez, c'est presque fini courage

Notre surface est prête, mais si vous testez le programme vous verrez qu'elle ne s'affichera pas ! En effet, la SDL n'affiche à l'écran que la surface
ecran. Pour que l'on puisse voir notre nouvelle surface, il va falloir
blitter la surface, c'est-à-dire la coller sur l'écran. On utilisera pour cela la fonction
SDL_BlitSurface.
Cette fonction attend :
- La surface à coller (ici, ce sera rectangle)
- Une information sur la partie de la surface à coller (facultative). Ca ne nous intéresse pas car on veut coller toute la surface, donc on enverra NULL
- La surface sur laquelle on doit coller, c'est-à-dire ecran dans notre cas.
- Un pointeur sur une variable contenant des coordonnées. Ces coordonnées indiquent où devra être collée notre surface sur l'écran (la position quoi
)
Pour indiquer les coordonnées, on doit utiliser une variable de type SDL_Rect.
C'est une structure qui contient plusieurs sous-variables, dont 2 qui nous intéressent ici :
- x : l'abscisse
- y : l'ordonnée
Il faut savoir que le point de coordonnées (0, 0) est situé tout en haut à gauche.
En bas à droite, le point a les coordonnées (640, 480) si vous avez ouvert une fenêtre de taille 640x480 comme moi.
Aidez-vous de ce schéma pour vous situer :
Si vous avez déjà fait des maths une fois dans votre vie vous ne devriez pas être trop perturbé
Créons donc une variable position. On va mettre x et y à 0 pour coller la surface en haut à gauche de l'écran :
Code : C | SDL_Rect position;
position.x = 0;
position.y = 0;
|
Maintenant qu'on a notre variable
position, on peut blitter notre rectangle sur l'écran :
Code : C | SDL_BlitSurface(rectangle, NULL, ecran, &position);
|
(remarquez le symbole &, car il faut envoyer l'adresse de notre variable position).
En résumé...
Je crois qu'un petit code pour résumer tout ça ne sera pas superflu
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | int main(int argc, char *argv[])
{
SDL_Surface *ecran = NULL, *rectangle = NULL;
SDL_Rect position;
SDL_Init(SDL_INIT_VIDEO);
ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
rectangle = SDL_CreateRGBSurface(SDL_HWSURFACE, 220, 180, 32, 0, 0, 0, 0); // Allocation de la surface
SDL_WM_SetCaption("Ma super fenêtre SDL !", NULL);
SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 17, 206, 112));
position.x = 0; // Les coordonnées de la surface seront (0, 0)
position.y = 0;
SDL_FillRect(rectangle, NULL, SDL_MapRGB(ecran->format, 255, 255, 255)); // Remplissage de la surface avec du blanc
SDL_BlitSurface(rectangle, NULL, ecran, &position); // Collage de la surface sur l'écran
SDL_Flip(ecran); // Mise à jour de l'écran
pause();
SDL_FreeSurface(rectangle); // Libération de la surface
SDL_Quit();
return EXIT_SUCCESS;
}
|
Et voilà le travail
Sympa non ?
Centrer la surface à l'écran
On sait afficher la surface en haut à gauche.
Il serait aussi facile de la mettre en bas à droite. Les coordonnées seraient (640 - 220, 480 - 180), car il faut retrancher la taille de notre rectangle pour qu'il s'affiche entièrement.
Mais... comment faire pour centrer le rectangle blanc ?
Si vous réfléchissez bien 2 secondes, c'est un simple petit calcul mathématique
Code : C | position.x = (640 / 2) - (220 / 2); // La surface sera centrée
position.y = (480 / 2) - (180 / 2);
|
L'abscisse du rectangle sera la moitié de la largeur de l'écran (640 / 2). Mais, en plus de ça, il faut retrancher la moitié de la largeur du rectangle (220 / 2), car sinon ça ne sera pas parfaitement centré (essayez de ne pas le faire, vous verrez ce que je veux dire

)
C'est la même chose pour l'ordonnée avec la hauteur de l'écran et du rectangle.
Résultat :
C'est pas magique, c'est mathématique !