Artikel

Grafik mit Format

Aufbau von PCX-Dateien

PCX ist ein weitverbreitetes Grafikformat. Fast alle Grafikprogramme kommen damit klar. Weil der Aufbau von PCX-Dateien vollständig offengelegt worden ist, können auch selbstgeschriebene Programme die Bildpunkte nach Herzenslust tanzen lassen. Am Beispiel eines Quick-Pascal-Programms zeigen wir, wie es gemacht wird.

Das PCX-Format wurde Anfang der achtziger Jahre von ZSoft Corporation zur Speicherung der mit PC-Paintbrush erzeugten Grafiken entwickelt. PCX wird von nahezu allen professionellen Systemen unterstützt. Der Grund dafür liegt neben der weltweiten Verbreitung des Paintbrush-Malprogramms in der vollständig veröffentlichten Dokumentation des Dateiformats. Die enge Kopplung des PCX-Formats an Paintbrush bringt auch einige Nachteile mit sich. Als das Format entwickelt wurde, hatte niemand damit gerechnet, daß PCX-Bilder auf verschiedenen Grafikkarten verarbeitet werden müssen. Es war vorgesehen, PCX-Bilder nur von der Hardware zu laden, auf der sie erzeugt wurden. Inzwischen sind PCX-Bilder grundsätzlich auf jeder Hardware darstellbar.
Jede PCX-Datei beginnt mit einem 128 Byte langen Header, von dem derzeit aber nur 70 verwendet werden (Tabelle: Header einer PCX-Datei).
Die Koordinaten des gespeicherten Bildes können von der Auflösung des Erzeugers abweichen. Spätesten dann ist für den Programmierer Panik angesagt. Während sich vertikale Abweichungen noch durch einfaches Verdoppeln oder Ignorieren von Zeilen korrigieren lassen, geben horizontale Abweichungen den Startschuß für umfangreiche Bitmanipulationen. Denn benachbarte Bits stehen für benachbarte Bildpunkte, die gemäß dem ursprünglichen Formatdesign einfach in den Bildschirmspeicher kopiert werden sollten. Findet eine Skalierung statt, müssen alle nachfolgenden Bits verschoben und zu neuen Bytes komprimiert werden, bevor das Byte in den Bildschirmspeicher kopiert werden kann. Spätestens hier wird klar, warum selbst Paintbrush für Windows gelegentlich eine ganze Weile benötigt, bis es ein PCX-Bild lädt und anzeigt.

Header einer PCX-Datei
Byte Bezeichner Kommentar
0 Creator immer 10 = ZSoft
1 Version PCX-Version:
0 = Version 2.5
2 = Version 2.8 mit Farbpalette
3 = Version 2.8 ohne Farbpalette
5 = Version 3.0 mit Farbpalette
2 Encoding 1 = Runlength-Kodierung
3 Bits Anzahl der Bits pro Pixel und Farbplane;
CGA LoRes + VGA mit 256 Farben: 2 Bit;
alle anderen: 1 Bit pro Pixel
4 xmin, ymin
xmax, ymax
logische Koordinaten des Bildes
12 HRes horizontale Auflösung des Erzeugers
14 VRes vertikale Auflösung des Erzeugers
16 Palette Farbpalette 16 × 3 Bytes = 16 × (R, G, B)
64 VMode nur für interne Paintbrush-Verwendung
65 Planes Anzahl der Farbebenen
EGA/VGA : 4
CGA/Hercules : 1
66 BytePerLine Anzahl der gespeicherten Bytes pro
Scanzeile, wobei nur eine Farbplane
gerechnet wird
68 Paletteinfo Interpretation der Farben:
1 = Farbe (auch schwarzweiß!)
2 = Graustufen
70 dummy Füller auf 128 Bytes

