Apprenez à programmer en C !
Last updated on Thursday, September 25, 2014
  • 4 semaines
  • Moyen

Ce cours est visible gratuitement en ligne.

Paperback available in this course

Ce cours existe en eBook.

Certificate of achievement available at the end this course

Got it!

Afficher des images

Nous venons d'apprendre à charger la SDL, à ouvrir une fenêtre et gérer des surfaces. C'est vraiment la base de ce qu'il faut connaître sur cette bibliothèque. Cependant, pour le moment nous ne pouvons créer que des surfaces unies, c'est-à-dire ayant la même couleur, ce qui est un peu monotone.

Dans ce chapitre, nous allons apprendre à charger des images dans des surfaces, que ce soit des BMP, des PNG, des GIF ou des JPG. La manipulation d'images est souvent très motivante car c'est en assemblant ces images (aussi appelées « sprites ») que l'on fabrique les premières briques d'un jeu vidéo.

Charger une image BMP

La SDL est une bibliothèque très simple. Elle ne propose à la base que le chargement d'images de type « bitmap » (extension .bmp). Ne paniquez pas pour autant, car grâce à une extension de la SDL (la bibliothèque SDL_Image), nous verrons qu'il est possible de charger de nombreux autres types.

Pour commencer, nous allons nous contenter de ce que la SDL offre à la base. Nous allons donc étudier le chargement de BMP.

Le format BMP

Un BMP (abréviation de « Bitmap ») est un format d'image.
Les images que vous voyez sur votre ordinateur sont stockées dans des fichiers. Il existe plusieurs formats d'images, c'est-à-dire plusieurs façons de coder l'image dans un fichier. Selon le format, l'image prend plus ou moins d'espace disque et se trouve être de plus ou moins bonne qualité.

Le Bitmap est un format non compressé (contrairement aux JPG, PNG, GIF, etc.).
Concrètement, cela signifie les choses suivantes :

  • le fichier est très rapide à lire, contrairement aux formats compressés qui doivent être décompressés, ce qui prend un peu plus de temps ;

  • la qualité de l'image est parfaite. Certains formats compressés (je pense au JPG plus particulièrement, car les PNG et GIF n'altèrent pas l'image) détériorent la qualité de l'image, ce n'est pas le cas du BMP ;

  • mais le fichier est aussi bien plus gros puisqu'il n'est pas compressé !

Il a donc des qualités et des défauts.
Pour la SDL, l'avantage c'est que ce type de fichier est simple et rapide à lire. Si vous avez souvent besoin de charger des images au cours de l'exécution de votre programme, il vaut mieux utiliser des BMP : certes le fichier est plus gros, mais il se chargera plus vite qu'un GIF par exemple. Cela peut se révéler utile si votre programme doit charger de très nombreuses images en peu de temps.

Charger un Bitmap

Téléchargement du pack d'images

Nous allons travailler avec plusieurs images dans ce chapitre. Si vous voulez faire les tests en même temps que vous lisez (et vous devriez !), je vous recommande de télécharger un pack qui contient toutes les images dont on va avoir besoin.

Télécharger le pack d'images (1 Mo)

Bien entendu, vous pouvez utiliser vos propres images. Il faudra en revanche adapter la taille de votre fenêtre à celles-ci.

Placez toutes les images dans le dossier de votre projet. Nous allons commencer par travailler avec le fichier lac_en_montagne.bmp. C'est une scène 3D d'exemple tirée de l'excellent logiciel de modélisation de paysages Vue d'Esprit 4, qui n'est aujourd'hui plus commercialisé. Depuis, le logiciel a été renommé en « Vue » et a beaucoup évolué. Si vous voulez en savoir plus, rendez-vous sur e-onsoftware.com.

Charger l'image dans une surface

Nous allons utiliser une fonction qui va charger l'image BMP et la mettre dans une surface.
Cette fonction a pour nom SDL_LoadBMP. Vous allez voir à quel point c'est simple :

