huffman/src/compress.c

142 lines
4.6 KiB
C

#include "common.h"
#include "compress.h"
#include "buffer.h"
#include "huftree.h"
#include "display.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
/**
* Effectuer un comptage des caractères dans le corpus d'entrée. En déduire
* le tableau des fréquences d'apparition des caractères ASCII
*
* (résultat à libérer avec `free`)
*/
static double* _createFrequencies(FILE*, uint64_t*);
/**
* Encoder le fichier d'entrée vers le fichier de sortie
* en suivant les étiquettes passées. Les étiquettes
* peuvent être calculées avec labelTree()
*/
static void _encodeFromTable(char**, FILE*, WriteBuffer*);
double* _createFrequencies(FILE* file, uint64_t* total) {
double* frequencies = malloc(NUM_CHARS * sizeof(*frequencies));
*total = 0;
for (size_t i = 0; i < NUM_CHARS; i++) {
frequencies[i] = 0;
}
// Lecture du fichier caractère par caractère et comptage
int current;
while ((current = fgetc(file)) != EOF) {
assert(current >= 0 && current < NUM_CHARS);
frequencies[current]++;
(*total)++;
}
// Conversion des effectifs des caractères en fréquences
for (size_t i = 0; i < NUM_CHARS; i++) {
frequencies[i] /= *total;
}
return frequencies;
}
void _encodeFromTable(char** labels, FILE* source, WriteBuffer* output) {
int current;
// Lecture du fichier d'entrée caractère par caractère
while ((current = fgetc(source)) != EOF) {
assert(current >= 0 && current < NUM_CHARS);
char* label = labels[current];
assert(label != NULL);
// Ajout du label dans le buffer, caractère par caractère
// vidant progressivement le buffer
while (*label != '\0') {
putBuffer(*label == '1', output);
label++;
}
}
}
void compress(FILE* source, FILE* dest) {
// ÉTAPE 1 : calcul des fréquences d'apparition de chaque caractère
// dans le fichier source. Ce programme prend le terme caractère au sens
// restreint d'octet. Dans le cas où l'on compresse des fichiers Unicode
// utilisant des caractères sur plusieurs octets, on aura donc une
// compression moins optimale.
// FIXME: éviter la division par zéro pour les fichiers vides
printVerbose("Calcul des fréquences d'apparition des caractères.\n");
uint64_t start_bytes = 0;
double* frequencies = _createFrequencies(source, &start_bytes);
if (isVerbose()) {
printFrequenciesTable(frequencies, NUM_CHARS);
}
// ÉTAPE 2 : construction d'un arbre de Huffman correspondant aux
// fréquences d'apparition des caractères
// FIXME: éviter les arbres malformés avec les fichiers ne contenant qu'un
// seul type de caractère
printVerbose("\nConstruction de l'arbre de Huffman.\n");
HufTree tree = createTree(frequencies);
free(frequencies);
frequencies = NULL;
if (isVerbose()) {
printTree(tree);
}
// ÉTAPE 3 : calcul de la clef de codage en étiquettant les chaque feuille
// de l'arbre. Pour chaque sommet traversé, s'il est le fils gauche de son
// parent, l'étiquette est augmentée d'un '0', sinon elle l'est d'un '1'
printVerbose("\nÉtiquetage des feuilles de l'arbre.\n");
char** labels = createTreeLabels(tree);
if (isVerbose()) {
printLabelsTable(labels, NUM_CHARS);
}
// ÉTAPE 4 : écriture des données compressées vers la sortie. Les données
// écrites permettent de restituer le fichier originel : nombre de
// caractères stockés, arbre de Huffman et données compressées brutes
WriteBuffer output = createWriteBuffer(dest);
printVerbose("\nÉcriture des données compressées.\n");
// - Entier 64 bits : nombre d'octets dans le fichier originel
fwrite(&start_bytes, sizeof(start_bytes), 1, dest);
// - Arbre linéarisé : arbre de Huffman permettant le calcul de la clef
writeTree(tree, &output);
// - Données compressées brutes (caractères originels traduits dans
// la clef de codage calculée avant)
rewind(source);
_encodeFromTable(labels, source, &output);
flushBuffer(&output);
// ÉTAPE 5 : affichage des statistiques de compression et fin
uint64_t end_bytes = sizeof(start_bytes) + getFlushedCount(&output);
double gain = ((double) start_bytes - end_bytes) / start_bytes * 100;
printVerbose("Taille originelle : %" PRIu64 " octets.\n", start_bytes);
printVerbose("Taille compressée : %" PRIu64 " octets.\n", end_bytes);
printVerbose("Gain : %.2f %% !\n", gain);
freeTree(tree);
freeTreeLabels(labels);
labels = NULL;
}