Program showpcx;
{ Free Software by TapirSoft Gisbert W.Selke, Dec 1991                       }
{$A+,B-,D+,E+,F-,I-,L+,N-,O-,V- }
{$M 65520,0,128000 }

{$UNDEF DEBUG }     { DEFINE while debugging }

{$IFDEF DEBUG }
{$R+,S+ }
{$ELSE }
{$R-,S- }
{$ENDIF }

  Uses Graph, CRT, Dos;

  Const progname = 'ShowPCX';
        version  = '1.0';
        copyright= 'Free Software by TapirSoft Gisbert W.Selke, Dec 1991';

        bufsize  = 60000;
        maxlinlen= 2048; { maximum length of screen line }
        Tab      = #9;
        finishset: Set Of char = [#3,#27,'q','Q'];

  Type headrec = Record
                   id           : byte;  { must be $0A }
                   version      : byte;  { 0, 2, 3, or 5 }
                   compr        : byte;  { 1 if RLE-coded }
                   bitsperpixel : byte;
                   xmin         : word;
                   ymin         : word;
                   xmax         : word;
                   ymax         : word;
                   horidpi      : word; { horizontal resolution, dots per inch }
                   vertdpi      : word; { vertical   resolution, dots per inch }
                   colormap     : Array [0..15,0..2] Of byte;
                   reserved     : byte;
                   ncolplanes   : byte; { number of colour planes; max 4 }
                   bytesperline : word; { must be even }
                   greyscale    : word; { 1 if colour or b/w; 2 if greyscale }
                   filler       : Array [1..58] Of byte;
                 End;
       buffer   = Array [1..bufsize ] Of byte;
       linbuffer= Array [0..maxlinlen] Of byte;

  Var listf : text;
      inbufptr : ^buffer;
      sr : SearchRec;
      saveexit : Pointer;
      dir, picname : string;
      grdriver, grmode : integer;
      maxx, maxy, maxcolour, deltime : word;
      parampt, xscale, yscale, videomode : byte;
      zverbose, zxcentre, zycentre, zprop, zmono, zconj, zebra : boolean;
      zquiet, zgraph, zlist, zfirst, zfinish, zfound, zrepeat : boolean;

  { Link in graphics drivers for EGA, VGA and Hercules: }
  Procedure egavga_driver; External;
  {$L EGAVGA.OBJ }
  Procedure svga256_driver; External;
  {$L SVGA256.OBJ }
  Procedure herc_driver; External;
  {$L HERC.OBJ }

{$F+} function DetectVGA256 : integer; {$F-}
var
  DetectedDriver : integer;
  SuggestedMode  : integer;
begin
  DetectGraph(DetectedDriver, SuggestedMode);
  DetectVGA256 := SuggestedMode;
  if (DetectedDriver = VGA) or (DetectedDriver = MCGA) then
    DetectVGA256 := 0        { Default video mode = 0 }
  else
    DetectVGA256 := grError; { Couldn't detect hardware }
end; { DetectVGA256 }

  {$F+ } Procedure myexit; {$F- }
  { exit procedure to clean things up                                        }
    Var c : char;
  Begin                                                             { myexit }
    ExitProc := saveexit;
    NoSound;
    If zgraph Then
    Begin
      SetGraphMode(GetGraphMode);
      CloseGraph;
      zgraph := False;
    End;
    If Not zfound Then writeln('No matching PCX files found.');
    While KeyPressed Do c := ReadKey;
  End;                                                              { myexit }

  Procedure beep;
  { emit a short beep                                                        }
  Begin                                                               { beep }
    If Not zquiet Then
    Begin
      Sound(440);
      Delay(50);
      NoSound;
    End;
  End;                                                                { beep }

  Procedure abort(msg : string; ierr : byte);
  { show error message and die                                               }
  Begin                                                              { abort }
    If zgraph Then CloseGraph;
    zgraph := False;
    If msg <> '' Then writeln(progname,': ',msg);
    Halt(ierr);
  End;                                                               { abort }

  Procedure usage;
  { show usage hints and die                                                 }
  Begin                                                              { usage }
    writeln;
    writeln(progname,' ',version,': display PCX files on screen');
    writeln(copyright);
    writeln;
    writeln('Usage: ',progname,'  [<options>] <filespec> [<filespec>...]');
    writeln('       where <filespec> is the name of a PCX file, possibly ',
            'containing');
    writeln('             wildcard characters (default extension .PCX),');
    writeln('       or "@", followed immediately by the name of a file ');
    writeln('             containing names of PCX files.');
    writeln('       Options: /c  : centre image');
    writeln('                /cx : centre image horizontally');
    writeln('                /cy : centre image vertically');
    writeln('                /d<num>  : delay in milliseconds after each ',
            'image');
    writeln('                /e<num>  : extended VGA mode (use at your own ',
            'risk!)');
    writeln('                /h  : display help');
    writeln('                /m  : force monochrome mode');
    writeln('                /p  : use alternate packing strategy for scaling');
    writeln('                /q  : quiet behaviour (don''t beep)');
    writeln('                /r  : repeat indefinitely');
    writeln('                /s<num>  : scale image by factor ',
            '1/<num> (0 = autoscale)');
    writeln('                /sx<num> : scale horizontally only');
    writeln('                /sy<num> : scale vertically only');
    writeln('                /v  : verbose image info');
    writeln('                /z  : zebra monochrome mode');
    zfound := True;
    abort('',1);
  End;                                                               { usage }

  Procedure strip(Var s : string);
  { remove leading and trailing white space                                  }
  Begin                                                              { strip }
    While (s <> '') And (s[1] In [' ',Tab]) Do Delete(s,1,1);
    While (s <> '') And (s[Length(s)] In [' ',Tab]) Do Delete(s,Length(s),1);
  End;                                                               { strip }

  Function getnextname : string;
  { get name of next file to display                                         }
    Var temp, nam, ext : string;
        doserr : integer;
  Begin                                                        { getnextname }
    sr.name := '';
    doserr := 0;
    If zfirst Then
    Begin
      temp := '';
      While zlist And (temp = '') Do
      Begin
        If EoLn(listf) And (Not EoF(listf)) Then readln(listf);
        If IOResult <> 0 Then;
        If zlist And EoF(listf) Then
        Begin
          Close(listf);
          Dispose(inbufptr);
          zlist := False;
        End;
        If zlist Then read(listf,temp);
        If IOResult <> 0 Then;
        strip(temp);
      End;
      If temp = '' Then
      Begin
        While (temp = '') And (parampt <= ParamCount) Do
        Begin
          If (parampt = ParamCount) And zrepeat And zfound Then parampt := 0;
          Inc(parampt);
          If parampt <= ParamCount Then temp := ParamStr(parampt);
          If temp[1] In ['-','/'] Then temp := '';
        End;
        If temp[1] = '@' Then
        Begin
          Assign(listf,Copy(temp,2,255));
          Reset(listf);
          If IOResult <> 0 Then;
          New(inbufptr);
          SetTextBuf(listf,inbufptr^);
          zlist := True;
          temp := getnextname;
        End;
      End;
      If temp <> '' Then
      Begin
        FSplit(temp,dir,nam,ext);
        If ext = '' Then ext := '.PCX';
        temp := dir + nam + ext;
        FindFirst(temp,ReadOnly+Hidden+SysFile+Archive,sr);
        doserr := DosError;
        If doserr = 0 Then zfound := True;
        zfirst := False;
      End
      Else
      Begin
        dir := '';
        sr.name := '';
      End;
    End
    Else
    Begin
      FindNext(sr);
      doserr := DosError;
    End;
    If doserr = 18 Then
    Begin
      zfirst := True;
      getnextname := getnextname;
    End
      Else getnextname := dir + sr.name;
  End;                                                         { getnextname }

  Procedure init;
  { do all necessary initializations                                         }
    Var temp : string;
        l : integer;
        i : byte;

    Function getnumber(str : string; min, max, default : word) : word;
    { convert a string to a number, checling bounds                          }
      Var num : longint;
          ires : integer;
    Begin                                                        { getnumber }
      ires := 0;
      {$R- }
      If str = '' Then num := default
                  Else Val(str,num,ires);
{$IFDEF DEBUG }
      {$R+ }
{$ENDIF }
      If ires <> 0 Then num := default;
      If num < min Then num := min;
      If num > max Then num := max;
      getnumber := num;
    End;                                                         { getnumber }

  Begin                                                               { init }
    If RegisterBGIDriver(@egavga_driver) < 0 Then
                   abort('Illegal EGA/VGA graphics driver information',2);
    If RegisterBGIDriver(@herc_driver) < 0 Then
                   abort('Illegal Hercules graphics driver information',2);
    zgraph   := False;
    saveexit := ExitProc;
    ExitProc := @myexit;
    zfirst   := True;
    zfinish  := False;
    zlist    := False;
    zquiet   := False;
    zverbose := False;
    zrepeat  := False;
    zmono    := False;
    zconj    := False;
    zebra    := False;
    zxcentre := False;
    zycentre := False;
    zprop    := False;
    deltime  := 65535;
    xscale   := 255;
    yscale   := 255;
    zfound   := False;
    parampt  := 0;
    FileMode := 0;
    If ParamCount = 0 Then usage;
    grdriver := Detect;
    grmode   := 0;
    videomode:= 255;
    InitGraph(grdriver,grmode,'');
    If GraphResult <> 0 Then abort('Cannot find graphics driver',2);
    zgraph   := True;
    For i := 1 To ParamCount Do
    Begin
      temp := ParamStr(i);
      If (temp[1] In ['-','/']) And (Length(temp) >= 2) Then
      Begin
        Case UpCase(temp[2]) Of
          'C' : Begin { centering }
                  If (Length(temp) >= 3) Then
                  Begin
                    Case UpCase(temp[3]) Of
                      'X' : zxcentre := True;
                      'Y' : zycentre := True;
                    End;
                  End
                  Else
                  Begin
                    zxcentre := True;
                    zycentre := True;
                  End;
                End;
          'E' : Begin { extended video mode }
                  If (Length(temp) >= 3) Then videomode :=
                                         getnumber(Copy(temp,3,255),0,255,0)
                                         Else videomode := 10;
                End;
          'D' : Begin { delay }
                  If (Length(temp) >= 3) Then deltime :=
                                         getnumber(Copy(temp,3,255),0,65534,10)
                                         Else deltime := 10;
                End;
          'H','?' : usage;
          'M' : zmono := True;
          { monochrome }
          'P' : Begin { packing strategy (for scaling) }
                  If (Length(temp) <= 2) Or (UpCase(temp[3]) = 'C') Then
                                                                zconj := True;
                End;
          'Q' : zquiet  := True;
          'R' : zrepeat := True;
          'S' : Begin { scaling }
                  If (Length(temp) >= 3) Then
                  Begin
                    Case UpCase(temp[3]) Of
                      'X' : xscale := getnumber(Copy(temp,4,255),0,255,0);
                      'Y' : yscale := getnumber(Copy(temp,4,255),0,255,0);
                      Else Begin
                             xscale := getnumber(Copy(temp,3,255),0,255,0);
                             yscale := xscale;
                             zprop  := True;
                           End;
                    End;
                  End
                  Else
                  Begin
                    xscale := 0;
                    yscale := 0;
                    zprop  := True;
                  End;
                End;
          'V' : zverbose := True;
          'Z' : Begin { zebra monochrome }
                  zmono := True;
                  zebra := True;
                End;
          Else usage;
        End;
      End;
    End;
    If (videomode <> 255) And (grdriver = VGA) Then
    Begin
      l := InstallUserDriver('SVGA256', @DetectVGA256);
      If l > 0 Then
      Begin
        grdriver := l;
        grmode := videomode;
        CloseGraph;
        If RegisterBGIDriver(@svga256_driver) < 0 Then
                   abort('Illegal SuperVGA graphics driver information',2);
        InitGraph(grdriver,grmode,'');
      End;
    End;
    maxx     := GetMaxX;
    maxy     := GetMaxY;
    maxcolour:= GetMaxColor;
    If maxx > maxlinlen Then abort('Screen too wide for internal buffer',2);
  End;                                                                { init }

  Procedure showfile(nam : string);
  { display the given PCX file                                               }

    Var picf : File;
        header : headrec;
        linbuf : linbuffer;
        picbuf : buffer;
        ltemp  : longint;
        iread, x, y, x2, y2, j, thisbyte : word;
        answer : char;
        repeatct, b, b2, c, i, horisub, vertsub, horict, vertct : byte;
        bitsperplane : byte;
        zdecomp, zcompr : boolean;

    Procedure showheader;
    { if in verbose mode, display info on PCX file                           }
    Begin                                                       { showheader }
      RestoreCRTMode;
      ClrScr;
      write('File: ',nam);
      writeln(' (Size: ',FileSize(picf),')');
      With header Do
      Begin
        write  ('Version: ',version:4,'; ');
        Case compr Of
          0 : writeln('Uncompressed');
          1 : writeln('RLE-compressed');
          Else writeln('Unknown compression method');
        End;
        write  ('Upper left corner: (',xmin:4,',',ymin:4,'); ');
        writeln('lower right corner: (',xmax:4,',',ymax:4,')');
        write  ('Resolution: horizontal: ',horidpi:4,' dpi; ');
        writeln('vertical: ',vertdpi:4,' dpi');
        write  ('Bits per pixel: ',bitsperpixel:4,'; ');
        writeln('number of colour planes: ',ncolplanes:4);
        write  ('Bytes per line: ',bytesperline:4,'; ');
        If greyscale = 2 Then writeln('display as grey scales')
                         Else writeln('display as colour rsp. b/w');
      End;
      write('Hit space bar to continue... ');
      answer  := ReadKey;
      zfinish := answer In FinishSet;
      If Not zfinish Then answer := #0;
      SetGraphMode(grmode);
    End;                                                        { showheader }

    Function getnextbyte : byte;
    { reads next byte from input file, handling compression                  }

      Procedure getnextchunk;
      { get next chunk from input file                                       }
      Begin                                                   { getnextchunk }
        If EoF(picf) Then iread := 0
        Else
        Begin
          BlockRead(picf,picbuf,SizeOf(picbuf),iread);
          If IOResult <> 0 Then iread := 0;
        End;
        thisbyte := 0;
      End;                                                    { getnextchunk }

    Begin                                                      { getnextbyte }
      If Not zdecomp Then
      Begin
        If thisbyte >= iread Then getnextchunk;
        If thisbyte < iread Then
        Begin
          Inc(thisbyte);
          If zcompr And (picbuf[thisbyte] >= 192) Then
          Begin
            repeatct := picbuf[thisbyte] And $3F;
            zdecomp := repeatct > 0;
            If thisbyte >= iread Then getnextchunk;
            Inc(thisbyte);
          End;
        End;
      End;
      If zdecomp Then
      Begin
        getnextbyte := picbuf[thisbyte];
        Dec(repeatct);
        zdecomp := repeatct > 0;
      End
      Else
      Begin
        If iread > 0 Then
        Begin
          getnextbyte := picbuf[thisbyte];
        End
          Else getnextbyte := 0;
      End;
    End;                                                       { getnextbyte }

    Procedure VGASetAllPalette(var P);
    { set all colour registers of the VGA quickly; values are RGB, 0..63     }
      Var regs : Registers;
    Begin                                                 { VGASetAllPalette }
      With regs Do
      Begin
        ax := $1012;
        bx := 0;
        cx := 256;
        es := Seg(P);
        dx := Ofs(P);
      End;
      Intr($10, regs);
    End;                                                  { VGASetAllPalette }

  Begin                                                           { showfile }
    ClearDevice;
    Assign(picf,nam);
    Reset(picf,1);
    answer := #0;
    If IOResult = 0 Then
    Begin
      BlockRead(picf,header,SizeOf(header),iread);
      If iread <> SizeOf(header) Then abort('PCX file too short',3);
    End;
    If IOResult = 0 Then
    Begin
      If zverbose Then showheader;
      With header Do
      Begin
        If id <> $0A Then abort('Illegal PCX header',3);
        If Not (version In [0,2,3,5]) Then abort('Illegal PCX header',3);
        If Not (compr In [0,1]) Then abort('Illegal PCX header',3);
        If Not (ncolplanes In [0..4]) Then abort('Illegal PCX header',3);
        If Odd(bytesperline) Then abort('Illegal PCX header',3);
        If Not (greyscale In [1..2]) Then greyscale := 1;
      End;
    End;
    With header Do
    Begin
      If ncolplanes = 0 Then ncolplanes := 1;
      bitsperplane := bitsperpixel*ncolplanes;
      i := grmode;
      x := xmax - xmin + 1;
      y := ymax - ymin + 1;
      Case grdriver Of
        CGA  : Begin
                 If x <= 320 Then i := CGAC0
                             Else i := CGAHi;
               End;
        MCGA, ATT400 : Begin
                 If (x <= 320) And (y <= 200) Then i := MCGAC0
                 Else
                 Begin
                   If y <= 200 Then i := MCGAMed
                               Else i := MCGAHi;
                 End;
               End;
        EGA, EGA64, EGAMono : Begin
                 If y <= 200 Then i := EGALo
                 Else
                 Begin
                   If grdriver = EGAMono Then i := EGAMonoHi
                                         Else i := EGAHi;
                 End;
               End;
        VGA  : Begin
                 If y <= 200 Then i := VGALo
                 Else
                 Begin
                   If y <= 350 Then i := VGAMed
                               Else i := VGAHi;
                 End;
               End;
      End;
      If i <> grmode Then
      Begin
        SetGraphMode(i);
        grmode    := GetGraphMode;
        maxx      := GetMaxX;
        maxy      := GetMaxY;
        maxcolour := GetMaxColor;
      End;
      If (Not zmono) And (version In [2,5]) And
         ((grdriver In [EGA,EGA64,VGA]) Or (videomode <> 255)) Then
      Begin
        Case bitsperplane Of
          4 : Begin
                For i := 0 To 15 Do
                  SetRGBPalette(i,colormap[i,0],colormap[i,1],colormap[i,2]);
              End;
          8 : Begin
                ltemp := FilePos(picf);
                Seek(picf,FileSize(picf)-768);
                BlockRead(picf,picbuf,768,x);
                Seek(picf,ltemp);
                If x = 768 Then
                Begin
                  For y := 1 To 768 Do picbuf[y] := picbuf[y] ShR 2;
                  VGASetAllPalette(picbuf);
                End;
              End;
        End;
      End;
      horisub := xscale;
      If xscale = 255 Then horisub := 1;
      If xscale = 0 Then
      Begin
        horisub := 1;
        While ((xmax-xmin+horisub-1) Div horisub) > maxx+5 Do Inc(horisub);
      End;
      vertsub := yscale;
      If yscale = 255 Then vertsub := 1;
      If yscale = 0 Then
      Begin
        vertsub := 1;
        While ((ymax-ymin+vertsub-1) Div vertsub) > maxy+5 Do Inc(vertsub);
      End;
      If zprop Then
      Begin
        If (horisub < vertsub) And (xscale = 0) Then horisub := vertsub;
        If (vertsub < horisub) And (yscale = 0) Then vertsub := horisub;
      End;
      If zxcentre Then
      Begin
        x := (xmax-xmin+horisub-1) Div horisub;
        If x < maxx Then
        Begin
          xmax := xmax - xmin + (maxx - x) Div 2;
          xmin := (maxx - x) Div 2;
        End;
      End;
      If zycentre Then
      Begin
        y := (ymax-ymin+vertsub-1) Div vertsub;
        If y < maxy Then
        Begin
          ymax := ymax - ymin + (maxy - y) Div 2;
          ymin := (maxy - y) Div 2;
        End;
      End;
      zcompr := compr = 1;
      thisbyte := Succ(iread);
      zdecomp := False;
      y  := ymin;
      y2 := ymin;
      vertct := 0;
      While (y <= ymax) And (y2 <= maxy) And (Not KeyPressed) And
                                             (Not zfinish) Do
      Begin
        If y2 < maxy Then
        Begin
          If y2-ymin <= maxx Then PutPixel(y2-ymin,maxy,maxcolour);
        End
        Else
        Begin
          SetColor(Black);
          Line(0,maxy,maxx,maxy);
        End;
        Case bitsperplane Of
          1 : Begin
            x  := xmin;
            x2 := xmin;
            horict := 0;
            If zconj Then b2 := $FF
                     Else b2 := 0;
            For j := 1 To bytesperline Do
            Begin
              b := getnextbyte;
              If vertct = 0 Then
              Begin
                For i := 1 To 8 Do
                Begin
                  If (x <= xmax) And (x2 <= maxx) Then
                  Begin
                    If zconj Then b2 := b2 And b
                             Else b2 := b2 Or  b;
                    Inc(horict);
                    If horict = horisub Then
                    Begin
                      If (b2 And $80) <> 0 Then PutPixel(x2,y2,maxcolour);
                      If zconj Then b2 := $FF
                               Else b2 := 0;
                      Inc(x2);
                      horict := 0;
                    End;
                    {$R- }
                    b := b ShL 1;
{$IFDEF DEBUG }
                    {$R+ }
{$ENDIF }
                    Inc(x);
                  End;
                End;
              End;
            End;
          End;
          2..7 : Begin
            FillChar(linbuf,Succ(maxx),#0);
            For c := 1 To ncolplanes Do
            Begin
              x  := xmin;
              x2 := 0;
              horict := 0;
              If zconj Then b2 := $FF
                       Else b2 := 0;
              For j := 1 To bytesperline Do
              Begin
                b := getnextbyte;
                If vertct = 0 Then
                Begin
                  For i := 1 To 8 Do
                  Begin
                    If (x <= xmax) And (x2 <= maxx) Then
                    Begin
                      If zconj Then b2 := b2 And b
                               Else b2 := b2 Or  b;
                      Inc(horict);
                      If horict = horisub Then
                      Begin
                        linbuf[x2] := linbuf[x2] ShL 1;
                        If (b2 And $80) <> 0 Then Inc(linbuf[x2]);
                        If zconj Then b2 := $FF
                                 Else b2 := 0;
                        Inc(x2);
                        horict := 0;
                      End;
                      {$R- }
                      b := b ShL 1;
{$IFDEF DEBUG }
                      {$R+ }
{$ENDIF }
                      Inc(x);
                    End;
                  End;
                End;
              End;
            End;
            If vertct = 0 Then
            Begin
              x  := xmin;
              x2 := 0;
              While x <= xmax Do
              Begin
                If linbuf[x2] <> 0 Then
                Begin
                  If zmono Then
                  Begin
                    If zebra Then
                    Begin
                      If Odd(linbuf[x2]) Then PutPixel(x,y2,maxcolour);
                    End
                    Else PutPixel(x,y2,maxcolour);
                  End
                    Else PutPixel(x,y2,linbuf[x2] Mod Succ(maxcolour));
                End;
                Inc(x2);
                Inc(x,horisub);
              End;
            End;
          End;
          8 : Begin
             If vertct = 0 Then
             Begin
               x := xmin;
               j := 1;
               While (j <= bytesperline) Do
               Begin
                 If zconj Then b2 := $FF
                          Else b2 := 0;
                 For i := 1 To horisub Do
                 Begin
                   If j <= bytesperline Then
                   Begin
                     b := getnextbyte;
                     If zconj Then
                     Begin
                       If b < b2 Then b2 := b;
                     End
                     Else
                     Begin
                       If b > b2 Then b2 := b;
                     End;
                     Inc(j);
                   End;
                 End;
                 If (b2 <> 0) And (x <= xmax) Then
                 Begin
                   If zmono Then
                   Begin
                     If zebra Then
                     Begin
                       If Odd(b2) Then PutPixel(x,y2,maxcolour);
                     End
                       Else PutPixel(x,y2,maxcolour);
                   End
                     Else PutPixel(x,y2,b2 Mod Succ(maxcolour));
                   If (x > 20) And (y2 > 20) And (x < 750) And (y < 300) And
                      (b2 = 0) Then
                   Begin
                     b2 := b2;
                   End;
                 End;
                 Inc(x);
               End;
             End
             Else
             Begin
               For j := 1 To bytesperline Do b := getnextbyte;
             End;
          End;
        End;
        Inc(y);
        If vertct = 0 Then Inc(y2);
        vertct := Succ(vertct) Mod vertsub;
      End;
      If y2 <= maxy Then
      Begin
        SetColor(Black);
        Line(0,maxy,maxx,maxy);
      End;
    End;
    Close(picf);
    If IOResult <> 0 Then;
    beep;
    x := 0;
    If KeyPressed Then answer := ReadKey;
    While (x < deltime) And (answer = #0) Do
    Begin
      Delay(100);
      If deltime < 65535 Then x := x + 100;
      If KeyPressed Then answer := ReadKey;
    End;
    zfinish := answer in FinishSet;
    SetGraphMode(grmode);
  End;                                                            { showfile }

Begin                                                                 { main }
  init;
  Repeat
    picname := getnextname;
    If picname <> '' Then showfile(picname);
  Until (picname = '') Or zfinish;
End.