maSurface = SDL_LoadBMP("image.bmp");

La fonction SDL_LoadBMP remplace deux fonctions que vous connaissez :

  • SDL_CreateRGBSurface : elle se chargeait d'allouer de la mémoire pour stocker une surface de la taille demandée (équivalent au malloc) ;

  • SDL_FillRect : elle remplissait la structure d'une couleur unie.

Pourquoi est-ce que ça remplace ces deux fonctions ? C'est très simple :

  • la taille à allouer en mémoire pour la surface dépend de la taille de l'image : si l'image a une taille de 250 x 300, alors votre surface aura une taille de 250 x 300 ;

  • d'autre part, votre surface sera remplie pixel par pixel par le contenu de votre image BMP.

Codons sans plus tarder :

int main(int argc, char *argv[])
{
    SDL_Surface *ecran = NULL, *imageDeFond = NULL;
    SDL_Rect positionFond;

    positionFond.x = 0;
    positionFond.y = 0;

    SDL_Init(SDL_INIT_VIDEO);

    ecran = SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE);
    SDL_WM_SetCaption("Chargement d'images en SDL", NULL);

    /* Chargement d'une image Bitmap dans une surface */
    imageDeFond = SDL_LoadBMP("lac_en_montagne.bmp");
    /* On blitte par-dessus l'écran */
    SDL_BlitSurface(imageDeFond, NULL, ecran, &positionFond);

    SDL_Flip(ecran);
    pause();

    SDL_FreeSurface(imageDeFond); /* On libère la surface */
    SDL_Quit();

    return EXIT_SUCCESS;
}

J'ai donc créé un pointeur vers une surface (imageDeFond) ainsi que les coordonnées correspondantes (positionFond).
La surface est créée en mémoire et remplie par la fonction SDL_LoadBMP.
On la blitte ensuite sur la surface ecran et c'est tout ! Admirez le résultat sur la fig. suivante.

Une image BMP chargée dans la fenêtre

Comme vous voyez ce n'était pas bien difficile !

Associer une icône à son application

Maintenant que nous savons charger des images, nous pouvons découvrir comment associer une icône à notre programme. L'icône sera affichée en haut à gauche de la fenêtre (ainsi que dans la barre des tâches). Pour le moment nous avons une icône par défaut.

Mais, les icônes des programmes ne sont-elles pas des .ico, normalement ?

Non, pas forcément ! D'ailleurs les .ico n'existent que sous Windows. La SDL réconcilie tout le monde en utilisant un système bien à elle : une surface !
Eh oui, l'icône d'un programme SDL n'est rien d'autre qu'une simple surface.

Pour ajouter l'icône à la fenêtre, on utilise la fonction SDL_WM_SetIcon.
Cette fonction prend deux paramètres : la surface qui contient l'image à afficher ainsi que des informations sur la transparence (NULL si on ne veut pas de transparence). La gestion de la transparence d'une icône est un peu compliquée (il faut préciser un à un quels sont les pixels transparents), nous ne l'étudierons donc pas.

On va combiner deux fonctions en une :

SDL_WM_SetIcon(SDL_LoadBMP("sdl_icone.bmp"), NULL);

L'image est chargée en mémoire par SDL_LoadBMP et l'adresse de la surface est directement envoyée à SDL_WM_SetIcon.

Voici le code source complet. Vous noterez que j'ai simplement ajouté le SDL_WM_SetIcon par rapport au code précédent :

int main(int argc, char *argv[])
{
    SDL_Surface *ecran = NULL, *imageDeFond = NULL;
    SDL_Rect positionFond;

    positionFond.x = 0;
    positionFond.y = 0;

    SDL_Init(SDL_INIT_VIDEO);

    /* Chargement de l'icône AVANT SDL_SetVideoMode */
    SDL_WM_SetIcon(SDL_LoadBMP("sdl_icone.bmp"), NULL);

    ecran = SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE);
    SDL_WM_SetCaption("Chargement d'images en SDL", NULL);

    imageDeFond = SDL_LoadBMP("lac_en_montagne.bmp");
    SDL_BlitSurface(imageDeFond, NULL, ecran, &positionFond);

    SDL_Flip(ecran);
    pause();

    SDL_FreeSurface(imageDeFond);
    SDL_Quit();

    return EXIT_SUCCESS;
}

