unit PCX256; { PCX256.PAS unit version 1.0 Reinout Raymakers 1997 }
{
When I was looking for a screen-export routine in TP 7.0 that writes
the picture in a wide-spread file format such as PCX, BMP or GIF, I could
not find anything that was both understandably and bugfree...
The only working routines contained assembler code, so that i could not
change the routines to fit my purpose. I then decided to do it from scratch
and here's the result (completely assembler-free and still quite fast):
This unit reads and writes 256 color .PCX files. Keep the following things
in mind:
- Files are only written correctly if the computer is in 256 color
mode using a .BGI file (e.g. SVGA256.BGI), because this unit
uses the graph unit to read and write pixels.
The routines also work in 16 color mode, but unless you carefully
define your colors (SetRGBPalette procedure), the colors of
the picture can turn out the wrong way......
- The procedures do not check if files exist, or if the files you
specify are indeed correct PCX files. You have to do that in
the main program yourself. (You can use CheckPCX256 to do that.)
- 'FileName' should always contain the extension of the file.
You are free to use or change this unit in any way you like, but I would
appreciate it, if you mailed me your suggestions / changes / bugs or
anything else you want changed....
P.S. Except for the SetVGAPalette, GetVGAPalette & FileExists routines, all
the code is written by me. The FileExists routine is from the Help
of Turbo Pascal. I don't remember where I got the other two routines,
but hereby I want to thank the authors, because I'm really bad
at low-level memory coding....
Reinout Raymakers
Nolensstraat 12
5344 SK Oss
The Netherlands
reinoutr@sci.kun.nl
RR, 18 May 1997
Projects that I'm still working on: - True color (24 bit) PCX files unit
- Export routines for GIF / BMP formats
}
interface
type Colorvalue = Record
Rvalue,
Gvalue,
Bvalue : byte;
end;
Palette16 = Array [0..15] of Colorvalue;
Palette256 = Array [0..255] of Colorvalue;
PCXType = Object { Header definition of .PCX file }
ID, { Manufacturer, 10 = ZSoft }
Version, { Version, 5 = latest }
Encoding, { Compression, 1 = RLE }
BPP : Byte; { Bits per pixel, 8 for 256 colors }
Window : Record { Original position and size of picture }
Left, { X1 }
Top, { Y1 }
Right, { X2 }
Bottom : Word { Y2 }
End;
HorRes,
VerRes : Word; { Physical properties of picture, in DPI }
Colors : Palette16; { 16 color palette, unused at 256 colors }
Reserved, { Reserved Byte }
NPlanes : Byte; { Number of planes, 1 for 256 colors }
BPL, { Bytes per horizontal line }
Palette : Word; { Greyscale or Color, 1 for Color (2 for Grayscale) }
Filler : Array[1..58] of Byte; { Reserved space }
End;
procedure WritePCX256(FileName : String; X1, Y1, X2, Y2 : Word);
{ FileName is the name of the file you want to write the data to, X1, Y1, X2
and X2 are the coordinates of the window your want to write to disk. }
procedure ReadPCX256(FileName : String; X, Y : Word);
{ FileName is the name of the file you want to read the data from. X and Y
are the coordinates at which position you want the upper left corner of the
picture.}
procedure InfoPCX(FileName : String; var Header : PCXType);
{ FileName is the name of the file you want info about. This procedure does
nothing but give the header of the .PCX file in the variable Header. }
function CheckPCX256(FileName : String; var Error : Byte) : Boolean;
{ FileName is the name of the file of which you want to be sure that it is
a correct 256 color .PCX file. CheckPCX256 returns TRUE if the file is
indeed such a file. If NOT, it returns FALSE.
'Error' contains more detailed information on what the problem is:
0 - file is ok
1 - file does not exist
2 - file is not a PCX file
3 - file is PCX, but not a 256 color file
4 - file is 256 color PCX, but the data compression type is unknown
(you can always try to read it anyway, it might be an error in the
header, because for PCX only RLE is documented!)
}
implementation
uses Dos, MyUnit;
const BufferSize = 4096; { Size of the buffer for reading and writing files }
procedure GetVGAPalette(var Pal : Palette256);
var ColorNo : Byte;
begin
for ColorNo := 0 to 255 do
begin
Port [$3c7] := ColorNo;
Pal[ColorNo].RValue := Port [$3c9];
Pal[ColorNo].GValue := Port [$3c9];
Pal[ColorNo].BValue := Port [$3c9];
end;
end; { GetVGAPalette }
procedure SetVGAPalette(var Pal : Palette256);
var Regs : Registers;
begin
with Regs do
begin
AX:=$1012;
BX:=0;
CX:=256;
ES:=Seg(Pal); DX:=Ofs(Pal);
end;
Intr($10,Regs);
end; { SetVGAPalette }
function FileExists(FileName: String): Boolean;
var
F: File;
begin
{$I-}
Assign(F, FileName);
Reset(F);
Close(F);
{$I+}
FileExists := (IOResult = 0) and (FileName <> '');
end; { FileExists }
procedure WritePCX256;
var Header : PCXType;
PCXFile : File;
Pal256 : Palette256;
procedure OpenFile;
begin
Assign(PCXFile,FileName);
ReWrite(PCXFile,1);
end;
procedure WriteHeader;
begin
with Header do
begin
ID := 10;
Version := 5;
Encoding := 1;
BPP := 8;
with Window do
begin
Left := X1;
Top := Y1;
Right := X2;
Bottom := Y2;
end;
HorRes := 300; { Since a picture that is written from the screen has no }
VerRes := 300; { physical size, 300 DPI is just a random chosen value... }
NPlanes := 1;
BPL := X2-X1+1;
{ If Odd(BPL) then Inc(BPL); } { Officially BPL should always be even, but
after implementation, a lot of programs
could not read the files anymore, and
this solution does not seem to give any
problems..... }
Palette := 1;
end;
BlockWrite(PCXFile,Header,SizeOf(Header));
end;
procedure WriteData; { This procedure writes a RLE compressed picture. }
var X, Y : Word;
Buffer : Array[0..BufferSize] of Byte;
Count : Word;
CurColor,
ByteCnt,
Color : Byte;
begin
For Y := Y1 to Y2 do
begin
Count := 0;
ByteCnt := 0;
For X := X1 to X2 do
begin
Color := GetPixel(X,Y);
If X = X1 then { just remember the first byte }
begin
CurColor := Color;
ByteCnt := 1;
end
else
begin
If (Color <> CurColor) OR (ByteCnt = 63) then { write data if new color or more than 63 the same }
begin
If (ByteCnt <> 1) or (CurColor AND $C0 = $C0) then { write RLE code if necessary }
begin
Buffer[Count] := ByteCnt OR $C0;
Inc(Count);
end;
Buffer[Count] := CurColor; { write color }
Inc(Count);
CurColor := Color;
ByteCnt := 1;
end
else
begin
Inc(ByteCnt);
end;
end;
If X = X2 then { write buffer to file after last byte }
begin
Buffer[Count] := ByteCnt OR $C0;
Inc(Count);
Buffer[Count] := CurColor;
Inc(Count);
BlockWrite(PCXFile,Buffer,Count);
end;
end;
end;
end;
procedure WritePalette;
Var Marker,
Count : Byte;
begin
Marker := 12;
GetVGAPalette(Pal256);
For Count := 0 to 255 do
begin
Pal256[Count].RValue := Pal256[Count].RValue * 4;
Pal256[Count].BValue := Pal256[Count].BValue * 4;
Pal256[Count].GValue := Pal256[Count].GValue * 4;
end;
BlockWrite(PCXFile,Marker,SizeOf(Marker));
Blockwrite(PCXFile,Pal256,SizeOf(Pal256));
end;
procedure CloseFile;
begin
Close(PCXFile);
end;
begin
OpenFile; { Create the new file }
WriteHeader; { Create a correct header and write it to file }
WriteData; { Compress data using RLE and write data to file }
WritePalette; { Get current standard VGA palette, convert it to
a 16.7 Million color palette and write it to file }
CloseFile; { And ready is your PCX! }
end; { WritePCX256 }
procedure ReadPCX256;
var Header : PCXType;
PCXFile : File;
Pal256 : Palette256;
X1, Y1 : Word;
procedure OpenFile;
begin
Assign(PCXFile,FileName);
Reset(PCXFile,1);
end;
procedure ReadHeader;
begin
BlockRead(PCXFile,Header,SizeOf(Header));
X1 := X;
Y1 := Y;
end;
procedure ReadPalette;
var Marker,
Count : Byte;
begin
Seek(PCXFile,FileSize(PCXFile) - 769);
BlockRead(PCXFile,Marker,SizeOf(Marker));
BlockRead(PCXFile,Pal256,SizeOf(Pal256));
For Count := 0 to 255 do
begin
Pal256[Count].RValue := Pal256[Count].RValue DIV 4;
Pal256[Count].BValue := Pal256[Count].BValue DIV 4;
Pal256[Count].GValue := Pal256[Count].GValue DIV 4;
end;
SetVGAPalette(Pal256);
end;
procedure ReadData;
var Buffer : Array[0..BufferSize] of Byte;
Count,
Count2 : Word;
Result : Word;
ByteCount : Byte;
BeginX,
BeginY : Word;
Finish : Boolean;
begin
Seek(PCXFile,SizeOf(Header));
BeginY := Y;
BeginX := X;
Finish := FALSE;
repeat
BlockRead(PCXFile,Buffer,BufferSize,Result);
If Result < BufferSize then Finish := TRUE;
If FilePos(PCXFile) > FileSize(PCXFile) - 769 then
begin
Result := Result - (FilePos(PCXFile) - (FileSize(PCXFile) - 769));
Finish := TRUE;
end;
For Count := 0 to Result - 1 do
begin
If Buffer[Count] AND $C0 = $C0 then { check if byte is a RLE code }
begin
If Count = Result - 1 then
begin
Seek(PCXFile,FilePos(PCXFile)-1); { check if we've got the color for that RLE }
end
else
begin
ByteCount := Buffer[Count] - $C0;
Inc(Count);
For Count2 := 1 to ByteCount do { write the number of pixels as stored in the RLE }
begin
If BeginX - X <= Header.Window.Right - Header.Window.Left { don't run of the screen }
then PutPixel(BeginX,BeginY,Buffer[Count]);
Inc(BeginX);
If BeginX >= (X + Header.BPL) then { do we have to go to the next line? }
begin
Inc(BeginY);
BeginX := X;
end;
end;
end;
end
else
begin
If BeginX - X <= Header.Window.Right - Header.Window.Left { identical to the above, but now we only write one pixel }
then PutPixel(BeginX,BeginY,Buffer[Count]);
Inc(BeginX);
If BeginX >= (X + Header.BPL) then
begin
Inc(BeginY);
BeginX := X;
end;
end;
end;
until Finish;
end;
procedure CloseFile;
begin
Close(PCXFile);
end;
begin
OpenFile; { Open the PCX file }
ReadHeader; { Read the header from the file }
ReadPalette; { Read the 16.7 Million color palette from the file,
convert it to standard VGA and apply it }
ReadData; { Read data from file, decompress and put picture on screen }
CloseFile; { Close the file }
end; { ReadPCX256 }
procedure InfoPCX;
var PCXFile : File;
begin
Assign(PCXFile,FileName);
Reset(PCXFile,1);
BlockRead(PCXFile,Header,SizeOf(Header));
Close(PCXFile);
end; { InfoPCX }
function CheckPCX256;
var Header : PCXType;
PCXFile : File;
Result : Word;
Marker : Byte;
begin
CheckPCX256 := TRUE;
Error := 0;
If not FileExists(FileName) then
begin
CheckPCX256 := FALSE;
Error := 1;
end
else
begin
Assign(PCXFile,FileName);
Reset(PCXFile,1);
BlockRead(PCXFile,Header,SizeOf(Header),Result);
Seek(PCXFile,FileSize(PCXFile) - 769);
BlockRead(PCXFile,Marker,SizeOf(Marker),Result);
Close(PCXFile);
If Header.ID <> 10 then
begin
CheckPCX256 := FALSE;
Error := 2;
end
else
begin
If (Marker <> 12) or (Header.BPP <> 8) or (Header.NPlanes <> 1) then
begin
CheckPCX256 := FALSE;
Error := 3;
end;
If (Error = 0) and (Header.Encoding <> 1) then
begin
CheckPCX256 := FALSE;
Error := 4;
end;
end;
end;
end; { CheckPCX256 }
end.