This commit is contained in:
AlgaLaptop
2026-01-02 20:52:44 +01:00
parent 2e0f44d28d
commit bceb70c052
31 changed files with 197 additions and 341 deletions
+12 -9
View File
@@ -120,29 +120,32 @@ Objectif : Lecture du fichier `.pif` + visualisateur opérationnel
| US | Assigné | Statut | | Description |
|----------|---------|--------|-----|-------------|
| US-D7 | AD | TODO | 🟥 | Reconstruire les codes canoniques depuis fichier |
| US-D8 | AD | TODO | 🟥 | Décoder pixels (implémenter PIFReader) |
| US-D7 | AD | DONE | 🟩 | Reconstruire les codes canoniques depuis fichier |
| US-D8 | AD | DONE | 🟩 | Décoder pixels (implémenter PIFReader) |
| US-U1 | YB | TODO | 🟥 | Ouvrir `.pif` via argument ou JFileChooser |
| US-U2 | AA | TODO | 🟥 | Afficher limage dans une fenêtre |
| US-U3 | AA | TODO | 🟥 | Centrer limage si elle est petite |
| US-U4 | AD | TODO | 🟥 | Déplacement de limage à la souris |
| US-P6 | AA | TODO | 🟥 | Préparer diagrammes UML |
## Fichiers à créer Sprint 3
## Fichiers à créer Sprint 2
## Fichiers à créer Sprint 3
### `src/mpif/`
| Nom du fichier | Rôle | US |
|----------------|-------|----|
| `PIFWriter.java` | Écriture du fichier `.pif` | US-C5, US-U6 |
| `PIFReader.java` | Lecture et décodage du `.pif` | US-D7, US-D8 |
### `src/vconverter/` (complément)
### `src/vviewer/`
| Nom du fichier | Rôle | US |
|----------------|-------|----|
| `SavePanel.java` *(optionnel)* | Interface de sauvegarde `.pif` | US-U6 |
| `ViewerWindow.java` | Fenêtre visualisation | US-U2 |
| `ImagePanel.java` | Affichage + déplacement image | US-U2, US-U3, US-U4 |
### `src/`
| Nom du fichier | Rôle | US |
|----------------|-------|----|
| `ViewerController.java` | Contrôle du visualisateur | US-U1..U4 |
| `Viewer.java` | Programme principal du visualisateur | US-U1 |
---
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,67 +0,0 @@
@startuml
skinparam packageStyle rectangle
' ============================
' MODEL (bleu)
' ============================
package mimage {
class Pixel #aliceblue
class RGBImage #aliceblue
}
RGBImage *-- Pixel
package mhuffman {
class FrequencyTable #aliceblue
class HuffmanNode #aliceblue
class HuffmanTree #aliceblue
class CanonicalCode #aliceblue
}
HuffmanTree *-- HuffmanNode
FrequencyTable ..> RGBImage
CanonicalCode ..> HuffmanTree
package util {
class BitInputStream #aliceblue
class BitOutputStream #aliceblue
class ByteUtils #aliceblue
class FileUtils #aliceblue
}
package mpif {
class PIFWriter #aliceblue
}
PIFWriter ..> RGBImage
PIFWriter ..> CanonicalCode
PIFWriter ..> BitOutputStream
' ============================
' VIEW (vert)
' ============================
package vconverter {
class ConverterWindow #palegreen
class PreviewPanel #palegreen
class FrequencyTablePanel #palegreen
class CodeTablePanel #palegreen
}
ConverterWindow *-- PreviewPanel
ConverterWindow *-- FrequencyTablePanel
ConverterWindow *-- CodeTablePanel
' ============================
' CONTROLLER (orange)
' ============================
class ConverterController #ffddaa
ConverterController ..> RGBImage
ConverterController ..> FrequencyTable
ConverterController ..> HuffmanTree
ConverterController ..> CanonicalCode
ConverterController ..> PIFWriter
@enduml
Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