Die PCX-Versionen 2.8 und 3.0 sind mit einer zusätzlichen Farbpalette ausgestattet (Versionsbyte = 2 bzw. 5) und offenbaren eine weitere Eigenheit des PCX-Formats. Da bei der Entwicklung des Formats an VGA-Karten mit 256 Farben noch gar nicht zu denken war, wurden lediglich 48 Byte für 16 Rot-, Grün- und Blauanteile eingeplant. Als dieser Speicherplatz nicht mehr ausreichte, wurde einfach an das Ende der PCX-Datei (hinter den Bilddaten) eine Zusatzpalette mit 768 Byte für die Farbwerte von VGA-Karten mit 256 Farben gepackt. Auf EGA-Grafikkarten müssen die Farbinformationen interpretiert werden. Denn in der PCX-Datei sind die Farbwerte im Bereich von 0–255 notiert. Die EGA-Karte kann aber nur Werte von 0–63 verarbeiten, dies jedoch in vier Stufen. Der korrekte Farbwert ergibt sich somit durch Division mit 64 (Tabelle 2).

Farbumsetzung auf der EGA
Farbwert EGA-Stufe
0 – 63 0
64 – 127 1
128 – 191 2
192 – 255 3

Die Bilddaten selbst sind zeilenweise komprimiert abgelegt. Das PCX-Format nutzt dazu die Runlength-Komprimierung, die speziell für Byte-Wiederholungen geeignet ist. Denn Grafiken haben oft große einfarbige Flächen, die durch gleichlautende Bytes beschrieben werden. Das PCX-Format nutzt diese Tatsache und packt sich wiederholende Bytes in zwei Bytes. Eine Komprimierung lohnt sich erst, wenn mindestens drei aufeinanderfolgende Bytes identisch sind. Aus diesem Grund kommen in PCX-Dateien sowohl komprimierte als auch "nackte" Datenbytes vor.
Erkennungszeichen für die Komprimierung sind die beiden höchstwertigen Bits eines Bytes, Bit 6 und 7. Haben beide Bits den Wert 1, dann beschreiben die Bit 0 bis 5 die Anzahl der Wiederholungen und das darauffolgende Byte den Farbwert.

Im Pseudocode lautet dieser Algorithmus:

temp := (gelesenes Byte);
IF (temp AND $C0) = $C0 THEN
BEGIN
  count := temp AND $3F;
  farbe := (nächstes Byte);
END;

Da für die Kodierung der Anzahl der Wiederholungen nur 6 Bit zur Verfügung stehen, ist der höchste mögliche Wert 63. Dies ist besonders beim Erzeugen von PCX-Dateien zu beachten.
Die Komprimierung arbeitet zeilenübergreifend. Das heißt, sowohl ein Wechsel der Farbebene, als auch ein Wechsel der Bildschirmzeile sind innerhalb einer Wiederholung möglich. Das lesende Programm muß selbst herausfinden, wie die Daten zu interpretieren sind. Als Steuerung für die Anzeige der PCX-Bilder kann allerdings der Wert der Header-Variable BytePerLine (Byte 66 + 67) verwendet werden. Diese Variable enthält immer die Anzahl der Bytes, die für eine komplette Farbebene einer Bildschirmzeile benötigt werden.
An dieser Stelle ist jedoch große Vorsicht angebracht, da PCX-Bilder größer als der Bildschirm werden können. Es ist darauf zu achten, daß nur so viele Bytes in den Bildschirmspeicher kopiert werden wie tatsächlich darstellbar sind.
Die abgedruckten Listings enthalten die Grundroutinen zum Speichern und Lesen von Bildern im PCX-Format. Da die Ablage hardwarenah erfolgt, ist eine genaue Kenntnis der jeweiligen Grafikkarte nötig. Die vorgestellten Routinen, die sowohl unter Quick Pascal als auch unter Turbo Pascal lauffähig sind, dienen als Vorlage für andere Grafikkarten-Exoten.
Mit der Funktion SavePCX lassen sich Bilder von CGA-, EGA-, VGA-, MCGA- und Hercules-Grafikkarten speichern. Das Einlesen ist etwas komplizierter. Eventuell sind einige Bitoperationen nötig, um die Grafiken auf das rechte Format zu bringen. Die Prozedur PutPCX unterstützt daher nur die Original CGA-, EGA-, VGA- und Hercules-Grafikformate ohne Skalierung.
Gestartet wird das Programm mit dem Befehl SHOWPCX Dateiname.

Dietmar Bückart


