<< Retour à la page principale du cours
Avant de commencer les TD Outils, vous devez télécharger le code disponible pour l'UE. Si vous connaissez déjà git ou sinon simplement en copiant/collant la ligne suivante dans un terminal :
git clone https://forge.univ-lyon1.fr/Alexandre.Meyer/L2_ConceptionDevApp.git
Sinon :
L'objectif de cette 1ère partie du TD est d'écrire un module (fichiers .h et .cpp) de gestion d'images.
L'important est la manière de coder et les outils utilisés plus que la réalisation technique. Votre mini-programme comportera les fichiers Pixel.h/Pixel.cpp
(le module Pixel), Image.h/Image.cpp
(le module Image), les programmes principaux et un Makefile
.
Ce TD outil est à réaliser sous Linux. ATTENTION, bien lire et respecter les consignes pour le rendu du module Image.
Un bon éditeur de code n'est pas un éditeur de texte. En effet, l'éditeur de code doit comporter les fonctionnalités suivantes :
Nous montrons principalement des copies d'écran de CodeBlocks mais donnons des liens vers d'autres outils qui sont tous aussi valables. L'objectif est que vous trouviez l'éditeur qui vous convient et surtout que vous soyez efficace avec. Choisissez parmi ces 6.
Les autres sont plus anecdotique pour du C++
Remarques : Apprenez et utilisez les raccourcis clavier de votre éditeur de code. Il peut y avoir des questions là-dessus lors du contrôle Vous devez rapidement ne plus toucher à votre souris , le gain de temps est considérable.
CodeBlocks est plus qu'un éditeur de code. C'est un “environnement de développement intégré” (IDE en anglais). CodeBlocks va gérer votre programme comme un projet. Vous pourrez par exemple déboguer directement dans l'éditeur.
Pour créer, compiler et exécuter un projet sous CodeBlocks:
printf
).Voir également ce tutoriel si besoin.
Prenez le temps de bien choisir votre éditeur.
Une image est en quelque sorte un tableau à 2 dimensions de largeur dimx et de hauteur dimy, dont les éléments sont des pixels. En interne, nous allons allouer un tableau 1D de taille dimx*dimy. En externe (ie. le code utilisant le module Image) nous interpréterons l'image avec deux dimensions (x,y) en passant par les fonctions getPix (un accesseur) et setPix (un mutateur). Les définitions de classes vous sont données ci-dessous en langage algorithmique.
1. Créez les fichiers Pixel.h
, Pixel.cpp
, Image.h
et Image.cpp
, et traduisez et implémentez les classes en C++. N'oubliez pas de compiler AU FUR ET A MESURE. N'attendez pas d'avoir écrit toutes les fonctions membres pour compiler. Ne modifiez pas les noms des classes, membres, paramètres, etc.
Classe Pixel privé : r,g,b : entier(0..255) // les composantes du pixel, unsigned char en C++ public : // Constructeur par défaut de la classe: initialise le pixel à la couleur noire Constructeur Pixel () // Constructeur de la classe: initialise r,g,b avec les paramètres Constructeur Pixel (nr, ng, nb : donnée entier(0..255) ) // Accesseur : récupère la composante rouge du pixel Fonction getRouge () -> entier(0..255) // Accesseur : récupère la composante verte du pixel Fonction getVert () -> entier(0..255) // Accesseur : récupère la composante bleue du pixel Fonction getBleu () -> entier(0..255) // Mutateur : modifie la composante rouge du pixel Procédure setRouge (nr : donnée entier(0..255)) // Mutateur : modifie la composante verte du pixel Procédure setVert (ng : donnée entier(0..255)) // Mutateur : modifie la composante bleue du pixel Procédure setBleu (nb : donnée entier(0..255)) FinClasse
Classe Image privé : tab : tableau de Pixel // le tableau 1D de pixel dimx, dimy : entier // les dimensions de l'image public : // Constructeur par défaut de la classe: initialise dimx et dimy à 0 // ce constructeur n'alloue pas de pixel Constructeur Image () // Constructeur de la classe: initialise dimx et dimy (après vérification) // puis alloue le tableau de pixel dans le tas (image noire) Constructeur Image (dimensionX, dimensionY : donnée entier); // Destructeur de la classe: déallocation de la mémoire du tableau de pixels // et mise à jour des champs dimx et dimy à 0 Destructeur Image () // Accesseur : récupère le pixel original de coordonnées (x,y) en vérifiant leur validité // la formule pour passer d'un tab 2D à un tab 1D est tab[y*dimx+x] Fonction getPix (x,y : donnée entier) -> 'Pixel' (l'original, pas une copie) // Mutateur : modifie le pixel de coordonnées (x,y) Procédure setPix (x,y : donnée entier; couleur : donnée Pixel) // Dessine un rectangle plein de la couleur dans l'image (en utilisant setPix, indices en paramètre compris) Procédure dessinerRectangle (Xmin,Ymin,Xmax,Ymax : donnée entier; couleur : donnée Pixel) // Efface l'image en la remplissant de la couleur en paramètre // (en appelant dessinerRectangle avec le bon rectangle) Procédure effacer (couleur : donnée Pixel) // Effectue une série de tests vérifiant que le module fonctionne et // que les données membres de l'objet sont conformes Procédure testRegression () FinClasse
dessinerRectangle allume les pixels de l'image qui sont compris dans le rectangle défini par le coin en haut à gauche (Xmin,Ymin) jusqu'au point en bas à droite (Xmax,Ymax), comme ceci :
2. Ecrivez la procédure testRegression
qui teste votre module Image dans tous les cas possibles d'utilisation. Soyez le plus exhaustif possible.
3. Créez un programme principal nommé mainTest.cpp
qui contient le code suivant (simple appel à la procédure de test de regréssion).
mainTest.cpp
(produisant l'exécutable bin/test
):
#include "Image.h" int main() { Image monImage; monImage.testRegression(); return 0; }
Voici quelques règles de bonne programmation à respecter :
assert
const
partout où vous pouvez (paramètres et fonctions membres)Il est important de comprendre la différence entre la pile et le tas. De comprendre que l'allocation se fait sur un tableau 1D car c'est la seule solution qu'offre new, mais que de l'extérieur à la classe Image les codeurs veulent travailler avec des informations 2D. Les accesseurs font donc la conversion pour gérer les données internes.
Trois fonctions d'entrée/sortie sauver
(sauver une image dans un fichier), ouvrir
(ouvrir une image depuis un fichier) et afficherConsole
(afficher les valeurs des pixels sur la console) sont données dans le fichier TD_moduleImage/IOimage.cpp
de l'archive fournie.
Vous devez tout d'abord les incorporer dans votre code (ie. les ajouter au module Image
). Des bugs ont été placés intentionnelement dans ces trois fonctions !!Vous devez les trouver et les corriger en utilisant GDB. Pour ceci vous pouvez tester votre programme avec une petite image (ex. de taille 3×2) et dérouler le code pas à pas.
Une fois ces trois fonctions corrigées, dans votre module Image, créez un deuxième exécutable nommé mainExemple.cpp
qui contient le code suivant. Attention, vous devez lancer l'exécutable à partir du dossier racine avec la commande bin/exemple
. Ceci est valable pour les trois exécutables du module Image.
mainExemple.cpp
(produisant l'exécutable bin/exemple
):
#include "Image.h" int main() { Pixel rouge (120, 15, 10); Pixel vert (20, 202, 15); Pixel bleu (4, 58, 218); Image image1 (64,48); image1.effacer(bleu); image1.dessinerRectangle(5, 20, 30, 40, rouge); image1.setPix(51,4,vert); image1.setPix(20,30,vert); image1.sauver("./data/image1.ppm"); Image image2; image2.ouvrir("./data/image1.ppm"); image2.dessinerRectangle(29, 10, 48, 15, rouge); image2.dessinerRectangle(25, 24, 40, 45, vert); image2.sauver("./data/image2.ppm"); return 0; }
UML est un langage graphique d'analyse et de conception de logiciel ou de système complexe. UML est standardisé et largement utilisé. UML vise plutôt une conception orientée objet que vous avez pu commencer à appréhender en LIFAP3 et LIFAP4. Voir le cours de LIFAP4, ainsi qu'une explication à propos des diagrammes de classes sur Wikipédia.
Vous aurez à plusieurs moments de ce cours à réaliser des diagrammes des classes de vos programmes. Pour l'édition de ce diagramme des classes UML, nous vous proposons plusieurs outils dans la section "Doc, outils et tuto". Vous avez par exemple Umbrello ou Dia installé sur les ordinateurs de l'Université. Par exemple avec Dia :
Class
et ajoutez tous leurs membresin
pour donnée et inout
pour donnée-résultatRemarque : sur les machines du Nautibus si Umbrello ou Dia ne sont pas installé, utilisez alors un outil en ligne comme ceux proposez dans la section UML de "Doc, outils et tuto".
A Faire. Réalisez le diagramme des classes UML du programme comportant les classes Image
et Pixel
: ce travail n'aura pas besoin d'être rendu mais il constitue un bon entrainement pour la réalisation de votre future diagramme des classes UML de projet.
Pour générer la documentation de votre code au format HTML :
# créez le fichier doxygen en tapant: doxygen -g doc/image.doxy # personnalisez le fichier doxygen (au minimum INPUT et OUTPUT_DIRECTORY) en tapant : gedit doc/image.doxy # generez la documentation en tapant: doxygen doc/image.doxy # ouvrez la page principale générée avec Firefox (ou autre browser) en tapant: firefox doc/html/index.html
Remarques :
image.doxy
si Dot est installé)
A faire : réaliser la documentation doxygen du code de Pixel et de Image.
TD_valgrind
de l'archive fourniemain.cpp
pour comprendre ce qui est calculéQuestion : En utilisant valgrind qui va nous donner des informations sur l'exécution du programme, vous devez résoudre les problèmes de lenteur et de mémoire : fuite de mémoire (memory leak), accès invalide dû à des débordements, etc.
Par défaut, valgrind permet de vérifier si les allocations mémoires se passent bien. En particulier, de savoir si vous ne débordez pas d'un tableau (ex: int t[10]; t[12]=14;
), de savoir si tous les new
correspondent à des delete
dans le programme, etc.
$> valgrind --tool=memcheck --leak-check=full ./prog ==20689== Invalid write of size 4 ==20689== at 0x804859D: main (main.cpp:23) ==20689== Address 0x4286FA0 is 0 bytes after a block of size 24 alloc'd ==20689== at 0x4022A55: operator new[](unsigned int) ==20689== by 0x8048541: main (main.cpp:18)
Ceci indique qu'il y a des problèmes d'écriture mémoire non autorisée dans main.cpp
à la ligne 23, ce qui est sûrement dû à un problème de taille d'allocation ou d'indice de tableau. Pour cela valgrind vous indique la ligne d'allocation mémoire de la variable mise en cause (ici le tableau f1) à la ligne 18. Faites la correction nécessaire pour résoudre ce problème, ainsi que celles nécessaires pour résoudre d'autres problèmes d'allocation et d'accès.
De plus, dans la rubrique “LEAK SUMMARY” à la fin vous trouverez que 24 octets sont perdus car ce programme ne fait pas le bon nombre de delete
. Faites le bon nombre de libérations mémoire et vérifiez que votre programme est propre en relançant valgrind. Faites la même chose pour votre module Image afin de vérifier si vous n'avez pas de fuite mémoire.
Pour optimiser votre code vous devez savoir où votre programme passe le plus de temps :
$> valgrind --tool=callgrind ./prog $> callgrind_annotate callgrind.out.20092
La 1ère ligne a produit un fichier de statistique : callgrind.out.20092 (nom automatiquement généré, change à chaque exécution). La 2ème ligne permet de visualiser les statistiques du fichier :
-------------------------------------------------------------------------------- Ir file:function -------------------------------------------------------------------------------- 2,028 Calcul.cpp:intAdd(int, int) [/home/.../prog] 570 Calcul.cpp:intMul(int, int) [/home/.../prog]
Ce programme passe 2028 fois dans intAdd et 570 fois dans intMul. C'est donc la fonction intAdd qu'il faut optimiser en priorité. Regardez et changez le code de intAdd pour avoir un programme plus efficace. Relancer valgrind pour voir le gain de performance obtenue. Faire éventuellement d'autres modifications du code pour gagner encore plus. Pour vous rendre compte du gain apporté, vous pouvez refaire l'expérience avec une valeur MAX
plus grande.
valgrind -h
pour avoir une liste.
Vous trouverez le code d'un embryon du jeu Pacman dans le répertoire du même nom dans l'archive fournie. Le projet Pacman peut être compilé sous Linux ou sous Windows à l'aide du Makefile ou du projet CodeBlocks qui utilise le Makefile. Si vous compilez sous Windows sur votre portable les bibliothèques Windows sont incluses dans le répertoire extern
. Sous Linux à l'université toutes les bibliothèques nécessaires sont installées par défaut sur les machines. Sous Linux sur votre portable vous devez installer SDL2
, SDL2_image
, SDL2_ttf
et SDL2_mixer
comme ceci:
$> sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev libsdl2-mixer-dev
L'objectif du TD est de vous familiariser avec SDL2, et de l'intégrer à votre module Image. Prenez donc du temps pour explorer le code, et plus particulièrement les modules Jeu, sdlJeu et txtJeu.
Dans votre module Image, créez un troisième programme principal nommé mainAffichage.cpp
qui contient impérativement le code suivant, sans le modifier pour le script de notation.
mainAffichage.cpp
(produisant l'exécutable bin/affichage
):
#include "Image.h" int main (int argc, char** argv) { Image image (11,11); Pixel gris (226, 226, 226); Pixel vert (1, 130, 0); Pixel noir (0, 0, 0); Pixel beigeC (210, 188, 154); Pixel beigeF (168, 105, 8); Pixel marron (102, 65, 8); image.effacer(gris); image.dessinerRectangle(3,0,7,10,vert); image.dessinerRectangle(0,1,10,1,vert); image.setPix(2,2,vert); image.setPix(8,2,vert); image.setPix(3,2,gris); image.setPix(7,2,gris); image.setPix(4,2,noir); image.setPix(6,2,noir); image.dessinerRectangle(1,5,9,6,vert); image.setPix(5,10,gris); image.dessinerRectangle(9,7,9,10,marron); image.dessinerRectangle(2,5,8,6,beigeC); image.dessinerRectangle(3,7,7,9,beigeC); image.dessinerRectangle(4,5,6,6,beigeF); image.setPix(5,7,beigeF); image.afficher(); return 0; }
La procédure afficher()
du module Image est à ajouter dans votre classe Image
et est la suivante:
// Affiche l'image dans une fenêtre SDL2 Procédure afficher ()
Vous devez vous inspirez du code du Pacman pour implémenter cette procédure (module sdlJeu
). Vous aurez probablement à créer des fonctions membres intermédiaires qui seront privées à la classe (ex. afficherInit
, afficherBoucle
et afficherDetruit
). L'image sera placée au centre d'une fenêtre SDL2 de taille 200×200 pixels de fond gris clair et vous devrez permettre de zoomer/dézoomer sur votre image grâce aux touches T
et G
et quitter la procédure afficher()
grâce à la touche ESCAPE
.
Si vous lancez valgrind sur un exécutable utilisant une bibliothèque comportant des fuites mémoire, vous obtiendrez ces fuites même si votre code libère bien toute la mémoire allouée que vous allouez. Elles apparaissent pour la plupart en still reachable, ce qui correspond à des zones de mémoire sur lesquelles on dispose encore de pointeurs et que l'on aurait pu désallouer si on l'avait voulu. Ces fuites (qui n'en sont pas vraiment…) relèvent du fonctionnement interne des bibliothèques et sont indépendantes de votre volonté. Elles ne risquent pas de faire planter votre programme mais gênent la recherche de vrais bugs.
Heureusement, pour ne voir que vos fuites mémoire, il est possible d'indiquer à valgrind d'ignorer ces erreurs, en utilisant le fichier de suppression valgrind_lif7.supp fourni dans l'archive, et en spécifiant les options adéquates lors de l'exécution de valgrind :
$> valgrind --leak-check=full --num-callers=50 --suppressions=./valgrind_lif7.supp --show-reachable=yes -v NOM_DE_L'EXECUTABLE
Les fuites mémoire propres à SDL2 sont toujours dénombrées mais apparaissent désormais en suppressed. Documentez vous sur valgrind pour comprendre le sens des options.
Créer un projet
L'université a mise en place une forge (lien) que vous utiliserez pour gérer les versions de votre TP projet. Pour créer un nouveau dépôt forge pour votre projet, un des membres du groupe doit suivre la procédure ci-dessous (il est nécessaire d'avoir un compte actif à l'université).
Ensuite vous devez ajouter les autres membres du groupe en tant que contributeur:
Pour le TP projet (mais pas pour le module Image), n'oubliez que vous aurez à ajouter votre encadrant de TP (qui sera indiqué sur Tomuss) en tant que “Reporter” avant la fin du semestre.
Pour créer votre version locale:
https://forge.univ-lyon1.fr/NUMERO_ETU/NOM_PROJET.git
où NUMERO_ETU est le numéro étudiant de celui qui a créé le projet (namespace en terme git) et NOM_PROJET est le nom du projet indiqué à la phase de créationhttps://forge.univ-lyon1.fr/NUMERO_ETU/NOM_PROJET
git clone https://forge.univ-lyon1.fr/NUMERO_ETU/NOM_PROJET.git
Tous les étudiants membres du projet doivent éxecuter cette commande sur leur propre compte pour pouvoir travailler sur le projet (après qu'ils aient été ajoutés en tant que membres). Cette opération n'est à faire qu'une seule fois pour chaque étudiant.
Travailler avec git
Une fois le répertoire de travail mis en place, vous pouvez effectuer des modifications (ajout de fichiers, suppression, modification etc.).
Sous Windows vous pouvez remplacer les commandes du terminal par les outils suivants : TortoiseGit et Git for Windows.