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.
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.
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.
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.
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.