Modifications apportés

This commit is contained in:
ANHDIRE
2026-01-03 13:08:16 +01:00
parent bceb70c052
commit 5cd5f4c044
34 changed files with 2566 additions and 2519 deletions
+192 -192
View File
@@ -1,192 +1,192 @@
# Sprint Planning Projet PIF # Sprint Planning Projet PIF
## Légende ## Légende
AD → Algassimou AD → Algassimou
AA → Ayoub AA → Ayoub
YB → Youness YB → Youness
🟥 TODO = À faire 🟥 TODO = À faire
🟨 DOING = En cours 🟨 DOING = En cours
🟩 DONE = Terminé 🟩 DONE = Terminé
# --------------------------------------- # ---------------------------------------
# SPRINT 1 (1320 décembre 2025) # SPRINT 1 (1320 décembre 2025)
# --------------------------------------- # ---------------------------------------
Objectif : Mise en place des fondations techniques Objectif : Mise en place des fondations techniques
(image, binaire, Huffman, canoniques) (image, binaire, Huffman, canoniques)
| US | Assigné | Statut | | Description | | US | Assigné | Statut | | Description |
|-------|---------|--------|----|----------------------------------------------------| |-------|---------|--------|----|----------------------------------------------------|
| US-D1 | AD | DONE | 🟩 | Implémenter BitInputStream (lecture bit par bit) | | US-D1 | AD | DONE | 🟩 | Implémenter BitInputStream (lecture bit par bit) |
| US-D2 | AD | DONE | 🟩 | Implémenter BitOutputStream (écriture bit par bit) | | US-D2 | AD | DONE | 🟩 | Implémenter BitOutputStream (écriture bit par bit) |
| US-D3 | AD | DONE | 🟩 | Générer les tables de fréquences RGB | | US-D3 | AD | DONE | 🟩 | Générer les tables de fréquences RGB |
| US-D4 | AD | DONE | 🟩 | Construire larbre Huffman | | US-D4 | AD | DONE | 🟩 | Construire larbre Huffman |
| US-D5 | AA | DONE | 🟩 | Générer les codes Huffman | | US-D5 | AA | DONE | 🟩 | Générer les codes Huffman |
| US-D6 | AA | DONE | 🟩 | Générer les codes canoniques | | US-D6 | AA | DONE | 🟩 | Générer les codes canoniques |
| US-U5 | YB | DONE | 🟩 | Chargement dimage via ImageIO | | US-U5 | YB | DONE | 🟩 | Chargement dimage via ImageIO |
| US-D8 | YB | DONE | 🟩 | Structure RGBImage + Pixel | | US-D8 | YB | DONE | 🟩 | Structure RGBImage + Pixel |
| US-P1 | AA | DONE | 🟩 | Interface simple daffichage des fréquences | | US-P1 | AA | DONE | 🟩 | Interface simple daffichage des fréquences |
| US-P2 | AD | DONE | 🟩 | Interface simple daffichage codes Huffman | | US-P2 | AD | DONE | 🟩 | Interface simple daffichage codes Huffman |
| US-P3 | AA | DONE | 🟩 | Interface simple daffichage codes canoniques | | US-P3 | AA | DONE | 🟩 | Interface simple daffichage codes canoniques |
## Fichiers à créer Sprint 1 ## Fichiers à créer Sprint 1
### `src/mimage/` ### `src/mimage/`
| Nom du fichier | Rôle | US | | Nom du fichier | Rôle | US |
|----------------|-------|----| |----------------|-------|----|
| `Pixel.java` | Représente un pixel (r, g, b) | US-D8 | | `Pixel.java` | Représente un pixel (r, g, b) | US-D8 |
| `RGBImage.java` | Matrice de pixels + utilitaires | US-D8, US-U5 | | `RGBImage.java` | Matrice de pixels + utilitaires | US-D8, US-U5 |
### `src/mhuffman/` ### `src/mhuffman/`
| Nom du fichier | Rôle | US | | Nom du fichier | Rôle | US |
|----------------|-------|----| |----------------|-------|----|
| `FrequencyTable.java` | Stocke les fréquences R/G/B | US-D3 | | `FrequencyTable.java` | Stocke les fréquences R/G/B | US-D3 |
| `HuffmanNode.java` | Nœud darbre Huffman | US-D4 | | `HuffmanNode.java` | Nœud darbre Huffman | US-D4 |
| `HuffmanTree.java` | Construction arbre + génération des codes | US-D4, US-D5 | | `HuffmanTree.java` | Construction arbre + génération des codes | US-D4, US-D5 |
| `CanonicalCode.java` | Génération des codes canoniques | US-D6 | | `CanonicalCode.java` | Génération des codes canoniques | US-D6 |
### `src/util/` ### `src/util/`
| Nom du fichier | Rôle | US | | Nom du fichier | Rôle | US |
|----------------|-------|----| |----------------|-------|----|
| `BitInputStream.java` | Lecture bit par bit | US-D1 | | `BitInputStream.java` | Lecture bit par bit | US-D1 |
| `BitOutputStream.java` | Écriture bit par bit | US-D2 | | `BitOutputStream.java` | Écriture bit par bit | US-D2 |
| `ByteUtils.java` | Conversion int octets | US-D3 et plus | | `ByteUtils.java` | Conversion int octets | US-D3 et plus |
| `FileUtils.java` | Méthodes utilitaires fichiers | US-U5 (indirect) | | `FileUtils.java` | Méthodes utilitaires fichiers | US-U5 (indirect) |
### `src/vconverter/` ### `src/vconverter/`
| Nom du fichier | Rôle | US | | Nom du fichier | Rôle | US |
|----------------|-------|----| |----------------|-------|----|
| `ConverterWindow.java` | Fenêtre du convertisseur | US-P1, US-P2, US-P3 | | `ConverterWindow.java` | Fenêtre du convertisseur | US-P1, US-P2, US-P3 |
| `PreviewPanel.java` | Aperçu de limage chargée | US-U5 | | `PreviewPanel.java` | Aperçu de limage chargée | US-U5 |
| `FrequencyTablePanel.java` | Affichage fréquences RGB | US-P1 | | `FrequencyTablePanel.java` | Affichage fréquences RGB | US-P1 |
| `CodeTablePanel.java` | Affichage Huffman + canoniques | US-P2, US-P3 | | `CodeTablePanel.java` | Affichage Huffman + canoniques | US-P2, US-P3 |
### `src/` (racine) ### `src/` (racine)
| Nom du fichier | Rôle | US | | Nom du fichier | Rôle | US |
|----------------|-------|----| |----------------|-------|----|
| `ConverterController.java` | Contrôleur du convertisseur | US-U5, US-D3..D6, US-P1..P3 | | `ConverterController.java` | Contrôleur du convertisseur | US-U5, US-D3..D6, US-P1..P3 |
| `Convertisseur.java` | Lancement du convertisseur | — | | `Convertisseur.java` | Lancement du convertisseur | — |
--- ---
### Résultat attendu Sprint 1 ### Résultat attendu Sprint 1
- Compression entièrement fonctionnelle - Compression entièrement fonctionnelle
- Import dimage opérationnel - Import dimage opérationnel
- GUI minimaliste affichant fréquences / Huffman / canoniques - GUI minimaliste affichant fréquences / Huffman / canoniques
- Aucun fichier `.pif` encore écrit - Aucun fichier `.pif` encore écrit
# --------------------------------------- # ---------------------------------------
# SPRINT 2 (2027 décembre 2025) # SPRINT 2 (2027 décembre 2025)
# --------------------------------------- # ---------------------------------------
Objectif : Écriture du format `.pif` + finalisation convertisseur Objectif : Écriture du format `.pif` + finalisation convertisseur
| US | Assigné | Statut | | Description | | US | Assigné | Statut | | Description |
|------------|---------|--------|-----|-------------| |------------|---------|--------|-----|-------------|
| US-D2 | AA | DONE | 🟩 | Vérifier BitOutputStream avec flux réel | | US-D2 | AA | DONE | 🟩 | Vérifier BitOutputStream avec flux réel |
| US-C5 | AD | DONE | 🟩 | Implémenter PIFWriter (header + tables + pixels compressés) | | US-C5 | AD | DONE | 🟩 | Implémenter PIFWriter (header + tables + pixels compressés) |
| US-U6 | AD | DONE | 🟩 | Exporter une image en `.pif` | | US-U6 | AD | DONE | 🟩 | Exporter une image en `.pif` |
| US-P1 | AA | DONE | 🟩 | Finaliser affichage des fréquences | | US-P1 | AA | DONE | 🟩 | Finaliser affichage des fréquences |
| US-P2 | AA | DONE | 🟩 | Finaliser affichage codes Huffman | | US-P2 | AA | DONE | 🟩 | Finaliser affichage codes Huffman |
| US-P3 | AA | DONE | 🟩 | Finaliser affichage codes canoniques | | US-P3 | AA | DONE | 🟩 | Finaliser affichage codes canoniques |
| US-U7 | AD | DONE | 🟩 | Implémenter laperçu (PreviewPanel) | | US-U7 | AD | DONE | 🟩 | Implémenter laperçu (PreviewPanel) |
## Fichiers à créer Sprint 2 ## Fichiers à créer Sprint 2
### `src/mpif/` ### `src/mpif/`
| Nom du fichier | Rôle | US | | Nom du fichier | Rôle | US |
|----------------|-------|----| |----------------|-------|----|
| `PIFWriter.java` | Écriture du fichier `.pif` | US-C5, US-U6 | | `PIFWriter.java` | Écriture du fichier `.pif` | US-C5, US-U6 |
### `src/vconverter/` (complément) ### `src/vconverter/` (complément)
| Nom du fichier | Rôle | US | | Nom du fichier | Rôle | US |
|----------------|-------|----| |----------------|-------|----|
| `SavePanel.java` en option a voir | Interface de sauvegarde `.pif` | US-U6 | | `SavePanel.java` en option a voir | Interface de sauvegarde `.pif` | US-U6 |
--- ---
### Résultat attendu Sprint 2 ### Résultat attendu Sprint 2
- `.pif` généré correctement - `.pif` généré correctement
- Convertisseur fonctionnel à 80 % - Convertisseur fonctionnel à 80 %
- UI complète pour la consultation des tables - UI complète pour la consultation des tables
- Aperçu image fonctionnel - Aperçu image fonctionnel
# --------------------------------------- # ---------------------------------------
# SPRINT 3 (27 décembre 2025 3 janvier 2026) # SPRINT 3 (27 décembre 2025 3 janvier 2026)
# --------------------------------------- # ---------------------------------------
Objectif : Lecture du fichier `.pif` + visualisateur opérationnel Objectif : Lecture du fichier `.pif` + visualisateur opérationnel
| US | Assigné | Statut | | Description | | US | Assigné | Statut | | Description |
|----------|---------|--------|-----|-------------| |----------|---------|--------|-----|-------------|
| US-D7 | AD | DONE | 🟩 | Reconstruire les codes canoniques depuis fichier | | US-D7 | AD | DONE | 🟩 | Reconstruire les codes canoniques depuis fichier |
| US-D8 | AD | DONE | 🟩 | Décoder pixels (implémenter PIFReader) | | US-D8 | AD | DONE | 🟩 | Décoder pixels (implémenter PIFReader) |
| US-U1 | YB | TODO | 🟥 | Ouvrir `.pif` via argument ou JFileChooser | | US-U1 | YB | TODO | 🟥 | Ouvrir `.pif` via argument ou JFileChooser |
| US-U2 | AA | TODO | 🟥 | Afficher limage dans une fenêtre | | US-U2 | AA | TODO | 🟥 | Afficher limage dans une fenêtre |
| US-U3 | AA | TODO | 🟥 | Centrer limage si elle est petite | | US-U3 | AA | TODO | 🟥 | Centrer limage si elle est petite |
| US-U4 | AD | TODO | 🟥 | Déplacement de limage à la souris | | US-U4 | AD | TODO | 🟥 | Déplacement de limage à la souris |
| US-P6 | AA | TODO | 🟥 | Préparer diagrammes UML | | US-P6 | AA | TODO | 🟥 | Préparer diagrammes UML |
## Fichiers à créer Sprint 3 ## Fichiers à créer Sprint 3
### `src/mpif/` ### `src/mpif/`
| Nom du fichier | Rôle | US | | Nom du fichier | Rôle | US |
|----------------|-------|----| |----------------|-------|----|
| `PIFReader.java` | Lecture et décodage du `.pif` | US-D7, US-D8 | | `PIFReader.java` | Lecture et décodage du `.pif` | US-D7, US-D8 |
### `src/vviewer/` ### `src/vviewer/`
| Nom du fichier | Rôle | US | | Nom du fichier | Rôle | US |
|----------------|-------|----| |----------------|-------|----|
| `ViewerWindow.java` | Fenêtre visualisation | US-U2 | | `ViewerWindow.java` | Fenêtre visualisation | US-U2 |
| `ImagePanel.java` | Affichage + déplacement image | US-U2, US-U3, US-U4 | | `ImagePanel.java` | Affichage + déplacement image | US-U2, US-U3, US-U4 |
### `src/` ### `src/`
| Nom du fichier | Rôle | US | | Nom du fichier | Rôle | US |
|----------------|-------|----| |----------------|-------|----|
| `ViewerController.java` | Contrôle du visualisateur | US-U1..U4 | | `ViewerController.java` | Contrôle du visualisateur | US-U1..U4 |
| `Viewer.java` | Programme principal du visualisateur | US-U1 | | `Viewer.java` | Programme principal du visualisateur | US-U1 |
--- ---
### Résultat attendu Sprint 3 ### Résultat attendu Sprint 3
- Visualisateur 100 % fonctionnel - Visualisateur 100 % fonctionnel
- Lecture complète du format `.pif` - Lecture complète du format `.pif`
- Image affichée, centrée, déplaçable - Image affichée, centrée, déplaçable
- UML structurel prêt pour rapport - UML structurel prêt pour rapport
# --------------------------------------- # ---------------------------------------
# SPRINT 4 (310 janvier 2026) # SPRINT 4 (310 janvier 2026)
# --------------------------------------- # ---------------------------------------
Objectif : Finalisation complète + livrable final Objectif : Finalisation complète + livrable final
| US | Assigné | Statut | | Description | | US | Assigné | Statut | | Description |
|------------|---------|--------|-----|-------------| |------------|---------|--------|-----|-------------|
| US-P5 | AD | TODO | 🟥 | Javadoc complète | | US-P5 | AD | TODO | 🟥 | Javadoc complète |
| US-P7 | AD | TODO | 🟥 | Makefile (compilation + jar + exécution) | | US-P7 | AD | TODO | 🟥 | Makefile (compilation + jar + exécution) |
| US-P6 | AA | TODO | 🟥 | Finalisation des diagrammes UML | | US-P6 | AA | TODO | 🟥 | Finalisation des diagrammes UML |
| Tests | YB | TODO | 🟥 | Tests convertisseur + visualisateur | | Tests | YB | TODO | 🟥 | Tests convertisseur + visualisateur |
| Rapport | AD/AA/YB| TODO | 🟥 | Rédaction rapport complet | | Rapport | AD/AA/YB| TODO | 🟥 | Rédaction rapport complet |
| Livraison | AD/AA/YB| TODO | 🟥 | Vérification dépôt Gitea + JAR exécutables | | Livraison | AD/AA/YB| TODO | 🟥 | Vérification dépôt Gitea + JAR exécutables |
## Fichiers à créer Sprint 4 ## Fichiers à créer Sprint 4
### Racine creee tout au long ### Racine creee tout au long
| Nom du fichier | Rôle | | Nom du fichier | Rôle |
|----------------|-------| |----------------|-------|
| `Makefile` | Compilation + génération `.jar` | | `Makefile` | Compilation + génération `.jar` |
### `/doc` ### `/doc`
| Nom du fichier | Rôle | | Nom du fichier | Rôle |
|----------------|-------| |----------------|-------|
| `rapport.pdf` | Livrable final | | `rapport.pdf` | Livrable final |
| `UML/___.plantuml` | Sources de diagrammes | | `UML/___.plantuml` | Sources de diagrammes |
--- ---
### Résultat attendu Sprint 4 ### Résultat attendu Sprint 4
- Rapport PDF validé - Rapport PDF validé
- Diagrammes UML terminés - Diagrammes UML terminés
- Makefile opérationnel - Makefile opérationnel
- Projet soumis proprement sur Gitea - Projet soumis proprement sur Gitea
+90 -90
View File
@@ -1,90 +1,90 @@
# PRODUCT BACKLOG — Projet PIF # PRODUCT BACKLOG — Projet PIF
# 1. US UTILISATEUR # 1. US UTILISATEUR
Ce sont les besoins réels dun utilisateur final qui voudrait simplement visualiser ou convertir une image. Ce sont les besoins réels dun utilisateur final qui voudrait simplement visualiser ou convertir une image.
--- ---
### **US-U1 — Ouvrir un fichier PIF** ### **US-U1 — Ouvrir un fichier PIF**
En tant quutilisateur, je veux pouvoir ouvrir un fichier `.pif` via un argument ou un sélecteur de fichiers, afin dafficher limage. En tant quutilisateur, je veux pouvoir ouvrir un fichier `.pif` via un argument ou un sélecteur de fichiers, afin dafficher limage.
### **US-U2 — Afficher limage dans une fenêtre** ### **US-U2 — Afficher limage dans une fenêtre**
En tant quutilisateur, je veux voir limage affichée dans une fenêtre redimensionnable. En tant quutilisateur, je veux voir limage affichée dans une fenêtre redimensionnable.
### **US-U3 — Centrage automatique** ### **US-U3 — Centrage automatique**
En tant quutilisateur, je veux que limage soit centrée si elle est plus petite que la fenêtre, pour une meilleure visibilité. En tant quutilisateur, je veux que limage soit centrée si elle est plus petite que la fenêtre, pour une meilleure visibilité.
### **US-U4 — Déplacement de limage** ### **US-U4 — Déplacement de limage**
En tant quutilisateur, je veux pouvoir déplacer limage à la souris si elle dépasse la taille de la fenêtre. En tant quutilisateur, je veux pouvoir déplacer limage à la souris si elle dépasse la taille de la fenêtre.
### **US-U5 — Charger une image RGB (PNG/JPEG)** ### **US-U5 — Charger une image RGB (PNG/JPEG)**
En tant quutilisateur, je veux charger une image standard afin de la convertir en `.pif`. En tant quutilisateur, je veux charger une image standard afin de la convertir en `.pif`.
### **US-U6 — Exporter une image au format PIF** ### **US-U6 — Exporter une image au format PIF**
En tant quutilisateur, je veux enregistrer limage sous format `.pif`. En tant quutilisateur, je veux enregistrer limage sous format `.pif`.
### **US-U7 — Aperçu de limage avant conversion** ### **US-U7 — Aperçu de limage avant conversion**
En tant quutilisateur, je veux voir une miniature de limage chargée. En tant quutilisateur, je veux voir une miniature de limage chargée.
--- ---
# 2. US DÉVELOPPEUR # 2. US DÉVELOPPEUR
Ce sont les besoins techniques indispensables au fonctionnement interne du format PIF. Ce sont les besoins techniques indispensables au fonctionnement interne du format PIF.
--- ---
### **US-D1 — Lire des bits depuis un flux** ### **US-D1 — Lire des bits depuis un flux**
Le système doit permettre la lecture bit par bit depuis un fichier PIF. Le système doit permettre la lecture bit par bit depuis un fichier PIF.
### **US-D2 — Écrire des bits dans un fichier** ### **US-D2 — Écrire des bits dans un fichier**
Le système doit permettre l’écriture de bits pour générer un fichier PIF. Le système doit permettre l’écriture de bits pour générer un fichier PIF.
### **US-D3 — Construire les tables de fréquences RGB** ### **US-D3 — Construire les tables de fréquences RGB**
Le système doit analyser limage pour obtenir les fréquences des valeurs R, G, B. Le système doit analyser limage pour obtenir les fréquences des valeurs R, G, B.
### **US-D4 — Construire un arbre de Huffman** ### **US-D4 — Construire un arbre de Huffman**
Le système doit créer un arbre à partir des fréquences dune composante. Le système doit créer un arbre à partir des fréquences dune composante.
### **US-D5 — Générer les codes Huffman** ### **US-D5 — Générer les codes Huffman**
Le système doit produire les codes initiaux à partir de larbre. Le système doit produire les codes initiaux à partir de larbre.
### **US-D6 — Générer les codes canoniques** ### **US-D6 — Générer les codes canoniques**
Le système doit transformer les codes Huffman en codes canoniques. Le système doit transformer les codes Huffman en codes canoniques.
### **US-D7 — Reconstruire les codes canoniques en lecture** ### **US-D7 — Reconstruire les codes canoniques en lecture**
Le système doit pouvoir reconstruire les codes à partir des longueurs contenues dans le fichier .pif. Le système doit pouvoir reconstruire les codes à partir des longueurs contenues dans le fichier .pif.
### **US-D8 — Décoder un fichier PIF** ### **US-D8 — Décoder un fichier PIF**
Le système doit pouvoir reconstituer limage RGB à partir des données compressées. Le système doit pouvoir reconstituer limage RGB à partir des données compressées.
--- ---
# 3. US PROFESSEUR (PEDAGOGIQUE) # 3. US PROFESSEUR (PEDAGOGIQUE)
Ces fonctionnalités nont **aucune utilité pour un utilisateur réel**, mais sont demandées par le professeur pour vérifier le bon fonctionnement de notre projet. Ces fonctionnalités nont **aucune utilité pour un utilisateur réel**, mais sont demandées par le professeur pour vérifier le bon fonctionnement de notre projet.
--- ---
### **US-P1 — Affichage des tables de fréquences** ### **US-P1 — Affichage des tables de fréquences**
En tant que professeur, je veux consulter la table de fréquences R, G et B pour vérifier que le calcul est correct. En tant que professeur, je veux consulter la table de fréquences R, G et B pour vérifier que le calcul est correct.
### **US-P2 — Affichage des codes Huffman** ### **US-P2 — Affichage des codes Huffman**
En tant que professeur, je veux voir les codes Huffman générés afin de valider votre algorithme. En tant que professeur, je veux voir les codes Huffman générés afin de valider votre algorithme.
### **US-P3 — Affichage des codes canoniques** ### **US-P3 — Affichage des codes canoniques**
En tant que professeur, je veux visualiser les codes canoniques afin d’évaluer votre compréhension de leur construction. En tant que professeur, je veux visualiser les codes canoniques afin d’évaluer votre compréhension de leur construction.
### **US-P4 — Affichage de larbre Huffman (optionnel)** ### **US-P4 — Affichage de larbre Huffman (optionnel)**
En tant que professeur, je veux pouvoir inspecter la structure de larbre pour vérifier votre implémentation. En tant que professeur, je veux pouvoir inspecter la structure de larbre pour vérifier votre implémentation.
### **US-P5 — Documentation Javadoc pour chaque classe** ### **US-P5 — Documentation Javadoc pour chaque classe**
En tant que professeur, je veux avoir une documentation claire auto-générable. En tant que professeur, je veux avoir une documentation claire auto-générable.
### **US-P6 — Diagrammes UML dans le rapport** ### **US-P6 — Diagrammes UML dans le rapport**
En tant que professeur, je veux retrouver un diagramme de classes et un diagramme dobjets dans le rapport. En tant que professeur, je veux retrouver un diagramme de classes et un diagramme dobjets dans le rapport.
### **US-P7 — Makefile complet** ### **US-P7 — Makefile complet**
En tant que professeur, je veux pouvoir compiler les deux programmes en .jar exécutables avec un Makefile clair. En tant que professeur, je veux pouvoir compiler les deux programmes en .jar exécutables avec un Makefile clair.
+174 -174
View File
@@ -1,174 +1,174 @@
# Projet : Primitive Image Format (PIF) # Projet : Primitive Image Format (PIF)
## Description générale ## Description générale
Ce projet consiste à implémenter un nouveau format dimage compressé sans perte, appelé **PIF (Primitive Image Format)**, inspiré du format JFIF. Ce projet consiste à implémenter un nouveau format dimage compressé sans perte, appelé **PIF (Primitive Image Format)**, inspiré du format JFIF.
Il s'appuie sur la création de tables de fréquences, de codes de Huffman, de codes canoniques et sur la manipulation binaire afin de réduire la taille des images. Il s'appuie sur la création de tables de fréquences, de codes de Huffman, de codes canoniques et sur la manipulation binaire afin de réduire la taille des images.
Deux programmes Java doivent être développés : Deux programmes Java doivent être développés :
1. **Visualisateur PIF** 1. **Visualisateur PIF**
Programme capable douvrir un fichier `.pif` et dafficher limage dans une interface graphique. Programme capable douvrir un fichier `.pif` et dafficher limage dans une interface graphique.
2. **Convertisseur vers PIF** 2. **Convertisseur vers PIF**
Programme permettant de charger une image classique (ImageIO), de générer ses tables de fréquences et codes associés, puis de produire un fichier `.pif`. Programme permettant de charger une image classique (ImageIO), de générer ses tables de fréquences et codes associés, puis de produire un fichier `.pif`.
Ce travail doit être réalisé en binôme ou trinôme. Ce travail doit être réalisé en binôme ou trinôme.
--- ---
## Deadline ## Deadline
**Date limite de rendu : dimanche 11 janvier 2025 à 23h59.** **Date limite de rendu : dimanche 11 janvier 2025 à 23h59.**
Toutes les sources doivent être présentes sur le serveur Gitea du département dans un dépôt privé nommé **SAE32_2025**. Toutes les sources doivent être présentes sur le serveur Gitea du département dans un dépôt privé nommé **SAE32_2025**.
--- ---
## Fonctionnalités attendues ## Fonctionnalités attendues
### 1. Visualisateur `.pif` ### 1. Visualisateur `.pif`
- Lecture du fichier `.pif` via argument de ligne de commande ou `JFileChooser`. - Lecture du fichier `.pif` via argument de ligne de commande ou `JFileChooser`.
- Décodage : - Décodage :
- de len-tête (largeur, hauteur), - de len-tête (largeur, hauteur),
- des trois tables canoniques (R, G, B), - des trois tables canoniques (R, G, B),
- des données binaires des pixels. - des données binaires des pixels.
- Affichage graphique sous Swing : - Affichage graphique sous Swing :
- fenêtre redimensionnable, - fenêtre redimensionnable,
- image centrée si petite, - image centrée si petite,
- image déplaçable à la souris si trop grande. - image déplaçable à la souris si trop grande.
--- ---
### 2. Convertisseur vers format `.pif` ### 2. Convertisseur vers format `.pif`
- Chargement d'une image via `ImageIO.read()`. - Chargement d'une image via `ImageIO.read()`.
- Extraction : - Extraction :
- des tables de fréquences, - des tables de fréquences,
- des codes Huffman initiaux, - des codes Huffman initiaux,
- des codes canoniques triés. - des codes canoniques triés.
- Affichage des tables pour inspection. - Affichage des tables pour inspection.
- Génération du fichier `.pif` : - Génération du fichier `.pif` :
- en-tête, - en-tête,
- tables canoniques compactes, - tables canoniques compactes,
- données binaires des pixels encodés. - données binaires des pixels encodés.
- Le deuxième argument de ligne de commande peut définir le nom du fichier `.pif`. - Le deuxième argument de ligne de commande peut définir le nom du fichier `.pif`.
--- ---
## Structure du format PIF ## Structure du format PIF
Un fichier `.pif` contient trois sections : Un fichier `.pif` contient trois sections :
1. **En-tête (4 octets)** 1. **En-tête (4 octets)**
- largeur (2 octets) - largeur (2 octets)
- hauteur (2 octets) - hauteur (2 octets)
2. **Tables canoniques (768 octets)** 2. **Tables canoniques (768 octets)**
Trois tables successives de 256 octets : rouge, vert, bleu. Trois tables successives de 256 octets : rouge, vert, bleu.
Chaque octet indique la longueur du code canonique dune valeur entre 0 et 255. Chaque octet indique la longueur du code canonique dune valeur entre 0 et 255.
3. **Section pixels (à partir du 773e octet)** 3. **Section pixels (à partir du 773e octet)**
Les trois composantes (R, G, B) sont encodées via leurs codes canoniques respectifs. Les trois composantes (R, G, B) sont encodées via leurs codes canoniques respectifs.
Les bits sont packés de manière contiguë. Les bits sont packés de manière contiguë.
--- ---
## Processus de compression ## Processus de compression
### 1. Création des tables de fréquences ### 1. Création des tables de fréquences
Pour R, G et B : compter combien de fois chaque valeur apparaît dans limage. Pour R, G et B : compter combien de fois chaque valeur apparaît dans limage.
### 2. Construction des codes Huffman ### 2. Construction des codes Huffman
- Un arbre est construit par composante. - Un arbre est construit par composante.
- Les valeurs les plus fréquentes reçoivent les codes les plus courts. - Les valeurs les plus fréquentes reçoivent les codes les plus courts.
- Les codes initiaux peuvent varier en longueur. - Les codes initiaux peuvent varier en longueur.
### 3. Génération des codes canoniques ### 3. Génération des codes canoniques
- Tri des valeurs par longueur de code puis par valeur. - Tri des valeurs par longueur de code puis par valeur.
- Premier code : rempli de zéros. - Premier code : rempli de zéros.
- Les suivants sont obtenus par incrément binaire. - Les suivants sont obtenus par incrément binaire.
- Permet une reconstruction simple côté visualisateur. - Permet une reconstruction simple côté visualisateur.
--- ---
## Architecture logicielle ## Architecture logicielle
Le projet doit inclure : Le projet doit inclure :
- un package Java unique, - un package Java unique,
- toutes les classes nécessaires au traitement : - toutes les classes nécessaires au traitement :
- gestion du fichier PIF, - gestion du fichier PIF,
- lecture/écriture binaire, - lecture/écriture binaire,
- génération des fréquences, - génération des fréquences,
- Huffman, - Huffman,
- codes canoniques, - codes canoniques,
- interface graphique, - interface graphique,
- programme principal du visualisateur, - programme principal du visualisateur,
- programme principal du convertisseur. - programme principal du convertisseur.
- un `Makefile` générant deux exécutables `.jar`. - un `Makefile` générant deux exécutables `.jar`.
--- ---
## Classes Java ## Classes Java
Les classes Java utilisées dans le projet : Les classes Java utilisées dans le projet :
- [BitinputStream](src/fr/iutfbleau/sae/util/BitinputStream.java) - [BitinputStream](src/fr/iutfbleau/sae/util/BitinputStream.java)
- [BitOutputStream](src/fr/iutfbleau/sae/util/BitOutputStream.java) - [BitOutputStream](src/fr/iutfbleau/sae/util/BitOutputStream.java)
- [ByteUtils](src/fr/iutfbleau/sae/util/ByteUtils.java) - [ByteUtils](src/fr/iutfbleau/sae/util/ByteUtils.java)
- [CanonicalCode](src/fr/iutfbleau/sae/mhuffman/CanonicalCode.java) - [CanonicalCode](src/fr/iutfbleau/sae/mhuffman/CanonicalCode.java)
- [FrequencyTable](src/fr/iutfbleau/sae/mhuffman/FrequencyTable.java) - [FrequencyTable](src/fr/iutfbleau/sae/mhuffman/FrequencyTable.java)
- [HuffmanNode](src/fr/iutfbleau/sae/mhuffman/HuffmanNode.java) - [HuffmanNode](src/fr/iutfbleau/sae/mhuffman/HuffmanNode.java)
- [HuffmanTree](src/fr/iutfbleau/sae/mhuffman/HuffmanTree.java) - [HuffmanTree](src/fr/iutfbleau/sae/mhuffman/HuffmanTree.java)
--- ---
## Rapport à produire ## Rapport à produire
Un rapport PDF doit contenir : Un rapport PDF doit contenir :
- noms des membres du groupe, - noms des membres du groupe,
- introduction résumant le sujet, - introduction résumant le sujet,
- description des fonctionnalités du programme, - description des fonctionnalités du programme,
- captures d’écran, - captures d’écran,
- diagrammes UML simplifiés, - diagrammes UML simplifiés,
- explication du compresseur (Huffman, canoniques, structure du fichier), - explication du compresseur (Huffman, canoniques, structure du fichier),
- explication du décompresseur, - explication du décompresseur,
- conclusion personnelle de chaque membre. - conclusion personnelle de chaque membre.
Le rapport ne doit pas contenir de code source. Le rapport ne doit pas contenir de code source.
--- ---
## Compilation et exécution ## Compilation et exécution
### Visualisateur ### Visualisateur
java -jar pif-viewer.jar chemin/image.pif java -jar pif-viewer.jar chemin/image.pif
### Convertisseur ### Convertisseur
java -jar pif-converter.jar image.png sortie.pif java -jar pif-converter.jar image.png sortie.pif
Si aucun argument nest fourni, un `JFileChooser` est ouvert. Si aucun argument nest fourni, un `JFileChooser` est ouvert.
--- ---
- Les commits, leur fréquence et la collaboration seront pris en compte dans la note. - Les commits, leur fréquence et la collaboration seront pris en compte dans la note.
--- ---
## Auteurs ## Auteurs
- Algassimou Pellel DIALLO - Algassimou Pellel DIALLO
- Ayoub ANHDIRE - Ayoub ANHDIRE
- Youness BOULALAME - Youness BOULALAME
## Enseignant ## Enseignant
- Luc HERNANDEZ - Luc HERNANDEZ
Binary file not shown.
+175 -166
View File
@@ -1,166 +1,175 @@
# Outils # Outils
JAVAC = javac JAVAC = javac
JAVA = java JAVA = java
JAVADOC = javadoc JAVADOC = javadoc
ARGS = ARGS =
# Dossiers # Dossiers
SRC = src SRC = src
BIN = build BIN = build
DOC = docjava DOC = docjava
LIB = lib/ LIB = lib/
# Package # Package
PKG_PATH = fr/iutfbleau/sae PKG_PATH = fr/iutfbleau/sae
# Points dentrée # Points dentrée
MAIN_CONVERTER = fr.iutfbleau.sae.Convertisseur MAIN_CONVERTER = fr.iutfbleau.sae.Convertisseur
MAIN_VIEWER = fr.iutfbleau.sae.Viewer MAIN_VIEWER = fr.iutfbleau.sae.Viewer
# Séparateur classpath # Séparateur classpath
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
SEP = ; SEP = ;
else else
SEP = : SEP = :
endif endif
# Règle par défaut # Règle par défaut
all: \ all: \
$(BIN)/$(PKG_PATH)/Convertisseur.class \ $(BIN)/$(PKG_PATH)/Convertisseur.class \
$(BIN)/$(PKG_PATH)/Viewer.class $(BIN)/$(PKG_PATH)/Viewer.class
# Compilation des classes main # Compilation des classes main
$(BIN)/$(PKG_PATH)/Convertisseur.class: $(BIN) \ $(BIN)/$(PKG_PATH)/Convertisseur.class: $(BIN) \
$(BIN)/$(PKG_PATH)/ConverterController.class \ $(BIN)/$(PKG_PATH)/ConverterController.class \
$(BIN)/$(PKG_PATH)/vconverter/ConverterWindow.class \ $(BIN)/$(PKG_PATH)/vconverter/ConverterWindow.class \
$(BIN)/$(PKG_PATH)/ExportButtonListener.class \ $(BIN)/$(PKG_PATH)/ExportButtonListener.class \
$(SRC)/$(PKG_PATH)/Convertisseur.java $(SRC)/$(PKG_PATH)/Convertisseur.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/Convertisseur.java $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/Convertisseur.java
$(BIN)/$(PKG_PATH)/Viewer.class: $(BIN) \ $(BIN)/$(PKG_PATH)/Viewer.class: $(BIN) \
$(SRC)/$(PKG_PATH)/Viewer.java $(SRC)/$(PKG_PATH)/Viewer.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/Viewer.java $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/Viewer.java
# Dossiers # Dossiers
$(BIN): $(BIN):
mkdir -p $(BIN) mkdir -p $(BIN)
$(DOC): $(DOC):
mkdir -p $(DOC) mkdir -p $(DOC)
# Compilation des classes util # Compilation des classes util
$(BIN)/$(PKG_PATH)/util/ByteUtils.class: $(BIN) \ $(BIN)/$(PKG_PATH)/util/ByteUtils.class: $(BIN) \
$(SRC)/$(PKG_PATH)/util/ByteUtils.java $(SRC)/$(PKG_PATH)/util/ByteUtils.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/util/ByteUtils.java $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/util/ByteUtils.java
$(BIN)/$(PKG_PATH)/util/BitInputStream.class: $(BIN) \ $(BIN)/$(PKG_PATH)/util/BitInputStream.class: $(BIN) \
$(SRC)/$(PKG_PATH)/util/BitInputStream.java $(SRC)/$(PKG_PATH)/util/BitInputStream.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/util/BitInputStream.java $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/util/BitInputStream.java
$(BIN)/$(PKG_PATH)/util/BitOutputStream.class: $(BIN) \ $(BIN)/$(PKG_PATH)/util/BitOutputStream.class: $(BIN) \
$(SRC)/$(PKG_PATH)/util/BitOutputStream.java $(SRC)/$(PKG_PATH)/util/BitOutputStream.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/util/BitOutputStream.java $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/util/BitOutputStream.java
# Compilation des classes mhuffman # Compilation des classes mhuffman
$(BIN)/$(PKG_PATH)/mhuffman/CanonicalCode.class: $(BIN) \
$(SRC)/$(PKG_PATH)/mhuffman/CanonicalCode.java # Ajout de la classe ComparateurCanonique :
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mhuffman/CanonicalCode.java $(BIN)/$(PKG_PATH)/mhuffman/ComparateurCanonique.class: $(BIN) \
$(SRC)/$(PKG_PATH)/mhuffman/ComparateurCanonique.java
$(BIN)/$(PKG_PATH)/mhuffman/FrequencyTable.class: $(BIN) \ $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mhuffman/ComparateurCanonique.java
$(BIN)/$(PKG_PATH)/mimage/RGBImage.class \ #
$(SRC)/$(PKG_PATH)/mhuffman/FrequencyTable.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mhuffman/FrequencyTable.java
$(BIN)/$(PKG_PATH)/mhuffman/CanonicalCode.class: $(BIN) \
$(BIN)/$(PKG_PATH)/mhuffman/HuffmanNode.class: $(BIN) \ $(SRC)/$(PKG_PATH)/mhuffman/CanonicalCode.java \
$(SRC)/$(PKG_PATH)/mhuffman/HuffmanNode.java $(BIN)/$(PKG_PATH)/mhuffman/ComparateurCanonique.class
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mhuffman/HuffmanNode.java $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mhuffman/CanonicalCode.java
$(BIN)/$(PKG_PATH)/mhuffman/HuffmanTree.class: $(BIN) \ $(BIN)/$(PKG_PATH)/mhuffman/FrequencyTable.class: $(BIN) \
$(BIN)/$(PKG_PATH)/mhuffman/HuffmanNode.class \ $(BIN)/$(PKG_PATH)/mimage/RGBImage.class \
$(SRC)/$(PKG_PATH)/mhuffman/HuffmanTree.java $(SRC)/$(PKG_PATH)/mhuffman/FrequencyTable.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mhuffman/HuffmanTree.java $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mhuffman/FrequencyTable.java
$(BIN)/$(PKG_PATH)/mhuffman/HuffmanNode.class: $(BIN) \
# Compilation des classes mimage $(SRC)/$(PKG_PATH)/mhuffman/HuffmanNode.java
$(BIN)/$(PKG_PATH)/mimage/Pixel.class: $(BIN) \ $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mhuffman/HuffmanNode.java
$(SRC)/$(PKG_PATH)/mimage/Pixel.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mimage/Pixel.java $(BIN)/$(PKG_PATH)/mhuffman/HuffmanTree.class: $(BIN) \
$(BIN)/$(PKG_PATH)/mhuffman/HuffmanNode.class \
$(BIN)/$(PKG_PATH)/mimage/RGBImage.class: $(BIN) \ $(SRC)/$(PKG_PATH)/mhuffman/HuffmanTree.java
$(BIN)/$(PKG_PATH)/mimage/Pixel.class \ $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mhuffman/HuffmanTree.java
$(SRC)/$(PKG_PATH)/mimage/RGBImage.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mimage/RGBImage.java
# Compilation des classes mimage
$(BIN)/$(PKG_PATH)/mimage/Pixel.class: $(BIN) \
# Interface graphique $(SRC)/$(PKG_PATH)/mimage/Pixel.java
$(BIN)/$(PKG_PATH)/vconverter/ImagePreviewPanel.class: $(BIN) \ $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mimage/Pixel.java
$(SRC)/$(PKG_PATH)/vconverter/ImagePreviewPanel.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/vconverter/ImagePreviewPanel.java $(BIN)/$(PKG_PATH)/mimage/RGBImage.class: $(BIN) \
$(BIN)/$(PKG_PATH)/mimage/Pixel.class \
$(BIN)/$(PKG_PATH)/vconverter/FrequencyTablePanel.class: $(BIN) \ $(SRC)/$(PKG_PATH)/mimage/RGBImage.java
$(SRC)/$(PKG_PATH)/vconverter/FrequencyTablePanel.java $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mimage/RGBImage.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/vconverter/FrequencyTablePanel.java
$(BIN)/$(PKG_PATH)/vconverter/CodeTablePanel.class: $(BIN) \ # Interface graphique
$(SRC)/$(PKG_PATH)/vconverter/CodeTablePanel.java $(BIN)/$(PKG_PATH)/vconverter/ImagePreviewPanel.class: $(BIN) \
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/vconverter/CodeTablePanel.java $(SRC)/$(PKG_PATH)/vconverter/ImagePreviewPanel.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/vconverter/ImagePreviewPanel.java
# $(BIN)/$(PKG_PATH)/vconverter/ConverterWindow.class: $(BIN) \
# $(BIN)/$(PKG_PATH)/ConverterController.class \ $(BIN)/$(PKG_PATH)/vconverter/FrequencyTablePanel.class: $(BIN) \
# $(BIN)/$(PKG_PATH)/vconverter/ImagePreviewPanel.class \ $(SRC)/$(PKG_PATH)/vconverter/FrequencyTablePanel.java
# $(BIN)/$(PKG_PATH)/vconverter/FrequencyTablePanel.class \ $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/vconverter/FrequencyTablePanel.java
# $(BIN)/$(PKG_PATH)/vconverter/CodeTablePanel.class \
# $(SRC)/$(PKG_PATH)/vconverter/ConverterWindow.java $(BIN)/$(PKG_PATH)/vconverter/CodeTablePanel.class: $(BIN) \
# $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/vconverter/ConverterWindow.java $(SRC)/$(PKG_PATH)/vconverter/CodeTablePanel.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/vconverter/CodeTablePanel.java
# $(BIN)/$(PKG_PATH)/vconverter/ConverterWindow.class: $(BIN) \
# Compilation PIFWriter # $(BIN)/$(PKG_PATH)/ConverterController.class \
$(BIN)/$(PKG_PATH)/mpif/PIFWriter.class: $(BIN) \ # $(BIN)/$(PKG_PATH)/vconverter/ImagePreviewPanel.class \
$(BIN)/$(PKG_PATH)/mimage/RGBImage.class \ # $(BIN)/$(PKG_PATH)/vconverter/FrequencyTablePanel.class \
$(BIN)/$(PKG_PATH)/util/BitOutputStream.class \ # $(BIN)/$(PKG_PATH)/vconverter/CodeTablePanel.class \
$(SRC)/$(PKG_PATH)/mpif/PIFWriter.java # $(SRC)/$(PKG_PATH)/vconverter/ConverterWindow.java
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mpif/PIFWriter.java # $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/vconverter/ConverterWindow.java
# GROSSE compilation du listener + ConvertController + ConvertWindow + PIFSaverTask car il y a une dependance cirulaire
$(BIN)/$(PKG_PATH)/ConverterController.class \ # Compilation PIFWriter
$(BIN)/$(PKG_PATH)/ExportButtonListener.class \ $(BIN)/$(PKG_PATH)/mpif/PIFWriter.class: $(BIN) \
$(BIN)/$(PKG_PATH)/PIFSaveTask.class \ $(BIN)/$(PKG_PATH)/mimage/RGBImage.class \
$(BIN)/$(PKG_PATH)/vconverter/ConverterWindow.class: \ $(BIN)/$(PKG_PATH)/util/BitOutputStream.class \
$(SRC)/$(PKG_PATH)/ConverterController.java \ $(SRC)/$(PKG_PATH)/mpif/PIFWriter.java
$(SRC)/$(PKG_PATH)/ExportButtonListener.java \ $(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mpif/PIFWriter.java
$(SRC)/$(PKG_PATH)/PIFSaveTask.java \
$(SRC)/$(PKG_PATH)/vconverter/ConverterWindow.java \
$(BIN)/$(PKG_PATH)/mimage/Pixel.class \ # GROSSE compilation du listener + ConvertController + ConvertWindow + PIFSaverTask car il y a une dependance cirulaire
$(BIN)/$(PKG_PATH)/mimage/RGBImage.class \ $(BIN)/$(PKG_PATH)/ConverterController.class \
$(BIN)/$(PKG_PATH)/mhuffman/FrequencyTable.class \ $(BIN)/$(PKG_PATH)/ExportButtonListener.class \
$(BIN)/$(PKG_PATH)/mhuffman/HuffmanTree.class \ $(BIN)/$(PKG_PATH)/PIFSaveTask.class \
$(BIN)/$(PKG_PATH)/mhuffman/CanonicalCode.class \ $(BIN)/$(PKG_PATH)/vconverter/ConverterWindow.class: \
$(BIN)/$(PKG_PATH)/vconverter/ImagePreviewPanel.class \ $(SRC)/$(PKG_PATH)/ConverterController.java \
$(BIN)/$(PKG_PATH)/vconverter/FrequencyTablePanel.class \ $(SRC)/$(PKG_PATH)/ExportButtonListener.java \
$(BIN)/$(PKG_PATH)/vconverter/CodeTablePanel.class \ $(SRC)/$(PKG_PATH)/PIFSaveTask.java \
$(BIN)/$(PKG_PATH)/mpif/PIFWriter.class | $(BIN) $(SRC)/$(PKG_PATH)/vconverter/ConverterWindow.java \
@$(JAVAC) -cp $(BIN) -d $(BIN) \ $(BIN)/$(PKG_PATH)/mimage/Pixel.class \
$(SRC)/$(PKG_PATH)/ConverterController.java \ $(BIN)/$(PKG_PATH)/mimage/RGBImage.class \
$(SRC)/$(PKG_PATH)/ExportButtonListener.java \ $(BIN)/$(PKG_PATH)/mhuffman/FrequencyTable.class \
$(SRC)/$(PKG_PATH)/PIFSaveTask.java \ $(BIN)/$(PKG_PATH)/mhuffman/HuffmanTree.class \
$(SRC)/$(PKG_PATH)/vconverter/ConverterWindow.java $(BIN)/$(PKG_PATH)/mhuffman/CanonicalCode.class \
$(BIN)/$(PKG_PATH)/vconverter/ImagePreviewPanel.class \
$(BIN)/$(PKG_PATH)/vconverter/FrequencyTablePanel.class \
# Exécution $(BIN)/$(PKG_PATH)/vconverter/CodeTablePanel.class \
run-conv: all $(BIN)/$(PKG_PATH)/mpif/PIFWriter.class | $(BIN)
$(JAVA) -cp $(BIN) $(MAIN_CONVERTER) $(ARGS) @$(JAVAC) -cp $(BIN) -d $(BIN) \
$(SRC)/$(PKG_PATH)/ConverterController.java \
run-view: all $(SRC)/$(PKG_PATH)/ExportButtonListener.java \
$(JAVA) -cp $(BIN) $(MAIN_VIEWER) $(SRC)/$(PKG_PATH)/PIFSaveTask.java \
$(SRC)/$(PKG_PATH)/vconverter/ConverterWindow.java
# Documentation
doc: $(DOC)
$(JAVADOC) -d $(DOC) $(SRC)/fr/iutfbleau/sae/**/*.java # Exécution
run-conv: all
# Nettoyage $(JAVA) -cp $(BIN) $(MAIN_CONVERTER) $(ARGS)
clean:
rm -rf $(BIN) $(DOC) run-view: all
$(JAVA) -cp $(BIN) $(MAIN_VIEWER)
# Documentation
doc: $(DOC)
$(JAVADOC) -d $(DOC) $(SRC)/fr/iutfbleau/sae/**/*.java
# Nettoyage
clean:
rm -rf $(BIN) $(DOC)
+225 -225
View File
@@ -1,225 +1,225 @@
package fr.iutfbleau.sae; package fr.iutfbleau.sae;
import fr.iutfbleau.sae.mhuffman.*; import fr.iutfbleau.sae.mhuffman.*;
import fr.iutfbleau.sae.mimage.*; import fr.iutfbleau.sae.mimage.*;
import fr.iutfbleau.sae.mpif.PIFWriter; import fr.iutfbleau.sae.mpif.PIFWriter;
import fr.iutfbleau.sae.vconverter.ConverterWindow; import fr.iutfbleau.sae.vconverter.ConverterWindow;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.util.Map; import java.util.Map;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.JFileChooser; import javax.swing.JFileChooser;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
/** /**
* Contrôleur pour la conversion d'images. * Contrôleur pour la conversion d'images.
* <p> * <p>
* Cette classe gère le chargement des fichiers image et les opérations * Cette classe gère le chargement des fichiers image et les opérations
* de conversion associées. tel que * de conversion associées. tel que
* </p> * </p>
* *
*/ */
public class ConverterController { public class ConverterController {
// Image convertie en RGBImage // Image convertie en RGBImage
private RGBImage image; private RGBImage image;
// Table de fréquences pour chaque composante // Table de fréquences pour chaque composante
private FrequencyTable frequencyTable; private FrequencyTable frequencyTable;
// Arbres de Huffman pour chaque composante // Arbres de Huffman pour chaque composante
private Map<Integer, String> abrHuffmanR; private Map<Integer, String> abrHuffmanR;
private Map<Integer, String> abrHuffmanG; private Map<Integer, String> abrHuffmanG;
private Map<Integer, String> abrHuffmanB; private Map<Integer, String> abrHuffmanB;
// Codes canoniques pour chaque composante // Codes canoniques pour chaque composante
private Map<Integer, String> canonRED; private Map<Integer, String> canonRED;
private Map<Integer, String> canonGREEN; private Map<Integer, String> canonGREEN;
private Map<Integer, String> canonBLUE; private Map<Integer, String> canonBLUE;
// La fenêtre du convertisseur // La fenêtre du convertisseur
private ConverterWindow fen; private ConverterWindow fen;
String outputPath; String outputPath;
String inputPath; String inputPath;
public ConverterController(ConverterWindow fen, String in, String out) { public ConverterController(ConverterWindow fen, String in, String out) {
this.fen = fen; this.fen = fen;
this.outputPath = out; this.outputPath = out;
this.inputPath = in; this.inputPath = in;
System.out.println(this.inputPath+" ==> "+this.outputPath); System.out.println(this.inputPath+" ==> "+this.outputPath);
} }
// charger une image depuis un fichier avec bufferedImage et la convertir en RGBImage // charger une image depuis un fichier avec bufferedImage et la convertir en RGBImage
public void loadImage(File file) { public void loadImage(File file) {
try{ try{
// Lire l'image avec BufferedImage // Lire l'image avec BufferedImage
BufferedImage buffimage = ImageIO.read(file); BufferedImage buffimage = ImageIO.read(file);
if (buffimage == null) { if (buffimage == null) {
throw new IllegalArgumentException("Le fichier spécifié n'est pas une image valide."); throw new IllegalArgumentException("Le fichier spécifié n'est pas une image valide.");
} }
int w = buffimage.getWidth(); int w = buffimage.getWidth();
int h = buffimage.getHeight(); int h = buffimage.getHeight();
// Créer une RGBImage de la même taille // Créer une RGBImage de la même taille
this.image = new RGBImage(w, h); this.image = new RGBImage(w, h);
// remplir la RGBImage avec les pixels de BufferedImage // remplir la RGBImage avec les pixels de BufferedImage
for (int y = 0; y < h; y++) { for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) { for (int x = 0; x < w; x++) {
int rgb = buffimage.getRGB(x, y); // obtenir la valeur RGB du pixel int rgb = buffimage.getRGB(x, y); // obtenir la valeur RGB du pixel
// Extraire les composantes R, G, B // Extraire les composantes R, G, B
int r = (rgb >> 16) & 0xFF; int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF; int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF; int b = rgb & 0xFF;
// Créer un pixel et l'ajouter à la RGBImage // Créer un pixel et l'ajouter à la RGBImage
this.image.setPixel(x, y, new Pixel(r, g, b)); this.image.setPixel(x, y, new Pixel(r, g, b));
} }
} }
// Mettre à jour le GUI // Mettre à jour le GUI
this.fen.setImagePreview(buffimage); this.fen.setImagePreview(buffimage);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public void computeFrequencies() { public void computeFrequencies() {
if (this.image == null) { if (this.image == null) {
System.err.println("Aucune image chargée pour le calcul des fréquences."); System.err.println("Aucune image chargée pour le calcul des fréquences.");
return; return;
} }
this.frequencyTable = new FrequencyTable(); this.frequencyTable = new FrequencyTable();
this.frequencyTable.computeFromImage(this.image); this.frequencyTable.computeFromImage(this.image);
// Mettre à jour le GUI avec les fréquences // Mettre à jour le GUI avec les fréquences
int[] freqR = this.frequencyTable.getRed(); int[] freqR = this.frequencyTable.getRed();
int[] freqG = this.frequencyTable.getGreen(); int[] freqG = this.frequencyTable.getGreen();
int[] freqB = this.frequencyTable.getBlue(); int[] freqB = this.frequencyTable.getBlue();
this.fen.setFrequencyTable(freqR, freqG, freqB); this.fen.setFrequencyTable(freqR, freqG, freqB);
} }
public void computeHuffman() { public void computeHuffman() {
if (this.frequencyTable == null) { if (this.frequencyTable == null) {
System.err.println("Les fréquences ne sont pas encore calculées."); System.err.println("Les fréquences ne sont pas encore calculées.");
return; return;
} }
// Génération des arbres de Huffman pour chaque composante et stockage des codes // Génération des arbres de Huffman pour chaque composante et stockage des codes
HuffmanTree arbreR = new HuffmanTree(this.frequencyTable.getRed()); HuffmanTree arbreR = new HuffmanTree(this.frequencyTable.getRed());
this.abrHuffmanR = arbreR.generateCodes(); this.abrHuffmanR = arbreR.generateCodes();
HuffmanTree arbreG = new HuffmanTree(this.frequencyTable.getGreen()); HuffmanTree arbreG = new HuffmanTree(this.frequencyTable.getGreen());
this.abrHuffmanG = arbreG.generateCodes(); this.abrHuffmanG = arbreG.generateCodes();
HuffmanTree arbreB = new HuffmanTree(this.frequencyTable.getBlue()); HuffmanTree arbreB = new HuffmanTree(this.frequencyTable.getBlue());
this.abrHuffmanB = arbreB.generateCodes(); this.abrHuffmanB = arbreB.generateCodes();
// Mettre à jour le GUI avec les codes de Huffman // Mettre à jour le GUI avec les codes de Huffman
this.fen.setHuffmanTable(this.abrHuffmanR, this.abrHuffmanG, this.abrHuffmanB); this.fen.setHuffmanTable(this.abrHuffmanR, this.abrHuffmanG, this.abrHuffmanB);
} }
public void computeCanonical() { public void computeCanonical() {
if (this.abrHuffmanR == null || this.abrHuffmanG == null || this.abrHuffmanB == null) { if (this.abrHuffmanR == null || this.abrHuffmanG == null || this.abrHuffmanB == null) {
System.err.println("Les codes de Huffman doivent être générés d'abord."); System.err.println("Les codes de Huffman doivent être générés d'abord.");
return; return;
} }
CanonicalCode codeCanoniques = new CanonicalCode(); CanonicalCode codeCanoniques = new CanonicalCode();
this.canonRED = codeCanoniques.generateCodes(this.abrHuffmanR); this.canonRED = codeCanoniques.generateCodes(this.abrHuffmanR);
this.canonGREEN = codeCanoniques.generateCodes(this.abrHuffmanG); this.canonGREEN = codeCanoniques.generateCodes(this.abrHuffmanG);
this.canonBLUE = codeCanoniques.generateCodes(this.abrHuffmanB); this.canonBLUE = codeCanoniques.generateCodes(this.abrHuffmanB);
// Mettre à jour le GUI avec les codes canoniques // Mettre à jour le GUI avec les codes canoniques
this.fen.setCanonicalTable(this.canonRED, this.canonGREEN, this.canonBLUE); this.fen.setCanonicalTable(this.canonRED, this.canonGREEN, this.canonBLUE);
} }
public void saveAsPIF(String pathfile) { public void saveAsPIF(String pathfile) {
// je Vérifie que l'image et les codes canoniques sont disponibles // je Vérifie que l'image et les codes canoniques sont disponibles
if(this.image == null || this.canonRED == null){ if(this.image == null || this.canonRED == null){
System.err.println("Impossible d'ecrire le fichier PIF : données manquantes."); System.err.println("Impossible d'ecrire le fichier PIF : données manquantes.");
return; return;
} }
try { try {
PIFWriter ecriveur = new PIFWriter(); PIFWriter ecriveur = new PIFWriter();
ecriveur.writeTOFile(pathfile, this.image, this.canonRED, this.canonGREEN, this.canonBLUE); ecriveur.writeTOFile(pathfile, this.image, this.canonRED, this.canonGREEN, this.canonBLUE);
} catch (Exception e) { } catch (Exception e) {
System.err.println("Erreur lors de l’écriture du fichier .pif : " + pathfile); System.err.println("Erreur lors de l’écriture du fichier .pif : " + pathfile);
} }
} }
public void saveViaBtn() { public void saveViaBtn() {
try { try {
if (outputPath != null) { if (outputPath != null) {
saveAsPIF(outputPath); saveAsPIF(outputPath);
System.out.println("Sauvegarde dans : " + outputPath); System.out.println("Sauvegarde dans : " + outputPath);
return; return;
} }
// Sinon JFileChooser // Sinon JFileChooser
JFileChooser chooser = new JFileChooser(); JFileChooser chooser = new JFileChooser();
chooser.setDialogTitle("Enregistrer le fichier .pif"); chooser.setDialogTitle("Enregistrer le fichier .pif");
if (chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { if (chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
//saveAsPIF(chooser.getSelectedFile().getAbsolutePath()); //saveAsPIF(chooser.getSelectedFile().getAbsolutePath());
// On lance la sauvegarde lourde dans un thread séparé // On lance la sauvegarde lourde dans un thread séparé
new Thread(() -> saveAsPIF(chooser.getSelectedFile().getAbsolutePath())).start(); new Thread(() -> saveAsPIF(chooser.getSelectedFile().getAbsolutePath())).start();
System.out.println("Fichier sauvegardé : " + chooser.getSelectedFile().getAbsolutePath()); System.out.println("Fichier sauvegardé : " + chooser.getSelectedFile().getAbsolutePath());
JOptionPane.showMessageDialog(null, "Fichier sauvegardé avec succès : " + chooser.getSelectedFile().getName()); JOptionPane.showMessageDialog(null, "Fichier sauvegardé avec succès : " + chooser.getSelectedFile().getName());
} }
System.out.println("Via BTN Sauvegarde terminée."); System.out.println("Via BTN Sauvegarde terminée.");
} catch (Exception ex) { } catch (Exception ex) {
System.out.println("Erreur lors de la sauvegarde : " + ex.getMessage()); System.out.println("Erreur lors de la sauvegarde : " + ex.getMessage());
} }
} }
public void StartconvessionProcess(){ public void StartconvessionProcess(){
// chragement // chragement
if (this.inputPath != null) { if (this.inputPath != null) {
// Chargement direct depuis les arguments // Chargement direct depuis les arguments
File file = new File(inputPath); File file = new File(inputPath);
this.loadImage(file); this.loadImage(file);
}else{ }else{
// Sinon JFileChooser pour choisir l'image // Sinon JFileChooser pour choisir l'image
JFileChooser choosser =new JFileChooser(); JFileChooser choosser =new JFileChooser();
choosser.setDialogTitle("Choisissez une image"); choosser.setDialogTitle("Choisissez une image");
if (choosser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { if (choosser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
this.loadImage(choosser.getSelectedFile()); this.loadImage(choosser.getSelectedFile());
}else { }else {
System.err.println("Aucune image choisie. Arrêt du programme."); System.err.println("Aucune image choisie. Arrêt du programme.");
System.exit(1); System.exit(1);
return; return;
} }
} }
// je place la logique de conversion dans lordre // je place la logique de conversion dans lordre
computeFrequencies(); computeFrequencies();
computeHuffman(); computeHuffman();
computeCanonical(); computeCanonical();
// Sauvegarder: un second argument est donné sauvegarde automatique // Sauvegarder: un second argument est donné sauvegarde automatique
if (this.outputPath != null) { if (this.outputPath != null) {
this.saveAsPIF(this.outputPath); this.saveAsPIF(this.outputPath);
System.out.println("Fichier sauvegardé automatiquement : " + this.outputPath); System.out.println("Fichier sauvegardé automatiquement : " + this.outputPath);
}else{ }else{
// pas de deuxième argument j'ajoute un boutton pour choisir avec un jfilechoser // pas de deuxième argument j'ajoute un boutton pour choisir avec un jfilechoser
fen.addSaveButton(this); fen.addSaveButton(this);
} }
} }
public RGBImage getImage(){ public RGBImage getImage(){
return this.image; return this.image;
} }
} }
+24 -24
View File
@@ -1,25 +1,25 @@
package fr.iutfbleau.sae; package fr.iutfbleau.sae;
import fr.iutfbleau.sae.vconverter.ConverterWindow; import fr.iutfbleau.sae.vconverter.ConverterWindow;
public class Convertisseur { public class Convertisseur {
public static void main(String[] args) { public static void main(String[] args) {
// chemins de l'image // chemins de l'image
String inpuPath = null; String inpuPath = null;
String outputPath = null; String outputPath = null;
if (args.length >= 1) { if (args.length >= 1) {
inpuPath = args[0]; inpuPath = args[0];
} }
if (args.length >= 2) { if (args.length >= 2) {
outputPath = args[1]; outputPath = args[1];
} }
// Créer et stocker la référence à la fenêtre // Créer et stocker la référence à la fenêtre
ConverterWindow window = new ConverterWindow(); ConverterWindow window = new ConverterWindow();
// je la passe au controleur // je la passe au controleur
ConverterController controller = new ConverterController(window, inpuPath, outputPath); ConverterController controller = new ConverterController(window, inpuPath, outputPath);
controller.StartconvessionProcess(); controller.StartconvessionProcess();
} }
} }
+17 -17
View File
@@ -1,17 +1,17 @@
package fr.iutfbleau.sae; package fr.iutfbleau.sae;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
public class ExportButtonListener implements ActionListener { public class ExportButtonListener implements ActionListener {
private PIFSaveTask saveTask; private PIFSaveTask saveTask;
public ExportButtonListener(ConverterController controller){ public ExportButtonListener(ConverterController controller){
this.saveTask = new PIFSaveTask(controller); this.saveTask = new PIFSaveTask(controller);
} }
@Override @Override
public void actionPerformed(ActionEvent e){ public void actionPerformed(ActionEvent e){
SwingUtilities.invokeLater(saveTask); SwingUtilities.invokeLater(saveTask);
} }
} }
+13 -13
View File
@@ -1,13 +1,13 @@
package fr.iutfbleau.sae; package fr.iutfbleau.sae;
public class PIFSaveTask implements Runnable{ public class PIFSaveTask implements Runnable{
private ConverterController controller; private ConverterController controller;
public PIFSaveTask(ConverterController c) { public PIFSaveTask(ConverterController c) {
this.controller = c; this.controller = c;
} }
@Override @Override
public void run() { public void run() {
controller.saveViaBtn(); controller.saveViaBtn();
} }
} }
+7 -7
View File
@@ -1,7 +1,7 @@
package fr.iutfbleau.sae; package fr.iutfbleau.sae;
public class Viewer { public class Viewer {
public static void main(String[] args) { public static void main(String[] args) {
System.out.println("dqkdjqkdjqkdjqkdjqkdj"); System.out.println("dqkdjqkdjqkdjqkdjqkdj");
} }
} }
@@ -1,72 +1,61 @@
package fr.iutfbleau.sae.mhuffman; package fr.iutfbleau.sae.mhuffman;
import java.util.*; import java.util.*;
public class CanonicalCode{ public class CanonicalCode{
//private Map<Integer, String> codeLengths = new HashMap<>(); //private Map<Integer, String> codeLengths = new HashMap<>();
//private Map<Integer, String> canonicalCodes = new HashMap<>(); //private Map<Integer, String> canonicalCodes = new HashMap<>();
public Map<Integer,String> generateCodes(Map<Integer,String> codesHuffman){ public Map<Integer,String> generateCodes(Map<Integer,String> codesHuffman){
// 1 ere chose à faire : on regarde uniquement la longueur des codes initiaux(Huffman) // 1 ere chose à faire : on regarde uniquement la longueur des codes initiaux(Huffman)
// 2eme chose à faire : remettre dans l'ordre des longueurs : si meme taille ==> regarder valeur // 2eme chose à faire : remettre dans l'ordre des longueurs : si meme taille ==> regarder valeur
// 3eme chose à faire : ecriture des codes canoniques // 3eme chose à faire : ecriture des codes canoniques
// on recupere les entrées des codes Huffman pour pouvoir les triés // on recupere les entrées des codes Huffman pour pouvoir les triés
List<Map.Entry<Integer, String>> liste = new ArrayList<>(codesHuffman.entrySet()); List<Map.Entry<Integer, String>> liste = new ArrayList<>(codesHuffman.entrySet());
// ici on comparer par longueur de la valeur ou sinon par la clé // ici on comparer par longueur de la valeur ou sinon par la clé
// =============== ATTENTION CLASSE ANONYME !!!!!!! ================ Collections.sort(liste, new ComparateurCanonique());
Collections.sort(liste, new Comparator<Map.Entry<Integer, String>>() {
@Override
public int compare(Map.Entry<Integer, String> arg1 ,Map.Entry<Integer, String> arg2) {
Map<Integer,String> canonicalCodes = new HashMap<>();
int length1 = arg1.getValue().length(); int code = 0; // code canonique à attribuer
int length2 = arg2.getValue().length(); int temp = 0; //garde la longueur du code précedent , pour gérer le décalage
if (length1 != length2) { for (Map.Entry<Integer, String> entree : liste ) {
return length1 - length2; int valeur = entree.getKey(); // symbole actuel
} int longueur = entree.getValue().length(); // longueur du code actuel
return arg1.getKey() - arg2.getKey();
} code <<= (longueur - temp); // permet de décaler le code actuel si la longueur augmente
}); String codeBinaire = Integer.toBinaryString(code);
Map<Integer,String> canonicalCodes = new HashMap<>(); // permet d'ajouter des zeros si nécessaire !!!
int code = 0; // code canonique à attribuer while (codeBinaire.length() < longueur) {
int temp = 0; //garde la longueur du code précedent , pour gérer le décalage codeBinaire = "0" + codeBinaire;
}
for (Map.Entry<Integer, String> entree : liste ) {
int valeur = entree.getKey(); // symbole actuel canonicalCodes.put(valeur,codeBinaire); // ajout dans le dictionnaire
int longueur = entree.getValue().length(); // longueur du code actuel code++; // incrémentation pour la valeur qui suit
temp = longueur; // mise à jour de la longueur précedente
code <<= (longueur - temp); // permet de décaler le code actuel si la longueur augmente }
String codeBinaire = Integer.toBinaryString(code);
return canonicalCodes;
// permet d'ajouter des zeros si nécessaire !!! }
while (codeBinaire.length() < longueur) {
codeBinaire = "0" + codeBinaire;
} public String getCode(Map<Integer,String> canonicalCodes,int value){
return canonicalCodes.get(value);
canonicalCodes.put(valeur,codeBinaire); // ajout dans le dictionnaire }
code++; // incrémentation pour la valeur qui suit
temp = longueur; // mise à jour de la longueur précedente public int getLength(Map<Integer,String> codesH,int value){
} return codesH.get(value).length();
}
return canonicalCodes;
} }
public String getCode(Map<Integer,String> canonicalCodes,int value){
return canonicalCodes.get(value);
}
public int getLength(Map<Integer,String> codesH,int value){
return codesH.get(value).length();
}
}
@@ -0,0 +1,19 @@
package fr.iutfbleau.sae.mhuffman;
import java.util.Comparator;
import java.util.Map;
public class ComparateurCanonique implements Comparator<Map.Entry<Integer, String>> {
@Override
public int compare(Map.Entry<Integer, String> entree1,Map.Entry<Integer, String> entree2) {
int longueur1 = entree1.getValue().length();
int longueur2 = entree2.getValue().length();
if (longueur1 != longueur2) {
return longueur1 - longueur2;
}
return entree1.getKey() - entree2.getKey();
}
}
+115 -115
View File
@@ -1,115 +1,115 @@
package fr.iutfbleau.sae.mhuffman; package fr.iutfbleau.sae.mhuffman;
import fr.iutfbleau.sae.mimage.RGBImage; import fr.iutfbleau.sae.mimage.RGBImage;
/** /**
* Représente une table de fréquences pour une image RGB. * Représente une table de fréquences pour une image RGB.
* <p> * <p>
* Cette classe est utilisée dans le cadre de la compression de Huffman. * Cette classe est utilisée dans le cadre de la compression de Huffman.
* Elle compte le nombre d'occurrences de chaque valeur possible (0 à 255) * Elle compte le nombre d'occurrences de chaque valeur possible (0 à 255)
* pour chacune des composantes de couleur : rouge, vert et bleu. * pour chacune des composantes de couleur : rouge, vert et bleu.
* </p> * </p>
* *
* <p> * <p>
* Chaque composante est stockée dans un tableau de taille 256 : * Chaque composante est stockée dans un tableau de taille 256 :
* <ul> * <ul>
* <li>{@code freqR} pour le rouge</li> * <li>{@code freqR} pour le rouge</li>
* <li>{@code freqG} pour le vert</li> * <li>{@code freqG} pour le vert</li>
* <li>{@code freqB} pour le bleu</li> * <li>{@code freqB} pour le bleu</li>
* </ul> * </ul>
* L'indice du tableau correspond à la valeur de la composante, * L'indice du tableau correspond à la valeur de la composante,
* et la valeur stockée correspond à sa fréquence d'apparition. * et la valeur stockée correspond à sa fréquence d'apparition.
* </p> * </p>
* *
* <p> * <p>
* Cette table de fréquences sert ensuite à construire les arbres de Huffman * Cette table de fréquences sert ensuite à construire les arbres de Huffman
* nécessaires à la compression des données de l'image. * nécessaires à la compression des données de l'image.
* </p> * </p>
* *
* @author Algassimou Pellel Diallo * @author Algassimou Pellel Diallo
* @version 1.0 * @version 1.0
* @since 2025-12-13 * @since 2025-12-13
*/ */
public class FrequencyTable { public class FrequencyTable {
/** Tableau des fréquences pour la composante rouge (valeurs de 0 à 255). */ /** Tableau des fréquences pour la composante rouge (valeurs de 0 à 255). */
private int[] freqR; private int[] freqR;
/** Tableau des fréquences pour la composante verte (valeurs de 0 à 255). */ /** Tableau des fréquences pour la composante verte (valeurs de 0 à 255). */
private int[] freqG; private int[] freqG;
/** Tableau des fréquences pour la composante bleue (valeurs de 0 à 255). */ /** Tableau des fréquences pour la composante bleue (valeurs de 0 à 255). */
private int[] freqB; private int[] freqB;
/** /**
* Construit une table de fréquences vide. * Construit une table de fréquences vide.
* <p> * <p>
* Les trois tableaux de fréquences sont initialisés * Les trois tableaux de fréquences sont initialisés
* avec 256 cases mises à zéro. * avec 256 cases mises à zéro.
* </p> * </p>
*/ */
public FrequencyTable() { public FrequencyTable() {
this.freqR = new int[256]; this.freqR = new int[256];
this.freqG = new int[256]; this.freqG = new int[256];
this.freqB = new int[256]; this.freqB = new int[256];
} }
/** /**
* Calcule les fréquences des composantes RGB à partir d'une image. * Calcule les fréquences des composantes RGB à partir d'une image.
* <p> * <p>
* Pour chaque pixel de l'image, la méthode récupère les valeurs * Pour chaque pixel de l'image, la méthode récupère les valeurs
* rouge, verte et bleue, puis incrémente la case correspondante * rouge, verte et bleue, puis incrémente la case correspondante
* dans le tableau de fréquences associé. * dans le tableau de fréquences associé.
* </p> * </p>
* *
* @param img l'image RGB à analyser * @param img l'image RGB à analyser
*/ */
public void computeFromImage(RGBImage img) { public void computeFromImage(RGBImage img) {
/*Nb: une composante de couleur est un entier entre 0 et 255 qui représente la part de rouge,vert ou bleu /*Nb: une composante de couleur est un entier entre 0 et 255 qui représente la part de rouge,vert ou bleu
dans la couleur d'un pixel. dans la couleur d'un pixel.
*/ */
/* pour chaque composante de couleur de chaque pixel, on incrémente la fréquence correspondante, /* pour chaque composante de couleur de chaque pixel, on incrémente la fréquence correspondante,
c'est-à-dire on compte le nombre de fois que la composante apparaît dans l'image. c'est-à-dire on compte le nombre de fois que la composante apparaît dans l'image.
ex: si un pixel P a une composante rouge de 150, on incrémente freqR[150] de 1. ex: si un pixel P a une composante rouge de 150, on incrémente freqR[150] de 1.
puis on fait de même pour les composantes verte et bleue. puis on fait de même pour les composantes verte et bleue.
on répète ce processus pour tous les pixels de l'image. on répète ce processus pour tous les pixels de l'image.
*/ */
for (int ligne = 0; ligne < img.getWidth(); ligne++) { for (int ligne = 0; ligne < img.getWidth(); ligne++) {
for (int colonne = 0; colonne < img.getHeight(); colonne++) { for (int colonne = 0; colonne < img.getHeight(); colonne++) {
this.freqR[img.getPixel(ligne, colonne).getR()]++; // Incrémente la fréquence de la composante rouge this.freqR[img.getPixel(ligne, colonne).getR()]++; // Incrémente la fréquence de la composante rouge
this.freqG[img.getPixel(ligne, colonne).getG()]++; // Incrémente la fréquence de la composante verte this.freqG[img.getPixel(ligne, colonne).getG()]++; // Incrémente la fréquence de la composante verte
this.freqB[img.getPixel(ligne, colonne).getB()]++; // Incrémente la fréquence de la composante bleue this.freqB[img.getPixel(ligne, colonne).getB()]++; // Incrémente la fréquence de la composante bleue
} }
} }
} }
/** /**
* Retourne le tableau des fréquences de la composante rouge. * Retourne le tableau des fréquences de la composante rouge.
* *
* @return un tableau de 256 entiers représentant les fréquences du rouge * @return un tableau de 256 entiers représentant les fréquences du rouge
*/ */
public int[] getRed() { public int[] getRed() {
return this.freqR; return this.freqR;
} }
/** /**
* Retourne le tableau des fréquences de la composante verte. * Retourne le tableau des fréquences de la composante verte.
* *
* @return un tableau de 256 entiers représentant les fréquences du vert * @return un tableau de 256 entiers représentant les fréquences du vert
*/ */
public int[] getGreen() { public int[] getGreen() {
return this.freqG; return this.freqG;
} }
/** /**
* Retourne le tableau des fréquences de la composante bleue. * Retourne le tableau des fréquences de la composante bleue.
* *
* @return un tableau de 256 entiers représentant les fréquences du bleu * @return un tableau de 256 entiers représentant les fréquences du bleu
*/ */
public int[] getBlue() { public int[] getBlue() {
return this.freqB; return this.freqB;
} }
} }
+119 -119
View File
@@ -1,119 +1,119 @@
package fr.iutfbleau.sae.mhuffman; package fr.iutfbleau.sae.mhuffman;
/** /**
* Représente un nœud de l'arbre de Huffman. * Représente un nœud de l'arbre de Huffman.
* <p> * <p>
* Un {@code HuffmanNode} peut être : * Un {@code HuffmanNode} peut être :
* <ul> * <ul>
* <li>une feuille, contenant une valeur (symbole) et une fréquence</li> * <li>une feuille, contenant une valeur (symbole) et une fréquence</li>
* <li>un nœud interne, contenant uniquement une fréquence et deux enfants</li> * <li>un nœud interne, contenant uniquement une fréquence et deux enfants</li>
* </ul> * </ul>
* </p> * </p>
* *
* <p> * <p>
* Cette classe est une structure de données utilisée par {@code HuffmanTree} * Cette classe est une structure de données utilisée par {@code HuffmanTree}
* pour construire l'arbre de Huffman. * pour construire l'arbre de Huffman.
* </p> * </p>
* *
* @author Algassimou Pellel Diallo * @author Algassimou Pellel Diallo
* @version 1.1 * @version 1.1
* @since 2025-12-17 * @since 2025-12-17
*/ */
public class HuffmanNode { public class HuffmanNode {
/** Valeur de la composante (cette valeur est appelée symbole, voir l'histoire de huffman, tres interressant) représentée par ce nœud (si feuille). c'est la part de la composante (rouge, verte ou bleue) dans la couleur d'un pixel. */ /** Valeur de la composante (cette valeur est appelée symbole, voir l'histoire de huffman, tres interressant) représentée par ce nœud (si feuille). c'est la part de la composante (rouge, verte ou bleue) dans la couleur d'un pixel. */
private int value; private int value;
/** Fréquence du symbole (somme des fréquences des enfants). */ /** Fréquence du symbole (somme des fréquences des enfants). */
private int frequence; private int frequence;
/** Fils gauche du nœud (null si feuille). */ /** Fils gauche du nœud (null si feuille). */
private HuffmanNode left; private HuffmanNode left;
/** Fils droit du nœud (null si feuille). */ /** Fils droit du nœud (null si feuille). */
private HuffmanNode right; private HuffmanNode right;
/** /**
* Construit un nœud feuille de Huffman. * Construit un nœud feuille de Huffman.
* <p> * <p>
* Ce constructeur est utilisé pour représenter une valeur * Ce constructeur est utilisé pour représenter une valeur
* issue de la table de fréquences. * issue de la table de fréquences.
* </p> * </p>
* *
* @param value la valeur (symbole) représentée par ce nœud * @param value la valeur (symbole) représentée par ce nœud
* @param frequence la fréquence d'apparition de la valeur * @param frequence la fréquence d'apparition de la valeur
*/ */
public HuffmanNode(int value, int frequence) { public HuffmanNode(int value, int frequence) {
this.value = value; this.value = value;
this.frequence = frequence; this.frequence = frequence;
this.left = null; this.left = null;
this.right = null; this.right = null;
} }
/** /**
* Construit un nœud interne de Huffman. * Construit un nœud interne de Huffman.
* <p> * <p>
* Ce constructeur est utilisé lors de la fusion de deux nœuds * Ce constructeur est utilisé lors de la fusion de deux nœuds
* de plus faible fréquence lors de la construction de l'arbre. * de plus faible fréquence lors de la construction de l'arbre.
* </p> * </p>
* *
* @param left le fils gauche * @param left le fils gauche
* @param right le fils droit * @param right le fils droit
*/ */
public HuffmanNode(HuffmanNode left, HuffmanNode right) { public HuffmanNode(HuffmanNode left, HuffmanNode right) {
this.left = left; this.left = left;
this.right = right; this.right = right;
this.frequence = left.frequence + right.frequence; this.frequence = left.frequence + right.frequence;
} }
/** /**
* Indique si ce nœud est une feuille. * Indique si ce nœud est une feuille.
* *
* @return {@code true} si le nœud est une feuille, {@code false} sinon * @return {@code true} si le nœud est une feuille, {@code false} sinon
*/ */
public boolean isLeaf() { public boolean isLeaf() {
return this.left == null && this.right == null; return this.left == null && this.right == null;
} }
/** /**
* Retourne la fréquence du nœud. * Retourne la fréquence du nœud.
* *
* @return la fréquence * @return la fréquence
*/ */
public int getFrequence() { public int getFrequence() {
return this.frequence; return this.frequence;
} }
/** /**
* Retourne le fils gauche du nœud. * Retourne le fils gauche du nœud.
* *
* @return le fils gauche * @return le fils gauche
*/ */
public HuffmanNode getLeft() { public HuffmanNode getLeft() {
return this.left; return this.left;
} }
/** /**
* Retourne le fils droit du nœud. * Retourne le fils droit du nœud.
* *
* @return le fils droit * @return le fils droit
*/ */
public HuffmanNode getRight() { public HuffmanNode getRight() {
return this.right; return this.right;
} }
/** /**
* Retourne la valeur représentée par ce nœud. * Retourne la valeur représentée par ce nœud.
* <p> * <p>
* Cette méthode n'a de sens que si le nœud est une feuille. * Cette méthode n'a de sens que si le nœud est une feuille.
* </p> * </p>
* *
* @return la valeur du symbole * @return la valeur du symbole
*/ */
public int getValue() { public int getValue() {
if (!this.isLeaf()) { if (!this.isLeaf()) {
throw new IllegalStateException("La valeur n'est définie que pour les feuilles."); throw new IllegalStateException("La valeur n'est définie que pour les feuilles.");
} }
return this.value; return this.value;
} }
} }
+218 -218
View File
@@ -1,218 +1,218 @@
package fr.iutfbleau.sae.mhuffman; package fr.iutfbleau.sae.mhuffman;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
// test // test
/** /**
* Implémente un arbre de Huffman utilisé pour la compression de données. * Implémente un arbre de Huffman utilisé pour la compression de données.
* <p> * <p>
* La classe {@code HuffmanTree} est chargée de représenter la structure * La classe {@code HuffmanTree} est chargée de représenter la structure
* de l'arbre de Huffman et de générer les codes binaires associés aux symboles. * de l'arbre de Huffman et de générer les codes binaires associés aux symboles.
* Elle s'appuie sur la classe {@link HuffmanNode} pour représenter les nœuds * Elle s'appuie sur la classe {@link HuffmanNode} pour représenter les nœuds
* de l'arbre. * de l'arbre.
* </p> * </p>
* *
* <p> * <p>
* L'arbre est construit à partir des fréquences des symboles calculées * L'arbre est construit à partir des fréquences des symboles calculées
* en amont (par exemple à l'aide d'une {@code FrequencyTable}). * en amont (par exemple à l'aide d'une {@code FrequencyTable}).
* Chaque symbole est d'abord représenté par une feuille, puis les nœuds * Chaque symbole est d'abord représenté par une feuille, puis les nœuds
* sont combinés progressivement selon l'algorithme de Huffman afin * sont combinés progressivement selon l'algorithme de Huffman afin
* d'obtenir un arbre binaire optimal. * d'obtenir un arbre binaire optimal.
* </p> * </p>
* *
* <p> * <p>
* Une fois l'arbre construit, celui-ci est parcouru afin de générer une * Une fois l'arbre construit, celui-ci est parcouru afin de générer une
* table de correspondance associant à chaque symbole un code binaire unique. * table de correspondance associant à chaque symbole un code binaire unique.
* Les symboles les plus fréquents se retrouvent plus proches de la racine * Les symboles les plus fréquents se retrouvent plus proches de la racine
* et possèdent donc des codes plus courts, ce qui permet de réduire * et possèdent donc des codes plus courts, ce qui permet de réduire
* la taille des données compressées. * la taille des données compressées.
* </p> * </p>
* *
* <p> * <p>
* Cette classe ne s'occupe pas de la lecture ou de l'écriture des bits. * Cette classe ne s'occupe pas de la lecture ou de l'écriture des bits.
* Elle fournit uniquement la structure et les informations nécessaires * Elle fournit uniquement la structure et les informations nécessaires
* à la compression, qui sont ensuite exploitées par des flux binaires * à la compression, qui sont ensuite exploitées par des flux binaires
* dédiés. * dédiés.
* </p> * </p>
* *
* @author Algassimou Pellel Diallo,Ayoub Anhdire * @author Algassimou Pellel Diallo,Ayoub Anhdire
* @version 1.0 * @version 1.0
* @since 2025-12-13 * @since 2025-12-13
*/ */
public class HuffmanTree { public class HuffmanTree {
/** /**
* Racine de l'arbre de Huffman. * Racine de l'arbre de Huffman.
* <p> * <p>
* Ce nœud est le résultat final de la construction de l'arbre et constitue * Ce nœud est le résultat final de la construction de l'arbre et constitue
* le point de départ pour la génération des codes binaires ainsi que * le point de départ pour la génération des codes binaires ainsi que
* pour le décodage des données compressées. * pour le décodage des données compressées.
* </p> * </p>
*/ */
private HuffmanNode root; private HuffmanNode root;
/** /**
* Dictionnaire pour enregistrer les codes Huffman * Dictionnaire pour enregistrer les codes Huffman
*/ */
// j'ai retirer le static car chaque arbre a ses propres codes et j'utilise string plutot que int pour stocker les codes car on construit une chaine de 0 et de 1 // j'ai retirer le static car chaque arbre a ses propres codes et j'utilise string plutot que int pour stocker les codes car on construit une chaine de 0 et de 1
private Map<Integer, String> codes; private Map<Integer, String> codes;
/** /**
* Chaine de caracteres qui va nous permettre de sauvegader le code Huffman * Chaine de caracteres qui va nous permettre de sauvegader le code Huffman
* Permet en d'autres termes de construire la chaine de 1 et de 0 * Permet en d'autres termes de construire la chaine de 1 et de 0
*/ */
private String chaineCarac; private String chaineCarac;
/** /**
* Construit un arbre de Huffman. * Construit un arbre de Huffman.
* <p> * <p>
* Le constructeur est responsable de l'initialisation de la structure * Le constructeur est responsable de l'initialisation de la structure
* de l'arbre. En pratique, il combine les nœuds feuilles représentant * de l'arbre. En pratique, il combine les nœuds feuilles représentant
* les symboles par ordre croissant de fréquence jusqu'à obtenir un * les symboles par ordre croissant de fréquence jusqu'à obtenir un
* unique nœud racine. * unique nœud racine.
* </p> * </p>
* *
* <p> * <p>
* Les détails de la construction (structure de données utilisée, * Les détails de la construction (structure de données utilisée,
* ordre des fusions, etc.) sont volontairement séparés de la logique * ordre des fusions, etc.) sont volontairement séparés de la logique
* de génération des codes. * de génération des codes.
* </p> * </p>
*/ */
public HuffmanTree(int[] freq) { public HuffmanTree(int[] freq) {
// J'initialise la racine à null. // J'initialise la racine à null.
this.root = null; this.root = null;
// je cree une collection de feuilles // je cree une collection de feuilles
List<HuffmanNode> feuilles = new ArrayList<>(); List<HuffmanNode> feuilles = new ArrayList<>();
// pour chaque valeur(symbole) dans la table de frequence // pour chaque valeur(symbole) dans la table de frequence
for (int i = 0; i < freq.length; i++) { for (int i = 0; i < freq.length; i++) {
// si la frequence est superieure a 0 , on cree une feuille // si la frequence est superieure a 0 , on cree une feuille
if (freq[i] > 0) { if (freq[i] > 0) {
// pour la valeur (symbole) i avec frequence freq[i], on cree une feuille // pour la valeur (symbole) i avec frequence freq[i], on cree une feuille
HuffmanNode feuille = new HuffmanNode(i, freq[i]); HuffmanNode feuille = new HuffmanNode(i, freq[i]);
// on ajoute la feuille à la collection // on ajoute la feuille à la collection
feuilles.add(feuille); feuilles.add(feuille);
} }
} }
// On tri les feuilles par frequence croissante j'utilise un comparator qui compare la valeur retournee par getFrequence de chaque feuille // On tri les feuilles par frequence croissante j'utilise un comparator qui compare la valeur retournee par getFrequence de chaque feuille
// Referencement de methode avec :: // Referencement de methode avec ::
feuilles.sort(Comparator.comparingInt(HuffmanNode::getFrequence)); feuilles.sort(Comparator.comparingInt(HuffmanNode::getFrequence));
// flemme de faire un algo de tri alors que java le fait tres bien a voir a la fin si je vais coder une liste chainee avec un tri par insertion personnalise // flemme de faire un algo de tri alors que java le fait tres bien a voir a la fin si je vais coder une liste chainee avec un tri par insertion personnalise
// Fusion des nœuds jusqu'à obtenir la racine // Fusion des nœuds jusqu'à obtenir la racine
// Tant qu'il y a plus d'une feuille dans la collection // Tant qu'il y a plus d'une feuille dans la collection
while (feuilles.size() > 1) { while (feuilles.size() > 1) {
// je prends les deux feuilles de plus faible fréquence // je prends les deux feuilles de plus faible fréquence
HuffmanNode left = feuilles.remove(0); HuffmanNode left = feuilles.remove(0);
HuffmanNode right = feuilles.remove(0); HuffmanNode right = feuilles.remove(0);
// je crée un nœud interne en les combinant // je crée un nœud interne en les combinant
HuffmanNode parent = new HuffmanNode(left, right); HuffmanNode parent = new HuffmanNode(left, right);
// j'insère le nœud parent dans la collection à la bonne position pour maintenir l'ordre (plus performant qu'un tri complet à chaque itération) // j'insère le nœud parent dans la collection à la bonne position pour maintenir l'ordre (plus performant qu'un tri complet à chaque itération)
int index = 0; int index = 0;
// tant que l'index est dans les limites et que la frequence du noeud à l'index est inférieure à celle du parent // tant que l'index est dans les limites et que la frequence du noeud à l'index est inférieure à celle du parent
while (index < feuilles.size() && feuilles.get(index).getFrequence() < parent.getFrequence()) { while (index < feuilles.size() && feuilles.get(index).getFrequence() < parent.getFrequence()) {
index++; index++;
} }
feuilles.add(index, parent); feuilles.add(index, parent);
} }
// a la fin il ne reste qu'un seul noeud : la racine de l'arbre // a la fin il ne reste qu'un seul noeud : la racine de l'arbre
this.root = feuilles.get(0); this.root = feuilles.get(0);
} }
// Méthode pour générer les codes Huffman à partir de l'arbre // Méthode pour générer les codes Huffman à partir de l'arbre
// pourquoi string et pas int ? car on va construire une chaine de 0 et de 1 // pourquoi string et pas int ? car on va construire une chaine de 0 et de 1
/** /**
* @return Map on stockera les codes Huffman sous forme de dictionnaire * @return Map on stockera les codes Huffman sous forme de dictionnaire
*/ */
public Map<Integer,String> generateCodes() { public Map<Integer,String> generateCodes() {
/** /**
* Le but de cette méthode est de pouvoir generer les codes Huffman à partir de l'arbre : * Le but de cette méthode est de pouvoir generer les codes Huffman à partir de l'arbre :
* Les branches prendront comme valeur 1 ou 0 selon differents cas : * Les branches prendront comme valeur 1 ou 0 selon differents cas :
* 1 - si on saute vers un fils droit * 1 - si on saute vers un fils droit
* 0 - si on saute vers un fils gauche. * 0 - si on saute vers un fils gauche.
* On construit les codes qui partent de la racine jusqu'à notre objectif * On construit les codes qui partent de la racine jusqu'à notre objectif
*/ */
this.codes = new HashMap<>(); this.codes = new HashMap<>();
// je lance la methode recursive avec une chaine vide qui va se remplir au fur et à mesure // je lance la methode recursive avec une chaine vide qui va se remplir au fur et à mesure
generateCodesRec(this.root, ""); generateCodesRec(this.root, "");
return codes; return codes;
} }
private void generateCodesRec(HuffmanNode node, String prefiixe) { private void generateCodesRec(HuffmanNode node, String prefiixe) {
// Cas de base: si le noeud est une feuille, on ajoute le code au dictionnaire // Cas de base: si le noeud est une feuille, on ajoute le code au dictionnaire
if (node.isLeaf()) { if (node.isLeaf()) {
if (prefiixe.length() > 0){ if (prefiixe.length() > 0){
this.codes.put(node.getValue(), prefiixe); this.codes.put(node.getValue(), prefiixe);
}else{ }else{
this.codes.put(node.getValue(), "0"); this.codes.put(node.getValue(), "0");
} }
return; return;
} }
//Case general : sinon on continue a parcourir l'arbre //Case general : sinon on continue a parcourir l'arbre
// On va a gauche en ajoutant "0" au code // On va a gauche en ajoutant "0" au code
generateCodesRec(node.getLeft(), prefiixe + "0"); generateCodesRec(node.getLeft(), prefiixe + "0");
// On va a droite en ajoutant "1" au code // On va a droite en ajoutant "1" au code
generateCodesRec(node.getRight(), prefiixe + "1"); generateCodesRec(node.getRight(), prefiixe + "1");
// this.codes = new HashMap<>(); // this.codes = new HashMap<>();
// this.chaineCarac = new String(); // this.chaineCarac = new String();
// if(root.isLeaf()){ // if(root.isLeaf()){
// codes.put(root.getValue(),Integer.parseInt(chaineCarac)); // codes.put(root.getValue(),Integer.parseInt(chaineCarac));
// return codes; // return codes;
// } // }
// HuffmanNode temp = root; // HuffmanNode temp = root;
// if (root.getLeft() != null) { // if (root.getLeft() != null) {
// root = root.getLeft(); // root = root.getLeft();
// chaineCarac = chaineCarac + "0"; // chaineCarac = chaineCarac + "0";
// generateCodes(); // generateCodes();
// // on retire le dernier bit lorsqu'on remonte car sinon les codes seront faussés // // on retire le dernier bit lorsqu'on remonte car sinon les codes seront faussés
// chaineCarac = chaineCarac.substring(0, chaineCarac.length() - 1); // chaineCarac = chaineCarac.substring(0, chaineCarac.length() - 1);
// } // }
// if (temp.getRight() != null) { // if (temp.getRight() != null) {
// root = temp.getRight(); // root = temp.getRight();
// chaineCarac = chaineCarac + "1"; // chaineCarac = chaineCarac + "1";
// generateCodes(); // generateCodes();
// chaineCarac = chaineCarac.substring(0, chaineCarac.length() - 1); // chaineCarac = chaineCarac.substring(0, chaineCarac.length() - 1);
// } // }
// root = temp; // root = temp;
// return codes; // return codes;
} }
/** /**
* @return Dictionnaire des codes Huffman * @return Dictionnaire des codes Huffman
*/ */
public Map<Integer,String> getCodes(){ public Map<Integer,String> getCodes(){
return codes; return codes;
} }
/** /**
* @return le nœud racine de l'arbre de Huffman * @return le nœud racine de l'arbre de Huffman
*/ */
public HuffmanNode getRoot() { public HuffmanNode getRoot() {
return root; return root;
} }
} }
+37 -37
View File
@@ -1,38 +1,38 @@
package fr.iutfbleau.sae.mimage; package fr.iutfbleau.sae.mimage;
public class Pixel{ public class Pixel{
private int r; private int r;
private int g; private int g;
private int b; private int b;
//à completer //à completer
public Pixel(int red, int green, int blue){ public Pixel(int red, int green, int blue){
this.r=red; this.r=red;
this.g=green; this.g=green;
this.b=blue; this.b=blue;
} }
public int getB() { public int getB() {
return b; return b;
} }
public int getG() { public int getG() {
return g; return g;
} }
public int getR() { public int getR() {
return r; return r;
} }
public void setR(int r) { public void setR(int r) {
this.r = r; this.r = r;
} }
public void setB(int b) { public void setB(int b) {
this.b = b; this.b = b;
} }
public void setG(int g) { public void setG(int g) {
this.g = g; this.g = g;
} }
} }
+29 -29
View File
@@ -1,30 +1,30 @@
package fr.iutfbleau.sae.mimage; package fr.iutfbleau.sae.mimage;
public class RGBImage { public class RGBImage {
private int width; private int width;
private int height; private int height;
private Pixel [][] pixels; private Pixel [][] pixels;
public RGBImage (int lar, int haut){ public RGBImage (int lar, int haut){
this.width=lar; this.width=lar;
this.height=haut; this.height=haut;
this.pixels = new Pixel[this.width][this.height]; this.pixels = new Pixel[this.width][this.height];
} }
public int getWidth() { public int getWidth() {
return width; return width;
} }
public int getHeight() { public int getHeight() {
return height; return height;
} }
public void setPixel(int x, int y, Pixel p) { public void setPixel(int x, int y, Pixel p) {
this.pixels[x][y] = p; this.pixels[x][y] = p;
} }
public Pixel getPixel(int x, int y) { public Pixel getPixel(int x, int y) {
return this.pixels[x][y]; return this.pixels[x][y];
} }
} }
+23 -23
View File
@@ -1,23 +1,23 @@
package fr.iutfbleau.sae.mpif; package fr.iutfbleau.sae.mpif;
public class DecodeNode { public class DecodeNode {
public DecodeNode left; public DecodeNode left;
public DecodeNode right; public DecodeNode right;
public Integer value; // null si pas une feuille public Integer value; // null si pas une feuille
public DecodeNode() { public DecodeNode() {
this.left = null; this.left = null;
this.right = null; this.right = null;
this.value = null; this.value = null;
} }
public DecodeNode(DecodeNode left, DecodeNode right, Integer value) { public DecodeNode(DecodeNode left, DecodeNode right, Integer value) {
this.left = left; this.left = left;
this.right = right; this.right = right;
this.value = value; this.value = value;
} }
public boolean isLeaf() { public boolean isLeaf() {
return this.left == null && this.right == null; return this.left == null && this.right == null;
} }
} }
+82 -70
View File
@@ -1,70 +1,82 @@
package fr.iutfbleau.sae.mpif; package fr.iutfbleau.sae.mpif;
import fr.iutfbleau.sae.util.BitInputStream;
import fr.iutfbleau.sae.util.BitInputStream; import fr.iutfbleau.sae.util.BitOutputStream;
import fr.iutfbleau.sae.util.BitOutputStream; import java.io.FileInputStream;
import java.util.Map;
import java.io.FileInputStream; import fr.iutfbleau.sae.mimage.RGBImage;
import java.util.Map;
import fr.iutfbleau.sae.mimage.RGBImage; public class PIFReader {
private int width;
public class PIFReader { private int height;
private int[] lenR;
private int width; private int[] lenG;
private int height; private int[] lenB;
private int[] lenR;
private int[] lenG; public RGBImage read(String filepath)
private int[] lenB; throws Exception {
FileInputStream fis = new FileInputStream(filepath);
public RGBImage read(String filepath) BitInputStream lecteur = new BitInputStream(fis);
throws Exception {
FileInputStream fis = new FileInputStream(filepath); // je lis l'entête et les tables canoniques
BitInputStream lecteur = new BitInputStream(fis); this.readHeader(lecteur);
this.readCanonicalTables(lecteur);
// je lis l'entête et les tables canoniques
this.readHeader(lecteur); // je reconstructe les tables canoniques car dans le fichier on a juste les longueurs en bits
this.readCanonicalTables(lecteur); Map<String, Integer> canonR = rebuildCanonical(lenR);
Map<String, Integer> canonG = rebuildCanonical(lenG);
// je reconstructe les tables canoniques car dans le fichier on a juste les longueurs en bits Map<String, Integer> canonB = rebuildCanonical(lenB);
Map<String, Integer> canonR = rebuildCanonical(lenR);
Map<String, Integer> canonG = rebuildCanonical(lenG); DecodeNode trieR = buildDecodageTree(canonR);
Map<String, Integer> canonB = rebuildCanonical(lenB); DecodeNode trieG = buildDecodageTree(canonG);
DecodeNode trieB = buildDecodageTree(canonB);
DecodeNode trieR = buildDecodageTree(canonR);
DecodeNode trieG = buildDecodageTree(canonG); RGBImage img = decodePixels(lecteur, trieR, trieG, trieB);
DecodeNode trieB = buildDecodageTree(canonB);
lecteur.closeFlux();
RGBImage img = decodePixels(lecteur, trieR, trieG, trieB); return img;
}
lecteur.closeFlux();
return img; public void readHeader(BitInputStream in) {
} // La largeur et l'hauteur de l'image occupe chaqun deux octets soit 16 bits :
this.width = in.readBits(16);
public void readHeader(BitInputStream in) { this.height = in.readBits(16);
// TODO: Implement header reading }
}
public void readCanonicalTables(BitInputStream in) {
public void readCanonicalTables(BitInputStream in) { this.lenR = new int[256];
// TODO: Implement canonical table reading this.lenG = new int[256];
} this.lenB = new int[256];
public Map<String,Integer> rebuildCanonical(int[] lengths) { for (int i = 0; i < 256; i++){
// TODO: Implement canonical table reconstruction lenR[i] = in.readBits(8);
return null; }
} for (int j = 0; j < 256; j++){
lenG[j] = in.readBits(8);
public RGBImage decodePixels(BitInputStream in, DecodeNode red, DecodeNode green, DecodeNode blue) { }
// TODO: Implement pixel decoding
return null; for (int k = 0; k < 256; k++){
} lenB[k] = in.readBits(8);
}
public DecodeNode buildDecodageTree(Map<String,Integer> codes) { }
// TODO: Implement trie building
return null; public Map<String,Integer> rebuildCanonical(int[] lengths) {
} // TODO: Implement canonical table reconstruction
return null;
}
public RGBImage decodePixels(BitInputStream in, DecodeNode red, DecodeNode green, DecodeNode blue) {
} // TODO: Implement pixel decoding
return null;
}
public DecodeNode buildDecodageTree(Map<String,Integer> codes) {
// TODO: Implement trie building
return null;
}
}
+126 -126
View File
@@ -1,126 +1,126 @@
package fr.iutfbleau.sae.mpif; package fr.iutfbleau.sae.mpif;
import fr.iutfbleau.sae.mimage.RGBImage; import fr.iutfbleau.sae.mimage.RGBImage;
import fr.iutfbleau.sae.util.BitOutputStream; import fr.iutfbleau.sae.util.BitOutputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
public class PIFWriter { public class PIFWriter {
public void writeTOFile(String filepath, RGBImage image, public void writeTOFile(String filepath, RGBImage image,
Map<Integer,String> canonR, Map<Integer,String> canonR,
Map<Integer,String> canonG, Map<Integer,String> canonG,
Map<Integer,String> canonB) Map<Integer,String> canonB)
throws Exception { throws Exception {
// Création du flux de sortie binaire // Création du flux de sortie binaire
FileOutputStream fos =new FileOutputStream(filepath); FileOutputStream fos =new FileOutputStream(filepath);
BufferedOutputStream bos =new BufferedOutputStream(fos); BufferedOutputStream bos =new BufferedOutputStream(fos);
BitOutputStream ecriveur =new BitOutputStream(bos); BitOutputStream ecriveur =new BitOutputStream(bos);
// Écriture de l'en-tête // Écriture de l'en-tête
writeHeader(ecriveur, image.getWidth(), image.getHeight()); writeHeader(ecriveur, image.getWidth(), image.getHeight());
// Écriture des tables de longueurs des codes canoniques // Écriture des tables de longueurs des codes canoniques
writeTables(ecriveur, canonR, canonG, canonB); writeTables(ecriveur, canonR, canonG, canonB);
// Ecriture des pixels // Ecriture des pixels
encodePixels(ecriveur, image, canonR, canonG, canonB); encodePixels(ecriveur, image, canonR, canonG, canonB);
ecriveur.fermerFlux(); ecriveur.fermerFlux();
System.err.println("SYSTEME"); System.err.println("SYSTEME");
} }
// Ecriture de l'en-tête du fichier PIF (largeur et hauteur) // Ecriture de l'en-tête du fichier PIF (largeur et hauteur)
public void writeHeader(BitOutputStream out,int width, int height){ public void writeHeader(BitOutputStream out,int width, int height){
try { try {
out.writeBits(width >> 8 & 0xFF, 8); // octet de poids fort out.writeBits(width >> 8 & 0xFF, 8); // octet de poids fort
out.writeBits(width & 0xFF, 8); // octet de poids faible out.writeBits(width & 0xFF, 8); // octet de poids faible
out.writeBits(height >> 8 & 0xFF, 8); // octet de poids fort out.writeBits(height >> 8 & 0xFF, 8); // octet de poids fort
out.writeBits(height & 0xFF, 8); // octet de poids faible out.writeBits(height & 0xFF, 8); // octet de poids faible
} catch (Exception e) { } catch (Exception e) {
System.err.println("Erreur lors de l’écriture de len-tête du fichier PIF"); System.err.println("Erreur lors de l’écriture de len-tête du fichier PIF");
} }
} }
public void writeTables(BitOutputStream out, Map<Integer, String> canonR, public void writeTables(BitOutputStream out, Map<Integer, String> canonR,
Map<Integer, String> canonG, Map<Integer, String> canonB){ Map<Integer, String> canonG, Map<Integer, String> canonB){
try { try {
// Écriture des longueurs des codes canoniques pour chaque composante // Écriture des longueurs des codes canoniques pour chaque composante
for (int i = 0; i < 256; i++) { for (int i = 0; i < 256; i++) {
int len; int len;
if (canonR.containsKey(i)) { // petite securité (au cas où) if (canonR.containsKey(i)) { // petite securité (au cas où)
len = canonR.get(i).length(); len = canonR.get(i).length();
}else { }else {
len = 0; len = 0;
} }
out.writeBits(len, 8); out.writeBits(len, 8);
} }
for (int i = 0; i < 256; i++) { for (int i = 0; i < 256; i++) {
int len; int len;
if (canonG.containsKey(i)) { if (canonG.containsKey(i)) {
len = canonG.get(i).length(); len = canonG.get(i).length();
}else { }else {
len = 0; len = 0;
} }
out.writeBits(len, 8); out.writeBits(len, 8);
} }
for (int i = 0; i < 256; i++) { for (int i = 0; i < 256; i++) {
int len; int len;
if (canonB.containsKey(i)) { if (canonB.containsKey(i)) {
len = canonB.get(i).length(); len = canonB.get(i).length();
}else { }else {
len = 0; len = 0;
} }
out.writeBits(len, 8); out.writeBits(len, 8);
} }
} catch (IOException e) { } catch (IOException e) {
System.err.println("Erreur lors de l’écriture des tables de fréquences dans le fichier PIF"); System.err.println("Erreur lors de l’écriture des tables de fréquences dans le fichier PIF");
} }
} }
private void writeBitFromString(BitOutputStream out, String code){ private void writeBitFromString(BitOutputStream out, String code){
try { try {
for (int i = 0; i < code.length(); i++) { for (int i = 0; i < code.length(); i++) {
if (code.charAt(i) == '1') { if (code.charAt(i) == '1') {
out.writeBit(1); out.writeBit(1);
} else { } else {
out.writeBit(0); out.writeBit(0);
} }
} }
} catch (IOException e) { } catch (IOException e) {
System.err.println("Erreur lors de l’écriture des bits dans le fichier PIF"); System.err.println("Erreur lors de l’écriture des bits dans le fichier PIF");
} }
} }
// Méthode pour encoder les pixels de l'image en utilisant les codes canoniques // Méthode pour encoder les pixels de l'image en utilisant les codes canoniques
public void encodePixels(BitOutputStream out, RGBImage image, Map<Integer, String> canonRED, Map<Integer, String> canonGREEN, Map<Integer, String> canonBLUE){ public void encodePixels(BitOutputStream out, RGBImage image, Map<Integer, String> canonRED, Map<Integer, String> canonGREEN, Map<Integer, String> canonBLUE){
int width = image.getWidth(); int width = image.getWidth();
int height = image.getHeight(); int height = image.getHeight();
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
// Récupérer les valeurs R, G, B du pixel // Récupérer les valeurs R, G, B du pixel
int r = image.getPixel(x, y).getR(); int r = image.getPixel(x, y).getR();
int g = image.getPixel(x, y).getG(); int g = image.getPixel(x, y).getG();
int b = image.getPixel(x, y).getB(); int b = image.getPixel(x, y).getB();
// Écrire les codes dans le flux binaire // Écrire les codes dans le flux binaire
writeBitFromString(out, canonRED.get(r)); writeBitFromString(out, canonRED.get(r));
writeBitFromString(out, canonGREEN.get(g)); writeBitFromString(out, canonGREEN.get(g));
writeBitFromString(out, canonBLUE.get(b)); writeBitFromString(out, canonBLUE.get(b));
} }
} }
} }
} }
+127 -127
View File
@@ -1,127 +1,127 @@
package fr.iutfbleau.sae.util; package fr.iutfbleau.sae.util;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
/** /**
* Décorateur de flux permettant l'écriture binaire à granularité du bit. * Décorateur de flux permettant l'écriture binaire à granularité du bit.
* <p> * <p>
* Cette classe encapsule un {@link OutputStream} existant et permet * Cette classe encapsule un {@link OutputStream} existant et permet
* l'écriture de bits individuellement ou par groupes. * l'écriture de bits individuellement ou par groupes.
* Les bits sont accumulés afin de former des octets avant écriture. * Les bits sont accumulés afin de former des octets avant écriture.
* </p> * </p>
* <p> * <p>
* Utilisée notamment pour l'encodage des fichiers compressés * Utilisée notamment pour l'encodage des fichiers compressés
* (ex : format PIF utilisant des codes de Huffman). * (ex : format PIF utilisant des codes de Huffman).
* </p> * </p>
* @author Algassimou Pellel Diallo * @author Algassimou Pellel Diallo
* @version 1.0 * @version 1.0
* @since 2025-12-13 * @since 2025-12-13
*/ */
public class BitOutputStream { public class BitOutputStream {
/** Flux de sortie sous-jacent */ /** Flux de sortie sous-jacent */
private final OutputStream fluxSortie; private final OutputStream fluxSortie;
/** Octet en cours de construction */ /** Octet en cours de construction */
private int octetEnConstruction; private int octetEnConstruction;
/** Position du prochain bit à écrire (de 7 à 0) */ /** Position du prochain bit à écrire (de 7 à 0) */
private int positionBit; private int positionBit;
/** Indique si le flux est fermé */ /** Indique si le flux est fermé */
private boolean fluxFerme; private boolean fluxFerme;
/** /**
* Construit un écrivain binaire à partir d'un flux existant. * Construit un écrivain binaire à partir d'un flux existant.
* *
* @param fluxSortie flux de sortie à décorer * @param fluxSortie flux de sortie à décorer
* @throws IllegalArgumentException si le flux est nul * @throws IllegalArgumentException si le flux est nul
*/ */
public BitOutputStream(OutputStream fluxSortie) { public BitOutputStream(OutputStream fluxSortie) {
if (fluxSortie == null) { if (fluxSortie == null) {
throw new IllegalArgumentException("Le flux de sortie ne peut pas être nul"); throw new IllegalArgumentException("Le flux de sortie ne peut pas être nul");
} }
this.fluxSortie = fluxSortie; this.fluxSortie = fluxSortie;
this.octetEnConstruction = 0; this.octetEnConstruction = 0;
this.positionBit = 7; this.positionBit = 7;
this.fluxFerme = false; this.fluxFerme = false;
} }
/** /**
* Écrit un bit dans le flux binaire. * Écrit un bit dans le flux binaire.
* *
* @param bit bit à écrire (0 ou 1) * @param bit bit à écrire (0 ou 1)
* @throws IOException si une erreur d'écriture survient * @throws IOException si une erreur d'écriture survient
* @throws IllegalArgumentException si le bit n'est ni 0 ni 1 * @throws IllegalArgumentException si le bit n'est ni 0 ni 1
*/ */
public void writeBit(int bit) throws IOException { public void writeBit(int bit) throws IOException {
if (bit != 0 && bit != 1) { if (bit != 0 && bit != 1) {
throw new IllegalArgumentException("Le bit doit être 0 ou 1"); throw new IllegalArgumentException("Le bit doit être 0 ou 1");
} }
if (fluxFerme) { if (fluxFerme) {
throw new IOException("Le flux de sortie est fermé"); throw new IOException("Le flux de sortie est fermé");
} }
if (bit == 1) { if (bit == 1) {
this.octetEnConstruction = this.octetEnConstruction | (1 << this.positionBit); this.octetEnConstruction = this.octetEnConstruction | (1 << this.positionBit);
} }
this.positionBit--; this.positionBit--;
// si on atteint la fin de l'octet, on le grave dans le flux et rebolotte // si on atteint la fin de l'octet, on le grave dans le flux et rebolotte
if(this.positionBit < 0){ if(this.positionBit < 0){
this.fluxSortie.write(this.octetEnConstruction); this.fluxSortie.write(this.octetEnConstruction);
this.octetEnConstruction = 0; this.octetEnConstruction = 0;
this.positionBit = 7; this.positionBit = 7;
} }
} }
/** /**
* Écrit une séquence de bits correspondant à une valeur entière. * Écrit une séquence de bits correspondant à une valeur entière.
* *
* @param valeur valeur contenant les bits à écrire * @param valeur valeur contenant les bits à écrire
* @param nombreBits nombre de bits à écrire (strictement positif) * @param nombreBits nombre de bits à écrire (strictement positif)
* @throws IOException si une erreur d'écriture survient * @throws IOException si une erreur d'écriture survient
*/ */
public void writeBits(int valeur, int nombreBits) throws IOException { public void writeBits(int valeur, int nombreBits) throws IOException {
for (int i = nombreBits - 1; i >= 0; i--) { for (int i = nombreBits - 1; i >= 0; i--) {
int bit = (valeur >> i) & 1; int bit = (valeur >> i) & 1;
writeBit(bit); writeBit(bit);
} }
} }
/** /**
* Force l'écriture immédiate des données accumulées dans le flux sous-jacent. * Force l'écriture immédiate des données accumulées dans le flux sous-jacent.
* *
* @throws IOException si une erreur survient lors du flush * @throws IOException si une erreur survient lors du flush
*/ */
public void flush() throws IOException { public void flush() throws IOException {
if (fluxFerme) { if (fluxFerme) {
throw new IOException("Le flux de sortie est fermé"); throw new IOException("Le flux de sortie est fermé");
} }
// Si l'octet nes pas vide on le complete avec des 0 // Si l'octet nes pas vide on le complete avec des 0
if(this.positionBit < 7){ if(this.positionBit < 7){
this.fluxSortie.write(this.octetEnConstruction); this.fluxSortie.write(this.octetEnConstruction);
this.octetEnConstruction = 0; this.octetEnConstruction = 0;
this.positionBit = 7; this.positionBit = 7;
} }
this.fluxSortie.flush(); // Force l'écriture dans le flux sous-jacent dans le but de vider le buffer this.fluxSortie.flush(); // Force l'écriture dans le flux sous-jacent dans le but de vider le buffer
} }
/** /**
* Vide les buffers internes et ferme le flux de sortie. * Vide les buffers internes et ferme le flux de sortie.
* *
* @throws IOException si une erreur survient lors de la fermeture * @throws IOException si une erreur survient lors de la fermeture
*/ */
public void fermerFlux() throws IOException { public void fermerFlux() throws IOException {
// si le flux n'est pas déjà fermé // si le flux n'est pas déjà fermé
if (!fluxFerme) { if (!fluxFerme) {
this.flush(); // compléter l'octet et forcer l'écriture this.flush(); // compléter l'octet et forcer l'écriture
this.fluxSortie.close(); // fermer le flux sous-jacent this.fluxSortie.close(); // fermer le flux sous-jacent
this.fluxFerme = true; // marquer le flux comme fermé this.fluxFerme = true; // marquer le flux comme fermé
} }
} }
} }
+115 -115
View File
@@ -1,115 +1,115 @@
package fr.iutfbleau.sae.util; package fr.iutfbleau.sae.util;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
/** /**
* Décorateur de flux permettant la lecture binaire à granularité du bit. * Décorateur de flux permettant la lecture binaire à granularité du bit.
* <p> * <p>
* Cette classe encapsule un {@link InputStream} existant et fournit * Cette classe encapsule un {@link InputStream} existant et fournit
* des opérations de lecture bit par bit ou par groupes de bits. * des opérations de lecture bit par bit ou par groupes de bits.
* Elle ne gère ni l'ouverture ni la sélection du fichier source. * Elle ne gère ni l'ouverture ni la sélection du fichier source.
* </p> * </p>
* *
* <p> * <p>
* Utilisée notamment pour le décodage des fichiers compressés * Utilisée notamment pour le décodage des fichiers compressés
* (ex : format PIF utilisant des codes de Huffman). * (ex : format PIF utilisant des codes de Huffman).
* </p> * </p>
* *
* *
* *
* @author Algassimou Pellel Diallo * @author Algassimou Pellel Diallo
* @version 1.0 * @version 1.0
* @since 2025-12-13 * @since 2025-12-13
*/ */
public class BitInputStream { public class BitInputStream {
/** Flux d'entrée sous-jacent */ /** Flux d'entrée sous-jacent */
private final InputStream fluxEntree; private final InputStream fluxEntree;
/** Octet actuellement chargé depuis le flux */ /** Octet actuellement chargé depuis le flux */
private int octetCourant; private int octetCourant;
/** Position du bit courant dans l'octet (du bit 7 au bit 0) */ /** Position du bit courant dans l'octet (du bit 7 au bit 0) */
private int positionBit; private int positionBit;
/** Indique si la fin du flux a été atteinte */ /** Indique si la fin du flux a été atteinte */
private boolean finDeFlux; private boolean finDeFlux;
/** /**
* Construit un lecteur binaire à partir d'un flux existant. * Construit un lecteur binaire à partir d'un flux existant.
* *
* @param fluxEntree flux d'entrée à décorer * @param fluxEntree flux d'entrée à décorer
* @throws IllegalArgumentException si le flux est nul * @throws IllegalArgumentException si le flux est nul
*/ */
public BitInputStream(InputStream fluxEntree) { public BitInputStream(InputStream fluxEntree) {
if (fluxEntree == null) { if (fluxEntree == null) {
throw new IllegalArgumentException("Le flux d'entrée ne peut pas être nul"); throw new IllegalArgumentException("Le flux d'entrée ne peut pas être nul");
} }
this.fluxEntree = fluxEntree; this.fluxEntree = fluxEntree;
this.octetCourant = 0; this.octetCourant = 0;
this.positionBit = -1; // force la lecture d'un nouvel octet this.positionBit = -1; // force la lecture d'un nouvel octet
this.finDeFlux = false; this.finDeFlux = false;
} }
/** /**
* Lit un bit depuis le flux binaire. * Lit un bit depuis le flux binaire.
* *
* @return 0 ou 1 si un bit est lu, -1 si la fin du flux est atteinte * @return 0 ou 1 si un bit est lu, -1 si la fin du flux est atteinte
* @throws IOException si une erreur de lecture survient * @throws IOException si une erreur de lecture survient
*/ */
public int readBit() throws IOException { public int readBit() throws IOException {
if (finDeFlux) { if (finDeFlux) {
return -1; return -1;
} }
if (this.positionBit < 0) { if (this.positionBit < 0) {
int octetLu = this.fluxEntree.read(); int octetLu = this.fluxEntree.read();
if (octetLu == -1) { if (octetLu == -1) {
this.finDeFlux = true; this.finDeFlux = true;
} else { } else {
this.octetCourant = octetLu; this.octetCourant = octetLu;
this.positionBit = 7; this.positionBit = 7;
} }
} }
if (finDeFlux) { if (finDeFlux) {
return -1; return -1;
} }
int bit = (this.octetCourant >> this.positionBit) & 1; int bit = (this.octetCourant >> this.positionBit) & 1;
this.positionBit--; this.positionBit--;
return bit; return bit;
} }
/** /**
* Lit une séquence de bits consécutifs et les assemble dans un entier. * Lit une séquence de bits consécutifs et les assemble dans un entier.
* *
* @param nombreBits nombre de bits à lire (strictement positif) * @param nombreBits nombre de bits à lire (strictement positif)
* @return valeur entière correspondant aux bits lus, * @return valeur entière correspondant aux bits lus,
* ou -1 si la fin du flux est atteinte prématurément * ou -1 si la fin du flux est atteinte prématurément
* @throws IOException si une erreur de lecture survient * @throws IOException si une erreur de lecture survient
*/ */
public int readBits(int nombreBits) throws IOException { public int readBits(int nombreBits) throws IOException {
int res=0; int res=0;
for (int i = 0; i < nombreBits; i++) { for (int i = 0; i < nombreBits; i++) {
int bit = readBit(); int bit = readBit();
if (bit == -1) { if (bit == -1) {
return -1; return -1;
} }
res = (res << 1) | bit; res = (res << 1) | bit;
} }
return res; return res;
} }
/** /**
* Ferme le flux d'entrée sous-jacent. * Ferme le flux d'entrée sous-jacent.
* *
* @throws IOException si une erreur survient lors de la fermeture * @throws IOException si une erreur survient lors de la fermeture
*/ */
public void closeFlux() throws IOException { public void closeFlux() throws IOException {
this.fluxEntree.close(); this.fluxEntree.close();
} }
} }
+89 -89
View File
@@ -1,89 +1,89 @@
package fr.iutfbleau.sae.util; package fr.iutfbleau.sae.util;
/** /**
* Classe utilitaire regroupant des opérations de conversion entre * Classe utilitaire regroupant des opérations de conversion entre
* entiers et octets. * entiers et octets.
* <p> * <p>
* Elle est utilisée pour encoder et décoder les champs binaires * Elle est utilisée pour encoder et décoder les champs binaires
* du format PIF (largeur, hauteur, tailles, etc.). * du format PIF (largeur, hauteur, tailles, etc.).
* </p> * </p>
* *
* <p> * <p>
* Cette classe : * Cette classe :
* <ul> * <ul>
* <li>ne lit aucun fichier</li> * <li>ne lit aucun fichier</li>
* <li>n'écrit aucun fichier</li> * <li>n'écrit aucun fichier</li>
* <li>ne manipule pas les bits individuellement</li> * <li>ne manipule pas les bits individuellement</li>
* </ul> * </ul>
* Elle fournit uniquement des conversions octets ↔ entiers. * Elle fournit uniquement des conversions octets ↔ entiers.
* </p> * </p>
*/ */
public final class ByteUtils { public final class ByteUtils {
/** /**
* Constructeur privé empêchant l'instanciation. * Constructeur privé empêchant l'instanciation.
* <p> * <p>
* Cette classe est purement utilitaire et ne doit pas être instanciée. * Cette classe est purement utilitaire et ne doit pas être instanciée.
* </p> * </p>
*/ */
private ByteUtils() { private ByteUtils() {
// j'empêche l'instanciation // j'empêche l'instanciation
} }
/** /**
* Convertit un entier non négatif en deux octets (ordre big-endian). * Convertit un entier non négatif en deux octets (ordre big-endian).
* <p> * <p>
* L'octet de poids fort est placé en première position, * L'octet de poids fort est placé en première position,
* suivi de l'octet de poids faible. * suivi de l'octet de poids faible.
* </p> * </p>
* *
* @param value valeur entière à convertir (0 ≤ value ≤ 65535) * @param value valeur entière à convertir (0 ≤ value ≤ 65535)
* @return tableau de deux octets : [octetFort, octetFaible] M * @return tableau de deux octets : [octetFort, octetFaible] M
* @throws IllegalArgumentException si la valeur ne tient pas sur 2 octets * @throws IllegalArgumentException si la valeur ne tient pas sur 2 octets
*/ */
public static byte[] toBytes(int value) { public static byte[] toBytes(int value) {
if (value < 0 || value > 0xFFFF) { if (value < 0 || value > 0xFFFF) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"La valeur doit être comprise entre 0 et 65535" "La valeur doit être comprise entre 0 et 65535"
); );
} }
byte[] result = new byte[2]; byte[] result = new byte[2];
/* /*
* Extraction de l'octet de poids fort : * Extraction de l'octet de poids fort :
* - décalage de 8 bits vers la droite * - décalage de 8 bits vers la droite
* - masquage pour ne conserver que les 8 bits utiles * - masquage pour ne conserver que les 8 bits utiles
*/ */
result[0] = (byte) ((value >>> 8) & 0xFF); result[0] = (byte) ((value >>> 8) & 0xFF);
/* /*
* Extraction de l'octet de poids faible : * Extraction de l'octet de poids faible :
* - aucun décalage nécessaire * - aucun décalage nécessaire
* - masquage pour conserver les 8 bits de droite * - masquage pour conserver les 8 bits de droite
*/ */
result[1] = (byte) (value & 0xFF); result[1] = (byte) (value & 0xFF);
return result; return result;
} }
/** /**
* Reconstruit un entier à partir de deux octets (ordre big-endian). * Reconstruit un entier à partir de deux octets (ordre big-endian).
* <p> * <p>
* L'octet de poids fort est replacé dans les bits 15 à 8, * L'octet de poids fort est replacé dans les bits 15 à 8,
* puis combiné avec l'octet de poids faible. * puis combiné avec l'octet de poids faible.
* </p> * </p>
* *
* @param high octet de poids fort * @param high octet de poids fort
* @param low octet de poids faible * @param low octet de poids faible
* @return entier reconstruit à partir des deux octets * @return entier reconstruit à partir des deux octets
*/ */
public static int toInt(byte high, byte low) { public static int toInt(byte high, byte low) {
/* /*
* - masquage pour supprimer le signe des octets Java * - masquage pour supprimer le signe des octets Java
* - décalage de l'octet fort vers la gauche * - décalage de l'octet fort vers la gauche
* - combinaison des deux octets par un OU binaire * - combinaison des deux octets par un OU binaire
*/ */
return ((high & 0xFF) << 8) | (low & 0xFF); return ((high & 0xFF) << 8) | (low & 0xFF);
} }
} }
+7 -7
View File
@@ -1,8 +1,8 @@
package fr.iutfbleau.sae.util; package fr.iutfbleau.sae.util;
public class GestErreur { public class GestErreur {
public static void erreur(String message) { public static void erreur(String message) {
System.err.println("Erreur : " + message); System.err.println("Erreur : " + message);
System.exit(1); System.exit(1);
} }
} }
@@ -1,110 +1,110 @@
package fr.iutfbleau.sae.vconverter; package fr.iutfbleau.sae.vconverter;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.util.Map; import java.util.Map;
/** /**
* Panneau d'affichage des codes Huffman et canoniques. * Panneau d'affichage des codes Huffman et canoniques.
* Affiche les codes pour chaque composante de couleur (rouge, vert, bleu). * Affiche les codes pour chaque composante de couleur (rouge, vert, bleu).
* @author Algassimou * @author Algassimou
*/ */
public class CodeTablePanel extends JPanel { public class CodeTablePanel extends JPanel {
// Zones de texte pour les codes Huffman // Zones de texte pour les codes Huffman
private JTextArea textHuffRouge, textHuffVert, textHuffBleu; private JTextArea textHuffRouge, textHuffVert, textHuffBleu;
// Zones de texte pour les codes canoniques // Zones de texte pour les codes canoniques
private JTextArea textCanonRouge, textCanonVert, textCanonBleu; private JTextArea textCanonRouge, textCanonVert, textCanonBleu;
/** /**
* Constructeur qui initialise l'interface utilisateur. * Constructeur qui initialise l'interface utilisateur.
*/ */
public CodeTablePanel() { public CodeTablePanel() {
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
// Titre pour les codes Huffman // Titre pour les codes Huffman
JLabel titreHuff = new JLabel("Codes Huffman"); JLabel titreHuff = new JLabel("Codes Huffman");
titreHuff.setFont(new Font("SansSerif", Font.BOLD, 16)); titreHuff.setFont(new Font("SansSerif", Font.BOLD, 16));
add(titreHuff); add(titreHuff);
add(Box.createVerticalStrut(10)); add(Box.createVerticalStrut(10));
// Création des zones de texte pour les codes Huffman // Création des zones de texte pour les codes Huffman
textHuffRouge = creerZoneTexte("Rouge"); textHuffRouge = creerZoneTexte("Rouge");
textHuffVert = creerZoneTexte("Vert"); textHuffVert = creerZoneTexte("Vert");
textHuffBleu = creerZoneTexte("Bleu"); textHuffBleu = creerZoneTexte("Bleu");
// Séparateur // Séparateur
add(Box.createVerticalStrut(20)); add(Box.createVerticalStrut(20));
// Titre pour les codes canoniques // Titre pour les codes canoniques
JLabel titreCanon = new JLabel("Codes Canoniques"); JLabel titreCanon = new JLabel("Codes Canoniques");
titreCanon.setFont(new Font("SansSerif", Font.BOLD, 16)); titreCanon.setFont(new Font("SansSerif", Font.BOLD, 16));
add(titreCanon); add(titreCanon);
add(Box.createVerticalStrut(10)); add(Box.createVerticalStrut(10));
// Création des zones de texte pour les codes canoniques // Création des zones de texte pour les codes canoniques
textCanonRouge = creerZoneTexte("Rouge (Canonique)"); textCanonRouge = creerZoneTexte("Rouge (Canonique)");
textCanonVert = creerZoneTexte("Vert (Canonique)"); textCanonVert = creerZoneTexte("Vert (Canonique)");
textCanonBleu = creerZoneTexte("Bleu (Canonique)"); textCanonBleu = creerZoneTexte("Bleu (Canonique)");
} }
/** /**
* Crée une zone de texte avec une étiquette. * Crée une zone de texte avec une étiquette.
* @param titre Le titre à afficher au-dessus de la zone de texte * @param titre Le titre à afficher au-dessus de la zone de texte
* @return La zone de texte configurée * @return La zone de texte configurée
*/ */
private JTextArea creerZoneTexte(String titre) { private JTextArea creerZoneTexte(String titre) {
add(new JLabel(titre + ":")); add(new JLabel(titre + ":"));
JTextArea zone = new JTextArea(8, 30); JTextArea zone = new JTextArea(8, 30);
zone.setEditable(false); zone.setEditable(false);
zone.setFont(new Font("Monospaced", Font.PLAIN, 12)); zone.setFont(new Font("Monospaced", Font.PLAIN, 12));
JScrollPane scroll = new JScrollPane(zone); JScrollPane scroll = new JScrollPane(zone);
scroll.setPreferredSize(new Dimension(300, 120)); scroll.setPreferredSize(new Dimension(300, 120));
add(scroll); add(scroll);
add(Box.createVerticalStrut(10)); add(Box.createVerticalStrut(10));
return zone; return zone;
} }
/** /**
* Met à jour l'affichage des codes Huffman. * Met à jour l'affichage des codes Huffman.
* @param rouge Les codes pour la composante rouge * @param rouge Les codes pour la composante rouge
* @param vert Les codes pour la composante verte * @param vert Les codes pour la composante verte
* @param bleu Les codes pour la composante bleue * @param bleu Les codes pour la composante bleue
*/ */
public void updateCodes(Map<Integer, String> rouge, public void updateCodes(Map<Integer, String> rouge,
Map<Integer, String> vert, Map<Integer, String> vert,
Map<Integer, String> bleu) { Map<Integer, String> bleu) {
mettreAJourZoneTexte(textHuffRouge, rouge); mettreAJourZoneTexte(textHuffRouge, rouge);
mettreAJourZoneTexte(textHuffVert, vert); mettreAJourZoneTexte(textHuffVert, vert);
mettreAJourZoneTexte(textHuffBleu, bleu); mettreAJourZoneTexte(textHuffBleu, bleu);
} }
/** /**
* Met à jour l'affichage des codes canoniques. * Met à jour l'affichage des codes canoniques.
* @param rouge Les codes pour la composante rouge * @param rouge Les codes pour la composante rouge
* @param vert Les codes pour la composante verte * @param vert Les codes pour la composante verte
* @param bleu Les codes pour la composante bleue * @param bleu Les codes pour la composante bleue
*/ */
public void updateCanonicalCodes(Map<Integer, String> rouge, public void updateCanonicalCodes(Map<Integer, String> rouge,
Map<Integer, String> vert, Map<Integer, String> vert,
Map<Integer, String> bleu) { Map<Integer, String> bleu) {
mettreAJourZoneTexte(textCanonRouge, rouge); mettreAJourZoneTexte(textCanonRouge, rouge);
mettreAJourZoneTexte(textCanonVert, vert); mettreAJourZoneTexte(textCanonVert, vert);
mettreAJourZoneTexte(textCanonBleu, bleu); mettreAJourZoneTexte(textCanonBleu, bleu);
} }
/** /**
* Met à jour le contenu d'une zone de texte avec les codes fournis. * Met à jour le contenu d'une zone de texte avec les codes fournis.
* @param zone La zone de texte à mettre à jour * @param zone La zone de texte à mettre à jour
* @param codes Les codes à afficher * @param codes Les codes à afficher
*/ */
private void mettreAJourZoneTexte(JTextArea zone, Map<Integer, String> codes) { private void mettreAJourZoneTexte(JTextArea zone, Map<Integer, String> codes) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (Map.Entry<Integer, String> entry : codes.entrySet()) { for (Map.Entry<Integer, String> entry : codes.entrySet()) {
sb.append(String.format("%3d : %s%n", entry.getKey(), entry.getValue())); sb.append(String.format("%3d : %s%n", entry.getKey(), entry.getValue()));
} }
zone.setText(sb.toString()); zone.setText(sb.toString());
} }
} }
@@ -1,161 +1,161 @@
package fr.iutfbleau.sae.vconverter; package fr.iutfbleau.sae.vconverter;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.Map; import java.util.Map;
import java.awt.*; import java.awt.*;
import javax.swing.*; import javax.swing.*;
import fr.iutfbleau.sae.ConverterController; import fr.iutfbleau.sae.ConverterController;
import fr.iutfbleau.sae.ExportButtonListener; import fr.iutfbleau.sae.ExportButtonListener;
/** /**
* Fenêtre principale du convertisseur. * Fenêtre principale du convertisseur.
* *
* <p> * <p>
* Cette classe correspond à la vue principale de lapplication. * Cette classe correspond à la vue principale de lapplication.
* Elle centralise laffichage des informations liées à la conversion * Elle centralise laffichage des informations liées à la conversion
* dune image (aperçu, fréquences, codes). * dune image (aperçu, fréquences, codes).
* </p> * </p>
* *
* *
* <p> * <p>
* Elle sert de point dentrée unique pour la partie graphique * Elle sert de point dentrée unique pour la partie graphique
* </p> * </p>
*/ */
public class ConverterWindow extends JFrame { public class ConverterWindow extends JFrame {
private ImagePreviewPanel imagePreviewPanel; private ImagePreviewPanel imagePreviewPanel;
private FrequencyTablePanel frequencyTablePanel; private FrequencyTablePanel frequencyTablePanel;
private CodeTablePanel codeTablePanel; private CodeTablePanel codeTablePanel;
/** /**
* Crée la fenêtre principale du convertisseur. * Crée la fenêtre principale du convertisseur.
* *
* <p> * <p>
* Le constructeur initialise la fenêtre et met en place * Le constructeur initialise la fenêtre et met en place
* les différents panneaux graphiques utilisés pour laffichage. * les différents panneaux graphiques utilisés pour laffichage.
* </p> * </p>
*/ */
public ConverterWindow() { public ConverterWindow() {
// Configuration de la fenetre // Configuration de la fenetre
this.setTitle("Convertisseur PIF - Visualisation des données"); this.setTitle("Convertisseur PIF - Visualisation des données");
this.setSize(900, 600); this.setSize(900, 600);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null); // Centre la fenêtre this.setLocationRelativeTo(null); // Centre la fenêtre
this.setResizable(true); // on autorise le this.setResizable(true); // on autorise le
this.setLayout(new BorderLayout()); this.setLayout(new BorderLayout());
// Initialisation des panels // Initialisation des panels
this.imagePreviewPanel = new ImagePreviewPanel(); this.imagePreviewPanel = new ImagePreviewPanel();
this.frequencyTablePanel = new FrequencyTablePanel(); this.frequencyTablePanel = new FrequencyTablePanel();
this.codeTablePanel = new CodeTablePanel(); this.codeTablePanel = new CodeTablePanel();
// Je gere le panel principal // Je gere le panel principal
JPanel contentPanel = new JPanel(); JPanel contentPanel = new JPanel();
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
//contentPanel.setBackground(new Color(255, 0, 0)); // rouge vif pour demo //contentPanel.setBackground(new Color(255, 0, 0)); // rouge vif pour demo
contentPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); contentPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
// Titre // Titre
JLabel header = new JLabel(" Convertisseur PIF Visualisation des données "); JLabel header = new JLabel(" Convertisseur PIF Visualisation des données ");
header.setFont(new Font("SansSerif", Font.BOLD, 18)); header.setFont(new Font("SansSerif", Font.BOLD, 18));
header.setAlignmentX(Component.CENTER_ALIGNMENT); header.setAlignmentX(Component.CENTER_ALIGNMENT);
contentPanel.add(header); contentPanel.add(header);
contentPanel.add(Box.createRigidArea(new Dimension(0, 5))); // espace contentPanel.add(Box.createRigidArea(new Dimension(0, 5))); // espace
// Ajout du panel d'aperçu // Ajout du panel d'aperçu
contentPanel.add(imagePreviewPanel); contentPanel.add(imagePreviewPanel);
contentPanel.add(Box.createRigidArea(new Dimension(0, 5))); contentPanel.add(Box.createRigidArea(new Dimension(0, 5)));
// Ajout du panel des fréquences // Ajout du panel des fréquences
contentPanel.add(frequencyTablePanel); contentPanel.add(frequencyTablePanel);
contentPanel.add(Box.createRigidArea(new Dimension(0, 5))); contentPanel.add(Box.createRigidArea(new Dimension(0, 5)));
// Ajout panel des codes // Ajout panel des codes
contentPanel.add(codeTablePanel); contentPanel.add(codeTablePanel);
contentPanel.add(Box.createRigidArea(new Dimension(0, 5))); contentPanel.add(Box.createRigidArea(new Dimension(0, 5)));
// la section du scrollpane // la section du scrollpane
JScrollPane scrollPane = new JScrollPane(contentPanel); JScrollPane scrollPane = new JScrollPane(contentPanel);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.getVerticalScrollBar().setUnitIncrement(16); // scroll plus adouci fluide scrollPane.getVerticalScrollBar().setUnitIncrement(16); // scroll plus adouci fluide
this.add(scrollPane, BorderLayout.CENTER); this.add(scrollPane, BorderLayout.CENTER);
this.setVisible(true); this.setVisible(true);
} }
/** /**
* Met à jour limage affichée dans la zone daperçu. * Met à jour limage affichée dans la zone daperçu.
* *
* <p> * <p>
* Cette méthode est appelée lorsque limage à convertir * Cette méthode est appelée lorsque limage à convertir
* a été chargée. La fenêtre ne modifie pas limage : * a été chargée. La fenêtre ne modifie pas limage :
* elle la transmet simplement au panneau daperçu. * elle la transmet simplement au panneau daperçu.
* </p> * </p>
* *
* @param img image à afficher * @param img image à afficher
*/ */
public void setImagePreview(BufferedImage img) { public void setImagePreview(BufferedImage img) {
imagePreviewPanel.setImage(img); imagePreviewPanel.setImage(img);
} }
/** /**
* Met à jour laffichage des tables de fréquences. * Met à jour laffichage des tables de fréquences.
*/ */
public void setFrequencyTable(int[] freqR,int[] freqG,int[] freqB) { public void setFrequencyTable(int[] freqR,int[] freqG,int[] freqB) {
frequencyTablePanel.updateFrequencies(freqR,freqG,freqB); frequencyTablePanel.updateFrequencies(freqR,freqG,freqB);
} }
/** /**
* Met à jour laffichage des codes Huffman. * Met à jour laffichage des codes Huffman.
* *
* <p> * <p>
* Elle permet uniquement dafficher les codes * Elle permet uniquement dafficher les codes
* qui ont été produits par la partie traitement. * qui ont été produits par la partie traitement.
* </p> * </p>
*/ */
public void setHuffmanTable(Map<Integer, String> codesRouge, public void setHuffmanTable(Map<Integer, String> codesRouge,
Map<Integer, String> codesVert, Map<Integer, String> codesVert,
Map<Integer, String> codesBleu) { Map<Integer, String> codesBleu) {
codeTablePanel.updateCodes(codesRouge, codesVert, codesBleu); codeTablePanel.updateCodes(codesRouge, codesVert, codesBleu);
} }
/** /**
* Met à jour laffichage des codes canoniques. * Met à jour laffichage des codes canoniques.
* *
* <p> * <p>
* Les codes canoniques sont transmis au panneau * Les codes canoniques sont transmis au panneau
* chargé de leur affichage. * chargé de leur affichage.
* </p> * </p>
*/ */
public void setCanonicalTable(Map<Integer, String> codesRouge, public void setCanonicalTable(Map<Integer, String> codesRouge,
Map<Integer, String> codesVert, Map<Integer, String> codesVert,
Map<Integer, String> codesBleu) { Map<Integer, String> codesBleu) {
codeTablePanel.updateCanonicalCodes(codesRouge, codesVert, codesBleu); codeTablePanel.updateCanonicalCodes(codesRouge, codesVert, codesBleu);
} }
public void addSaveButton(ConverterController controller) { public void addSaveButton(ConverterController controller) {
JButton saveBtn = new JButton("Exporter en .pif"); JButton saveBtn = new JButton("Exporter en .pif");
ExportButtonListener ecouteur =new ExportButtonListener(controller); ExportButtonListener ecouteur =new ExportButtonListener(controller);
saveBtn.addActionListener(ecouteur); saveBtn.addActionListener(ecouteur);
// panneau du bas // panneau du bas
JPanel bottomPanel = new JPanel(); JPanel bottomPanel = new JPanel();
bottomPanel.setLayout(new FlowLayout(FlowLayout.CENTER)); bottomPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
bottomPanel.add(saveBtn); bottomPanel.add(saveBtn);
// ajoute au bas de la fenêtre // ajoute au bas de la fenêtre
this.add(bottomPanel, BorderLayout.SOUTH); this.add(bottomPanel, BorderLayout.SOUTH);
// rafraîchir l'affichage // rafraîchir l'affichage
this.revalidate(); this.revalidate();
this.repaint(); this.repaint();
} }
} }
@@ -1,70 +1,70 @@
package fr.iutfbleau.sae.vconverter; package fr.iutfbleau.sae.vconverter;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
public class FrequencyTablePanel extends JPanel { public class FrequencyTablePanel extends JPanel {
// 3 Zone de texte pour la fréquence du rouge , du vert et du bleu // 3 Zone de texte pour la fréquence du rouge , du vert et du bleu
private JTextArea freqRouge , freqVert , freqBleu; private JTextArea freqRouge , freqVert , freqBleu;
public FrequencyTablePanel() { public FrequencyTablePanel() {
setLayout(new BoxLayout(this , BoxLayout.Y_AXIS)); setLayout(new BoxLayout(this , BoxLayout.Y_AXIS));
setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
// Premiere étiquette pour les fréquences en géneral // Premiere étiquette pour les fréquences en géneral
JLabel etiquette1 = new JLabel("Frequence"); JLabel etiquette1 = new JLabel("Frequence");
etiquette1.setFont(new Font("SansSerif", Font.BOLD, 16)); etiquette1.setFont(new Font("SansSerif", Font.BOLD, 16));
super.add(etiquette1); super.add(etiquette1);
super.add(Box.createVerticalStrut(10)); super.add(Box.createVerticalStrut(10));
// Puis création de zone de texte pour le rouge , le vert et le bleu // Puis création de zone de texte pour le rouge , le vert et le bleu
this.freqRouge = creationZoneText("Rouge"); this.freqRouge = creationZoneText("Rouge");
this.freqVert = creationZoneText("Vert"); this.freqVert = creationZoneText("Vert");
this.freqBleu = creationZoneText("Bleu"); this.freqBleu = creationZoneText("Bleu");
} }
private JTextArea creationZoneText(String t) { private JTextArea creationZoneText(String t) {
super.add(new JLabel(t + ":")); super.add(new JLabel(t + ":"));
GridLayout gestionnaire_mise_en_page = new GridLayout(5,5,10,10); GridLayout gestionnaire_mise_en_page = new GridLayout(5,5,10,10);
JTextArea zone = new JTextArea(8, 30); JTextArea zone = new JTextArea(8, 30);
zone.setLayout(gestionnaire_mise_en_page); zone.setLayout(gestionnaire_mise_en_page);
zone.setEditable(false); zone.setEditable(false);
zone.setFont(new Font("Monospaced", Font.PLAIN, 12)); zone.setFont(new Font("Monospaced", Font.PLAIN, 12));
JScrollPane scroll = new JScrollPane(zone); JScrollPane scroll = new JScrollPane(zone);
scroll.setPreferredSize(new Dimension(300, 120)); scroll.setPreferredSize(new Dimension(300, 120));
add(scroll); add(scroll);
add(Box.createVerticalStrut(10)); add(Box.createVerticalStrut(10));
return zone; return zone;
} }
public void updateFrequencies(int[] freqR,int[] freqG,int[] freqB) { public void updateFrequencies(int[] freqR,int[] freqG,int[] freqB) {
mettreAJour(freqRouge,freqR); mettreAJour(freqRouge,freqR);
mettreAJour(freqVert,freqG); mettreAJour(freqVert,freqG);
mettreAJour(freqBleu,freqB); mettreAJour(freqBleu,freqB);
} }
public void mettreAJour(JTextArea zone,int[] frequence){ public void mettreAJour(JTextArea zone,int[] frequence){
StringBuilder string = new StringBuilder(); StringBuilder string = new StringBuilder();
for(int i = 0 ; i < frequence.length ; i++){ for(int i = 0 ; i < frequence.length ; i++){
string.append(String.format("%3d : %s%n", i, frequence[i])); string.append(String.format("%3d : %s%n", i, frequence[i]));
if(i%10 == 0 && i!=0){ if(i%10 == 0 && i!=0){
string.append("\n"); string.append("\n");
} }
} }
zone.setText(string.toString()); zone.setText(string.toString());
} }
} }
@@ -1,76 +1,76 @@
package fr.iutfbleau.sae.vconverter; package fr.iutfbleau.sae.vconverter;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import javax.swing.JPanel; import javax.swing.JPanel;
import java.awt.*; import java.awt.*;
/** /**
* Le panneau daperçu de limage. * Le panneau daperçu de limage.
* *
* <p> * <p>
* Ce panneau affiche un aperçu de limage en cours de conversion. * Ce panneau affiche un aperçu de limage en cours de conversion.
* </p> * </p>
*/ */
public class ImagePreviewPanel extends JPanel { public class ImagePreviewPanel extends JPanel {
private BufferedImage image; private BufferedImage image;
// je donne une taille préférée au panel // je donne une taille préférée au panel
public ImagePreviewPanel() { public ImagePreviewPanel() {
this.setPreferredSize(new Dimension(600, 800)); this.setPreferredSize(new Dimension(600, 800));
this.setMinimumSize(new Dimension(600, 800)); this.setMinimumSize(new Dimension(600, 800));
} }
public void setImage(BufferedImage img) { public void setImage(BufferedImage img) {
this.image = img; this.image = img;
repaint(); repaint();
} }
@Override @Override
protected void paintComponent(Graphics pinceau) { protected void paintComponent(Graphics pinceau) {
// Appel de la méthode parente pour effacer l'arrière-plan // Appel de la méthode parente pour effacer l'arrière-plan
super.paintComponent(pinceau); super.paintComponent(pinceau);
if (image == null) { if (image == null) {
return; return;
} }
// Recuperer les dimensions du panel pour centrer l'image // Recuperer les dimensions du panel pour centrer l'image
int panelWidth = this.getWidth(); int panelWidth = this.getWidth();
int panelHeight = this.getHeight(); int panelHeight = this.getHeight();
// Recuperer les dimensions de l'image // Recuperer les dimensions de l'image
int imgWidth = image.getWidth(); int imgWidth = image.getWidth();
int imgHeight = image.getHeight(); int imgHeight = image.getHeight();
// Je calcule le facteur du reduction (si l'image est trop grande) en gros le dezoom // Je calcule le facteur du reduction (si l'image est trop grande) en gros le dezoom
double scale = Math.min( double scale = Math.min(
(double) panelWidth / imgWidth, (double) panelWidth / imgWidth,
(double) panelHeight / imgHeight (double) panelHeight / imgHeight
); );
// Si l'image est plus petite que le panel, on ne la redimensionne pas donc scale = 1 // Si l'image est plus petite que le panel, on ne la redimensionne pas donc scale = 1
if (scale > 1.0) { if (scale > 1.0) {
scale = 1.0; scale = 1.0;
} }
// je recalcule les dimensions de l'image à dessiner // je recalcule les dimensions de l'image à dessiner
int drawWidth = (int) (imgWidth * scale); int drawWidth = (int) (imgWidth * scale);
int drawHeight = (int) (imgHeight * scale); int drawHeight = (int) (imgHeight * scale);
// Centrage de l'image dans le panel // Centrage de l'image dans le panel
int x = (panelWidth - drawWidth) / 2; int x = (panelWidth - drawWidth) / 2;
int y = (panelHeight - drawHeight) / 2; int y = (panelHeight - drawHeight) / 2;
Graphics2D pinceau2D = (Graphics2D) pinceau; Graphics2D pinceau2D = (Graphics2D) pinceau;
pinceau2D.setRenderingHint( pinceau2D.setRenderingHint(
RenderingHints.KEY_INTERPOLATION, RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR RenderingHints.VALUE_INTERPOLATION_BILINEAR
); );
pinceau2D.drawImage(image, x, y, drawWidth, drawHeight, this); pinceau2D.drawImage(image, x, y, drawWidth, drawHeight, this);
} }
} }
@@ -0,0 +1,7 @@
package fr.iutfbleau.sae.vviewer;
public class ImagePanel extends JPanel{
}
@@ -0,0 +1,11 @@
package fr.iutfbleau.sae.vviewer;
public class ViewerWindow extends JFrame{
}
+49 -49
View File
@@ -1,50 +1,50 @@
Learning log Problème de blocage de la fenêtre après la sauvegarde: Learning log Problème de blocage de la fenêtre après la sauvegarde:
Après avoir implémenté la fonctionnalité dexport au format PIF depuis linterface du convertisseur, Après avoir implémenté la fonctionnalité dexport au format PIF depuis linterface du convertisseur,
jai rencontré un problème important : une fois que je cliquais sur le bouton dexportation, jai rencontré un problème important : une fois que je cliquais sur le bouton dexportation,
la fenêtre se figeait complètement. Elle restait visible, mais impossible à fermer ou à interagir avec. la fenêtre se figeait complètement. Elle restait visible, mais impossible à fermer ou à interagir avec.
Le programme semblait bloqué. Le programme semblait bloqué.
En analysant le comportement et en utilisant des impressions de debug, jai constaté que le blocage n’était pas lié à l’écriture du En analysant le comportement et en utilisant des impressions de debug, jai constaté que le blocage n’était pas lié à l’écriture du
fichier ni à la logique du convertisseur, mais bien à un problème de gestion des threads dans Swing. fichier ni à la logique du convertisseur, mais bien à un problème de gestion des threads dans Swing.
Swing repose sur un fonctionnement particulier : toute linterface graphique est gérée par un seul thread dédié, Swing repose sur un fonctionnement particulier : toute linterface graphique est gérée par un seul thread dédié,
appelé lEvent Dispatch Thread (EDT). Ce thread est responsable de tout ce qui concerne linterface utilisateur : appelé lEvent Dispatch Thread (EDT). Ce thread est responsable de tout ce qui concerne linterface utilisateur :
la gestion des clics, le rafraîchissement de la fenêtre, la fermeture, le dessin et laffichage en général. la gestion des clics, le rafraîchissement de la fenêtre, la fermeture, le dessin et laffichage en général.
Tant que ce thread tourne correctement, lapplication reste réactive. Tant que ce thread tourne correctement, lapplication reste réactive.
LEDT ne démarre réellement quaprès lappel à la méthode permettant dafficher la fenêtre. À partir de ce moment, LEDT ne démarre réellement quaprès lappel à la méthode permettant dafficher la fenêtre. À partir de ce moment,
toutes les opérations qui modifient linterface devraient strictement être exécutées sur ce thread. toutes les opérations qui modifient linterface devraient strictement être exécutées sur ce thread.
Cest une règle fondamentale pour éviter les blocages. Cest une règle fondamentale pour éviter les blocages.
En examinant mon programme, je me suis rendu compte que le problème venait de la manière dont javais structuré mon point dentrée. En examinant mon programme, je me suis rendu compte que le problème venait de la manière dont javais structuré mon point dentrée.
Mon programme principal créait la fenêtre, le contrôleur, puis lançait immédiatement tout le processus de conversion, Mon programme principal créait la fenêtre, le contrôleur, puis lançait immédiatement tout le processus de conversion,
qui incluait le chargement du fichier image, le calcul des fréquences, la construction des arbres de Huffman, qui incluait le chargement du fichier image, le calcul des fréquences, la construction des arbres de Huffman,
la génération des codes canoniques, et éventuellement l’écriture du fichier PIF. la génération des codes canoniques, et éventuellement l’écriture du fichier PIF.
Ce sont des opérations potentiellement longues et qui se déroulaient sur le thread principal, Ce sont des opérations potentiellement longues et qui se déroulaient sur le thread principal,
avant même que lEDT ne prenne le relais pour gérer linterface. avant même que lEDT ne prenne le relais pour gérer linterface.
Cela avait pour conséquences que Swing se retrouvait dans un état instable, puisque certaines opérations graphiques avaient été réalisées Cela avait pour conséquences que Swing se retrouvait dans un état instable, puisque certaines opérations graphiques avaient été réalisées
hors du thread dédié. hors du thread dédié.
Cest exactement ce qui provoquait le gel de linterface : une fois le fichier enregistré, la fenêtre ne répondait plus car Cest exactement ce qui provoquait le gel de linterface : une fois le fichier enregistré, la fenêtre ne répondait plus car
lEDT était bloqué ou interrompu, empêchant toute interaction, y compris la fermeture de la fenêtre. lEDT était bloqué ou interrompu, empêchant toute interaction, y compris la fermeture de la fenêtre.
Une fois le problème identifié, les solutions étaient claires. Il fallait sassurer que toutes les opérations qui touchent Une fois le problème identifié, les solutions étaient claires. Il fallait sassurer que toutes les opérations qui touchent
a linterface graphique soient exécutées sur lEvent Dispatch Thread. Cela signifie que toute interaction, y compris louverture a linterface graphique soient exécutées sur lEvent Dispatch Thread. Cela signifie que toute interaction, y compris louverture
dun sélecteur de fichiers, doit obligatoirement être déclenchée dans ce contexte. De plus, il fallait veiller à ne pas exécuter dun sélecteur de fichiers, doit obligatoirement être déclenchée dans ce contexte. De plus, il fallait veiller à ne pas exécuter
de longues opérations synchrones avant le démarrage complet de lEDT. de longues opérations synchrones avant le démarrage complet de lEDT.
La solution consiste donc à déléguer lappel du processus de conversion au thread graphique, en utilisant le mécanisme fourni par La solution consiste donc à déléguer lappel du processus de conversion au thread graphique, en utilisant le mécanisme fourni par
Swing pour garantir que le code sexécute sur lEDT. Une autre possibilité serait dexécuter les opérations lourdes dans un thread en Swing pour garantir que le code sexécute sur lEDT. Une autre possibilité serait dexécuter les opérations lourdes dans un thread en
arrière-plan pour éviter de bloquer linterface, mais dans tous les cas le respect strict de la séparation entre traitements et interface arrière-plan pour éviter de bloquer linterface, mais dans tous les cas le respect strict de la séparation entre traitements et interface
est essentiel. est essentiel.
Grâce à cette analyse, jai mieux compris la manière dont Swing gère les threads et jai pu corriger la structure de mon programme Grâce à cette analyse, jai mieux compris la manière dont Swing gère les threads et jai pu corriger la structure de mon programme
afin quil reste totalement réactif, même après lexport. Cette expérience ma rappelé limportance de maîtriser les principes fondamentaux afin quil reste totalement réactif, même après lexport. Cette expérience ma rappelé limportance de maîtriser les principes fondamentaux
des bibliothèques graphiques et leurs contraintes en matière de multithreading. des bibliothèques graphiques et leurs contraintes en matière de multithreading.
le proble aussi avec mon flush infini le proble aussi avec mon flush infini
BIN
View File
Binary file not shown.