Listing 1. PCX.PAS: Unit für das Programm SHOWPCX.PAS
{ ****************************************************************** }
{ *  UNIT:   PCX.PAS  -  Routinen zum Bearbeiten von PCX-Dateien   * }
{ ****************************************************************** }
{$R-,S-,I-}

UNIT PCX;

INTERFACE

USES DOS;

CONST
  ActivePage    : WORD = 0;
  MAX_LINEBYTES        = 1023;
  HercBase             = $B000;
  EgaBase              = $A000;
  CgaBase              = $B800;

TYPE
  PlaneType = ARRAY[0..MAX_LINEBYTES] OF BYTE;
  plane     = ^Planetype;
  ScanLine  = ARRAY[0..3] OF plane;

VAR
  z : ScanLine;

TYPE
  PCX_HEADER = RECORD  { Header einer PCX-Datei:              }
    Creator    : BYTE; { Immer 10 für ZSoft                   }
    Version    : BYTE; { PCX-Version:                         }
                       { 0 = Version 2.5 ohne Palette-Info    }
                       { 2 = Version 2.8 mit Palette-Info     }
                       {     oder Version 3.0 ohne Palette    }
                       { 3 = Version 2.8/3.0 ohne Palette-Info}
                       { 5 = Version 3.0 mit Palette-Info     }
    Encoding   : BYTE; { 1 = Run-Length-Encoded               }
    Bits       : BYTE; { Anzahl der Pixel pro Bit; i.d.R. 1;  }
                       { für CGA 320x200 2 Bits, die aufein-  }
                       { ander folgen und die Farbe bilden    }
    xmin, ymin,        { die Dimension des Bildes             }
    xmax, ymax : INTEGER;
    HRes, VRes : INTEGER; { Auflösung des Erzeugers           }
    Palette    : ARRAY[0..15, 0..2] OF BYTE; { Farbpalette    }
    VMode      : BYTE;    { Reserviert                        }
    Planes     : BYTE;    { Anzahl der Farbebenen             }
    BytePerLine: INTEGER; { Bytes pro Scanzeile               }
    PaletteInfo: INTEGER; { 1 = Farbe/Schwarz-Weiß            }
                          { 2 = Grauwerte                     }
    dummy      : ARRAY[0..57] OF BYTE; { Füller auf 128 Bytes }
  END;

PROCEDURE SetEgaReadPlane ( Nr          : BYTE);
PROCEDURE SetEgaWritePlane( Nr          : BYTE);
PROCEDURE SetEgaReg       ( Nr, wert    : BYTE);

FUNCTION GetPCXHeader   (VAR PCXH       : PCX_Header;
                             name       : STRING) : INTEGER;

FUNCTION PutPCXHeader   (VAR PCXH       : PCX_Header;
                             name       : STRING) : INTEGER;

FUNCTION GetPCXByte     (VAR F          : FILE) : BYTE;

FUNCTION PutPCXByte     (VAR F          : FILE;
                             wert,
                             count      : BYTE) : INTEGER;

FUNCTION PutPCXLine     (VAR F          : FILE;
                         VAR buf        : plane;
                             count      : BYTE) : INTEGER;

PROCEDURE DefPCXPalette (VAR PCXH       : PCX_Header;
                             ColType    : BYTE);

FUNCTION SavePCX        (    gd, gm     : INTEGER;
                             XMin, YMin,
                             XMax, YMax : INTEGER;
                             name       : STRING) : INTEGER;

IMPLEMENTATION


CONST
  BLOCKSIZE : WORD = 512;

  PCXDefaultPalette : ARRAY[0..15, 0..2] OF BYTE =
     ( (0, 0, 0), (0, 0, 170), (0, 170, 0), (0, 170, 170),
       (170, 0, 0), (170, 0, 170), (170, 170, 0), (170, 170, 170),
       (85, 85, 85), (85, 85, 255), (85, 255, 85), (85, 255, 255),
       (255, 85, 85), (255, 85, 255), (255, 255, 85), (255, 255, 255) );

VAR
  pcxbuf : ARRAY[1..512] OF BYTE;

PROCEDURE SetEgaReadPlane(Nr : BYTE);
BEGIN
  PORT[$3CE] := 4;
  PORT[$3CF] := Nr;
