package fr.iutfbleau.sae.mpif; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Cette classe permet de lire un fichier .pif et de reconstruire l'image d'origine. * Elle lit d'abord l'en-tête puis les longueurs des codes canoniques. * Ensuite elle reconstruit les codes, construit les arbres de décodage * et décode les pixels un par un pour obtenir l'image RGB finale. * Elle est utilisée par le programme Viewer pour afficher une image .pif. */ public class PIFReader { private int width; private int height; private int[] lenR; private int[] lenG; private int[] lenB; /** * Cette methode sert de point d'entrer pour le decodage d'un pif. * Elle Lit complètement un fichier .pif et renvoie l'image décodée. * Elle lit l'en-tête, récupère les longueurs des codes, * reconstruit les codes canoniques puis les arbres nécessaires * au décodage. Enfin elle lit pixel par pixel. * * @param file fichier .pif à décoder * @return l'image obtenue après décodage * @throws Exception si le fichier est invalide ou si une erreur survient pendant la lecture */ public RGBImage decodePifFile(File file) throws Exception { FileInputStream fis = new FileInputStream(file); BufferedInputStream bos = new BufferedInputStream(fis); BitInputStream lecteur = new BitInputStream(bos); // Je lis l'en-tête et les tables canoniques this.readHeader(lecteur); this.readCanonicalTables(lecteur); // Je reconstruis les tables canoniques car dans le fichier on a juste les longueurs en bits Map canonR = rebuildCanonical(lenR); Map canonG = rebuildCanonical(lenG); Map canonB = rebuildCanonical(lenB); // Je construis les arbres de décodage DecodeNode trieR = buildDecodageTree(canonR); DecodeNode trieG = buildDecodageTree(canonG); DecodeNode trieB = buildDecodageTree(canonB); // Je décode les pixels RGBImage img = decodePixels(lecteur, trieR, trieG, trieB); lecteur.closeFlux(); return img; } /** * Lit l'en-tête du fichier .pif. On y récupère la largeur * et la hauteur de l'image, chacune codée sur 16 bits. * * @param in flux binaire à lire * @throws IOException si la lecture échoue */ public void readHeader(BitInputStream in) throws IOException { this.width = in.readBits(16); this.height = in.readBits(16); } /** * Lit les longueurs des code canoniques pour les trois composantes * rouge, vert et bleu. Chaque table contient 256 valeurs sur 5 bits. * Ces longueurs permettront de reconstruire les vrais codes plus tard * * @param in flux binaire d'entrée pdfpdof * @throws IOException si erreur de lecture se produit */ public void readCanonicalTables(BitInputStream in) throws IOException { // Table Rouge this.lenR = new int[256]; for (int i = 0; i < 256; i++){ this.lenR[i] = in.readBits(8); } // Table Vert this.lenG = new int[256]; for (int i = 0; i < 256; i++){ this.lenG[i] = in.readBits(8); } // Table Bleu this.lenB = new int[256]; for (int i = 0; i < 256; i++){ this.lenB[i] = in.readBits(8); } } /** * Reconstruit les codes canoniques à partir des longueurs stockées dans le fichier. * On trie d'abord les paires (symbole, longueur), puis on génère les codes * en appliquant la règle des codes canoniques. * * @param lengths tableau contenant les longueurs des codes pour 256 symboles * @return une table qui associe un code binaire (sous forme de texte) à son symbole */ public Map rebuildCanonical(int[] lengths) { // je cree une liste de paires (symbole, longueur) List> entiers = new ArrayList<>(); for (int i = 0; i < lengths.length; i++) { if (lengths[i] > 0) { entiers.add(new java.util.AbstractMap.SimpleEntry<>(i, lengths[i])); } } // Je trie par longueur croissante, puis par symbole croissant ComparateurEntreeCanonique comparateur = new ComparateurEntreeCanonique(); entiers.sort(comparateur); // je genere les codes canoniques Map codes = new HashMap<>(); int code = 0; int previousLength = 0; for (Map.Entry entry : entiers) { int symbol = entry.getKey(); int length = entry.getValue(); // Decalage pour aligner le code sur la longueur courante code <<= (length - previousLength); // je convertit ce code en texte binaire String codeStr = Integer.toBinaryString(code); // On s'assure que la chaîne a la bonne longueur en ajoutant des zéros à gauche si nécessaire while (codeStr.length() < length) { codeStr = "0" + codeStr; } // je restocke le code + symbole en inversant car la map est inverse dans l'encodage codes.put(codeStr, symbol); code++; previousLength = length; } return codes; } /** * Construit un arbre de décodage à partir des codes canoniques. * Chaque code binaire définit un chemin dans l'arbre jusqu'à une feuille * contenant le symbole à décoder. * * @param codes dictionnaire associant le code binaire au symbole * @return la racine de l'arbre de décodage */ public DecodeNode buildDecodageTree(Map codes) { DecodeNode root = new DecodeNode(); // la racine de larbre for (Map.Entry entry : codes.entrySet()) { String code = entry.getKey(); // 101011101110 par exemple int symbol = entry.getValue(); // 0,1,2,3 etc. par exemple DecodeNode current = root; // je parcours le code bit à bit for (int i = 0; i < code.length(); i++) { char bit = code.charAt(i); if(i == code.length() - 1) { // Dernier bit: je cree une feuille avec la valeur du symbol if(bit == '0') { current.left = new DecodeNode(null, null, symbol); } else { current.right = new DecodeNode(null, null, symbol); } } else { // Si c'est pas le dernier bit : je creer un node interne if(bit == '0') { if(current.left == null) { current.left = new DecodeNode(); // Node interne } current = current.left; } else { if(current.right == null) { current.right = new DecodeNode(); // node intern } current = current.right; } } } } return root; } /** * Décode l'ensemble des pixels de l'image en utilisant les trois arbres * correspondant aux composantes rouge, verte et bleue. * Chaque symbole est lu en parcourant l'arbre bit par bit. * * @param in flux binaire contenant les données des pixels * @param red arbre de décodage pour le rouge * @param green arbre pour le vert * @param blue arbre pour le bleu * @return l'image RGB reconstruite * @throws IOException si un symbole est invalide ou si la lecture échoue */ public RGBImage decodePixels(BitInputStream in, DecodeNode red, DecodeNode green, DecodeNode blue) throws IOException{ RGBImage image = new RGBImage(width, height); for(int y = 0; y < height; y++) { for(int x = 0; x < width; x++) { // je decode chaque composante en parcourant son arbre int r = decodeSymbole(in, red); int g = decodeSymbole(in, green); int b = decodeSymbole(in, blue); // je cree et je place le pixel Pixel pixel = new Pixel(r, g, b); image.setPixel(x, y, pixel); } } return image; } /** * Décode un seul symbole en parcourant l'arbre tant qu'on n'est pas sur une feuille. * Chaque bit lu détermine si on part à gauche ou à droite dans l'arbre. * * @param in flux binaire source * @param root racine de l'arbre de décodage * @return la valeur du symbole trouvé * @throws IOException si un chemin est invalide ou si la lecture échoue */ private int decodeSymbole(BitInputStream in, DecodeNode root) throws IOException { DecodeNode current = root; // je parcours l'arbre en suivant les bits du flux jusqu'à atteindre une feuille while (!current.isLeaf()) { int bit = in.readBit(); if (bit == 0) { current = current.left; } else { current = current.right; } if (current == null) { throw new IOException("code invalide: noeud null rencontre"); } } // si on est arrivé à une feuille, on retourne la valeur if (current.value == -1) { throw new IOException("Feuille sans valeur assignée"); } return current.value; } /** * Vérifie si un fichier est un fichier .pif valide. * On teste l'existence du fichier, son extension et une taille minimale * permettant au moins de contenir l'en-tête et les tables de longueurs. * * @param f fichier à tester * @return true si le fichier semble être un .pif valide, sinon false */ public static boolean isPIFFile(File f) { if (f == null){ return false; } if (!f.exists() || !f.isFile()){ return false; } //je verifi l'extension String name = f.getName().toLowerCase(); if (!name.endsWith(".pif")) { return false; } return f.length() >= 772; // taille minimal pour un fichier pif longueur largeur et tables de frequance } }