sae fini les gars
This commit is contained in:
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 l’application.
|
||||
* Elle centralise l’affichage des informations liées à la conversion
|
||||
* d’une image (aperçu, fréquences, codes).
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Elle sert de point d’entré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 l’affichage.
|
||||
* </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 l’image affichée dans la zone d’aperçu.
|
||||
*
|
||||
* <p>
|
||||
* Cette méthode est appelée lorsque l’image à convertir
|
||||
* a été chargée. La fenêtre ne modifie pas l’image :
|
||||
* elle la transmet simplement au panneau d’aperç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 l’affichage 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 l’affichage des codes Huffman.
|
||||
*
|
||||
* <p>
|
||||
* Elle permet uniquement d’afficher 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 l’affichage 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
package fr.iutfbleau.sae.util;
|
||||
package fr.iutfbleau.sae;
|
||||
import javax.swing.*;
|
||||
|
||||
public class GestionErreur {
|
||||
@@ -1,76 +1,109 @@
|
||||
package fr.iutfbleau.sae;
|
||||
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import javax.swing.JPanel;
|
||||
/**
|
||||
* Le panneau d’aperçu de l’image.
|
||||
|
||||
/**
|
||||
* Panneau d'aperçu de l'image.
|
||||
*
|
||||
* <p>
|
||||
* Ce panneau affiche un aperçu de l’image 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+115
-115
@@ -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
-1
@@ -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, c’est 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user