Lz77

aus RHWiki, der freien Romhacking-Enzyklopädie

Der LZ77-Algorithmus ist einer der weit verbreitetesten Komprimierungsalgorithmen. LZ77 bietet die Möglichkeit große Daten auf eine kleinere Gesamtgröße zu komprimieren und später wieder zu dekomprimieren. Je öfter sich Bereiche in den zu komprimierenden Daten wiederholen um so besser können die Daten komprimiert werden.

Inhaltsverzeichnis

Das Grundkonzept

Der LZ77-Algorithmus sucht in den zu komprimierenden Daten nach sich wiederholenden Texten/Bytefolgen. Am besten lässt sich sowas jedoch an einem Beispiel erklären:

Position: 0        1         2         3         4         5         6         7
          1234567890123456789012345678901234567890123456789012345678901234567890
Eingabe:  wiederholende wörter wiederholen sich wie wörter die sich wiederholen.
Ausgabe:  wiederholende wörter [1:11] sich[21:4] [15:8]die[18:9][4:8].
 
[offset:größe]

Die Werte in den Eckigen Klammern geben nun einmal den Offset des Datenblockes sowie, dessen Größe an. [15:8] bedeutet nun, dass an diese Stelle 8 Bytes von der Position 15 kopiert werden sollen. So spart man sich die Wiederholung des kompletten Textabschnittes nochmal, da sich Offset+Größe meist auch schon in 2 Bytes speichern lassen.

Details

Zunächst muss Anfangs in die Datei gespeichert werden, wie groß die dekomprimierten Daten ursprünglich waren. Das ist nötig, da es bei dem LZ77-Algorithmus kein ENDE-Signal/Byte gibt.

Nun muss der Algorithmus irgendwoher wissen, welche Bytes nun übernommen werde können und wo Informationen über einen Offset/eine Größe zur Kopie von Daten gespeichert sind. Dazu wird alle 8 Byte (inklusive dem ersten) ein zusätzliches Kontrollbyte gespeichert.

Beispieldarstellung:

Gesamtlänge: 13
Kontrollbyte (dual): 00100010
       0 0100010: a     -> (a)
     0 0 100010:  n     -> a(n)
    00 1 00010:   (0,3) -> an(ana)
   001 0 0010:    s     -> anana(s)
  0010 0 010:     _     -> ananas(_)
 00100 0 10:      b     -> ananas_(b)
001000 1 0:       (0,5) -> ananas_b(anana)

Die Position für die Quelldaten werden allerdings meistens relativ zur aktuellen Position angegeben. Das bedeutet, normalerweise würde es heißen: Kopiere 6 Bytes von den Daten, die 8 Bytes vor "hier" liegen. Außerdem wird zu der Größe und dem Offset meist noch ein konstanter Wert addiert. Schließlich ist es wenig sinnvoll einen Datenbereich von 1 Byte zu kopieren, wenn die Information über den Offset und die Größe selber 2 Byte brauchen.

Beispielquellcode

Hier ist ein Beispielquellcode in C++ geschrieben, der das Dekomprimieren der Daten anhand der GBA-LZ77-Komprimierung demonstriert:

//----------------------------------------------------------------------------
// Diese Funktion dekomprimiert die Daten aus 'Source' und gibt die
// dekomprimierten Daten in 'Data' zurück. Der Rückgabewert der
// Funktion ist entweder 0 bei einem Fehler, oder die Größe der
// dekomprimierten Daten
//----------------------------------------------------------------------------
unsigned Decompress( char* Source, char* &Data )
{
    unsigned       Header, i;
    unsigned       DataSize, DataLeft;
    unsigned char  ControlByte, CopyLength;
    unsigned short Data, CopyOffset;
    char           *CurPos, *SrcPnt;
    
    // Überprüfen des Headers und auslesen der Orginalgröße
    Header     = *((unsigned*)Source);   // Header auslesen
    DataSize   = Header >> 8;            // Datengröße auslesen
    DataLeft   = DataSize;               // Datengröße zur Bearbeitung
    
    // Kurze Fehlerüberprüfung
    if ( (Header & 0xFF) != 0x10 ) return 0;
    
	// Speicher reservieren
    Data       = new char[ DataSize ]    // Speicher reservieren
    CurPos     = Data;                   // Die aktuelle Position an den Anfang
    
    // Schleife durchlaufen bis die Datei vollständig dekomprimiert wurde
    while ( DataLeft > 0 )
    {
        ControlByte = *Source++          // Auslesen des Kontrollbytes
        
        // Gibt es mindestens einen gesetzten Bit im ControlByte?
        if ( ControlByte!=0 )
        {
            // 2 Byte auslesen (umgedreht)
            Data  = *((unsigned char*)(Source++)) << 8;
            Data |= *((unsigned char*)(Source++));
            
            // Kopierlänge und Größe auslesen
            CopyLength = ( Data >> 12 ) + 3;
            CopyOffset = ( Data & 0x0FFF );

            // Länge der zu kopierenden Daten zur Sicherheit überprüfen
            if ( CopyLength > DataLeft ) CopyLength = DataLeft;
            DataLeft -= CopyLength;

            // Die Quellposition ist relativ zur aktuellen Position
            SrcPnt    = CurPos - CopyOffset - 1;

            // Kopieren der Daten
            while ( CopyLength-->0 )
                *CurPos++ = *SrcPnt++;
        }
        else
        {
            // Einfach die folgenden 8 Byte kopieren
            for ( i=0; i<8 && DataLeft>0; ++i, --DataLeft )
                *CurPos++ = *Source++;
        }
    }
    
    return DataSize;
}

Siehe auch

'Persönliche Werkzeuge