Résultat, l'icône est chargée et affichée sur la fenêtre (fig. suivante).

Une icône associée au programme

Gestion de la transparence

Le problème de la transparence

Nous avons tout à l'heure chargé une image bitmap dans notre fenêtre.
Supposons que l'on veuille blitter une image par-dessus. Ça vous arrivera très fréquemment car dans un jeu, en général, le personnage que l'on déplace est un Bitmap et il se déplace sur une image de fond.

On va blitter l'image de Zozor (il s'agit de la bonne vieille mascotte du Site du Zéro pour ceux qui ne le connaîtraient pas) sur la scène :

int main(int argc, char *argv[])
{
    SDL_Surface *ecran = NULL, *imageDeFond = NULL, *zozor = NULL;
    SDL_Rect positionFond, positionZozor;

    positionFond.x = 0;
    positionFond.y = 0;
    positionZozor.x = 500;
    positionZozor.y = 260;

    SDL_Init(SDL_INIT_VIDEO);

    SDL_WM_SetIcon(SDL_LoadBMP("sdl_icone.bmp"), NULL);

    ecran = SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE);
    SDL_WM_SetCaption("Chargement d'images en SDL", NULL);

    imageDeFond = SDL_LoadBMP("lac_en_montagne.bmp");
    SDL_BlitSurface(imageDeFond, NULL, ecran, &positionFond);

    /* Chargement et blittage de Zozor sur la scène */
    zozor = SDL_LoadBMP("zozor.bmp");
    SDL_BlitSurface(zozor, NULL, ecran, &positionZozor);

    SDL_Flip(ecran);
    pause();

    SDL_FreeSurface(imageDeFond);
    SDL_FreeSurface(zozor);
    SDL_Quit();

    return EXIT_SUCCESS;
}

On a juste rajouté une surface pour y stocker Zozor, que l'on blitte ensuite à un endroit sur la scène (fig. suivante).

Zozor blitté par-dessus l'image de fond

C'est plutôt laid, non ?

Je sais pourquoi, c'est parce que tu as mis un fond bleu tout moche sur l'image de Zozor !

Parce que vous croyez qu'avec un fond noir ou un fond marron derrière Zozor, ça aurait été plus joli ? Eh bien non, le problème ici c'est que notre image est forcément rectangulaire, donc si on la colle sur la scène on voit son fond, ce qui ne rend pas très bien.

Heureusement, la SDL gère la transparence !

Rendre une image transparente

Étape 1 : préparer l'image

Pour commencer, il faut préparer l'image que vous voulez blitter sur la scène.
Le format BMP ne gère pas la transparence, contrairement aux GIF et PNG. Il va donc falloir utiliser une astuce.

Il faut mettre la même couleur de fond sur toute l'image. Celle-ci sera rendue transparente par la SDL au moment du blit. Observez à quoi ressemble mon zozor.bmp de plus près (fig. suivante).

L'image zozor.bmp a un fond bleu

Le fond bleu derrière est donc volontaire. Notez que j'ai choisi le bleu au hasard, j'aurais très bien pu mettre un fond vert ou rouge par exemple. Ce qui compte, c'est que cette couleur soit unique et unie. J'ai choisi le bleu parce qu'il n'y en avait pas dans l'image de Zozor. Si j'avais choisi le vert, j'aurais pris le risque que l'herbe que machouille Zozor (en bas à gauche de l'image) soit rendue transparente.

À vous donc de vous débrouiller avec votre logiciel de dessin (Paint, Photoshop, The Gimp, chacun ses goûts) pour donner un fond uni à votre image.

Étape 2 : indiquer la couleur transparente

Pour indiquer à la SDL la couleur qui doit être rendue transparente, vous devez utiliser la fonction SDL_SetColorKey. Cette fonction doit être appelée avant de blitter l'image.
Voici comment je m'en sers pour rendre le bleu derrière Zozor transparent :

SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor->format, 0, 0, 255));

