Files
SAE32_2025/rapport/RapportSAE32_2025.tex
T
AlgaLaptop 57657d0b73 FIN FIN
2026-01-11 14:50:05 +01:00

576 lines
32 KiB
TeX

\documentclass[12pt, a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[french]{babel}
\usepackage{graphicx}
\usepackage{float}
\usepackage{array}
\usepackage{geometry}
\usepackage{enumitem}
\usepackage{amsmath} % Pour \text dans les formules
\usepackage{amssymb} % Symboles mathématiques supplémentaires
\usepackage{tikz} % pour les diagreme
\usetikzlibrary{positioning, shapes, arrows.meta} %diagrame
\usepackage{longtable}
\usepackage{listings}
\usepackage[table]{xcolor}
\usepackage[colorlinks=true, linkcolor=black, urlcolor=blue]{hyperref}
\geometry{a4paper, margin=2cm}
% Listes compactes
\setlist[itemize]{noitemsep, topsep=0pt, parsep=0pt, partopsep=0pt}
% Espacement figures et paragraphes
\setlength{\intextsep}{8pt}
\setlength{\floatsep}{8pt}
\setlength{\parskip}{0.5em}
% Configuration listings pour le code
\lstset{
basicstyle=\ttfamily\small,
backgroundcolor=\color{gray!10},
frame=single,
framerule=0.5pt,
breaklines=true,
tabsize=2,
showstringspaces=false
}
\begin{document}
% PAGE DE TITRE
\begin{titlepage}
\centering
\vspace*{0.5cm}
\rule{\textwidth}{1.5pt}\\[0.4cm]
{\Huge\bfseries SAE 3.2 -- Application PIF\\[0.3cm]}
{\Large\itshape Primitive Image Format}\\[0.2cm]
\rule{\textwidth}{1.5pt}\\[1cm]
{\large BUT Informatique -- 2\textsuperscript{ème} année -- Groupe 4}\\[1.5cm]
\begin{tabular}{>{\bfseries}l l}
\multicolumn{2}{c}{\Large\underline{Équipe de développement}}\\[0.4cm]
Youness BOULALAM & \texttt{youness}\\[0.15cm]
Algassimou DIALLO & \texttt{Diallo-VM-fbleau}\\[0.15cm]
Ayoub ANHDIRE & \texttt{anhdire}\\
\end{tabular}
\vfill
\begin{tabular}{rl}
\textbf{Technologie :} & Java -- Architecture MVC\\[0.15cm]
\textbf{Dépôt Gitea :} & \url{https://grond.iut-fbleau.fr/dialloa/SAE32_2025}\\[0.15cm]
\textbf{Encadrant :} & M. Luc Hernandez\\[0.15cm]
\textbf{Date de rendu :} & 11 janvier 2025\\
\end{tabular}
\vspace{0.5cm}
\end{titlepage}
% SOMMAIRE
\renewcommand{\contentsname}{Sommaire}
\tableofcontents
\newpage
% INTRODUCTION
\section{Introduction}
Pour cette deuxième SAE du semestre 3, il nous a fallu réaliser \textbf{deux} programmes : un convertisseur d'image au format PIF et un visualisateur. Le convertisseur prend en entrée une image PNG (ou tout format supporté par \texttt{ImageIO.read()}) via argument ou \textbf{JFileChooser}. L'interface affiche les tables de fréquences, codes Huffman et codes canoniques, avec possibilité d'export en PIF.
Le visualisateur affiche une image PIF dans une fenêtre adaptée : image centrée si plus petite, déplaçable à la souris si plus grande.
Ce projet a été réalisé en trinôme en Java, avec architecture MVC, makefile, et interface graphique native. L'ergonomie a été soignée pour une utilisation simple et claire.
\vspace{0.3cm}
% Figures côte à côte
\begin{figure}[H]
\centering
\begin{minipage}{0.52\textwidth}
\centering
\includegraphics[width=\textwidth]{./images/ConvertisseurFR}
\caption{Interface du convertisseur}
\end{minipage}%
\hfill
\begin{minipage}{0.42\textwidth}
\centering
\includegraphics[width=\textwidth]{./images/Vis}
\caption{Interface du visualisateur}
\end{minipage}
\end{figure}
% RÉPARTITION DES TÂCHES
\section{Répartition des tâches}
\begin{longtable}{|p{3.5cm}|p{12cm}|}
\hline
\textbf{Nom} & \textbf{Tâches effectuées} \\
\hline
\endfirsthead
\hline
\textbf{Nom} & \textbf{Tâches effectuées} \\
\hline
\endhead
\textbf{Algassimou Pellel DIALLO} &
\begin{itemize}
\item Création de la structure générale du projet (Dossiers, UML)
\item Implémentation de BitOutputStream et BitInputStream
\item Implémentation complète du contrôleur (ConverterController)
\item Génération et affichage des fréquences
\item Travail sur HuffmanTree et HuffmanNode
\item Intégration de l'interface graphique (ConverterWindow)
\item Gestion du bouton Export et sauvegarde .pif (thread séparé)
\item J'ai coder le Makefile dans sa totalité
\item Implémentation du PIFWriter et PIFReader
\item Implémentation du contrôleur pour le visualisateur (ViewControleur.java)
\item Implémentation du visualisateur avec toute les specificité demander dans le sujet. (Gestion de la taille, deplacement avec la souris, etc ...).
\end{itemize} \\
\hline
\textbf{Youness BOULALAM} &
\begin{itemize}
\item Gestion des erreurs et messages utilisateur
\item Conversion RGBImage $\leftrightarrow$ BufferedImage (Viewer et Convertisseur)
\item Ouverture du fichier .pif via argument ou JFileChooser
\item Support sur les tâches des coéquipiers
\end{itemize} \\
\hline
\textbf{Ayoub ANHDIRE} &
\begin{itemize}
\item Javadoc (Équipe)
\item Génération des codes Huffman et codes canoniques
\item Interface d'affichage des fréquences
\item Test de BitOutputStream
\item Lecture de l'en-tête
\item Diagramme de classe
\end{itemize} \\
\hline
\end{longtable}
% FONCTIONNALITÉS
\section{Fonctionnalités principales}
\subsection{Conversion au format PIF}
La conversion d'une image vers le format PIF repose sur plusieurs étapes : analyse des composantes RGB, calcul des fréquences, construction de l'arbre de Huffman, création des codes canoniques, puis écriture finale dans un fichier binaire structuré. Cette section présente les mécanismes mis en place et la contribution de chaque membre du groupe.
\subsubsection{Contribution de Algassimou Pellel Diallo}
Dans ce projet, je me suis surtout occupé de toute la partie qui concerne le fonctionnement général du convertisseur PIF, ainsi que de la coordination entre le traitement et l'interface graphique. Mon rôle a été de faire en sorte que le programme suive un déroulement clair, et compréhensible.
\paragraph{Représentation de l'image avec RGBImage}
Lors du chargement d'une image, nous devons la stocker sous une forme qui soit simple à manipuler. Nous avons décidé d'utiliser une classe \textbf{RGBImage} qui représente l'image comme un tableau 2D de pixels.
Pourquoi un tableau 2D ? Parce que cela offre plusieurs avantages :
\begin{itemize}
\item \textbf{Accès direct} : grâce aux coordonnées (x, y), et aussi la mémoire est contiguë O(1) et cache-friendly, on peut accéder rapidement à n'importe quel pixel.
\item \textbf{Exploitation} : parcourir le tableau est simple (deux boucles imbriquées) et efficace.
\item \textbf{Compatibilité} : les valeurs de pixels stockées peuvent directement être converties en \texttt{BufferedImage} pour l'affichage.
\end{itemize}
Chaque pixel stocke trois valeurs entières : rouge, vert et bleu (RGB), chacune entre 0 et 255.
J'ai entendu parler d'une valeur alpha mais on ne l'a pas utilisée dans le projet.
\paragraph{Organisation du contrôleur et déroulement de la conversion}
J'ai mis en place la structure du contrôleur, qui sert de lien entre le traitement et l'affichage. C'est lui qui décide de l'ordre des opérations et de la façon dont les informations sont envoyées à la fenêtre du convertisseur. La conversion s'appuie sur cinq méthodes principales :
\begin{itemize}
\item \texttt{loadImage} : charge l'image choisie et la transforme en structure RGB exploitable ;
\item \texttt{computeFrequencies} : analyse tous les pixels et crée les tableaux de fréquences ;
\item \texttt{computeHuffman} : génère les trois arbres de Huffman (un pour R, un pour G, un pour B) ;
\item \texttt{computeCanonical} : crée les codes canoniques utilisés pour la compression ;
\item \texttt{saveAsPIF} : écrit le fichier final (déclenche l'écrivain) (\texttt{.pif}) avec l'en-tête, les tables et les bits encodés.
\end{itemize}
Le contrôleur gère aussi le comportement selon les arguments donnés par l'utilisateur. S'il fournit deux chemins en ligne de commande, alors la conversion et la sauvegarde se font semi-automatiquement (le user déclenche quand même la sauvegarde via le bouton). Sinon, l'utilisateur passe par des \texttt{JFileChooser}.
\paragraph{BitOutputStream et BitInputStream}
Une autre partie importante de mon travail a été l'utilisation et l'adaptation de deux classes essentielles : \textbf{BitOutputStream} et \textbf{BitInputStream}. Elles servent à manipuler les données bit par bit. Même si dans la théorie ce ne sont pas vraiment des décorateurs, comme me l'a expliqué M. Florant Madeleine (merci à lui), elles fonctionnent quand même comme une couche au-dessus des flux classiques.
Ces classes permettent d'écrire et de lire des bits de manière précise, ce qui est indispensable pour un format comme le PIF. Par exemple, les codes Huffman ne mesurent pas toujours un multiple de 8, donc on doit absolument travailler au niveau du bit. C'est grâce à ces classes que la compression finale est propre et sans gaspillage.
\paragraph{Le casse-tête des threads et le blocage de la fenêtre}
Une difficulté importante que j'ai rencontrée concerne la sauvegarde du fichier. Au début, quand j'appelais \texttt{saveAsPIF} directement depuis le bouton "Exporter", l'interface se figeait complètement. La fenêtre devenait impossible à fermer, impossible à bouger, et parfois même Windows indiquait que le programme "ne répond pas". La seule manière de tout arrêter était d'utiliser le gestionnaire de tâches.
Au début, je ne comprenais pas d'où venait le problème. J'ai donc fait plusieurs tests, et j'ai remarqué que le blocage apparaissait exactement au moment où l'écriture du fichier PIF commençait. C'est en cherchant sur StackOverflow et Reddit que j'ai compris que Swing utilise un seul thread pour gérer toute l'interface graphique. Dès que ce thread est occupé par une opération longue, tout le programme se bloque.
Écrire un fichier PIF peut prendre du temps, surtout pour de grandes images. Le thread graphique ne pouvait donc plus s'occuper de la fenêtre.
Pour résoudre ce problème, j'ai complètement déplacé la sauvegarde dans un thread séparé. J'ai créé une classe dédiée qui hérite de \texttt{Thread}. L'exécution se fait ainsi :
Désolé je cite un peu mon code mais c'est plus simple pour expliquer :
\begin{lstlisting}[language=Java]
ThreadSauvegardePIF thread = new ThreadSauvegardePIF(this, fichierSortie);
thread.start();
\end{lstlisting}
À partir de ce moment-là, la fenêtre n'a plus jamais été bloquée. J'ai rajouté un dialogue de progression, pour éviter que l'utilisateur ne se sente un peu perdu pendant le mini laps de temps que cela prend, et un message de confirmation apparaît lorsque la sauvegarde est terminée. Ce problème m'a beaucoup appris, car je n'avais jamais réalisé que Swing reposait autant sur un thread unique. Cela m'a permis de comprendre pourquoi certaines opérations doivent absolument être effectuées en arrière-plan.
\paragraph{Mise en place de la fenêtre du convertisseur}
Enfin, j'ai organisé la fenêtre principale pour afficher les différentes étapes du traitement : aperçu de l'image, fréquences, codes Huffman et codes canoniques.
\begin{figure}[H]
\centering
\includegraphics[width=0.75\textwidth]{images/ConvertisseurSimple}
\caption{Architecture MVC du convertisseur - Flux de conversion (Bleu: Model, Orange: Controller, Vert: View)}
\end{figure}
\subsubsection{Contribution de Ayoub Anhdire}
Un \textit{arbre binaire} est une structure abstraite composée de nœuds dont la principale contrainte est qu'un nœud doit avoir au maximum deux enfants :
\begin{itemize}
\item un enfant gauche,
\item un enfant droit.
\end{itemize}
\paragraph{Comment l'arbre d'Huffman est construit ?}
Au préalable, pour construire l'arbre binaire de Huffman, il nous faut calculer les fréquences pour les composantes R, G et B. Après avoir calculé ces fréquences, pour construire l'arbre d'Huffman, il nous faut prendre les feuilles avec les plus basses fréquences, c'est-à-dire les plus proches de zéro : en l'occurrence, il faut en prendre deux et à partir de ces deux feuilles, on crée un nouveau nœud qui a comme fréquence la somme des feuilles correspondantes. Lorsqu'il ne reste plus qu'une feuille et qu'on ne peut plus appliquer ce principe : alors cela signifie que c'est la racine de l'arbre. Elle est censée avoir la plus grande fréquence que les feuilles de départ. Autrement dit, plus la fréquence est grande, plus le code Huffman associé sera court.
Essayons d'expliquer ce principe avec un diagramme objet et un petit dessin de l'arbre. Prenons l'exemple le plus simple avec simplement deux feuilles, voici le diagramme objet basé sur notre code et un dessin de l'arbre de ce diagramme objet :
% Figures côte à côte
\begin{figure}[H]
\centering
\begin{minipage}{0.48\textwidth}
\centering
\includegraphics[width=\textwidth]{images/Huffman.png}
\caption{Diagramme Objet - Construction de l'arbre Huffman}
\end{minipage}%
\hfill
\begin{minipage}{0.48\textwidth}
\centering
\includegraphics[width=\textwidth]{images/ARBRE.png}
\caption{Dessin Arbre Huffman - Basé sur le diagramme objet}
\end{minipage}
\end{figure}
Puisqu'il y a trois composantes R, G et B, il est censé avoir 3 arbres Huffman mais pour simplifier la compréhension, nous n'en avons fait qu'un seul : celui de la composante Rouge. Expliquons le diagramme objet : nous avons un objet \textit{freq} de la classe \textbf{FrequencyTable}. Cette classe permet d'initialiser les trois tableaux de fréquences (R, G et B) et ces tableaux sont passés en argument dans le constructeur de classe \textbf{HuffmanTree}.
Après ça on peut remarquer la présence de deux feuilles \textit{Node1} et \textit{Node2}. Elles ont comme fréquence respective 7 et 8. Comme répété plus haut, pour faciliter la compréhension, nous avons choisi que deux feuilles. Voici la base de notre arbre. On remarque la présence d'un nœud \textit{Node3} avec comme fréquence, il a été obtenu en faisant la somme des deux nœuds \textit{Node1} et \textit{Node2} et la fréquence obtenue est 15, donc 7+8. C'est la seule feuille qui reste dans notre arbre : on en conclut que c'est la racine de notre arbre, comme en témoigne l'attribut \underline{root} de la classe \textbf{HuffmanTree}.
Après avoir compris le principe de comment construire l'arbre Huffman, comment générer les codes Huffman ? Nous avons codé cela de manière récursive : si on saute vers un fils gauche on ajoute 0 et si on saute vers un fils droit on ajoute 1 : les codes sont enregistrés dans des dictionnaires : \textbf{Map<Integer,String>}. Pour chaque feuille, pour obtenir son code, on parcourt l'arbre de la racine jusqu'à la feuille.
La question qu'on se pose désormais : c'est est-ce que cette solution est optimale ? La réponse est oui ! Pourquoi ? Comme on a placé les symboles fréquents près de la racine, la moyenne des longueurs de tous les codes est minimale. De plus, les codes sont différents, puisqu'aucun code n'est le début d'un autre code, donc il n'y a pas d'erreur possible à la lecture. Même si certains symboles ont la même fréquence et que l'arbre peut être légèrement différent, la longueur moyenne reste toujours la plus courte possible. C'est pour cela que la génération des codes à partir de l'arbre de Huffman est optimale : aucun autre code ne peut donner une longueur moyenne plus courte pour les mêmes symboles.
\paragraph{Les codes canoniques et leur logique}
Un \textit{code canonique} est une version basée sur les codes Huffman : la longueur de chaque code Huffman est préservée mais les codes sont réorganisés de manière en commençant par les codes les plus courts. On commence par trier les codes initiaux par longueur du code puis par valeur. Les nouveaux codes s'obtiennent ainsi : le premier est rempli de zéro, le deuxième commençant par 1 et rempli de zéro à droite mais bien faire attention à ce que la longueur ne soit pas dépassée, le troisième commençant par 11 et ainsi de suite jusqu'à avoir réalisé cela, jusqu'à ce que toutes les valeurs aient un code canonique.
Pour cela, la démarche que nous avons employée est celle-là : on récupérait les entrées (des dictionnaires en l'occurrence) des codes Huffman afin de les trier, puis on a trié la liste avec un \textbf{Comparator} que nous avons implémenté : on compare d'abord par la longueur des codes (longueur de la valeur dans le dictionnaire) ou sinon on trie par rapport à la valeur de la clé. Puis on fait une boucle qui parcourt toute la liste, on attribue un code canonique à chaque symbole qu'on ajoute dans un dictionnaire : \textbf{Map<Integer,String>}.
\paragraph{Pourquoi les codes canoniques au lieu des codes Huffman ?}
Pour le décodage d'un fichier au format PIF, le fait de stocker l'arbre d'Huffman prendrait énormément de place et de temps mais on peut restituer ces codes Huffman grâce aux codes canoniques. Il nous suffit juste de connaître la longueur des codes et l'ordre des symboles pour pouvoir les reconstituer. On garde la même longueur que les codes initiaux, donc la compression reste optimale.
\paragraph{Le résumé de ces deux principes}
L'algorithme de Huffman sert à coder les symboles avec des codes plus courts pour les symboles fréquents et plus longs pour les rares, ce qui permet de gagner de l'espace. Les codes sont optimaux, puisqu'aucun code n'est le début d'un autre, donc on peut les lire sans erreur. Les codes canoniques sont une version plus simple des codes Huffman : ils gardent la même longueur pour chaque symbole mais suivent d'autres contraintes qui les diffèrent des codes initiaux. Cela permet de stocker moins de données et de décoder plus vite, tout en gardant la même efficacité que Huffman.
\subsection{Visualisateur au format PIF}
Le visualisateur a pour rôle de lire un fichier au format \texttt{.pif} et de reconstruire l'image d'origine. Pour cela, il ne récupère pas directement les codes Huffman, ni l'arbre utilisé lors de la compression. Le fichier \texttt{.pif} ne contient qu'une information minimale : les longueurs des codes canoniques pour chaque symbole (un symbole étant ici une valeur de couleur entre 0 et 255). À partir de ces longueurs, le visualisateur reconstruit entièrement les codes puis les arbres nécessaires au décodage.
Le point d'entrée principal est la méthode \texttt{decodePifFile()} qui orchestre l'ensemble du processus : lecture de l'en-tête, récupération des tables de longueurs, reconstruction des codes canoniques, construction des arbres et décodage des pixels.
\subsubsection{Structure complète du fichier PIF}
Le fichier \texttt{.pif} est organisé de la manière suivante :
\begin{enumerate}
\item \textbf{En-tête} (4 octets) :
\begin{itemize}
\item Largeur de l'image sur 16 bits,
\item Hauteur de l'image sur 16 bits.
\end{itemize}
\item \textbf{Tables de longueurs} (768 octets) :
\begin{itemize}
\item 256 longueurs pour le Rouge,
\item 256 longueurs pour le Vert,
\item 256 longueurs pour le Bleu.
\end{itemize}
\item \textbf{Données compressées} (taille variable) :
\begin{itemize}
\item Flux de bits contenant les codes Huffman canoniques de chaque pixel,
\item Les pixels sont encodés ligne par ligne (R, G, B pour chaque pixel).
\end{itemize}
\end{enumerate}
\subsubsection{La forme des tables de codes dans le visualisateur}
Juste après l'en-tête du fichier (largeur et hauteur, chacune sur 16 bits), le fichier PIF contient trois tables :
\begin{itemize}
\item 256 longueurs pour la composante Rouge,
\item 256 longueurs pour la composante Verte,
\item 256 longueurs pour la composante Bleue.
\end{itemize}
Chaque longueur est stockée sur 8 bits. Cela donne au total :
\[
256 \times 3 = 768 \text{ octets de longueurs}
\]
La méthode \texttt{readHeader()} lit d'abord la largeur et la hauteur de l'image (chacune sur 16 bits), puis \texttt{readCanonicalTables()} parcourt les trois tables de 256 valeurs pour stocker les longueurs dans les tableaux \texttt{lenR}, \texttt{lenG} et \texttt{lenB}.
Ces longueurs correspondent à celles des codes canoniques générés pendant la compression. Aucun code Huffman ni aucun arbre n'est sauvegardé. Le visualisateur doit tout reconstruire à partir de ces seules informations.
Ce choix permet d'obtenir un fichier plus compact qu'un format brut comme le BMP. En revanche, il ne peut pas rivaliser avec des formats déjà hautement compressés tels que PNG ou JPG. D'ailleurs, au début du projet, je pensais que notre fichier \texttt{.pif} serait toujours plus léger que tout autre format, mais je me suis rendu compte en testant et en cherchant sur le Web (Wikipedia, etc.) que PNG et JPG sont beaucoup plus optimisés que ce qu'on fabrique ici. En revanche, par rapport à un fichier BMP, notre fichier PIF est bel et bien plus léger.
La méthode \texttt{isPIFFile()} permet de vérifier qu'un fichier est valide avant de tenter le décodage : elle contrôle l'existence du fichier, son extension \texttt{.pif} et une taille minimale de 772 octets (4 octets d'en-tête + 768 octets de tables).
\subsubsection{Reconstruction des codes canoniques}
À partir des longueurs lues, la méthode \texttt{rebuildCanonical()} reconstruit les codes selon le principe suivant :
\begin{enumerate}
\item On récupère chaque symbole dont la longueur est non nulle.
\item On trie les couples (symbole, longueur) à l'aide d'un \texttt{ComparateurEntreeCanonique} :
\begin{itemize}
\item d'abord par longueur croissante,
\item puis par valeur du symbole.
\end{itemize}
\item On génère les codes dans cet ordre :
\begin{itemize}
\item le premier code d'une longueur donnée est rempli de zéros,
\item les suivants sont obtenus en incrémentant un compteur binaire,
\item lorsque la longueur augmente, on décale le compteur avec l'opération \texttt{code <<= (length - previousLength)} pour l'aligner correctement.
\end{itemize}
\end{enumerate}
Le résultat est stocké dans une \texttt{Map<String, Integer>} où la clé est le code binaire sous forme de chaîne et la valeur est le symbole correspondant.
\subsubsection{Choix de l'arbre plutôt que du dictionnaire}
Pour décoder les données compressées, j'aurais pu utiliser un simple dictionnaire où chaque code binaire serait associé à son symbole. Cette approche aurait été plus simple à implémenter : il suffirait d'accumuler les bits lus et de vérifier à chaque étape si la chaîne obtenue existe dans le dictionnaire.
Cependant, j'ai finalement choisi d'utiliser un \textbf{arbre de décodage}, même si cela était plus difficile à coder. Ce choix s'explique par plusieurs raisons :
\begin{itemize}
\item \textbf{Performant} : Avec un dictionnaire, il faudrait accumuler les bits lus et tester à chaque étape si la chaîne obtenue correspond à un code existant. Cette approche nécessite de nombreuses recherches dans le dictionnaire et n'est pas efficace.
\item \textbf{Parcours DFS} : L'arbre permet un parcours où chaque bit lu (0 ou 1) détermine directement si l'on descend à gauche ou à droite. Dès qu'on atteint une feuille, on a trouvé le symbole sans aucune recherche supplémentaire.
\item \textbf{Approche standard} : En regardant plusieurs vidéos explicatives sur YouTube concernant le décodage Huffman, j'ai constaté que toutes utilisaient un arbre de décodage plutôt qu'un dictionnaire. Cela m'a conforté dans l'idée que cette approche est la méthode de référence pour ce type de problème.
\end{itemize}
\subsubsection{Construction de l'arbre de décodage}
Une fois les codes reconstruits sous forme de chaînes de bits, la méthode \texttt{buildDecodageTree()} crée un arbre binaire composé de nœuds \texttt{DecodeNode}. Le parcours suit la règle suivante :
\begin{itemize}
\item \texttt{0} signifie descendre à gauche (\texttt{current.left}),
\item \texttt{1} signifie descendre à droite (\texttt{current.right}).
\end{itemize}
Lorsqu'on arrive au dernier bit d'un code, la méthode crée une feuille contenant le symbole associé via \texttt{new DecodeNode(null, null, symbol)}. Ce symbole est la valeur d'une composante (entre 0 et 255). Ce procédé est répété pour les trois composantes : Rouge, Vert et Bleu. On obtient ainsi trois arbres distincts (\texttt{trieR}, \texttt{trieG}, \texttt{trieB}).
% Constructuion de larbre de decodage pour une compsatae
\begin{figure}[H]
\centering
\resizebox{0.95\textwidth}{!}{%
\begin{tikzpicture}[
every node/.style={font=\small},
object/.style={rectangle, draw, rounded corners, minimum width=1.8cm, minimum height=0.7cm, align=center},
leaf/.style={rectangle, draw, rounded corners, minimum width=1.4cm, minimum height=0.6cm, align=center, fill=green!20},
arrow/.style={->, thick},
level 1/.style={sibling distance=5cm, level distance=1.5cm},
level 2/.style={sibling distance=2.5cm, level distance=1.5cm}
]
\node[object] (root) {\textbf{root}\\null}
child {
node[object] (node1) {\textbf{node1}\\null}
child {
node[leaf] (leaf0) {\textbf{leaf0}\\128}
edge from parent node[left] {0}
}
child {
node[leaf] (leaf1) {\textbf{leaf1}\\255}
edge from parent node[right] {1}
}
edge from parent node[left] {0}
}
child {
node[object] (node2) {\textbf{node2}\\null}
child {
node[leaf] (leaf2) {\textbf{leaf2}\\0}
edge from parent node[left] {0}
}
child {
node[leaf] (leaf3) {\textbf{leaf3}\\64}
edge from parent node[right] {1}
}
edge from parent node[right] {1}
};
\end{tikzpicture}%
}
\vspace{0.3cm}
{\small
\begin{tabular}{|c|c|}
\hline
\textbf{Code} & \textbf{Valeur} \\
\hline
00 & 128 \\
01 & 255 \\
10 & 0 \\
11 & 64 \\
\hline
\end{tabular}
}
\caption{Diagramme Objet -- Arbre de décodage Huffman}
\end{figure}
\subsubsection{Décodage des pixels}
Une fois les arbres construits, la méthode \texttt{decodePixels()} lit le reste du fichier bit par bit grâce au flux \texttt{BitInputStream}. Pour chaque pixel :
\begin{enumerate}
\item On appelle \texttt{decodeSymbole(in, trieR)} pour parcourir l'arbre Rouge jusqu'à tomber sur une feuille et obtenir la valeur du rouge.
\item On appelle \texttt{decodeSymbole(in, trieG)} pour l'arbre Vert.
\item On appelle \texttt{decodeSymbole(in, trieB)} pour l'arbre Bleu.
\end{enumerate}
À la fin, les trois valeurs retrouvées permettent de créer un objet \texttt{Pixel} qui est placé dans l'image via \texttt{image.setPixel(x, y, pixel)}. L'ensemble des pixels donne l'image complète sous forme de \texttt{RGBImage}.
La méthode \texttt{decodeSymbole()} effectue ce travail pour une seule composante : elle parcourt l'arbre avec \texttt{in.readBit()} jusqu'à ce que \texttt{current.isLeaf()} soit vrai, puis retourne \texttt{current.value}. Grâce à la propriété des codes canoniques (aucun code n'est préfixe d'un autre), le décodage est fiable et ne provoque aucune ambiguïté.
\begin{figure}[H]
\centering
\includegraphics[width=0.8\textwidth]{images/VisualisateurSimple.png}
\caption{Diagramme Objet -- Arbre de décodage Huffman}
\end{figure}
% MAKEFILE
\section{Makefile}
\subsection{Gestion des dépendances}
Le projet utilise un \textbf{Makefile} pour automatiser la compilation. Les classes interdépendantes sont compilées ensemble :
\begin{lstlisting}
$(BIN)/$(PKG_CONV)/ConverterController.class \
$(BIN)/$(PKG_CONV)/ConverterWindow.class \
$(BIN)/$(PKG_CONV)/ExportButtonListener.class \
$(BIN)/$(PKG_CONV)/ThreadSauvegardePIF.class: \
$(SRC)/$(PKG_CONV)/ConverterController.java \
$(SRC)/$(PKG_CONV)/ConverterWindow.java \
$(SRC)/$(PKG_CONV)/ExportButtonListener.java \
$(SRC)/$(PKG_CONV)/ThreadSauvegardePIF.java
$(JAVAC) -d $(BIN) -sourcepath $(SRC) $^
\end{lstlisting}
\subsection{Génération des JARs}
Le Makefile génère deux JARs exécutables :
\begin{itemize}
\item \texttt{pifConverter.jar} : le convertisseur d'images
\item \texttt{pifViewer.jar} : le visualisateur d'images PIF
\end{itemize}
\subsection{Commandes disponibles}
\rowcolors{2}{gray!10}{white}
\begin{center}
\begin{tabular}{|l|p{10cm}|}
\hline
\rowcolor{gray!30}
\textbf{Commande} & \textbf{Description} \\
\hline
\texttt{make} & Compile tout le projet (équivalent à \texttt{make all}) \\
\hline
\texttt{make runnotjar-conv ARGS="..."} & Lance le convertisseur sans créer de JAR \\
\hline
\texttt{make runnotjar-view ARGS="..."} & Lance le visualisateur sans JAR \\
\hline
\texttt{make jar-conv} & Génère \texttt{pifConverter.jar} \\
\hline
\texttt{make jar-view} & Génère \texttt{pifViewer.jar} \\
\hline
\texttt{make jar} & Génère les deux JARs \\
\hline
\texttt{make run-conv ARGS="..."} & Exécute le convertisseur via JAR \\
\hline
\texttt{make run-view ARGS="..."} & Exécute le visualisateur via JAR \\
\hline
\texttt{make doc} & Génère la Javadoc dans \texttt{docjava/} \\
\hline
\texttt{make clean} & Supprime \texttt{build/}, JARs et documentation \\
\hline
\end{tabular}
\end{center}
\subsection{Exemple d'exécution}
\begin{lstlisting}
# Conversion d'une image
make run-conv ARGS="image.png output.pif"
# Visualisation d'un fichier PIF
make run-view ARGS="output.pif"
# Nettoyage
make clean
\end{lstlisting}
% CONCLUSION
\section{Conclusion}
\subsection{Youness BOULALAM}
Dans ce projet, j'ai pu, contrairement au précédent, échanger avec mes collaborateurs afin de rendre la meilleure version possible du projet et d'avoir une vue d'ensemble de celui-ci.
Sans vous mentir, le Java n'est pas vraiment ma tasse de thé, mais lorsqu'on est assisté, on peut plus facilement comprendre et moins rester bloqué sur des concepts qui peuvent nous démotiver, voire nous dégoûter du projet.
Pour conclure, je souhaite remercier mes camarades Algassimou et Ayoub, ainsi que vous, M. Hernandez, de nous avoir permis de réaliser ce projet et de le mener à bien.
\subsection{Algassimou DIALLO}
Pour conclure, ce projet m'a beaucoup apporté, même s'il m'a posé plusieurs casse-têtes, notamment avec le gel de la fenêtre lors de la sauvegarde et la gestion des flux binaires. J'ai dû comprendre d'où venaient ces problèmes et chercher des solutions propres, comme le déplacement de la sauvegarde dans un \textit{thread} dédié.
J'ai aussi découvert la complexité d'un vrai projet Java : les arbres, les codes canoniques, le décodage bit par bit, mais aussi la structure générale du programme et les dépendances lors de la compilation. Malgré les difficultés, j'ai apprécié le travail, car chaque blocage m'a permis de progresser et de mieux comprendre ce que je faisais. Au final, ce projet a été une bonne expérience et m'a réellement aidé à monter en compétence.
\subsection{Ayoub ANHDIRE}
Pour conclure, j'ai bien aimé ce projet en général, j'ai pris du plaisir à coder en Java d'autant plus que j'affectionne la programmation orientée objet. Ce projet m'a permis d'augmenter mes compétences techniques, plus précisément dans la compréhension de structures abstraites notamment les dictionnaires ou encore les arbres. J'ai pu travailler d'autres notions comme la récursivité où j'avais du mal à comprendre la logique mais grâce à ce projet, j'ai pu m'améliorer.
J'ai pu aussi développer mes qualités de communication avec mes camarades : chacun a joué un rôle où il sait qu'il va perfectionner et la communication a été un enjeu majeur dans cette SAE car lorsque quelqu'un était bloqué, il faisait signe et ne restait pas tout seul sans avancer. En conclusion, ce projet a été pour moi une expérience enrichissante.
\end{document}