Artikel

Bits mit Image

Das IMG-Grafikformat entschlüsselt

Neben dem PCX-Grafikformat ist das IMG-Format von GEM weit verbreitet. Obwohl der überwältigende Erfolg von Windows 3.0 das Ende von GEM einzuläuten scheint, gibt es nach wie vor viele Grafikprogramme, die IMG-Dateien erzeugen. Damit Sie Ihre wertvollen Bilddaten auch weiterhin verwenden können, legen wir das IMG-Format offen.

Der generelle Aufbau einer IMG-Datei unterscheidet sich nur in wenigen Punkten von anderen Grafikformaten. Zu Beginn der Datei steht der Header mit den Eckdaten der Grafik. Alle Werte sind mit zwei Byte codiert, wobei aber zu beachten ist, daß das Low- und High-Byte des Wortes jeweils vertauscht ist. Es steht immer zuerst das High-Byte und erst danach folgt das Low-Byte.
Der Header besteht derzeit aus 8 Worten, also 16 Byte, deren Bedeutung in der Tabelle erläutert ist. Da die Pixelgröße in Mikrometer gespeichert ist, lassen sich IMG-Bilder mit den richtigen Proportionen auf unterschiedlichen Geräten ausgeben.
Der siebte Eintrag im Header (Wort 6) gibt die Länge einer Zeile an. Er wird für jeweils eine Farbebene angegeben. Im Gegensatz zu den meisten anderen Grafikformaten wird die Höhe des Bildes nicht im Header gespeichert. Daraus ergeben sich eine Reihe von Problemen bei der korrekten Skalierung des Bildes.

Header einer IMG-Datei
Wort Bedeutung
0 Version der Datei (in der Regel 1)
1 Kopflänge in Worten
2 Anzahl der Farbebenen
3 Länge eines Musters
4 Pixelbreite in Mikrometer
5 Pixelhöhe in Mikrometer
6 Länge einer Zeile in Pixel = Bits
7 Anzahl der Elemente in der Datei

Wenn man ein und dasselbe Bild sowohl im IMG als auch im PCX-Format speichert, fällt sofort auf, daß die IMG-Datei in der Regel kleiner ist als die entsprechende PCX-Datei. Und das hat seinen Grund. Während PCX nur eine Lauflängen-Komprimierung kennt, werden in IMG-Dateien gleich vier verschiedene Komprimiermethoden angewandt: vertikale Wiederholung, Solid Run, Pattern Run und Bit String.
Bei der einfachsten Methode, der vertikalen Wiederholung, werden einfach identische Bildschirmzeilen zusammengefaßt. Dazu benötigt GEM insgesamt vier Byte. Die ersten drei Byte dienen als Flag zur Erkennung des Codes und haben immer die Werte 00h, 00h und FFh. Im vierten Byte steht schließlich die Anzahl der identischen Bildschirmzeilen. Damit lassen sich maximal 255 Zeilen in 4 Byte unterbringen, aber diese enorme Komprimierung bringt auch Nachteile mit sich. Da die maximale Anzahl der Bildschirmzeilen nicht bekannt ist, muß die Ausgaberoutine immer prüfen, ob nicht die letzte Bildschirmzeile erreicht worden ist. Anderenfalls könnte es zu unangenehmen Überraschungen kommen.
Innerhalb einer Zeile kennt GEM drei Methoden zum Codieren der Farbinformationen. Jede Zeile besteht aus einer (CGA und Hercules) oder mehreren Farbebenen (EGA und VGA). Alle Ebenen einer Zeile werden nacheinander codiert, wobei GEM versucht, die einzelnen Bits bestmöglich zu packen.
Wenn mehrere aufeinanderfolgende Bytes entweder nur den Wert 00h oder FFh haben, genügt es, die Anzahl der sich wiederholenden Bytes zu codieren. Diese Lauflängenkomprimierung wird Solid Run genannt. In nur einem Byte lassen sich bei Schwarz-weiß-Darstellungen bis zu 127 Byte codieren. Haben die aufeinanderfolgenden Bytes den Wert FFh (schwarz), wird das das erste Bit des codierten Byte auf 1 gesetzt. Die restlichen 7 Bit des codierten Byte repräsentieren die Länge der aufeinanderfolgenden Bytes mit dem Wert FFh. Das gleiche gilt für ein Feld von aufeinanderfolgen 00h-Bytes (weiß). In diesem Fall wird das erste Bit des codierten Byte auf 0 gesetzt.
Wenn ein Bild aus Mustern zusammengesetzt ist, lassen sich diese zu einem Pattern Run zusammenfassen. Allerdings offenbart sich dabei eine kleine Schwäche des Formats. Die Länge eines Musters ergibt sich aus Wort 3 des Headers und gilt für alle Muster der Datei. Ein Muster muß also immer dieselbe Länge haben. In der Regel wird der Wert 2 (Byte) verwendet.
Im Gegensatz zum Solid Run muß dieses Format über ein Flag zur Kennzeichnung der Komprimiermethode verfügen. GEM verwendet dazu den Wert 00h, dem die Anzahl der Muster und das Muster selbst folgen.
Handelt es sich um ein einmaliges Muster, so muß auch GEM kapitulieren. Solche Bytefolgen können lediglich unkomprimiert als sogenannter Bit String abgelegt werden.
Als Erkennungsflag verwendet GEM den Wert 80h, dem die Anzahl der Bytes und die Werte selbst folgen. Betrachtet man sich diese Codes, so ergibt sich eine zwangsläufige Reihenfolge bei der Decodierung. Denn die Solid-Run-Methode verfügt über kein Erkennungsflag. Deshalb müssen zuerst alle übrigen Flags abgefragt werden. Für die Praxis bedeutet dies:

  • Test auf vertikale Wiederholung (mehrere identische Zeilen)
  • Test auf Pattern Run und Bit String
  • Alles übrige ist ein Solid Run