Il y a trois paramètres :

  • la surface qui doit être rendue transparente (ici, c'est zozor) ;

  • une liste de flags : utilisez SDL_SRCCOLORKEY pour activer la transparence, 0 pour la désactiver ;

  • indiquez ensuite la couleur qui doit être rendue transparente. J'ai utilisé SDL_MapRGB pour créer la couleur au format nombre (Uint32) comme on l'a déjà fait par le passé. Comme vous le voyez, c'est le bleu pur (0, 0, 255) que je rends transparent.

En résumé, on charge d'abord l'image avec SDL_LoadBMP, on indique la couleur transparente avec SDL_SetColorKey, puis on peut blitter avec SDL_BlitSurface :

/* On charge l'image : */
zozor = SDL_LoadBMP("zozor.bmp");
/* On rend le bleu derrière Zozor transparent : */
SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor->format, 0, 0, 255));
/* On blitte l'image maintenant transparente sur le fond : */
SDL_BlitSurface(zozor, NULL, ecran, &positionZozor);

Résultat : Zozor est parfaitement intégré à la scène (fig. suivante) !

Zozor transparent sur l'image de fond

Voilà LA technique de base que vous réutiliserez tout le temps dans vos futurs programmes. Apprenez à bien manier la transparence car c'est fondamental pour réaliser un jeu un minimum réaliste.

La transparence Alpha

C'est un autre type de transparence.
Jusqu'ici, on se contentait de définir UNE couleur de transparence (par exemple le bleu). Cette couleur n'apparaissait pas une fois l'image blittée.

La transparence Alpha correspond à tout autre chose. Elle permet de réaliser un « mélange » entre une image et le fond. C'est une sorte de fondu.

La transparence Alpha d'une surface peut être activée par la fonction SDL_SetAlpha :

SDL_SetAlpha(zozor, SDL_SRCALPHA, 128);

Il y a là encore trois paramètres :

  • la surface en question (zozor) ;

  • une liste de flags : mettez SDL_SRCALPHA pour activer la transparence, 0 pour la désactiver ;

  • très important : la valeur Alpha de la transparence. C'est un nombre compris entre 0 (image totalement transparente, donc invisible) et 255 (image totalement opaque, comme s'il n'y avait pas de transparence Alpha).

Plus le nombre Alpha est petit, plus l'image est transparente et fondue.

Voici par exemple un code qui applique une transparence Alpha de 128 à notre Zozor :

zozor = SDL_LoadBMP("zozor.bmp");
SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor->format, 0, 0, 255));
/* Transparence Alpha moyenne (128) : */
SDL_SetAlpha(zozor, SDL_SRCALPHA, 128);
SDL_BlitSurface(zozor, NULL, ecran, &positionZozor);

Vous noterez que j'ai conservé la transparence de SDL_SetColorKey. Les deux types de transparence sont en effet combinables.

La fig. suivante vous montre à quoi ressemble Zozor selon la valeur Alpha.

Alpha

Aperçu

255
(entièrement opaque)

Image utilisateurImage utilisateurImage utilisateur

190

Image utilisateurImage utilisateurImage utilisateur

128
(transparence moyenne)

Image utilisateurImage utilisateurImage utilisateur

75

Image utilisateurImage utilisateurImage utilisateur

0
(entièrement transparente)

Image utilisateurImage utilisateurImage utilisateur

Charger plus de formats d'image avec SDL_Image

