05

PCX + Plasma

Using palette cycling to create plasma effects was a demo scene standard, and I wanted to do it but couldn't find example code. I had the PCX rendering code from ZSoft, and I knew how to rotate palettes, but my first few attempts on random pictures had terrible results, mostly because images don't tend to neatly line up their colors.

So I found someone else's plasma executable, grabbed some screenshots, and tried to get the effect working. Below is my result, and you can see I didn't quite get it. Flipping all the toggles will get to where I was heading.

the 256 DAC registers, live - this is the only thing changing

PLASMA.PAS

This is the loader plus a 255-entry color history buffer: every frame the table shifts down one and a new color is pushed in at the top, each channel doing a ±1 or ±2 random walk between 0 and 63.

The decoder is not mine

SHOW_PCX.PAS opens “(C) Copyright 1989 ZSoft, Corporation” under a Permission-to-Copy notice asking that the notice be retained. TECHREF.TXT is ZSoft's own PCX Technical Reference Manual, Revision 4, and its introduction advertises the BBS you could call for “the source for a complete Turbo Pascal program to show .PCX files”. That is where SHOW_PCX came from, the oldest file in the archive, at the head of the chain that ends here.

JDPCX.PAS is my edit of it: the five {$...} directive comments survive verbatim and the copyright block does not. PLASMA then reduces it again to single letters: r is the header record, k the ColorMap, z the screen at $A000, b ReadByte, c Read_PCX_Line, e Read_PCX.

PLASMA.PAS

1995-07-26 05:59 · 2,099 bytes · The loader reduced to single letters, plus the walker.

{$R-}{$B-}{$S-}{$I+}uses jmodex,Crt,Dos;type r=record a,b,c,d:byte;
e,f,g,h,i,j:integer;k:array[0..15,0..2]of byte;l,m:byte;n:integer;end;var
jn:string;ja:array[1..255]of record jr,jg,jb:byte;end;
js,jt,ju:record m,d:shortint;end;
o:file;p:array[0..4096]of byte;q:r;s:array[0..255,0..2]of byte;
t:array[0..4000]of byte;u,v,y:integer;a,w:byte;x:Registers;
z:array[0..64000]of byte absolute $A000:$0000;
procedure b;var n:integer;begin if u=4096then begin BlockRead(o,p,4096,n);u:=0
;end;w:=p[u];inc(u);end;procedure c;var n,d:integer;begin d:=q.n;if v<>0 then
FillChar(t[0],v,w);while(v<d)do begin b;if(w and 192)=192then begin n:=w and
63;b;FillChar(t[v],n,w);inc(v,n);end else begin t[v]:=w;inc(v);end;end;v:=v-d;
end;procedure e(n:string);var k,kmax:integer;begin assign(o,n);reset(o,1);
BlockRead(o,q,128);seek(o,FileSize(o)-769);BlockRead(o,a,1);if a<>12then exit;
BlockRead(o,s,768);seek(o,128);v:=0;u:=4096;asm mov ah,0;mov al,19;int 16;end;
for y:=0to 255do begin s[y,0]:=s[y,0]shr 2;s[y,1]:=s[y,1]shr 2;
s[y,2]:=s[y,2]shr 2;end;x.ah:=16;x.al:=18;x.bx:=0;x.cx:=255;x.dx:=ofs(s);
x.es:=seg(s);intr(16,x);kmax:=q.f+199;if q.h<kmax then kmax:=q.h;for k:=q.f
to kmax do begin c;y:=q.g-q.e;if y>320then y:=320;Move(t[0],z[k*320],y);end;
close(o);end;begin ClrScr;
if(ParamCount=0)then begin write('Pcx for plasmation: ');
readln(jn);writeln;end else jn:=ParamStr(1);if(Pos('.',jn)=0)then jn
:=Concat(jn,'.pcx');e(jn);
js.m:=1;jt.m:=1;ju.m:=1;js.d:=1;jt.d:=1;ju.d:=1;
repeat repeat until(port[$3da]and 8)=0;repeat until(port[$3da]and 8)<>0;
for a:=1to 255Do set_dac_register(a,ja[a].jr,ja[a].jg,ja[a].jb);
for a:=1to 254do ja[a]:=ja[a+1];with ja[255]do begin
if((js.d>0)and(jr>=(63-js.m)))or((js.d<0)and(jr<=js.m))then js.d:=-js.d;
if((jt.d>0)and(jg>=(63-jt.m)))or((jt.d<0)and(jg<=jt.m))then jt.d:=-jt.d;
if((ju.d>0)and(jb>=(63-ju.m)))or((ju.d<0)and(jb<=ju.m))then ju.d:=-ju.d;
jr:=ja[254].jr+(js.m*js.d);
jg:=ja[254].jg+(jt.m*jt.d);
jb:=ja[254].jb+(ju.m*ju.d);
js.m:=random(2)+1;jt.m:=random(2)+1;ju.m:=random(2)+1;
end;until keypressed;
readln;TextMode(3);end.
JDPCX.PAS

