From 81411eb4988825059a89e91e0630b640e529e8c3 Mon Sep 17 00:00:00 2001 From: AlgaLaptop Date: Sun, 14 Dec 2025 18:49:01 +0100 Subject: [PATCH] US-D1 et US-D2 --- Diagrame.plantuml | 173 ++++++++++++++++++++++++++++++++++ PlaningDeTavail.md | 6 +- src/util/BitInputStream.java | 110 +++++++++++++++++++++ src/util/BitOutputStream.java | 119 +++++++++++++++++++++++ 4 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 Diagrame.plantuml create mode 100644 src/util/BitInputStream.java create mode 100644 src/util/BitOutputStream.java diff --git a/Diagrame.plantuml b/Diagrame.plantuml new file mode 100644 index 0000000..857c8af --- /dev/null +++ b/Diagrame.plantuml @@ -0,0 +1,173 @@ +@startuml +skinparam packageStyle rectangle +skinparam classAttributeIconSize 0 + + +' ============================ +' PACKAGE mimage +' ============================ +package mimage { + +class Pixel { + - int r + - int g + - int b + + Pixel(int r, int g, int b) + + int getR() + + int getG() + + int getB() + + void setR(int r) + + void setG(int g) + + void setB(int b) +} + +class RGBImage { + - int width + - int height + - Pixel[][] pixels + + RGBImage(int width, int height) + + int getWidth() + + int getHeight() + + Pixel getPixel(int x, int y) + + void setPixel(int x, int y, Pixel p) +} + +} + +' ============================ +' PACKAGE mhuffman +' ============================ +package mhuffman { + +class FrequencyTable { + - int[] freqR + - int[] freqG + - int[] freqB + + FrequencyTable() + + void computeFromImage(RGBImage img) + + int[] getRed() + + int[] getGreen() + + int[] getBlue() +} + +class HuffmanNode { + + int value + + int frequency + + HuffmanNode left + + HuffmanNode right + + boolean isLeaf() +} + +class HuffmanTree { + - HuffmanNode root + + HuffmanTree(FrequencyTable freq, char component) + + Map generateCodes() +} + +class CanonicalCode { + - Map codeLengths + - Map canonicalCodes + + CanonicalCode(Map huffmanCodes) + + String getCode(int value) + + int getLength(int value) +} + +FrequencyTable --> RGBImage +HuffmanTree --> HuffmanNode +CanonicalCode --> HuffmanTree + +} + +' ============================ +' PACKAGE util +' ============================ +package util { + +class BitInputStream { + - InputStream in + - int currentByte + - int bitPosition + + BitInputStream(InputStream in) + + int readBit() + + int readBits(int n) + + void close() +} + +class BitOutputStream { + - OutputStream out + - int currentByte + - int bitCount + + BitOutputStream(OutputStream out) + + void writeBit(int bit) + + void writeBits(int value, int n) + + void flush() + + void close() +} + +class ByteUtils { + + static int toInt(byte high, byte low) + + static byte[] toBytes(int value) +} + +class FileUtils { + + static boolean isPIFFile(File f) + + static byte[] readBinaryFile(String path) +} + +} + +' ============================ +' PACKAGE vconverter +' ============================ +package vconverter { + +class ConverterWindow { + + void setImagePreview(RGBImage img) + + void setFrequencyTable(int[] r, int[] g, int[] b) + + void setHuffmanTable(Map red, + Map green, + Map blue) + + void setCanonicalTable(CanonicalCode r, CanonicalCode g, CanonicalCode b) +} + +class PreviewPanel { + - RGBImage image + + void setImage(RGBImage img) +} + +class FrequencyTablePanel { + + void updateFrequencies(int[] r, int[] g, int[] b) +} + +class CodeTablePanel { + + void updateCodes(Map hR, + Map hG, + Map hB, + CanonicalCode cR, + CanonicalCode cG, + CanonicalCode cB) +} + +ConverterWindow --> PreviewPanel +ConverterWindow --> FrequencyTablePanel +ConverterWindow --> CodeTablePanel + +} + +' ============================ +' CONTROLLER (Sprint 1) +' ============================ +class ConverterController { + + void loadImage(String path) + + void computeFrequencies() + + void computeHuffman() + + void computeCanonical() + + void saveAsPIF(String path) 'encore vide pour Sprint 1 +} + +ConverterController --> RGBImage +ConverterController --> FrequencyTable +ConverterController --> HuffmanTree +ConverterController --> CanonicalCode + +@enduml diff --git a/PlaningDeTavail.md b/PlaningDeTavail.md index c931f19..d907fe8 100644 --- a/PlaningDeTavail.md +++ b/PlaningDeTavail.md @@ -18,8 +18,8 @@ Objectif : Mise en place des fondations techniques | US | Assigné | Statut | | Description | |----------|---------|--------|-----|-------------| -| US-D1 | AD | TODO | 🟥 | Implémenter BitInputStream (lecture bit par bit) | -| US-D2 | AD | TODO | 🟥 | Implémenter BitOutputStream (écriture bit par bit) | +| US-D1 | AD | TODO | 🟩 | Implémenter BitInputStream (lecture bit par bit) | +| US-D2 | AD | TODO | 🟩 | Implémenter BitOutputStream (écriture bit par bit) | | US-D3 | AD | TODO | 🟥 | Générer les tables de fréquences RGB | | US-D4 | AD | TODO | 🟥 | Construire l’arbre Huffman | | US-D5 | AA | TODO | 🟥 | Générer les codes Huffman | @@ -51,7 +51,7 @@ Objectif : Mise en place des fondations techniques |----------------|-------|----| | `BitInputStream.java` | Lecture bit par bit | US-D1 | | `BitOutputStream.java` | Écriture bit par bit | US-D2 | -| `ByteUtils.java` | Conversion int ↔ octets | — | +| `ByteUtils.java` | Conversion int octets | — | | `FileUtils.java` | Méthodes utilitaires fichiers | US-U5 (indirect) | ### `src/vconverter/` diff --git a/src/util/BitInputStream.java b/src/util/BitInputStream.java new file mode 100644 index 0000000..ace61de --- /dev/null +++ b/src/util/BitInputStream.java @@ -0,0 +1,110 @@ +import java.io.IOException; +import java.io.InputStream; + +/** + * Décorateur de flux permettant la lecture binaire à granularité du bit. + *