La SDL ne gère que les Bitmap (BMP) comme on l'a vu.
A priori, ce n'est pas un très gros problème parce que la lecture des BMP est rapide pour la SDL, mais il faut reconnaître qu'aujourd'hui on a plutôt l'habitude d'utiliser d'autres formats. En particulier, nous sommes habitués aux formats d'images « compressés » comme le PNG, le GIF et le JPEG. Ça tombe bien, il existe justement une bibliothèque SDL_Image qui gère tous les formats suivants :

  • TGA ;

  • BMP ;

  • PNM ;

  • XPM ;

  • XCF ;

  • PCX ;

  • GIF ;

  • JPG ;

  • TIF ;

  • LBM ;

  • PNG.

Il est en fait possible de rajouter des extensions à la SDL. Ce sont des bibliothèques qui ont besoin de la SDL pour fonctionner. On peut voir ça comme des add-ons (on emploie aussi parfois le mot « greffon », plus français). SDL_Image est l'une d'entre elles.

Installer SDL_image sous Windows

Téléchargement

Une page spéciale du site de la SDL référence les bibliothèques utilisant la SDL. Cette page s'intitule Libraries, vous trouverez un lien dans le menu de gauche.
Vous pourrez voir qu'il y a beaucoup de bibliothèques disponibles. Celles-ci ne proviennent généralement pas des auteurs de la SDL, ce sont plutôt des utilisateurs de la SDL qui proposent leurs bibliothèques pour améliorer la SDL.

Certaines sont très bonnes et méritent le détour, d'autres sont moins bonnes et encore boguées. Il faut arriver à faire le tri.

Cherchez SDL_Image dans la liste… vous arriverez sur la page dédiée à SDL_Image.

Téléchargez la version de SDL_Image qui vous correspond dans la section Binary (ne prenez PAS la source, on n'en a pas besoin !). Si vous êtes sous Windows, téléchargez SDL_image-devel-1.2.10-VC.zip, et ce même si vous n'utilisez pas Visual C++ !

Installation

Dans ce .zip, vous trouverez :

  • SDL_image.h : le seul header dont a besoin la bibliothèque SDL_Image. Placez-le dans C:\Program Files\CodeBlocks\SDL-1.2.13\include, c'est-à-dire à côté des autres headers de la SDL ;

  • SDL_image.lib : copiez dans C:\Program Files\CodeBlocks\SDL-1.2.13\lib. Oui, je sais, je vous ai dit que normalement les .lib étaient des fichiers réservés à Visual, mais ici exceptionnellement le .lib fonctionnera même avec le compilateur mingw ;

  • plusieurs DLL : placez-les toutes dans le dossier de votre projet (à côté de SDL.dll, donc).

Ensuite, vous devez modifier les options de votre projet pour « linker » avec le fichier SDL_image.lib.

Si vous êtes sous Code::Blocks par exemple, allez dans le menu Projects / Build options. Dans l'onglet Linker, cliquez sur le bouton Add et indiquez où se trouve le fichier SDL_image.lib (fig. suivante).

Sélection de SDL_image.lib

Si on vous demande Keep as a relative path?, répondez ce que vous voulez, ça ne changera rien dans l'immédiat. Je recommande de répondre par la négative, personnellement.

Ensuite, vous n'avez plus qu'à inclure le header SDL_image.h dans votre code source. Selon l'endroit où vous avez placé le fichier SDL_image.h, vous devrez soit utiliser ce code :

#include <SDL/SDL_image.h>

… soit celui-ci :

#include <SDL_image.h>

Essayez les deux, l'un des deux devrait fonctionner.

Installer SDL_image sous Mac OS X

Si vous utilisez Mac OS X, téléchargez le fichier .dmg sur le site de la SDL et mettez-le dans le dossier /Library/Frameworks (/Bibliothèque/Frameworks en français).

Ensuite, tapez « search paths » dans le champ de recherche de Xcode. Repérez la ligne Header search paths ; double-cliquez sur la ligne à droite, et ajoutez « /Library/Frameworks/SDL_image.framework/Headers ».

Il ne vous reste plus qu'à ajouter le framework à votre projet. La figure suivante vous montre à quoi ressemble le Header search paths du projet après avoir installé SDL_image.

Le Header search paths du projet

Il faudra en revanche inclure le fichier .h dans votre code comme ceci :

#include "SDL_image.h"

… au lieu d'utiliser des chevrons < >. Remplacez donc la ligne d'include de SDL_image dans le code qui va suivre par celle que je viens de vous donner.

Charger les images

En fait, installer SDL_image est 100 fois plus compliqué que de l'utiliser, c'est vous dire la complexité de la bibliothèque !

Il y a UNE seule fonction à connaître : IMG_Load.
Elle prend un paramètre : le nom du fichier à ouvrir.

Ce qui est pratique, c'est que cette fonction est capable d'ouvrir tous les types de fichiers que gère SDL_image (GIF, PNG, JPG, mais aussi BMP, TIF…). Elle détectera toute seule le type du fichier en fonction de son extension.

Autre bon point : si l'image que vous chargez gère la transparence (comme c'est le cas des PNG et des GIF), alors SDL_image activera automatiquement la transparence pour cette image ! Cela vous évite donc d'avoir à appeler SDL_SetColorKey.