1995-07-26 03:34 · 12,132 bytes · My edit of ZSoft's SHOW_PCX.

{$R-}    {Range checking off}
{$B-}    {Boolean complete evaluation off}
{$S-}    {Stack checking off}
{$I+}    {I/O checking on}
{$N-}    {No numeric coprocessor}
uses Crt, Dos;
const
   MAX_WIDTH = 4000;    {maximum width (in bytes) of a PCX image }
   COMPRESS_NUM = $C0;  {this is the upper two bits that indicate a count }
   MAX_BLOCK = 4096;
   RED = 0;
   GREEN = 1;
   BLUE = 2;
   CGA4 = $04;          { video modes }
   CGA2 = $06;
   EGA  = $10;
   VGA  = $12;
   MCGA = $13;

type
   str80=string[80];
   file_buffer=array[0..127]of byte;
   block_array=array[0..4096]of byte;
   pal_array=array[0..255,0..2]of byte;
   ega_array=array[0..16]of byte;
   line_array=array[0..MAX_WIDTH]of byte;
   pcx_header=record
        Manufacturer:byte;     { Always 10 for PCX file }
        Version:byte;          { 2 - old PCX - no palette (not used anymore),
                                 3 - no palette,
                                 4 - Microsoft Windows - no palette (only in
                                     old files, new Windows version uses 3),
                                 5 - with palette }
        Encoding:byte;         { 1 is PCX, it is possible that we may add
                                 additional encoding methods in the future }
        Bits_per_pixel:byte;   { Number of bits to represent a pixel
                                 (per plane) - 1, 2, 4, or 8 }
        Xmin:integer;          { Image window dimensions (inclusive) }
        Ymin:integer;          { Xmin, Ymin are usually zero (not always) }
        Xmax:integer;
        Ymax:integer;
        Hdpi:integer;          { Resolution of image (dots per inch) }
        Vdpi:integer;          { Set to scanner resolution - 300 is default }
        ColorMap:array[0..15,0..2] of byte;
                               { RGB palette data (16 colors or less)
                                 256 color palette is appended to end of file }
        Reserved:byte;         { (used to contain video mode)
                                 now it is ignored - just set to zero }
        Nplanes:byte;          { Number of planes }
        Bytes_per_line_per_plane:integer;   { Number of bytes to allocate
                                              for a scanline plane.
                                              MUST be an an EVEN number!
                                              Do NOT calculate from Xmax-Xmin! }
        PaletteInfo:integer;   { 1 = black & white or color image,
                                 2 = grayscale image - ignored in PB4, PB4+
                                 palette must also be set to shades of gray! }
        HscreenSize:integer;   { added for PC Paintbrush IV Plus ver 1.0,  }
        VscreenSize:integer;   { PC Paintbrush IV ver 1.02 (and later)     }
                               { I know it is tempting to use these fields
                                 to determine what video mode should be used
                                 to display the image - but it is NOT
                                 recommended since the fields will probably
                                 just contain garbage. It is better to have
                                 the user install for the graphics mode he
                                 wants to use... }
        Filler:array[74..127]of byte;  { Just set to zeros }
        end;