@@ -1,206 +0,0 @@
@startuml
skinparam packageStyle rectangle
' ============================
' PACKAGE mimage (MODEL - BLEU)
' ============================
package mimage {
class Pixel #aliceblue {
- r : int
- g : int
- b : int
+ Pixel(r, g, b)
+ getR()
+ getG()
+ getB()
+ setR(r)
+ setG(g)
+ setB(b)
}
class RGBImage #aliceblue {
- width : int
- height : int
- pixels : Pixel[*]
+ RGBImage(width, height)
+ getWidth()
+ getHeight()
+ getPixel(x, y)
+ setPixel(x, y, p)
}
}
RGBImage *-- Pixel : contient
' ============================
' PACKAGE mhuffman (MODEL - BLEU)
' ============================
package mhuffman {
class FrequencyTable #aliceblue {
- freqR : int[*]
- freqG : int[*]
- freqB : int[*]
+ computeFromImage(img)
+ getRed()
+ getGreen()
+ getBlue()
}
class HuffmanNode #aliceblue {
- value : int
- frequency : int
- left : HuffmanNode
- right : HuffmanNode
+ setLeft(HuffmanNode lef): void
+ setRight(HuffmanNode rig): void
+ isLead(HuffmanNode f): boolean
+ getFrequence(): int
}
class HuffmanTree #aliceblue {
- root : HuffmanNode
+ generateCodes()
}
class CanonicalCode #aliceblue {
- codeLengths : Map
- canonicalCodes : Map
+ getCode(value)
+ getLength(value)
}
}
FrequencyTable ..> RGBImage : analyse
HuffmanTree *-- HuffmanNode
CanonicalCode ..> HuffmanTree : dérive
' ============================
' PACKAGE util (MODEL - BLEU)
' ============================
package util {
class BitInputStream #aliceblue {
- fluxEntree
- octetCourant : int
- positionBit : int
- finDeFlux : boolean
+ BitInputStream(fluxEntree)
+ readBit() : int
+ readBits(nombreBits) : int
+ closeFlux()
}
class BitOutputStream #aliceblue {
- fluxSortie
- octetEnConstruction : int
- positionBit : int
- fluxFerme : boolean
+ BitOutputStream(fluxSortie)
+ writeBit(bit)
+ writeBits(valeur, nombreBits)
+ flush()
+ fermerFlux()
}
class ByteUtils #aliceblue {
<<utilitaire>>
+ toInt(high, low) : int
+ toBytes(value) : byte[]
}
class FileUtils #aliceblue {
<<utilitaire>>
+ isPIFFile(f) : boolean
+ readBinaryFile(path)
}
}
' ============================
' PACKAGE mpif (MODEL - BLEU) — Sprint 2
' ============================
package mpif {
class PIFWriter #aliceblue {
+ writeHeader(out, width, height)
+ writeTables(out, red, green, blue)
+ encodePixels(out, img, red, green, blue)
+ writeToFile(path, img, red, green, blue)
}
' (optionnel) PIFImage si tu souhaite la représenter
class PIFImage #aliceblue {
- width : int
- height : int
- lenR : int[*]
- lenG : int[*]
- lenB : int[*]
- pixelData
}
}
PIFWriter ..> RGBImage
PIFWriter ..> CanonicalCode
PIFWriter ..> BitOutputStream
' ============================
' PACKAGE vconverter (VIEW - VERT)
' ============================
package vconverter {
class ConverterWindow #palegreen {
+ setImagePreview(img)
+ setFrequencyTable()
+ setHuffmanTable()
+ setCanonicalTable()
}
class PreviewPanel #palegreen {
- image
+ setImage(img)
}
class FrequencyTablePanel #palegreen {
+ updateFrequencies()
}
class CodeTablePanel #palegreen {
+ updateCodes()
}
ConverterWindow *-- PreviewPanel
ConverterWindow *-- FrequencyTablePanel
ConverterWindow *-- CodeTablePanel
}
' ============================
' CONTROLLER (ORANGE)
' ============================
class ConverterController #ffddaa {
+ loadImage(path)
+ computeFrequencies()
+ computeHuffman()
+ computeCanonical()
+ saveAsPIF(path)
}
ConverterController ..> RGBImage
ConverterController ..> FrequencyTable
ConverterController ..> HuffmanTree
ConverterController ..> CanonicalCode
ConverterController ..> PIFWriter : écrit le .pif
@enduml
Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