Im Gegensatz zum PCX-Format verfügen IMG-Dateien über keinerlei Farbpaletten-Informationen. Die Farben sind so zu setzen, wie sie im Speicher stehen. Diese Schwäche ist ein weiterer Grund dafür, daß das Format immer seltener eingesetzt wird. Allerdings gibt es noch viele Archive mit IMG-Dateien, so daß man nur wissen muß wie diese Dateien codiert sind.
Im folgenden stellen wir daher ein Programm (Listing) vor, das in der Lage ist, IMG-Dateien auf CGA-, EGA-, VGA- und Hercules-Bildschirmen darzustellen. Sie können es mit Turbo oder Quick Pascal kompilieren. Sie starten das Programm mit dem Befehl SHOWIMG Dateiname.
Den Namen der IMG-Datei können Sie mit oder ohne Dateiendung eingeben. Das Lesen von IMG-Dateien und das Darstellen der Bilder ist im allgemeinen recht einfach. Etwas schwieriger ist das Drucken und das Konvertieren zu einem anderen Format. Es ergeben sich nämlich eine Reihe von Problemen, die durch die mögliche vertikale Wiederholung ausgelöst werden. Es führt letztendlich kein Weg daran vorbei, einen temporären Puffer zu verwalten, der in der Lage ist, bis zu 255 Zeilen zu halten. In diesem Fall müssen Sie eine virtuelle Speicherverwaltung in Ihr Programm einfügen.

Dietmar Bückart


Pascal-Programm zur Bildschirmausgabe von IMG-Dateien
{$R-,S-,I-}
USES CRT, DOS, GRAPH;

VAR
  IMGFile      : FILE OF BYTE;
  IMGName      : STRING[79];
  Pattern      : ARRAY [1..16] OF BYTE;
  Muster       : ARRAY [0..4095] OF BYTE;
  MusterLen,
  Planes,
  Breite,
  Links,
  MaxBits,
  ActX,ActY    : WORD;
  gd, gm       : INTEGER;

FUNCTION GetIMGHeader :
INTEGER;
VAR
  I, J    : BYTE;
  HeadLen : WORD;
BEGIN
  Assign (IMGFile, IMGName);
  Reset (IMGFile);
  DOSERROR := IOResult;
  IF DOSERROR <> 0 THEN
  BEGIN
    GetIMGHeader := DOSERROR;
    EXIT;
  END;
  Seek (IMGFile, 2);            { die Versionsnummer überspringen }
  Read (IMGFile, I, J);         { die Länge des Vorspanns lesen   }
  HeadLen := I*256+J;           { Bytes vertauschen!              }
  Read (IMGFile, I, J);         { Anzahl der Planes lesen         }
  Planes := PRED(I*256+J);      { Bytes vertauschen!              }
  Read (IMGFile, I, J);         { Musterlänge lesen               }
  MusterLen := I*256+J;         { Bytes vertauschen!              }
  Seek (IMGFile, 12);           { Erzeuger-Info überspringen      }
  Read (IMGFile, I, J);         { Bildbreite lesen                }
  Breite := I*256+J;            { Bytes vertauschen!              }
  Links := ((640-Breite) DIV 2) AND $FFF8;
  Seek (IMGFile, HeadLen*2);
  GetIMGHeader := 0;
END;

