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
View File
@@ -7,7 +7,6 @@ import javax.swing.*;
/**
* Panneau d'affichage des codes Huffman et canoniques.
* Affiche les codes pour chaque composante de couleur (rouge, vert, bleu).
* @author Algassimou
*/
public class CodeTablePanel extends JPanel {
+129 -77
View File
@@ -3,7 +3,7 @@ import fr.iutfbleau.sae.mhuffman.*;
import fr.iutfbleau.sae.mpif.PIFWriter;
import fr.iutfbleau.sae.mpif.Pixel;
import fr.iutfbleau.sae.mpif.RGBImage;
import fr.iutfbleau.sae.util.GestionErreur;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
@@ -12,77 +12,105 @@ import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
/**
* Contrôleur pour la conversion d'images.
* <p>
* Cette classe gère le chargement des fichiers image et les opérations
* de conversion associées. tel que
* </p>
*
* Controleur pour la conversion d'images au format PIF.
* Gere le chargement des images, le calcul des frequences,
* la generation des codes de Huffman et la sauvegarde au format PIF.
*/
public class ConverterController {
// Image convertie en RGBImage
/** Image convertie en RGBImage */
private RGBImage image;
// Table de fréquences pour chaque composante
/** Table de frequences pour chaque composante */
private FrequencyTable frequencyTable;
// Arbres de Huffman pour chaque composante
/** Codes de Huffman pour la composante rouge */
private Map<Integer, String> abrHuffmanR;
/** Codes de Huffman pour la composante verte */
private Map<Integer, String> abrHuffmanG;
/** Codes de Huffman pour la composante bleue */
private Map<Integer, String> abrHuffmanB;
// Codes canoniques pour chaque composante
/** Codes canoniques pour la composante rouge */
private Map<Integer, String> canonRED;
/** Codes canoniques pour la composante verte */
private Map<Integer, String> canonGREEN;
/** Codes canoniques pour la composante bleue */
private Map<Integer, String> canonBLUE;
// La fenêtre du convertisseur
/** La fenetre du convertisseur */
private ConverterWindow fen;
/** Chemin du fichier de sortie */
String outputPath;
/** Chemin du fichier d'entree */
String inputPath;
/**
* Construit un nouveau controleur de conversion.
*
* @param fen la fenetre de l'interface graphique
* @param in le chemin du fichier d'entree (peut etre null)
* @param out le chemin du fichier de sortie (peut etre null)
*/
public ConverterController(ConverterWindow fen, String in, String out) {
this.fen = fen;
this.outputPath = out;
this.inputPath = in;
}
// charger une image depuis un fichier avec bufferedImage et la convertir en RGBImage
/**
* Charge une image depuis un fichier et la convertit en RGBImage.
* Extrait les composantes RGB de chaque pixel et met a jour l'interface.
*
* @param file le fichier image a charger
*/
public void loadImage(File file) {
try{
try {
// Lire l'image avec BufferedImage
BufferedImage buffimage = ImageIO.read(file);
BufferedImage buffimage = ImageIO.read(file);
if (buffimage == null) {
throw new IllegalArgumentException("Le fichier spécifié n'est pas une image valide.");
throw new IllegalArgumentException("Le fichier specifie n'est pas une image valide.");
}
int w = buffimage.getWidth();
int h = buffimage.getHeight();
// Créer une RGBImage de la même taille
// Creer une RGBImage de la meme taille
this.image = new RGBImage(w, h);
// remplir la RGBImage avec les pixels de BufferedImage
// Remplir la RGBImage avec les pixels de BufferedImage
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int rgb = buffimage.getRGB(x, y); // obtenir la valeur RGB du pixel
int rgb = buffimage.getRGB(x, y);
// Extraire les composantes R, G, B
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
// Créer un pixel et l'ajouter à la RGBImage
// Creer un pixel et l'ajouter a la RGBImage
this.image.setPixel(x, y, new Pixel(r, g, b));
}
}
// Mettre à jour le GUI
// Mettre a jour le GUI
this.fen.setImagePreview(buffimage);
} catch (IOException e) {
GestionErreur.afficherErreur("Erreur lors du chargement : " + e.getMessage());
}
}
/**
* Calcule les frequences d'apparition de chaque valeur RGB dans l'image.
* Met a jour l'interface avec les tables de frequences calculees.
*/
public void computeFrequencies() {
this.frequencyTable = new FrequencyTable();
if (this.image == null) {
@@ -90,42 +118,59 @@ public class ConverterController {
}
this.frequencyTable.computeFromImage(this.image);
// Mettre à jour le GUI avec les fréquences
// Mettre a jour le GUI avec les frequences
int[] freqR = this.frequencyTable.getRed();
int[] freqG = this.frequencyTable.getGreen();
int[] freqB = this.frequencyTable.getBlue();
this.fen.setFrequencyTable(freqR, freqG, freqB);
}
/**
* Genere les arbres de Huffman pour chaque composante RGB.
* Cree les codes binaires optimaux bases sur les frequences calculees.
* Met a jour l'interface avec les codes de Huffman generes.
*/
public void computeHuffman() {
// Génération des arbres de Huffman pour chaque composante et stockage des codes
// Generation des arbres de Huffman pour chaque composante
HuffmanTree arbreR = new HuffmanTree(this.frequencyTable.getRed());
this.abrHuffmanR = arbreR.generateCodes();
HuffmanTree arbreG = new HuffmanTree(this.frequencyTable.getGreen());
this.abrHuffmanG = arbreG.generateCodes();
HuffmanTree arbreB = new HuffmanTree(this.frequencyTable.getBlue());
this.abrHuffmanB = arbreB.generateCodes();
// Mettre à jour le GUI avec les codes de Huffman
// Mettre a jour le GUI avec les codes de Huffman
this.fen.setHuffmanTable(this.abrHuffmanR, this.abrHuffmanG, this.abrHuffmanB);
}
public void computeCanonical(){
/**
* Genere les codes canoniques a partir des codes de Huffman.
* Les codes canoniques sont une forme normalisee des codes de Huffman
* qui facilite le stockage et le decodage.
* Met a jour l'interface avec les codes canoniques generes.
*/
public void computeCanonical() {
CanonicalCode codeCanoniques = new CanonicalCode();
this.canonRED = codeCanoniques.generateCodes(this.abrHuffmanR);
this.canonGREEN = codeCanoniques.generateCodes(this.abrHuffmanG);
this.canonBLUE = codeCanoniques.generateCodes(this.abrHuffmanB);
// Mettre à jour le GUI avec les codes canoniques
// Mettre a jour le GUI avec les codes canoniques
this.fen.setCanonicalTable(this.canonRED, this.canonGREEN, this.canonBLUE);
}
/**
* Sauvegarde l'image au format PIF.
* Verifie que l'image et les codes canoniques sont disponibles avant
* d'effectuer la sauvegarde.
*
* @param pathfile le chemin du fichier de sortie
*/
public void saveAsPIF(String pathfile) {
// je vérifie que l'image et les codes canoniques sont disponibles
if(this.image == null || this.canonRED == null){
// Verifier que l'image et les codes canoniques sont disponibles
if (this.image == null || this.canonRED == null) {
GestionErreur.afficherErreur("Impossible de sauvegarder : image ou codes canoniques manquants.");
return;
}
@@ -134,42 +179,47 @@ public class ConverterController {
PIFWriter ecriveur = new PIFWriter();
ecriveur.writeTOFile(pathfile, this.image, this.canonRED, this.canonGREEN, this.canonBLUE);
} catch (Exception e) {
GestionErreur.afficherErreur("Erreur lors de l’écriture du fichier .pif : ");
GestionErreur.afficherErreur("Erreur lors de l'ecriture du fichier .pif : ");
}
}
/**
* Gere la sauvegarde via le bouton de l'interface.
* Si un chemin de sortie est defini, sauvegarde directement.
* Sinon, ouvre un selecteur de fichier pour que l'utilisateur choisisse
* l'emplacement de sauvegarde.
* La sauvegarde est effectuee dans un thread separe pour ne pas bloquer
* l'interface graphique.
*/
public void saveViaBtn() {
try {
if (outputPath != null) {
saveAsPIF(outputPath);
GestionErreur.afficherInfo("Fichier sauvegardé avec succès. Chemin : " + outputPath);
return;
}
if (outputPath != null) {
saveAsPIF(outputPath);
GestionErreur.afficherInfo("Fichier sauvegarde avec succes. Chemin : " + outputPath);
return;
}
// Sinon JFileChooser
JFileChooser chooser = new JFileChooser();
chooser.setDialogTitle("Enregistrer le fichier .pif");
// Sinon JFileChooser
JFileChooser chooser = new JFileChooser();
chooser.setDialogTitle("Enregistrer le fichier .pif");
if (chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
//saveAsPIF(chooser.getSelectedFile().getAbsolutePath());
// On lance la sauvegarde lourde dans un thread séparé
new Thread(() -> {
try {
saveAsPIF(chooser.getSelectedFile().getAbsolutePath());
GestionErreur.afficherInfo("Fichier sauvegardé avec succès : " + chooser.getSelectedFile().getName());
} catch (Exception e) {
GestionErreur.afficherErreur("Erreur lors de la sauvegarde : " + e.getMessage());
}
}).start();
}
} catch (Exception e) {
GestionErreur.afficherErreur("Erreur lors de la sauvegarde : ");
if (chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
File fichierSelectionne = chooser.getSelectedFile();
ThreadSauvegardePIF thread = new ThreadSauvegardePIF(this, fichierSelectionne);
thread.start();
}
}
public void convessionProcess(){
// chragement
/**
* Lance le processus complet de conversion d'une image au format PIF.
* Etapes :
* 1. Chargement de l'image (depuis un fichier ou via selecteur)
* 2. Calcul des frequences
* 3. Generation des codes de Huffman
* 4. Generation des codes canoniques
* 5. Sauvegarde (automatique ou via bouton selon les parametres)
*/
public void conversionProcess() {
// Chargement
if (this.inputPath != null) {
// Chargement direct depuis les arguments
File file = new File(inputPath);
@@ -179,38 +229,40 @@ public class ConverterController {
return;
}
this.loadImage(file);
}else{
} else {
// Sinon JFileChooser pour choisir l'image
JFileChooser choosser =new JFileChooser();
choosser.setDialogTitle("Choisissez une image");
if (choosser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
this.loadImage(choosser.getSelectedFile());
}else {
GestionErreur.afficherErreur("Aucune image choisie. Arrêt du programme.");
JFileChooser chooser = new JFileChooser();
chooser.setDialogTitle("Choisissez une image");
if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
this.loadImage(chooser.getSelectedFile());
} else {
GestionErreur.afficherErreur("Aucune image choisie. Arret du programme.");
System.exit(1);
return;
}
}
// je place la logique de conversion dans lordre
// Placer la logique de conversion dans l'ordre
computeFrequencies();
computeHuffman();
computeCanonical();
// Sauvegarder: un second argument est donné sauvegarde automatique
// Sauvegarder : si un second argument est donne, sauvegarde automatique
if (this.outputPath != null) {
this.saveAsPIF(this.outputPath);
GestionErreur.afficherInfo("Fichier sauvegardé automatiquement : " + this.outputPath);
}else{
// pas de deuxième argument j'ajoute un boutton pour choisir avec un jfilechoser
GestionErreur.afficherInfo("Fichier sauvegarde automatiquement : " + this.outputPath);
} else {
// Pas de deuxieme argument : ajouter un bouton pour choisir avec un JFileChooser
fen.addSaveButton(this);
}
}
public RGBImage getImage(){
/**
* Retourne l'image actuellement chargee.
*
* @return l'image RGB chargee, ou null si aucune image n'est chargee
*/
public RGBImage getImage() {
return this.image;
}
}
}
+65 -103
View File
@@ -1,4 +1,5 @@
package fr.iutfbleau.sae;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Map;
@@ -6,17 +7,9 @@ import javax.swing.*;
/**
* Fenêtre principale du convertisseur.
*
* <p>
* Cette classe correspond à la vue principale de lapplication.
* Elle centralise laffichage des informations liées à la conversion
* dune image (aperçu, fréquences, codes).
* </p>
*
*
* <p>
* Elle sert de point dentrée unique pour la partie graphique
* </p>
* Cette classe affiche l'image chargée, les tables de fréquences,
* les codes Huffman et les codes canoniques.
* C'est la partie "Vue" de l'application.
*/
public class ConverterWindow extends JFrame {
@@ -24,136 +17,105 @@ public class ConverterWindow extends JFrame {
private FrequencyTablePanel frequencyTablePanel;
private CodeTablePanel codeTablePanel;
/**
* Crée la fenêtre principale du convertisseur.
*
* <p>
* Le constructeur initialise la fenêtre et met en place
* les différents panneaux graphiques utilisés pour laffichage.
* </p>
* Constructeur de la fenêtre du convertisseur.
* Initialise la fenêtre et installe tous les panneaux graphiques.
*/
public ConverterWindow() {
// Configuration de la fenetre
this.setTitle("Convertisseur PIF - Visualisation des données");
this.setSize(900, 600);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null); // Centre la fenêtre
this.setResizable(true); // on autorise le
this.setLayout(new BorderLayout());
setTitle("Convertisseur PIF - Visualisation des données");
setSize(900, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(true);
setLayout(new BorderLayout());
// Initialisation des panels
this.imagePreviewPanel = new ImagePreviewPanel();
this.frequencyTablePanel = new FrequencyTablePanel();
this.codeTablePanel = new CodeTablePanel();
// Création des panneaux d'affichage
imagePreviewPanel = new ImagePreviewPanel();
frequencyTablePanel = new FrequencyTablePanel();
codeTablePanel = new CodeTablePanel();
// Je gere le panel principal
JPanel contentPanel = new JPanel();
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
//contentPanel.setBackground(new Color(255, 0, 0)); // rouge vif pour demo
// Panneau principal avec un scroll
JPanel contentPanel = new JPanel(new BorderLayout());
contentPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
// Titre
JLabel header = new JLabel(" Convertisseur PIF Visualisation des données ");
header.setFont(new Font("SansSerif", Font.BOLD, 18));
header.setAlignmentX(Component.CENTER_ALIGNMENT);
contentPanel.add(header);
header.setHorizontalAlignment(SwingConstants.CENTER);
contentPanel.add(Box.createRigidArea(new Dimension(0, 5))); // espace
contentPanel.add(header, BorderLayout.NORTH);
contentPanel.add(imagePreviewPanel, BorderLayout.CENTER);
// Les tables en bas
JPanel tablesPanel = new JPanel();
tablesPanel.setLayout(new BoxLayout(tablesPanel, BoxLayout.Y_AXIS));
tablesPanel.add(frequencyTablePanel);
tablesPanel.add(Box.createRigidArea(new Dimension(0, 5)));
tablesPanel.add(codeTablePanel);
contentPanel.add(tablesPanel, BorderLayout.SOUTH);
// Ajout du panel d'aperçu
contentPanel.add(imagePreviewPanel);
contentPanel.add(Box.createRigidArea(new Dimension(0, 5)));
// Ajout du panel des fréquences
contentPanel.add(frequencyTablePanel);
contentPanel.add(Box.createRigidArea(new Dimension(0, 5)));
// Ajout panel des codes
contentPanel.add(codeTablePanel);
contentPanel.add(Box.createRigidArea(new Dimension(0, 5)));
// la section du scrollpane
JScrollPane scrollPane = new JScrollPane(contentPanel);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.getVerticalScrollBar().setUnitIncrement(16); // scroll plus adouci fluide
scrollPane.getVerticalScrollBar().setUnitIncrement(16);
this.add(scrollPane, BorderLayout.CENTER);
add(scrollPane, BorderLayout.CENTER);
// Fenêtre visible immédiatement
setVisible(true);
}
/**
* Met à jour limage affichée dans la zone daperçu.
*
* <p>
* Cette méthode est appelée lorsque limage à convertir
* a été chargée. La fenêtre ne modifie pas limage :
* elle la transmet simplement au panneau daperçu.
* </p>
*
* @param img image à afficher
* Affiche l'image chargée dans le panneau d'aperçu.
* @param img l'image à afficher
*/
public void setImagePreview(BufferedImage img) {
imagePreviewPanel.setImage(img);
}
/**
* Met à jour laffichage des tables de fréquences.
* Met à jour l'affichage des fréquences des trois composantes.
*/
public void setFrequencyTable(int[] freqR,int[] freqG,int[] freqB) {
frequencyTablePanel.updateFrequencies(freqR,freqG,freqB);
public void setFrequencyTable(int[] freqR, int[] freqG, int[] freqB) {
frequencyTablePanel.updateFrequencies(freqR, freqG, freqB);
}
/**
* Met à jour laffichage des codes Huffman.
*
* <p>
* Elle permet uniquement dafficher les codes
* qui ont été produits par la partie traitement.
* </p>
* Met à jour l'affichage des codes Huffman.
*/
public void setHuffmanTable(Map<Integer, String> codesRouge,
Map<Integer, String> codesVert,
Map<Integer, String> codesBleu) {
codeTablePanel.updateCodes(codesRouge, codesVert, codesBleu);
public void setHuffmanTable(Map<Integer, String> r,
Map<Integer, String> g,
Map<Integer, String> b) {
codeTablePanel.updateCodes(r, g, b);
}
/**
* Met à jour laffichage des codes canoniques.
*
* <p>
* Les codes canoniques sont transmis au panneau
* chargé de leur affichage.
* </p>
* Met à jour l'affichage des codes canoniques.
*/
public void setCanonicalTable(Map<Integer, String> codesRouge,
Map<Integer, String> codesVert,
Map<Integer, String> codesBleu) {
codeTablePanel.updateCanonicalCodes(codesRouge, codesVert, codesBleu);
this.setVisible(true); // je limage visible apre avoir tout ajouter
public void setCanonicalTable(Map<Integer, String> r,
Map<Integer, String> g,
Map<Integer, String> b) {
codeTablePanel.updateCanonicalCodes(r, g, b);
}
/**
* Ajoute un bouton permettant d'exporter l'image en .pif.
* Le contrôleur est envoyé au listener responsable de la sauvegarde.
*/
public void addSaveButton(ConverterController controller) {
JButton saveBtn = new JButton("Exporter en .pif");
ExportButtonListener ecouteur =new ExportButtonListener(controller);
saveBtn.addActionListener(ecouteur);
// panneau du bas
JPanel bottomPanel = new JPanel();
bottomPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
bottomPanel.add(saveBtn);
// ajoute au bas de la fenêtre
this.add(bottomPanel, BorderLayout.SOUTH);
// rafraîchir l'affichage
this.revalidate();
this.repaint();
}
JButton saveBtn = new JButton("Exporter en .pif");
ExportButtonListener listener = new ExportButtonListener(controller);
saveBtn.addActionListener(listener);
JPanel bottomPanel = new JPanel();
bottomPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
bottomPanel.add(saveBtn);
add(bottomPanel, BorderLayout.SOUTH);
revalidate();
repaint();
}
}
+28 -9
View File
@@ -1,24 +1,43 @@
package fr.iutfbleau.sae;
/**
* Point d'entree principal du programme de conversion d'images au format PIF.
* Lance l'interface graphique et initialise le processus de conversion.
*/
public class Convertisseur {
/**
* Methode principale qui demarre l'application de conversion.
*
* Arguments acceptes :
* - args[0] : chemin du fichier image d'entree (optionnel)
* - args[1] : chemin du fichier PIF de sortie (optionnel)
*
* Si aucun argument n'est fourni, l'utilisateur pourra choisir
* les fichiers via l'interface graphique.
*
* @param args les arguments de la ligne de commande
*/
public static void main(String[] args) {
// chemins de l'image
String inpuPath = null;
String outputPath = null;
// Chemins de l'image
String inputPath = null;
String outputPath = null;
if (args.length >= 1) {
inpuPath = args[0];
inputPath = args[0];
}
if (args.length >= 2) {
outputPath = args[1];
}
// Créer et stocker la référence à la fenêtre
// Creer et stocker la reference a la fenetre
ConverterWindow window = new ConverterWindow();
// je la passe au controleur
ConverterController controller = new ConverterController(window, inpuPath, outputPath);
// Passer la fenetre au controleur
ConverterController controller = new ConverterController(window, inputPath, outputPath);
// je demare le programe de conversion
controller.convessionProcess();
// Demarrer le programme de conversion
controller.conversionProcess(); // ✅ Correction : conversionProcess (pas convessionProcess)
}
}
@@ -1,17 +1,16 @@
package fr.iutfbleau.sae;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.SwingUtilities;
public class ExportButtonListener implements ActionListener {
private PIFSaveTask saveTask;
private ConverterController controller;
public ExportButtonListener(ConverterController controller){
this.saveTask = new PIFSaveTask(controller);
this.controller = controller;
}
@Override
public void actionPerformed(ActionEvent e){
SwingUtilities.invokeLater(saveTask);
this.controller.saveViaBtn();
}
}
+58 -37
View File
@@ -3,68 +3,89 @@ package fr.iutfbleau.sae;
import java.awt.*;
import javax.swing.*;
/**
*
* Panneau qui affiche les fréquences des composantes rouge, verte et bleue.
* Chaque composante est affichée dans une zone de texte non modifiable.
*/
public class FrequencyTablePanel extends JPanel {
// 3 Zone de texte pour la fréquence du rouge , du vert et du bleu
private JTextArea freqRouge , freqVert , freqBleu;
/** Zone d'affichage pour les fréquences du rouge. */
private JTextArea freqRouge;
/** Zone d'affichage pour les fréquences du vert. */
private JTextArea freqVert;
/** Zone d'affichage pour les fréquences du bleu. */
private JTextArea freqBleu;
/**
* Constructeur du panneau.
* Initialise l'affichage avec trois zones de texte (R, G, B).
*/
public FrequencyTablePanel() {
setLayout(new BoxLayout(this , BoxLayout.Y_AXIS));
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
// Premiere étiquette pour les fréquences en géneral
JLabel etiquette1 = new JLabel("Frequence");
JLabel etiquette1 = new JLabel("Fréquences");
etiquette1.setFont(new Font("SansSerif", Font.BOLD, 16));
super.add(etiquette1);
super.add(Box.createVerticalStrut(10));
// Puis création de zone de texte pour le rouge , le vert et le bleu
this.freqRouge = creationZoneText("Rouge");
this.freqVert = creationZoneText("Vert");
this.freqBleu = creationZoneText("Bleu");
add(etiquette1);
add(Box.createVerticalStrut(10));
freqRouge = creationZoneText("Rouge");
freqVert = creationZoneText("Vert");
freqBleu = creationZoneText("Bleu");
}
private JTextArea creationZoneText(String t) {
super.add(new JLabel(t + ":"));
GridLayout gestionnaire_mise_en_page = new GridLayout(5,5,10,10);
/**
* Crée une zone de texte avec un label et un scroll.
* Cette méthode est utilisée trois fois : rouge, vert et bleu.
*/
private JTextArea creationZoneText(String titre) {
add(new JLabel(titre + " :"));
JTextArea zone = new JTextArea(8, 30);
zone.setLayout(gestionnaire_mise_en_page);
zone.setEditable(false);
zone.setFont(new Font("Monospaced", Font.PLAIN, 12));
JScrollPane scroll = new JScrollPane(zone);
scroll.setPreferredSize(new Dimension(300, 120));
add(scroll);
add(Box.createVerticalStrut(10));
return zone;
}
public void updateFrequencies(int[] freqR,int[] freqG,int[] freqB) {
mettreAJour(freqRouge,freqR);
mettreAJour(freqVert,freqG);
mettreAJour(freqBleu,freqB);
/**
* Met à jour les trois zones d'affichage avec les nouvelles valeurs.
*
* @param freqR tableau des fréquences du rouge
* @param freqG tableau des fréquences du vert
* @param freqB tableau des fréquences du bleu
*/
public void updateFrequencies(int[] freqR, int[] freqG, int[] freqB) {
mettreAJour(freqRouge, freqR);
mettreAJour(freqVert, freqG);
mettreAJour(freqBleu, freqB);
}
/**
* Remplit une zone de texte avec le contenu d'un tableau de fréquences.
*/
private void mettreAJour(JTextArea zone, int[] frequence) {
public void mettreAJour(JTextArea zone,int[] frequence){
StringBuilder string = new StringBuilder();
StringBuilder sb = new StringBuilder();
for(int i = 0 ; i < frequence.length ; i++){
string.append(String.format("%3d : %s%n", i, frequence[i]));
for (int i = 0; i < frequence.length; i++) {
sb.append(String.format("%3d : %d%n", i, frequence[i]));
if(i%10 == 0 && i!=0){
string.append("\n");
if ((i + 1) % 10 == 0) { // retour à la ligne toutes les 10 valeurs
sb.append("\n");
}
}
}
zone.setText(string.toString());
zone.setText(sb.toString());
}
}
@@ -1,4 +1,4 @@
package fr.iutfbleau.sae.util;
package fr.iutfbleau.sae;
import javax.swing.*;
public class GestionErreur {
+57 -24
View File
@@ -1,76 +1,109 @@
package fr.iutfbleau.sae;
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
/**
* Le panneau daperçu de limage.
/**
* Panneau d'aperçu de l'image.
*
* <p>
* Ce panneau affiche un aperçu de limage en cours de conversion.
* </p>
* Ce panneau est utilisé pour afficher l'image chargée dans le convertisseur.
* Si l'image est trop grande, elle est automatiquement réduite.
* Si elle est plus petite que l'espace disponible, elle est centrée.
*/
public class ImagePreviewPanel extends JPanel {
/** Image affichée dans le panneau */
private BufferedImage image;
// je donne une taille préférée au panel
/** Taille maximale pour l'affichage */
private static final int MAX_WIDTH = 800;
private static final int MAX_HEIGHT = 600;
/**
* Constructeur du panneau d'aperçu.
*/
public ImagePreviewPanel() {
this.setPreferredSize(new Dimension(600, 800));
this.setMinimumSize(new Dimension(600, 800));
setPreferredSize(new Dimension(MAX_WIDTH, MAX_HEIGHT));
}
/**
* Met à jour l'image à afficher et demande le rafraîchissement du panneau.
*
* @param img l'image à afficher
*/
public void setImage(BufferedImage img) {
this.image = img;
repaint();
// Recalculer la taille préférée selon l'image
if (image != null) {
int imgWidth = image.getWidth();
int imgHeight = image.getHeight();
// Calculer la taille d'affichage
double scale = Math.min(
(double) MAX_WIDTH / imgWidth,
(double) MAX_HEIGHT / imgHeight
);
if (scale > 1.0) {
scale = 1.0;
}
int prefWidth = Math.max((int) (imgWidth * scale), MAX_WIDTH);
int prefHeight = Math.max((int) (imgHeight * scale), MAX_HEIGHT);
setPreferredSize(new Dimension(prefWidth, prefHeight));
}
revalidate();
repaint();
}
/**
* Affiche l'image dans le panneau.
* L'image est centrée et éventuellement réduite si elle dépasse la taille du panneau.
*/
@Override
protected void paintComponent(Graphics pinceau) {
// Appel de la méthode parente pour effacer l'arrière-plan
super.paintComponent(pinceau);
if (image == null) {
return;
}
// Recuperer les dimensions du panel pour centrer l'image
int panelWidth = this.getWidth();
int panelHeight = this.getHeight();
int panelWidth = getWidth();
int panelHeight = getHeight();
// Recuperer les dimensions de l'image
int imgWidth = image.getWidth();
int imgHeight = image.getHeight();
// Je calcule le facteur du reduction (si l'image est trop grande) en gros le dezoom
double scale = Math.min(
(double) panelWidth / imgWidth,
(double) panelHeight / imgHeight
);
// Si l'image est plus petite que le panel, on ne la redimensionne pas donc scale = 1
if (scale > 1.0) {
scale = 1.0;
}
// je recalcule les dimensions de l'image à dessiner
int drawWidth = (int) (imgWidth * scale);
int drawHeight = (int) (imgHeight * scale);
// Centrage de l'image dans le panel
int x = (panelWidth - drawWidth) / 2;
int y = (panelHeight - drawHeight) / 2;
// Amélioration de la qualité du redimensionnement
Graphics2D pinceau2D = (Graphics2D) pinceau;
pinceau2D.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR
);
pinceau2D.setRenderingHint(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY // Je privilege la qualite au lieu de la vitesse
);
pinceau2D.drawImage(image, x, y, drawWidth, drawHeight, this);
}
}
}
-13
View File
@@ -1,13 +0,0 @@
package fr.iutfbleau.sae;
public class PIFSaveTask implements Runnable{
private ConverterController controller;
public PIFSaveTask(ConverterController c) {
this.controller = c;
}
@Override
public void run() {
controller.saveViaBtn();
}
}
@@ -0,0 +1,37 @@
package fr.iutfbleau.sae;
import java.io.File;
/**
* Thread pour sauvegarder un fichier PIF en arriere-plan.
* Permet d'eviter de bloquer l'interface graphique pendant la sauvegarde.
*/
public class ThreadSauvegardePIF extends Thread {
private final ConverterController controleur;
private final File fichier;
/**
* Construit un nouveau thread de sauvegarde.
*
* @param controleur le controleur qui gere la sauvegarde
* @param fichier le fichier dans lequel sauvegarder
*/
public ThreadSauvegardePIF(ConverterController controleur, File fichier) {
this.controleur = controleur;
this.fichier = fichier;
}
/**
* Execute la sauvegarde du fichier PIF.
*/
@Override
public void run() {
try {
controleur.saveAsPIF(fichier.getAbsolutePath());
GestionErreur.afficherInfo("Fichier sauvegarde avec succes : " + fichier.getName());
} catch (Exception e) {
GestionErreur.afficherErreur("Erreur lors de la sauvegarde : " + e.getMessage());
}
}
}
+12 -3
View File
@@ -1,7 +1,18 @@
package fr.iutfbleau.sae;
/**
* Classe principale qui lance l'application de visualisation d'images PIF.
* Elle recupere le chemin de l'image en argument et demarre l'interface.
*/
public class Viewer {
/**
* Point d'entree de l'application.
* Recupere le chemin du fichier image passe en argument si present,
* cree la fenetre et le controleur, puis charge l'image PIF.
*
* @param args tableau d'arguments, le premier element est le chemin de l'image (optionnel)
*/
public static void main(String[] args) {
// chemins de l'image
String inpuPath = null;
@@ -12,9 +23,7 @@ public class Viewer {
ViewerWindow fen = new ViewerWindow();
ViewerControleur controleur = new ViewerControleur(fen,inpuPath);
controleur.loadPIF();
}
}
+42 -29
View File
@@ -3,17 +3,27 @@ package fr.iutfbleau.sae;
import fr.iutfbleau.sae.mpif.PIFReader;
import fr.iutfbleau.sae.mpif.Pixel;
import fr.iutfbleau.sae.mpif.RGBImage;
import fr.iutfbleau.sae.util.GestionErreur;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.swing.JFileChooser;
/**
* Controleur du visualiseur d'images PIF.
* Gere le chargement du fichier PIF et la conversion en image affichable.
*/
public class ViewerControleur {
private RGBImage image; // L'image à décoder
private ViewerWindow window; // La fenêtre du visualiseur
private RGBImage image; // L'image a decoder
private ViewerWindow window; // La fenetre du visualiseur
private File file;
/**
* Constructeur du controleur.
*
* @param window la fenetre du visualiseur
* @param path le chemin du fichier PIF ou null si aucun
*/
public ViewerControleur(ViewerWindow window, String path) {
this.window = window;
if (path != null) {
@@ -22,18 +32,23 @@ public class ViewerControleur {
this.file = null;
}
}
/**
* Charge et affiche un fichier PIF.
* Si aucun fichier n'est fourni, ouvre un selecteur de fichier.
* Verifie que le fichier est au format PIF puis le decode et l'affiche.
*/
public void loadPIF() {
File fichierPIF;
// Déterminer si on le charge avec jFilechoose ou pas
// Determiner si on le charge avec jFilechoose ou pas
if (this.file != null) {
// Fichier fourni en argument
fichierPIF = this.file;
} else {
// Demander à l'utilisateur via JFileChooser
// Demander a l'utilisateur via JFileChooser
JFileChooser chooser = new JFileChooser();
chooser.setDialogTitle("Choisissez un fichier PIF");
if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
fichierPIF = chooser.getSelectedFile();
} else {
@@ -42,57 +57,55 @@ public class ViewerControleur {
return;
}
}
// je Vérifi la conformiter du fichier avec isPIFFile
// je Verifie la conformite du fichier avec isPIFFile
if (!PIFReader.isPIFFile(fichierPIF)) {
GestionErreur.afficherErreur("Le fichier fourni n'est pas au format PIF (.pif)");
return;
}
// je Charge et décoder le fichier PIF
// je Charge et decode le fichier PIF
try {
PIFReader lecteur = new PIFReader();
this.image = lecteur.decodePifFile(fichierPIF);
System.out.println("Image décodée : " + this.image.getWidth() + "x" + this.image.getHeight());
// je convertit RGBImage en BufferedImage
BufferedImage buffImg = convertToBufferedImage(this.image);
// j'affiche l'image
this.window.displayImage(buffImg);
} catch (Exception e) {
GestionErreur.afficherErreur("Erreur lors du chargement du fichier PIF : ");
}
}
/**
* Convertit une RGBImage en BufferedImage.
*
* @param rgbImage l'image à convertir
* @return l'image convertie
* Parcourt tous les pixels et compose la couleur RGB sur 32 bits.
*
* @param rgbImage l'image a convertir
* @return l'image convertie en BufferedImage
*/
private BufferedImage convertToBufferedImage(RGBImage rgbImage) {
int width = rgbImage.getWidth();
int height = rgbImage.getHeight();
BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// Copier tous les pixels
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Pixel pixel = rgbImage.getPixel(x, y);
// Composer la couleur RGB la couleur est coder sur 32 bit argb chacun a 8 bit
int rgb = (pixel.getR() << 16) | (pixel.getG() << 8) | pixel.getB();
// Définir le pixel dans le BufferedImage
// Definir le pixel dans le BufferedImage
buffImg.setRGB(x, y, rgb);
}
}
return buffImg;
}
}
}
+130
View File
@@ -0,0 +1,130 @@
package fr.iutfbleau.sae;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
/**
* Panneau personnalise pour afficher l'image.
* Utilise le double buffering pour un affichage fluide.
* Gere le deplacement de l'image avec la souris.
*/
public class ViewerImagePanel extends JPanel implements MouseListener, MouseMotionListener {
// l'image que j'affiche dans le panneau
private BufferedImage image;
// je stocke le decalage horizontal de l'image pour le deplacement
private int offsetX = 0;
// je stocke le decalage vertical de l'image pour le deplacement
private int offsetY = 0;
// je garde la derniere position X de la souris pour calculer le deplacement
private int lastX;
// je garde la derniere position Y de la souris pour calculer le deplacement
private int lastY;
/**
* Constructeur du panneau.
* Active le double buffering et ajoute les ecouteurs de souris.
*/
public ViewerImagePanel() {
// j'active le double buffering pour eviter le clignotement
this.setDoubleBuffered(true);
// je mets un fond noir pour que ca soit plus joli
this.setBackground(Color.BLACK);
// j'ajoute les ecouteurs pour detecter les clics et les mouvements de souris
this.addMouseListener(this);
this.addMouseMotionListener(this);
}
/**
* Definit l'image a afficher et reinitialise le decalage.
*
* @param img l'image a afficher
*/
public void setImage(BufferedImage img) {
this.image = img;
// je reinitialise le decalage quand je charge une nouvelle image
this.offsetX = 0;
this.offsetY = 0;
// je demande un reaffichage
repaint();
}
/**
* Methode de dessin du panneau.
* Dessine l'image avec le decalage actuel ou la centre si elle est plus petite.
*
* @param g le contexte graphique
*/
@Override
protected void paintComponent(Graphics g) {
// j'appelle le paintComponent parent pour nettoyer le panneau
super.paintComponent(g);
// si j'ai pas d'image je fais rien
if (image == null) return;
// je recupere la taille actuelle du panneau
int panelW = getWidth();
int panelH = getHeight();
// je recupere la taille de l'image
int imgW = image.getWidth();
int imgH = image.getHeight();
// je pars du decalage actuel pour le dessin
int drawX = offsetX;
int drawY = offsetY;
// si l'image est plus petite que le panneau je la centre automatiquement
if (imgW < panelW) drawX = (panelW - imgW) / 2;
if (imgH < panelH) drawY = (panelH - imgH) / 2;
// je dessine l'image a la position calculee
g.drawImage(image, drawX, drawY, null);
}
/**
* Detecte quand l'utilisateur appuie sur le bouton de la souris.
* Je sauvegarde la position pour calculer le deplacement ensuite.
*
* @param e l'evenement souris
*/
@Override
public void mousePressed(MouseEvent e) {
// je memorise la position de depart du clic
lastX = e.getX();
lastY = e.getY();
}
/**
* Detecte quand l'utilisateur fait glisser la souris.
* Je calcule le deplacement et je mets a jour l'affichage.
*
* @param e l'evenement souris
*/
@Override
public void mouseDragged(MouseEvent e) {
// je calcule la difference entre la position actuelle et la derniere
// et j'ajoute ca au decalage de l'image
offsetX += e.getX() - lastX;
offsetY += e.getY() - lastY;
// je mets a jour la derniere position connue
lastX = e.getX();
lastY = e.getY();
// je redemande un affichage pour voir le deplacement
repaint();
}
// les methodes suivantes sont obligatoires mais j'en ai pas besoin
@Override public void mouseReleased(MouseEvent e) {}
@Override public void mouseClicked(MouseEvent e) {}
@Override public void mouseEntered(MouseEvent e) {}
@Override public void mouseExited(MouseEvent e) {}
@Override public void mouseMoved(MouseEvent e) {}
}
+28 -62
View File
@@ -1,91 +1,57 @@
package fr.iutfbleau.sae;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class ViewerWindow extends JFrame implements MouseListener, MouseMotionListener {
/**
* Fenetre principale du visualiseur d'images PIF.
* Permet d'afficher une image et de la deplacer avec la souris.
*/
public class ViewerWindow extends JFrame {
private BufferedImage image;
private int offsetX = 0;
private int offsetY = 0;
private int lastX;
private int lastY;
// le panneau qui affiche l'image avec double buffering
private final ViewerImagePanel imagePanel;
/**
* Constructeur de la fenetre.
* Initialise le titre, le comportement de fermeture et le panneau d'affichage.
*/
public ViewerWindow() {
super("PIF Viewer");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(true);
this.addMouseListener(this);
this.addMouseMotionListener(this);
// je cree le panneau qui va afficher l'image
this.imagePanel = new ViewerImagePanel();
this.add(imagePanel);
}
/**
* Affiche une image dans la fenetre.
* Adapte la taille de la fenetre a l'image sans depasser 90% de l'ecran.
*
* @param img l'image a afficher
*/
public void displayImage(BufferedImage img) {
this.image = img;
// je passe l'image au panneau
this.imagePanel.setImage(img);
// je prend la taille de l'ecran
// je recupere la taille de l'ecran pour adapter la fenetre
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
// je prend una taille de 90% pour etre raisonable
// je limite a 90% de l'ecran pour rester raisonnable
int maxW = (int)(screen.width * 0.9);
int maxH = (int)(screen.height * 0.9);
// je prends le minimum entre la taille de l'image et la taille max
int w = Math.min(img.getWidth(), maxW);
int h = Math.min(img.getHeight(), maxH);
// j'applique la taille calculee a la fenetre
this.setSize(w, h);
// je centre la fenetre sur l'ecran
this.setLocationRelativeTo(null);
this.setVisible(true);
}
@Override
public void paint(Graphics g) {
super.paint(g);
if (image == null) return;
int panelW = getWidth();
int panelH = getHeight();
int imgW = image.getWidth();
int imgH = image.getHeight();
int drawX = offsetX;
int drawY = offsetY;
// centrage automatique si image plus petite
if (imgW < panelW) drawX = (panelW - imgW) / 2;
if (imgH < panelH) drawY = (panelH - imgH) / 2;
g.drawImage(image, drawX, drawY, null);
}
@Override
public void mousePressed(MouseEvent e) {
lastX = e.getX();
lastY = e.getY();
}
@Override
public void mouseDragged(MouseEvent e) {
offsetX += e.getX() - lastX;
offsetY += e.getY() - lastY;
lastX = e.getX();
lastY = e.getY();
repaint();
}
@Override public void mouseReleased(MouseEvent e) {}
@Override public void mouseClicked(MouseEvent e) {}
@Override public void mouseEntered(MouseEvent e) {}
@Override public void mouseExited(MouseEvent e) {}
@Override public void mouseMoved(MouseEvent e) {}
}
@@ -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;
}
}
@@ -1,115 +1,115 @@
package fr.iutfbleau.sae.util;
import java.io.IOException;
import java.io.InputStream;
/**
* Décorateur de flux permettant la lecture binaire à granularité du bit.
* <p>
* Cette classe encapsule un {@link InputStream} existant et fournit
* des opérations de lecture bit par bit ou par groupes de bits.
* Elle ne gère ni l'ouverture ni la sélection du fichier source.
* </p>
*
* <p>
* Utilisée notamment pour le décodage des fichiers compressés
* (ex : format PIF utilisant des codes de Huffman).
* </p>
*
*
*
* @author Algassimou Pellel Diallo
* @version 1.0
* @since 2025-12-13
*/
public class BitInputStream {
/** Flux d'entrée sous-jacent */
private final InputStream fluxEntree;
/** Octet actuellement chargé depuis le flux */
private int octetCourant;
/** Position du bit courant dans l'octet (du bit 7 au bit 0) */
private int positionBit;
/** Indique si la fin du flux a été atteinte */
private boolean finDeFlux;
/**
* Construit un lecteur binaire à partir d'un flux existant.
*
* @param fluxEntree flux d'entrée à décorer
* @throws IllegalArgumentException si le flux est nul
*/
public BitInputStream(InputStream fluxEntree) {
if (fluxEntree == null) {
throw new IllegalArgumentException("Le flux d'entrée ne peut pas être nul");
}
this.fluxEntree = fluxEntree;
this.octetCourant = 0;
this.positionBit = -1; // force la lecture d'un nouvel octet
this.finDeFlux = false;
}
/**
* Lit un bit depuis le flux binaire.
*
* @return 0 ou 1 si un bit est lu, -1 si la fin du flux est atteinte
* @throws IOException si une erreur de lecture survient
*/
public int readBit() throws IOException {
if (finDeFlux) {
return -1;
}
if (this.positionBit < 0) {
int octetLu = this.fluxEntree.read();
if (octetLu == -1) {
this.finDeFlux = true;
} else {
this.octetCourant = octetLu;
this.positionBit = 7;
}
}
if (finDeFlux) {
return -1;
}
int bit = (this.octetCourant >> this.positionBit) & 1;
this.positionBit--;
return bit;
}
/**
* Lit une séquence de bits consécutifs et les assemble dans un entier.
*
* @param nombreBits nombre de bits à lire (strictement positif)
* @return valeur entière correspondant aux bits lus,
* ou -1 si la fin du flux est atteinte prématurément
* @throws IOException si une erreur de lecture survient
*/
public int readBits(int nombreBits) throws IOException {
int res=0;
for (int i = 0; i < nombreBits; i++) {
int bit = readBit();
if (bit == -1) {
return -1;
}
res = (res << 1) | bit;
}
return res;
}
/**
* Ferme le flux d'entrée sous-jacent.
*
* @throws IOException si une erreur survient lors de la fermeture
*/
public void closeFlux() throws IOException {
this.fluxEntree.close();
}
}
package fr.iutfbleau.sae.mpif;
import java.io.IOException;
import java.io.InputStream;
/**
* Décorateur de flux permettant la lecture binaire à granularité du bit.
* <p>
* Cette classe encapsule un {@link InputStream} existant et fournit
* des opérations de lecture bit par bit ou par groupes de bits.
* Elle ne gère ni l'ouverture ni la sélection du fichier source.
* </p>
*
* <p>
* Utilisée notamment pour le décodage des fichiers compressés
* (ex : format PIF utilisant des codes de Huffman).
* </p>
*
*
*
* @author Algassimou Pellel Diallo
* @version 1.0
* @since 2025-12-13
*/
public class BitInputStream {
/** Flux d'entrée sous-jacent */
private final InputStream fluxEntree;
/** Octet actuellement chargé depuis le flux */
private int octetCourant;
/** Position du bit courant dans l'octet (du bit 7 au bit 0) */
private int positionBit;
/** Indique si la fin du flux a été atteinte */
private boolean finDeFlux;
/**
* Construit un lecteur binaire à partir d'un flux existant.
*
* @param fluxEntree flux d'entrée à décorer
* @throws IllegalArgumentException si le flux est nul
*/
public BitInputStream(InputStream fluxEntree) {
if (fluxEntree == null) {
throw new IllegalArgumentException("Le flux d'entrée ne peut pas être nul");
}
this.fluxEntree = fluxEntree;
this.octetCourant = 0;
this.positionBit = -1; // force la lecture d'un nouvel octet
this.finDeFlux = false;
}
/**
* Lit un bit depuis le flux binaire.
*
* @return 0 ou 1 si un bit est lu, -1 si la fin du flux est atteinte
* @throws IOException si une erreur de lecture survient
*/
public int readBit() throws IOException {
if (finDeFlux) {
return -1;
}
if (this.positionBit < 0) {
int octetLu = this.fluxEntree.read();
if (octetLu == -1) {
this.finDeFlux = true;
} else {
this.octetCourant = octetLu;
this.positionBit = 7;
}
}
if (finDeFlux) {
return -1;
}
int bit = (this.octetCourant >> this.positionBit) & 1;
this.positionBit--;
return bit;
}
/**
* Lit une séquence de bits consécutifs et les assemble dans un entier.
*
* @param nombreBits nombre de bits à lire (strictement positif)
* @return valeur entière correspondant aux bits lus,
* ou -1 si la fin du flux est atteinte prématurément
* @throws IOException si une erreur de lecture survient
*/
public int readBits(int nombreBits) throws IOException {
int res=0;
for (int i = 0; i < nombreBits; i++) {
int bit = readBit();
if (bit == -1) {
return -1;
}
res = (res << 1) | bit;
}
return res;
}
/**
* Ferme le flux d'entrée sous-jacent.
*
* @throws IOException si une erreur survient lors de la fermeture
*/
public void closeFlux() throws IOException {
this.fluxEntree.close();
}
}
@@ -1,4 +1,4 @@
package fr.iutfbleau.sae.util;
package fr.iutfbleau.sae.mpif;
import java.io.IOException;
import java.io.OutputStream;
@@ -1,35 +1,41 @@
package fr.iutfbleau.sae.mpif;
import java.util.Comparator;
import java.util.Comparator;
import java.util.Map;
/**
* Comparateur pour trier les entrées (symbole, longueur) lors de la reconstruction
* des codes canoniques.
* Le tri s'effectue d'abord par longueur croissante, puis par symbole croissant
* en cas d'égalité de longueur.
* </p>
*
* Comparateur utilisé pendant la reconstruction des codes canoniques.
* Il permet de trier les paires (symbole, longueur) dans le bon ordre
* avant de générer les codes binaires.
*
* Le tri se fait en deux étapes :
* - d'abord par la longueur du code (ordre croissant)
* - si deux longueurs sont identiques, on trie par le symbole lui-même
*
* Ce comparateur est utilisé dans la méthode rebuildCanonical du PIFReader.
*/
public class ComparateurEntreeCanonique implements Comparator<Map.Entry<Integer, Integer>> {
/**
* Compare deux entrées (symbole, longueur).
*
* @param entree1 première entrée
* @param entree2 deuxième entrée
* @return un entier négatif, zéro, ou positif selon que la première entrée
* est inférieure, égale ou supérieure à la seconde
* Compare deux entrées contenant chacune un symbole et sa longueur de code.
* Le but est de les classer dans l'ordre nécessaire pour produire
* les codes canoniques dans l'ordre standard.
*
* @param entree1 première paire (symbole, longueur)
* @param entree2 deuxième paire (symbole, longueur)
* @return un entier indiquant si la première entrée doit être placée
* avant, au même niveau ou après la seconde
*/
@Override
public int compare(Map.Entry<Integer, Integer> entree1, Map.Entry<Integer, Integer> entree2) {
// Comparer d'abord par longueur (valeur)
int comparaisonLongueur = entree1.getValue().compareTo(entree2.getValue());
// Si les longueurs sont différentes, cest le critère principal
if (comparaisonLongueur != 0) {
return comparaisonLongueur;
}
// Si les longueurs sont égales, comparer par symbole (clé)
// Sinon, on départage avec le symbole (la clé)
return entree1.getKey().compareTo(entree2.getKey());
}
}
}
+61
View File
@@ -0,0 +1,61 @@
package fr.iutfbleau.sae.mpif;
/**
*
* Représente un nœud utilisé pour décoder les données dans un arbre binaire.
*
* Un nœud peut être :
* - un nœud interne : il possède un fils gauche et/ou un fils droit. Il ne contient pas de valeur utile.
* - une feuille : les deux fils sont null et le nœud contient une valeur (symbole entre 0 et 255).
*
* Cette structure est utilisée par PIFReader pour reconstruire l'arbre de décodage
* des codes canoniques et retrouver les valeurs des pixels.
*/
public class DecodeNode {
/** Fils gauche du nœud. Null si le nœud est une feuille. */
public DecodeNode left;
/** Fils droit du nœud. Null si le nœud est une feuille. */
public DecodeNode right;
/**
* Valeur associée à la feuille.
* Vaut -1 pour les nœuds internes qui ne représentent aucun symbole.
*/
public Integer value;
/**
* Constructeur d'un nœud interne vide.
*
* Le nœud ne contient pas de valeur et possède des pointeurs
* initialement à null pour ses enfants.
*/
public DecodeNode() {
this.left = null;
this.right = null;
this.value = -1;
}
/**
* Constructeur d'un nœud complet, utilisé pour créer une feuille ou un nœud interne.
*
* @param left fils gauche
* @param right fils droit
* @param value valeur associée si c'est une feuille, ou -1 pour un nœud interne
*/
public DecodeNode(DecodeNode left, DecodeNode right, Integer value) {
this.left = left;
this.right = right;
this.value = value;
}
/**
* Indique si le nœud est une feuille (aucun enfant).
*
* @return true si le nœud est une feuille, false sinon
*/
public boolean isLeaf() {
return this.left == null && this.right == null;
}
}
-6
View File
@@ -1,7 +1,5 @@
package fr.iutfbleau.sae.mpif;
import fr.iutfbleau.sae.util.BitInputStream;
import fr.iutfbleau.sae.util.DecodeNode;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -60,7 +58,6 @@ public class PIFReader {
RGBImage img = decodePixels(lecteur, trieR, trieG, trieB);
lecteur.closeFlux();
System.out.println("Fichier PIF lu avec succès : " + width + "x" + height);
return img;
}
@@ -75,7 +72,6 @@ public class PIFReader {
public void readHeader(BitInputStream in) throws IOException {
this.width = in.readBits(16);
this.height = in.readBits(16);
System.out.println("Dimensions lues : " + this.width + "x" + this.height);
}
@@ -105,8 +101,6 @@ public class PIFReader {
for (int i = 0; i < 256; i++){
this.lenB[i] = in.readBits(8);
}
System.out.println("Tables de longueurs lues");
}
+52 -3
View File
@@ -1,13 +1,34 @@
package fr.iutfbleau.sae.mpif;
import fr.iutfbleau.sae.util.BitOutputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
/**
* Cette classe permet d'écrire une image au format PIF.
* Le fichier PIF contient une en-tête avec la taille de l'image,
* les longueurs des codes canoniques, puis l'encodage des pixels
* en utilisant ces codes.
*
* Les méthodes de cette classe suivent l'ordre d'écriture du fichier :
* 1) écrire l'en-tête
* 2) écrire les tables de longueurs
* 3) encoder les pixels un par un
*/
public class PIFWriter {
/**
* Sauvegarde une image au format PIF dans un fichier.
* Le fichier est créé ou écrasé si il existe déjà.
*
* @param filepath chemin du fichier à écrire
* @param image l'image RGB à encoder
* @param canonR codes canoniques de la composante rouge
* @param canonG codes canoniques de la composante verte
* @param canonB codes canoniques de la composante bleue
* @throws Exception si une erreur survient dans l'écriture
*/
public void writeTOFile(String filepath, RGBImage image,
Map<Integer,String> canonR,
Map<Integer,String> canonG,
@@ -31,7 +52,15 @@ public class PIFWriter {
ecriveur.fermerFlux();
}
// Ecriture de l'en-tête du fichier PIF (largeur et hauteur)
/**
* Écrit l'en-tête du fichier PIF.
* L'en-tête contient uniquement la largeur et la hauteur,
* chacune codée sur 16 bits.
*
* @param out flux de sortie binaire
* @param width largeur de l'image
* @param height hauteur de l'image
*/
public void writeHeader(BitOutputStream out, int width, int height) {
try {
out.writeBits(width, 16);
@@ -41,6 +70,16 @@ public class PIFWriter {
}
}
/**
* Écrit dans le fichier les longueurs des codes canoniques.
* Pour chaque composante (R, G, B), on parcourt les 256 valeurs possibles.
* Si un symbole n'existe pas dans les codes, on écrit une longueur 0.
*
* @param out flux binaire vers le fichier
* @param canonR longueurs pour la composante rouge
* @param canonG longueurs pour la composante verte
* @param canonB longueurs pour la composante bleue
*/
public void writeTables(BitOutputStream out, Map<Integer, String> canonR,
Map<Integer, String> canonG, Map<Integer, String> canonB) {
@@ -80,7 +119,17 @@ public class PIFWriter {
}
}
// Méthode pour encoder les pixels de l'image en utilisant les codes canoniques
/**
* Encode chaque pixel de l'image en utilisant les codes canoniques.
* Pour chaque pixel, les composants R, G et B sont écrits
* dans cet ordre en utilisant writeBitString().
*
* @param out flux d'écriture binaire
* @param image l'image source
* @param canonRED codes canoniques pour le rouge
* @param canonGREEN codes canoniques pour le vert
* @param canonBLUE codes canoniques pour le bleu
*/
public void encodePixels(BitOutputStream out, RGBImage image,
Map<Integer, String> canonRED,
Map<Integer, String> canonGREEN,
+34 -5
View File
@@ -1,38 +1,67 @@
package fr.iutfbleau.sae.mpif;
/**
* Cette classe représente un pixel composé de trois valeurs :
* rouge, vert et bleu. Chaque valeur est comprise entre 0 et 255.
* La classe sert surtout pour manipuler les pixels dans une image RGB.
*/
public class Pixel{
private int r;
private int g;
private int b;
//à completer
/**
* Crée un pixel avec ses trois composantes.
*
* @param red valeur de la composante rouge
* @param green valeur de la composante verte
* @param blue valeur de la composante bleue
*/
public Pixel(int red, int green, int blue){
this.r=red;
this.g=green;
this.b=blue;
this.r = red;
this.g = green;
this.b = blue;
}
/**
* Retourne la composante bleue du pixel.
*/
public int getB() {
return b;
}
/**
* Retourne la composante verte du pixel.
*/
public int getG() {
return g;
}
/**
* Retourne la composante rouge du pixel.
*/
public int getR() {
return r;
}
/**
* Modifie la composante rouge du pixel.
*/
public void setR(int r) {
this.r = r;
}
/**
* Modifie la composante bleue du pixel.
*/
public void setB(int b) {
this.b = b;
}
/**
* Modifie la composante verte du pixel.
*/
public void setG(int g) {
this.g = g;
}
}
}
+35 -4
View File
@@ -1,31 +1,62 @@
package fr.iutfbleau.sae.mpif;
/**
* Cette classe représente une image composée de pixels RGB.
* L'image est stockée sous forme d'un tableau 2D contenant des objets Pixel.
* Chaque pixel possède trois valeurs : rouge, vert et bleu.
*/
public class RGBImage {
private int width;
private int height;
private Pixel [][] pixels;
/**
* Crée une image vide avec une largeur et une hauteur données.
* Tous les pixels doivent être ajoutés ensuite avec setPixel().
*
* @param lar largeur de l'image
* @param haut hauteur de l'image
*/
public RGBImage (int lar, int haut){
this.width=lar;
this.height=haut;
this.width = lar;
this.height = haut;
this.pixels = new Pixel[this.width][this.height];
}
/**
* Retourne la largeur de l'image.
*/
public int getWidth() {
return width;
}
/**
* Retourne la hauteur de l'image.
*/
public int getHeight() {
return height;
}
/**
* Place un pixel aux coordonnées indiquées.
*
* @param x position horizontale
* @param y position verticale
* @param p pixel à insérer
*/
public void setPixel(int x, int y, Pixel p) {
this.pixels[x][y] = p;
}
/**
* Retourne le pixel situé aux coordonnées indiquées.
*
* @param x position horizontale
* @param y position verticale
* @return le pixel présent à cette position
*/
public Pixel getPixel(int x, int y) {
return this.pixels[x][y];
}
}
}
-23
View File
@@ -1,23 +0,0 @@
package fr.iutfbleau.sae.util;
public class DecodeNode {
public DecodeNode left;
public DecodeNode right;
public Integer value; // null si pas une feuille
public DecodeNode() {
this.left = null;
this.right = null;
this.value = -1; // valeur non initialisée
}
public DecodeNode(DecodeNode left, DecodeNode right, Integer value) {
this.left = left;
this.right = right;
this.value = value;
}
public boolean isLeaf() {
return this.left == null && this.right == null;
}
}
-153
View File
@@ -1,153 +0,0 @@
package fr.iutfbleau.sae.util;
/**
* Représente un nœud de l'arbre de Huffman.
* <p>
* Un {@code HuffmanNode} peut être :
* <ul>
* <li>une feuille, contenant une valeur (symbole) et une fréquence</li>
* <li>un nœud interne, contenant uniquement une fréquence et deux enfants</li>
* </ul>
* </p>
*
* <p>
* Cette classe est une structure de données utilisée par {@code HuffmanTree}
* pour construire l'arbre de Huffman.
* </p>
*
* @author Algassimou Pellel Diallo
* @version 1.1
* @since 2025-12-17
*/
public class HuffmanNode {
/**
* Valeur de la composante (symbole) représentée par ce nœud (uniquement pour les feuilles).
* Représente la part de la composante (rouge, verte ou bleue) dans la couleur d'un pixel.
* Pour les nœuds internes, cette valeur vaut -1.
*/
private int value;
/** Fréquence du symbole (somme des fréquences des enfants pour les nœuds internes). */
private int frequence;
/** Fils gauche du nœud (null si feuille). */
private HuffmanNode left;
/** Fils droit du nœud (null si feuille). */
private HuffmanNode right;
/**
* Construit un nœud feuille de Huffman.
* <p>
* Ce constructeur est utilisé pour représenter une valeur
* issue de la table de fréquences.
* </p>
*
* @param value la valeur (symbole) représentée par ce nœud (entre 0 et 255)
* @param frequence la fréquence d'apparition de la valeur
* @throws IllegalArgumentException si la valeur n'est pas entre 0 et 255
* ou si la fréquence est négative
*/
public HuffmanNode(int value, int frequence) {
if (value < 0 || value > 255) {
throw new IllegalArgumentException("La valeur 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;
}
/**
* Construit un nœud interne de Huffman.
* <p>
* Ce constructeur est utilisé lors de la fusion de deux nœuds
* de plus faible fréquence lors de la construction de l'arbre.
* </p>
*
* @param left le fils gauche
* @param right le fils droit
* @throws IllegalArgumentException si l'un des fils est null
*/
public HuffmanNode(HuffmanNode left, HuffmanNode right) {
if (left == null || right == null) {
throw new IllegalArgumentException("Les fils ne peuvent pas être null");
}
this.value = -1; // Valeur sentinelle pour les nœuds internes
this.left = left;
this.right = right;
this.frequence = left.frequence + right.frequence;
}
/**
* Indique si ce nœud est une feuille.
*
* @return {@code true} si le nœud est une feuille, {@code false} sinon
*/
public boolean isLeaf() {
return this.left == null && this.right == null;
}
/**
* Retourne la fréquence du nœud.
*
* @return la fréquence
*/
public int getFrequence() {
return this.frequence;
}
/**
* Retourne le fils gauche du nœud.
*
* @return le fils gauche, ou null si le nœud est une feuille
*/
public HuffmanNode getLeft() {
return this.left;
}
/**
* Retourne le fils droit du nœud.
*
* @return le fils droit, ou null si le nœud est une feuille
*/
public HuffmanNode getRight() {
return this.right;
}
/**
* Retourne la valeur représentée par ce nœud.
* <p>
* Cette méthode n'a de sens que si le nœud est une feuille.
* </p>
*
* @return la valeur du symbole (entre 0 et 255)
* @throws IllegalStateException si le nœud n'est pas une feuille
*/
public int getValue() {
if (!this.isLeaf()) {
throw new IllegalStateException("La valeur n'est définie que pour les feuilles.");
}
return this.value;
}
/**
* Retourne une représentation textuelle du nœud.
* <p>
* Utile pour le débogage et l'affichage de l'arbre.
* </p>
*
* @return une chaîne décrivant le nœud
*/
@Override
public String toString() {
if (this.isLeaf()) {
return "Feuille(valeur=" + this.value + ", freq=" + this.frequence + ")";
}
return "Noeud(freq=" + this.frequence + ")";
}
}