FAQ SDL
FAQ SDLConsultez toutes les FAQ
Nombre d'auteurs : 6, nombre de questions : 67, création le 10 mai 2013
- Comment appliquer une rotation, un zoom ou n'importe quelle transformation à une surface ?
- Comment récupérer des informations sur le hardware vidéo du système ?
- Comment modifier les pixels d'une surface ?
- Comment définir des couleurs avec SDL_MapRGB() ?
- Comment lire d'autres formats d'image que le BMP ?
- Comment sélectionner une couleur de transparence ?
- Comment donner de la transparence alpha à une surface ?
- Comment gérer la vitesse d'affichage ?
- Quelle est la différence entre les surfaces software et hardware ?
- Faut-il utiliser les surfaces software ou hardware ?
- Qu'est-ce que le double buffering ?
- Comment faire une capture d'écran ?
- À quoi sert SDL_DisplayFormat ?
- Comment faire pour que SDL_DisplayFormat ne supprime pas la transparence ?
- À quoi sert SDL_Flip ?
- Comment se servir de SDL_Flip ?
SDL étant basée sur des (parfois vieilles) API 2D, des opérations telles que l'alpha-blending, les rotations ou n'importe quelle transformation ne peut être accélérée par le matériel. Vous pouvez bien sûr les effectuer « à la main » avec le CPU, mais ce sera très lent.
Une bibliothèque réglant tous ces problèmes existe, il s'agit de SGE. Elle permet :
- des opérations sur les pixels (alpha-blending par exemple) ;
- du clipping ;
- de dessiner des lignes, cercles et autres figures (avec antialiasing et alpha-blending) ;
- d'effectuer des rotations et mises à l'échelle de surfaces ;
- de fournir des classes de sprites ;
- de gérer les polices TrueType pour l'affichage de texte ;
- de gérer des collisions basiques ;
- etc.
SGE est libre (sous licence LGPL), et devrait tourner sur à peu près n'importe quelle plateforme supportant SDL (testé sous Windows, Linux et FreeBSD).
Une autre bibliothèque, SDL_gfx, permet de gérer également des transformations, primitives, zooms et filtres d'images de manière optimisée. Elle fournit les fonctions suivantes (inclure <SDL/SDL_rotozoom.h>) :
rotation suivie d'un zoom :
SDL_Surface *
rotozoomSurface
(
SDL_Surface *
src, double
angle, double
zoom, int
smooth);
une variante permet de faire un zoom différent par rapport à chaque axe :
SDL_Surface *
rotozoomSurfaceXY
(
SDL_Surface *
src, double
angle, double
zoomx, double
zoomy, int
smooth);
puisque la taille va changer, vous pourrez récupérer la taille dans la structure SDL_Surface, mais aussi au préalable avec ces fonctions :
void
rotozoomSurfaceSize
(
int
width, int
height, double
angle, double
zoom, int
*
dstwidth, int
*
dstheight);
void
rotozoomSurfaceSizeXY
(
int
width, int
height, double
angle, double
zoomx, double
zoomy, int
*
dstwidth, int
*
dstheight);
Quelques remarques
- Ces fonctions créent de nouvelles surfaces, donc il faut faire attention à la gestion de la mémoire.
- Ce sont des opérations relativement lourdes, donc cela ne serait pas envisageable dans un programme qui nécessite ces traitementsà chaque frame. Cela sert pour les rares fois où le programme a besoin d'une rotation ou d'un zoom
- Les angles sont donnés en degrés et non en radians.
Enfin, il existe aussi des fonctions qui ne font que les zooms :
SDL_Surface *
zoomSurface
(
SDL_Surface *
src, double
zoomx, double
zoomy, int
smooth);
void
zoomSurfaceSize
(
int
width, int
height, double
zoomx, double
zoomy, int
*
dstwidth, int
*
dstheight);
Lien : Site officiel de SGE
Lien : Site officiel de SDL_gfx
SDL propose une fonction permettant de récupérer toute sorte d'informations utiles sur le matériel :
const
SDL_VideoInfo*
SDL_GetVideoInfo
(
)
Elle retourne un pointeur sur une structure de type SDL_VideoInfo contenant quelques membres donnant des informations sur le système vidéo, comme, entre autres :
const
SDL_VideoInfo*
VideoInfo;
/* Récupération des informations sur le hardware vidéo */
VideoInfo =
SDL_GetVideoInfo
(
);
printf
(
"
Surfaces hardware disponibles -> %i
"
, VideoInfo->
hw_available);
printf
(
"
Gestionnaire de fenêtre disponible -> %i
"
, VideoInfo->
wm_available);
printf
(
"
Support de l'accélération du blit hardware -> %i
"
, VideoInfo->
blit_hw);
printf
(
"
Support de l'accélération du remplissage des couleurs hardware -> %i
"
, VideoInfo->
blit_fill);
printf
(
"
Mémoire vidéo totale sur la carte graphique (en Ko) -> %i
"
, VideoInfo->
video_mem);
printf
(
"
Nombre d'octets par pixels utilisés par la carte graphique -> %i
"
, VideoInfo->
vfmt->
BytesPerPixel);
/* ... */
Pour une liste exhaustive, voir l'habituelle documentation officielle.
Pour faire des transformations de base (couleur et alpha), on peut accéder directement aux pixels grâce au membre pixels de la structure SDL_Surface qui est un pointeur sur les données de pixels.
La fonction suivante sert à modifier le pixel (x, y) d'une SDL_Surface (tiré de la doc officielle) :
void
SetPixel
(
SDL_Surface*
Surface, int
x, int
y, Uint32 pixel)
{
/* p est l'adresse du pixel que l'on veut modifier */
Uint8 *
p =
(
Uint8*
)Surface->
pixels +
y *
Surface->
pitch +
x *
bpp;
switch
(
Surface->
format->
BytesPerPixel)
{
case
1
:
*
p =
pixel;
break
;
case
2
:
*(
Uint16*
)p =
pixel;
break
;
case
3
:
if
(
SDL_BYTEORDER ==
SDL_BIG_ENDIAN)
{
p[0
] =
(
pixel >>
16
) &
0xff
;
p[1
] =
(
pixel >>
8
) &
0xff
;
p[2
] =
pixel &
0xff
;
}
else
{
p[0
] =
pixel &
0xff
;
p[1
] =
(
pixel >>
8
) &
0xff
;
p[2
] =
(
pixel >>
16
) &
0xff
;
}
break
;
case
4
:
*(
Uint32 *
)p =
pixel;
break
;
}
}
La plupart des fonctions de SDL qui utilisent les couleurs demandent un code couleur de type Uint32 qui correspond à un pixel. Cependant, ce type étant peu parlant et interprété différemment selon le format de pixel des surfaces, il existe la fonction SDL_MapRGB qui retourne un Uint32 correspondant au format, d'après les valeurs RGB passées dans les trois derniers paramètres. Le premier paramètre correspond au format de pixel de la surface, qui peut être renseigné grâce au membre format de SDL_Suface :
/* Dessine un rectangle bleu (0, 0, 255) de 30 * 10 pixels sur la surface */
SDL_Rect Rect;
Rect.x =
0
;
Rect.y =
0
;
Rect.w =
30
;
Rect.h =
10
;
SDL_FillRect
(
mySurf, &
Rect, SDL_MapRGB
(
mySurf->
format, 0
, 0
, 255
));
Pour des pixels utilisant le canal alpha, on peut utiliser la fonction :
Uint32 SDL_MapRGBA
(
SDL_PixelFormat*
fmt, Uint8 r, Uint8 g, Uint8 b, Uint8 a)
qui a le même fonctionnement que SDLMapRGB, avec un paramètre de plus pour le canal alpha.
Pour faire l'inverse, c'est-à-dire récupérer chaque canal de couleur depuis un Uint32, il existe la fonction :
void
SDL_GetRGBA
(
Uint32 pixel, SDL_PixelFormat*
fmt, Uint8*
r, Uint8*
g, Uint8*
b, Uint8*
a)
qui fournit les valeurs dans les paramètres correspondants. Il faut aussi renseigner la fonction avec le format de pixel.
Uint8 r, g, b;
/* Récupère la valeur RGB du premier pixel (0, 0) d'une surface */
SDL_GetRGB
((
Uint8)Surf->
pixels, Surf->
format, &
r, &
g, &
b);
printf
(
"
Couleur : rouge = %i, vert = %i, bleu = %i
"
, r, g, b);
Grâce à une bibliothèque presque indispensable avec SDL : SDL_image, que l'on peut télécharger sur le site officiel.
Cette bibliothèque permet de lire les formats suivants : BMP, GIF, JPG, TIF, PNG, TGA et d'autres encore.
On peut ouvrir indifféremment n'importe quel type d'image simplement en appelant la fonction suivante :
SDL_Surface*
IMG_Load(const
char
*
file)
qui retourne la bonne surface.
Cependant, cette bibliothèque permet seulement l'ouverture des fichiers, pas la sauvegarde.
Parfois, on ne veut pas afficher une certaine couleur d'une surface, et garder le fond transparent. Pour dessiner un objet non-rectangle par exemple, ou comportant des « trous » (sprite, personnage, arbre…). On peut pour cela définir une couleur de transparence pour la surface avec la fonction suivante :
int
SDL_SetColorKey
(
SDL_Surface*
surface, Uint32 flag, Uint32 key)
En mettant le flag SDL_SRCCOLORKEY au paramètre flag. Le paramètre key correspond à la couleur à définir comme couleur transparente (voir https://jeux.developpez.com/faq/sdl/?page=2d#2D_definir_rgb).
/* Ouverture d'un BMP, un personnage par exemple */
SDL_Surface*
Perso =
SDL_LoadBMP
(
"
perso.bmp
"
);
/* Autour du perso, on a par exemple mis une horrible couleur magenta :) qu'on ne va jamais utiliser (255, 0, 255) */
SDL_SetColorKey
(
Perso, SDL_SRCCOLORKEY, SDL_MapRGB
(
Perso->
format, 255
, 0
, 255
));
Avec la fonction :
int
SDL_SetAlpha
(
SDL_Surface*
surface, Uint32 flag, Uint8 alpha)
qui prend en premier paramètre la surface sur laquelle il faut appliquer de l'alpha, en second paramètre un flag à renseigner (la plupart du temps avec SDL_SRCALPHA), et en dernier paramètre une valeur entre 0 et 255, où 0 = le plus transparent et 255 = le plus opaque. Les minima et maxima sont aussi définis par les macros SDL_ALPHA_TRANSPARENT et SDL_ALPHA_OPAQUE.
/* Création d'une surface de 400 sur 300 (pour un menu transparent par exemple) */
MenuSurf =
SDL_CreateRGBSurface
(
SDL_HWSURFACE, 400
, 300
, 32
, 0xff000000
, 0x00ff0000
, 0x0000ff00
, 0x000000ff
);
/* On donne un joli effet de transparence */
SDL_SetAlpha
(
MenuSurf, SDL_SRCALPHA, 180
);
/* On dessine notre beau menu sur l'écran avec de la transparence */
SDL_BlitSurface
(
MenuSurf, NULL
, MainScreen, &
PosRect);
Si votre programme tourne normalement sur votre PC, mais trop (ou pas assez) vite lorsque vous l'essayez sur un autre, c'est sûrement que vous n'avez pas géré la fréquence d'affichage. En effet, si l'on ne précise rien et qu'on fait nos opérations en boucle, le programme tournera simplement à la vitesse maximale possible, qui dépend du processeur et d'autres choses encore.
Pour pallier ce problème, on peut utiliser simplement un timer, c'est-à-dire afficher seulement à une fréquence donnée.
Ce code dessine seulement à 20 fps (images par seconde) par exemple :
unsigned
int
checkTime =
SDL_GetTicks
(
);
const
unsigned
int
fps =
20
;
if
(
SDL_GetTicks
(
) >
(
checkTime +
1000
/
fps) )
{
/* Code à exécuter 20 fois / seconde ... */
/* On remet à jour le temps à contrôler */
checkTime =
SDL_GetTicks
(
);
}
On parle de surface software lorsque celle-ci est chargée dans la mémoire vive (RAM) du PC et de mémoire hardware lorsque celle-ci est chargée en mémoire vidéo (RAM de la carte graphique).
Cela dépend de ce que l'on veut faire.
Le principal avantage des surfaces en mémoire vidéo (hardware) c'est que pour l'affichage (le blitting) ce sera beaucoup plus rapide. En effet, la surface sera gérée par la carte graphique et pourra donc être envoyée beaucoup plus vite à l'écran. L'inconvénient c'est que l'on doit accéder à la carte graphique à chaque traitement de l'image.
L'avantage des surfaces en mémoire système (software) c'est que le processeur y accède très rapidement, il sera donc plus facile de la lire, la modifier, la filtrer, etc.
Une chose importante à savoir est que pour activer le double buffering (voir https://jeux.developpez.com/faq/sdl/?page=2d#2D_double_buffering), il faudra mettre la surface de l'écran en mémoire vidéo.
Le double buffering est une fonction intéressante que l'on peut choisir d'activer lors de la création de la fenêtre. En mode double buffer (littéralement « double tampon »), le dessin ne se fera pas directement sur l'écran, mais sur un tampon qui n'est pas affiché. Lorsqu'on a tout dessiné, on inverse les deux tampons et c'est le nouveau qui est affiché en un coup, alors que l'on dessinera sur le second. L'avantage est double : d'une part le dessin se fait en un coup et paraît moins saccadé, d'autre part, on gagne en performances, car pendant que le premier tampon est affiché à l'écran, on peut déjà commencer à dessiner la prochaine frame sur le second.
Pour activer le double buffering il faut mettre le flag SDL_DOUBLEBUF dans la fonction SDL_SetVideoMode(). Pour que l'on soit réellement en double buffering, il faut aussi que la surface de l'écran soit en mémoire vidéo (voir https://jeux.developpez.com/faq/sdl/?page=2d#2D_choix_surface) avec le flag SDL_HWSURFACE dans la fonction SDL_SetVideoMode(), sinon on aura sans s'en apercevoir du simple buffering et un appel à SDL_Flip() sera équivalent à un simple appel à SDL_UpdateRect().
En plus de la fonction SDL_LoadBMP() pour charger des images, SDL propose la fonction suivante :
int
SDL_SaveBMP
(
SDL_Surface*
surface, const
char
*
file)
qui permet d'enregistrer dans un fichier au format BMP la surface passée dans le premier paramètre, sur le chemin passé en second paramètre.
Pour faire une capture de tout l'écran, il suffit d'appeler cette fonction sur la surface de l'écran, celle créée grâce à SDL_SetVideoMode().
SDL_Surface*
SDL_DisplayFormat
(
SDL_Surface*
surface)
Cette fonction prend en entrée une surface quelconque, et renvoie une (autre) surface ayant le même format que la surface vidéo.
L'intérêt réside dans la vitesse de l'exécution de la fonction SDL_BlitSurface. En effet, pour qu'une surface puisse être collée sur une autre, il faut que toutes deux aient le même format de pixel.
Si lors de l'appel à SDL_BlitSurface, les deux surfaces n'ont pas le même format, SDL_BlitSurface effectue la conversion pour vous (en appelant SDL_ConvertSurface). Mais cela prend (énormément) de temps. Or si l'on doit coller une surface à l'écran beaucoup de fois, mieux vaut changer son format dès le départ.
SDL_Surface*
tmp;
SDL_Surface*
mon_image_au_format_ecran;
/* On charge l'image */
tmp =
IMG_Load
(
"
mon_image.png
"
);
/* On vérifie qu'elle a bien été chargée */
if
(
tmp ==
NULL
)
{
/* Erreur */
}
/* On change son format afin qu'il soit le même que celui de l'écran */
mon_image_au_format_ecran =
SDL_DisplayFormat
(
tmp);
/* On revérifie que tout est correct */
if
(
mon_image_au_format_ecran ==
NULL
)
{
/* Erreur */
}
/* On supprime l'image temporaire */
SDL_FreeSurface
(
tmp);
Remarque : il faut bien évidemment que la fonction SDL_SetVideoMode ait été appelée auparavant.
Si les images possèdent un canal de transparence alpha, il faut utiliser SDL_DisplayFormatAlpha. Voir https://jeux.developpez.com/faq/sdl/?page=2d#2D_displayformat_alpha.
On ne peut pas, pour cela il faut utiliser la fonction complémentaire :
SDL_Surface *
SDL_DisplayFormatAlpha
(
SDL_Surface *
surface)
Elle fonctionne sur le même principe, à savoir :
SDL_Surface*
temp =
0
;
SDL_Surface*
mon_image_finale =
0
;
/* On charge une image qui contient de la transparence */
temp =
IMG_Load
(
"
mon_image.png
"
);
if
(
temp ==
0
)
return
0
; /* erreur lors du chargement de l'image */
mon_image_finale =
SDL_DisplayFormatAlpha
(
temp);
if
(
mon_image_finale ==
0
)
{
SDL_FreeSurface
(
temp);
return
0
; /* erreur lors du changement de format */
}
SDL_FreeSurface
(
temp);
Lorsqu'on utilise un double tampon pour l'affichage, le programme dessine la prochaine image en même temps que la carte graphique affiche l'image précédente.
Supposons que la carte graphique affiche le tampon A et que le code de rendu travaille sur le tampon B.
SDL_Flip permet de dire à la carte graphique que le code de rendu sur le tampon B est fini et qu'elle peut à présent l'afficher.
Ainsi, le code de rendu qui va suivre va écrire sur le tampon A pendant que la carte graphique affiche l'image qui se trouve dans le tampon B.
Le prochain SDL_Flip fera que la carte graphique affichera tampon A pendant que le code de rendu travaille sur le tampon B.
Lorsqu'un code SDL est écrit, il possède généralement une boucle globale qui inclut une boucle évènementielle, ceci permet de bien définir l'emplacement de l'appel à SDL_Flip :
while
(
jeuencours ) {
while
(
SDL_PollEvent
(&
event) ) {
case
SDL_QUIT :
jeuencours =
0
;
break
;
default
:
}
/* Code de rendu */
..... Pas de SDL_Flip ....
/* Un seul SDL_Flip */
SDL_Flip
(
ecran);
}
Remarque : il ne faut pas voir SDL_Flip comme une solution pour afficher le dernier Blit qui vient d'être fait. Il faut un et un seul appel par code de rendu.