home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
DP Tool Club 21
/
CD_ASCQ_21_040595.iso
/
dos
/
prg
/
c
/
complzw
/
lzw.cpp
< prev
next >
Wrap
C/C++ Source or Header
|
1995-01-30
|
13KB
|
355 lines
#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 *).
}