home *** CD-ROM | disk | FTP | other *** search
- #include "lzw.h"
- #include "fbits.h" // notre librairie d'entrées-sorties de bits sur disque,
- // pour les constructeurs ifBits et ofBits et les fonc-
- // tions writeBits et readBits
-
-
- // Constructeur (voir aussi lzwdico.h et lzwdico.cpp)
- LZW::LZW( unsigned int nombre_bits_final )
- : NBITS_FINAL( nombre_bits_final ),
- DICO( *new LZWDico( nombre_bits_final ) ),
- PILE( *new Pile( LZWDico::maxval( nombre_bits_final ) ) )
- { }
-
-
- // Destructeur
- LZW::~LZW()
- {
- delete & DICO ; delete & PILE ;
- }
-
-
- LZW::erreur LZW::compresse( const char * source, const char * cible )
- //
- // On compresse le fichier existant de nom source, vers un nouveau
- // fichier devant être nommé cible. On retourne le statut final sous
- // la forme d'un type énuméré.
- {
- // Ouvrir, si possible, le fichier source comme un flux d'octets,
- // et le fichier cible comme un flux de bits (notre classe ofBits)
- ifstream SOURCE( source, ios::binary ) ;
- ofBits CIBLE( cible, ios::binary ) ;
- if( ! SOURCE.good() )
- return fsource ;
- if( ! CIBLE.good() )
- return fcible ;
-
- // Laisser de la place pour l'en-tête au début du fichier cible
- ecrireEnTeteDeb( CIBLE ) ;
- LG_ORIG = 0 ; CRC_ORIG.raz() ;
-
- // Ce qui suit est l'implémentation, avec nos outils et classes, de
- // l'algorythme LZW tel que décrit par Mark Nelson ; voir son livre
-
- DICO.vider() ;
-
- // Lire le premier octet ; si source vide, émettre le code FIN_FICHIER,
- // sinon émettre caractère lu, puis incrémenter longueur et calculer crc
- int car = SOURCE.get() ;
- LZWEntree::code code = car == EOF ? LZWEntree::FIN_FICHIER : ( LZWEntree::code )car ;
- if( car != EOF )
- {
- ++LG_ORIG ;
- CRC_ORIG.valeur( ( unsigned char )car ) ;
- }
-
- // Boucle de traitement des caractères suivants lus dans le source
-
- while( /* SOURCE.good() && CIBLE.good() && */ ( car = SOURCE.get() ) != EOF )
- // les tests en commentaires sont très souhaitables théoriquement, hélas
- // ils coûtent du temps : si source est 1 MO, on passe 1 million de fois !
- {
- // Pour chaque caractère lu, incrémenter longueur, et recalculer crc
- ++LG_ORIG ; CRC_ORIG.valeur( ( unsigned char )car ) ;
-
- // Recherche dans le dictionnaire
- LZWEntree::code index = DICO.index( code, car ) ;
- // Si l'on a trouvé, le code de cette entrée devient le code courrant
- if( DICO[ index ].leCode() != LZWEntree::RIEN )
- code = DICO[ index ].leCode() ;
- // Si l'on a pas trouvé, on ajoute une entrée au dictionnaire, en
- // émettant le code avec le nombre de bits actuel (car il variera)
- else
- {
- DICO[ index ].ajoute( DICO.codeSuivant(), code, (unsigned char)car ) ;
- CIBLE.writeBits( code, DICO.nbits() ) ;
- code = ( LZWEntree::code ) car ;
-
- // On demande au dictionnaire d'allouer un autre code libre ; celui
- // ci est mis dans la variable ancien_nombre_bits, passée comme une
- // référence. Il y a 2 cas particuliers : si la longueur actuelle du
- // code est saturée, le dico l'augmente, et nous signale cela en re-
- // tournant pluslong (type énuméré) ; nous devons alors émettre un
- // code spécial pour prévenir le décompresseur de faire la même cho-
- // se, au même moment ; d'autre part, si la longueur de code elle-
- // même est arrivée à son maximum, le dico se vide, et nous le si-
- // gnale en retournant troplong ; nous devons alors émettre pour le
- // décompresseur le code spécial de vidage du dictionnaire.
- // Il importe de remarquer qu'en cas de changement de longueur du
- // code, le code spécial signalant ce fait est lui-même émis avec
- // l'ancienne longueur.
- unsigned int ancien_nombre_bits ;
- switch( DICO.incrementer( ancien_nombre_bits ) )
- {
- case LZWDico::troplong : CIBLE.writeBits( LZWEntree::VIDER_DICO, ancien_nombre_bits ) ;
- break ;
- case LZWDico::pluslong : CIBLE.writeBits( LZWEntree::ALLONGER_CODE, ancien_nombre_bits ) ;
- }
- }
- }
- // On écrit le dernier octet, puis le code spécial de fin de fichier
- CIBLE.writeBits( code, DICO.nbits() ) ;
- CIBLE.writeBits( LZWEntree::FIN_FICHIER, DICO.nbits() ) ;
- // On revient sur l'en-tête, puisqu'on possède désormais les informations
- // pour le remplir (longueur, crc, etc).
- return ecrireEnTeteFin( CIBLE ) ;
- }
-
-
- LZW::erreur LZW::decompresse( const char * source, const char * cible )
- //
- // Cette fois, source est le nom du fichier comprimé,et cible celui du
- // nouveau fichier décompressé à créer. On renvoie un statut d'erreur.
- {
- ifBits SOURCE( source, ios::binary ) ; // flux de lecture de bits
- ofstream CIBLE( cible, ios::binary ) ; // flux d'écriture d'octets
- if( ! SOURCE.good() )
- return fsource ;
- if( ! CIBLE.good() )
- return fcible ;
-
- LG_ORIG = 0 ; CRC_ORIG.raz() ;
-
- // On lit l'en-tête et l'on vérifie son intégrité
- erreur er = lireEnTete( SOURCE ) ;
- if( er != ras )
- return er ;
-
- // Algorythme de décompression LZW (cf Mark Nelson)
-
- while( 1 /* SOURCE.good() && CIBLE.good() */ )
- // tests souhaitables mais coûteux en temps
- {
- DICO.vider() ; // survient aussi à réception du code de vidage
- LZWEntree::code ancien_code = 0 ;
-
- // Traitement du premier code lu (avec la longueur de code initiale)
- SOURCE.readBits( ancien_code, DICO.nbits() ) ;
- if( ancien_code == LZWEntree::FIN_FICHIER )
- return ras ;
- unsigned char car = ( unsigned char )ancien_code ;
- CIBLE.put( car ) ; ++LG_ORIG ; CRC_ORIG.valeur( car ) ;
-
- // Traitement des autres codes
-
- while( 1 /* SOURCE.good() && CIBLE.good() */ )
- // tests souhaitables mais coûteux en temps
- {
- LZWEntree::code nouveau_code = 0 ;
- SOURCE.readBits( nouveau_code, DICO.nbits() ) ;
-
- // Fin du source ; on vérifie sa longueur, son
- // crc, par rapport aux données de l'en-tête
- if( nouveau_code == LZWEntree::FIN_FICHIER )
- return verifEnTete() ;
-
- // Traitement des codes spéciaux émis par le compresseur :
- // allongement du code, ou vidage du dictionnaire. Il est
- // essentiel de faire exactement la même chose que le com-
- // presseur, et au même moment !
- if( nouveau_code == LZWEntree::VIDER_DICO )
- break ;
- if( nouveau_code == LZWEntree::ALLONGER_CODE )
- {
- DICO.allongerCode() ;
- continue ;
- }
-
- // Cas particulier de l'algo LZW : on reçoit un code
- // qui n'est pas encore dans le dictionnaire ! Il faut
- // empiler le caractère dans la pile (cf Mark Nelson)
- if( nouveau_code >= DICO.codeSuivant() ) // cas spécial : code inconnu
- {
- PILE.push( car ) ;
- empilerChaine( ancien_code ) ;
- }
- else
- empilerChaine( nouveau_code ) ;
-
- // On dépile en émettant les caractères, et l'on met à jour le crc
- car = PILE.top() ; // simple consultation sans dépilement
- while( ! PILE.isEmpty() )
- {
- unsigned char c = PILE.pop() ;
- CIBLE.put( c ) ; ++LG_ORIG ; CRC_ORIG.valeur( car ) ;
- }
-
- // On ajoute une entrée au dictionnaire
- DICO[ DICO.codeSuivant() ].ajoute( LZWEntree::RIEN, ancien_code, car ) ;
- DICO.incrementer() ;
- ancien_code = nouveau_code ;
- }
- }
- }
-
-
- void LZW::empilerChaine( LZWEntree::code code_depart )
- //
- // On remonte l'arborescence du dictionnaire depuis code_depart,
- // en empilant dans notre PILE les caractères rencontrés.
- //
- {
- LZWEntree::code code = code_depart ;
-
- while( code >= LZWDico::codeDepart() )
- {
- PILE.push( DICO[ code ].leCar() ) ;
- code = DICO[ code ].leParent() ;
- }
- PILE.push( ( unsigned char )code ) ;
- }
-
-
- #ifdef DEPANNAGE
- void LZW::lister() const
- //
- // Vous voyez bien que vous n'êtes pas seul à éprouver
- // des difficultés de mise au point de temps à autre !
- {
- DICO.lister() ;
- }
- #endif
-
-
- LZW::erreur LZW::lireEnTete( ifBits & fbits )
- //
- // On lit et on vérifie l'en-tête, en renvoyant
- // nos conclusions sous forme d'un type énuméré.
- {
- // Lire l'en-tête comme un tableau d'octets
- fbits.read( en_tete.ucars, sizeof( en_tete.ucars ) ) ;
-
- // Le crc des informations de l'en-tête qui fut stocké par le décom-
- // presseur doit être égal à celui que nous recalculons maintenant.
- if( en_tete.infos.crc_infos != CRCEnTete() )
- return crctete ;
-
- // La longueur de code initiale et maximale étant paramétrables,
- // il faut s'assurer que l'on décompresse avec les mêmes bornes
- // que lors de la compression : une décompression 9 à 13 bits
- // après une compression 9 à 12 bits par exemple, donnerait évi-
- // demment du chinois (ou du français, si vous êtes chinois !).
- if( en_tete.infos.nbits_depart != 9 )
- return version ;
- if( en_tete.infos.nbits_limite != NBITS_FINAL )
- return version ;
-
- return ras ; // ouf, c'est heureusement le cas le plus fréquent !
- }
-
-
- LZW::erreur LZW::verifEnTete() const
- //
- // On compare la longueur et le crc recalculés du fichier
- // avec leurs valeurs mémorisées dans l'en-tête , et l'on
- // renvoie le verdict sous forme d'un type énuméré.
- {
- if( en_tete.infos.lg_fich_originel != LG_ORIG )
- return lgfich ;
- if( en_tete.infos.crc_fich_originel != CRC_ORIG.valeur() )
- return crcfich ;
-
- return ras ;
- }
-
-
- void LZW::ecrireEnTeteDeb( ofBits & fbits )
- //
- // Nous écrivons séquentiellement dans le flux cible en argument,
- // donc il nous faut dès le début réserver la place nécessaire
- // pour l'en-tête. Comme la plupart des informations sont indis-
- // ponibles avant d'avoir lu entièrement le source (longueurs,
- // crc, etc), nous remplissons provisoirement avec des zéros...
- {
- en_tete.infos.crc_infos = 0 ; // sera mis à jour par ecrireEnTeteFin
- en_tete.infos.nbits_depart = 9 ;
- en_tete.infos.nbits_limite = NBITS_FINAL ;
- en_tete.infos.lg_fich_originel = 0 ; // sera mis à jour par ecrireEnTeteFin
- en_tete.infos.crc_fich_originel = 0 ; // sera mis à jour par ecrireEnTeteFin
- en_tete.infos.lg_fich_comprime = 0 ; // sera mis à jour par ecrireEnTeteFin
-
- fbits.write( en_tete.ucars, sizeof( en_tete.ucars ) ) ;
- }
-
-
- LZW::erreur LZW::ecrireEnTeteFin( ofBits & fbits )
- //
- // En fin de compression, nous disposons des informations à mémoriser dans
- // l'en-tête ; donc nous y revenons (en début de fichier) pour les mettre à
- // jour. Noter que ces informations d'en-tête font elles-mêmes l'objet d'un
- // crc mémorisé, qui sera vérifié en tout premier lieu par le décompresseur.
- {
- fbits.flush() ; // sage précaution
- unsigned long lg_compr = fbits.tellp() ; // longueur comprimée
-
- // Remplir la structure d'en-tête avec les infos désormais disponibles
- en_tete.infos.lg_fich_originel = LG_ORIG ;
- en_tete.infos.crc_fich_originel = CRC_ORIG.valeur() ;
- en_tete.infos.lg_fich_comprime = lg_compr ;
- en_tete.infos.crc_infos = CRCEnTete() ; // à laisser en dernier !!
- // (crc de ce qui précède =
- // auto-vérif de l'en-tête)
-
- // Revenir sur l'en-tête en début de fichier, pour le ré-écrire
- fbits.seekp( 0 ) ;
- fbits.write( en_tete.ucars, sizeof( en_tete.ucars ) ) ;
-
- // Renvoie un avertissement si le fichier comprimé est égal ou supérieur
- // à l'original (peut arriver si l'on comprime un fichier déjà comprimé)
- return lg_compr < LG_ORIG ? ras : taille ;
- }
-
-
- CRC32::crc LZW::CRCEnTete() const
- //
- // Calcule le crc des informations de l'en-tête, qui sera lui même mémorisé
- // dans l'en-tête passé au décompresseur. Voir aussi crc32.h et crc32.cpp.
- {
- CRC32 C ; // classe de calcul de crc, cf crc32.h et crc32.cpp
- C.valeur( en_tete.ucars+sizeof( en_tete.infos.crc_infos ), sizeof( en_tete.ucars ) - sizeof( en_tete.infos.crc_infos ) ) ;
- return C.valeur() ; // consultation de crc calculé
- }
-
-
- const char * LZW::message( erreur er ) const
- //
- // Renvoie une chaîne de message d'erreur explicite,
- // selon la valeur du code en argument (type énuméré)
- {
- char * pt ;
- switch( er )
- {
- case ras : pt = "aucune erreur" ;
- break ;
- case taille : pt = "taille compressée supérieure ; original probablement compressé" ;
- break ;
- case version : pt = "la compression fut effectuée avec une autre version du programme" ;
- break ;
- case lgfich : pt = "le fichier décompressé a une longueur différente de l'originel" ;
- break ;
- case crcfich : pt = "le crc du fichier décompressé est différent de l'originel" ;
- break ;
- case crctete : pt = "le crc de l'en-tête est erronné" ;
- break ;
- case fsource : pt = "impossible d'ouvrir le fichier source" ;
- break ;
- case fcible : pt = "impossible de créer le fichier cible" ;
- break ;
- default : pt = "erreur non répertoriée" ; // ne devrait pas survenir
- }
- return ( const char * )pt ; // tricher pour la bonne cause (expression STROUSTRUPienne !)
- // (j'ai assigné pt dans le switch, donc je ne pouvais le dé-
- // clarer const ; toutefois, je dois tenir la promesse faite
- // quand j'ai déclaré renvoyer un const char *).
- }
-