Lolokai – Supervision, systèmes, réseaux, base de données…

5

Gestion des surfaces avec la SDL

Julien 15 mars 2012

Introduction

Dans cet article nous allons voir comment créer une classe statique permettant de manipuler les surfaces dans SDL. Une surface est une zone de la mémoire graphique qui peut être dessinée à l’écran. On pourrait simplifier en disant qu’une surface est un élément graphique de notre application. Dans notre classe nous utiliserons aussi une extension SDL permettant de charger des images, appelé SDL_image. Le but de cette classe sera de rassembler tous les outils offerts par la SDL dans un même emplacement.

Installation SDL_image

Nous allons commencer par installer notre extension, SDL_image.

Téléchargement
Le fichier se trouve sur cette page: SDL_image.
Il faudra prendre l’archive SDL_Image-devel-1.2.12-VC.zip qui se trouve dans la section Binary > Windows. Cette page propose quelques informations bien utiles sur l’extension, n’hésitez pas à la lire.

Installation
Dans l’archive que vous venez de télécharger, il y a 2 dossier, un include et un lib.

Le contenu dossier include doit être décompressé dans le dossier SDL situé dans le dossier include de MinGW (par défaut dans C:/MinGW/include/SDL/).

Le dossier lib contient deux sous-dossiers x64 et x86, il vaut mieux prendre le contenu du dossier x86 pour des soucis de compatibilité. Décompresser ce contenu dans le dossier lib de MinGW (par défaut dans C:/MinGW/lib/).

Liaison dans Eclipse
Il faut maintenant lier la librairie SDL_image à votre projet. Ouvrez Eclipse allez dans Project > Properties dans la liste de droite choisissez C/C++ General > Paths and Symbols puis dans l’onglet Libraries. Ajoutez comme suit.

Installation librairie SDL_image dans Eclipse

Toutes les extensions SDL sont à ajouter de la même manière.

Modèle de classe

Avant de créé la classe nous devons réfléchir à son contenu. Nous savons que la SDL propose des fonctions permettant de manipuler des surfaces comme par exemple, pour créer une surface (SDL_CreateRGB), coller (SDL_BlitSurface) et beaucoup d’autres. Certaines de ces fonctions nécessitent de faire des opérations avant de pouvoir les utiliser. Ce serait donc une bonne chose de placer toute cette logique dans notre classe, ce qui éviterais la répétition de code, qui engendrerais une maintenance plus difficile. Cependant notre classe sera différente des autres classes, elle ne représentera pas un objet comme le ferais un classe Personnage ou Arme. Elle ne sera qu’une structure qui contiendra nos différentes méthodes.

Aussi en C++, il n’existe pas vraiment de classe statique comme en PHP par exemple. Ce sont ses méthodes qui seront statiques.

Création de la classe

Nous devons maintenant créer une nouvelle classe, nous allons pour cela utiliser le Wizard d’Eclipse. Allez dans File > New > Class.

Wizard Eclipse

Si la case « Namespace » est cochez, décochez là on en aura pas besoin ici.

Ce qui nous intéresse ici, c’est le champ Class Name, donnez la comme valeur Surface.

Par défaut, Eclipse place le fichier en-tête (.h) et le fichier source (.cpp) à la racine de votre projet. Vous pouvez les placer dans un dossier, selon votre organisation.

Cliquez sur Finish pour terminer. Vous devez vous retrouver avec deux fichiers comme suit:

Surface.h

#ifndef SURFACE_H_
#define SURFACE_H_

class Surface {
public:
Surface();
virtual ~Surface();
};

#endif /* SURFACE_H_ */

Surface.cpp

#include "Surface.h"

Surface::Surface() {
// TODO Auto-generated constructor stub

}

Surface::~Surface() {
// TODO Auto-generated destructor stub
}

Il ne faut pas oubliez l’inclusion des 2 librairies que nous allons utiliser dans cette classe.

#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <iostream.h> //Utile pour le débogage.

Voilà notre classe est maintenant créée, il nous faut maintenant ajouter les méthodes statiques utiles à notre application.

Note: Je commenterais chaque ligne du code, même la plus simple, pour être sûr que vous comprendrais chaque opération.

Chargement de fichier

C’est dans cette méthode que notre extension SDL_image sera utilisé. La SDL fourni une fonction SDL_LoadBMP qui permet de charger des images. Mais comme son nom le présuppose, elle ne permet que de charger des images au format BMP. L’extension SDL_image permet de débloquer cette situation en permettant le chargement d’images dans les formats les plus connu comme le PNG, JPG, GIF,.. avec une seule fonction IMG_Load. Cette méthode fournira aussi une version optimisée de la surface. Optimisé veut dire que la surface aura le même format de pixel et de couleur que le framebuffer, ce qui améliorera ainsi la vitesse de collage.
La méthode comporte aussi une ligne de débogage (SDL_GetError) qu’il faudra supprimer, lorsque le projet sera en production.

