first commit
@@ -0,0 +1,26 @@
|
||||
# ---> Java
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
replay_pid*
|
||||
|
||||
|
After Width: | Height: | Size: 249 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,210 @@
|
||||
# =========================
|
||||
# VARIABLES
|
||||
# =========================
|
||||
JC = javac
|
||||
JCFLAGS = -encoding UTF-8 -implicit:none -d build -cp "build" -sourcepath "src"
|
||||
|
||||
JVM = java
|
||||
JVMFLAGS = -cp "build"
|
||||
|
||||
JAR = jar
|
||||
|
||||
PACKAGE_NAME = fr.iutfbleau.pif.
|
||||
SRC_PATH = src/fr/iutfbleau/pif/
|
||||
BUILD_PATH = build/fr/iutfbleau/pif/
|
||||
DIST_PATH = dist/
|
||||
|
||||
# =========================
|
||||
# CIBLE PAR DEFAUT
|
||||
# =========================
|
||||
all : jar
|
||||
|
||||
# =========================
|
||||
# UTILITAIRES
|
||||
# =========================
|
||||
$(BUILD_PATH)SelecteurFichier.class : $(SRC_PATH)SelecteurFichier.java
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)SelecteurFichier.java
|
||||
|
||||
$(BUILD_PATH)PIFImage.class : $(SRC_PATH)PIFImage.java
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)PIFImage.java
|
||||
|
||||
$(BUILD_PATH)CodageRGB.class : $(SRC_PATH)CodageRGB.java
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)CodageRGB.java
|
||||
|
||||
$(BUILD_PATH)GestionSortiePIF.class : $(SRC_PATH)GestionSortiePIF.java
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)GestionSortiePIF.java
|
||||
|
||||
# =========================
|
||||
# HUFFMAN (ARBRE)
|
||||
# =========================
|
||||
$(BUILD_PATH)Noeud.class : $(SRC_PATH)Noeud.java
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)Noeud.java
|
||||
|
||||
$(BUILD_PATH)NoeudFeuille.class : $(SRC_PATH)NoeudFeuille.java $(BUILD_PATH)Noeud.class
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)NoeudFeuille.java
|
||||
|
||||
$(BUILD_PATH)NoeudInterne.class : $(SRC_PATH)NoeudInterne.java $(BUILD_PATH)Noeud.class
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)NoeudInterne.java
|
||||
|
||||
# =========================
|
||||
# ENTREES CANONIQUES
|
||||
# =========================
|
||||
$(BUILD_PATH)EntreeCanonique.class : $(SRC_PATH)EntreeCanonique.java
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)EntreeCanonique.java
|
||||
|
||||
$(BUILD_PATH)EntreeLongueur.class : $(SRC_PATH)EntreeLongueur.java
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)EntreeLongueur.java
|
||||
|
||||
# =========================
|
||||
# TABLE CODES CANONIQUES
|
||||
# =========================
|
||||
$(BUILD_PATH)TableCodesCanoniques.class : $(SRC_PATH)TableCodesCanoniques.java $(BUILD_PATH)EntreeCanonique.class
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)TableCodesCanoniques.java
|
||||
|
||||
# =========================
|
||||
# HUFFMAN
|
||||
# =========================
|
||||
$(BUILD_PATH)Huffman.class : $(SRC_PATH)Huffman.java \
|
||||
$(BUILD_PATH)Noeud.class \
|
||||
$(BUILD_PATH)NoeudFeuille.class \
|
||||
$(BUILD_PATH)NoeudInterne.class \
|
||||
$(BUILD_PATH)TableCodesCanoniques.class
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)Huffman.java
|
||||
|
||||
# =========================
|
||||
# ECRITURE / LECTURE PIF
|
||||
# =========================
|
||||
$(BUILD_PATH)EcrivainPIF.class : $(SRC_PATH)EcrivainPIF.java $(BUILD_PATH)PIFImage.class
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)EcrivainPIF.java
|
||||
|
||||
$(BUILD_PATH)NoeudDecodage.class : $(SRC_PATH)NoeudDecodage.java
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)NoeudDecodage.java
|
||||
|
||||
$(BUILD_PATH)LecteurPIF.class : $(SRC_PATH)LecteurPIF.java \
|
||||
$(BUILD_PATH)PIFImage.class \
|
||||
$(BUILD_PATH)NoeudDecodage.class \
|
||||
$(BUILD_PATH)EntreeLongueur.class
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)LecteurPIF.java
|
||||
|
||||
# =========================
|
||||
# AFFICHAGE
|
||||
# =========================
|
||||
$(BUILD_PATH)ControleurVisualisateur.class : $(SRC_PATH)ControleurVisualisateur.java
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)ControleurVisualisateur.java
|
||||
|
||||
$(BUILD_PATH)FenetreVisualisateur.class : $(SRC_PATH)FenetreVisualisateur.java \
|
||||
$(BUILD_PATH)PIFImage.class \
|
||||
$(BUILD_PATH)ControleurVisualisateur.class
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)FenetreVisualisateur.java
|
||||
|
||||
$(BUILD_PATH)FormateurTexte.class : $(SRC_PATH)FormateurTexte.java
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)FormateurTexte.java
|
||||
|
||||
$(BUILD_PATH)OutilsImageSwing.class : $(SRC_PATH)OutilsImageSwing.java $(BUILD_PATH)PIFImage.class
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)OutilsImageSwing.java
|
||||
|
||||
# =========================
|
||||
# FENETRE CONVERTISSEUR
|
||||
# =========================
|
||||
$(BUILD_PATH)FenetreConvertisseur.class : $(SRC_PATH)FenetreConvertisseur.java \
|
||||
$(BUILD_PATH)PIFImage.class \
|
||||
$(BUILD_PATH)Huffman.class \
|
||||
$(BUILD_PATH)FormateurTexte.class \
|
||||
$(BUILD_PATH)OutilsImageSwing.class
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)FenetreConvertisseur.java
|
||||
|
||||
# =========================
|
||||
# MAINS
|
||||
# =========================
|
||||
$(BUILD_PATH)MainConvertisseur.class : $(SRC_PATH)MainConvertisseur.java \
|
||||
$(BUILD_PATH)SelecteurFichier.class \
|
||||
$(BUILD_PATH)PIFImage.class \
|
||||
$(BUILD_PATH)CodageRGB.class \
|
||||
$(BUILD_PATH)Huffman.class \
|
||||
$(BUILD_PATH)EcrivainPIF.class \
|
||||
$(BUILD_PATH)GestionSortiePIF.class \
|
||||
$(BUILD_PATH)FenetreConvertisseur.class
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)MainConvertisseur.java
|
||||
|
||||
$(BUILD_PATH)MainVisualisateur.class : $(SRC_PATH)MainVisualisateur.java \
|
||||
$(BUILD_PATH)SelecteurFichier.class \
|
||||
$(BUILD_PATH)LecteurPIF.class \
|
||||
$(BUILD_PATH)FenetreVisualisateur.class
|
||||
$(JC) $(JCFLAGS) $(SRC_PATH)MainVisualisateur.java
|
||||
|
||||
# =========================
|
||||
# DIST (création dossier)
|
||||
# =========================
|
||||
distdir:
|
||||
ifeq ($(OS),Windows_NT)
|
||||
@if not exist dist mkdir dist
|
||||
else
|
||||
mkdir -p $(DIST_PATH)
|
||||
endif
|
||||
|
||||
# =========================
|
||||
# JAR (avec Main-Class) -> dist/
|
||||
# =========================
|
||||
# Les vrais artefacts
|
||||
JAR_CONV = $(DIST_PATH)convertisseur.jar
|
||||
JAR_VISU = $(DIST_PATH)visualisateur.jar
|
||||
|
||||
all: $(JAR_CONV) $(JAR_VISU)
|
||||
|
||||
$(JAR_CONV): distdir $(BUILD_PATH)MainConvertisseur.class
|
||||
$(JAR) cfe $(JAR_CONV) $(PACKAGE_NAME)MainConvertisseur -C build fr
|
||||
|
||||
$(JAR_VISU): distdir $(BUILD_PATH)MainVisualisateur.class
|
||||
$(JAR) cfe $(JAR_VISU) $(PACKAGE_NAME)MainVisualisateur -C build fr
|
||||
|
||||
jar: $(JAR_CONV) $(JAR_VISU)
|
||||
|
||||
# =========================
|
||||
# EXECUTION DES JAR
|
||||
# =========================
|
||||
|
||||
# Arguments donnés après la cible dans la ligne de commande
|
||||
run_convertisseur: $(JAR_CONV)
|
||||
$(JVM) -jar $(JAR_CONV) $(filter-out $@,$(MAKECMDGOALS))
|
||||
|
||||
run_visualisateur: $(JAR_VISU)
|
||||
$(JVM) -jar $(JAR_VISU) $(filter-out $@,$(MAKECMDGOALS))
|
||||
|
||||
%:
|
||||
@:
|
||||
|
||||
# =========================
|
||||
# JAVADOC
|
||||
# =========================
|
||||
javadoc :
|
||||
ifeq ($(OS),Windows_NT)
|
||||
if not exist javadoc mkdir javadoc
|
||||
else
|
||||
mkdir -p javadoc
|
||||
endif
|
||||
javadoc -d javadoc -sourcepath src -subpackages fr.iutfbleau.pif
|
||||
|
||||
# =========================
|
||||
# NETTOYAGE
|
||||
# =========================
|
||||
clean:
|
||||
ifeq ($(OS),Windows_NT)
|
||||
-rmdir /S /Q build
|
||||
-rmdir /S /Q dist
|
||||
else
|
||||
-rm -rf build
|
||||
-rm -rf dist
|
||||
endif
|
||||
|
||||
cleanjavadoc:
|
||||
ifeq ($(OS),Windows_NT)
|
||||
-rmdir /S /Q javadoc
|
||||
else
|
||||
-rm -rf javadoc
|
||||
endif
|
||||
|
||||
# =========================
|
||||
# BUTS FACTICES
|
||||
# =========================
|
||||
.PHONY : all clean javadoc cleanjavadoc run_convertisseur run_visualisateur distdir
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
# PIF — Primitive Image Format 📁
|
||||
|
||||
|
||||
Bienvenue sur notre projet **"PIF"**, ce projet a été réalisé dans le cadre de la [SAÉ 3.2](https://iut-fbleau.fr/sitebp/sae3/32_2025/TC6H66SI9M28YUVH.php) du département Informatique à l’IUT de Fontainebleau et a pour objectif de créer un support logiciel pour un nouveau format d’image, appelé PIF (Primitive Image Format), inspiré du format JFIF (JPEG File Interchange Format)
|
||||
|
||||
|
||||
|
||||
## Table des matières
|
||||
- [Présentation](#présentation)
|
||||
- [Organisation du projet](#organisation-du-projet)
|
||||
- [Compilation et lancement](#Compilation-et-lancement)
|
||||
- [Création de la documentation](#création-de-la-documentation)
|
||||
- [Nettoyage des fichiers temporaires](#Nettoyage-des-fichiers-temporaires)
|
||||
- [Rapport d'avancement](#Rapport-d-avancement)
|
||||
- [Crédits](#crédits)
|
||||
|
||||
---
|
||||
|
||||
## Présentation 🧩
|
||||
|
||||
Le format PIF est un format d’image compressé sans perte, basé sur l’algorithme de Huffman. Son objectif est de mettre en œuvre les principes de la compression et du décodage des données, plutôt que de concurrencer les formats d’images existants déjà optimisés (comme PNG ou JPEG).
|
||||
|
||||
Ce projet consiste à développer deux programmes en Java :
|
||||
|
||||
|
||||
### Visualisateur PIF
|
||||
|
||||
- Ouvre et affiche une image contenue dans un fichier .pif
|
||||
|
||||
- Le chemin du fichier peut être fourni en argument ou choisi via un JFileChooser
|
||||
|
||||
- Fenêtre redimensionnable et image navigable à la souris
|
||||
|
||||
|
||||
### Convertisseur vers PIF
|
||||
|
||||
- Charge une image dans un format supporté par ImageIO.
|
||||
|
||||
- Affiche des informations détaillées (image, tables de fréquences, codes Huffman et codes canoniques)
|
||||
|
||||
- Permet d’exporter l’image au format .pif
|
||||
|
||||
---
|
||||
|
||||
Ce projet a été développé en **Java** en respectant les consignes de l’IUT, avec une organisation claire du code (séparation des rôles) afin de faciliter la compréhension, la maintenance et l’évolution du programme.
|
||||
|
||||
---
|
||||
|
||||
## Organisation du projet
|
||||
|
||||
L’organisation du projet suit une structure claire permettant de séparer le code source, les composants externes et les fichiers de configuration. Voici l'aborescence :
|
||||
|
||||
```
|
||||
SAE32_2025/
|
||||
├── Documentations/
|
||||
│ ├── diagramme_uml/
|
||||
│ ├── diagramme_classes
|
||||
│ └── diagramme_objets
|
||||
│ └── Rapports.pdf
|
||||
├── res/ #Image que l'ont veut convertir en PIF (dans ce cas on met celle du projet)
|
||||
│ ├── rotsnake.pif
|
||||
│ └── rotsnake.png
|
||||
├── src/ # Code source du projet
|
||||
│ └── fr/iutbleau/pif/ # Package principal contenant toutes les classes Java
|
||||
│ ├── CodageRGb.java
|
||||
│ ├── ControleurAfficheurImage.java
|
||||
│ ├── ControleurVisualisateur.java
|
||||
│ ├── EcrivainPIF.java
|
||||
│ ├── EntreeCanonique.java
|
||||
│ ├── EntreeLongueur.java
|
||||
│ ├── FenetreConvertisseur.java
|
||||
│ ├── FenetreVisualisateur.java
|
||||
│ ├── FormateurTexte.java
|
||||
│ ├── GestionSortiePIF.java
|
||||
│ ├── Huffman.java
|
||||
│ ├── LecteurPIF.java
|
||||
│ ├── MainConvertisseur.java
|
||||
│ ├── MainVisualisateur.java
|
||||
│ ├── Noeud.java
|
||||
│ ├── NoeudDecodage.java
|
||||
│ ├── NoeudFeuille.java
|
||||
│ ├── NoeudInterne.java
|
||||
│ ├── OutilsImageSwing.java
|
||||
│ ├── PIFImage.java
|
||||
│ ├── SelecteurFichier.java
|
||||
│ └── TableCodesCanoniques.java
|
||||
├── .gitignore # Permet d’ignorer les fichiers .class générés lors de la compilation
|
||||
├── Makefile # Automatisation pour la compilation et l’exécution
|
||||
└── README.md # Fichier actuelle
|
||||
|
||||
```
|
||||
---
|
||||
|
||||
## Compilation et lancement
|
||||
|
||||
|
||||
|
||||
Pour avoir accès au projet faire les commandes suivantes:
|
||||
|
||||
```bash
|
||||
# Cloner le dépôt
|
||||
git clone https://grond.iut-fbleau.fr/sehl/SAE32_2025/
|
||||
cd SAE32_2025
|
||||
```
|
||||
---
|
||||
|
||||
### Compilation et création des JAR :
|
||||
|
||||
```bash
|
||||
make
|
||||
# ou
|
||||
make all
|
||||
```
|
||||
Compile toutes les classes Java
|
||||
|
||||
Crée les archives :
|
||||
- dist/convertisseur.jar
|
||||
- dist/visualisateur.jar
|
||||
|
||||
---
|
||||
|
||||
### Lancer le convertisseur PIF :
|
||||
```bash
|
||||
make run_convertisseur
|
||||
```
|
||||
|
||||
Ouvre une fenêtre pour choisir l’image à convertir
|
||||
-> Le fichier .pif est créé automatiquement
|
||||
|
||||
Avec arguments :
|
||||
- make run_convertisseur image.png
|
||||
- make run_convertisseur image.png sortie.pif
|
||||
|
||||
---
|
||||
|
||||
Si un seul argument est fourni :
|
||||
- l’image est utilisée comme entrée
|
||||
- la sortie est générée automatiquement dans res/sortie/
|
||||
|
||||
---
|
||||
|
||||
Si deux arguments sont fournis :
|
||||
- premier : image d’entrée
|
||||
- second : fichier de sortie (.pif forcé automatiquement)
|
||||
|
||||
---
|
||||
|
||||
### Lancer le visualisateur PIF :
|
||||
|
||||
```bash
|
||||
make run_visualisateur
|
||||
```
|
||||
Ouvre une fenêtre pour choisir un fichier .pif
|
||||
|
||||
Avec argument :
|
||||
|
||||
```bash
|
||||
make run_visualisateur image.pif
|
||||
```
|
||||
---
|
||||
|
||||
|
||||
### Création de la documentation
|
||||
|
||||
La documentation du code source est générée via **Javadoc**. Utilisez :
|
||||
|
||||
```bash
|
||||
make javadoc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nettoyage des fichiers temporaires
|
||||
|
||||
Pour supprimer les fichiers intermédiaires on fait :
|
||||
|
||||
```bash
|
||||
make clean
|
||||
```
|
||||
|
||||
Supprime les dossiers :
|
||||
- build/
|
||||
- dist/
|
||||
|
||||
Pour supprimer la documentation JavaDoc (dossier javadoc/) :
|
||||
|
||||
```bash
|
||||
make cleanjavadoc
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Rapport d'avancement
|
||||
|
||||
Le rapport du projet est disponible dans le dossier `Documentations`. Accédez-y directement ici : [Documentations/Pif-Rapport.pdf](./Documentations/Pif-Rapport.pdf).
|
||||
|
||||
---
|
||||
|
||||
## Crédits
|
||||
|
||||
Ce projet a été réalisé par :
|
||||
- [Jenson VAL](https://grond.iut-fbleau.fr/val)
|
||||
- [Aylane SEHL](https://grond.iut-fbleau.fr/sehl)
|
||||
- [Séri-Khane YOLOU](https://grond.iut-fbleau.fr/yolou)
|
||||
|
||||
Professeur : **Luc Hernandez**.
|
||||
|
After Width: | Height: | Size: 184 KiB |
@@ -0,0 +1,73 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
/**
|
||||
* Classe utilitaire qui sert à extraire les composantes Rouge, Vert et Bleu
|
||||
* d'une image stockée sous forme de pixels.
|
||||
*
|
||||
* Chaque composante peut être traitée séparément (par exemple pour le codage
|
||||
* Huffman), puis recombinée pour reconstruire l'image finale.
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class CodageRGB {
|
||||
|
||||
|
||||
/**
|
||||
* Extrait la composante rouge (0..255) de chaque pixel.
|
||||
*
|
||||
* @param pixels tableau des pixels (format int Java)
|
||||
* @return tableau des rouges (0..255)
|
||||
*/
|
||||
|
||||
public static int[] extraireRouge(int[] pixels) {
|
||||
int[] rouges = new int[pixels.length];
|
||||
|
||||
for (int i = 0; i < pixels.length; i = i + 1) {
|
||||
int pixel = pixels[i];
|
||||
rouges[i] = (pixel >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
return rouges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrait la composante verte (0..255) de chaque pixel.
|
||||
*
|
||||
* @param pixels tableau des pixels (format int Java)
|
||||
* @return tableau des verts (0..255)
|
||||
*/
|
||||
|
||||
public static int[] extraireVert(int[] pixels) {
|
||||
int[] verts = new int[pixels.length];
|
||||
|
||||
for (int i = 0; i < pixels.length; i = i + 1) {
|
||||
int pixel = pixels[i];
|
||||
verts[i] = (pixel >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
return verts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrait la composante bleue (0..255) de chaque pixel.
|
||||
*
|
||||
* @param pixels tableau des pixels (format int Java)
|
||||
* @return tableau des bleus (0..255)
|
||||
*/
|
||||
|
||||
public static int[] extraireBleu(int[] pixels) {
|
||||
int[] bleus = new int[pixels.length];
|
||||
|
||||
for (int i = 0; i < pixels.length; i = i + 1) {
|
||||
int pixel = pixels[i];
|
||||
bleus[i] = pixel & 0xFF;
|
||||
}
|
||||
|
||||
return bleus;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JViewport;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
/**
|
||||
* La classe <code>ControleurVisualisateur</code> gère les interactions
|
||||
* de la souris pour permettre le déplacement d'une image affichée
|
||||
* dans un <code>JScrollPane</code>.
|
||||
*
|
||||
* Le déplacement se fait en maintenant le bouton gauche de la souris
|
||||
* et en la faisant glisser.
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class ControleurVisualisateur implements MouseListener, MouseMotionListener {
|
||||
|
||||
/**
|
||||
* Panneau de défilement contenant l'image.
|
||||
*/
|
||||
private JScrollPane panneauDefilement;
|
||||
|
||||
/**
|
||||
* Composant affichant l'image.
|
||||
*/
|
||||
private JLabel etiquetteImage;
|
||||
|
||||
/**
|
||||
* Indique si l'utilisateur est en train de déplacer l'image.
|
||||
*/
|
||||
private boolean glisser;
|
||||
|
||||
/**
|
||||
* Dernière position connue de la souris (coordonnées écran).
|
||||
*/
|
||||
private Point dernierPointEcran;
|
||||
|
||||
/**
|
||||
* Construit le contrôleur de déplacement de l'image.
|
||||
*
|
||||
* @param panneauDefilement panneau de défilement
|
||||
* @param etiquetteImage composant contenant l'image
|
||||
*/
|
||||
public ControleurVisualisateur(JScrollPane panneauDefilement, JLabel etiquetteImage) {
|
||||
this.panneauDefilement = panneauDefilement;
|
||||
this.etiquetteImage = etiquetteImage;
|
||||
this.glisser = false;
|
||||
this.dernierPointEcran = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Détecte l'appui sur le bouton gauche de la souris
|
||||
* et initialise le déplacement.
|
||||
*/
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (SwingUtilities.isLeftMouseButton(e)) {
|
||||
this.glisser = true;
|
||||
this.dernierPointEcran = e.getLocationOnScreen();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Arrête le déplacement lorsque le bouton de la souris est relâché.
|
||||
*/
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
this.glisser = false;
|
||||
this.dernierPointEcran = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Déplace la zone visible de l'image en fonction du mouvement
|
||||
* de la souris.
|
||||
*/
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
|
||||
if (!this.glisser || this.dernierPointEcran == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Point positionCourante = e.getLocationOnScreen();
|
||||
|
||||
int deplacementX = positionCourante.x - this.dernierPointEcran.x;
|
||||
int deplacementY = positionCourante.y - this.dernierPointEcran.y;
|
||||
|
||||
JViewport viewport = this.panneauDefilement.getViewport();
|
||||
Point positionVue = viewport.getViewPosition();
|
||||
|
||||
positionVue.x = positionVue.x - deplacementX;
|
||||
positionVue.y = positionVue.y - deplacementY;
|
||||
|
||||
int maxX = this.etiquetteImage.getWidth() - viewport.getWidth();
|
||||
int maxY = this.etiquetteImage.getHeight() - viewport.getHeight();
|
||||
|
||||
if (maxX < 0) {
|
||||
maxX = 0;
|
||||
}
|
||||
|
||||
if (maxY < 0) {
|
||||
maxY = 0;
|
||||
}
|
||||
|
||||
if (positionVue.x < 0) {
|
||||
positionVue.x = 0;
|
||||
}
|
||||
|
||||
if (positionVue.y < 0) {
|
||||
positionVue.y = 0;
|
||||
}
|
||||
|
||||
if (positionVue.x > maxX) {
|
||||
positionVue.x = maxX;
|
||||
}
|
||||
|
||||
if (positionVue.y > maxY) {
|
||||
positionVue.y = maxY;
|
||||
}
|
||||
|
||||
viewport.setViewPosition(positionVue);
|
||||
this.dernierPointEcran = positionCourante;
|
||||
}
|
||||
|
||||
/* Méthodes inutilisées mais obligatoires */
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) { }
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) { }
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) { }
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) { }
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* La classe <code>EcrivainPIF</code> permet d'écrire une image compressée
|
||||
* au format <code>.pif</code> (RGB) à l'aide de la compression de Huffman.
|
||||
*
|
||||
* <p>Le fichier PIF contient :</p>
|
||||
* <ul>
|
||||
* <li>une signature permettant d'identifier le format,</li>
|
||||
* <li>les dimensions de l'image (largeur et hauteur),</li>
|
||||
* <li>trois tables de codes canoniques (Rouge, Vert, Bleu),</li>
|
||||
* <li>trois flux compressés correspondant aux composantes RGB.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Les tables canoniques ne stockent que les longueurs des codes.
|
||||
* Les codes binaires complets sont reconstruits lors de la lecture.</p>
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class EcrivainPIF {
|
||||
|
||||
/** Signature "PIF" en int : 0x50 0x49 0x46 0x32. */
|
||||
private static final int SIGNATURE_PIF = 0x50494632;
|
||||
|
||||
/**
|
||||
* Écrit un fichier PIF à partir d'une image et de ses données compressées.
|
||||
* Les composantes Rouge, Vert et Bleu sont écrites dans cet ordre.
|
||||
*
|
||||
* @param cheminSortie chemin du fichier de sortie
|
||||
* @param image image source (dimensions)
|
||||
* @param codesRouge table canonique Rouge (valeur -> code)
|
||||
* @param codesVert table canonique Vert (valeur -> code)
|
||||
* @param codesBleu table canonique Bleu (valeur -> code)
|
||||
* @param nombreBitsRouge nombre de bits utiles du flux Rouge
|
||||
* @param nombreBitsVert nombre de bits utiles du flux Vert
|
||||
* @param nombreBitsBleu nombre de bits utiles du flux Bleu
|
||||
* @param octetsRouge flux compressé Rouge
|
||||
* @param octetsVert flux compressé Vert
|
||||
* @param octetsBleu flux compressé Bleu
|
||||
* @throws IOException en cas d'erreur d'écriture
|
||||
*/
|
||||
|
||||
public static void ecrire(String cheminSortie, PIFImage image, Map<Integer, String> codesRouge, Map<Integer, String> codesVert, Map<Integer, String> codesBleu, int nombreBitsRouge, int nombreBitsVert, int nombreBitsBleu, byte[] octetsRouge, byte[] octetsVert, byte[] octetsBleu) throws IOException {
|
||||
|
||||
DataOutputStream sortie = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(cheminSortie)));
|
||||
|
||||
try {
|
||||
// --- En-tête ---
|
||||
sortie.writeInt(SIGNATURE_PIF);
|
||||
sortie.writeInt(image.getLargeur());
|
||||
sortie.writeInt(image.getHauteur());
|
||||
|
||||
// --- Tables canoniques (ordre : Rouge, Vert, Bleu) ---
|
||||
ecrireTableCanonique(sortie, codesRouge);
|
||||
ecrireTableCanonique(sortie, codesVert);
|
||||
ecrireTableCanonique(sortie, codesBleu);
|
||||
|
||||
// --- Flux compressés (ordre : Rouge, Vert, Bleu) ---
|
||||
ecrireFlux(sortie, nombreBitsRouge, octetsRouge);
|
||||
ecrireFlux(sortie, nombreBitsVert, octetsVert);
|
||||
ecrireFlux(sortie, nombreBitsBleu, octetsBleu);
|
||||
|
||||
sortie.flush();
|
||||
|
||||
} finally {
|
||||
sortie.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Écrit une table canonique sous forme :
|
||||
* <ul>
|
||||
* <li>int : nombre d'entrées</li>
|
||||
* <li>pour chaque entrée : unsigned byte valeur (0..255), unsigned byte longueur</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Le lecteur pourra reconstruire les codes canoniques en triant par
|
||||
* (longueur puis valeur), puis en générant les codes.</p>
|
||||
*
|
||||
* @param sortie flux de sortie
|
||||
* @param tableCodes table valeur -> code (le code sert uniquement à connaître la longueur)
|
||||
* @throws IOException si erreur d'écriture
|
||||
*/
|
||||
private static void ecrireTableCanonique(DataOutputStream sortie, Map<Integer, String> tableCodes) throws IOException {
|
||||
|
||||
sortie.writeInt(tableCodes.size());
|
||||
|
||||
for (Map.Entry<Integer, String> entree : tableCodes.entrySet()) {
|
||||
|
||||
int valeur = entree.getKey().intValue();
|
||||
int longueur = entree.getValue().length();
|
||||
|
||||
// Valeur doit être 0..255 (RGB)
|
||||
if (valeur < 0 || valeur > 255) {
|
||||
throw new IOException("Valeur RGB invalide dans la table canonique : " + valeur);
|
||||
}
|
||||
|
||||
// Longueur tient largement dans 1 octet pour ce projet
|
||||
if (longueur < 1 || longueur > 255) {
|
||||
throw new IOException("Longueur de code invalide : " + longueur);
|
||||
}
|
||||
|
||||
sortie.writeByte(valeur); // 1 octet
|
||||
sortie.writeByte(longueur); // 1 octet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Écrit un flux compressé sous forme :
|
||||
* <ul>
|
||||
* <li>int : nbBits utiles</li>
|
||||
* <li>int : nbOctets</li>
|
||||
* <li>byte[] : données</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param sortie flux de sortie
|
||||
* @param nbBits nombre de bits utiles
|
||||
* @param octets tableau d'octets compressés
|
||||
* @throws IOException si erreur
|
||||
*/
|
||||
private static void ecrireFlux(DataOutputStream sortie, int nbBits, byte[] octets) throws IOException {
|
||||
sortie.writeInt(nbBits);
|
||||
sortie.writeInt(octets.length);
|
||||
sortie.write(octets);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
/**
|
||||
* Représente une entrée de la table des codes canoniques.
|
||||
* Une entrée associe une valeur à la longueur de son code binaire.
|
||||
*
|
||||
* Cette classe est utilisée pour trier les symboles avant
|
||||
* la génération des codes canoniques.
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class EntreeCanonique implements Comparable<EntreeCanonique> {
|
||||
|
||||
/** Valeur du symbole */
|
||||
private int valeur;
|
||||
|
||||
/** Longueur du code associé */
|
||||
private int longueur;
|
||||
|
||||
/**
|
||||
* Construit une entrée canonique.
|
||||
*
|
||||
* @param valeur valeur du symbole
|
||||
* @param longueur longueur du code Huffman
|
||||
*/
|
||||
public EntreeCanonique(int valeur, int longueur) {
|
||||
this.valeur = valeur;
|
||||
this.longueur = longueur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la valeur du symbole.
|
||||
*
|
||||
* @return valeur du symbole
|
||||
*/
|
||||
public int getValeur() {
|
||||
return this.valeur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la longueur du code.
|
||||
*
|
||||
* @return longueur du code
|
||||
*/
|
||||
public int getLongueur() {
|
||||
return this.longueur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare deux entrées canoniques :
|
||||
* - d'abord par longueur de code
|
||||
* - puis par valeur si les longueurs sont identiques
|
||||
*
|
||||
* @param autre autre entrée à comparer
|
||||
* @return ordre de comparaison
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(EntreeCanonique autre) {
|
||||
if (this.longueur < autre.longueur) {
|
||||
return -1;
|
||||
} else if (this.longueur > autre.longueur) {
|
||||
return 1;
|
||||
} else {
|
||||
return Integer.compare(this.valeur, autre.valeur);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
/**
|
||||
* Cette classe représente une association entre :<br>
|
||||
* - une valeur (ex : un niveau de couleur)<br>
|
||||
* - la longueur du code binaire qui lui est associée
|
||||
* <br>
|
||||
* Elle est utilisée lors de la reconstruction des codes canoniques,
|
||||
* quand on connaît uniquement les longueurs des codes.
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class EntreeLongueur implements Comparable<EntreeLongueur> {
|
||||
|
||||
/**
|
||||
* Valeur associée au code (ex : intensité d’un pixel).
|
||||
*/
|
||||
private int valeur;
|
||||
|
||||
/**
|
||||
* Longueur du code binaire associé à la valeur.
|
||||
*/
|
||||
private int longueur;
|
||||
|
||||
/**
|
||||
* Construit une entrée avec une valeur et la longueur de son code.
|
||||
*
|
||||
* @param valeur la valeur représentée
|
||||
* @param longueur la longueur du code binaire
|
||||
*/
|
||||
public EntreeLongueur(int valeur, int longueur) {
|
||||
this.valeur = valeur;
|
||||
this.longueur = longueur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la valeur associée.
|
||||
*
|
||||
* @return la valeur
|
||||
*/
|
||||
public int getValeur() {
|
||||
return this.valeur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la longueur du code binaire.
|
||||
*
|
||||
* @return la longueur du code
|
||||
*/
|
||||
public int getLongueur() {
|
||||
return this.longueur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare deux entrées pour les trier.
|
||||
* - d’abord par longueur de code
|
||||
* - puis par valeur si les longueurs sont identiques
|
||||
*
|
||||
* @param autre l’autre entrée à comparer
|
||||
* @return un entier négatif, nul ou positif selon l’ordre
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(EntreeLongueur autre) {
|
||||
if (this.longueur < autre.longueur) {
|
||||
return -1;
|
||||
} else {
|
||||
if (this.longueur > autre.longueur) {
|
||||
return 1;
|
||||
} else {
|
||||
if (this.valeur < autre.valeur) {
|
||||
return -1;
|
||||
} else {
|
||||
if (this.valeur > autre.valeur) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.JTextArea;
|
||||
|
||||
/**
|
||||
* Fenêtre graphique du convertisseur PIF.
|
||||
*
|
||||
* <p>
|
||||
* Cette fenêtre affiche :
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>à gauche : l’image à convertir (redimensionnée si nécessaire),</li>
|
||||
* <li>à droite : les tables de fréquences, de codes Huffman
|
||||
* et de codes canoniques, organisées sous forme d’onglets.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Cette classe est responsable uniquement de l’affichage graphique.
|
||||
* Elle ne réalise aucun calcul de compression.
|
||||
* </p>
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class FenetreConvertisseur extends JFrame {
|
||||
|
||||
/**
|
||||
* Construit la fenêtre du convertisseur et initialise
|
||||
* tous les composants graphiques.
|
||||
*
|
||||
* @param image image à convertir
|
||||
* @param hR objet Huffman associé à la composante rouge
|
||||
* @param hV objet Huffman associé à la composante verte
|
||||
* @param hB objet Huffman associé à la composante bleue
|
||||
*/
|
||||
|
||||
public FenetreConvertisseur(PIFImage image, Huffman hR, Huffman hV, Huffman hB) {
|
||||
|
||||
super("Convertisseur PIF");
|
||||
|
||||
/* =============================
|
||||
* Image affichée à gauche
|
||||
* ============================= */
|
||||
|
||||
// Construction de l'image RGB puis réduction si nécessaire
|
||||
Image imageSwing = OutilsImageSwing.reduireSiNecessaire(OutilsImageSwing.construireImageRGB(image));
|
||||
|
||||
JLabel etiquetteImage = new JLabel(new ImageIcon(imageSwing));
|
||||
|
||||
/* =============================
|
||||
* Onglets à droite
|
||||
* ============================= */
|
||||
|
||||
JTabbedPane ongletsPrincipaux = new JTabbedPane();
|
||||
|
||||
/* ----- Onglet Fréquences ----- */
|
||||
JTabbedPane ongletsFrequences = new JTabbedPane();
|
||||
ongletsFrequences.add("Rouge",creerZoneTexte(FormateurTexte.frequences(hR.getTableFrequences(),"Fréquences - Rouge")));
|
||||
ongletsFrequences.add("Vert",creerZoneTexte(FormateurTexte.frequences(hV.getTableFrequences(),"Fréquences - Vert")));
|
||||
ongletsFrequences.add("Bleu",creerZoneTexte(FormateurTexte.frequences(hB.getTableFrequences(),"Fréquences - Bleu")));
|
||||
ongletsPrincipaux.add("Fréquences", ongletsFrequences);
|
||||
|
||||
/* ----- Onglet Codes Huffman ----- */
|
||||
JTabbedPane ongletsHuffman = new JTabbedPane();
|
||||
ongletsHuffman.add("Rouge",creerZoneTexte(FormateurTexte.codes(hR.getTableCodesHuffman(),"Codes Huffman - Rouge")));
|
||||
ongletsHuffman.add("Vert",creerZoneTexte(FormateurTexte.codes(hV.getTableCodesHuffman(),"Codes Huffman - Vert")));
|
||||
ongletsHuffman.add("Bleu",creerZoneTexte(FormateurTexte.codes(hB.getTableCodesHuffman(),"Codes Huffman - Bleu")));
|
||||
ongletsPrincipaux.add("Codes Huffman", ongletsHuffman);
|
||||
|
||||
/* ----- Onglet Codes canoniques ----- */
|
||||
JTabbedPane ongletsCanoniques = new JTabbedPane();
|
||||
ongletsCanoniques.add("Rouge",creerZoneTexte(FormateurTexte.codes(hR.getTableCodesCanoniques(),"Codes canoniques - Rouge")));
|
||||
ongletsCanoniques.add("Vert",creerZoneTexte(FormateurTexte.codes(hV.getTableCodesCanoniques(),"Codes canoniques - Vert")));
|
||||
ongletsCanoniques.add("Bleu",creerZoneTexte(FormateurTexte.codes(hB.getTableCodesCanoniques(),"Codes canoniques - Bleu")));
|
||||
ongletsPrincipaux.add("Codes canoniques", ongletsCanoniques);
|
||||
|
||||
/* =============================
|
||||
* Séparation gauche / droite
|
||||
* ============================= */
|
||||
|
||||
JPanel panneauImage = new JPanel(new BorderLayout());
|
||||
panneauImage.add(etiquetteImage, BorderLayout.CENTER);
|
||||
|
||||
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,panneauImage,ongletsPrincipaux);
|
||||
split.setResizeWeight(0.7);
|
||||
split.setDividerLocation(800);
|
||||
|
||||
/* =============================
|
||||
* Paramètres de la fenêtre
|
||||
* ============================= */
|
||||
|
||||
this.setLayout(new BorderLayout());
|
||||
this.add(split, BorderLayout.CENTER);
|
||||
|
||||
this.setSize(1200, 800);
|
||||
this.setLocationRelativeTo(null);
|
||||
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
this.setResizable(true);
|
||||
this.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une zone de texte non modifiable pour afficher des tableaux.
|
||||
*
|
||||
* <p>
|
||||
* Une police monospace est utilisée afin d’aligner correctement
|
||||
* les colonnes (valeur → fréquence ou valeur → code).
|
||||
* </p>
|
||||
*
|
||||
* @param texte contenu à afficher
|
||||
* @return un composant JScrollPane contenant la zone de texte
|
||||
*/
|
||||
|
||||
private JScrollPane creerZoneTexte(String texte) {
|
||||
|
||||
JTextArea zone = new JTextArea();
|
||||
zone.setEditable(false);
|
||||
zone.setFont(new Font("Monospaced", Font.PLAIN, 12));
|
||||
zone.setText(texte);
|
||||
zone.setCaretPosition(0);
|
||||
|
||||
return new JScrollPane(zone);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
/**
|
||||
* La classe <code>FenetreVisualisateur</code> permet d'afficher une image
|
||||
* représentée par un objet <code>PIFImage</code> dans une fenêtre graphique.
|
||||
* <p>
|
||||
* La taille initiale de la fenêtre est adaptée à la taille de l'image, tout en
|
||||
* restant dans une taille maximale raisonnable (pour éviter de dépasser l'écran).
|
||||
* La fenêtre est redimensionnable.
|
||||
* <p>
|
||||
* Lorsque l'image est plus petite que la fenêtre, elle est centrée.
|
||||
* Lorsque l'image est plus grande que la fenêtre, elle est partiellement visible
|
||||
* et la portion visible peut être déplacée à la souris en maintenant le bouton
|
||||
* gauche enfoncé.
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class FenetreVisualisateur{
|
||||
|
||||
/**
|
||||
* Affiche une image dans une fenêtre graphique.
|
||||
*
|
||||
* @param image l'image à afficher
|
||||
* @param titre le titre de la fenêtre
|
||||
*/
|
||||
public static void afficher(PIFImage image, String titre) {
|
||||
|
||||
int largeurImage = image.getLargeur();
|
||||
int hauteurImage = image.getHauteur();
|
||||
|
||||
BufferedImage imageBuffer = new BufferedImage(largeurImage, hauteurImage, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
imageBuffer.setRGB(0, 0, largeurImage, hauteurImage, image.getPixels(), 0, largeurImage);
|
||||
|
||||
JLabel etiquetteImage = new JLabel(new ImageIcon(imageBuffer));
|
||||
|
||||
// Conteneur centré : si la fenêtre est plus grande que l'image, l'image reste au centre
|
||||
JPanel conteneurCentre = new JPanel(new GridBagLayout());
|
||||
GridBagConstraints contraintes = new GridBagConstraints();
|
||||
contraintes.gridx = 0;
|
||||
contraintes.gridy = 0;
|
||||
conteneurCentre.add(etiquetteImage, contraintes);
|
||||
|
||||
JScrollPane panneauDefilement = new JScrollPane(conteneurCentre);
|
||||
|
||||
// Gestion du drag au clic gauche
|
||||
ControleurVisualisateur controleur = new ControleurVisualisateur(panneauDefilement, etiquetteImage);
|
||||
|
||||
etiquetteImage.addMouseListener(controleur);
|
||||
etiquetteImage.addMouseMotionListener(controleur);
|
||||
|
||||
JFrame fenetre = new JFrame(titre);
|
||||
fenetre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
fenetre.setContentPane(panneauDefilement);
|
||||
|
||||
fenetre.pack();
|
||||
|
||||
// Taille maximum raisonnable : 90% de l'écran
|
||||
Dimension ecran = Toolkit.getDefaultToolkit().getScreenSize();
|
||||
int largeurMax = (int) (ecran.width * 0.90);
|
||||
int hauteurMax = (int) (ecran.height * 0.90);
|
||||
|
||||
if (fenetre.getWidth() > largeurMax || fenetre.getHeight() > hauteurMax) {
|
||||
fenetre.setSize(Math.min(fenetre.getWidth(), largeurMax), Math.min(fenetre.getHeight(), hauteurMax));
|
||||
} else {
|
||||
// rien
|
||||
}
|
||||
|
||||
fenetre.setVisible(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Classe utilitaire chargée de générer des représentations textuelles
|
||||
* des différentes tables utilisées dans le projet.
|
||||
*
|
||||
* <p>
|
||||
* Cette classe permet de transformer :
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>une table de fréquences (valeur → fréquence),</li>
|
||||
* <li>une table de codes (valeur → code binaire),</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* en chaînes de caractères lisibles, destinées à être affichées
|
||||
* dans l’interface graphique (zones de texte Swing).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Elle ne contient aucune logique de compression ou de calcul,
|
||||
* uniquement de la mise en forme.
|
||||
* </p>
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class FormateurTexte {
|
||||
|
||||
/**
|
||||
* Génère une représentation textuelle d’une table de fréquences.
|
||||
*
|
||||
* <p>
|
||||
* Chaque ligne associe une valeur (par exemple une composante RGB)
|
||||
* à son nombre d’occurrences dans l’image.
|
||||
* </p>
|
||||
*
|
||||
* @param tableFrequences table associant une valeur à sa fréquence
|
||||
* @param titre titre affiché en en-tête du tableau
|
||||
* @return une chaîne de caractères formatée
|
||||
*/
|
||||
public static String frequences(Map<Integer, Integer> tableFrequences, String titre) {
|
||||
|
||||
String texte = "";
|
||||
texte = texte + "=== " + titre + " ===\n";
|
||||
texte = texte + "Nombre de symboles distincts : " + tableFrequences.size() + "\n";
|
||||
texte = texte + "\n";
|
||||
texte = texte + "Valeur -> Frequence\n";
|
||||
texte = texte + "-------------------\n";
|
||||
|
||||
for (Map.Entry<Integer, Integer> entree : tableFrequences.entrySet()) {
|
||||
texte = texte + entree.getKey() + " -> " + entree.getValue() + "\n";
|
||||
}
|
||||
|
||||
return texte;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère une représentation textuelle d’une table de codes binaires.
|
||||
*
|
||||
* <p>
|
||||
* Cette méthode est utilisée aussi bien pour :
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>les codes Huffman initiaux,</li>
|
||||
* <li>les codes canoniques.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Chaque ligne associe une valeur à son code binaire.
|
||||
* </p>
|
||||
*
|
||||
* @param tableCodes table associant une valeur à un code binaire
|
||||
* @param titre titre affiché en en-tête du tableau
|
||||
* @return une chaîne de caractères formatée
|
||||
*/
|
||||
public static String codes(Map<Integer, String> tableCodes, String titre) {
|
||||
|
||||
String texte = "";
|
||||
texte = texte + "=== " + titre + " ===\n";
|
||||
texte = texte + "Nombre de codes : " + tableCodes.size() + "\n";
|
||||
texte = texte + "\n";
|
||||
texte = texte + "Valeur -> Code\n";
|
||||
texte = texte + "--------------\n";
|
||||
|
||||
for (Map.Entry<Integer, String> entree : tableCodes.entrySet()) {
|
||||
texte = texte + entree.getKey() + " -> " + entree.getValue() + "\n";
|
||||
}
|
||||
|
||||
return texte;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import javax.swing.JFileChooser;
|
||||
|
||||
/**
|
||||
* Classe utilitaire chargée de gérer le nom et l’emplacement
|
||||
* du fichier de sortie au format PIF.
|
||||
*
|
||||
* Elle permet de générer automatiquement un chemin de sortie
|
||||
* cohérent à partir du fichier d’entrée.
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class GestionSortiePIF {
|
||||
|
||||
/**
|
||||
* Vérifie qu’un chemin se termine bien par l’extension ".pif".
|
||||
* Si ce n’est pas le cas, l’extension est ajoutée.
|
||||
*
|
||||
* @param chemin chemin fourni par l’utilisateur
|
||||
* @return chemin garanti avec l’extension ".pif"
|
||||
*/
|
||||
public static String forcerExtensionPIF(String chemin) {
|
||||
|
||||
if (chemin == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// On nettoie juste le nom final (pas tout le chemin !)
|
||||
File f = new File(chemin);
|
||||
String parent = f.getParent();
|
||||
String nom = f.getName();
|
||||
|
||||
nom = nettoyerNomFichier(nom);
|
||||
|
||||
String min = nom.toLowerCase();
|
||||
if (!min.endsWith(".pif")) {
|
||||
nom = nom + ".pif";
|
||||
}
|
||||
|
||||
if (parent == null) {
|
||||
return nom;
|
||||
} else {
|
||||
return new File(parent, nom).getPath();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ouvre un JFileChooser pour demander à l'utilisateur où enregistrer le fichier PIF.
|
||||
* On force ensuite l’extension .pif.
|
||||
*
|
||||
* @param nomParDefaut nom proposé (ex: image.pif)
|
||||
* @return chemin complet choisi, ou null si annulé
|
||||
*/
|
||||
public static String choisirCheminSortieAvecFenetre(String nomParDefaut) {
|
||||
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
chooser.setDialogTitle("Choisir l'emplacement du fichier PIF");
|
||||
|
||||
if (nomParDefaut != null) {
|
||||
chooser.setSelectedFile(new File(nomParDefaut));
|
||||
}
|
||||
|
||||
int resultat = chooser.showSaveDialog(null);
|
||||
|
||||
if (resultat != JFileChooser.APPROVE_OPTION) {
|
||||
return null;
|
||||
}
|
||||
|
||||
File fichier = chooser.getSelectedFile();
|
||||
String chemin = fichier.getPath();
|
||||
|
||||
return forcerExtensionPIF(chemin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit automatiquement un chemin de sortie pour un fichier PIF
|
||||
* dans le dossier <code>res/sortie</code> du projet.
|
||||
*
|
||||
* <p>
|
||||
* Le fichier de sortie porte le même nom que le fichier image d’entrée,
|
||||
* avec l’extension <code>.pif</code>.
|
||||
* Les espaces présents dans le nom du fichier sont remplacés
|
||||
* par des underscores afin d’éviter tout problème de lecture.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Exemple :
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Image d’entrée : <code>mon image test.png</code></li>
|
||||
* <li>Fichier créé : <code>res/sortie/mon_image_test.pif</code></li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Si le dossier <code>res</code> ou <code>res/sortie</code> n’existe pas,
|
||||
* il est créé automatiquement.
|
||||
* </p>
|
||||
*
|
||||
* @param fichierEntree fichier image sélectionné en entrée
|
||||
* @return chemin complet du fichier de sortie au format PIF
|
||||
*/
|
||||
public static String cheminSortieAutoDansResSortie(File fichierEntree) {
|
||||
|
||||
// Dossier res/output
|
||||
File dossierRes = new File("res");
|
||||
File dossierOutput = new File(dossierRes, "sortie");
|
||||
|
||||
// Création si nécessaire
|
||||
if (!dossierOutput.exists()) {
|
||||
dossierOutput.mkdirs();
|
||||
}
|
||||
|
||||
// Nom du fichier sans extension
|
||||
String nom = fichierEntree.getName();
|
||||
int point = nom.lastIndexOf('.');
|
||||
if (point != -1) {
|
||||
nom = nom.substring(0, point);
|
||||
}
|
||||
|
||||
// Suppression des espaces
|
||||
nom = nettoyerNomFichier(nom);
|
||||
|
||||
File sortie = new File(dossierOutput, nom + ".pif");
|
||||
return sortie.getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remplace les espaces (et autres caractères gênants) dans un nom de fichier.
|
||||
* Ici on remplace les espaces par des '_' pour éviter les soucis en ligne de commande.
|
||||
*
|
||||
* @param nom nom de fichier (sans dossier)
|
||||
* @return nom nettoyé
|
||||
*/
|
||||
public static String nettoyerNomFichier(String nom) {
|
||||
|
||||
if (nom == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
nom = nom.trim();
|
||||
|
||||
// Remplace les espaces par underscore
|
||||
nom = nom.replace(' ', '_');
|
||||
|
||||
// Optionnel : si tu veux aussi éviter les tabulations
|
||||
nom = nom.replace('\t', '_');
|
||||
|
||||
return nom;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.PriorityQueue;
|
||||
|
||||
/**
|
||||
* La classe <code>Huffman</code> implémente l'algorithme de compression
|
||||
* de Huffman appliqué aux valeurs des pixels d'une image.
|
||||
* <p>
|
||||
* Elle permet de :
|
||||
* <ul>
|
||||
* <li>analyser les pixels afin de construire une table de fréquences,</li>
|
||||
* <li>construire un arbre binaire de Huffman à partir de ces fréquences,</li>
|
||||
* <li>générer les codes binaires associés à chaque valeur,</li>
|
||||
* <li>transformer ces codes en codes canoniques,</li>
|
||||
* <li>encoder les pixels sous forme d’un flux binaire compressé.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Cette classe est utilisée par le programme de conversion afin de
|
||||
* produire les données compressées stockées dans le fichier PIF.
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class Huffman {
|
||||
|
||||
private Map<Integer, String> tableCodesHuffman;
|
||||
private Map<Integer, String> tableCodesCanoniques;
|
||||
|
||||
|
||||
/**
|
||||
* Table associant chaque valeur de pixel à sa fréquence d'apparition.
|
||||
*/
|
||||
private Map<Integer, Integer> tableFrequences;
|
||||
|
||||
/**
|
||||
* Table associant chaque valeur de pixel à son code binaire de Huffman.
|
||||
*/
|
||||
private Map<Integer, String> tableCodes;
|
||||
|
||||
/**
|
||||
* Racine de l'arbre de Huffman.
|
||||
*/
|
||||
private Noeud racine;
|
||||
|
||||
/**
|
||||
* Construit un objet Huffman vide.
|
||||
* <p>
|
||||
* Les structures internes seront initialisées lors des différentes
|
||||
* étapes de construction.
|
||||
*/
|
||||
public Huffman() {
|
||||
this.tableCodesHuffman = new HashMap<Integer, String>();
|
||||
this.tableCodesCanoniques = new HashMap<Integer, String>();
|
||||
|
||||
tableFrequences = new HashMap<Integer, Integer>();
|
||||
tableCodes = new HashMap<Integer, String>();
|
||||
racine = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit la table des fréquences à partir des valeurs des pixels.
|
||||
*
|
||||
* Chaque valeur rencontrée dans le tableau est comptée afin de connaître
|
||||
* le nombre d'occurrences de chaque symbole.
|
||||
* Cette table est utilisée ensuite pour construire l'arbre de Huffman.
|
||||
*
|
||||
* @param pixels tableau contenant les valeurs à analyser
|
||||
*/
|
||||
|
||||
public void construireTableFrequences(int[] pixels) {
|
||||
tableFrequences.clear();
|
||||
|
||||
for (int valeur : pixels) {
|
||||
if (tableFrequences.containsKey(valeur)) {
|
||||
tableFrequences.put(valeur, tableFrequences.get(valeur) + 1);
|
||||
} else {
|
||||
tableFrequences.put(valeur, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit l'arbre de Huffman à partir de la table des fréquences.
|
||||
*
|
||||
* Les nœuds sont organisés dans une file de priorité afin de toujours
|
||||
* combiner les deux nœuds de plus faible fréquence.
|
||||
* Le résultat est un arbre binaire optimisé pour le codage Huffman.
|
||||
*/
|
||||
|
||||
public void construireArbre() {
|
||||
|
||||
PriorityQueue<Noeud> file = new PriorityQueue<Noeud>();
|
||||
|
||||
for (Map.Entry<Integer, Integer> entree : tableFrequences.entrySet()) {
|
||||
file.add(new NoeudFeuille(entree.getKey(), entree.getValue()));
|
||||
}
|
||||
|
||||
if (file.size() == 1) {
|
||||
Noeud unique = file.poll();
|
||||
racine = new NoeudInterne(unique, null);
|
||||
return;
|
||||
}
|
||||
|
||||
while (file.size() > 1) {
|
||||
Noeud gauche = file.poll();
|
||||
Noeud droite = file.poll();
|
||||
|
||||
NoeudInterne parent = new NoeudInterne(gauche, droite);
|
||||
file.add(parent);
|
||||
}
|
||||
|
||||
racine = file.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit la table des codes Huffman à partir de l'arbre.
|
||||
*
|
||||
* Cette méthode lance le parcours récursif de l'arbre et enregistre
|
||||
* les codes binaires associés à chaque valeur.
|
||||
* Une copie est conservée afin de garder la version Huffman initiale.
|
||||
*/
|
||||
|
||||
public void construireTableCodes() {
|
||||
tableCodes.clear();
|
||||
construireCodesRecursif(racine, "");
|
||||
|
||||
tableCodesHuffman.clear();
|
||||
tableCodesHuffman.putAll(tableCodes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transforme la table de codes Huffman en table de codes canoniques.
|
||||
*/
|
||||
public void rendreCodesCanoniques() {
|
||||
tableCodesCanoniques =
|
||||
TableCodesCanoniques.construire(tableCodesHuffman);
|
||||
|
||||
// on encode avec les canoniques
|
||||
tableCodes = tableCodesCanoniques;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renvoie la table des fréquences (valeur -> fréquence).
|
||||
*
|
||||
* @return la table des fréquences
|
||||
*/
|
||||
public java.util.Map<Integer, Integer> getTableFrequences() {
|
||||
return this.tableFrequences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la table des codes actuellement utilisée pour l'encodage.
|
||||
*
|
||||
* Selon l'étape du traitement, il peut s'agir des codes Huffman
|
||||
* ou des codes canoniques.
|
||||
*
|
||||
* @return table associant chaque valeur à son code binaire
|
||||
*/
|
||||
|
||||
public java.util.Map<Integer, String> getTableCodes() {
|
||||
return this.tableCodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parcourt récursivement l'arbre de Huffman pour générer les codes binaires.
|
||||
*
|
||||
* Lorsqu'un nœud feuille est atteint, le code binaire accumulé correspond
|
||||
* à la valeur stockée dans cette feuille.
|
||||
*
|
||||
* @param noeud nœud courant de l'arbre
|
||||
* @param codeActuel code binaire construit jusqu'à ce nœud
|
||||
*/
|
||||
|
||||
private void construireCodesRecursif(Noeud noeud, String codeActuel) {
|
||||
|
||||
if (noeud == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (noeud instanceof NoeudFeuille) {
|
||||
NoeudFeuille feuille = (NoeudFeuille) noeud;
|
||||
|
||||
String codeFinal;
|
||||
if (codeActuel.equals("")) {
|
||||
codeFinal = "0";
|
||||
} else {
|
||||
codeFinal = codeActuel;
|
||||
}
|
||||
|
||||
tableCodes.put(feuille.valeur, codeFinal);
|
||||
return;
|
||||
}
|
||||
|
||||
NoeudInterne interne = (NoeudInterne) noeud;
|
||||
|
||||
if (interne.gauche != null) {
|
||||
construireCodesRecursif(interne.gauche, codeActuel + "0");
|
||||
}
|
||||
|
||||
if (interne.droite != null) {
|
||||
construireCodesRecursif(interne.droite, codeActuel + "1");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode un tableau de pixels en une suite d’octets en utilisant les codes de Huffman.
|
||||
* <p>
|
||||
* Chaque pixel est remplacé par son code binaire associé, puis les bits sont
|
||||
* regroupés dans un tableau d’octets.
|
||||
*
|
||||
* @param pixels le tableau des pixels à encoder
|
||||
* @param nombreBits tableau de taille 1 permettant de stocker le nombre total de bits encodés
|
||||
* @return un tableau d’octets contenant les données compressées
|
||||
*/
|
||||
public byte[] encoderPixelsEnOctets(int[] pixels, int[] nombreBits) {
|
||||
|
||||
/**
|
||||
* Capacité initiale du tableau d’octets.
|
||||
* On prend une estimation large pour éviter des redimensionnements trop fréquents.
|
||||
*/
|
||||
int capacite = pixels.length * 4;
|
||||
byte[] octets = new byte[capacite];
|
||||
|
||||
/**
|
||||
* Index de l’octet courant dans le tableau.
|
||||
*/
|
||||
int indexOctet = 0;
|
||||
|
||||
/**
|
||||
* Position du bit courant dans l’octet (entre 0 et 7).
|
||||
*/
|
||||
int positionBit = 0;
|
||||
|
||||
/**
|
||||
* Compteur du nombre total de bits encodés.
|
||||
*/
|
||||
int totalBits = 0;
|
||||
|
||||
// Parcours de tous les pixels
|
||||
for (int i = 0; i < pixels.length; i = i + 1) {
|
||||
int valeur = pixels[i];
|
||||
|
||||
/**
|
||||
* Code binaire associé à la valeur du pixel.
|
||||
*/
|
||||
String code = tableCodes.get(valeur);
|
||||
|
||||
// Parcours des bits du code binaire
|
||||
for (int j = 0; j < code.length(); j = j + 1) {
|
||||
|
||||
/**
|
||||
* Si le bit courant vaut 1, on positionne le bit correspondant
|
||||
* dans l’octet courant.
|
||||
*/
|
||||
if (code.charAt(j) == '1') {
|
||||
octets[indexOctet] =
|
||||
(byte) (octets[indexOctet] | (1 << (7 - positionBit)));
|
||||
}
|
||||
|
||||
positionBit = positionBit + 1;
|
||||
totalBits = totalBits + 1;
|
||||
|
||||
/**
|
||||
* Lorsque l’octet est rempli (8 bits),
|
||||
* on passe à l’octet suivant.
|
||||
*/
|
||||
if (positionBit == 8) {
|
||||
positionBit = 0;
|
||||
indexOctet = indexOctet + 1;
|
||||
|
||||
/**
|
||||
* Si le tableau d’octets est plein,
|
||||
* on l’agrandit en doublant sa taille.
|
||||
*/
|
||||
if (indexOctet == octets.length) {
|
||||
byte[] nouveau = new byte[octets.length * 2];
|
||||
for (int k = 0; k < octets.length; k = k + 1) {
|
||||
nouveau[k] = octets[k];
|
||||
}
|
||||
octets = nouveau;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Si le dernier octet n’est pas complètement rempli,
|
||||
* on le compte tout de même comme valide.
|
||||
*/
|
||||
if (positionBit != 0) {
|
||||
indexOctet = indexOctet + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Création du tableau final contenant uniquement
|
||||
* les octets réellement utilisés.
|
||||
*/
|
||||
byte[] resultat = new byte[indexOctet];
|
||||
for (int i = 0; i < indexOctet; i = i + 1) {
|
||||
resultat[i] = octets[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Stockage du nombre total de bits encodés.
|
||||
*/
|
||||
nombreBits[0] = totalBits;
|
||||
|
||||
return resultat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la table des codes Huffman initiaux,
|
||||
* construite directement à partir de l'arbre de Huffman
|
||||
* (avant la transformation canonique).
|
||||
*
|
||||
* @return table des codes Huffman (valeur -> code binaire)
|
||||
*/
|
||||
public Map<Integer, String> getTableCodesHuffman() {
|
||||
return tableCodesHuffman;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la table des codes canoniques,
|
||||
* utilisée pour l'encodage final dans le fichier PIF.
|
||||
*
|
||||
* @return table des codes canoniques (valeur -> code binaire)
|
||||
*/
|
||||
public Map<Integer, String> getTableCodesCanoniques() {
|
||||
return tableCodesCanoniques;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* La classe <code>LecteurPIF</code> permet de lire un fichier <code>.pif</code>
|
||||
* et de reconstruire l'image originale.
|
||||
*
|
||||
* <p>Le format PIF contient :</p>
|
||||
* <ul>
|
||||
* <li>une signature qui identifie le format,</li>
|
||||
* <li>la largeur et la hauteur de l'image,</li>
|
||||
* <li>trois tables de codes canoniques (Rouge, Vert, Bleu) stockées sous forme (valeur, longueur),</li>
|
||||
* <li>trois flux compressés correspondant aux composantes Rouge, Vert et Bleu.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Les codes canoniques sont reconstruits uniquement à partir des longueurs
|
||||
* puis un arbre de décodage est construit pour décompresser chaque flux.</p>
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class LecteurPIF {
|
||||
|
||||
/** Signature "PIF" en int (0x50 0x49 0x46 0x32). */
|
||||
private static final int SIGNATURE_PIF = 0x50494632;
|
||||
|
||||
/**
|
||||
* Lit un fichier PIF et reconstruit l'image en mémoire.
|
||||
*
|
||||
* <p>Étapes principales :</p>
|
||||
* <ol>
|
||||
* <li>vérification de la signature,</li>
|
||||
* <li>lecture des dimensions,</li>
|
||||
* <li>lecture des 3 tables canoniques, puis reconstruction des codes,</li>
|
||||
* <li>construction des arbres de décodage,</li>
|
||||
* <li>décodage des 3 flux compressés (R,V,B),</li>
|
||||
* <li>recomposition des pixels RGB.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param chemin chemin du fichier <code>.pif</code>
|
||||
* @return l'image reconstruite
|
||||
* @throws IOException si le fichier ne peut pas être lu ou si le format est invalide
|
||||
*/
|
||||
|
||||
public static PIFImage lire(String chemin) throws IOException {
|
||||
|
||||
|
||||
DataInputStream entree = new DataInputStream(new BufferedInputStream(new FileInputStream(chemin)));
|
||||
|
||||
try {
|
||||
int signature = entree.readInt();
|
||||
if (signature != SIGNATURE_PIF) {
|
||||
throw new IOException("Format PIF non reconnu (signature incorrecte).");
|
||||
}
|
||||
|
||||
int largeur = entree.readInt();
|
||||
int hauteur = entree.readInt();
|
||||
int nbPixels = largeur * hauteur;
|
||||
|
||||
// 1) Lire les 3 tables canoniques (valeur + longueur) puis reconstruire les codes
|
||||
Map<Integer, String> codesR = lireTableCanonique(entree);
|
||||
Map<Integer, String> codesV = lireTableCanonique(entree);
|
||||
Map<Integer, String> codesB = lireTableCanonique(entree);
|
||||
|
||||
// 2) Construire 3 arbres de décodage
|
||||
NoeudDecodage racineR = construireArbreDecodage(codesR);
|
||||
NoeudDecodage racineV = construireArbreDecodage(codesV);
|
||||
NoeudDecodage racineB = construireArbreDecodage(codesB);
|
||||
|
||||
// 3) Lire et décoder 3 flux compressés
|
||||
int[] rouge = decoderFlux(entree, racineR, nbPixels);
|
||||
int[] vert = decoderFlux(entree, racineV, nbPixels);
|
||||
int[] bleu = decoderFlux(entree, racineB, nbPixels);
|
||||
|
||||
// 4) Recomposer les pixels RGB
|
||||
int[] pixels = new int[nbPixels];
|
||||
for (int i = 0; i < nbPixels; i = i + 1) {
|
||||
int r = rouge[i] & 0xFF;
|
||||
int v = vert[i] & 0xFF;
|
||||
int b = bleu[i] & 0xFF;
|
||||
|
||||
pixels[i] = (0xFF << 24) | (r << 16) | (v << 8) | b;
|
||||
}
|
||||
|
||||
return new PIFImage(largeur, hauteur, pixels);
|
||||
|
||||
} finally {
|
||||
entree.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lit une table canonique dans le fichier PIF.
|
||||
*
|
||||
* <p>Dans le fichier, la table est stockée sous forme :</p>
|
||||
* <ul>
|
||||
* <li>int : nombre d'entrées</li>
|
||||
* <li>pour chaque entrée : (valeur, longueur) sur 2 octets</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>À partir des longueurs, on reconstruit les codes canoniques en :</p>
|
||||
* <ol>
|
||||
* <li>triant par longueur puis par valeur,</li>
|
||||
* <li>générant les codes binaires dans l'ordre canonique.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param entree flux d'entrée
|
||||
* @return table des codes canoniques (valeur -> code binaire)
|
||||
* @throws IOException si une lecture échoue
|
||||
*/
|
||||
|
||||
private static Map<Integer, String> lireTableCanonique(DataInputStream entree) throws IOException{
|
||||
|
||||
|
||||
int nbEntrees = entree.readInt();
|
||||
List<EntreeLongueur> liste = new ArrayList<EntreeLongueur>();
|
||||
|
||||
for (int i = 0; i < nbEntrees; i = i + 1) {
|
||||
int valeur = entree.readUnsignedByte();
|
||||
int longueur = entree.readUnsignedByte();
|
||||
liste.add(new EntreeLongueur(valeur, longueur));
|
||||
}
|
||||
|
||||
// Trier comme demandé : longueur puis valeur
|
||||
Collections.sort(liste);
|
||||
|
||||
// Reconstruire les codes canoniques
|
||||
Map<Integer, String> table = new HashMap<Integer, String>();
|
||||
|
||||
int code = 0;
|
||||
int longueurPrecedente = 0;
|
||||
|
||||
for (int i = 0; i < liste.size(); i = i + 1) {
|
||||
EntreeLongueur e = liste.get(i);
|
||||
|
||||
if (i == 0) {
|
||||
// premier code : tout à zéro avec la bonne longueur
|
||||
longueurPrecedente = e.getLongueur();
|
||||
code = 0;
|
||||
} else {
|
||||
// on incrémente puis on ajuste la longueur
|
||||
code = code + 1;
|
||||
if (e.getLongueur() > longueurPrecedente) {
|
||||
code = code << (e.getLongueur() - longueurPrecedente);
|
||||
longueurPrecedente = e.getLongueur();
|
||||
}
|
||||
}
|
||||
|
||||
String binaire = Integer.toBinaryString(code);
|
||||
while (binaire.length() < e.getLongueur()) {
|
||||
binaire = "0" + binaire;
|
||||
}
|
||||
|
||||
table.put(Integer.valueOf(e.getValeur()), binaire);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit un arbre binaire de décodage à partir d'une table de codes.
|
||||
*
|
||||
* <p>Chaque code binaire est inséré dans l'arbre :
|
||||
* 0 signifie "aller à gauche" et 1 signifie "aller à droite".</p>
|
||||
*
|
||||
* @param tableCodes table (valeur -> code binaire)
|
||||
* @return racine de l'arbre de décodage
|
||||
*/
|
||||
|
||||
private static NoeudDecodage construireArbreDecodage(Map<Integer, String> tableCodes) {
|
||||
|
||||
NoeudDecodage racine = new NoeudDecodage();
|
||||
|
||||
for (Map.Entry<Integer, String> entree : tableCodes.entrySet()) {
|
||||
int valeur = entree.getKey().intValue();
|
||||
String code = entree.getValue();
|
||||
insererCode(racine, code, valeur);
|
||||
}
|
||||
|
||||
return racine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insère un code binaire dans l'arbre de décodage.
|
||||
*
|
||||
* <p>À la fin du parcours, le nœud atteint devient une feuille
|
||||
* et on y stocke la valeur associée.</p>
|
||||
*
|
||||
* @param racine racine de l'arbre
|
||||
* @param code chaîne binaire composée de '0' et '1'
|
||||
* @param valeur valeur associée au code
|
||||
*/
|
||||
|
||||
private static void insererCode(NoeudDecodage racine, String code, int valeur) {
|
||||
|
||||
NoeudDecodage courant = racine;
|
||||
|
||||
for (int i = 0; i < code.length(); i = i + 1) {
|
||||
char bit = code.charAt(i);
|
||||
|
||||
if (bit == '0') {
|
||||
if (courant.getGauche() == null) {
|
||||
courant.setGauche(new NoeudDecodage());
|
||||
}
|
||||
courant = courant.getGauche();
|
||||
} else { // '1'
|
||||
if (courant.getDroite() == null) {
|
||||
courant.setDroite(new NoeudDecodage());
|
||||
}
|
||||
courant = courant.getDroite();
|
||||
}
|
||||
}
|
||||
|
||||
courant.setEstFeuille(true);
|
||||
courant.setValeur(valeur);
|
||||
}
|
||||
|
||||
/**
|
||||
* Décode un flux compressé avec Huffman à l'aide d'un arbre de décodage.
|
||||
*
|
||||
* <p>Le flux est stocké sous forme :</p>
|
||||
* <ul>
|
||||
* <li>int : nbBits utiles</li>
|
||||
* <li>int : nbOctets</li>
|
||||
* <li>byte[] : données</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>On lit les bits un par un, on traverse l'arbre, et à chaque feuille
|
||||
* on produit une valeur décodée.</p>
|
||||
*
|
||||
* @param entree flux d'entrée
|
||||
* @param racine racine de l'arbre de décodage
|
||||
* @param nbValeurs nombre de valeurs attendues (souvent largeur * hauteur)
|
||||
* @return tableau de valeurs décodées
|
||||
* @throws IOException si le flux est invalide ou incomplet
|
||||
*/
|
||||
|
||||
private static int[] decoderFlux(DataInputStream entree, NoeudDecodage racine, int nbValeurs) throws IOException {
|
||||
|
||||
int nbBits = entree.readInt();
|
||||
int nbOctets = entree.readInt();
|
||||
|
||||
byte[] octets = new byte[nbOctets];
|
||||
entree.readFully(octets);
|
||||
|
||||
int[] resultat = new int[nbValeurs];
|
||||
|
||||
int indexValeur = 0;
|
||||
int indexBit = 0;
|
||||
|
||||
NoeudDecodage courant = racine;
|
||||
|
||||
while (indexBit < nbBits && indexValeur < nbValeurs) {
|
||||
|
||||
int bit = lireBit(octets, indexBit);
|
||||
indexBit = indexBit + 1;
|
||||
|
||||
if (bit == 0) {
|
||||
courant = courant.getGauche();
|
||||
} else {
|
||||
courant = courant.getDroite();
|
||||
}
|
||||
|
||||
if (courant == null) {
|
||||
throw new IOException("Flux invalide : parcours Huffman impossible (arbre incomplet).");
|
||||
}
|
||||
|
||||
if (courant.isEstFeuille()) {
|
||||
resultat[indexValeur] = courant.getValeur();
|
||||
indexValeur = indexValeur + 1;
|
||||
courant = racine;
|
||||
}
|
||||
}
|
||||
|
||||
if (indexValeur != nbValeurs) {
|
||||
throw new IOException("Flux invalide : pas assez de valeurs décodées (" + indexValeur + "/" + nbValeurs + ").");
|
||||
}
|
||||
|
||||
return resultat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lit un bit dans un tableau d'octets.
|
||||
* Le bit 0 correspond au bit de poids fort du premier octet.
|
||||
*
|
||||
* @param octets tableau d'octets
|
||||
* @param position position du bit à lire (à partir de 0)
|
||||
* @return 0 ou 1 selon la valeur du bit
|
||||
*/
|
||||
|
||||
private static int lireBit(byte[] octets, int position) {
|
||||
|
||||
int indexOctet = position / 8;
|
||||
int positionDansOctet = position % 8;
|
||||
|
||||
int masque = 1 << (7 - positionDansOctet);
|
||||
|
||||
if ((octets[indexOctet] & masque) != 0) {
|
||||
return 1;
|
||||
|
||||
} else {
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Le programme <code>MainConvertisseur</code> permet de convertir une image
|
||||
* classique (PNG, JPG, etc.) en un fichier au format <code>.pif</code>.
|
||||
*
|
||||
* Le programme réalise les étapes suivantes :
|
||||
* <ul>
|
||||
* <li>sélection d’un fichier image (argument ou JFileChooser),</li>
|
||||
* <li>lecture de l’image et récupération des pixels,</li>
|
||||
* <li>séparation des composantes Rouge / Vert / Bleu,</li>
|
||||
* <li>compression Huffman séparée sur chaque composante,</li>
|
||||
* <li>génération des codes canoniques,</li>
|
||||
* <li>affichage d’une fenêtre Swing (image + tables),</li>
|
||||
* <li>écriture du fichier PIF dans un chemin de sortie.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class MainConvertisseur {
|
||||
|
||||
/**
|
||||
* Point d’entrée du convertisseur PIF.
|
||||
* <p>
|
||||
* Si un chemin d’image est fourni en argument, il est utilisé comme fichier
|
||||
* d’entrée. Sinon, une fenêtre de sélection de fichier est ouverte.
|
||||
*
|
||||
* Le fichier PIF de sortie est automatiquement créé dans le dossier
|
||||
* <code>res</code> du projet, qui est généré s’il n’existe pas.
|
||||
*
|
||||
* @param args arguments de la ligne de commande
|
||||
*/
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
try {
|
||||
/* ===============================
|
||||
* 1) Sélection du fichier d'entrée
|
||||
* =============================== */
|
||||
File fichierEntree = SelecteurFichier.getFichier(args);
|
||||
|
||||
if (fichierEntree == null) {
|
||||
System.out.println("Aucun fichier sélectionné.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* ===============================
|
||||
* 2) Chargement de l'image en mémoire
|
||||
* =============================== */
|
||||
PIFImage image = PIFImage.chargerDepuisImage(fichierEntree.getAbsolutePath());
|
||||
|
||||
System.out.println("Image chargée : " + fichierEntree.getName());
|
||||
System.out.println("Dimensions : " + image.getLargeur() + " x " + image.getHauteur());
|
||||
|
||||
/* ===============================
|
||||
* 3) Extraction des composantes RGB
|
||||
* ===============================
|
||||
* On sépare les pixels en 3 tableaux :
|
||||
* - un tableau pour le Rouge (0..255)
|
||||
* - un tableau pour le Vert (0..255)
|
||||
* - un tableau pour le Bleu (0..255)
|
||||
*/
|
||||
int[] pixels = image.getPixels();
|
||||
|
||||
int[] pixelsRouge = CodageRGB.extraireRouge(pixels);
|
||||
int[] pixelsVert = CodageRGB.extraireVert(pixels);
|
||||
int[] pixelsBleu = CodageRGB.extraireBleu(pixels);
|
||||
|
||||
/* ===============================
|
||||
* 4) Compression Huffman (R, V, B)
|
||||
* ===============================
|
||||
* Chaque composante est compressée séparément :
|
||||
* - table de fréquences
|
||||
* - arbre de Huffman
|
||||
* - codes Huffman
|
||||
* - transformation en codes canoniques
|
||||
*/
|
||||
Huffman huffmanRouge = new Huffman();
|
||||
Huffman huffmanVert = new Huffman();
|
||||
Huffman huffmanBleu = new Huffman();
|
||||
|
||||
huffmanRouge.construireTableFrequences(pixelsRouge);
|
||||
huffmanVert.construireTableFrequences(pixelsVert);
|
||||
huffmanBleu.construireTableFrequences(pixelsBleu);
|
||||
|
||||
huffmanRouge.construireArbre();
|
||||
huffmanVert.construireArbre();
|
||||
huffmanBleu.construireArbre();
|
||||
|
||||
huffmanRouge.construireTableCodes();
|
||||
huffmanVert.construireTableCodes();
|
||||
huffmanBleu.construireTableCodes();
|
||||
|
||||
huffmanRouge.rendreCodesCanoniques();
|
||||
huffmanVert.rendreCodesCanoniques();
|
||||
huffmanBleu.rendreCodesCanoniques();
|
||||
|
||||
/* ===============================
|
||||
* 5) Affichage des informations (Swing)
|
||||
* ===============================
|
||||
* La fenêtre montre :
|
||||
* - l'image à convertir
|
||||
* - la table des fréquences
|
||||
* - la table Huffman initiale
|
||||
* - la table canonique finale
|
||||
*/
|
||||
new FenetreConvertisseur(image, huffmanRouge, huffmanVert, huffmanBleu);
|
||||
|
||||
/* ===============================
|
||||
* 6) Détermination du chemin de sortie
|
||||
* ===============================
|
||||
* - si l'utilisateur donne args[1], on l'utilise (et on force .pif)
|
||||
* - sinon on génère automatiquement un chemin dans le dossier res/
|
||||
*/
|
||||
String cheminSortie;
|
||||
|
||||
if (args.length >= 2) {
|
||||
// Sortie imposée par l'utilisateur
|
||||
cheminSortie = GestionSortiePIF.forcerExtensionPIF(args[1]);
|
||||
|
||||
} else if (args.length == 1) {
|
||||
// 1 seul argument → sortie auto dans res/output/
|
||||
cheminSortie = GestionSortiePIF.cheminSortieAutoDansResSortie(fichierEntree);
|
||||
|
||||
} else {
|
||||
// Aucun argument → on demande où enregistrer
|
||||
String nomBase = fichierEntree.getName();
|
||||
int point = nomBase.lastIndexOf('.');
|
||||
if (point != -1) {
|
||||
nomBase = nomBase.substring(0, point);
|
||||
}
|
||||
|
||||
cheminSortie = GestionSortiePIF.choisirCheminSortieAvecFenetre(nomBase + ".pif");
|
||||
|
||||
if (cheminSortie == null) {
|
||||
System.out.println("Enregistrement annulé.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ===============================
|
||||
* 7) Encodage des composantes en octets
|
||||
* ===============================
|
||||
* Chaque composante produit :
|
||||
* - un tableau d’octets compressé
|
||||
* - un nombre total de bits utiles (pour ignorer le padding)
|
||||
*/
|
||||
int[] nbBitsR = new int[1];
|
||||
int[] nbBitsV = new int[1];
|
||||
int[] nbBitsB = new int[1];
|
||||
|
||||
byte[] octetsR = huffmanRouge.encoderPixelsEnOctets(pixelsRouge, nbBitsR);
|
||||
byte[] octetsV = huffmanVert.encoderPixelsEnOctets(pixelsVert, nbBitsV);
|
||||
byte[] octetsB = huffmanBleu.encoderPixelsEnOctets(pixelsBleu, nbBitsB);
|
||||
|
||||
/* ===============================
|
||||
* 8) Écriture du fichier PIF
|
||||
* ===============================
|
||||
* On écrit :
|
||||
* - les dimensions
|
||||
* - les tables canoniques (R, V, B) sous forme (valeur, longueur)
|
||||
* - les 3 flux compressés (bits + octets)
|
||||
*/
|
||||
EcrivainPIF.ecrire(cheminSortie, image, huffmanRouge.getTableCodesCanoniques(), huffmanVert.getTableCodesCanoniques(), huffmanBleu.getTableCodesCanoniques(), nbBitsR[0], nbBitsV[0], nbBitsB[0], octetsR, octetsV, octetsB);
|
||||
|
||||
System.out.println("Conversion terminée : " + cheminSortie);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Erreur lors de la conversion : " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Le programme <code>MainVisualisateur</code> permet de lire un fichier
|
||||
* au format <code>.pif</code>, de le décoder à l’aide du lecteur PIF,
|
||||
* puis d’afficher l’image reconstruite dans une fenêtre graphique.
|
||||
*
|
||||
* Ce programme correspond à la partie "visualisation" du projet,
|
||||
* indépendante de la compression.
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class MainVisualisateur {
|
||||
|
||||
/**
|
||||
* Point d’entrée du visualisateur PIF.
|
||||
* <p>
|
||||
* Si un chemin de fichier est fourni en argument, il est utilisé.
|
||||
* Sinon, une fenêtre de sélection de fichier est ouverte.
|
||||
*
|
||||
* @param args arguments de la ligne de commande
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
try {
|
||||
/* ===============================
|
||||
* Sélection du fichier .pif
|
||||
* =============================== */
|
||||
File fichier = SelecteurFichier.getFichier(args);
|
||||
|
||||
if (fichier == null) {
|
||||
System.out.println("Aucun fichier selectionne.");
|
||||
return;
|
||||
}
|
||||
|
||||
String chemin = fichier.getAbsolutePath();
|
||||
System.out.println("Lecture du fichier : " + fichier.getName());
|
||||
|
||||
/* ===============================
|
||||
* Lecture et décodage du fichier PIF
|
||||
* =============================== */
|
||||
PIFImage image = LecteurPIF.lire(chemin);
|
||||
|
||||
System.out.println("Image decodée");
|
||||
System.out.println("Dimensions : " + image.getLargeur() + " x " + image.getHauteur());
|
||||
|
||||
/* ===============================
|
||||
* Affichage de l'image reconstruite
|
||||
* =============================== */
|
||||
FenetreVisualisateur.afficher(image,"Visualisateur PIF - " + fichier.getName());
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println("Erreur lors du chargement du fichier PIF :");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
/**
|
||||
* La classe abstraite <code>Noeud</code> représente un nœud générique
|
||||
* de l’arbre de Huffman.
|
||||
*
|
||||
* <p>Chaque nœud possède une fréquence d’apparition, utilisée lors de la
|
||||
* construction de l’arbre afin d’ordonner les nœuds.</p>
|
||||
*
|
||||
* <p>Cette classe est la super-classe de :</p>
|
||||
* <ul>
|
||||
* <li>{@link NoeudFeuille} : nœud contenant une valeur réelle (pixel)</li>
|
||||
* <li>{@link NoeudInterne} : nœud intermédiaire reliant deux sous-arbres</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Les nœuds sont comparables entre eux afin de pouvoir être stockés
|
||||
* dans une file de priorité lors de l’algorithme de Huffman.</p>
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public abstract class Noeud implements Comparable<Noeud> {
|
||||
|
||||
/**
|
||||
* Fréquence d’apparition associée au nœud.
|
||||
*/
|
||||
protected int frequence;
|
||||
|
||||
/**
|
||||
* Construit un nœud avec une fréquence donnée.
|
||||
*
|
||||
* <p>Si la fréquence fournie est négative, elle est ramenée à 0
|
||||
* afin de garantir une valeur valide.</p>
|
||||
*
|
||||
* @param frequence fréquence associée au nœud
|
||||
*/
|
||||
public Noeud(int frequence) {
|
||||
if (frequence >= 0) {
|
||||
this.frequence = frequence;
|
||||
} else {
|
||||
this.frequence = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare deux nœuds selon leur fréquence.
|
||||
*
|
||||
* <p>Cette méthode permet d’ordonner les nœuds dans une file de priorité,
|
||||
* ce qui est indispensable pour la construction de l’arbre de Huffman.</p>
|
||||
*
|
||||
* @param autre le nœud à comparer
|
||||
* @return un entier négatif, nul ou positif selon l’ordre des fréquences
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(Noeud autre) {
|
||||
if (this.frequence < autre.frequence) {
|
||||
return -1;
|
||||
} else if (this.frequence > autre.frequence) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
/**
|
||||
* Représente un nœud d’un arbre de décodage Huffman.
|
||||
*
|
||||
* <p>Cet arbre est utilisé lors de la lecture d’un fichier PIF afin de
|
||||
* décoder un flux binaire et de reconstruire les valeurs originales
|
||||
* des composantes RGB.</p>
|
||||
*
|
||||
* <p>Un nœud peut être :</p>
|
||||
* <ul>
|
||||
* <li>un nœud interne, possédant un fils gauche et/ou un fils droit ;</li>
|
||||
* <li>une feuille, contenant une valeur décodée.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class NoeudDecodage {
|
||||
|
||||
/**
|
||||
* Fils gauche du nœud.
|
||||
* <p>Correspond au bit 0 lors du décodage.</p>
|
||||
*/
|
||||
private NoeudDecodage gauche;
|
||||
|
||||
/**
|
||||
* Fils droit du nœud.
|
||||
* <p>Correspond au bit 1 lors du décodage.</p>
|
||||
*/
|
||||
private NoeudDecodage droite;
|
||||
|
||||
/**
|
||||
* Valeur associée au nœud.
|
||||
* <p>Cette valeur n’a de sens que si le nœud est une feuille.</p>
|
||||
*/
|
||||
private int valeur;
|
||||
|
||||
/**
|
||||
* Indique si le nœud est une feuille de l’arbre.
|
||||
*/
|
||||
private boolean estFeuille;
|
||||
|
||||
/**
|
||||
* Construit un nœud de décodage vide.
|
||||
*
|
||||
* <p>Par défaut, le nœud :</p>
|
||||
* <ul>
|
||||
* <li>n’a pas de fils,</li>
|
||||
* <li>ne contient aucune valeur significative,</li>
|
||||
* <li>n’est pas une feuille.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public NoeudDecodage() {
|
||||
this.gauche = null;
|
||||
this.droite = null;
|
||||
this.valeur = 0;
|
||||
this.estFeuille = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le fils gauche du nœud.
|
||||
*
|
||||
* @return le nœud gauche, ou {@code null} s’il n’existe pas
|
||||
*/
|
||||
public NoeudDecodage getGauche() {
|
||||
return this.gauche;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit le fils gauche du nœud.
|
||||
*
|
||||
* @param gauche nœud à placer à gauche
|
||||
*/
|
||||
public void setGauche(NoeudDecodage gauche) {
|
||||
this.gauche = gauche;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le fils droit du nœud.
|
||||
*
|
||||
* @return le nœud droit, ou {@code null} s’il n’existe pas
|
||||
*/
|
||||
public NoeudDecodage getDroite() {
|
||||
return this.droite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit le fils droit du nœud.
|
||||
*
|
||||
* @param droite nœud à placer à droite
|
||||
*/
|
||||
public void setDroite(NoeudDecodage droite) {
|
||||
this.droite = droite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la valeur stockée dans le nœud.
|
||||
*
|
||||
* @return la valeur décodée
|
||||
*/
|
||||
public int getValeur() {
|
||||
return this.valeur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit la valeur stockée dans le nœud.
|
||||
*
|
||||
* @param valeur valeur décodée à associer au nœud
|
||||
*/
|
||||
public void setValeur(int valeur) {
|
||||
this.valeur = valeur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indique si le nœud est une feuille.
|
||||
*
|
||||
* @return {@code true} si le nœud est une feuille, {@code false} sinon
|
||||
*/
|
||||
public boolean isEstFeuille() {
|
||||
return this.estFeuille;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit si le nœud est une feuille.
|
||||
*
|
||||
* @param estFeuille {@code true} pour marquer le nœud comme feuille
|
||||
*/
|
||||
public void setEstFeuille(boolean estFeuille) {
|
||||
this.estFeuille = estFeuille;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
/**
|
||||
* La classe <code>NoeudFeuille</code> représente une feuille
|
||||
* dans un arbre de Huffman.
|
||||
*
|
||||
* <p>Une feuille correspond à une valeur concrète (par exemple
|
||||
* une composante de pixel) associée à une fréquence d'apparition.
|
||||
* Elle ne possède pas d'enfants.</p>
|
||||
*
|
||||
* <p>Les feuilles sont utilisées comme points de départ lors
|
||||
* de la construction de l'arbre de Huffman.</p>
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class NoeudFeuille extends Noeud {
|
||||
|
||||
/**
|
||||
* Valeur portée par la feuille.
|
||||
* Il s'agit du symbole à encoder (par exemple une valeur RGB).
|
||||
*/
|
||||
protected int valeur;
|
||||
|
||||
/**
|
||||
* Construit une feuille de l'arbre de Huffman.
|
||||
*
|
||||
* <p>La fréquence représente le nombre d'occurrences de la valeur
|
||||
* dans les données à compresser.</p>
|
||||
*
|
||||
* @param valeur valeur du symbole (pixel ou composante)
|
||||
* @param frequence fréquence d'apparition de cette valeur
|
||||
*/
|
||||
public NoeudFeuille(int valeur, int frequence) {
|
||||
super(0);
|
||||
|
||||
if (frequence >= 0) {
|
||||
this.frequence = frequence;
|
||||
} else {
|
||||
this.frequence = 0;
|
||||
}
|
||||
|
||||
this.valeur = valeur;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
/**
|
||||
* La classe <code>NoeudInterne</code> représente un nœud interne
|
||||
* de l'arbre de Huffman.
|
||||
* <p>
|
||||
* Un nœud interne relie deux sous-arbres (gauche et droit) et sa
|
||||
* fréquence correspond à la somme des fréquences de ses enfants.
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class NoeudInterne extends Noeud {
|
||||
|
||||
/**
|
||||
* Sous-arbre gauche du nœud.
|
||||
*/
|
||||
protected Noeud gauche;
|
||||
|
||||
/**
|
||||
* Sous-arbre droit du nœud.
|
||||
*/
|
||||
protected Noeud droite;
|
||||
|
||||
/**
|
||||
* Construit un nœud interne à partir de deux nœuds enfants.
|
||||
* <p>
|
||||
* La fréquence du nœud interne est calculée comme la somme
|
||||
* des fréquences de ses enfants.
|
||||
*
|
||||
* @param gauche le sous-arbre gauche
|
||||
* @param droite le sous-arbre droit
|
||||
*/
|
||||
|
||||
public NoeudInterne(Noeud gauche, Noeud droite) {
|
||||
super(0);
|
||||
|
||||
if (gauche != null && droite != null) {
|
||||
this.frequence = gauche.frequence + droite.frequence;
|
||||
} else if (gauche != null) {
|
||||
this.frequence = gauche.frequence;
|
||||
} else if (droite != null) {
|
||||
this.frequence = droite.frequence;
|
||||
} else {
|
||||
this.frequence = 0;
|
||||
}
|
||||
|
||||
this.gauche = gauche;
|
||||
this.droite = droite;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Image;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* Classe utilitaire regroupant des méthodes liées à l'affichage
|
||||
* des images dans une interface Swing.
|
||||
*
|
||||
* <p>
|
||||
* Elle permet notamment :
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>de convertir un objet {@link PIFImage} en image RGB affichable,</li>
|
||||
* <li>de redimensionner une image si elle est trop grande pour l’écran.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Cette classe ne contient aucune logique de compression ou de lecture de fichier.
|
||||
* Elle est dédiée uniquement à l’adaptation des images pour l’affichage graphique.
|
||||
* </p>
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class OutilsImageSwing {
|
||||
|
||||
/**
|
||||
* Construit une image RGB affichable par Swing à partir d’un objet {@link PIFImage}.
|
||||
*
|
||||
* <p>
|
||||
* Les composantes rouge, verte et bleue sont extraites de chaque pixel
|
||||
* afin de créer une {@link BufferedImage} compatible avec un {@code ImageIcon}.
|
||||
* </p>
|
||||
*
|
||||
* @param image image en mémoire contenant les pixels ARGB
|
||||
* @return une image RGB utilisable pour l’affichage
|
||||
*/
|
||||
public static BufferedImage construireImageRGB(PIFImage image) {
|
||||
|
||||
int largeur = image.getLargeur();
|
||||
int hauteur = image.getHauteur();
|
||||
int[] pixels = image.getPixels();
|
||||
|
||||
BufferedImage buffer = new BufferedImage(largeur,hauteur,BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
for (int y = 0; y < hauteur; y = y + 1) {
|
||||
for (int x = 0; x < largeur; x = x + 1) {
|
||||
|
||||
int pixel = pixels[y * largeur + x];
|
||||
|
||||
int rouge = (pixel >> 16) & 0xFF;
|
||||
int vert = (pixel >> 8) & 0xFF;
|
||||
int bleu = pixel & 0xFF;
|
||||
|
||||
int rgb = (rouge << 16) | (vert << 8) | bleu;
|
||||
buffer.setRGB(x, y, rgb);
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Réduit une image si ses dimensions dépassent la moitié
|
||||
* de la taille de l’écran.
|
||||
*
|
||||
* <p>
|
||||
* Le redimensionnement conserve les proportions de l’image
|
||||
* afin d’éviter toute déformation.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Si l’image est déjà de taille raisonnable, elle est retournée
|
||||
* sans modification.
|
||||
* </p>
|
||||
*
|
||||
* @param img image à vérifier et éventuellement redimensionner
|
||||
* @return image affichable, redimensionnée si nécessaire
|
||||
*/
|
||||
public static Image reduireSiNecessaire(BufferedImage img) {
|
||||
|
||||
int largeur = img.getWidth();
|
||||
int hauteur = img.getHeight();
|
||||
|
||||
Dimension ecran = Toolkit.getDefaultToolkit().getScreenSize();
|
||||
int maxLargeur = ecran.width / 2;
|
||||
int maxHauteur = ecran.height / 2;
|
||||
|
||||
// Si l'image tient déjà dans l'écran, on ne la modifie pas
|
||||
if (largeur <= maxLargeur && hauteur <= maxHauteur) {
|
||||
return img;
|
||||
}
|
||||
|
||||
// Calcul du facteur de réduction en conservant les proportions
|
||||
double facteurLargeur = (double) maxLargeur / largeur;
|
||||
double facteurHauteur = (double) maxHauteur / hauteur;
|
||||
double facteur = Math.min(facteurLargeur, facteurHauteur);
|
||||
|
||||
int nouvelleLargeur = (int) (largeur * facteur);
|
||||
int nouvelleHauteur = (int) (hauteur * facteur);
|
||||
|
||||
return img.getScaledInstance(nouvelleLargeur, nouvelleHauteur,Image.SCALE_SMOOTH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
/**
|
||||
* La classe <code>PIFImage</code> représente une image en mémoire
|
||||
* à l'aide de ses dimensions et d'un tableau de pixels.
|
||||
*
|
||||
* <p>Elle constitue la structure centrale du projet pour manipuler
|
||||
* les images, aussi bien lors de la compression que lors de la
|
||||
* décompression.</p>
|
||||
*
|
||||
* <p>Les pixels sont stockés sous forme d'entiers.</p>
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class PIFImage {
|
||||
|
||||
/**
|
||||
* Largeur de l'image (en nombre de pixels).
|
||||
*/
|
||||
private int largeur;
|
||||
|
||||
/**
|
||||
* Hauteur de l'image (en nombre de pixels).
|
||||
*/
|
||||
private int hauteur;
|
||||
|
||||
/**
|
||||
* Tableau contenant les pixels de l'image.
|
||||
* Chaque pixel est codé sous la forme d'un entier.
|
||||
*/
|
||||
private int[] pixels;
|
||||
|
||||
/**
|
||||
* Construit une image à partir de ses dimensions et de son tableau de pixels.
|
||||
*
|
||||
* @param largeur largeur de l'image
|
||||
* @param hauteur hauteur de l'image
|
||||
* @param pixels tableau de pixels
|
||||
*/
|
||||
public PIFImage(int largeur, int hauteur, int[] pixels) {
|
||||
this.largeur = largeur;
|
||||
this.hauteur = hauteur;
|
||||
this.pixels = pixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la largeur de l'image.
|
||||
*
|
||||
* @return la largeur en pixels
|
||||
*/
|
||||
public int getLargeur() {
|
||||
return largeur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la hauteur de l'image.
|
||||
*
|
||||
* @return la hauteur en pixels
|
||||
*/
|
||||
public int getHauteur() {
|
||||
return hauteur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le tableau des pixels de l'image.
|
||||
*
|
||||
* @return tableau des pixels au format ARGB
|
||||
*/
|
||||
public int[] getPixels() {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge une image classique (PNG, JPG, etc.) depuis le disque
|
||||
* et la convertit en un objet <code>PIFImage</code>.
|
||||
*
|
||||
* <p>Cette méthode utilise la classe <code>ImageIO</code> pour lire
|
||||
* l'image, puis extrait tous les pixels dans un tableau.</p>
|
||||
*
|
||||
* @param cheminImage chemin du fichier image à charger
|
||||
* @return une instance de <code>PIFImage</code> correspondant à l'image
|
||||
* @throws IOException si le fichier ne peut pas être lu ou si le format
|
||||
* n'est pas supporté
|
||||
*/
|
||||
public static PIFImage chargerDepuisImage(String cheminImage) throws IOException {
|
||||
|
||||
BufferedImage imageChargee = ImageIO.read(new File(cheminImage));
|
||||
|
||||
if (imageChargee == null) {
|
||||
throw new IOException("Image illisible ou format non supporté : " + cheminImage);
|
||||
}
|
||||
|
||||
int largeurImage = imageChargee.getWidth();
|
||||
int hauteurImage = imageChargee.getHeight();
|
||||
|
||||
int[] pixelsImage = new int[largeurImage * hauteurImage];
|
||||
imageChargee.getRGB(0, 0, largeurImage, hauteurImage, pixelsImage, 0, largeurImage);
|
||||
|
||||
return new PIFImage(largeurImage, hauteurImage, pixelsImage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.io.File;
|
||||
import javax.swing.JFileChooser;
|
||||
|
||||
/**
|
||||
* La classe <code>SelecteurFichier</code> permet de choisir un fichier
|
||||
* soit à partir des arguments de la ligne de commande,
|
||||
* soit à l'aide d'une fenêtre graphique.
|
||||
*
|
||||
* <p>Elle est utilisée par les classes principales du projet
|
||||
* afin de centraliser la logique de sélection de fichiers
|
||||
* et d'éviter de dupliquer ce code.</p>
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class SelecteurFichier {
|
||||
|
||||
/**
|
||||
* Retourne un fichier sélectionné par l'utilisateur.
|
||||
*
|
||||
* <p>Deux cas sont possibles :</p>
|
||||
* <ul>
|
||||
* <li>Si un chemin est fourni en argument, ce fichier est utilisé.</li>
|
||||
* <li>Sinon, une fenêtre graphique s'ouvre pour permettre à
|
||||
* l'utilisateur de choisir un fichier.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param args arguments de la ligne de commande
|
||||
* @return le fichier sélectionné, ou <code>null</code> si aucun fichier
|
||||
* valide n'a été choisi
|
||||
*/
|
||||
public static File getFichier(String[] args) {
|
||||
|
||||
File fichier = null;
|
||||
|
||||
// Cas où un chemin est fourni en argument
|
||||
if (args.length > 0) {
|
||||
fichier = new File(args[0]);
|
||||
|
||||
if (!fichier.exists() || !fichier.isFile()) {
|
||||
System.err.println("Le chemin fourni n'est pas valide : " + args[0]);
|
||||
return null;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Cas où aucun argument n'est fourni : ouverture d'une fenêtre
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
chooser.setDialogTitle("Sélectionnez un fichier");
|
||||
|
||||
int resultat = chooser.showOpenDialog(null);
|
||||
|
||||
if (resultat == JFileChooser.APPROVE_OPTION) {
|
||||
fichier = chooser.getSelectedFile();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return fichier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package fr.iutfbleau.pif;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* La classe <code>TableCodesCanoniques</code> sert à fabriquer une table
|
||||
* de codes canoniques à partir d'une table de codes Huffman.
|
||||
*
|
||||
* <p>Idée :</p>
|
||||
* <ul>
|
||||
* <li>Avec Huffman, chaque valeur (0..255) possède un code binaire.</li>
|
||||
* <li>Pour passer en canonique, on ne garde pas les bits exacts,
|
||||
* seulement la <strong>longueur</strong> de chaque code.</li>
|
||||
* <li>Ensuite, on recrée des codes binaires dans un ordre fixe :
|
||||
* d'abord par longueur, puis par valeur.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Intérêt :</p>
|
||||
* <ul>
|
||||
* <li>La table est plus simple à stocker (valeur + longueur).</li>
|
||||
* <li>Le lecteur peut reconstruire exactement les mêmes codes
|
||||
* sans avoir besoin de stocker tous les bits.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Aylane Sehl
|
||||
* @author Jenson Val
|
||||
* @author Séri-khane Yolou
|
||||
*/
|
||||
|
||||
public class TableCodesCanoniques {
|
||||
|
||||
/**
|
||||
* Construit une table de codes canoniques (valeur -> code binaire)
|
||||
* à partir d'une table Huffman (valeur -> code binaire).
|
||||
*
|
||||
* <p>Étapes :</p>
|
||||
* <ol>
|
||||
* <li>On récupère, pour chaque valeur, la longueur de son code Huffman.</li>
|
||||
* <li>On trie les entrées par longueur puis par valeur.</li>
|
||||
* <li>On attribue des codes canoniques dans cet ordre
|
||||
* (en incrémentant et en décalant quand la longueur augmente).</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param tableHuffman table des codes Huffman initiaux (valeur -> code binaire)
|
||||
* @return table des codes canoniques (valeur -> code binaire)
|
||||
*/
|
||||
|
||||
public static Map<Integer, String> construire(Map<Integer, String> tableHuffman) {
|
||||
|
||||
List<EntreeCanonique> entrees = new ArrayList<EntreeCanonique>();
|
||||
|
||||
// 1) On conserve valeur + longueur du code
|
||||
for (Map.Entry<Integer, String> entree : tableHuffman.entrySet()) {
|
||||
entrees.add(new EntreeCanonique(entree.getKey(), entree.getValue().length()));
|
||||
}
|
||||
|
||||
// 2) Tri canonique : longueur puis valeur
|
||||
Collections.sort(entrees);
|
||||
|
||||
// 3) Attribution des codes canoniques
|
||||
Map<Integer, String> tableCanonique = new HashMap<Integer, String>();
|
||||
|
||||
int code = 0;
|
||||
int longueurPrecedente = entrees.get(0).getLongueur();
|
||||
|
||||
for (int i = 0; i < entrees.size(); i++) {
|
||||
|
||||
EntreeCanonique e = entrees.get(i);
|
||||
int longueurActuelle = e.getLongueur();
|
||||
|
||||
// Décalage si la longueur augmente
|
||||
code = code << (longueurActuelle - longueurPrecedente);
|
||||
|
||||
// Conversion en binaire avec padding
|
||||
String binaire = Integer.toBinaryString(code);
|
||||
while (binaire.length() < longueurActuelle) {
|
||||
binaire = "0" + binaire;
|
||||
}
|
||||
|
||||
tableCanonique.put(e.getValeur(), binaire);
|
||||
|
||||
code = code + 1;
|
||||
longueurPrecedente = longueurActuelle;
|
||||
}
|
||||
|
||||
return tableCanonique;
|
||||
}
|
||||
|
||||
}
|
||||