END;

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

PROCEDURE SetEgaReg (Nr, wert : BYTE);
BEGIN
  PORT[$3CE] := Nr;
  PORT[$3CF] := wert;
END;

FUNCTION GetPCXHeader(VAR PCXH : PCX_Header;
                          name : STRING) : INTEGER;
VAR
  F : FILE;
BEGIN
  FillChar(PCXH, 128, 0);
  ASSIGN(F, name);
  Reset(F, 1);
  DOSERROR := IOResult;
  IF DOSERROR <> 0 THEN
  BEGIN
    GetPCXHeader := DOSERROR;
    EXIT;
  END;

  Blockread(F, PCXH, 128);
  DOSERROR := IOResult;
  IF DOSERROR <> 0 THEN
  BEGIN
    GetPCXHeader := DOSERROR;
    Close(F);
    EXIT;
  END;

  Close(F);
  GetPCXHeader := IOResult;
  IF (PCXH.version > 5) OR (PCXH.encoding > 1) THEN GetPCXHeader := -1;
END;

FUNCTION PutPCXHeader(VAR PCXH : PCX_Header;
                          name : STRING) : INTEGER;
VAR
  F : FILE;
BEGIN
  ASSIGN(F, name);
  Rewrite(F, 1);
  DOSERROR := IOResult;
  IF DOSERROR <> 0 THEN
  BEGIN
    PutPCXHeader := DOSERROR;
    EXIT;
  END;

  Blockwrite(F, PCXH, 128);
  DOSERROR := IOResult;
  IF DOSERROR <> 0 THEN
  BEGIN
    PutPCXHeader := DOSERROR;
    Close(F);
    IF IOResult <> 0 THEN;
    EXIT;
  END;

  Close(F);
  PutPCXHeader := IOResult;
END;

FUNCTION GetPCXByte(VAR F : FILE) : BYTE;
CONST
  count   : BYTE = 0;
  wert    : BYTE = 0;
  p       : WORD = 512;
  endfile : BOOLEAN = FALSE;
VAR
  temp    :   BYTE;

  PROCEDURE Read_Block;
  VAR
    result : WORD;
  BEGIN
    IF EOF(F) THEN endfile := TRUE
              ELSE BEGIN
                BlockRead(F, pcxbuf, BLOCKSIZE, result);
                IF result < BLOCKSIZE THEN BLOCKSIZE := result;
                p := 1
              END;
  END;

  FUNCTION get_byte : BYTE;
  BEGIN
    IF endfile THEN get_byte := 0
               ELSE BEGIN
                 IF p = BLOCKSIZE THEN Read_Block
                                  ELSE INC(p);
                 get_byte := pcxbuf[p];
               END;
  END;

BEGIN
  IF count > 0 THEN
  BEGIN
    DEC(count);
    GetPcxByte := wert;
    EXIT;
  END;

  temp := Get_byte;

  IF temp AND $C0 = $C0 THEN
  BEGIN
    count := temp AND $3F-1;
    wert  := Get_Byte;
  END
  ELSE BEGIN
    count := 0;
    wert  := temp;
  END;
  GetPCXByte := wert;
END;

FUNCTION PutPCXByte(VAR F  : FILE;
                        wert,
                        count : BYTE) : INTEGER;
CONST
  total : LongInt = 0;
BEGIN
  IF (count = 1) AND ($C0 <> $C0 AND wert) THEN
  BEGIN
    Blockwrite(F, wert, 1);
    PutPCXByte := IOResult;
    total := total + 1;
  END
  ELSE BEGIN
    count := $C0 OR count;
    Blockwrite(F, count, 1);
    PutPCXByte := IOResult;
    Blockwrite(F, wert, 1);
    PutPCXByte := IOResult;
    total := total + 2;
  END;
END;

FUNCTION PutPCXLine(VAR F     : FILE;
                    VAR buf   : plane;
                        count : BYTE) : INTEGER;
VAR
  this, last,
  cptr, RunCount : BYTE;

