Mega Code Archive

 
Categories / Delphi / Graphic
 

PCXImage

Title: PCXImage Question: How to write a graphic component? Answer: /////////////////////////////////////////////////////////////////////// // // // TPCXImage // // ========= // // // // Completed: The 10th of August 2001 // // Author: M. de Haan // // Email: M.deHaan@inn.nl // // Tested: under W95 SP1, NT4 SP6 // // 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. // // // /////////////////////////////////////////////////////////////////////// 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); // -------------------------- // 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 If (By 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 If X 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 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 If X 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 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 If X 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 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 If X 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 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 don't 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 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 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 Raise Exception.Create(WIDTH_OUT_OF_RANGE); // Calculate height fHeight := fPCXHeader.fWindow.wBottom - fPCXHeader.fWindow.wTop + 1; If fHeight 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 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. //=====================================================================