var
   Name:str80;                        { Name of PCX file to load }
   ImageName:str80;                   { Name of PCX file - used by ReadError }
   BlockFile:file;                    { file for reading block data }
   BlockData:block_array;             { 4k data buffer }

   Header:pcx_header;                 { PCX file header }
   Palette256:pal_array;              { place to put 256 color palette }
   PaletteEGA:ega_array;              { place to put 17 EGA palette values }
   PCXline:line_array;                { place to put uncompressed data }

   Ymax:integer;                      { maximum Y value on screen }
   NextByte:integer;                  { index into file buffer in ReadByte }
   Index:integer;                     { PCXline index - where to put Data }
   Data:byte;                         { PCX compressed data byte }

   PictureMode:integer;               { Graphics mode number }
   Reg:Registers;                     { Register set - used for int 10 calls }

procedure VideoMode(n:byte);begin asm mov ah,0;mov al,n;int 16;end;end;

procedure setpalette;var i:integer;begin for i:=0to 255do begin
Palette256[i,0]:=Palette256[i,0]shr 2;Palette256[i,1]:=Palette256[i,1]shr 2;
Palette256[i,2]:=Palette256[i,2]shr 2;end;reg.ah:=16;Reg.al:=18;Reg.bx:=0;
Reg.cx:=255;Reg.dx:=ofs(Palette256);Reg.es:=seg(Palette256);intr(16,Reg);end;


{ =========================== ShowCGA =============================== }

procedure ShowCGA (Y: integer);

{ Put a line of CGA data on the screen }
{ In Turbo Pascal, a '$' means the number is hexadeximal. }

var
   i, j, l, m, t: integer;
   Yoffset: integer;
   CGAScreen: array [0..32000] of byte absolute $B800:$0000;

begin
i := 8 div Header.Bits_per_pixel;        { i is pixels per byte }

if (i = 8) then                          { 1 bit per pixel }
   j := 7
else                                     { 2 bits per pixel }
   j := 3;

t := (Header.Xmax - Header.Xmin + 1);    { width in pixels }
m := t and j;                            { left over bits }