Ajout du prototype dans Surface.h

static SDL_Surface* chargeImage(const char* file);

Ajout du code dans Surface.cpp

SDL_Surface* Surface::chargeImage(const char* file) {

//Surface qui contiendra l'image chargé
SDL_Surface* surfaceBasique = NULL;

//Surface qui contiendra l'image optimisé
SDL_Surface* surfaceOptimise = NULL;

//Charge l'image
surfaceBasique = IMG_Load(file);

//Test si chargement réussit
if(surfaceBasique != NULL) {

//Copie une version optimisé de l'image chargé
surfaceOptimise = SDL_DisplayFormat(surfaceBasique);

//Supprime l'image basique, pour libéré la mémoire
SDL_FreeSurface(surfaceBasique);
}
//Image n'a pas pu être chargé
else {
cout << "Erreur :" << SDL_GetError() << endl;
}

//Retourne la surface optimisé
return surfaceOptimise;
}
&#91;/code&#93;
<h2><span style="color: #33cccc;">Collage de surface simple</span></h2>
Cette méthode permet de coller une surface sur une autre, de la source vers la destination, selon des coordonnées X et Y. Dans notre prototype, la destination est égale à NULL. Bien sûr on ne peut pas coller une source sur rien. Cette notation permet en fait, que lorsque la destination est à NULL, on colle directement sur la surface vidéo. Les coordonnées permettent de définir l'emplacement où la source sera collé sur la surface de destination.

<strong>Surface.h</strong>

static void collage(SDL_Surface* source, SDL_Surface* destination = NULL, int destinationX, int destinationY);

Surface.cpp

void Surface::collage(SDL_Surface* source, SDL_Surface* destination, int destinationX, int destinationY) {

//Si la source est null == Erreur
if(source == NULL) {
cout << "La source est vide, impossible de Blitter" << endl;
return;
}

//Si la destination est null
if(destination == NULL) {

//Utilise la surface de la fenêtre
destination = SDL_GetVideoSurface();
}

//Zone destination
SDL_Rect destinationRect;
destinationRect.x = destinationX;
destinationRect.y = destinationY;

//Applique une surface
SDL_BlitSurface(source, NULL, destination, &amp;amp;amp;destinationRect);
}

&#91;/code&#93;
<h2><span style="color: #33cccc;">Collage de surface avancé</span></h2>
Le collage avancé effectue les mêmes opérations que le collage simple, mais elle permet de ne coller qu'une seule partie de la surface source sur la surface de destination.

<strong>Surface.h</strong>

static void collage(SDL_Surface* source, SDL_Surface* destination = NULL, int destinationX, int destinationY, int sourceX, int sourceY, int sourceH, int sourceW);

Surface.cpp