BEGIN
  PutPCXLine := 0;
  last       := buf^[0];
  RunCount   := 1;

  FOR cptr := 1 TO count-1 DO
  BEGIN
    IF buf^[cptr] = last THEN
    BEGIN
      INC(RunCount);
      IF RunCount = 62 THEN
      BEGIN
        DOSERROR := PutPCXByte(F, last, RunCount);
        IF DOSERROR <> 0 THEN
        BEGIN
          PutPCXLine := DOSERROR;
          EXIT;
        END;
        RunCount := 0;
      END;
    END
    ELSE BEGIN
      DOSERROR := PutPCXByte(F, last, RunCount);
      IF DOSERROR <> 0 THEN
      BEGIN
        PutPCXLine := DOSERROR;
        EXIT;
      END;
      last := buf^[cptr];
      RunCount := 1;
    END;
  END;

  IF RunCount > 0 THEN
  BEGIN
    DOSERROR := PutPCXByte(F, last, RunCount);
    IF DOSERROR <> 0 THEN PutPCXLine := DOSERROR;
  END;
END;

PROCEDURE DefPCXPalette(VAR PCXH : PCX_Header; ColTYPE : BYTE);
VAR
  I, J : INTEGER;
BEGIN
  CASE ColType OF
    0 : BEGIN
          FillChar(PCXH.Palette, 48, 255);
          FillChar(PCXH.Palette,  3, 0);
        END;
    1 : FOR I := 0 TO 15 DO
        BEGIN
          IF ODD(I) THEN FOR J := 0 TO 2 DO PCXH.Palette[I, J] := 240
                    ELSE FOR J := 0 TO 2 DO PCXH.Palette[I, J] := 0;
        END;
    2 : MOVE(PCXDefaultPalette, PCXH.Palette, 48);
  END;
END;

FUNCTION SavePCX(gd, gm     : INTEGER;
                 XMin, YMin,
                 XMax, YMax : INTEGER;
                 name       : STRING) : INTEGER;
VAR
  F    : FILE;
  Page : BYTE;
  I, J : INTEGER;
  SPtr : POINTER;
  PCXH : PCX_Header;

  PROCEDURE ErrorCheck;
  BEGIN
    IF DOSERROR <> 0 THEN
    BEGIN
      SavePCX := DOSERROR;
      EXIT;
    END;
  END;

  PROCEDURE ReOpenFile;
  BEGIN
    Assign(F, name);
    Reset(f, 1);
    DOSERROR := IOResult;
    ErrorCheck;
    SEEK(F, 128);
    DOSERROR := IOResult;
    ErrorCheck;
  END;

BEGIN
  FillChar(PCXH, 128, 0);
  PCXH.creator     := 10;
  PCXH.version     := 3;
  PCXH.encoding    := 1;
  PCXH.bits        := 1;
  PCXH.xmin        := Xmin;
  PCXH.ymin        := Ymin;
  PCXH.xmax        := XMax;
  PCXH.ymax        := YMax;
  PCXH.HRes        := 75;
  PCXH.VRes        := 75;
  PCXH.PaletteInfo := 1;

  CASE gd OF
    3,4,5,9 : BEGIN
                CASE gm OF
                  0 : BEGIN
                        PCXH.Planes := 4;
                        PCXH.BytePerLine := 80;
                        DefPCXPalette(PCXH, 2);
                        DOSERROR := PutPCXHeader(PCXH, name);
                        ErrorCheck;
                        ReOpenFile;
                        FOR I := 0 TO 199 DO
                        BEGIN
                          SPTR := Ptr(EgaBase + $400 * ActivePage, I*80);
                          FOR Page := 0 TO 3 DO
                          BEGIN
                            SetEgaReadPlane(Page);
                            MOVE(SPtr^, Z[0]^, 80);
                            DOSERROR := PutPCXLine(F, Z[0], 80);
                            ErrorCheck;
                          END;
                        END;
                      END;
                  1 : BEGIN
                        PCXH.Planes := 4;
                        PCXH.BytePerLine := 80;
                        DefPCXPalette(PCXH, 2);
                        DOSERROR := PutPCXHeader(PCXH, name);
                        ErrorCheck;
                        ReOpenFile;
                        FOR I := 0 TO 349 DO
                        BEGIN
                          SPTR := Ptr(EgaBase + $800 * ActivePage, I*80);
                          FOR Page := 0 TO 3 DO
                          BEGIN
                            SetEgaReadPlane(Page);
                            MOVE(SPtr^, Z[0]^, 80);
                            DOSERROR := PutPCXLine(F, Z[0], 80);
                            ErrorCheck;
                          END;
                        END;
                      END;
                  2 : BEGIN
                        PCXH.Planes := 4;
                        PCXH.BytePerLine := 80;
                        DefPCXPalette(PCXH, 2);
                        DOSERROR := PutPCXHeader(PCXH, name);
                        ErrorCheck;
                        ReOpenFile;
                        FOR I := 0 TO 479 DO
                        BEGIN
                          SPTR := Ptr(EgaBase + $960 * ActivePage, I*80);
                          FOR Page := 0 TO 3 DO
                          BEGIN
                            SetEgaReadPlane(Page);
                            MOVE(SPtr^, Z[0]^, 80);
                            DOSERROR := PutPCXLine(F, Z[0], 80);
                            ErrorCheck;
                          END;
                        END;
                      END;
                  3 : BEGIN
                        PCXH.Planes := 1;
                        PCXH.BytePerLine := 80;
                        PCXH.Version := 2;
                        DefPCXPalette(PCXH, 0);
                        DOSERROR := PutPCXHeader(PCXH, name);
                        ErrorCheck;
                        ReOpenFile;
                        SetEgaReadPlane(0);
                        FOR I := 0 TO 349 DO
                        BEGIN
                          SPTR := Ptr(EgaBase + $800 * ActivePage, I*80);
                          MOVE(SPtr^, Z[0]^, 80);
                          BlockWrite(F, Z[0]^, 80);
                          DOSERROR := PutPCXLine(F, Z[0], 80);
                          ErrorCheck;
                        END;
                      END;
                END;
              END;
          7 : BEGIN { CASE gd OF 7 }
                PCXH.Planes := 1;
                PCXH.BytePerLine := 90;
                PCXH.Version := 2;
                DefPCXPalette(PCXH, 0);
                DOSERROR := PutPCXHeader(PCXH, name);
                ErrorCheck;                 ReOpenFile;
                FOR I := 0 TO 347 DO
                BEGIN
                  SPtr := Ptr(HercBase, WORD((I AND 3) SHL 13 + 90*(I SHR 2)));
                  MOVE(SPtr^, Z[0]^, 90);
                  DOSERROR := PutPCXLine(F, Z[0], 90);
                  ErrorCheck;
                END;
              END;
        1,2 : BEGIN { CASE gd OF 1, 2 }
                PCXH.Planes      := 1;
                PCXH.Bits        := 2;
                PCXH.BytePerLine := 80;
                IF (gd = 2) AND (gm = 3) THEN
                BEGIN
                  J := 479;
                  PCXH.Bits := 1;
                END
                ELSE J := 199;
                IF gm = 4 THEN PCXH.Bits := 1;
                PCXH.Version := 5;
                DefPCXPalette(PCXH, 1);
                DOSERROR := PutPCXHeader(PCXH, name);
                ErrorCheck;
                ReOpenFile;
                FOR I := 0 TO J DO
                BEGIN
                  SPtr := Ptr(CgaBase, WORD((I AND 1) SHL 13 + 80*(I SHR 1)));
                  MOVE(SPtr^, Z[0]^, 80);
                  DOSERROR := PutPCXLine(F, Z[0], 80);
                  ErrorCheck;
                END;
              END;
  END;
  CLOSE(F);
  IF IOResult <> 0 THEN;
END;

BEGIN
END.

 

Listing 2. SHOWPCX.PAS: Programm zum Zeigen von PCX-Dateien
{ ******************************************************** }
{ *  Programm: SHOWPCX - Anzeigen von PCX-Bilder         * }
{ ******************************************************** }
{SR-,S-,I-}

USES CRT, DOS, GRAPH, PCX;

CONST
{ Kartencodes: }
  PCX_VGA      = 0;
  PCX_EGA      = 1;
  PCX_CGA      = 2;
  PCX_HERCULES = 3;