+6 -2
View File
@@ -2,6 +2,7 @@
JAVAC = javac
JAVA = java
JAVADOC = javadoc
ARGS =
# Dossiers
SRC = src
@@ -124,12 +125,14 @@ $(BIN)/$(PKG_PATH)/mpif/PIFWriter.class: $(BIN) \
$(JAVAC) -cp $(BIN) -d $(BIN) $(SRC)/$(PKG_PATH)/mpif/PIFWriter.java
# GROSSE compilation du listener + ConvertController + ConvertWindow car il y a une dependance cirulaire
# GROSSE compilation du listener + ConvertController + ConvertWindow + PIFSaverTask car il y a une dependance cirulaire
$(BIN)/$(PKG_PATH)/ConverterController.class \
$(BIN)/$(PKG_PATH)/ExportButtonListener.class \
$(BIN)/$(PKG_PATH)/PIFSaveTask.class \
$(BIN)/$(PKG_PATH)/vconverter/ConverterWindow.class: \
$(SRC)/$(PKG_PATH)/ConverterController.java \
$(SRC)/$(PKG_PATH)/ExportButtonListener.java \
$(SRC)/$(PKG_PATH)/PIFSaveTask.java \
$(SRC)/$(PKG_PATH)/vconverter/ConverterWindow.java \
$(BIN)/$(PKG_PATH)/mimage/Pixel.class \
$(BIN)/$(PKG_PATH)/mimage/RGBImage.class \
@@ -143,12 +146,13 @@ $(BIN)/$(PKG_PATH)/mpif/PIFWriter.class | $(BIN)
@$(JAVAC) -cp $(BIN) -d $(BIN) \
$(SRC)/$(PKG_PATH)/ConverterController.java \
$(SRC)/$(PKG_PATH)/ExportButtonListener.java \
$(SRC)/$(PKG_PATH)/PIFSaveTask.java \
$(SRC)/$(PKG_PATH)/vconverter/ConverterWindow.java
# Exécution
run-conv: all
$(JAVA) -cp $(BIN) $(MAIN_CONVERTER)
$(JAVA) -cp $(BIN) $(MAIN_CONVERTER) $(ARGS)
run-view: all
$(JAVA) -cp $(BIN) $(MAIN_VIEWER)
@@ -45,6 +45,8 @@ public class ConverterController {
this.fen = fen;
this.outputPath = out;
this.inputPath = in;
System.out.println(this.inputPath+" ==> "+this.outputPath);
}
@@ -166,7 +168,9 @@ public class ConverterController {
chooser.setDialogTitle("Enregistrer le fichier .pif");
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é
new Thread(() -> saveAsPIF(chooser.getSelectedFile().getAbsolutePath())).start();
System.out.println("Fichier sauvegardé : " + chooser.getSelectedFile().getAbsolutePath());
JOptionPane.showMessageDialog(null, "Fichier sauvegardé avec succès : " + chooser.getSelectedFile().getName());
}
@@ -175,7 +179,6 @@ public class ConverterController {
} catch (Exception ex) {
System.out.println("Erreur lors de la sauvegarde : " + ex.getMessage());
ex.printStackTrace();
}
}
@@ -193,6 +196,7 @@ public class ConverterController {
this.loadImage(choosser.getSelectedFile());
}else {
System.err.println("Aucune image choisie. Arrêt du programme.");
System.exit(1);
return;
}
}
@@ -1,16 +1,17 @@
package fr.iutfbleau.sae;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.SwingUtilities;
public class ExportButtonListener implements ActionListener {
private ConverterController controller;
private PIFSaveTask saveTask;
public ExportButtonListener(ConverterController controller){
this.controller = controller;
this.saveTask = new PIFSaveTask(controller);
}
@Override
public void actionPerformed(ActionEvent e){
controller.saveViaBtn();
SwingUtilities.invokeLater(saveTask);
}
}
+13
View File
@@ -0,0 +1,13 @@
package fr.iutfbleau.sae;
public class PIFSaveTask implements Runnable{
private ConverterController controller;
public PIFSaveTask(ConverterController c) {
this.controller = c;
}
@Override
public void run() {
controller.saveViaBtn();
}
}
+23
View File
@@ -0,0 +1,23 @@
package fr.iutfbleau.sae.mpif;
public class DecodeNode {
public DecodeNode left;
public DecodeNode right;
public Integer value; // null si pas une feuille
public DecodeNode() {
this.left = null;
this.right = null;
this.value = null;
}
public DecodeNode(DecodeNode left, DecodeNode right, Integer value) {
this.left = left;
this.right = right;
this.value = value;
}
public boolean isLeaf() {
return this.left == null && this.right == null;
}
}
+70
View File
@@ -0,0 +1,70 @@
package fr.iutfbleau.sae.mpif;
import fr.iutfbleau.sae.util.BitInputStream;
import fr.iutfbleau.sae.util.BitOutputStream;
import java.io.FileInputStream;
import java.util.Map;
import fr.iutfbleau.sae.mimage.RGBImage;
public class PIFReader {
private int width;
private int height;
private int[] lenR;
private int[] lenG;
private int[] lenB;
public RGBImage read(String filepath)
throws Exception {
FileInputStream fis = new FileInputStream(filepath);
BitInputStream lecteur = new BitInputStream(fis);
// je lis l'entête et les tables canoniques
this.readHeader(lecteur);
this.readCanonicalTables(lecteur);
// je reconstructe les tables canoniques car dans le fichier on a juste les longueurs en bits
Map<String, Integer> canonR = rebuildCanonical(lenR);
Map<String, Integer> canonG = rebuildCanonical(lenG);
Map<String, Integer> canonB = rebuildCanonical(lenB);
DecodeNode trieR = buildDecodageTree(canonR);
DecodeNode trieG = buildDecodageTree(canonG);
DecodeNode trieB = buildDecodageTree(canonB);
RGBImage img = decodePixels(lecteur, trieR, trieG, trieB);
lecteur.closeFlux();
return img;
}
public void readHeader(BitInputStream in) {
// TODO: Implement header reading
}
public void readCanonicalTables(BitInputStream in) {
// TODO: Implement canonical table reading
}
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;
}
}
+45 -43
View File
@@ -2,11 +2,38 @@ package fr.iutfbleau.sae.mpif;
import fr.iutfbleau.sae.mimage.RGBImage;
import fr.iutfbleau.sae.util.BitOutputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
public class PIFWriter {
public void writeTOFile(String filepath, RGBImage image,
Map<Integer,String> canonR,
Map<Integer,String> canonG,
Map<Integer,String> canonB)
throws Exception {
// Création du flux de sortie binaire
FileOutputStream fos =new FileOutputStream(filepath);
BufferedOutputStream bos =new BufferedOutputStream(fos);
BitOutputStream ecriveur =new BitOutputStream(bos);
// Écriture de l'en-tête
writeHeader(ecriveur, image.getWidth(), image.getHeight());
// Écriture des tables de longueurs des codes canoniques
writeTables(ecriveur, canonR, canonG, canonB);
// Ecriture des pixels
encodePixels(ecriveur, image, canonR, canonG, canonB);
ecriveur.fermerFlux();
System.err.println("SYSTEME");
}
// Ecriture de l'en-tête du fichier PIF (largeur et hauteur)
public void writeHeader(BitOutputStream out,int width, int height){
try {
@@ -26,7 +53,7 @@ public class PIFWriter {
try {
// Écriture des longueurs des codes canoniques pour chaque composante
for (int i = 0; i < 10; i++) {
for (int i = 0; i < 256; i++) {
int len;
if (canonR.containsKey(i)) { // petite securité (au cas où)
len = canonR.get(i).length();
@@ -36,7 +63,7 @@ public class PIFWriter {
out.writeBits(len, 8);
}
for (int i = 0; i < 10; i++) {
for (int i = 0; i < 256; i++) {
int len;
if (canonG.containsKey(i)) {
len = canonG.get(i).length();
@@ -45,7 +72,7 @@ public class PIFWriter {
}
out.writeBits(len, 8);
}
for (int i = 0; i < 10; i++) {
for (int i = 0; i < 256; i++) {
int len;
if (canonB.containsKey(i)) {
len = canonB.get(i).length();
@@ -54,7 +81,7 @@ public class PIFWriter {
}
out.writeBits(len, 8);
}
} catch (Exception e) {
} catch (IOException e) {
System.err.println("Erreur lors de l’écriture des tables de fréquences dans le fichier PIF");
}
}
@@ -68,57 +95,32 @@ public class PIFWriter {
out.writeBit(0);
}
}
} catch (Exception e) {
} catch (IOException e) {
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
public void encodePixels(BitOutputStream out, RGBImage image, Map<Integer, String> canonRED, Map<Integer, String> canonGREEN, Map<Integer, String> canonBLUE){
try {
int width = image.getWidth();
int height = image.getHeight();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// Récupérer les valeurs R, G, B du pixel
int r = image.getPixel(x, y).getR();
int g = image.getPixel(x, y).getG();
int b = image.getPixel(x, y).getB();
int width = image.getWidth();
int height = image.getHeight();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// Récupérer les valeurs R, G, B du pixel
int r = image.getPixel(x, y).getR();
int g = image.getPixel(x, y).getG();
int b = image.getPixel(x, y).getB();
// Écrire les codes dans le flux binaire
writeBitFromString(out, canonRED.get(r));
writeBitFromString(out, canonGREEN.get(g));
writeBitFromString(out, canonBLUE.get(b));
// Écrire les codes dans le flux binaire
writeBitFromString(out, canonRED.get(r));
writeBitFromString(out, canonGREEN.get(g));
writeBitFromString(out, canonBLUE.get(b));
}
}
} catch (Exception e) {
System.err.println("Erreur lors de lencodage des pixels dans le fichier PIF");
}
}
public void writeTOFile(String filepath, RGBImage image,
Map<Integer,String> canonR,
Map<Integer,String> canonG,
Map<Integer,String> canonB)
throws Exception {
// Création du flux de sortie binaire
FileOutputStream fos =new FileOutputStream(filepath);
BitOutputStream ecriveur =new BitOutputStream(fos);
// Écriture de l'en-tête
writeHeader(ecriveur, image.getWidth(), image.getHeight());
// Écriture des tables de longueurs des codes canoniques
writeTables(ecriveur, canonR, canonG, canonB);
// Ecriture des pixels
encodePixels(ecriveur, image, canonR, canonG, canonB);
ecriveur.fermerFlux();
System.err.println("SYSTEME");
}
}
@@ -1,5 +1,4 @@
package fr.iutfbleau.sae.util;
import fr.iutfbleau.sae.util.BitOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -99,9 +98,14 @@ public class BitOutputStream {
if (fluxFerme) {
throw new IOException("Le flux de sortie est fermé");
}
while (this.positionBit >= 0) {
writeBit(0);
// Si l'octet nes pas vide on le complete avec des 0
if(this.positionBit < 7){
this.fluxSortie.write(this.octetEnConstruction);
this.octetEnConstruction = 0;
this.positionBit = 7;
}
this.fluxSortie.flush(); // Force l'écriture dans le flux sous-jacent dans le but de vider le buffer
}
@@ -21,7 +21,7 @@ import java.io.InputStream;
* @version 1.0
* @since 2025-12-13
*/
public class BitinputStream {
public class BitInputStream {
/** Flux d'entrée sous-jacent */
private final InputStream fluxEntree;
@@ -41,7 +41,7 @@ public class BitinputStream {
* @param fluxEntree flux d'entrée à décorer
* @throws IllegalArgumentException si le flux est nul
*/
public BitinputStream(InputStream fluxEntree) {
public BitInputStream(InputStream fluxEntree) {
if (fluxEntree == null) {
throw new IllegalArgumentException("Le flux d'entrée ne peut pas être nul");
}
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

+7 -2
View File
@@ -24,8 +24,7 @@ Learning log Problème de blocage de la fenêtre après la sauvegarde:
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.
Cela avait deux conséquences. D'abord, la fenêtre pouvait parfois être affichée trop tard ou de manière irrégulière.
Ensuite, après lexport, 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é.
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.
@@ -43,3 +42,9 @@ Learning log Problème de blocage de la fenêtre après la sauvegarde:
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
des bibliothèques graphiques et leurs contraintes en matière de multithreading.
le proble aussi avec mon flush infini
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB