Компонент для работы с PCX файлами
Компонент для работы с PCX файлами
Fully supports reading and writing of: 1, 8 and 24 bit PCX images.
///////////////////////////////////////////////////////////////////////
////
// TPCXImage //
// ========= //
// //
// Completed: The 10th of August 2001 //
// Author: M. de Haan //
// Email: M.deHaan@inn.nl //
// Tested: under W95 SP1, NT4 SP6, WIN2000 //
// Version: 1.0 //
//-------------------------------------------------------------------//
// Update: The 14th of August 2001 to version 1.1. //
// Reason: Added version check. //
// Added comment info on version. //
// Changed PCX header ID check. //
//-------------------------------------------------------------------//
// Update: The 19th of August 2001 to version 2.0. //
// Reason: Warning from Delphi about using abstract methods, //
// caused by not implementing ALL TGraphic methods. //
// (Thanks goes to R.P. Sterkenburg for his diagnostic.) //
// Added: SaveToClipboardFormat, LoadFromClipboardFormat, //
// GetEmpty. //
//-------------------------------------------------------------------//
// Update: The 13th of October 2001 to version 2.1. //
// Reason: strange errors, read errors, EExternalException, IDE //
// hanging, Delphi hanging, Debugger hanging, windows //
// hanging, keyboard locked, and so on. //
// Changed: Assign procedure. //
//-------------------------------------------------------------------//
// Update: The 5th of April 2002 to version 2.2. //
// Changed: RLE compressor routine. //
// Reason: Incompatibility problems with other programs caused //
// by the RLE compressor. //
// Other programs encode: $C0 as: $C1 $C0. //
// ($C0 means: repeat the following byte 0 times //
// $C1 means: repeat the following byte 1 time.) //
// Changed: File read routine. //
// Reason: Now detects unsupported PCX data formats. //
// Added: 'Unsupported data format' in exception handler. //
// Added: 1 bit PCX support in reading. //
// Added: Procedure Convert1BitPCXDataToImage. //
// Renamed: Procedure ConvertPCXDataToImage to //
// Convert24BitPCXDataToImage. //
//-------------------------------------------------------------------//
// Update: The 14th of April 2002 to version 2.3. //
// Now capable of reading and writing 1 and 24 bit PCX //
// images. //
// Added: 1 bit PCX support in writing. //
// Added: Procedure ConvertImageTo1bitPCXData. //
// Changed: Procedure CreatePCXHeader. //
// Changed: Procedure TPCXImage.SaveToFile. //
//-------------------------------------------------------------------//
// Update: The 19th of April 2002 to version 2.4. //
// Now capable of reading and writing: 1, 8 and 24 bit //
// PCX images. //
// Added: 8 bit PCX support in reading and writing. //
// Renamed: Procedure ConvertImageTo1And8bitPCXData. //
// Renamed: Procedure Convert1And8bitPCXDataToImage. //
// Changed: Procedure fSetPalette, fGetPalette. //
//-------------------------------------------------------------------//
// Update: The 7th of May 2002 to version 2.5. //
// Reason: The palette of 8-bit PCX images couldn't be read in //
// the calling program. //
// Changed: Procedures Assign, AssignTo, fSetPalette, fGetPalette. //
// Tested: All formats were tested with the following programs: //
// - import in Word 97, //
// * (Word ignores the palette of 1 bit PCX images!) //
// - import and export in MigroGrafX. //
// * (MicroGrafX also ignores the palette of 1 bit PCX //
// images.) //
// No problems were detected. //
// //
//===================================================================//
// //
// The PCX image file format is copyrighted by: //
// ZSoft, PC Paintbrush, PC Paintbrush plus //
// Trademarks: N/A //
// Royalty fees: NONE //
// //
//===================================================================//
// //
// The author can not be held responsable for using this software //
// in anyway. //
// //
// The features and restrictions of this component are: //
// ---------------------------------------------------- //
// //
// The reading and writing (import / export) of files / images: //
// - PCX version 5 definition, PC Paintbrush 3 and higher, //
// - RLE-compressed, //
// - 1 and 8 bit PCX images WITH palette and //
// - 24 bit PCX images without palette, //
// are supported by this component. //
// //
// Known issues //
// ------------ //
// //
// 1) GetEmpty is NOT tested. //
// //
// 2) SaveToClipboardFormat is NOT tested. //
// //
// 3) LoadFromClipboardFormat is NOT tested. //
// //
// 4) 4 bit PCX images (with palette) are NOT (yet) implemented. //
// (I have no 4-bit PCX images to test it on...) //
// //
///////////////////////////////////////////////////////////////////////
unit
PCXImage;
interface
uses
Windows,
SysUtils,
Classes,
Graphics;
const
WIDTH_OUT_OF_RANGE = 'Illegal width entry in PCX file header';
HEIGHT_OUT_OF_RANGE = 'Illegal height entry in PCX file header';
FILE_FORMAT_ERROR = 'Invalid file format';
VERSION_ERROR = 'Only PC Paintbrush (plus) V3.0 and ' +
'higher are supported';
FORMAT_ERROR = 'Illegal identification byte in PCX file' +
' header';
PALETTE_ERROR = 'Invalid palette signature found';
ASSIGN_ERROR = 'Can only Assign a TBitmap or a TPicture';
ASSIGNTO_ERROR = 'Can only AssignTo a TBitmap';
PCXIMAGE_EMPTY = 'The PCX image is empty';
BITMAP_EMPTY = 'The bitmap is empty';
INPUT_FILE_TOO_LARGE = 'The input file is too large to be read';
IMAGE_WIDTH_TOO_LARGE = 'Width of PCX image is too large to handle';
// added 19/08/2001
CLIPBOARD_LOAD_ERROR = 'Loading from clipboard failed';
// added 19/08/2001
CLIPBOARD_SAVE_ERROR = 'Saving to clipboard failed';
// added 14/10/2001
PCX_WIDTH_ERROR = 'Unexpected line length in PCX data';
PCX_HEIGHT_ERROR = 'More PCX data found than expected';
PCXIMAGE_TOO_LARGE = 'PCX image is too large';
// added 5/4/2002
ERROR_UNSUPPORTED = 'Unsupported PCX format';
const
sPCXImageFile = 'PCX V3.0+ image';
// added 19/08/2001
var
CF_PCX: WORD;
///////////////////////////////////////////////////////////////////////
// //
// PCXHeader //
// //
///////////////////////////////////////////////////////////////////////
type
QWORD = Cardinal; // Seems more logical to me...
type
fColorEntry = packed record
ceRed: BYTE;
ceGreen: BYTE;
ceBlue: BYTE;
end; // of packed record fColorEntry
type
TPCXImageHeader = packed record
fID: BYTE;
fVersion: BYTE;
fCompressed: BYTE;
fBitsPerPixel: BYTE;
fWindow: packed record
wLeft,
wTop,
wRight,
wBottom: WORD;
end; // of packed record fWindow
fHorzResolution: WORD;
fVertResolution: WORD;
fColorMap: array[0..15] of fColorEntry;
fReserved: BYTE;
fPlanes: BYTE;
fBytesPerLine: WORD;
fPaletteInfo: WORD;
fFiller: array[0..57] of BYTE;
end; // of packed record TPCXImageHeader
///////////////////////////////////////////////////////////////////////
// //
// PCXData //
// //
///////////////////////////////////////////////////////////////////////
type
TPCXData = object
fData: array of BYTE;
end; // of Type TPCXData
///////////////////////////////////////////////////////////////////////
// //
// ScanLine //
// //
///////////////////////////////////////////////////////////////////////
const
fMaxScanLineLength = $FFF; // Max image width: 4096 pixels
type
mByteArray = array[0..fMaxScanLineLength] of BYTE;
pmByteArray = ^mByteArray;
// The "standard" pByteArray from Delphi allocates 32768 bytes,
// which is a little bit overdone here, I think...
const
fMaxImageWidth = $FFF; // Max image width: 4096 pixels
type
xByteArray = array[0..fMaxImageWidth] of BYTE;
///////////////////////////////////////////////////////////////////////
// //
// PCXPalette //
// //
///////////////////////////////////////////////////////////////////////
type
TPCXPalette = packed record
fSignature: BYTE;
fPalette: array[0..255] of fColorEntry;
end; // of packed record TPCXPalette
///////////////////////////////////////////////////////////////////////
// //
// Classes //
// //
///////////////////////////////////////////////////////////////////////
type
TPCXImage = class;
TPCXFile = class;
///////////////////////////////////////////////////////////////////////
// //
// PCXFile //
// //
// File handler //
// //
///////////////////////////////////////////////////////////////////////
TPCXFile = class(TPersistent)
private
fHeight: Integer;
fWidth: Integer;
fPCXHeader: TPCXImageHeader;
fPCXData: TPCXData;
fPCXPalette: TPCXPalette;
fColorDepth: QWORD;
fPixelFormat: BYTE; // added 5/4/2002
fCurrentPos: QWORD;
fHasPalette: Boolean; // added 7/5/2002
protected
// Protected declarations
public
// Public declarations
constructor Create;
destructor Destroy; override;
procedure LoadFromFile(const Filename: string);
procedure LoadFromStream(Stream: TStream);
procedure SaveToFile(const Filename: string);
procedure SaveToStream(Stream: TStream);
published
// Published declarations
// The publishing is done in the TPCXImage section
end;
///////////////////////////////////////////////////////////////////////
// //
// TPCXImage //
// //
// Image handler //
// //
///////////////////////////////////////////////////////////////////////
TPCXImage = class(TGraphic)
private
// Private declarations
fBitmap: TBitmap;
fPCXFile: TPCXFile;
fRLine: xByteArray;
fGLine: xByteArray;
fBLine: xByteArray;
fP: pmByteArray;
fhPAL: HPALETTE;
procedure fConvert24BitPCXDataToImage;
procedure fConvert1And8BitPCXDataToImage;
procedure fConvertImageTo24BitPCXData;
procedure fConvertImageTo1And8BitPCXData(ImageWidthInBytes:
QWORD);
procedure fFillDataLines(const fLine: array of BYTE);
procedure fCreatePCXHeader(const byBitsPerPixel: BYTE;
const byPlanes: BYTE; const wBytesPerLine: DWORD);
procedure fSetPalette(const wNumColors: WORD);
procedure fGetPalette(const wNumColors: WORD);
function fGetPixelFormat: TPixelFormat; // Added 07/05/2002
function fGetBitmap: TBitmap; // Added 07/05/2002
protected
// Protected declarations
procedure Draw(ACanvas: TCanvas; const Rect: TRect); override;
function GetHeight: Integer; override;
function GetWidth: Integer; override;
procedure SetHeight(Value: Integer); override;
procedure SetWidth(Value: Integer); override;
function GetEmpty: Boolean; override;
public
// Public declarations
constructor Create; override;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
procedure AssignTo(Dest: TPersistent); override;
procedure LoadFromFile(const Filename: string); override;
procedure LoadFromStream(Stream: TStream); override;
procedure SaveToFile(const Filename: string); override;
procedure SaveToStream(Stream: TStream); override;
procedure LoadFromClipboardFormat(AFormat: WORD;
AData: THandle; APalette: HPALETTE); override;
procedure SaveToClipboardFormat(var AFormat: WORD;
var AData: THandle; var APalette: HPALETTE); override;
published
// Published declarations
property Height: Integer
read GetHeight write SetHeight;
property Width: Integer
read GetWidth write SetWidth;
property PixelFormat: TPixelFormat
read fGetPixelFormat;
property Bitmap: TBitmap
read fGetBitmap; // Added 7/5/2002
end;
implementation
///////////////////////////////////////////////////////////////////////
// //
// TPCXImage //
// //
// Image handler //
// //
///////////////////////////////////////////////////////////////////////
constructor TPCXImage.Create;
begin
inherited Create;
// Init HPALETTE
fhPAL := 0;
// Create a private bitmap to hold the image
if not Assigned(fBitmap) then
fBitmap := TBitmap.Create;
// Create the PCXFile
if not Assigned(fPCXFile) then
fPCXFile := TPCXFile.Create;
end;
//---------------------------------------------------------------------
destructor TPCXImage.Destroy;
begin
// Reversed order of create
// Free fPCXFile
fPCXFile.Free;
// Free private bitmap
fBitmap.Free;
// Delete palette
if fhPAL <> 0 then
DeleteObject(fhPAL);
// Distroy all the other things
inherited Destroy;
end;
//---------------------------------------------------------------------
procedure TPCXImage.SetHeight(Value: Integer);
begin
if Value >= 0 then
fBitmap.Height := Value;
end;
//---------------------------------------------------------------------
procedure TPCXImage.SetWidth(Value: Integer);
begin
if Value >= 0 then
fBitmap.Width := Value;
end;
//---------------------------------------------------------------------
function TPCXImage.GetHeight: Integer;
begin
Result := fPCXFile.fHeight;
end;
//---------------------------------------------------------------------
function TPCXImage.GetWidth: Integer;
begin
Result := fPCXFile.fWidth;
end;
//---------------------------------------------------------------------
function TPCXImage.fGetBitmap: TBitmap;
begin
Result := fBitmap;
end;
//-------------------------------------------------------------------//
// The credits for this procedure go to his work of TGIFImage by //
// Reinier P. Sterkenburg //
// Added 19/08/2001 //
//-------------------------------------------------------------------//
// NOT TESTED!
procedure TPCXImage.LoadFromClipboardFormat(AFormat: WORD;
ADAta: THandle; APalette: HPALETTE);
var
Size: QWORD;
Buf: Pointer;
Stream: TMemoryStream;
BMP: TBitmap;
begin
if (AData = 0) then
AData := GetClipBoardData(AFormat);
if (AData <> 0) and (AFormat = CF_PCX) then
begin
Size := GlobalSize(AData);
Buf := GlobalLock(AData);
try
Stream := TMemoryStream.Create;
try
Stream.SetSize(Size);
Move(Buf^, Stream.Memory^, Size);
Self.LoadFromStream(Stream);
finally
Stream.Free;
end;
finally
GlobalUnlock(AData);
end;
end
else if (AData <> 0) and (AFormat = CF_BITMAP) then
begin
BMP := TBitmap.Create;
try
BMP.LoadFromClipboardFormat(AFormat, AData, APalette);
Self.Assign(BMP);
finally
BMP.Free;
end;
end
else
raise Exception.Create(CLIPBOARD_LOAD_ERROR);
end;
//-------------------------------------------------------------------//
// The credits for this procedure go to his work of TGIFImage by //
// Reinier P. Sterkenburg //
// Added 19/08/2001 //
//-------------------------------------------------------------------//
// NOT TESTED!
procedure TPCXImage.SaveToClipboardFormat(var AFormat: WORD;
var AData: THandle; var APalette: HPALETTE);
var
Stream: TMemoryStream;
Data: THandle;
Buf: Pointer;
begin
if Empty then
Exit;
// First store the bitmap to the clipboard
fBitmap.SaveToClipboardFormat(AFormat, AData, APalette);
// Then try to save the PCX
Stream := TMemoryStream.Create;
try
SaveToStream(Stream);
Stream.Position := 0;
Data := GlobalAlloc(HeapAllocFlags, Stream.Size);
try
if Data <> 0 then
begin
Buf := GlobalLock(Data);
try
Move(Stream.Memory^, Buf^, Stream.Size);
finally
GlobalUnlock(Data);
end;
if SetClipBoardData(CF_PCX, Data) = 0 then
raise Exception.Create(CLIPBOARD_SAVE_ERROR);
end;
except
GlobalFree(Data);
raise;
end;
finally
Stream.Free;
end;
end;
//-------------------------------------------------------------------//
// NOT TESTED!
function TPCXImage.GetEmpty: Boolean; // Added 19/08/2002
begin
if Assigned(fBitmap) then
Result := fBitmap.Empty
else
Result := (fPCXFile.fHeight = 0) or (fPCXFile.fWidth = 0);
end;
//---------------------------------------------------------------------
procedure TPCXImage.SaveToFile(const Filename: string);
var
fPCX: TFileStream;
W, WW: QWORD;
begin
if (fBitmap.Width = 0) or (fBitmap.Height = 0) then
raise Exception.Create(BITMAP_EMPTY);
W := fBitmap.Width;
WW := W div 8;
if (W mod 8) > 0 then
Inc(WW);
case fBitmap.PixelFormat of
pf1bit:
begin
// Fully supported by PCX and by this component
fCreatePCXHeader(1, 1, WW);
fConvertImageTo1And8BitPCXData(WW);
fGetPalette(2);
end;
pf4bit:
begin
// I don't have 4-bit PCX images to test with
// It will be treated as a 24 bit image
fCreatePCXHeader(8, 3, W);
fConvertImageTo24BitPCXData;
end;
pf8bit:
begin
// Fully supported by PCX and by this component
fCreatePCXHeader(8, 1, W);
fConvertImageTo1And8BitPCXData(W);
fGetPalette(256);
end;
pf15bit:
begin
// Is this supported in PCX?
// It will be treated as a 24 bit image
fCreatePCXHeader(8, 3, W);
fConvertImageTo24BitPCXData;
end;
pf16bit:
begin
// Is this supported in PCX?
// It will be treated as a 24 bit image
fCreatePCXHeader(8, 3, W);
fConvertImageTo24BitPCXData;
end;
pf24bit:
begin
// Fully supported by PCX and by this component
fCreatePCXHeader(8, 3, W);
fConvertImageTo24BitPCXData;
end;
pf32bit:
begin
// Not supported by PCX
fCreatePCXHeader(8, 3, W);
fConvertImageTo24BitPCXData;
end;
else
begin
fCreatePCXHeader(8, 3, W);
fConvertImageTo24BitPCXData;
end; // of else
end; // of Case
fPCX := TFileStream.Create(Filename, fmCreate);
try
fPCX.Position := 0;
SaveToStream(fPCX);
finally
fPCX.Free;
end; // of finally
SetLength(fPCXFile.fPCXData.fData, 0);
end; // of Procedure SaveToFile
//-------------------------------------------------------------------//
procedure TPCXImage.AssignTo(Dest: TPersistent);
var
bAssignToError: Boolean;
begin
bAssignToError := True;
if Dest is TBitmap then
begin
// The old AssignTo procedure was like this.
// But then the palette was couldn't be accessed in the calling
// program for some reason.
// --------------------------
// (Dest as TBitmap).Assign(fBitmap);
// If fBitmap.Palette <> 0 then
// (Dest as TBitmap).Palette := CopyPalette(fBitmap.Palette);
// --------------------------
// Do the assigning
(Dest as TBitmap).Assign(fBitmap);
if fPCXFile.fHasPalette then
(Dest as TBitmap).Palette := CopyPalette(fhPAL);
// Now the calling program can access the palette
// (if it has one)!
bAssignToError := False;
end;
if Dest is TPicture then
begin
(Dest as TPicture).Graphic.Assign(fBitmap);
bAssignToError := False;
end;
if bAssignToError then
raise Exception.Create(ASSIGNTO_ERROR);
// You can write other assignments here, if you want...
end;
//-------------------------------------------------------------------//
procedure TPCXImage.Assign(Source: TPersistent);
var
iX, iY: DWORD;
bAssignError: Boolean;
begin
bAssignError := True;
if (Source is TBitmap) then
begin
fBitmap.Assign(Source as TBitmap);
if (Source as TBitmap).Palette <> 0 then
begin
fhPAL := CopyPalette((Source as TBitmap).Palette);
fBitmap.Palette := fhPAL;
end;
bAssignError := False;
end;
if (Source is TPicture) then
begin
iX := (Source as TPicture).Width;
iY := (Source as TPicture).Height;
fBitmap.Width := iX;
fBitmap.Height := iY;
fBitmap.Canvas.Draw(0, 0, (Source as TPicture).Graphic);
bAssignError := False;
end;
// You can write other assignments here, if you want...
if bAssignError then
raise Exception.Create(ASSIGN_ERROR);
end;
//---------------------------------------------------------------------
procedure TPCXImage.Draw(ACanvas: TCanvas; const Rect: TRect);
begin
// Faster
// ACanvas.Draw(0,0,fBitmap);
// Slower
ACanvas.StretchDraw(Rect, fBitmap);
end;
//---------------------------------------------------------------------
procedure TPCXImage.LoadFromFile(const Filename: string);
begin
fPCXFile.LoadFromFile(Filename);
// added 5/4/2002
case fPCXFile.fPixelFormat of
1: fConvert1And8BitPCXDataToImage;
8: fConvert1And8BitPCXDataToImage;
24: fConvert24BitPCXDataToImage;
end;
end;
//---------------------------------------------------------------------
procedure TPCXImage.SaveToStream(Stream: TStream);
begin
fPCXFile.SaveToStream(Stream);
end;
//---------------------------------------------------------------------
procedure TPCXImage.LoadFromStream(Stream: TStream);
begin
fPCXFile.LoadFromStream(Stream);
end;
///////////////////////////////////////////////////////////////////////
// //
// Called by RLE compressor //
// //
///////////////////////////////////////////////////////////////////////
procedure TPCXImage.fFillDataLines(const fLine: array of BYTE);
var
By: BYTE;
Cnt: WORD;
I: QWORD;
W: QWORD;
begin
I := 0;
By := fLine[0];
Cnt := $C1;
W := fBitmap.Width;
repeat
Inc(I);
if By = fLine[I] then
begin
Inc(Cnt);
if Cnt = $100 then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] :=
BYTE(Pred(Cnt));
Inc(fPCXFile.fCurrentPos);
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := By;
Inc(fPCXFile.fCurrentPos);
Cnt := $C1;
By := fLine[I];
end;
end;
if (By <> fLine[I]) then
begin
if (Cnt = $C1) then
begin
// If (By < $C1) then
if (By < $C0) then // changed 5/4/2002
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := By;
Inc(fPCXFile.fCurrentPos);
end
else
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := BYTE(Cnt);
Inc(fPCXFile.fCurrentPos);
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := By;
Inc(fPCXFile.fCurrentPos);
end;
end
else
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := BYTE(Cnt);
Inc(fPCXFile.fCurrentPos);
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := By;
Inc(fPCXFile.fCurrentPos);
end;
Cnt := $C1;
By := fLine[I];
end;
until I = W - 1;
// Write the last byte(s)
if (Cnt > $C1) then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := BYTE(Cnt);
Inc(fPCXFile.fCurrentPos);
end;
if (Cnt = $C1) and (By > $C0) then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := BYTE(Cnt);
Inc(fPCXFile.fCurrentPos);
end;
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := By;
Inc(fPCXFile.fCurrentPos);
end;
//-------------------------------------------------------------------//
// RLE Compression algorithm //
//-------------------------------------------------------------------//
procedure TPCXImage.fConvertImageTo24BitPCXData; // Renamed 5/4/2002
var
H, W: QWORD;
X, Y: QWORD;
I: QWORD;
begin
H := fBitmap.Height;
W := fBitmap.Width;
fPCXFile.fCurrentPos := 0;
SetLength(fPCXFile.fPCXData.fData, 6 * H * W); // To be sure...
fBitmap.PixelFormat := pf24bit; // Always do this if you're using
// ScanLine!
for Y := 0 to H - 1 do
begin
fP := fBitmap.ScanLine[Y];
I := 0;
for X := 0 to W - 1 do
begin
fRLine[X] := fP[I];
Inc(I); // Extract a red line
fGLine[X] := fP[I];
Inc(I); // Extract a green line
fBLine[X] := fP[I];
Inc(I); // Extract a blue line
end;
fFillDataLines(fBLine); // Compress the blue line
fFillDataLines(fGLine); // Compress the green line
fFillDataLines(fRLine); // Compress the red line
end;
// Correct the length of fPCXData.fData
SetLength(fPCXFile.fPCXData.fData, fPCXFile.fCurrentPos);
end;
//-------------------------------------------------------------------//
procedure TPCXImage.fConvertImageTo1And8BitPCXData(ImageWidthInBytes:
QWORD);
var
H, W, X, Y: QWORD;
oldByte, newByte: BYTE;
Cnt: BYTE;
begin
H := fBitmap.Height;
W := ImageWidthInBytes;
fPCXFile.fCurrentPos := 0;
SetLength(fPCXFile.fPCXData.fData, 2 * H * W); // To be sure...
oldByte := 0; // Otherwise the compiler issues a warning about
// oldByte not being initialized...
Cnt := $C1;
for Y := 0 to H - 1 do
begin
fP := fBitmap.ScanLine[Y];
for X := 0 to W - 1 do
begin
newByte := fP[X];
if X > 0 then
begin
if (Cnt = $FF) then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := Cnt;
Inc(fPCXFile.fCurrentPos);
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := oldByte;
Inc(fPCXFile.fCurrentPos);
Cnt := $C1;
end
else if newByte = oldByte then
Inc(Cnt);
if newByte <> oldByte then
begin
if (Cnt > $C1) or (oldByte >= $C0) then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := Cnt;
Inc(fPCXFile.fCurrentPos);
Cnt := $C1;
end;
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := oldByte;
Inc(fPCXFile.fCurrentPos);
end;
end;
oldByte := newByte;
end;
// Write last byte of line
if (Cnt > $C1) or (oldByte >= $C0) then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := Cnt;
Inc(fPCXFile.fCurrentPos);
Cnt := $C1;
end;
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := oldByte;
Inc(fPCXFile.fCurrentPos);
end;
// Write last byte of image
if (Cnt > $C1) or (oldByte >= $C0) then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := Cnt;
Inc(fPCXFile.fCurrentPos);
// Cnt := 1;
end;
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := oldByte;
Inc(fPCXFile.fCurrentPos);
// Correct the length of fPCXData.fData
SetLength(fPCXFile.fPCXData.fData, fPCXFile.fCurrentPos);
end;
//-------------------------------------------------------------------//
// RLE Decompression algorithm //
//-------------------------------------------------------------------//
procedure TPCXImage.fConvert24BitPCXDataToImage; // Renamed 5/4/2002
var
I: QWORD;
By: BYTE;
Cnt: BYTE;
H, W: QWORD;
X, Y: QWORD;
K, L: QWORD;
begin
H := fPCXFile.fPCXHeader.fWindow.wBottom -
fPCXFile.fPCXHeader.fWindow.wTop + 1;
W := fPCXFile.fPCXHeader.fWindow.wRight -
fPCXFile.fPCXHeader.fWindow.wLeft + 1;
Y := 0; // First line of image
fBitmap.Width := W; // Set bitmap width
fBitmap.Height := H; // Set bitmap height
fBitmap.PixelFormat := pf24bit; // Always do this if you're using
// ScanLine!
I := 0; // Pointer to data byte of fPXCFile
repeat
// Process the red line
// ProcessLine(fRLine,W);
X := 0; // Pointer to position in Red / Green / Blue line
repeat
By := fPCXFile.fPCXData.fData[I];
Inc(I);
// one byte
if By < $C1 then
if X <= W then // added 5/4/2002
begin
fRLine[X] := By;
Inc(X);
end;
// multiple bytes (RLE)
if By > $C0 then
begin
Cnt := By and $3F;
By := fPCXFile.fPCXData.fData[I];
Inc(I);
//FillChar(fRLine[J],Cnt,By);
//Inc(J,Cnt);
for K := 1 to Cnt do
if X <= W then // added 5/4/2002
begin
fRLine[X] := By;
Inc(X);
end;
end;
until X >= W;
// Process the green line
// ProcessLine(fGLine,W);
X := 0;
repeat
By := fPCXFile.fPCXData.fData[I];
Inc(I);
// one byte
if By < $C1 then
if X <= W then // added 5/4/2002
begin
fGLine[X] := By;
Inc(X);
end;
// multiple bytes (RLE)
if By > $C0 then
begin
Cnt := By and $3F;
By := fPCXFile.fPCXData.fData[I];
Inc(I);
for K := 1 to Cnt do
if X <= W then // added 5/4/2002
begin
fGLine[X] := By;
Inc(X);
end;
end;
until X >= W;
// Process the blue line
// ProcessLine(fBLine,W);
X := 0;
repeat
By := fPCXFile.fPCXData.fData[I];
Inc(I);
// one byte
if By < $C1 then
if X <= W then // added 5/4/2002
begin
fBLine[X] := By;
Inc(X);
end;
// multiple bytes (RLE)
if By > $C0 then
begin
Cnt := By and $3F;
By := fPCXFile.fPCXData.fData[I];
Inc(I);
for K := 1 to Cnt do
if X <= W then // added 5/4/2002
begin
fBLine[X] := By;
Inc(X);
end;
end;
until X >= W;
// Write the just processed data RGB lines to the bitmap
fP := fBitmap.ScanLine[Y];
L := 0;
for X := 0 to W - 1 do
begin
fP[L] := fBLine[X];
Inc(L);
fP[L] := fGLine[X];
Inc(L);
fP[L] := fRLine[X];
Inc(L);
end;
Inc(Y); // Process the next RGB line
until Y >= H;
SetLength(fPCXFile.fPCXData.fData, 0);
end;
//-------------------------------------------------------------------//
procedure TPCXImage.fConvert1And8BitPCXDataToImage; // added 5/4/2002
var
I, J: QWORD;
By: BYTE;
Cnt: BYTE;
H, W, WW: QWORD;
X, Y: QWORD;
begin
H := fPCXFile.fPCXHeader.fWindow.wBottom -
fPCXFile.fPCXHeader.fWindow.wTop + 1;
W := fPCXFile.fPCXHeader.fWindow.wRight -
fPCXFile.fPCXHeader.fWindow.wLeft + 1;
fBitmap.Width := W; // Set bitmap width
fBitmap.Height := H; // Set bitmap height
WW := W;
// 1 bit PCX
if fPCXFile.fPixelFormat = 1 then
begin
// All 1 bit images have a palette
fBitmap.PixelFormat := pf1bit; // Always do this if you're using
// ScanLine!
WW := W div 8; // Correct width for pf1bit
if W mod 8 > 0 then
begin
Inc(WW);
fBitMap.Width := WW * 8;
end;
fSetPalette(2);
end;
// 8 bit PCX
if fPCXFile.fPixelFormat = 8 then
begin
// All 8 bit images have a palette!
// This is how to set the palette of a bitmap
// 1. First set the bitmap to pf8bit;
// 2. then set the palette of the bitmap;
// 3. then set the pixels with ScanLine or with Draw.
// If you do it with StretchDraw, it won't work. Don't ask me why.
// If you don't do it in this order, it won't work either! You'll
// get strange colors.
fBitmap.PixelFormat := pf8bit; // Always do this if you're using
// ScanLine!
fSetPalette(256);
end;
I := 0;
Y := 0;
repeat
fP := fBitmap.ScanLine[Y];
X := 0; // Pointer to position in line
repeat
By := fPCXFile.fPCXData.fData[I];
Inc(I);
// one byte
if By < $C1 then
if X <= WW then
begin
fP[X] := By;
Inc(X);
end;
// multiple bytes (RLE)
if By > $C0 then
begin
Cnt := By and $3F;
By := fPCXFile.fPCXData.fData[I];
Inc(I);
for J := 1 to Cnt do
if X <= WW then
begin
fP[X] := By;
Inc(X);
end;
end;
until X >= WW;
Inc(Y); // Next line
until Y >= H;
end;
//---------------------------------------------------------------------
procedure TPCXImage.fCreatePCXHeader(const byBitsPerPixel: BYTE;
const byPlanes: BYTE; const wBytesPerLine: DWORD);
var
H, W: WORD;
begin
W := fBitmap.Width;
H := fBitmap.Height;
// PCX header
fPCXFile.fPCXHeader.fID := BYTE($0A); // BYTE (1)
fPCXFile.fPCXHeader.fVersion := BYTE(5); // BYTE (2)
fPCXFile.fPCXHeader.fCompressed := BYTE(1); // BYTE (3)
// 0 = uncompressed, 1 = compressed
// Only RLE compressed files are supported by this component
fPCXFile.fPCXHeader.fBitsPerPixel := BYTE(byBitsPerPixel);
// BYTE (4)
fPCXFile.fPCXHeader.fWindow.wLeft := WORD(0); // WORD (5,6)
fPCXFile.fPCXHeader.fWindow.wTop := WORD(0); // WORD (7,8)
fPCXFile.fPCXHeader.fWindow.wRight := WORD(W - 1); // WORD (9,10)
fPCXFile.fPCXHeader.fWindow.wBottom := WORD(H - 1); // WORD (11,12)
fPCXFile.fPCXHeader.fHorzResolution := WORD(72); // WORD (13,14)
fPCXFile.fPCXHeader.fVertResolution := WORD(72); // WORD (15,16)
FillChar(fPCXFile.fPCXHeader.fColorMap, 48, 0); // Array of Byte
// (17..64)
fPCXFile.fPCXHeader.fReserved := BYTE(0); // BYTE (65)
fPCXFile.fPCXHeader.fPlanes := BYTE(byPlanes);
// BYTE (66)
fPCXFile.fPCXHeader.fBytesPerLine := WORD(wBytesPerLine);
// WORD (67,68)
// must be even
// rounded above
fPCXFile.fPCXHeader.fPaletteInfo := WORD(1); // WORD (69,70)
FillChar(fPCXFile.fPCXHeader.fFiller, 58, 0); // Array of Byte
// (71..128)
fPCXFile.fPixelFormat := fPCXFile.fPCXHeader.fPlanes *
fPCXFile.fPCXHeader.fBitsPerPixel;
fPCXFile.fColorDepth := 1 shl fPCXFile.fPixelFormat;
end;
//---------------------------------------------------------------------
(*
// From Delphi 5.0, graphics.pas
Function CopyPalette(Palette: HPALETTE): HPALETTE;
Var
PaletteSize : Integer;
LogPal : TMaxLogPalette;
Begin
Result := 0;
If Palette = 0 then
Exit;
PaletteSize := 0;
If GetObject(Palette,SizeOf(PaletteSize),@PaletteSize) = 0 then
Exit;
If PaletteSize = 0 then
Exit;
With LogPal do
Begin
palVersion := $0300;
palNumEntries := PaletteSize;
GetPaletteEntries(Palette,0,PaletteSize,palPalEntry);
End;
Result := CreatePalette(PLogPalette(@LogPal)^);
End;
*)
//---------------------------------------------------------------------
// From Delphi 5.0, graphics.pas
(*
Procedure TPCXImage.fSetPixelFormat(Value : TPixelFormat);
Const
BitCounts : Array [pf1Bit..pf32Bit] of BYTE = (1,4,8,16,16,24,32);
Var
DIB : TDIBSection;
Pal : HPALETTE;
DC : hDC;
KillPal : Boolean;
Begin
If Value = GetPixelFormat then
Exit;
Case Value of
pfDevice : Begin
HandleType := bmDDB;
Exit;
End;
pfCustom : InvalidGraphic(@SInvalidPixelFormat);
else
FillChar(DIB,sizeof(DIB), 0);
DIB.dsbm := FImage.FDIB.dsbm;
KillPal := False;
With DIB, dsbm,dsbmih do
Begin
bmBits := nil;
biSize := SizeOf(DIB.dsbmih);
biWidth := bmWidth;
biHeight := bmHeight;
biPlanes := 1;
biBitCount := BitCounts[Value];
Pal := FImage.FPalette;
Case Value of
pf4Bit : Pal := SystemPalette16;
pf8Bit : Begin
DC := GDICheck(GetDC(0));
Pal := CreateHalftonePalette(DC);
KillPal := True;
ReleaseDC(0, DC);
End;
pf16Bit : Begin
biCompression := BI_BITFIELDS;
dsBitFields[0] := $F800;
dsBitFields[1] := $07E0;
dsBitFields[2] := $001F;
End;
End; // of Case
Try
CopyImage(Handle, Pal, DIB);
PaletteModified := (Pal <> 0);
Finally
if KillPal then
DeleteObject(Pal);
End; // of Try
Changed(Self);
End; // of With
End; // of Case
End; // of Procedure
*)
//---------------------------------------------------------------------
procedure TPCXImage.fSetPalette(const wNumColors: WORD);
(* From Delphi 5.0, graphics.pas
Type
TPalEntry = packed record
peRed : BYTE;
peGreen : BYTE;
peBlue : BYTE;
End;
Type
tagLOGPALETTE = packed record
palVersion : WORD;
palNumEntries : WORD;
palPalEntry : Array[0..255] of TPalEntry
End;
Type
TMAXLogPalette = tagLOGPALETTE;
PMAXLogPalette = ^TMAXLogPalette;
Type
PRGBQuadArray = ^TRGBQuadArray;
TRGBQuadArray = Array[BYTE] of TRGBQuad;
Type
PRGBQuadArray = ^TRGBQuadArray;
TRGBQuadArray = Array[BYTE] of TRGBQuad;
*)
var
pal: TMaxLogPalette;
W: WORD;
begin
pal.palVersion := $300; // The "Magic" number
pal.palNumEntries := wNumColors;
for W := 0 to 255 do
begin
pal.palPalEntry[W].peRed :=
fPCXFile.fPCXPalette.fPalette[W].ceRed;
pal.palPalEntry[W].peGreen :=
fPCXFile.fPCXPalette.fPalette[W].ceGreen;
pal.palPalEntry[W].peBlue :=
fPCXFile.fPCXPalette.fPalette[W].ceBlue;
pal.palPalEntry[W].peFlags := 0;
end;
(* Must we delete the old palette first here? I dont know.
If fhPAL <> 0 then
DeleteObject(fhPAL);
*)
fhPAL := CreatePalette(PLogPalette(@pal)^);
if fhPAL <> 0 then
fBitmap.Palette := fhPAL;
end;
//---------------------------------------------------------------------
function TPCXImage.fGetPixelFormat: TPixelFormat;
// Only pf1bit, pf4bit and pf8bit images have a palette.
// pf15bit, pf16bit, pf24bit and pf32bit images have no palette.
// You can change the palette of pf1bit images in windows.
// The foreground color and the background color of pf1bit images
// do not have to be black and white. You can choose any tow colors.
// The palette of pf4bit images is fixed.
// The palette entries 0..9 and 240..255 of pf8bit images are reserved
// in windows.
begin
Result := pfDevice;
case fPCXFile.fPixelFormat of
01: Result := pf1bit; // Implemented WITH palette.
// 04 : Result := pf4bit; // Not yet implemented in this component,
// is however implemented in PCX format.
08: Result := pf8bit; // Implemented WITH palette.
// 15 : Result := pf15bit; // Not implemented in PCX format?
// 16 : Result := pf16bit; // Not implemented in PCX format?
24: Result := pf24bit; // Implemented, has no palette.
// 32 : Result := pf32bit; // Not implemented in PCX format.
end;
end;
//---------------------------------------------------------------------
procedure TPCXImage.fGetPalette(const wNumColors: WORD);
var
pal: TMaxLogPalette;
W: WORD;
begin
fPCXFile.fPCXPalette.fSignature := $0C;
pal.palVersion := $300; // The "Magic" number
pal.palNumEntries := wNumColors;
GetPaletteEntries(CopyPalette(fBitmap.Palette), 0, wNumColors,
pal.palPalEntry);
for W := 0 to 255 do
if W < wNumColors then
begin
fPCXFile.fPCXPalette.fPalette[W].ceRed :=
pal.palPalEntry[W].peRed;
fPCXFile.fPCXPalette.fPalette[W].ceGreen :=
pal.palPalEntry[W].peGreen;
fPCXFile.fPCXPalette.fPalette[W].ceBlue :=
pal.palPalEntry[W].peBlue;
end
else
begin
fPCXFile.fPCXPalette.fPalette[W].ceRed := 0;
fPCXFile.fPCXPalette.fPalette[W].ceGreen := 0;
fPCXFile.fPCXPalette.fPalette[W].ceBlue := 0;
end;
end;
//=====================================================================
///////////////////////////////////////////////////////////////////////
// //
// TPCXFile //
// //
///////////////////////////////////////////////////////////////////////
constructor TPCXFile.Create;
begin
inherited Create;
fHeight := 0;
fWidth := 0;
fCurrentPos := 0;
end;
//---------------------------------------------------------------------
destructor TPCXFile.Destroy;
begin
SetLength(fPCXData.fData, 0);
inherited Destroy;
end;
//---------------------------------------------------------------------
procedure TPCXFile.LoadFromFile(const Filename: string);
var
fPCXStream: TFileStream;
begin
fPCXStream := TFileStream.Create(Filename, fmOpenRead);
try
fPCXStream.Position := 0;
LoadFromStream(fPCXStream);
finally
fPCXStream.Free;
end;
end;
//---------------------------------------------------------------------
procedure TPCXFile.SaveToFile(const Filename: string);
var
fPCXStream: TFileStream;
begin
fPCXStream := TFileStream.Create(Filename, fmCreate);
try
fPCXStream.Position := 0;
SaveToStream(fPCXStream);
finally
fPCXStream.Free;
end;
end;
//---------------------------------------------------------------------
procedure TPCXFile.LoadFromStream(Stream: TStream);
var
fFileLength: Cardinal;
begin
// Read the PCX header
Stream.Read(fPCXHeader, SizeOf(fPCXHeader));
// Check the ID byte
if fPCXHeader.fID <> $0A then
raise Exception.Create(FORMAT_ERROR);
(*
Check PCX version byte
======================
Versionbyte = 0 => PC PaintBrush V2.5
Versionbyte = 2 => PC Paintbrush V2.8 with palette information
Versionbyte = 3 => PC Paintbrush V2.8 without palette information
Versionbyte = 4 => PC Paintbrush for Windows
Versionbyte = 5 => PC Paintbrush V3 and up, and PC Paintbrush Plus
with 24 bit image support
*)
// Check the PCX version
if fPCXHeader.fVersion <> 5 then
raise Exception.Create(VERSION_ERROR);
// Calculate width
fWidth := fPCXHeader.fWindow.wRight - fPCXHeader.fWindow.wLeft + 1;
if fWidth < 0 then
raise Exception.Create(WIDTH_OUT_OF_RANGE);
// Calculate height
fHeight := fPCXHeader.fWindow.wBottom - fPCXHeader.fWindow.wTop + 1;
if fHeight < 0 then
raise Exception.Create(HEIGHT_OUT_OF_RANGE);
// Is it too large?
if fWidth > fMaxImageWidth then
raise Exception.Create(IMAGE_WIDTH_TOO_LARGE);
// Calculate pixelformat
fPixelFormat := fPCXHeader.fPlanes * fPCXHeader.fBitsPerPixel;
// Calculate number of colors
fColorDepth := 1 shl fPixelFormat;
// Is this image supported?
if not (fPixelFormat in [1, 8, 24]) then
raise Exception.Create(ERROR_UNSUPPORTED);
// The lines following are NOT tested!!!
(*
If fColorDepth <= 16 then
For I := 0 to fColorDepth - 1 do
Begin
If fPCXHeader.fVersion = 3 then
Begin
fPCXPalette.fPalette[I].R := fPCXHeader.fColorMap[I].R shl 2;
fPCXPalette.fPalette[I].G := fPCXHeader.fColorMap[I].G shl 2;
fPCXPalette.fPalette[I].B := fPCXHeader.fColorMap[I].B shl 2;
End
else
Begin
fPCXPalette.fPalette[I].R := fPCXHeader.fColorMap[I].R;
fPCXPalette.fPalette[I].G := fPCXHeader.fColorMap[I].G;
fPCXPalette.fPalette[I].B := fPCXHeader.fColorMap[I].B;
End;
End;
*)
// Calculate number of data bytes
// If fFileLength > fMaxDataFileLength then
// Raise Exception.Create(INPUT_FILE_TOO_LARGE);
if fPixelFormat = 24 then
begin
fFileLength := Stream.Size - Stream.Position;
SetLength(fPCXData.fData, fFileLength);
// Read the data
Stream.Read(fPCXData.fData[0], fFileLength);
fHasPalette := False;
end;
if fPixelFormat in [1, 8] then
begin
fFileLength := Stream.Size - Stream.Position - 769;
SetLength(fPCXData.fData, fFileLength);
// Correct number of data bytes
Stream.Read(fPCXData.fData[0], fFilelength);
// Read the palette
Stream.Read(fPCXPalette, SizeOf(fPCXPalette));
fHasPalette := True;
// Check palette signature byte
if fPCXPalette.fSignature <> $0C then
raise Exception.Create(PALETTE_ERROR);
end;
end;
//---------------------------------------------------------------------
procedure TPCXFile.SaveToStream(Stream: TStream);
begin
fHasPalette := False;
Stream.Write(fPCXHeader, SizeOf(fPCXHeader));
Stream.Write(fPCXData.fData[0], fCurrentPos);
if fPixelFormat in [1, 8] then
begin
Stream.Write(fPCXPalette, SizeOf(fPCXPalette));
fHasPalette := True;
end;
end;
//---------------------------------------------------------------------
// Register PCX format
initialization
TPicture.RegisterFileFormat('PCX', sPCXImageFile, TPCXImage);
CF_PCX := RegisterClipBoardFormat('PCX Image');
TPicture.RegisterClipBoardFormat(CF_PCX, TPCXImage);
//---------------------------------------------------------------------
// Unregister PCX format
finalization
TPicture.UnRegisterGraphicClass(TPCXImage);
//---------------------------------------------------------------------
end.
//=====================================================================