Je vais vous présenter le code source qui charge sapin.png et l'affiche.
Notez bien que j'inclue SDL/SDL_image.h et que je ne fais pas appel à SDL_SetColorKey car mon PNG est naturellement transparent.
Vous allez voir que j'utilise IMG_Load partout dans ce code en remplacement de SDL_LoadBMP.

#include <stdlib.h>
#include <stdio.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h> /* Inclusion du header de SDL_image (adapter le dossier au besoin) */
 
void pause();
 
int main(int argc, char *argv[])
{
    SDL_Surface *ecran = NULL, *imageDeFond = NULL, *sapin = NULL;
    SDL_Rect positionFond, positionSapin;
 
    positionFond.x = 0;
    positionFond.y = 0;
    positionSapin.x = 500;
    positionSapin.y = 260;
 
    SDL_Init(SDL_INIT_VIDEO);
 
    SDL_WM_SetIcon(IMG_Load("sdl_icone.bmp"), NULL);
 
    ecran = SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE);
    SDL_WM_SetCaption("Chargement d'images en SDL", NULL);
 
    imageDeFond = IMG_Load("lac_en_montagne.bmp");
    SDL_BlitSurface(imageDeFond, NULL, ecran, &positionFond);
 
    /* Chargement d'un PNG avec IMG_Load
    Celui-ci est automatiquement rendu transparent car les informations de
    transparence sont codées à l'intérieur du fichier PNG */
    sapin = IMG_Load("sapin.png");
    SDL_BlitSurface(sapin, NULL, ecran, &positionSapin);
 
    SDL_Flip(ecran);
    pause();
 
    SDL_FreeSurface(imageDeFond);
    SDL_FreeSurface(sapin);
    SDL_Quit();
 
    return EXIT_SUCCESS;
}
 
void pause()
{
    int continuer = 1;
    SDL_Event event;
 
    while (continuer)
    {
        SDL_WaitEvent(&event);
        switch(event.type)
        {
            case SDL_QUIT:
                continuer = 0;
        }
    }
}

Comme on peut le voir sur la fig. suivante, l'image PNG a été insérée avec la transparence sur l'image de fond !

Une image PNG transparente insérée à l'aide de SDL_image

En résumé

  • La SDL permet de charger des images dans des surfaces. Par défaut, elle ne gère que les BMP avec SDL_LoadBMP.

  • On peut définir une couleur transparente avec SDL_SetColorKey.

  • On peut rendre l'ensemble de l'image plus ou moins transparent avec la fonction SDL_SetAlpha .

  • La bibliothèque SDL_image permet d'insérer n'importe quel type d'image (PNG, JPEG…) avec IMG_Load. Il faut cependant l'installer en plus de la SDL.

Example of certificate of achievement
Example of certificate of achievement