sae fini les gars

This commit is contained in:
AlgaLaptop
2026-01-07 19:27:03 +01:00
parent c8556d469a
commit bf03d0cf8d
137 changed files with 13871 additions and 1525 deletions
@@ -1,55 +1,70 @@
package fr.iutfbleau.sae.mhuffman;
import java.util.*;
public class CanonicalCode{
//private Map<Integer, String> codeLengths = new HashMap<>();
//private Map<Integer, String> canonicalCodes = new HashMap<>();
/**
*
* Classe pour generer des codes canoniques de Huffman.
* Les codes canoniques sont des codes de Huffman reorganises
* pour faciliter la compression et le decodage.
*/
public class CanonicalCode {
/**
* Genere des codes canoniques a partir de codes de Huffman.
*
* @param codesHuffman les codes de Huffman initiaux
* @return les codes canoniques generes
*/
public Map<Integer, String> generateCodes(Map<Integer, String> codesHuffman) {
// On recupere les entrees des codes Huffman pour pouvoir les trier
List<Map.Entry<Integer, String>> liste = new ArrayList<>(codesHuffman.entrySet());
public Map<Integer,String> generateCodes(Map<Integer,String> codesHuffman){
// 1 ere chose à faire : on regarde uniquement la longueur des codes initiaux(Huffman)
// 2eme chose à faire : remettre dans l'ordre des longueurs : si meme taille ==> regarder valeur
// 3eme chose à faire : ecriture des codes canoniques
// on recupere les entrées des codes Huffman pour pouvoir les triés
List<Map.Entry<Integer, String>> liste = new ArrayList<>(codesHuffman.entrySet());
// ici on comparer par longueur de la valeur ou sinon par la clé
// On trie par longueur de la valeur ou sinon par la cle
Collections.sort(liste, new ComparateurCanonique());
Map<Integer,String> canonicalCodes = new HashMap<>();
int code = 0; // code canonique à attribuer
int temp = 0; //garde la longueur du code précedent , pour gérer le décalage
Map<Integer, String> canonicalCodes = new HashMap<>();
int code = 0; // Code canonique a attribuer
int temp = 0; // Garde la longueur du code precedent, pour gerer le decalage
for (Map.Entry<Integer, String> entree : liste ) {
int valeur = entree.getKey(); // symbole actuel
int longueur = entree.getValue().length(); // longueur du code actuel
for (Map.Entry<Integer, String> entree : liste) {
int valeur = entree.getKey(); // Symbole actuel
int longueur = entree.getValue().length(); // Longueur du code actuel
code <<= (longueur - temp); // permet de décaler le code actuel si la longueur augmente
code <<= (longueur - temp); // Permet de decaler le code actuel si la longueur augmente
String codeBinaire = Integer.toBinaryString(code);
// permet d'ajouter des zeros si nécessaire !!!
while (codeBinaire.length() < longueur) {
codeBinaire = "0" + codeBinaire;
}
// Permet d'ajouter des zeros si necessaire
while (codeBinaire.length() < longueur) {
codeBinaire = "0" + codeBinaire;
}
canonicalCodes.put(valeur,codeBinaire); // ajout dans le dictionnaire
code++; // incrémentation pour la valeur qui suit
temp = longueur; // mise à jour de la longueur précedente
canonicalCodes.put(valeur, codeBinaire); // Ajout dans le dictionnaire
code++; // Incrementation pour la valeur qui suit
temp = longueur; // Mise a jour de la longueur precedente
}
return canonicalCodes;
}
public String getCode(Map<Integer,String> canonicalCodes,int value){
return canonicalCodes.get(value);
}
public int getLength(Map<Integer,String> codesH,int value){
return codesH.get(value).length();
}
}
return canonicalCodes;
}
/**
* Recupere le code canonique d'une valeur.
*
* @param canonicalCodes la table des codes canoniques
* @param value la valeur dont on veut le code
* @return le code canonique correspondant
*/
public String getCode(Map<Integer, String> canonicalCodes, int value) {
return canonicalCodes.get(value);
}
/**
* Recupere la longueur d'un code pour une valeur donnee.
*
* @param codesH la table des codes
* @param value la valeur dont on veut la longueur
* @return la longueur du code en bits
*/
public int getLength(Map<Integer, String> codesH, int value) {
return codesH.get(value).length();
}
}
@@ -1,19 +1,40 @@
package fr.iutfbleau.sae.mhuffman;
import java.util.Comparator;
import java.util.Map;
/**
*
* Comparateur utilisé lors de la génération des codes canoniques.
* Il permet de trier des couples (symbole, code Huffman sous forme de chaîne).
*
* Le tri se fait dans cet ordre :
* 1. Par longueur du code (du plus court au plus long)
* 2. En cas d'égalité, par ordre croissant des symboles
*/
public class ComparateurCanonique implements Comparator<Map.Entry<Integer, String>> {
/**
* Compare deux entrées contenant un symbole et son code Huffman.
*
* @param entree1 première entrée à comparer
* @param entree2 deuxième entrée à comparer
* @return un entier négatif si entree1 doit venir avant entree2,
* positif si elle doit venir après,
* et zéro si elles sont équivalentes selon le tri.
*/
@Override
public int compare(Map.Entry<Integer, String> entree1,Map.Entry<Integer, String> entree2) {
public int compare(Map.Entry<Integer, String> entree1, Map.Entry<Integer, String> entree2) {
int longueur1 = entree1.getValue().length();
int longueur2 = entree2.getValue().length();
// On compare d'abord la longueur des codes
if (longueur1 != longueur2) {
return longueur1 - longueur2;
}
// Si les longueurs sont identiques, on trie par symbole
return entree1.getKey() - entree2.getKey();
}
}
}
@@ -0,0 +1,27 @@
package fr.iutfbleau.sae.mhuffman;
import java.util.Comparator;
/**
*
* Comparateur utilisé pour trier les nœuds de l'arbre de Huffman.
* Le tri se fait uniquement en fonction de la fréquence du nœud.
* Les nœuds ayant une plus petite fréquence doivent être placés avant.
*
* Cette classe permet d'éviter les classes anonymes ou les
* expressions avancées, et reste simple à comprendre.
*/
public class ComparateurHuffmanNode implements Comparator<HuffmanNode> {
/**
* Compare deux nœuds en fonction de leur fréquence.
*
* @param a premier nœud à comparer
* @param b second nœud à comparer
* @return un entier négatif si a < b, positif si a > b, 0 si égalité
*/
@Override
public int compare(HuffmanNode a, HuffmanNode b) {
return a.getFrequence() - b.getFrequence();
}
}
@@ -1,52 +1,28 @@
package fr.iutfbleau.sae.mhuffman;
import fr.iutfbleau.sae.mpif.Pixel;
import fr.iutfbleau.sae.mpif.RGBImage;
/**
* Représente une table de fréquences pour une image RGB.
* <p>
* Cette classe est utilisée dans le cadre de la compression de Huffman.
* Elle compte le nombre d'occurrences de chaque valeur possible (0 à 255)
* pour chacune des composantes de couleur : rouge, vert et bleu.
* </p>
*
* <p>
* Chaque composante est stockée dans un tableau de taille 256 :
* <ul>
* <li>{@code freqR} pour le rouge</li>
* <li>{@code freqG} pour le vert</li>
* <li>{@code freqB} pour le bleu</li>
* </ul>
* L'indice du tableau correspond à la valeur de la composante,
* et la valeur stockée correspond à sa fréquence d'apparition.
* </p>
*
* <p>
* Cette table de fréquences sert ensuite à construire les arbres de Huffman
* nécessaires à la compression des données de l'image.
* </p>
*
* @author Algassimou Pellel Diallo
* @version 1.1
* @since 2025-12-13
*
* Classe qui calcule les fréquences des valeurs de couleur dans une image RGB.
* Elle compte combien de fois chaque valeur de rouge, vert et bleu apparaît,
* ce qui servira ensuite pour construire les arbres de Huffman.
*/
public class FrequencyTable {
/** Tableau des fréquences pour la composante rouge (valeurs de 0 à 255). */
/** Fréquences des valeurs de rouge entre 0 et 255. */
private final int[] freqR;
/** Tableau des fréquences pour la composante verte (valeurs de 0 à 255). */
/** Fréquences des valeurs de vert entre 0 et 255. */
private final int[] freqG;
/** Tableau des fréquences pour la composante bleue (valeurs de 0 à 255). */
private final int[] freqB;
/** Fréquences des valeurs de bleu entre 0 et 255. */
private final int[] freqB;
/**
* Construit une table de fréquences vide.
* <p>
* Les trois tableaux de fréquences sont initialisés
* avec 256 cases mises à zéro.
* </p>
* Constructeur qui initialise les trois tableaux de fréquences
* à zéro pour toutes les valeurs possibles.
*/
public FrequencyTable() {
this.freqR = new int[256];
@@ -55,33 +31,23 @@ public class FrequencyTable {
}
/**
* Calcule les fréquences des composantes RGB à partir d'une image.
* <p>
* Pour chaque pixel de l'image, la méthode récupère les valeurs
* rouge, verte et bleue, puis incrémente la case correspondante
* dans le tableau de fréquences associé.
* </p>
* Analyse une image et remplit les tableaux de fréquences.
* Pour chaque pixel, on récupère la valeur de rouge, de vert et de bleu,
* puis on incrémente la fréquence associée.
*
* @param img l'image RGB à analyser
* @throws IllegalArgumentException si l'image est null
* @param img l'image à analyser
* @throws IllegalArgumentException si l'image est nulle
*/
public void computeFromImage(RGBImage img) {
if (img == null) {
throw new IllegalArgumentException("L'image ne peut pas être null");
}
/* Pour chaque composante de couleur de chaque pixel, on incrémente la fréquence correspondante,
* c'est-à-dire on compte le nombre de fois que la composante apparaît dans l'image.
* Ex: si un pixel P a une composante rouge de 150, on incrémente freqR[150] de 1.
* Puis on fait de même pour les composantes verte et bleue.
* On répète ce processus pour tous les pixels de l'image.
*/
for (int x = 0; x < img.getWidth(); x++) {
for (int y = 0; y < img.getHeight(); y++) {
// On récupère le pixel une seule fois pour optimiser
Pixel pixel = img.getPixel(x, y);
// Puis on incrémente les trois fréquences
this.freqR[pixel.getR()]++;
this.freqG[pixel.getG()]++;
this.freqB[pixel.getB()]++;
@@ -90,28 +56,29 @@ public class FrequencyTable {
}
/**
* Retourne le tableau des fréquences de la composante rouge.
* Renvoie les fréquences des valeurs rouges.
*
* @return un tableau de 256 entiers représentant les fréquences du rouge
* @return tableau de fréquence pour le rouge
*/
public int[] getRed() {
return this.freqR;
}
/**
* Retourne le tableau des fréquences de la composante verte.
* @return un tableau de 256 entiers représentant les fréquences du vert
* Renvoie les fréquences des valeurs vertes.
*
* @return tableau de fréquence pour le vert
*/
public int[] getGreen() {
return this.freqG;
}
/**
* Retourne le tableau des fréquences de la composante bleue.
* Renvoie les fréquences des valeurs bleues.
*
* @return un tableau de 256 entiers représentant les fréquences du bleu
* @return tableau de fréquence pour le bleu
*/
public int[] getBlue() {
return this.freqB;
}
}
}
@@ -0,0 +1,123 @@
package fr.iutfbleau.sae.mhuffman;
/**
*
* Représente un nœud de l'arbre de Huffman.
*
* Un nœud peut être :
* - une feuille : il contient une valeur (symbole de 0 à 255) et sa fréquence
* - un nœud interne : il n'a pas de valeur mais possède deux enfants et une fréquence
* correspondant à la somme des fréquences de ces enfants
*
* Cette classe sert uniquement de structure pour construire l'arbre de Huffman,
* utilisé ensuite pour générer les codes dans la compression.
*/
public class HuffmanNode {
/** Valeur du symbole pour une feuille. Vaut -1 pour un nœud interne. */
private int value;
/** Fréquence associée au symbole ou somme des fréquences pour les nœuds internes. */
private int frequence;
/** Fils gauche du nœud. Null si le nœud est une feuille. */
private HuffmanNode left;
/** Fils droit du nœud. Null si le nœud est une feuille. */
private HuffmanNode right;
/**
* Constructeur d'un nœud feuille.
*
* @param value symbole représenté (entre 0 et 255)
* @param frequence fréquence d'apparition du symbole
*/
public HuffmanNode(int value, int frequence) {
if (value < 0 || value > 255) {
throw new IllegalArgumentException("Le symbole doit être entre 0 et 255.");
}
if (frequence < 0) {
throw new IllegalArgumentException("La fréquence ne peut pas être négative.");
}
this.value = value;
this.frequence = frequence;
this.left = null;
this.right = null;
}
/**
* Constructeur d'un nœud interne (créé lors de la fusion de deux sous-arbres).
*
* @param left fils gauche
* @param right fils droit
*/
public HuffmanNode(HuffmanNode left, HuffmanNode right) {
if (left == null || right == null) {
throw new IllegalArgumentException("Les deux enfants doivent être non nuls.");
}
this.value = -1; // Valeur spéciale indiquant que c'est un nœud interne
this.left = left;
this.right = right;
this.frequence = left.frequence + right.frequence;
}
/**
* Indique si le nœud est une feuille.
*
* @return true si le nœud est une feuille, false sinon
*/
public boolean isLeaf() {
return this.left == null && this.right == null;
}
/**
* Renvoie la fréquence associée à ce nœud.
*
* @return fréquence du symbole ou somme des fréquences
*/
public int getFrequence() {
return this.frequence;
}
/**
* Renvoie le fils gauche.
*
* @return fils gauche ou null si feuille
*/
public HuffmanNode getLeft() {
return this.left;
}
/**
* Renvoie le fils droit.
*
* @return fils droit ou null si feuille
*/
public HuffmanNode getRight() {
return this.right;
}
/**
* Renvoie la valeur du symbole représenté.
*
* @return valeur du symbole (0 à 255)
* @throws IllegalStateException si le nœud n'est pas une feuille
*/
public int getValue() {
if (!this.isLeaf()) {
throw new IllegalStateException("Seules les feuilles ont une valeur de symbole.");
}
return this.value;
}
/**
* Représentation textuelle du nœud. Utile en phase de débogage.
*/
@Override
public String toString() {
if (this.isLeaf()) {
return "Feuille(valeur=" + this.value + ", freq=" + this.frequence + ")";
}
return "Noeud(freq=" + this.frequence + ")";
}
}
+88 -155
View File
@@ -1,181 +1,114 @@
package fr.iutfbleau.sae.mhuffman;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import fr.iutfbleau.sae.util.HuffmanNode;
import java.util.PriorityQueue;
/**
* Implémente un arbre de Huffman utilisé pour la compression de données.
* <p>
* La classe {@code HuffmanTree} est chargée de représenter la structure
* de l'arbre de Huffman et de générer les codes binaires associés aux symboles.
* Elle s'appuie sur la classe {@link HuffmanNode} pour représenter les nœuds
* de l'arbre.
* </p>
*
* Cette classe construit un arbre de Huffman à partir d'un tableau
* de fréquences. Une fois l'arbre construit, elle permet aussi de
* générer les codes Huffman associés à chaque symbole.
*
* <p>
* L'arbre est construit à partir des fréquences des symboles calculées
* en amont (par exemple à l'aide d'une {@code FrequencyTable}).
* Chaque symbole est d'abord représenté par une feuille, puis les nœuds
* sont combinés progressivement selon l'algorithme de Huffman afin
* d'obtenir un arbre binaire optimal.
* </p>
* Le principe est le suivant :
* - chaque symbole non nul devient une feuille avec sa fréquence
* - on fusionne toujours les deux plus petites fréquences
* - on obtient une racine unique
* - un parcours de l'arbre permet ensuite de fabriquer les codes
*
* <p>
* Une fois l'arbre construit, celui-ci est parcouru afin de générer une
* table de correspondance associant à chaque symbole un code binaire unique.
* Les symboles les plus fréquents se retrouvent plus proches de la racine
* et possèdent donc des codes plus courts, ce qui permet de réduire
* la taille des données compressées.
* </p>
*
* <p>
* Cette classe ne s'occupe pas de la lecture ou de l'écriture des bits.
* Elle fournit uniquement la structure et les informations nécessaires
* à la compression, qui sont ensuite exploitées par des flux binaires
* dédiés.
* </p>
*
* @author Algassimou Pellel Diallo,Ayoub Anhdire
* @version 1.0
* @since 2025-12-13
* Les codes sont construits en descendant l'arbre :
* aller à gauche ajoute "0", aller à droite ajoute "1".
*/
public class HuffmanTree {
/**
* Racine de l'arbre de Huffman.
* <p>
* Ce nœud est le résultat final de la construction de l'arbre et constitue
* le point de départ pour la génération des codes binaires ainsi que
* pour le décodage des données compressées.
* </p>
*/
private HuffmanNode root;
private Map<Integer, String> codes;
/**
* Dictionnaire pour enregistrer les codes Huffman
*/
// j'ai retirer le static car chaque arbre a ses propres codes et j'utilise string plutot que int pour stocker les codes car on construit une chaine de 0 et de 1
private Map<Integer, String> codes;
/**
* Construit un arbre de Huffman.
* <p>
* Le constructeur est responsable de l'initialisation de la structure
* de l'arbre. En pratique, il combine les nœuds feuilles représentant
* les symboles par ordre croissant de fréquence jusqu'à obtenir un
* unique nœud racine.
* </p>
* Construit l'arbre de Huffman à partir d'un tableau de fréquences.
*
* <p>
* Les détails de la construction (structure de données utilisée,
* ordre des fusions, etc.) sont volontairement séparés de la logique
* de génération des codes.
* </p>
* @param freq tableau contenant la fréquence de chaque symbole (0 à 255)
*/
public HuffmanTree(int[] freq) {
// J'initialise la racine à null.
this.root = null;
// je cree une collection de feuilles
List<HuffmanNode> feuilles = new ArrayList<>();
this.root = null;
// pour chaque valeur(symbole) dans la table de frequence
for (int i = 0; i < freq.length; i++) {
// si la frequence est superieure a 0 , on cree une feuille
if (freq[i] > 0) {
// pour la valeur (symbole) i avec frequence freq[i], on cree une feuille
HuffmanNode feuille = new HuffmanNode(i, freq[i]);
// on ajoute la feuille à la collection
feuilles.add(feuille);
}
}
// Création de la file de priorité avec le comparateur
ComparateurHuffmanNode cmp = new ComparateurHuffmanNode();
PriorityQueue<HuffmanNode> feuilles = new PriorityQueue<>(cmp);
// On tri les feuilles par frequence croissante j'utilise un comparator qui compare la valeur retournee par getFrequence de chaque feuille
// Referencement de methode avec ::
feuilles.sort(Comparator.comparingInt(HuffmanNode::getFrequence));
// flemme de faire un algo de tri alors que java le fait tres bien a voir a la fin si je vais coder une liste chainee avec un tri par insertion personnalise
// Ajout des feuilles
for (int i = 0; i < freq.length; i++) {
if (freq[i] > 0) {
feuilles.add(new HuffmanNode(i, freq[i]));
}
}
// Fusion des nœuds (feuille) jusqu'à obtenir la racine
while (feuilles.size() > 1) {
HuffmanNode left = feuilles.poll();
HuffmanNode right = feuilles.poll();
HuffmanNode parent = new HuffmanNode(left, right);
feuilles.add(parent);
}
// Fusion des nœuds jusqu'à obtenir la racine
// Tant qu'il y a plus d'une feuille dans la collection
while (feuilles.size() > 1) {
// je prends les deux feuilles de plus faible fréquence
HuffmanNode left = feuilles.remove(0);
HuffmanNode right = feuilles.remove(0);
// je crée un nœud interne en les combinant
HuffmanNode parent = new HuffmanNode(left, right);
// j'insère le nœud parent dans la collection à la bonne position pour maintenir l'ordre (plus performant qu'un tri complet à chaque itération)
int index = 0;
// tant que l'index est dans les limites et que la frequence du noeud à l'index est inférieure à celle du parent
while (index < feuilles.size() && feuilles.get(index).getFrequence() < parent.getFrequence()) {
index++;
}
feuilles.add(index, parent);
}
// a la fin il ne reste qu'un seul noeud : la racine de l'arbre
this.root = feuilles.get(0);
}
// Méthode pour générer les codes Huffman à partir de l'arbre
// pourquoi string et pas int ? car on va construire une chaine de 0 et de 1
/**
* @return Map on stockera les codes Huffman sous forme de dictionnaire
*/
public Map<Integer,String> generateCodes() {
/**
* Le but de cette méthode est de pouvoir generer les codes Huffman à partir de l'arbre :
* Les branches prendront comme valeur 1 ou 0 selon differents cas :
* 1 - si on saute vers un fils droit
* 0 - si on saute vers un fils gauche.
* On construit les codes qui partent de la racine jusqu'à notre objectif
*/
this.codes = new HashMap<>();
// je lance la methode recursive avec une chaine vide qui va se remplir au fur et à mesure
generateCodesRec(this.root, "");
return codes;
}
private void generateCodesRec(HuffmanNode node, String prefiixe) {
// Cas de base: si le noeud est une feuille, on ajoute le code au dictionnaire
if (node.isLeaf()) {
if (prefiixe.length() > 0){
this.codes.put(node.getValue(), prefiixe);
}else{
this.codes.put(node.getValue(), "0");
}
return;
}
//Case general : sinon on continue a parcourir l'arbre
// On va a gauche en ajoutant "0" au code
generateCodesRec(node.getLeft(), prefiixe + "0");
// On va a droite en ajoutant "1" au code
generateCodesRec(node.getRight(), prefiixe + "1");
}
/**
* @return Dictionnaire des codes Huffman
*/
public Map<Integer,String> getCodes(){
return codes;
}
/**
* @return le nœud racine de l'arbre de Huffman
*/
public HuffmanNode getRoot() {
return root;
this.root = feuilles.poll();
}
/**
* Lance la génération des codes Huffman en parcourant l'arbre.
*
* @return une map associant chaque symbole à son code Huffman
*/
public Map<Integer, String> generateCodes() {
this.codes = new HashMap<>();
generateCodesRec(this.root, "");
return this.codes;
}
/**
* Méthode récursive qui construit les codes Huffman.
*
* @param node nœud courant
* @param prefixe code accumulé (suite de 0 et de 1)
*/
private void generateCodesRec(HuffmanNode node, String prefixe) {
// Cas feuille : on ajoute le code
if (node.isLeaf()) {
if (prefixe.length() > 0) {
this.codes.put(node.getValue(), prefixe);
} else {
// Cas spécial : un seul symbole dans l'arbre
this.codes.put(node.getValue(), "0");
}
return;
}
// Descendre à gauche s
generateCodesRec(node.getLeft(), prefixe + "0");
// Descendre à droite
generateCodesRec(node.getRight(), prefixe + "1");
}
/**
* Permet de récupérer la racine de l'arbre.
*
* @return la racine de l'arbre de Huffman
*/
public HuffmanNode getRoot() {
return this.root;
}
/**
* Retourne les codes Huffman générés.
*
* @return une map symbole code
*/
public Map<Integer, String> getCodes() {
return this.codes;
}
}