PROCEDURE SetEGAWritePlane(Nr : BYTE);
BEGIN
  PORT[$3C4] := 2;
  PORT[$3C5] := 1 SHL Nr;
END;

PROCEDURE Anzeigen (v,f: BYTE; Count: WORD);
VAR
  I       : BYTE;
  L, Adrs : WORD;
BEGIN
  L := ActY;                    { Bildschirmzeile übernehmen }

  IF (gd=3) OR (gd=9)           { richtige EGA-Plane setzen  }
    THEN SetEGAWritePlane(f);   { falls EGA oder VGA         }

  IF (ActX+Count*8) > MaxBits   { rechten Rand beachten!     }
    THEN Count := (MaxBits DIV 8) - (ActX DIV 8);

  FOR I := 0 TO PRED(v) DO
  BEGIN
    CASE gd OF
      3, 9 : BEGIN    { VGA      }
               Adrs := L*80 + (ActX SHR 3);
               Move (Muster, Mem[$A000:Adrs], Count);
             END;
      7   : BEGIN     { Hercules }
              Adrs := (L AND 3) SHL 13 + 90*(L SHR 2) + (ActX SHR 3);
              Move (Muster, Mem[$B000:Adrs], Count);
            END;
      1   : BEGIN     { CGA      }
              Adrs := (L AND 1) SHL 13 + 80*(L SHR 1) + (ActX SHR 3);
              Move (Muster, Mem[$B800:Adrs], Count);
            END;
    END;

    INC(L);
    IF L > GetMaxY THEN EXIT;
  END;
  INC(ActX, Count*8);
END;

PROCEDURE GetScanLine;
VAR
  I, J, C,
  Farbe,
  VertRep :  BYTE;
  Count   :  WORD;
BEGIN
  Read (IMGFile, I, J, C);
  IF (I=0) AND (J=0) AND (C=$FF)
    THEN Read (IMGFile, VertRep)
    ELSE IF (I=0) AND (J=0)
           THEN EXIT
           ELSE BEGIN
                  Seek (IMGFile, FilePos(IMGFile)-3);
                  VertRep := 1;
                END;
  FOR Farbe := 0 TO Planes DO
  BEGIN
    ActX := Links;
    WHILE (ActX < Breite+Links) DO
    BEGIN
      Read (IMGFile, I);
      CASE I OF
        $00: BEGIN       { Pattern Run }
               Read (IMGFile, I);
               FOR J := 1 TO MusterLen DO
                 Read (IMGFile, Pattern[J]);

               FOR J := 0 TO 1-1 DO
                 Move (Pattern, Muster[J*MusterLen], MusterLen);

               Count := I*MusterLen;
             END;
        $80: BEGIN        { BitString  }
               Read (IMGFile, I);
               FOR J := 0 TO PRED(I) DO
                 Read (IMGFile, Muster[J]);
                 Count := I;
               END;
        ELSE BEGIN
               Count := I AND $7F;
               IF (I AND $80)=0
                 THEN FillChar (Muster, Count, 0)
                 ELSE FillChar (Muster, Count, $FF);
             END;
      END;

      IF ActY <= GetMaxY
        THEN Anzeigen(VertRep, Farbe, Count);
    END;
  END;

  INC(ActY, VertRep);
END;

PROCEDURE EncodeIMG;
BEGIN
  WHILE   (NOT Eof(IMGFile))    { Dateiende noch nicht erreicht     }
      AND (NOT KeyPressed)      { und keine Taste gedrückt          }
      AND (ActY < GetMaxY)      { und Bildschirmende nicht erreicht }
    Do GetScanLine;             { Zeile bearbeiten                  }
END;

BEGIN
  IF (ParamCount = 0) THEN
  BEGIN
    Writeln(^J^M'Sie haben keinen Dateinamen angegeben.'^J^M);
    HALT;
  END ELSE IMGName := ParamStr(1);

  IF Pos('.', IMGName)=0
    THEN IMGName := IMGName+'.img';

  IF (GetIMGHeader <> 0) THEN
  BEGIN
    Writeln(^J^M'Datei ', IMGName, ' nichtgefunden!'^J^M);
    HALT;
  END;

  ActX := 0;
  ActY := 0;

  DetectGraph(gd, gm);
  InitGraph(gd, gm, '');

  CASE gd OF                    { für Test im Anzeigen() }
    1, 3, 9 : MaxBits := 640;
    7       : MaxBits := 720;
  END;

  EncodeIMG;

  REPEAT UNTIL KeyPressed;

  Close (IMGFile);

  RestoreCrtMode;
END.

 

aus mc 01/91 – Seite 116-119