+ * Cette classe encapsule un {@link InputStream} existant et fournit + * des opérations de lecture bit par bit ou par groupes de bits. + * Elle ne gère ni l'ouverture ni la sélection du fichier source. + *

+ * + *

+ * Utilisée notamment pour le décodage des fichiers compressés + * (ex : format PIF utilisant des codes de Huffman). + *

+ */ +public class BitInputStream { + + /** Flux d'entrée sous-jacent */ + private final InputStream fluxEntree; + + /** Octet actuellement chargé depuis le flux */ + private int octetCourant; + + /** Position du bit courant dans l'octet (du bit 7 au bit 0) */ + private int positionBit; + + /** Indique si la fin du flux a été atteinte */ + private boolean finDeFlux; + + /** + * Construit un lecteur binaire à partir d'un flux existant. + * + * @param fluxEntree flux d'entrée à décorer + * @throws IllegalArgumentException si le flux est nul + */ + public BitInputStream(InputStream fluxEntree) { + if (fluxEntree == null) { + throw new IllegalArgumentException("Le flux d'entrée ne peut pas être nul"); + } + this.fluxEntree = fluxEntree; + this.octetCourant = 0; + this.positionBit = -1; // force la lecture d'un nouvel octet + this.finDeFlux = false; + } + + /** + * Lit un bit depuis le flux binaire. + * + * @return 0 ou 1 si un bit est lu, -1 si la fin du flux est atteinte + * @throws IOException si une erreur de lecture survient + */ + public int readBit() throws IOException { + if (finDeFlux) { + return -1; + } + + if (this.positionBit < 0) { + int octetLu = this.fluxEntree.read(); + if (octetLu == -1) { + this.finDeFlux = true; + } else { + this.octetCourant = octetLu; + this.positionBit = 7; + } + } + + if (finDeFlux) { + return -1; + } + + int bit = (this.octetCourant >> this.positionBit) & 1; + this.positionBit--; + return bit; + } + + /** + * Lit une séquence de bits consécutifs et les assemble dans un entier. + * + * @param nombreBits nombre de bits à lire (strictement positif) + * @return valeur entière correspondant aux bits lus, + * ou -1 si la fin du flux est atteinte prématurément + * @throws IOException si une erreur de lecture survient + */ + public int readBits(int nombreBits) throws IOException { + int res=0; + for (int i = 0; i < nombreBits; i++) { + int bit = readBit(); + if (bit == -1) { + return -1; + } + res = (res << 1) | bit; + } + return res; + } + + + + /** + * Ferme le flux d'entrée sous-jacent. + * + * @throws IOException si une erreur survient lors de la fermeture + */ + public void closeFlux() throws IOException { + this.fluxEntree.close(); + } + + + +} diff --git a/src/util/BitOutputStream.java b/src/util/BitOutputStream.java new file mode 100644 index 0000000..7220c83 --- /dev/null +++ b/src/util/BitOutputStream.java @@ -0,0 +1,119 @@ +import java.io.IOException; +import java.io.OutputStream; + +/** + * Décorateur de flux permettant l'écriture binaire à granularité du bit. + *

+ * Cette classe encapsule un {@link OutputStream} existant et permet + * l'écriture de bits individuellement ou par groupes. + * Les bits sont accumulés afin de former des octets avant écriture. + *

+ * + *

+ * Utilisée notamment pour l'encodage des fichiers compressés + * (ex : format PIF utilisant des codes de Huffman). + *

+ */ +public class BitOutputStream { + + /** Flux de sortie sous-jacent */ + private final OutputStream fluxSortie; + + /** Octet en cours de construction */ + private int octetEnConstruction; + + /** Position du prochain bit à écrire (de 7 à 0) */ + private int positionBit; + + /** Indique si le flux est fermé */ + private boolean fluxFerme; + + /** + * Construit un écrivain binaire à partir d'un flux existant. + * + * @param fluxSortie flux de sortie à décorer + * @throws IllegalArgumentException si le flux est nul + */ + public BitOutputStream(OutputStream fluxSortie) { + if (fluxSortie == null) { + throw new IllegalArgumentException("Le flux de sortie ne peut pas être nul"); + } + this.fluxSortie = fluxSortie; + this.octetEnConstruction = 0; + this.positionBit = 7; + this.fluxFerme = false; + } + + /** + * Écrit un bit dans le flux binaire. + * + * @param bit bit à écrire (0 ou 1) + * @throws IOException si une erreur d'écriture survient + * @throws IllegalArgumentException si le bit n'est ni 0 ni 1 + */ + public void writeBit(int bit) throws IOException { + if (bit != 0 && bit != 1) { + throw new IllegalArgumentException("Le bit doit être 0 ou 1"); + } + if (fluxFerme) { + throw new IOException("Le flux de sortie est fermé"); + } + if (bit == 1) { + this.octetEnConstruction = this.octetEnConstruction | (1 << this.positionBit); + } + this.positionBit--; + + // si on atteint la fin de l'octet, on le grave dans le flux et rebolotte + if(this.positionBit < 0){ + this.fluxSortie.write(this.octetEnConstruction); + this.octetEnConstruction = 0; + this.positionBit = 7; + } + + } + + /** + * Écrit une séquence de bits correspondant à une valeur entière. + * + * @param valeur valeur contenant les bits à écrire + * @param nombreBits nombre de bits à écrire (strictement positif) + * @throws IOException si une erreur d'écriture survient + */ + public void writeBits(int valeur, int nombreBits) throws IOException { + for (int i = nombreBits - 1; i >= 0; i--) { + int bit = (valeur >> i) & 1; + writeBit(bit); + } + } + + /** + * Force l'écriture immédiate des données accumulées dans le flux sous-jacent. + * + * @throws IOException si une erreur survient lors du flush + */ + public void flush() throws IOException { + if (fluxFerme) { + throw new IOException("Le flux de sortie est fermé"); + } + while (this.positionBit >= 0) { + writeBit(0); + } + this.fluxSortie.flush(); // Force l'écriture dans le flux sous-jacent + } + + /** + * Vide les buffers internes et ferme le flux de sortie. + * + * @throws IOException si une erreur survient lors de la fermeture + */ + public void fermerFlux() throws IOException { + // si le flux n'est pas déjà fermé + if (!fluxFerme) { + this.flush(); // compléter l'octet et forcer l'écriture + this.fluxSortie.close(); // fermer le flux sous-jacent + this.fluxFerme = true; // marquer le flux comme fermé + } + } + + +}