void Surface::collage(SDL_Surface* source, SDL_Surface* destination, int destinationX, int destinationY, int sourceX, int sourceY, int sourceH, int sourceW) {

//Si la source est null == Erreur
if(source == NULL) {
cout << "La source est vide, impossible de coller" << endl; return; } //Si la destination est null if(destination == NULL) { //Utilise la surface de la fenêtre destination = SDL_GetVideoSurface(); } //Zone source SDL_Rect sourceRect; sourceRect.x = sourceX; sourceRect.y = sourceY; sourceRect.h = sourceH; sourceRect.w = sourceW; //Zone destination SDL_Rect destinationRect; destinationRect.x = destinationX; destinationRect.y = destinationY; //Applique une surface SDL_BlitSurface(source, &amp;amp;sourceRect, destination, &amp;amp;destinationRect); } [/code]

Conclusion

Vous avez maintenant une classe statique fonctionnelle, prête à être utiliser dans vos projets. Elle permet d’effectuer les actions les plus courantes avec les Surfaces. Les méthodes présentées ici peuvent améliorer pour mieux servir vos besoins.

Et vous, quelle méthode auriez-vous rajouter dans cette classe ? Gérez-vous SDL avec un autre IDE ?Julien

Tagged with: , , , , ,

Comments (5)

  1. Je ne vois pas du tout l’intérêt de faire une classe statique pour les surfaces SDL…
    Pourquoi ne pas plutôt faire une classe Surface qui encapsulerait la SDL_Surface et les fonctions qui lui sont relatives?
    Ce serait plus cohérent et ça collerait plus au paradigme objet.
    Et si on voulait vraiment procéder de la sorte (un ensemble de fonctions), on utiliserait un namespace au lieu d’une classe…

    Pour ce qui est des erreurs, il serait plus approprié de les écrire dans std::cerr plutôt que dans std::cout; ou encore de lancer une exception.

    En réponse dernière question, il y a pas mal de méthodes à ajouter, jeter un coup d’oeil à la doc SDL (à commencer par getPixel et setPixel).

    Répondre
    • Cette article est en faite dirigé vers les débutants, bien que je ne le dis pas, tu l’as très bien compris. Je voulais trouver un moyen d’aborder la notion de classe statique, et j’ai trouver un exemple d’utilisation avec cette petite classe.
      Oui, c’est vrai tu as raison, on aurait pu employé un namespace c’est vrai.
      Aussi pour setPixel et getPixel, sa sortait un peu de la logique « débutant ». Et je ne pense pas faire mieux que ce qui existe déjà, quand on voit qu’un setpixel ne prend qu’une ligne de code (si on reste dans un format de pixel géré unique)… bon.

      Mais je tiens à te remercier, j’apprend.

      Répondre
      • « Je voulais trouver un moyen d’aborder la notion de classe statique, et j’ai trouver un exemple d’utilisation avec cette petite classe. »

        Je comprends que l’article s’adresse aux débutants, c’est une raison de plus pour leur inculquer de bonnes pratiques.
        C’est fondamental dans tout apprentissage d’avoir de bonnes bases; a fortiori lorsqu’il s’agit de programmation, suis-je tenté de dire…

        Le principal intérêt d’un tutoriel qui présente un concept tel que celui de classe statique est de montrer en quoi il est utile, et dans quels cas y recourir au travers d’un exemple bien choisi. Or ici, il me semble que la solution est maladroite et inadaptée au problème; on passe quand même à côté du concept d’objet.
        De plus la classe reste instanciable…

        Mon intention est seulement de clarifier les choses et mettre en garde le programmeur en herbe qui s’initie aux joies(?) de la POO: les « classes utilitaires » peuvent se révéler utiles, mais c’est *rarement* un bon choix de conception.
        En l’occurrence, il serait plus judicieux d’opter pour la solution que j’ai mentionnée plus haut, plus naturelle: « une classe Surface qui encapsulerait la SDL_Surface et les fonctions qui lui sont relatives ».

        PS:
        « Mais je tiens à te remercier, j’apprend. »
        Je t’en prie :)

        « quand on voit qu’un setpixel ne prend qu’une ligne de code (si on reste dans un format de pixel géré unique)…  »
        J’ignorais qu’une ligne suffisait (à moins de recourir à une instruction à rallonge bien sûr).

        Répondre
        • C’est cool, c’est constructif. Un avis externe critique est vraiment une bonne chose. Je me rends compte que j’aurais pu faire mieux, donc je vais essayer de modifier cet article dès que je peux.

          Au début j’étais parti sur une classe non-statique plus avancée (celui que j’utilise dans mon projet en cours avec gestion alpha, détection auto colorKey), bon j’ai eu quelque bug en l’utilisant avec des vecteurs, et pressé par le temps j’ai préféré faire une classe qui peut passer partout.

          Bref… je t’inviterais à y jeter un coup d’œil dès que c’est fait :).

          Oui, un setPixel 32Bit est possible sur une ligne, la notation est carrément obscure ça c’est sûr.

          Bon maintenant l’utilité d’un setPixel est un peu limité, peut-être pour un système de particule, lui aussi ayant une utilité assez limité :).

          Merci Hug0

          Répondre
          • « C’est cool, c’est constructif. Un avis externe critique est vraiment une bonne chose. »
            Vive le Web 2.0 😀

            « Oui, un setPixel 32Bit est possible sur une ligne, la notation est carrément obscure ça c’est sûr. »
            C’est bien ce qui me semblait, ça justifie quand même l’écriture d’une fonction qui l’englobe;
            d’autant qu’on peut souhaiter appeler SDL_LockSurface() et SDL_UnlockSurface() lors de l’accès aux pixels.
            Et pourquoi se priver de la « fonction magique » qui prend en charge tous les formats?

            « Bon maintenant l’utilité d’un setPixel est un peu limité, peut-être pour un système de particule, lui aussi ayant une utilité assez limité :) »
            Elle s’est révélée quand j’ai eu besoin de stocker des données en bitmap (pratique pour les cartes de jeu 2D en tuiles, ou pour délimiter les caractères dans une fonte bitmap).

            Encore une fois pas de quoi, je serais content de voir cette classe Surface :)

Laisser un commentaire

Login to your account

Can't remember your Password ?

Register for this site!