l := (t + j) div i;                      { compute number of bytes to display }
if l > 80 then
   begin
   l := 80;                              { don't overrun screen width }
   m := 0;
   end;

if (m <> 0) then                         { we need to mask unseen pixels }
   begin
   m := $FF shl (8 - (m * Header.Bits_per_pixel));   { m = mask }
   t := l - 1;
   PCXline [t] := PCXline [t] and m;     { mask off unseen pixels }
   end;

Yoffset := 8192 * (Y and 1);
Move (PCXline [0], CGAScreen [((Y shr 1) * 80) + Yoffset], l);

end;   { ShowCGA }


{ =========================== ShowEGA =============================== }

procedure ShowEGA (Y: integer);

{ Put a line of EGA (or VGA) data on the screen }
{ In Turbo Pascal, a '$' means the number is hexadeximal. }

var
   i, j, l, m, t: integer;
   EGAplane: integer;
   EGAscreen: array [0..32000] of byte absolute $A000:$0000;

begin
EGAplane := $0100;                       { the first plane to update }
PortW [$3CE] := $0005;                   { use write mode 0 }

{ PortW [$3CE] := $0005;      does port I/O by words. It is the same as:

  Out 03CEh,05h
  Out 03CFh,00h
}

t := (Header.Xmax - Header.Xmin + 1);    { width in pixels }
m := t and 7;                            { left over bits }

l := (t + 7) shr 3;                      { compute number of bytes to display }
if (l >= 80) then
   begin
   l := 80;                              { don't overrun screen width }
   m := 0;
   end;

if (m <> 0) then
   m := $FF shl (8 - m)                  { m = mask for unseen pixels }
else
   m := $FF;

for i := 0 to Header.Nplanes-1 do
   begin
   j := i * Header.Bytes_per_line_per_plane;
   t := j + l - 1;
   PCXline [t] := PCXline [t] and m;           { mask off unseen pixels }

   PortW [$3C4] := EGAplane + 2;               { set plane number }
   Move (PCXline [j], EGAscreen [Y * 80], l);
   EGAplane := EGAplane shl 1;
   end;

PortW [$3C4] := $0F02;                         { default plane mask }
end;   { ShowEGA }


{ =========================== ShowMCGA =============================== }

procedure ShowMCGA (Y: integer);

{ Put a line of MCGA data on the screen }
{ In Turbo Pascal, a '$' means the number is hexadeximal. }

var
   l: integer;
   MCGAscreen: array [0..64000] of byte absolute $A000:$0000;

begin
l := Header.XMax - Header.Xmin;            { compute number of bytes to display }
if l > 320 then
   l := 320;                               { don't overrun screen width }

Move (PCXline [0], MCGAScreen [Y * 320], l);

end;   { ShowMCGA }


{ =========================== Read256palette =============================== }

procedure Read256palette;

{ Read in a 256 color palette at end of PCX file }

var
   i: integer;
   b: byte;

begin
seek (BlockFile, FileSize (BlockFile) - 769);
BlockRead (BlockFile, b, 1);           { read indicator byte }


if b <> 12 then                        { no palette here... }
   exit;

BlockRead (BlockFile, Palette256, 3*256);

seek (BlockFile, 128);                 { go back to start of PCX data }

end;  { Read256palette }


{ =========================== ReadHeader =============================== }
procedure ReadHeader;begin{$I-}BlockRead(BlockFile,Header,128);Ymax:=199;
PictureMode:=19;{Read256palette;}Index:=0;NextByte:=MAX_BLOCK;end;

{ =========================== ReadByte =============================== }

procedure ReadByte;

{ read a single byte of data - use BlockRead because it is FAST! }

var
   NumBlocksRead: integer;

begin
if NextByte = MAX_BLOCK then
   begin
   BlockRead (BlockFile, BlockData, MAX_BLOCK, NumBlocksRead);
   NextByte := 0;
   end;

data := BlockData [NextByte];
inc (NextByte);                         { NextByte++; }
end;  { ReadByte }


{ =========================== Read_PCX_Line =============================== }

procedure Read_PCX_Line;

{ Read a line from a PC Paintbrush PCX file }

var
   count: integer;
   bytes_per_line: integer;

begin
{$I-}

bytes_per_line := Header.Bytes_per_line_per_plane * Header.Nplanes;

                          { bring in any data that wrapped from previous line }
                          { usually none  -  this is just to be safe          }
if Index <> 0 then
   FillChar (PCXline [0], Index, data);    { fills a contiguous block of data }

while (Index < bytes_per_line) do          { read 1 line of data (all planes) }
   begin
   ReadByte;

   if (data and $C0) = compress_num then
      begin
      count := data and $3F;
      ReadByte;
      FillChar (PCXline [Index], count, data);  { fills a contiguous block }
      inc (Index, count);                       { Index += count; }
      end
   else
      begin
      PCXline [Index] := data;
      inc (Index);                              { Index++; }
      end;
   end;


Index := Index - bytes_per_line;

{$I+}
end;  { Read_PCX_Line }


{ =========================== Read_PCX =============================== }
procedure Read_PCX (name: str80);
var k, kmax: integer;
begin
{$I-}
assign (BlockFile, name);
reset (BlockFile, 1);
ReadHeader;                           
VideoMode (PictureMode);              
if Header.Version = 5 then
   SetPalette;                         { set the screen palette, if available }

{ >>>>> Note: You should compute the height of the image as follows. <<<<<
  >>>>> Do NOT just read until End-Of-File!                          <<<<< }

kmax := Header.Ymin + Ymax;
if Header.Ymax < kmax then        { don't show more than the screen can display }
   kmax := Header.ymax;

if (PictureMode = EGA) or (PictureMode = VGA) then
   begin
   for k := Header.Ymin to kmax do          { each loop is separate for speed }
      begin
      Read_PCX_Line;
      ShowEGA (k);
      end;
   end
else if (PictureMode = MCGA) then
   begin
   for k := Header.Ymin to kmax do
      begin
      Read_PCX_Line;
      ShowMCGA (k);
      end;
   end
else                                         { it's a CGA picture }
   begin
   for k := Header.Ymin to kmax do
      begin
      Read_PCX_Line;
      ShowCGA (k);
      end;
    end;

close (BlockFile);
{$I+}
end;  { Read_PCX }


{ *************************** MAIN ******************************* }
begin ClrScr;
{if(ParamCount=0)then begin write('Name of pcx to display: ');
readln(name);writeln;end else Name:=ParamStr(1);if(Pos('.',Name)=0)then Name
:=Concat(Name,'.pcx');read_pcx (Name);}
read_pcx('\stuff\pcxedit\a1.pcx');
readln;TextMode(3);end.