{ Fehlercodes: }
  NoScanMem    = 1010;
  NoInitGraph  = 1011;
  NoCrtSupport = 1012;
  NoPcxFound   = 1013;
  WrongPcxType = 1014;

VAR
  Header        : PCX_HEADER; { PCX-Header                 }
  name          : STRING;     { Name der PCX-Datei         }
  gm, gd,                     { Grafik-Initialisierung     }
  screen,                     { Bildschirmtyp              }
  MaxScanLines,               { Anzahl der Bildzeilen      }
  MoveBytes     : INTEGER;    { Anzahl der Bytes pro Zeile }

PROCEDURE PutPCX(VAR Header : PCX_HEADER;
                     name   : STRING);
VAR
  F     : FILE;
  I,J,L : INTEGER;
  P     : POINTER;
BEGIN
  Assign(F, name);
  IF IOResult <> 0 THEN EXIT;

  Reset(F, 1);
  IF IOResult <> 0 THEN EXIT;

  Seek(f, 128);
  IF IOResult <> 0 THEN EXIT;

  IF (Screen = PCX_EGA) OR (Screen = PCX_VGA) THEN
  BEGIN
    SetEgaReg(5, 0);
    SetEgaReg(1, 0);
  END;

  FOR L := 0 TO MaxScanLines DO
  BEGIN
    FOR J := 0 TO Header.Planes-1 DO
    BEGIN
      FOR I:= 0 TO Header.BytePerLine-1 DO
      BEGIN
        z[J]^[I] := GetPCXByte(F);
      END;
    END;

    CASE screen OF
      PCX_EGA,
      PCX_VGA      : BEGIN   { alle Planes anzeigen }
                       P := Ptr(EgaBase, L*80);
                       FOR J := 0 TO Header.Planes-1 DO
                       BEGIN
                         SetEgaWritePlane(J);
                         MOVE(Z[J]^, P^, MoveBytes);
                       END;
                     END;
      PCX_HERCULES : BEGIN   { nur die erste Plane wird geschrieben }
                       P := Ptr(HercBase, WORD((L AND 3) SHL 13 + 90*(L SHR 2)));
                       MOVE(Z[0]^, P^, MoveBytes);
                     END;
      PCX_CGA      : BEGIN   { nur die erste Plane wird geschrieben }
                       P := Ptr(CgaBase, WORD((L AND 1) SHL 13 + 80*(L SHR 1)));
                       MOVE(Z[0]^, P^, MoveBytes);
                     END;
    END;
  END;

  Close(F);
  IF IOResult <> 0 THEN {};

  IF (Screen = PCX_EGA) OR (Screen = PCX_VGA) THEN SetEgaWritePlane($F);
END;

VAR
  I : INTEGER;
BEGIN
  IF paramcount < 1 THEN HALT(1);
  name := ParamStr(1);

  DetectGraph(gd, gm);

  CASE gd OF
    3 : Screen := PCX_EGA;
    1 : Screen := PCX_CGA;
    7 : Screen := PCX_HERCULES;
    9 : Screen := PCX_VGA;
  END;

  I := GetPCXHeader(Header, name);
  IF I <> 0 THEN HALT(NoPcxFound);

  IF (Header.Bits > 1) AND (gd <> 1) AND (gd <> 2) THEN HALT(WrongPcxType);

  IF (Header.Bits = 1) AND ((gd = 1) OR (gd = 2)) THEN HALT(WrongPcxType);

  FOR I := 0 TO Header.planes-1 DO GetMem(z[I], Header.BytePerLine);

  initgraph(gd, gm, '');

  IF GraphResult <> 0 THEN HALT(NoInitGraph);

  MoveBytes := GetmaxX DIV 8;
  IF MoveBytes > Header.BytePerLine THEN MoveBytes := Header.BytePerLine;

  MaxScanLines := Header.ymax - Header.ymin;

  IF MaxScanLines > GetMaxY + 1 THEN MaxScanLines := GetMaxY;

  PutPCX(Header, name);

  REPEAT UNTIL KEYPRESSED;

  RestoreCrtMode;
END.

 

aus mc 12/90 – Seite 104-109