fruit-popper/GDLIB/GDPCX.PAS
2021-07-07 17:10:18 -04:00

357 lines
9.9 KiB
Plaintext

{ GDlib PCX file load/save support
Gered King, 2018 }
{$A+,B-,F-,G+,I-,N+,P-,Q-,R-,S-,T-,V-,X+}
unit GDPCX;
interface
uses GDGfx;
type
PCXResult = (PCXNotFound, PCXIOError, PCXBadFile, PCXOk);
function LoadPCXTo(const filename: string; pal: PPalette; dest: pointer; bytesPerLine: word) : PCXResult;
function LoadPCXToBitmap(const filename: string; pal: PPalette; bmp: PBitmap) : PCXResult;
function LoadPCX(const filename: string; pal: PPalette) : PCXResult;
function SavePCXFrom(const filename: string; pal: PPalette; src: pointer; srcWidth, srcHeight: word) : PCXResult;
function SavePCXFromBitmap(const filename: string; pal: PPalette; const bmp: PBitmap) : PCXResult;
function SavePCX(const filename: string; pal: PPalette) : PCXResult;
implementation
uses Toolbox;
type
PCXHeader = record
Manufacturer : byte;
Version : byte;
Encoding : byte;
Bpp : byte;
X, Y : word;
Width, Height : word;
HorizontalDpi : word;
VerticalDpi : word;
EgaPalette : array[0..47] of byte;
Reserved : byte;
NumColorPlanes : byte;
BytesPerLine : word;
PaletteType : word;
HorizontalSize : word;
VerticalSize : word;
Padding : array[0..53] of byte;
end;
function LoadPCXTo(const filename: string;
pal: PPalette;
dest: pointer;
bytesPerLine: word) : PCXResult;
{ loads a PCX file, storing the loaded pixel data at the pointer given. if
a palette is provided, the palette data from the PCX file will also be
loaded. returns PCXOk if successful. }
var
p : ^byte;
linePtrInc : word;
f : file;
header : PCXHeader;
i, count, x, y : integer;
pcxWidth : integer;
pcxHeight : integer;
data : byte;
label ioError;
begin
Assign(f, filename);
Reset(f, 1);
if IOResult <> 0 then begin
Close(f);
x := IOResult; { clear i/o error flag }
LoadPCXTo := PCXNotFound;
exit;
end;
{ read PCX header }
BlockRead(f, header, SizeOf(PCXHeader));
if IOResult <> 0 then goto ioError;
{ validate header. we only support about 256 color PCX files }
if (header.Manufacturer <> 10)
or (header.Version <> 5)
or (header.Encoding <> 1)
or (header.Bpp <> 8) then begin
Close(f);
LoadPCXTo := PCXBadFile;
exit;
end;
pcxWidth := header.Width;
pcxHeight := header.Height;
p := dest;
linePtrInc := (bytesPerLine - pcxWidth - 1);
for y := 0 to pcxHeight do begin
{ write pixels out per-scanline }
x := 0;
while x < header.BytesPerLine do begin
{ read pixel (or RLE count ...) }
BlockRead(f, data, 1);
if IOResult <> 0 then goto ioError;
if (data and $c0) = $c0 then begin
{ it was an RLE count, actual pixel is the next byte ... }
count := data and $3f;
BlockRead(f, data, 1);
if IOResult <> 0 then goto ioError;
end else begin
count := 1;
end;
{ store this pixel color the specified number of times }
while count > 0 do begin
if x <= pcxWidth then
p^ := data;
inc(p);
inc(x);
dec(count);
end;
end;
inc(p, linePtrInc);
end;
{ read palette, if needed }
if pal <> nil then begin
Seek(f, FileSize(f) - 768);
BlockRead(f, pal^, SizeOf(Palette));
if IOResult <> 0 then goto ioError;
{ convert PCX palette format to VGA RGB format }
for i := 0 to 255 do begin
pal^[i, 0] := pal^[i, 0] shr 2;
pal^[i, 1] := pal^[i, 1] shr 2;
pal^[i, 2] := pal^[i, 2] shr 2;
end;
end;
Close(f);
x := IOResult; { clear i/o error flag (just in case) }
LoadPCXTo := PCXOk;
exit;
ioError:
Close(f);
x := IOResult; { clear i/o error flag }
LoadPCXTo := PCXIOError;
end;
function LoadPCXToBitmap(const filename: string;
pal: PPalette;
bmp: PBitmap) : PCXResult;
{ loads a PCX file onto the given bitmap. the destination bitmap should be
pre-allocated to a size sufficient to hold the PCX file being loaded. if a
palette is provided, the palette data from the PCX file will also be
loaded. returns PCXOk if successful. }
var
dest : pointer;
begin
dest := @bmp^.Pixels;
LoadPCXToBitmap := LoadPCXTo(filename, pal, dest, bmp^.Width);
end;
function LoadPCX(const filename: string;
pal: PPalette) : PCXResult;
{ loads a PCX file onto the currently bound layer. the PCX file being loaded
should not contain an image larger then 320x200. if a palette is provided,
the palette data from the PCX file will also be loaded. returns PCXOk if
successful. }
var
dest : pointer;
begin
dest := ptr(GetBoundLayerSegment, GetBoundLayerOffset);
LoadPCX := LoadPCXTo(filename, pal, dest, SCREEN_WIDTH);
end;
function WritePCXData(var f: file;
const runCount: integer;
pixel: byte) : boolean;
var
data : byte;
begin
WritePCXData := true;
if (runCount > 1) or ((pixel and $c0) = $c0) then begin
data := $c0 or runCount;
BlockWrite(f, data, 1);
if IOResult <> 0 then begin
WritePCXData := false;
exit;
end;
end;
BlockWrite(f, pixel, 1);
if IOResult <> 0 then begin
WritePCXData := false;
exit;
end;
end;
function SavePCXFrom(const filename: string;
pal: PPalette;
src: pointer;
srcWidth, srcHeight: word) : PCXResult;
{ saves the pixel data located at the given pointer (with the given
dimensions) to a PCX file. if a palette is provided, that palette is saved
to the file, otherwise the current VGA palette is saved instead. returns
PCXOk if successful. }
var
p : ^byte;
linePtrInc : word;
f : file;
x, y, runCount, i : integer;
pixel, runPixel : byte;
rgb : array[0..2] of byte;
header : PCXHeader;
srcRight : word;
srcBottom : word;
label ioError;
begin
Assign(f, filename);
Rewrite(f, 1);
if IOResult <> 0 then goto ioError;
srcRight := srcWidth - 1;
srcBottom := srcHeight - 1;
MemFill(@header, 0, SizeOf(PCXHeader));
header.Manufacturer := 10;
header.Version := 5;
header.Encoding := 1;
header.Bpp := 8;
header.X := 0;
header.Y := 0;
header.Width := srcRight;
header.Height := srcBottom;
header.HorizontalDpi := 0;
header.VerticalDpi := 0;
header.NumColorPlanes := 1;
header.BytesPerLine := srcWidth;
header.PaletteType := 1;
header.HorizontalSize := 320;
header.VerticalSize := 200;
BlockWrite(f, header, SizeOf(PCXHeader));
if IOResult <> 0 then goto ioError;
{ write image data }
p := src;
i := 0;
for y := 0 to srcBottom do begin
{ write one scanline at a time. breaking runs that could have continued
across scanlines in the process, as per the pcx standard }
runCount := 0;
runPixel := 0;
for x := 0 to srcRight do begin
pixel := p^;
inc(p);
if runCount = 0 then begin
runCount := 1;
runPixel := pixel;
end else begin
if (pixel <> runPixel) or (runCount >= 63) then begin
if (not WritePCXData(f, runCount, runPixel)) then goto ioError;
runCount := 1;
runPixel := pixel;
end else begin
inc(runCount);
end;
end;
end;
{ end the scanline, writing out whatever run we might have had going }
if (not WritePCXData(f, runCount, runPixel)) then
goto ioError;
end;
pixel := 12;
BlockWrite(f, pixel, 1);
if IOResult <> 0 then goto ioError;
{ write out provided palette, or the current VGA palette }
if pal <> nil then begin
for i := 0 to 255 do begin
rgb[0] := pal^[i, 0] shl 2;
rgb[1] := pal^[i, 1] shl 2;
rgb[2] := pal^[i, 2] shl 2;
BlockWrite(f, rgb, 3);
if IOResult <> 0 then goto ioError;
end;
end else begin
for i := 0 to 255 do begin
GetColor(i, rgb[0], rgb[1], rgb[2]);
rgb[0] := rgb[0] shl 2;
rgb[1] := rgb[1] shl 2;
rgb[2] := rgb[2] shl 2;
BlockWrite(f, rgb, 3);
if IOResult <> 0 then goto ioError;
end;
end;
Close(f);
x := IOResult; { clear i/o error flag (just in case) }
SavePCXFrom := PCXOk;
exit;
ioError:
Close(f);
x := IOResult; { clear i/o error flag }
SavePCXFrom := PCXIOError;
end;
function SavePCXFromBitmap(const filename: string;
pal: PPalette;
const bmp: PBitmap) : PCXResult;
{ saves the specified bitmap to a PCX file. if a palette is provided, that
palette is saved to the file, otherwise the current VGA palette is saved
instead. returns PCXOk if successful. }
var
src : pointer;
begin
src := @bmp^.Pixels;
SavePCXFromBitmap := SavePCXFrom(filename,
pal,
src,
bmp^.Width,
bmp^.Height);
end;
function SavePCX(const filename: string;
pal: PPalette) : PCXResult;
{ saves the currently bound layer to a PCX file. if a palette is provided,
that palette is saved to the file, otherwise the current VGA palette is
saved instead. returns PCXOk if successful. }
var
src : pointer;
begin
src := ptr(GetBoundLayerSegment, GetBoundLayerOffset);
SavePCX := SavePCXFrom(filename,
pal,
src,
SCREEN_WIDTH,
SCREEN_HEIGHT);
end;
end.