{ +-------------------------------------------------------------+ } { | | } { | GM-Software | } { | =========== | } { | | } { | Project: All Projects | } { | | } { | Description: PNG Image | } { | Original version written by Gustavo Daud. | } { | | } { | Copyright (C) - 2009 - Gerrit Moeller. | } { | | } { | Source code distributed under MIT license. | } { | | } { | See: https://www.gm-software.de | } { | | } { +-------------------------------------------------------------+ } {$INCLUDE GMCompilerSettings.inc} unit GMPngImage; interface {$TYPEDADDRESS OFF} {.$DEFINE UseDelphi} // Disable fat vcl units(perfect for small apps) {$DEFINE ErrorOnUnknownCritical} // Error when finds an unknown critical chunk {$DEFINE CheckCRC} // Enables CRC checking {.$DEFINE RegisterGraphic} // Registers TPNGObject to use with TPicture {$DEFINE PartialTransparentDraw} // Draws partial transparent images {$DEFINE Store16bits} // Stores the extra 8 bits from 16bits/sample {$RANGECHECKS OFF} {$J+} {$HPPEMIT '#pragma link "pngimage.obj"'} //Resolve linkage for C++ uses {$IFDEF JEDIAPI}{$ELSE}Windows,{$ENDIF} GMActiveX, GMIntf, GMZStrm, SysUtils {$IFDEF UseDelphi}, Classes, Graphics, SysUtils{$ENDIF}; const cResTypePngImg = 'PNGIMAGE'; //LibraryVersion = '1.564'; {ZLIB constants} ZLIBErrors: array[-6..2] of string = ('incompatible version (-6)', 'buffer error (-5)', 'insufficient memory (-4)', 'data error (-3)', 'stream error (-2)', 'file error (-1)', '(0)', 'stream end (1)', 'need dictionary (2)'); // Filters for mode 0 FILTER_NONE = 0; FILTER_SUB = 1; FILTER_UP = 2; FILTER_AVERAGE = 3; FILTER_PAETH = 4; // PNG color modes COLOR_GRAYSCALE = 0; COLOR_RGB = 2; COLOR_PALETTE = 3; COLOR_GRAYSCALEALPHA = 4; COLOR_RGBALPHA = 6; type {$IFNDEF UseDelphi} TColor = ColorRef; TCanvas = THandle; TBitmap = HBitmap; TPersistent = TObject; {$ENDIF} {Error types} EPNGOutMemory = class(Exception); EPngError = class(Exception); EPngUnexpectedEnd = class(Exception); EPngInvalidCRC = class(Exception); EPngInvalidIHDR = class(Exception); EPNGMissingMultipleIDAT = class(Exception); EPNGZLIBError = class(Exception); EPNGInvalidPalette = class(Exception); EPNGInvalidFileHeader = class(Exception); EPNGIHDRNotFirst = class(Exception); EPNGNotExists = class(Exception); EPNGSizeExceeds = class(Exception); EPNGMissingPalette = class(Exception); EPNGUnknownCriticalChunk = class(Exception); EPNGUnknownCompression = class(Exception); EPNGUnknownInterlace = class(Exception); EPNGNoImageData = class(Exception); EPNGCouldNotLoadResource = class(Exception); EPNGCannotChangeTransparent = class(Exception); EPNGHeaderNotPresent = class(Exception); EPNGInvalidNewSize = class(Exception); EPNGInvalidSpec = class(Exception); type TRGBLine = array[word] of TRGBTriple; pRGBLine = ^TRGBLine; // Same as TBitmapInfo but with allocated space for palette entries TMAXBITMAPINFO = packed record bmiHeader: TBitmapInfoHeader; bmiColors: packed array[0..255] of TRGBQuad; end; TPNGTransparencyMode = (ptmNone, ptmBit, ptmPartial); pCardinal = ^Cardinal; pRGBPixel = ^TRGBPixel; TRGBPixel = packed record B, G, R: Byte; end; TByteArray = array[Word] of Byte; pByteArray = ^TByteArray; TGMPngImage = class; pPointerArray = ^TPointerArray; TPointerArray = array[Word] of Pointer; TChunk = class; TChunkClass = class of TChunk; TChunkList = class(TObject) private FOwner: TGMPngImage; FEntries: array of TChunk; function GetItem(const AIndex: LongInt): TChunk; procedure SetItem(const AIndex: LongInt; const AValue: TChunk); function GetCount: LongInt; procedure SetCount(const AValue: LongInt); public constructor Create(const AOwner: TGMPngImage); function FindChunk(const ChunkClass: TChunkClass): TChunk; procedure RemoveChunk(const Chunk: TChunk); overload; procedure Insert(const AChunk: TChunk; const APosition: LongInt); procedure Add(const AChunk: TChunk); function AddByClass(const ChunkClass: TChunkClass): TChunk; function ItemFromClass(ChunkClass: TChunkClass): TChunk; property Item [const Idx: LongInt]: TChunk read GetItem write SetItem; property Owner: TGMPngImage read FOwner; property Count: LongInt read GetCount write SetCount; end; TChunkIHDR = class; TChunkpHYs = class; TInterlaceMethod = (imNone, imAdam7); TCompressionLevel = 0..9; TPNGFilter = (pfNone, pfSub, pfUp, pfAverage, pfPaeth); TPNGFilters = set of TPNGFilter; IGMPngImage = interface(IUnknown) ['{751BBD8E-EB62-4349-B936-AB69527EA497}'] function Obj: TGMPngImage; end; TGMPngImage = class(TGMRefCountedObj, IGMPngImage){$IFDEF UseDelphi}(TGraphic){$ENDIF} protected FInverseGamma: array[Byte] of Byte; {$IFDEF UseDelphi}FCanvas: TCanvas;{$ENDIF} FFilters: TPNGFilters; FCompressionLevel: TCompressionLevel; FMaxIdatSize: LongWord; FInterlaceMethod: TInterlaceMethod; FChunkList: TChunkList; procedure ClearChunks; function HeaderPresent: Boolean; procedure GetPixelInfo(var LineSize, Offset: Cardinal); procedure SetMaxIdatSize(const Value: LongWord); function GetAlphaScanline(const LineIndex: Integer): pByteArray; function GetScanline(const LineIndex: Integer): Pointer; {$IFDEF Store16bits} function GetExtraScanline(const LineIndex: Integer): Pointer; {$ENDIF} function GetPixelInformation: TChunkpHYs; function GetTransparencyMode: TPNGTransparencyMode; function GetTransparentColor: TColor; procedure SetTransparentColor(const Value: TColor); //function GetLibraryVersion: String; procedure InitializeGamma; protected FBeingCreated: Boolean; //function GetSupportsPartialTransparency: Boolean; override; function GetPalette: HPALETTE; {$IFDEF UseDelphi}override;{$ENDIF} procedure SetPalette(Value: HPALETTE); {$IFDEF UseDelphi}override;{$ENDIF} procedure DoSetPalette(Value: HPALETTE; const UpdateColors: Boolean); function GetWidth: Integer; {$IFDEF UseDelphi}override;{$ENDIF} function GetHeight: Integer; {$IFDEF UseDelphi}override; {$ENDIF} procedure SetWidth(Value: Integer); {$IFDEF UseDelphi}override; {$ENDIF} procedure SetHeight(Value: Integer); {$IFDEF UseDelphi}override;{$ENDIF} procedure AssignPNG(Source: TGMPngImage); function GetEmpty: Boolean; {$IFDEF UseDelphi}override; {$ENDIF} function GetHeader: TChunkIHDR; procedure DrawPartialTrans(DC: HDC; Rect: TRect); {$IFDEF UseDelphi} function GetTransparent: Boolean; override; {$ENDIF} function GetPixels(const X, Y: Integer): TColor; virtual; procedure SetPixels(const X, Y: Integer; const Value: TColor); virtual; public GammaTable: array[Byte] of Byte; constructor Create(const ARefLifeTime: Boolean = True); override; constructor CreateBlank(ColorType, Bitdepth: Cardinal; cx, cy: Integer; const ARefLifeTime: Boolean = True); destructor Destroy; override; function Obj: TGMPngImage; procedure Resize(const CX, CY: Integer); procedure CreateAlpha; procedure RemoveTransparency; property TransparentColor: TColor read GetTransparentColor write SetTransparentColor; procedure AddtEXt(const Keyword, Text: AnsiString); procedure AddzTXt(const Keyword, Text: AnsiString); function Size: TPoint; {$IFDEF UseDelphi} procedure SaveToClipboardGMFormat(var AFormat: Word; var AData: THandle; var APalette: HPalette); override; procedure LoadFromClipboardGMFormat(AFormat: Word; AData: THandle; APalette: HPalette); override; {$ENDIF} procedure RaiseError(ExceptionClass: ExceptClass; Text: String); property Scanline[const Index: Integer]: Pointer read GetScanline; {$IFDEF Store16bits} property ExtraScanline[const Index: Integer]: Pointer read GetExtraScanline; {$ENDIF} {Used to return pixel information} function HasPixelInformation: Boolean; property PixelInformation: TChunkpHYs read GetPixelInformation; property AlphaScanline[const Index: Integer]: pByteArray read GetAlphaScanline; procedure DrawUsingPixelInformation(Canvas: TCanvas; Point: TPoint); {$IFDEF UseDelphi}property Canvas: TCanvas read FCanvas;{$ENDIF} property Header: TChunkIHDR read GetHeader; property TransparencyMode: TPNGTransparencyMode read GetTransparencyMode; procedure Assign(Source: TPersistent);{$IFDEF UseDelphi}override;{$ENDIF} procedure AssignTo(Dest: TPersistent);{$IFDEF UseDelphi}override;{$ENDIF} procedure AssignHandle(Handle: HBitmap; Transparent: Boolean; TransparentColor: ColorRef); procedure Draw(ACanvas: TCanvas; const Rect: TRect); {$IFDEF UseDelphi}override;{$ENDIF} property Width: Integer read GetWidth; property Height: Integer read GetHeight; property InterlaceMethod: TInterlaceMethod read FInterlaceMethod write FInterlaceMethod; property Filters: TPNGFilters read FFilters write FFilters; property MaxIdatSize: LongWord read FMaxIdatSize write SetMaxIdatSize; property Empty: Boolean read GetEmpty; property CompressionLevel: TCompressionLevel read FCompressionLevel write FCompressionLevel; property Chunks: TChunkList read FChunkList; // {$IFNDEF UseDelphi}procedure LoadFromFile(const Filename: String);{.$ENDIF} // {$IFNDEF UseDelphi}procedure SaveToFile(const Filename: String);{.$ENDIF} procedure LoadFromStream(Stream: IStream); {$IFDEF UseDelphi}override;{$ENDIF} procedure SaveToStream(Stream: IStream); {$IFDEF UseDelphi}override;{$ENDIF} //procedure LoadFromResourceName(Instance: HInst; const Name: String); //procedure LoadFromResourceID(Instance: HInst; ResID: Integer); {Access to the png pixels} property Pixels[const X, Y: Integer]: TColor read GetPixels write SetPixels; {$IFNDEF UseDelphi}property Palette: HPalette read GetPalette write SetPalette;{$ENDIF} //property Version: String read GetLibraryVersion; end; TChunkName = array[0..3] of AnsiChar; TChunk = class private FData: Pointer; FDataSize: Cardinal; FOwner: TGMPngImage; FName: TChunkName; function GetHeader: TChunkIHDR; function GetIndex: Integer; public property Index: Integer read GetIndex; property Header: TChunkIHDR read GetHeader; procedure ResizeData(const NewSize: Cardinal); property Data: Pointer read FData; property DataSize: Cardinal read FDataSize; procedure Assign(Source: TChunk); virtual; property Owner: TGMPngImage read FOwner; constructor Create(Owner: TGMPngImage); virtual; destructor Destroy; override; function LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; virtual; function SaveData(Stream: IStream): Boolean; function SaveToStream(Stream: IStream): Boolean; virtual; end; TChunkIEND = class(TChunk); pIHDRData = ^TIHDRData; TIHDRData = packed record Width, Height: Cardinal; BitDepth, ColorType, CompressionMethod, FilterMethod, InterlaceMethod: Byte; end; TChunkIHDR = class(TChunk) private FImageHandle: HBitmap; FImageDC: HDC; FImagePalette: HPalette; FHasPalette: Boolean; FBitmapInfo: TMaxBitmapInfo; {$IFDEF Store16bits}FExtraImageData: Pointer;{$ENDIF} FImageData: pointer; FImageAlpha: Pointer; FIHDRData: TIHDRData; protected BytesPerRow: Integer; function CreateGrayscalePalette(Bitdepth: Integer): HPalette; procedure PaletteToDIB(Palette: HPalette); procedure PrepareImageData; procedure FreeImageData; public {Access to FImageHandle} property ImageHandleValue: HBitmap read FImageHandle; {Properties} property Width: Cardinal read FIHDRData.Width write FIHDRData.Width; property Height: Cardinal read FIHDRData.Height write FIHDRData.Height; property BitDepth: Byte read FIHDRData.BitDepth write FIHDRData.BitDepth; property ColorType: Byte read FIHDRData.ColorType write FIHDRData.ColorType; property CompressionMethod: Byte read FIHDRData.CompressionMethod write FIHDRData.CompressionMethod; property FilterMethod: Byte read FIHDRData.FilterMethod write FIHDRData.FilterMethod; property InterlaceMethod: Byte read FIHDRData.InterlaceMethod write FIHDRData.InterlaceMethod; function LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; override; function SaveToStream(Stream: IStream): Boolean; override; constructor Create(Owner: TGMPngImage); override; destructor Destroy; override; procedure Assign(Source: TChunk); override; property ImageDC_: HDC read FImageDC; end; pUnitType = ^TUnitType; TUnitType = (utUnknown, utMeter); TChunkpHYs = class(TChunk) private FPPUnitX, FPPUnitY: Cardinal; FUnit: TUnitType; public property PPUnitX: Cardinal read FPPUnitX write FPPUnitX; property PPUnitY: Cardinal read FPPUnitY write FPPUnitY; property UnitType: TUnitType read FUnit write FUnit; function LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; override; function SaveToStream(Stream: IStream): Boolean; override; procedure Assign(Source: TChunk); override; end; TChunkgAMA = class(TChunk) private function GetValue: Cardinal; procedure SetValue(const Value: Cardinal); public property Gamma: Cardinal read GetValue write SetValue; function LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; override; constructor Create(Owner: TGMPngImage); override; procedure Assign(Source: TChunk); override; end; TZStreamRec2 = packed record ZLIB: TZStreamRec; // Additional info Data: Pointer; fStream : IStream; end; TChunkPLTE = class(TChunk) protected FCount: Integer; private function GetPaletteItem(Index: Byte): TRGBQuad; public property Item[Index: Byte]: TRGBQuad read GetPaletteItem; property Count: Integer read FCount; function LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; override; function SaveToStream(Stream: IStream): Boolean; override; procedure Assign(Source: TChunk); override; end; TChunktRNS = class(TChunk) private FBitTransparency: Boolean; function GetTransparentColor: ColorRef; procedure SetTransparentColor(const Value: ColorRef); public PaletteValues: array[Byte] of Byte; property BitTransparency: Boolean read FBitTransparency; property TransparentColor: ColorRef read GetTransparentColor write SetTransparentColor; function LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; override; function SaveToStream(Stream: IStream): Boolean; override; procedure Assign(Source: TChunk); override; end; TChunkIDAT = class(TChunk) private Header: TChunkIHDR; ImageWidth, ImageHeight: Integer; Row_Bytes, Offset : Cardinal; Encode_Buffer: array[0..5] of pByteArray; Row_Buffer: array[Boolean] of pByteArray; RowUsed: Boolean; EndPos: Integer; procedure FilterRow; function FilterToEncode: Byte; function IDATZlibRead(var ZLIBStream: TZStreamRec2; Buffer: Pointer; Count: LongWord; var EndPos: Integer; var crcfile: Cardinal): Integer; procedure IDATZlibWrite(var ZLIBStream: TZStreamRec2; Buffer: Pointer; const Length: Cardinal); procedure FinishIDATZlib(var ZLIBStream: TZStreamRec2); procedure PreparePalette; protected procedure DecodeInterlacedAdam7(Stream: IStream; var ZLIBStream: TZStreamRec2; const Size: Integer; var crcfile: Cardinal); procedure DecodeNonInterlaced(Stream: IStream; var ZLIBStream: TZStreamRec2; const Size: Integer; var crcfile: Cardinal); procedure EncodeNonInterlaced(Stream: IStream; var ZLIBStream: TZStreamRec2); procedure EncodeInterlacedAdam7(Stream: IStream; var ZLIBStream: TZStreamRec2); // Memory copy methods to decode procedure CopyNonInterlacedRGB8(Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyNonInterlacedRGB16(Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyNonInterlacedPalette148(Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyNonInterlacedPalette2(Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyNonInterlacedGray2(Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyNonInterlacedGrayscale16(Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyNonInterlacedRGBAlpha8(Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyNonInterlacedRGBAlpha16(Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyNonInterlacedGrayscaleAlpha8(Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyNonInterlacedGrayscaleAlpha16(Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyInterlacedRGB8(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyInterlacedRGB16(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyInterlacedPalette148(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyInterlacedPalette2(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyInterlacedGray2(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyInterlacedGrayscale16(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyInterlacedRGBAlpha8(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyInterlacedRGBAlpha16(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyInterlacedGrayscaleAlpha8(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); procedure CopyInterlacedGrayscaleAlpha16(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); // Memory copy methods to encode procedure EncodeNonInterlacedRGB8(Src, Dest, Trans: pByte); procedure EncodeNonInterlacedRGB16(Src, Dest, Trans: pByte); procedure EncodeNonInterlacedGrayscale16(Src, Dest, Trans: pByte); procedure EncodeNonInterlacedPalette148(Src, Dest, Trans: pByte); procedure EncodeNonInterlacedRGBAlpha8(Src, Dest, Trans: pByte); procedure EncodeNonInterlacedRGBAlpha16(Src, Dest, Trans: pByte); procedure EncodeNonInterlacedGrayscaleAlpha8(Src, Dest, Trans: pByte); procedure EncodeNonInterlacedGrayscaleAlpha16(Src, Dest, Trans: pByte); procedure EncodeInterlacedRGB8(const Pass: Byte; Src, Dest, Trans: pByte); procedure EncodeInterlacedRGB16(const Pass: Byte; Src, Dest, Trans: pByte); procedure EncodeInterlacedPalette148(const Pass: Byte; Src, Dest, Trans: pByte); procedure EncodeInterlacedGrayscale16(const Pass: Byte; Src, Dest, Trans: pByte); procedure EncodeInterlacedRGBAlpha8(const Pass: Byte; Src, Dest, Trans: pByte); procedure EncodeInterlacedRGBAlpha16(const Pass: Byte; Src, Dest, Trans: pByte); procedure EncodeInterlacedGrayscaleAlpha8(const Pass: Byte; Src, Dest, Trans: pByte); procedure EncodeInterlacedGrayscaleAlpha16(const Pass: Byte; Src, Dest, Trans: pByte); public function LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; override; function SaveToStream(Stream: IStream): Boolean; override; end; // Image last modification chunk TChunktIME = class(TChunk) private FYear: Word; FMonth, FDay, FHour, FMinute, FSecond: Byte; public property Year: Word read FYear write FYear; property Month: Byte read FMonth write FMonth; property Day: Byte read FDay write FDay; property Hour: Byte read FHour write FHour; property Minute: Byte read FMinute write FMinute; property Second: Byte read FSecond write FSecond; function LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; override; function SaveToStream(Stream: IStream): Boolean; override; procedure Assign(Source: TChunk); override; end; // Textual data TChunktEXt = class(TChunk) private FKeyword, FText: AnsiString; public property Keyword: AnsiString read FKeyword write FKeyword; property Text: AnsiString read FText write FText; function LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; override; function SaveToStream(Stream: IStream): Boolean; override; procedure Assign(Source: TChunk); override; end; {zTXT chunk} TChunkzTXt = class(TChunktEXt) {Loads the chunk from a stream} function LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; override; {Saves the chunk to a stream} function SaveToStream(Stream: IStream): Boolean; override; end; {Here we test if it's c++ builder or delphi version 3 or less} {$IFDEF VER110}{$DEFINE DelphiBuilder3Less}{$ENDIF} {$IFDEF VER100}{$DEFINE DelphiBuilder3Less}{$ENDIF} {$IFDEF VER93}{$DEFINE DelphiBuilder3Less}{$ENDIF} {$IFDEF VER90}{$DEFINE DelphiBuilder3Less}{$ENDIF} {$IFDEF VER80}{$DEFINE DelphiBuilder3Less}{$ENDIF} function update_crc(crc: {$IFNDEF DelphiBuilder3Less}Cardinal{$ELSE}Integer {$ENDIF}; buf: pByteArray; len: Integer): Cardinal; function ByteSwap(const a: integer): integer; function GMLoadPngImgFromRes(const AResourceName: PChar; AResourceType: PChar = nil; AModuleHandle: THandle = INVALID_HANDLE_VALUE): IGMPngImage; implementation uses GMCommon; resourcestring EPngInvalidCRCText = 'This PNG image is not valid because it contains invalid pieces of data (crc error)'; EPNGInvalidIHDRText = 'The PNG image could not be loaded because one of its main piece of data (ihdr) might be corrupted'; EPNGMissingMultipleIDATText = 'This PNG image is invalid because it has missing image parts'; EPNGZLIBErrorText = 'Could not decompress the image because it contains invalid compressed data.'#13#10 + ' Description: '; EPNGInvalidPaletteText = 'The PNG image contains an invalid palette'; EPNGInvalidFileHeaderText = 'The file being readed is not a valid PNG image because it contains an invalid header. This file may be corruped, try obtaining it again'; EPNGIHDRNotFirstText = 'This PNG image is not supported or it might be invalid.'#13#10 + '(IHDR chunk is not the first)'; EPNGNotExistsText = 'The png file could not be loaded because it does not exists'; EPNGSizeExceedsText = 'This PNG image is not supported because either it''s width or height exceeds the maximum size, which is 65535 pixels length'; EPNGUnknownPalEntryText = 'There is no such palette entry'; EPNGMissingPaletteText = 'This PNG could not be loaded because it uses a color table which is missing'; EPNGUnknownCriticalChunkText = 'This PNG image contains an unknown critical part which could not be decoded'; EPNGUnknownCompressionText = 'This PNG image is encoded with an unknown compression scheme which could not be decoded'; EPNGUnknownInterlaceText = 'This PNG image uses an unknown interlace scheme which could not be decoded'; EPNGCannotAssignChunkText = 'The chunks must be compatible to be assigned'; EPNGUnexpectedEndText = 'This PNG image is invalid because the decoder found an unexpected end of the file'; EPNGNoImageDataText = 'This PNG image contains no data'; EPNGCannotChangeSizeText = 'The PNG image can not be resize by changing width and height properties. Try assigning the image from a bitmap'; EPNGCannotAddChunkText = 'The program tried to add a existent critical chunk to the current image which is not allowed'; EPNGCannotAddInvalidImageText = 'It''s not allowed to add a new chunk because the current image is invalid'; EPNGCouldNotLoadResourceText = 'The png image could not be loaded from the resource ID'; EPNGOutMemoryText = 'Some operation could not be performed because the system is out of resources. Close some windows and try again'; EPNGCannotChangeTransparentText = 'Setting bit transparency color is not allowed for png images containing alpha value for each pixel (COLOR_RGBALPHA and COLOR_GRAYSCALEALPHA)'; EPNGHeaderNotPresentText = 'This operation is not valid because the current image contains no valid header'; EInvalidNewSizeText = 'The new size provided for image resizing is invalid'; EInvalidSpecText = 'The PNG image could not be created because invalid image type parameters have being provided'; var crc_table: array[0..255] of Cardinal; crc_table_computed: Boolean; function GMLoadPngImgFromRes(const AResourceName: PChar; AResourceType: PChar; AModuleHandle: THandle): IGMPngImage; var ResStrm: IStream; begin if AResourceName <> nil then begin if AResourceType = nil then AResourceType := cResTypePngImg; if AModuleHandle = INVALID_HANDLE_VALUE then AModuleHandle := HInstance; ResStrm := TGMResourceIStream.Create(AResourceName, AResourceType, AModuleHandle, True); Result := TGMPngImage.Create(True); Result.Obj.LoadFromStream(ResStrm); end; end; {Draw transparent image using transparent color} procedure DrawTransparentBitmap(dc: HDC; srcBits: Pointer; var srcHeader: TBitmapInfoHeader; srcBitmapInfo: pBitmapInfo; Rect: TRect; cTransparentColor: COLORREF); var cColor: COLORREF; bmpBkgndMask, bmpImageMask, bmAndMem: HBITMAP; bmpOldBkgnd, bmpOldImage, bmpMemOld: HBITMAP; hdcMem, dcBkgndMask, dcImageMask, dcDraw: HDC; ptSize, orgSize: TPOINT; OldBitmap, bmpDraw: HBITMAP; begin dcDraw := CreateCompatibleDC(dc); {Select the bitmap} bmpDraw := CreateDIBitmap(dc, srcHeader, CBM_INIT, srcBits, srcBitmapInfo^, DIB_RGB_COLORS); OldBitmap := SelectObject(dcDraw, bmpDraw); {Get sizes} OrgSize.x := abs(srcHeader.biWidth); OrgSize.y := abs(srcHeader.biHeight); ptSize.x := Rect.Right - Rect.Left; // Get width of bitmap ptSize.y := Rect.Bottom - Rect.Top; // Get height of bitmap {Create some DCs to hold temporary data} dcBkgndMask := CreateCompatibleDC(dc); dcImageMask := CreateCompatibleDC(dc); hdcMem := CreateCompatibleDC(dc); // Create a bitmap for each DC. DCs are required for a number of // GDI functions. // Monochrome DCs bmpBkgndMask := CreateBitmap(ptSize.x, ptSize.y, 1, 1, nil); bmpImageMask := CreateBitmap(ptSize.x, ptSize.y, 1, 1, nil); bmAndMem := CreateCompatibleBitmap(dc, ptSize.x, ptSize.y); // Each DC must select a bitmap object to store pixel data. bmpOldBkgnd := SelectObject(dcBkgndMask, bmpBkgndMask); bmpOldImage := SelectObject(dcImageMask, bmpImageMask); bmpMemOld := SelectObject(hdcMem, bmAndMem); // Set the background color of the source DC to the color // contained in the parts of the bitmap that should be transparent cColor := SetBkColor(dcDraw, cTransparentColor); // Create the object mask for the bitmap by performing a BitBlt // from the source bitmap to a monochrome bitmap. StretchBlt(dcImageMask, 0, 0, ptSize.x, ptSize.y, dcDraw, 0, 0, orgSize.x, orgSize.y, SRCCOPY); // Set the background color of the source DC back to the original color. SetBkColor(dcDraw, cColor); // Create the inverse of the object mask. BitBlt(dcBkgndMask, 0, 0, ptSize.x, ptSize.y, dcImageMask, 0, 0, NOTSRCCOPY); // Copy the background of the main DC to the destination. BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, dc, Rect.Left, Rect.Top, SRCCOPY); // Mask out the places where the bitmap will be placed. BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, dcImageMask, 0, 0, SRCAND); // Mask out the transparent colored pixels on the bitmap. // BitBlt(dcDraw, 0, 0, ptSize.x, ptSize.y, dcBkgndMask, 0, 0, SRCAND); StretchBlt(dcDraw, 0, 0, OrgSize.x, OrgSize.y, dcBkgndMask, 0, 0, PtSize.x, PtSize.y, SRCAND); // XOR the bitmap with the background on the destination DC. StretchBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, dcDraw, 0, 0, OrgSize.x, OrgSize.y, SRCPAINT); // Copy the destination to the screen. BitBlt(dc, Rect.Left, Rect.Top, ptSize.x, ptSize.y, hdcMem, 0, 0, SRCCOPY); // Delete the memory bitmaps. DeleteObject(SelectObject(dcBkgndMask, bmpOldBkgnd)); DeleteObject(SelectObject(dcImageMask, bmpOldImage)); DeleteObject(SelectObject(hdcMem, bmpMemOld)); DeleteObject(SelectObject(dcDraw, OldBitmap)); // Delete the memory DCs. DeleteDC(hdcMem); DeleteDC(dcBkgndMask); DeleteDC(dcImageMask); DeleteDC(dcDraw); end; procedure make_crc_table; var c: Cardinal; n, k: Integer; begin for n := 0 to 255 do begin c := Cardinal(n); for k := 0 to 7 do if Boolean(c and 1) then c := $edb88320 xor (c shr 1) else c := c shr 1; crc_table[n] := c; end; crc_table_computed := true; end; {Update a running CRC with the bytes buf[0..len-1]--the CRC should be initialized to all 1's, and the transmitted value is the 1's complement of the final running CRC (see the crc() routine below)).} function update_crc(crc: {$IFNDEF DelphiBuilder3Less}Cardinal{$ELSE}Integer {$ENDIF}; buf: pByteArray; len: Integer): Cardinal; var c: Cardinal; n: Integer; begin c := crc; if not crc_table_computed then make_crc_table; for n := 0 to len - 1 do c := crc_table[(c XOR buf^[n]) and $FF] XOR (c shr 8); Result := c; end; {$IFNDEF UseDelphi} function FileExists(Filename: String): Boolean; var FindFile: THandle; FindData: TWin32FindData; begin FindFile := FindFirstFile(PChar(Filename), FindData); Result := FindFile <> INVALID_HANDLE_VALUE; if Result then Windows.FindClose(FindFile); end; {$ENDIF} {Calculates the paeth predictor} function PaethPredictor(a, b, c: Byte): Byte; var pa, pb, pc: Integer; begin { a = left, b = above, c = upper left } pa := abs(b - c); { distances to a, b, c } pb := abs(a - c); pc := abs(a + b - c * 2); { return nearest of a, b, c, breaking ties in order a, b, c } if (pa <= pb) and (pa <= pc) then Result := a else if pb <= pc then Result := b else Result := c; end; {Invert bytes using assembly} function ByteSwap(const a: integer): integer; asm bswap eax end; function ByteSwap16(inp:word): word; asm bswap eax shr eax, 16 end; {Calculates number of bytes for the number of pixels using the} {color mode in the paramenter} function BytesForPixels(const Pixels: Integer; const ColorType, BitDepth: Byte): Integer; begin case ColorType of {Palette and grayscale contains a single value, for palette} {an value of size 2^bitdepth pointing to the palette index} {and grayscale the value from 0 to 2^bitdepth with color intesity} COLOR_GRAYSCALE, COLOR_PALETTE: Result := (Pixels * BitDepth + 7) div 8; {RGB contains 3 values R, G, B with size 2^bitdepth each} COLOR_RGB: Result := (Pixels * BitDepth * 3) div 8; {Contains one value followed by alpha value booth size 2^bitdepth} COLOR_GRAYSCALEALPHA: Result := (Pixels * BitDepth * 2) div 8; {Contains four values size 2^bitdepth, Red, Green, Blue and alpha} COLOR_RGBALPHA: Result := (Pixels * BitDepth * 4) div 8; else Result := 0; end; {case ColorType} end; function CreateChunkByClass(AOwner: TGMPngImage; AName: TChunkName): TChunk; var ChunkCreateClass: TChunkClass; const cnIEND = Ord('I') + Ord('E') shl 8 + Ord('N') shl 16 + Ord('D') shl 24; const cnIHDR = Ord('I') + Ord('H') shl 8 + Ord('D') shl 16 + Ord('R') shl 24; const cnIDAT = Ord('I') + Ord('D') shl 8 + Ord('A') shl 16 + Ord('T') shl 24; const cnPLTE = Ord('P') + Ord('L') shl 8 + Ord('T') shl 16 + Ord('E') shl 24; const cngAMA = Ord('g') + Ord('A') shl 8 + Ord('M') shl 16 + Ord('A') shl 24; const cntRNS = Ord('t') + Ord('R') shl 8 + Ord('N') shl 16 + Ord('S') shl 24; const cnpHYs = Ord('p') + Ord('H') shl 8 + Ord('Y') shl 16 + Ord('s') shl 24; const cntIME = Ord('t') + Ord('I') shl 8 + Ord('M') shl 16 + Ord('E') shl 24; const cntEXt = Ord('t') + Ord('E') shl 8 + Ord('X') shl 16 + Ord('t') shl 24; const cnzTXt = Ord('z') + Ord('T') shl 8 + Ord('X') shl 16 + Ord('t') shl 24; begin case LongWord(AName) of // Important chunks cnIEND: ChunkCreateClass := TChunkIEND; cnIHDR: ChunkCreateClass := TChunkIHDR; cnIDAT: ChunkCreateClass := TChunkIDAT; cnPLTE: ChunkCreateClass := TChunkPLTE; cngAMA: ChunkCreateClass := TChunkgAMA; cntRNS: ChunkCreateClass := TChunktRNS; // Less important chunks cnpHYs: ChunkCreateClass := TChunkpHYs; cntIME: ChunkCreateClass := TChunktIME; cntEXt: ChunkCreateClass := TChunktEXt; cnzTXt: ChunkCreateClass := TChunkzTXt; else ChunkCreateClass := TChunk; // <- default creation end; Result := ChunkCreateClass.Create(AOwner); Result.FName := AName; end; {ZLIB support} const ZLIBAllocate = High(Word); {Initializes ZLIB for decompression} function ZLIBInitInflate(Stream: IStream): TZStreamRec2; begin Fillchar(Result, SIZEOF(TZStreamRec2), #0); with Result do begin GetMem(Data, ZLIBAllocate); fStream := Stream; end; InflateInit_(Result.zlib, zlib_version, SIZEOF(TZStreamRec)); end; {Initializes ZLIB for compression} function ZLIBInitDeflate(Stream: IStream; Level: TCompressionlevel; Size: Cardinal): TZStreamRec2; begin Fillchar(Result, SIZEOF(TZStreamRec2), #0); with Result, ZLIB do begin GetMem(Data, Size); fStream := Stream; next_out := Data; avail_out := Size; end; deflateInit_(Result.zlib, Level, zlib_version, sizeof(TZStreamRec)); end; {Terminates ZLIB for compression} procedure ZLIBTerminateDeflate(var ZLIBStream: TZStreamRec2); begin {Terminates decompression} DeflateEnd(ZLIBStream.zlib); {Free internal record} FreeMem(ZLIBStream.Data, ZLIBAllocate); end; {Terminates ZLIB for decompression} procedure ZLIBTerminateInflate(var ZLIBStream: TZStreamRec2); begin {Terminates decompression} InflateEnd(ZLIBStream.zlib); {Free internal record} FreeMem(ZLIBStream.Data, ZLIBAllocate); end; {Decompresses ZLIB into a memory address} function DecompressZLIB(const Input: Pointer; InputSize: Integer; var Output: Pointer; var OutputSize: LongWord; var ErrorOutput: String): Boolean; var StreamRec : TZStreamRec; Buffer : array[Byte] of Byte; InflateRet: Integer; begin with StreamRec do begin {Initializes} Result := True; OutputSize := 0; {Prepares the data to decompress} FillChar(StreamRec, SizeOf(TZStreamRec), #0); InflateInit_(StreamRec, zlib_version, SIZEOF(TZStreamRec)); next_in := Input; avail_in := InputSize; {Decodes data} repeat {In case it needs an output buffer} if (avail_out = 0) then begin next_out := @Buffer; avail_out := SizeOf(Buffer); end {if (avail_out = 0)}; {Decompress and put in output} InflateRet := inflate(StreamRec, 0); if (InflateRet = Z_STREAM_END) or (InflateRet = 0) then begin {Reallocates output buffer} inc(OutputSize, total_out); if Output = nil then GetMem(Output, OutputSize) else ReallocMem(Output, OutputSize); {Copies the new data} CopyMemory(Ptr(PtrUInt(Output) + OutputSize - total_out), @Buffer, total_out); end {if (InflateRet = Z_STREAM_END) or (InflateRet = 0)} {Now tests for errors} else if InflateRet < 0 then begin Result := False; ErrorOutput := string(AnsiString(StreamRec.msg)); InflateEnd(StreamRec); Exit; end {if InflateRet < 0} until InflateRet = Z_STREAM_END; {Terminates decompression} InflateEnd(StreamRec); end {with StreamRec} end; {Compresses ZLIB into a memory address} function CompressZLIB(Input: Pointer; InputSize, CompressionLevel: Integer; var Output: Pointer; var OutputSize: LongWord; var ErrorOutput: String): Boolean; var StreamRec : TZStreamRec; Buffer : array[Byte] of Byte; DeflateRet: Integer; begin with StreamRec do begin Result := True; {By default returns TRUE as everything might have gone ok} OutputSize := 0; {Initialize} {Prepares the data to compress} FillChar(StreamRec, SizeOf(TZStreamRec), #0); DeflateInit_(StreamRec, CompressionLevel,zlib_version, SIZEOF(TZStreamRec)); next_in := Input; avail_in := InputSize; while avail_in > 0 do begin {When it needs new buffer to stores the compressed data} if avail_out = 0 then begin {Restore buffer} next_out := @Buffer; avail_out := SizeOf(Buffer); end {if avail_out = 0}; {Compresses} DeflateRet := deflate(StreamRec, Z_FINISH); if (DeflateRet = Z_STREAM_END) or (DeflateRet = 0) then begin {Updates the output memory} inc(OutputSize, total_out); if Output = nil then GetMem(Output, OutputSize) else ReallocMem(Output, OutputSize); {Copies the new data} CopyMemory(Ptr(PtrUInt(Output) + OutputSize - total_out), @Buffer, total_out); end {if (InflateRet = Z_STREAM_END) or (InflateRet = 0)} {Now tests for errors} else if DeflateRet < 0 then begin Result := False; ErrorOutput := string(AnsiString(StreamRec.msg)); DeflateEnd(StreamRec); Exit; end {if InflateRet < 0} end {while avail_in > 0}; {Finishes compressing} DeflateEnd(StreamRec); end {with StreamRec} end; {TChunkList implementation} constructor TChunkList.Create(const AOwner: TGMPngImage); begin inherited Create; FOwner := AOwner; end; {Finds the first chunk of this class} function TChunkList.FindChunk(const ChunkClass: TChunkClass): TChunk; var i: Integer; begin Result := nil; for i := 0 to Count - 1 do if Item[i] is ChunkClass then begin Result := Item[i]; Break; end; end; function TChunkList.GetCount: LongInt; begin Result := Length(FEntries); end; procedure TChunkList.Insert(const AChunk: TChunk; const APosition: LongInt); begin SetLength(FEntries, Length(FEntries)+1); if APosition <= High(FEntries) then System.Move(FEntries[APosition], FEntries[APosition+1], (Length(FEntries) - APosition -1) * SizeOf(TChunk)); FEntries[APosition] := AChunk; end; procedure TChunkList.Add(const AChunk: TChunk); begin SetLength(FEntries, Length(FEntries)+1); FEntries[High(FEntries)] := AChunk; end; function TChunkList.GetItem(const AIndex: LongInt): TChunk; begin if (AIndex >= Low(FEntries)) and (AIndex <= High(FEntries)) then Result := FEntries[AIndex] else Result := nil; end; procedure TChunkList.SetItem(const AIndex: LongInt; const AValue: TChunk); begin if (AIndex >= Low(FEntries)) and (AIndex <= High(FEntries)) then FEntries[AIndex] := AValue; end; procedure TChunkList.SetCount(const AValue: LongInt); begin SetLength(FEntries, Length(FEntries) + 1); end; procedure TChunkList.RemoveChunk(const Chunk: TChunk); var i: LongInt; begin if Length(FEntries) = 0 then Exit; for i := Low(FEntries) to High(FEntries) do if Chunk = FEntries[i] then Break; if Chunk <> FEntries[i] then Exit; if i < High(FEntries) then System.Move(FEntries[i+1], FEntries[i], (Length(FEntries) - i-1) * SizeOf(TChunk)); SetLength(FEntries, Length(FEntries)-1); Chunk.Free; end; function TChunkList.AddByClass(const ChunkClass: TChunkClass): TChunk; var IHDR: TChunkIHDR; IEND: TChunkIEND; IDAT: TChunkIDAT; PLTE: TChunkPLTE; begin Result := nil; {Default Result} {Adding these is not allowed} if ((ChunkClass = TChunkIHDR) or (ChunkClass = TChunkIDAT) or (ChunkClass = TChunkPLTE) or (ChunkClass = TChunkIEND)) and not (Owner.FBeingCreated) then FOwner.RaiseError(EPngError, EPNGCannotAddChunkText) {Two of these is not allowed} else if ((ChunkClass = TChunkgAMA) and (ItemFromClass(TChunkgAMA) <> nil)) or ((ChunkClass = TChunktRNS) and (ItemFromClass(TChunktRNS) <> nil)) or ((ChunkClass = TChunkpHYs) and (ItemFromClass(TChunkpHYs) <> nil)) then FOwner.RaiseError(EPngError, EPNGCannotAddChunkText) {There must have an IEND and IHDR chunk} else if ((ItemFromClass(TChunkIEND) = nil) or (ItemFromClass(TChunkIHDR) = nil)) and not Owner.FBeingCreated then FOwner.RaiseError(EPngError, EPNGCannotAddInvalidImageText) else begin {Get common chunks} IHDR := ItemFromClass(TChunkIHDR) as TChunkIHDR; IEND := ItemFromClass(TChunkIEND) as TChunkIEND; {Create new chunk} Result := ChunkClass.Create(Owner); {Add to the list} if (ChunkClass = TChunkgAMA) or (ChunkClass = TChunkpHYs) or (ChunkClass = TChunkPLTE) then Insert(Result, IHDR.Index + 1) {Header and end} else if (ChunkClass = TChunkIEND) then Insert(Result, Count) else if (ChunkClass = TChunkIHDR) then Insert(Result, 0) {Transparency chunk (fix by Ian Boyd)} else if (ChunkClass = TChunktRNS) then begin {Transparecy chunk must be after PLTE; before IDAT} IDAT := ItemFromClass(TChunkIDAT) as TChunkIDAT; PLTE := ItemFromClass(TChunkPLTE) as TChunkPLTE; if Assigned(PLTE) then Insert(Result, PLTE.Index + 1) else if Assigned(IDAT) then Insert(Result, IDAT.Index) else Insert(Result, IHDR.Index + 1) end else {All other chunks} Insert(Result, IEND.Index); end {if} end; function TChunkList.ItemFromClass(ChunkClass: TChunkClass): TChunk; // Returns first item from the list using the class from parameter var i: Integer; begin Result := nil; {Initial Result} FOR i := 0 TO Count - 1 DO {Test if this item has the same class} if Item[i] is ChunkClass then begin {Returns this item and Exit} Result := Item[i]; break; end {if} end; {TChunk implementation} {Resizes the data} procedure TChunk.ResizeData(const NewSize: Cardinal); begin FDataSize := NewSize; ReallocMem(FData, NewSize + 1); end; {Returns index from list} function TChunk.GetIndex: Integer; var i: Integer; begin Result := -1; {Avoiding warnings} {Searches in the list} FOR i := 0 TO Owner.Chunks.Count - 1 DO if Owner.Chunks.Item[i] = Self then begin {Found match} Result := i; Exit; end {for i} end; {Returns pointer to the TChunkIHDR} function TChunk.GetHeader: TChunkIHDR; begin Result := Owner.Chunks.Item[0] as TChunkIHDR; end; {Assigns from another TChunk} procedure TChunk.Assign(Source: TChunk); begin {Copy properties} FName := Source.FName; {Set data size and realloc} ResizeData(Source.FDataSize); {Copy data (if there's any)} if FDataSize > 0 then CopyMemory(FData, Source.FData, FDataSize); end; constructor TChunk.Create(Owner: TGMPngImage); var ChunkName: AnsiString; begin inherited Create; {If it's a registered class, set the chunk name based on the class} {name. For instance, if the class name is TChunkgAMA, the GAMA part} {will become the chunk name} ChunkName := AnsiString(Copy(ClassName, Length('TChunk') + 1, Length(ClassName))); if Length(ChunkName) = 4 then CopyMemory(@FName[0], @ChunkName[1], 4); {Initialize data holder} GetMem(FData, 1); FDataSize := 0; {Record owner} FOwner := Owner; end; destructor TChunk.Destroy; begin FreeMem(FData, FDataSize + 1); inherited Destroy; end; function TChunk.SaveData(Stream: IStream): Boolean; var ChunkSize, ChunkCRC: Cardinal; begin {First, write the size for the following data in the chunk} ChunkSize := ByteSwap(DataSize); GMSafeIStreamWrite(Stream, @ChunkSize, SizeOf(ChunkSize)); {The chunk name} GMSafeIStreamWrite(Stream, @FName, SizeOf(FName)); {If there is data for the chunk, write it} if DataSize > 0 then GMSafeIStreamWrite(Stream, Data, DataSize); {Calculates and write CRC} ChunkCRC := update_crc($ffffffff, @FName[0], SizeOf(FName)); ChunkCRC := Byteswap(update_crc(ChunkCRC, Data, DataSize) xor $ffffffff); GMSafeIStreamWrite(Stream, @ChunkCRC, SizeOf(ChunkCRC)); {Returns that everything went ok} Result := TRUE; end; function TChunk.SaveToStream(Stream: IStream): Boolean; begin Result := SaveData(Stream) end; function TChunk.LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; var CheckCRC: Cardinal; {$IFDEF CheckCRC}RightCRC: Cardinal;{$ENDIF} begin {Copies data from source} ResizeData(Size); if Size > 0 then GMSafeIStreamRead(Stream, FData, Size); {Reads CRC} GMSafeIStreamRead(Stream, @CheckCRC, SizeOf(CheckCRC)); CheckCrc := ByteSwap(CheckCRC); {Check if crc readed is valid} {$IFDEF CheckCRC} RightCRC := update_crc($ffffffff, @ChunkName[0], SizeOf(ChunkName)); RightCRC := update_crc(RightCRC, FData, Size) xor $ffffffff; Result := RightCRC = CheckCrc; {Handle CRC error} if not Result then begin {In case it coult not load chunk} Owner.RaiseError(EPngInvalidCRC, EPngInvalidCRCText); Exit; end {$ELSE}Result := TRUE; {$ENDIF} end; {TChunktIME implementation} {Chunk being loaded from a stream} function TChunktIME.LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; begin {Let ancestor load the data} Result := inherited LoadFromStream(Stream, ChunkName, Size); if not Result or (Size <> 7) then Exit; {Size must be 7} {Reads data} FYear := ((pByte(LongInt(Data) )^) * 256)+ (pByte(LongInt(Data) + 1)^); FMonth := pByte(LongInt(Data) + 2)^; FDay := pByte(LongInt(Data) + 3)^; FHour := pByte(LongInt(Data) + 4)^; FMinute := pByte(LongInt(Data) + 5)^; FSecond := pByte(LongInt(Data) + 6)^; end; {Assigns from another TChunk} procedure TChunktIME.Assign(Source: TChunk); begin FYear := TChunktIME(Source).FYear; FMonth := TChunktIME(Source).FMonth; FDay := TChunktIME(Source).FDay; FHour := TChunktIME(Source).FHour; FMinute := TChunktIME(Source).FMinute; FSecond := TChunktIME(Source).FSecond; end; {Saving the chunk to a stream} function TChunktIME.SaveToStream(Stream: IStream): Boolean; begin {Update data} ResizeData(7); {Make sure the size is 7} pWord(Data)^ := ByteSwap16(Year); pByte(LongInt(Data) + 2)^ := Month; pByte(LongInt(Data) + 3)^ := Day; pByte(LongInt(Data) + 4)^ := Hour; pByte(LongInt(Data) + 5)^ := Minute; pByte(LongInt(Data) + 6)^ := Second; {Let inherited save data} Result := inherited SaveToStream(Stream); end; {TChunkztXt implementation} {Loading the chunk from a stream} function TChunkzTXt.LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; var ErrorOutput: String; CompressionMethod: Byte; Output: Pointer; OutputSize: LongWord; begin {Load data from stream and validate} Result := inherited LoadFromStream(Stream, ChunkName, Size); if not Result or (Size < 4) then Exit; FKeyword := PAnsiChar(Data); {Get keyword and compression method bellow} if LongInt(FKeyword) = 0 then CompressionMethod := pByte(Data)^ else CompressionMethod := pByte(LongInt(FKeyword) + Length(FKeyword))^; FText := ''; {In case the compression is 0 (only one accepted by specs), reads it} if CompressionMethod = 0 then begin Output := nil; if DecompressZLIB(PAnsiChar(LongInt(Data) + Length(FKeyword) + 2), Size - Length(FKeyword) - 2, Output, OutputSize, ErrorOutput) then begin SetLength(FText, OutputSize); CopyMemory(@FText[1], Output, OutputSize); end {if DecompressZLIB(...}; FreeMem(Output); end {if CompressionMethod = 0} end; {Saving the chunk to a stream} function TChunkztXt.SaveToStream(Stream: IStream): Boolean; var Output: Pointer; OutputSize: LongWord; ErrorOutput: String; begin Output := nil; {Initializes output} if FText = '' then FText := ' '; {Compresses the data} if CompressZLIB(@FText[1], Length(FText), Owner.CompressionLevel, Output, OutputSize, ErrorOutput) then begin {Size is length from keyword, plus a null character to divide} {plus the compression method, plus the length of the text (zlib compressed)} ResizeData(Cardinal(Length(FKeyword)) + 2 + OutputSize); Fillchar(Data^, DataSize, #0); {Copies the keyword data} if Keyword <> '' then CopyMemory(Data, @FKeyword[1], Length(Keyword)); {Compression method 0 (inflate/deflate)} pByte(Ptr(LongInt(Data) + Length(Keyword) + 1))^ := 0; if OutputSize > 0 then CopyMemory(Ptr(LongInt(Data) + Length(Keyword) + 2), Output, OutputSize); {Let ancestor calculate crc and save} Result := SaveData(Stream); end {if CompressZLIB(...} else Result := False; {Frees output} if Output <> nil then FreeMem(Output) end; {TChunktEXt implementation} {Assigns from another text chunk} procedure TChunktEXt.Assign(Source: TChunk); begin FKeyword := TChunktEXt(Source).FKeyword; FText := TChunktEXt(Source).FText; end; {Loading the chunk from a stream} function TChunktEXt.LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; begin {Load data from stream and validate} Result := inherited LoadFromStream(Stream, ChunkName, Size); if not Result or (Size < 3) then Exit; {Get text} FKeyword := PAnsiChar(Data); SetLength(FText, Size - Length(FKeyword) - 1); CopyMemory(@FText[1], Ptr(LongInt(Data) + Length(FKeyword) + 1), Length(FText)); end; {Saving the chunk to a stream} function TChunktEXt.SaveToStream(Stream: IStream): Boolean; begin {Size is length from keyword, plus a null character to divide} {plus the length of the text} ResizeData(Length(FKeyword) + 1 + Length(FText)); Fillchar(Data^, DataSize, #0); {Copy data} if Keyword <> '' then CopyMemory(Data, @FKeyword[1], Length(Keyword)); if Text <> '' then CopyMemory(Ptr(LongInt(Data) + Length(Keyword) + 1), @FText[1], Length(Text)); {Let ancestor calculate crc and save} Result := inherited SaveToStream(Stream); end; {TChunkIHDR implementation} {Chunk being created} constructor TChunkIHDR.Create(Owner: TGMPngImage); begin {Prepare pointers} FImageHandle := 0; FImagePalette := 0; FImageDC := 0; {Call inherited} inherited Create(Owner); end; {Chunk being destroyed} destructor TChunkIHDR.Destroy; begin {Free memory} FreeImageData(); {Calls TChunk destroy} inherited Destroy; end; {Copies the palette} procedure CopyPalette(Source: HPALETTE; Destination: HPALETTE); var PaletteSize: Integer; Entries: array[Byte] of TPaletteEntry; begin PaletteSize := 0; if GetObject(Source, SizeOf(PaletteSize), @PaletteSize) = 0 then Exit; if PaletteSize = 0 then Exit; ResizePalette(Destination, PaletteSize); GetPaletteEntries(Source, 0, PaletteSize, Entries); SetPaletteEntries(Destination, 0, PaletteSize, Entries); end; {Assigns from another IHDR chunk} procedure TChunkIHDR.Assign(Source: TChunk); begin {Copy the IHDR data} if Source is TChunkIHDR then begin {Copy IHDR values} FIHDRData := TChunkIHDR(Source).FIHDRData; {Prepare to hold data by filling FBitmapInfo structure and} {resizing FImageData and FImageAlpha memory allocations} PrepareImageData(); {Copy image data} CopyMemory(FImageData, TChunkIHDR(Source).FImageData, BytesPerRow * Integer(Height)); CopyMemory(FImageAlpha, TChunkIHDR(Source).FImageAlpha, Integer(Width) * Integer(Height)); {Copy palette colors} FBitmapInfo.bmiColors := TChunkIHDR(Source).FBitmapInfo.bmiColors; {Copy palette also} CopyPalette(TChunkIHDR(Source).FImagePalette, FImagePalette); end else Owner.RaiseError(EPNGError, EPNGCannotAssignChunkText); end; {Release allocated image data} procedure TChunkIHDR.FreeImageData; begin {Free old image data} if FImageHandle <> 0 then DeleteObject(FImageHandle); if FImageDC <> 0 then DeleteDC(FImageDC); if FImageAlpha <> nil then FreeMem(FImageAlpha); if FImagePalette <> 0 then DeleteObject(FImagePalette); {$IFDEF Store16bits} if FExtraImageData <> nil then FreeMem(FExtraImageData); {$ENDIF} FImageHandle := 0; FImageDC := 0; FImageAlpha := nil; FImageData := nil; FImagePalette := 0; FExtraImageData := nil; end; {Chunk being loaded from a stream} function TChunkIHDR.LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; begin {Let TChunk load it} Result := inherited LoadFromStream(Stream, ChunkName, Size); if not Result then Exit; {Now check values} {Note: It's recommended by png specification to make sure that the size} {must be 13 bytes to be valid, but some images with 14 bytes were found} {which could be loaded by internet explorer and other tools} if (FDataSize < SIZEOF(TIHdrData)) then begin {Ihdr must always have at least 13 bytes} Result := False; Owner.RaiseError(EPNGInvalidIHDR, EPNGInvalidIHDRText); Exit; end; {Everything ok, reads IHDR} FIHDRData := pIHDRData(FData)^; FIHDRData.Width := ByteSwap(FIHDRData.Width); FIHDRData.Height := ByteSwap(FIHDRData.Height); {The width and height must not be larger than 65535 pixels} if (FIHDRData.Width > High(Word)) or (FIHDRData.Height > High(Word)) then begin Result := False; Owner.RaiseError(EPNGSizeExceeds, EPNGSizeExceedsText); Exit; end {if FIHDRData.Width > High(Word)}; {Compression method must be 0 (inflate/deflate)} if (FIHDRData.CompressionMethod <> 0) then begin Result := False; Owner.RaiseError(EPNGUnknownCompression, EPNGUnknownCompressionText); Exit; end; {Interlace must be either 0 (none) or 7 (adam7)} if (FIHDRData.InterlaceMethod <> 0) and (FIHDRData.InterlaceMethod <> 1) then begin Result := False; Owner.RaiseError(EPNGUnknownInterlace, EPNGUnknownInterlaceText); Exit; end; {Updates owner properties} Owner.InterlaceMethod := TInterlaceMethod(FIHDRData.InterlaceMethod); {Prepares data to hold image} PrepareImageData(); end; {Saving the IHDR chunk to a stream} function TChunkIHDR.SaveToStream(Stream: IStream): Boolean; begin {Ignore 2 bits images} if BitDepth = 2 then BitDepth := 4; {It needs to do is update the data with the IHDR data} {structure containing the write values} ResizeData(SizeOf(TIHDRData)); pIHDRData(FData)^ := FIHDRData; {..byteswap 4 byte types} pIHDRData(FData)^.Width := ByteSwap(pIHDRData(FData)^.Width); pIHDRData(FData)^.Height := ByteSwap(pIHDRData(FData)^.Height); {..update interlace method} pIHDRData(FData)^.InterlaceMethod := Byte(Owner.InterlaceMethod); {..and then let the ancestor SaveToStream do the hard work} Result := inherited SaveToStream(Stream); end; {Creates a grayscale palette} function TChunkIHDR.CreateGrayscalePalette(Bitdepth: Integer): HPalette; var j: Integer; palEntries: TMaxLogPalette; begin {Prepares and fills the strucutre} if Bitdepth = 16 then Bitdepth := 8; fillchar(palEntries, sizeof(palEntries), 0); palEntries.palVersion := $300; palEntries.palNumEntries := 1 shl Bitdepth; {Fill it with grayscale colors} for j := 0 to palEntries.palNumEntries - 1 do begin palEntries.palPalEntry[j].peRed := FOwner.GammaTable[MulDiv(j, 255, palEntries.palNumEntries - 1)]; palEntries.palPalEntry[j].peGreen := palEntries.palPalEntry[j].peRed; palEntries.palPalEntry[j].peBlue := palEntries.palPalEntry[j].peRed; end; {Creates and returns the palette} Result := CreatePalette(pLogPalette(@palEntries)^); end; {Copies the palette to the Device Independent bitmap header} procedure TChunkIHDR.PaletteToDIB(Palette: HPalette); var j: Integer; palEntries: TMaxLogPalette; begin {Copy colors} Fillchar(palEntries, sizeof(palEntries), #0); FBitmapInfo.bmiHeader.biClrUsed := GetPaletteEntries(Palette, 0, 256, palEntries.palPalEntry[0]); for j := 0 to FBitmapInfo.bmiHeader.biClrUsed - 1 do begin FBitmapInfo.bmiColors[j].rgbBlue := palEntries.palPalEntry[j].peBlue; FBitmapInfo.bmiColors[j].rgbRed := palEntries.palPalEntry[j].peRed; FBitmapInfo.bmiColors[j].rgbGreen := palEntries.palPalEntry[j].peGreen; end; end; {Resizes the image data to fill the color type, bit depth, } {width and height parameters} procedure TChunkIHDR.PrepareImageData(); {Set the bitmap info} procedure SetInfo(const Bitdepth: Integer; const Palette: Boolean); begin {Copy if the bitmap contain palette entries} FHasPalette := Palette; {Fill the strucutre} with FBitmapInfo.bmiHeader do begin biSize := sizeof(TBitmapInfoHeader); biHeight := Height; biWidth := Width; biPlanes := 1; biBitCount := BitDepth; biCompression := BI_RGB; end {with FBitmapInfo.bmiHeader} end; begin {Prepare bitmap info header} Fillchar(FBitmapInfo, sizeof(TMaxBitmapInfo), #0); {Release old image data} FreeImageData(); {Obtain number of bits for each pixel} case ColorType of COLOR_GRAYSCALE, COLOR_PALETTE, COLOR_GRAYSCALEALPHA: case BitDepth of {These are supported by windows} 1, 4, 8: SetInfo(BitDepth, TRUE); {2 bits for each pixel is not supported by windows bitmap} 2 : SetInfo(4, TRUE); {Also 16 bits (2 bytes) for each pixel is not supported} {and should be transormed into a 8 bit grayscale} 16 : SetInfo(8, TRUE); end; {Only 1 byte (8 bits) is supported} COLOR_RGB, COLOR_RGBALPHA: SetInfo(24, FALSE); end {case ColorType}; {Number of bytes for each scanline} BytesPerRow := (((FBitmapInfo.bmiHeader.biBitCount * Width) + 31) and not 31) div 8; {Build array for alpha information, if necessary} if (ColorType = COLOR_RGBALPHA) or (ColorType = COLOR_GRAYSCALEALPHA) then begin GetMem(FImageAlpha, Integer(Width) * Integer(Height)); FillChar(FImageAlpha^, Integer(Width) * Integer(Height), #0); end; {Build array for extra byte information} {$IFDEF Store16bits} if (BitDepth = 16) then begin GetMem(FExtraImageData, BytesPerRow * Integer(Height)); FillChar(FExtraImageData^, BytesPerRow * Integer(Height), #0); end; {$ENDIF} {Creates the image to hold the data, CreateDIBSection does a better} {work in allocating necessary memory} FImageDC := CreateCompatibleDC(0); {$IFDEF UseDelphi}Self.Owner.Canvas.Handle := FImageDC;{$ENDIF} {In case it is a palette image, create the palette} if FHasPalette then begin {Create a standard palette} if ColorType = COLOR_PALETTE then FImagePalette := CreateHalfTonePalette(FImageDC) else FImagePalette := CreateGrayscalePalette(Bitdepth); ResizePalette(FImagePalette, 1 shl FBitmapInfo.bmiHeader.biBitCount); FBitmapInfo.bmiHeader.biClrUsed := 1 shl FBitmapInfo.bmiHeader.biBitCount; SelectPalette(FImageDC, FImagePalette, False); RealizePalette(FImageDC); PaletteTODIB(FImagePalette); end; {Create the device independent bitmap} FImageHandle := CreateDIBSection(FImageDC, pBitmapInfo(@FBitmapInfo)^, DIB_RGB_COLORS, FImageData, 0, 0); SelectObject(FImageDC, FImageHandle); {Build array and allocate bytes for each row} fillchar(FImageData^, BytesPerRow * Integer(Height), 0); end; {TChunktRNS implementation} {$IFNDEF UseDelphi} function CompareMem(P1, P2: pByte; const Size: Integer): Boolean; var i: Integer; begin Result := True; for i := 1 to Size do begin if P1^ <> P2^ then Result := False; inc(P1); inc(P2); end {for i} end; {$ENDIF} {Sets the transpararent color} procedure TChunktRNS.SetTransparentColor(const Value: ColorRef); var i: Byte; LookColor: TRGBQuad; begin {Clears the palette values} Fillchar(PaletteValues, SizeOf(PaletteValues), #0); {Sets that it uses bit transparency} FBitTransparency := True; {Depends on the color type} with Header do case ColorType of COLOR_GRAYSCALE: begin Self.ResizeData(2); pWord(@PaletteValues[0])^ := ByteSwap16(GetRValue(Value)); end; COLOR_RGB: begin Self.ResizeData(6); pWord(@PaletteValues[0])^ := ByteSwap16(GetRValue(Value)); pWord(@PaletteValues[2])^ := ByteSwap16(GetGValue(Value)); pWord(@PaletteValues[4])^ := ByteSwap16(GetBValue(Value)); end; COLOR_PALETTE: begin {Creates a RGBQuad to search for the color} LookColor.rgbRed := GetRValue(Value); LookColor.rgbGreen := GetGValue(Value); LookColor.rgbBlue := GetBValue(Value); {Look in the table for the entry} for i := 0 to FBitmapInfo.bmiHeader.biClrUsed - 1 do if CompareMem(@FBitmapInfo.bmiColors[i], @LookColor, 3) then Break; {Fill the transparency table} Fillchar(PaletteValues, i, 255); Self.ResizeData(i + 1) end end {case / with}; end; {Returns the transparent color for the image} function TChunktRNS.GetTransparentColor: ColorRef; var PaletteChunk: TChunkPLTE; i: Integer; Value: Byte; begin Result := 0; {Default: Unknown transparent color} {Depends on the color type} with Header do case ColorType of COLOR_GRAYSCALE: begin Value := FBitmapInfo.bmiColors[PaletteValues[1]].rgbRed; Result := RGB(Value, Value, Value); end; COLOR_RGB: Result := RGB(FOwner.GammaTable[PaletteValues[1]], FOwner.GammaTable[PaletteValues[3]], FOwner.GammaTable[PaletteValues[5]]); COLOR_PALETTE: begin {Obtains the palette chunk} PaletteChunk := Owner.Chunks.ItemFromClass(TChunkPLTE) as TChunkPLTE; {Looks for an entry with 0 transparency meaning that it is the} {full transparent entry} for i := 0 to Self.DataSize - 1 do if PaletteValues[i] = 0 then with PaletteChunk.GetPaletteItem(i) do begin Result := RGB(rgbRed, rgbGreen, rgbBlue); break end end {COLOR_PALETTE} end {case Header.ColorType}; end; {Saving the chunk to a stream} function TChunktRNS.SaveToStream(Stream: IStream): Boolean; begin {Copy palette into data buffer} if DataSize <= 256 then CopyMemory(FData, @PaletteValues[0], DataSize); Result := inherited SaveToStream(Stream); end; {Assigns from another chunk} procedure TChunktRNS.Assign(Source: TChunk); begin CopyMemory(@PaletteValues[0], @TChunkTrns(Source).PaletteValues[0], 256); FBitTransparency := TChunkTrns(Source).FBitTransparency; inherited Assign(Source); end; {Loads the chunk from a stream} function TChunktRNS.LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; var i, Differ255: Integer; begin {Let inherited load} Result := inherited LoadFromStream(Stream, ChunkName, Size); if not Result then Exit; {Make sure size is correct} if Size > 256 then Owner.RaiseError(EPNGInvalidPalette, EPNGInvalidPaletteText); {The unset items should have value 255} Fillchar(PaletteValues[0], 256, 255); {Copy the other values} CopyMemory(@PaletteValues[0], FData, Size); {Create the mask if needed} case Header.ColorType of {Mask for grayscale and RGB} COLOR_RGB, COLOR_GRAYSCALE: FBitTransparency := True; COLOR_PALETTE: begin Differ255 := 0; {Count the entries with a value different from 255} {Tests if it uses bit transparency} for i := 0 to Size - 1 do if PaletteValues[i] <> 255 then inc(Differ255); {If it has one value different from 255 it is a bit transparency} FBitTransparency := (Differ255 = 1); end {COLOR_PALETTE} end {case Header.ColorType}; end; {Prepares the image palette} procedure TChunkIDAT.PreparePalette; var Entries: Word; j : Integer; palEntries: TMaxLogPalette; begin {In case the image uses grayscale, build a grayscale palette} with Header do if (ColorType = COLOR_GRAYSCALE) or (ColorType = COLOR_GRAYSCALEALPHA) then begin {Calculate total number of palette entries} Entries := (1 shl Byte(FBitmapInfo.bmiHeader.biBitCount)); Fillchar(palEntries, sizeof(palEntries), #0); palEntries.palVersion := $300; palEntries.palNumEntries := Entries; FOR j := 0 TO Entries - 1 DO with palEntries.palPalEntry[j] do begin {Calculate each palette entry} peRed := FOwner.GammaTable[MulDiv(j, 255, Entries - 1)]; peGreen := peRed; peBlue := peRed; end {with FBitmapInfo.bmiColors[j]}; Owner.SetPalette(CreatePalette(pLogPalette(@palEntries)^)); end {if ColorType = COLOR_GRAYSCALE..., with Header} end; {Reads from ZLIB} function TChunkIDAT.IDATZlibRead(var ZLIBStream: TZStreamRec2; Buffer: Pointer; Count: LongWord; var EndPos: Integer; var crcfile: Cardinal): Integer; var ProcResult : Integer; IDATHeader : array[0..3] of AnsiChar; IDATCRC : Cardinal; begin {Uses internal record pointed by ZLIBStream to gather information} with ZLIBStream, ZLIBStream.zlib do begin {Set the buffer the zlib will read into} next_out := Buffer; avail_out := Count; {Decode until it reach the Count variable} while avail_out > 0 do begin {In case it needs more data and it's in the end of a IDAT chunk,} {it means that there are more IDAT chunks} if (GMIStreamPos(fStream) = EndPos) and (avail_out > 0) and (avail_in = 0) then begin {End this chunk by reading and testing the crc value} GMSafeIStreamRead(fStream, @IDATCRC, SizeOf(IDATCRC)); {$IFDEF CheckCRC} if crcfile xor $ffffffff <> Cardinal(ByteSwap(IDATCRC)) then begin Result := -1; Owner.RaiseError(EPNGInvalidCRC, EPNGInvalidCRCText); Exit; end; {$ENDIF} {Start reading the next chunk} GMSafeIStreamRead(fStream, @EndPos, SizeOf(EndPos)); {Reads next chunk size} GMSafeIStreamRead(fStream, @IDATHeader[0], SizeOf(IDATHeader)); {Next chunk header} {It must be a IDAT chunk since image data is required and PNG} {specification says that multiple IDAT chunks must be consecutive} if IDATHeader <> 'IDAT' then begin Owner.RaiseError(EPNGMissingMultipleIDAT, EPNGMissingMultipleIDATText); Result := -1; Exit; end; {Calculate chunk name part of the crc} {$IFDEF CheckCRC} crcfile := update_crc($ffffffff, @IDATHeader[0], SizeOf(IDATHeader)); {$ENDIF} EndPos := GMIStreamPos(fStream) + ByteSwap(EndPos); end; {In case it needs compressed data to read from} if avail_in = 0 then begin {In case it's trying to read more than it is avaliable} if GMIStreamPos(fStream) + ZLIBAllocate > EndPos then avail_in := GMIStreamRead(fStream, Data, EndPos - GMIStreamPos(fStream)) else avail_in := GMIStreamRead(fStream, Data, ZLIBAllocate); {Update crc} {$IFDEF CheckCRC} crcfile := update_crc(crcfile, Data, avail_in); {$ENDIF} {In case there is no more compressed data to read from} if avail_in = 0 then begin Result := Count - avail_out; Exit; end; {Set next buffer to read and record current position} next_in := Data; end {if avail_in = 0}; ProcResult := inflate(zlib, 0); {In case the Result was not sucessfull} if (ProcResult < 0) then begin Result := -1; Owner.RaiseError(EPNGZLIBError, EPNGZLIBErrorText + zliberrors[procresult]); Exit; end; end {while avail_out > 0}; end {with}; {If everything gone ok, it returns the count bytes} Result := Count; end; {TChunkIDAT implementation} const {Adam 7 interlacing values} RowStart: array[0..6] of Integer = (0, 0, 4, 0, 2, 0, 1); ColumnStart: array[0..6] of Integer = (0, 4, 0, 2, 0, 1, 0); RowIncrement: array[0..6] of Integer = (8, 8, 8, 4, 4, 2, 2); ColumnIncrement: array[0..6] of Integer = (8, 8, 4, 4, 2, 2, 1); {Copy interlaced images with 1 byte for R, G, B} procedure TChunkIDAT.CopyInterlacedRGB8(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var Col: Integer; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; Dest := pByte(LongInt(Dest) + Col * 3); repeat {Copy this row} PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 2)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 1)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) )^]; inc(Dest); {Move to next column} inc(Src, 3); inc(Dest, ColumnIncrement[Pass] * 3 - 3); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Copy interlaced images with 2 bytes for R, G, B} procedure TChunkIDAT.CopyInterlacedRGB16(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var Col: Integer; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; Dest := pByte(LongInt(Dest) + Col * 3); repeat {Copy this row} PByte(Dest)^ := Owner.GammaTable[pByte(LongInt(Src) + 4)^]; inc(Dest); PByte(Dest)^ := Owner.GammaTable[pByte(LongInt(Src) + 2)^]; inc(Dest); PByte(Dest)^ := Owner.GammaTable[pByte(LongInt(Src) )^]; inc(Dest); {$IFDEF Store16bits} {Copy extra pixel values} PByte(Extra)^ := FOwner.GammaTable[pByte(LongInt(Src) + 5)^]; inc(Extra); PByte(Extra)^ := FOwner.GammaTable[pByte(LongInt(Src) + 3)^]; inc(Extra); PByte(Extra)^ := FOwner.GammaTable[pByte(LongInt(Src) + 1)^]; inc(Extra); {$ENDIF} {Move to next column} inc(Src, 6); inc(Dest, ColumnIncrement[Pass] * 3 - 3); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Copy �mages with palette using bit depths 1, 4 or 8} procedure TChunkIDAT.CopyInterlacedPalette148(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); const BitTable: array[1..8] of Integer = ($1, $3, 0, $F, 0, 0, 0, $FF); StartBit: array[1..8] of Integer = (7 , 0 , 0, 4, 0, 0, 0, 0); var CurBit, Col: Integer; Dest2: pByte; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; repeat {Copy data} CurBit := StartBit[Header.BitDepth]; repeat {Adjust pointer to pixel byte bounds} Dest2 := pByte(LongInt(Dest) + (Header.BitDepth * Col) div 8); {Copy data} PByte(Dest2)^ := Byte(Dest2^) or ( ((Byte(Src^) shr CurBit) and BitTable[Header.BitDepth]) shl (StartBit[Header.BitDepth] - (Col * Header.BitDepth mod 8))); {Move to next column} inc(Col, ColumnIncrement[Pass]); {Will read next bits} dec(CurBit, Header.BitDepth); until CurBit < 0; {Move to next byte in source} inc(Src); until Col >= ImageWidth; end; {Copy �mages with palette using bit depth 2} procedure TChunkIDAT.CopyInterlacedPalette2(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var CurBit, Col: Integer; Dest2: pByte; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; repeat {Copy data} CurBit := 6; repeat {Adjust pointer to pixel byte bounds} Dest2 := pByte(LongInt(Dest) + Col div 2); {Copy data} PByte(Dest2)^ := Byte(Dest2^) or (((Byte(Src^) shr CurBit) and $3) shl (4 - (4 * Col) mod 8)); {Move to next column} inc(Col, ColumnIncrement[Pass]); {Will read next bits} dec(CurBit, 2); until CurBit < 0; {Move to next byte in source} inc(Src); until Col >= ImageWidth; end; {Copy �mages with grayscale using bit depth 2} procedure TChunkIDAT.CopyInterlacedGray2(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var CurBit, Col: Integer; Dest2: pByte; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; repeat {Copy data} CurBit := 6; repeat {Adjust pointer to pixel byte bounds} Dest2 := pByte(LongInt(Dest) + Col div 2); {Copy data} PByte(Dest2)^ := Byte(Dest2^) or ((((Byte(Src^) shr CurBit) shl 2) and $F) shl (4 - (Col*4) mod 8)); {Move to next column} inc(Col, ColumnIncrement[Pass]); {Will read next bits} dec(CurBit, 2); until CurBit < 0; {Move to next byte in source} inc(Src); until Col >= ImageWidth; end; {Copy �mages with palette using 2 bytes for each pixel} procedure TChunkIDAT.CopyInterlacedGrayscale16(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var Col: Integer; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; Dest := pByte(LongInt(Dest) + Col); repeat {Copy this row} Dest^ := Src^; inc(Dest); {$IFDEF Store16bits} Extra^ := pByte(LongInt(Src) + 1)^; inc(Extra); {$ENDIF} {Move to next column} inc(Src, 2); inc(Dest, ColumnIncrement[Pass] - 1); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Decodes interlaced RGB alpha with 1 byte for each sample} procedure TChunkIDAT.CopyInterlacedRGBAlpha8(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var Col: Integer; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; Dest := pByte(LongInt(Dest) + Col * 3); Trans := pByte(LongInt(Trans) + Col); repeat {Copy this row and alpha value} Trans^ := pByte(LongInt(Src) + 3)^; PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 2)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 1)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) )^]; inc(Dest); {Move to next column} inc(Src, 4); inc(Dest, ColumnIncrement[Pass] * 3 - 3); inc(Trans, ColumnIncrement[Pass]); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Decodes interlaced RGB alpha with 2 bytes for each sample} procedure TChunkIDAT.CopyInterlacedRGBAlpha16(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var Col: Integer; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; Dest := pByte(LongInt(Dest) + Col * 3); Trans := pByte(LongInt(Trans) + Col); repeat {Copy this row and alpha value} Trans^ := pByte(LongInt(Src) + 6)^; PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 4)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 2)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) )^]; inc(Dest); {$IFDEF Store16bits} {Copy extra pixel values} PByte(Extra)^ := FOwner.GammaTable[pByte(LongInt(Src) + 5)^]; inc(Extra); PByte(Extra)^ := FOwner.GammaTable[pByte(LongInt(Src) + 3)^]; inc(Extra); PByte(Extra)^ := FOwner.GammaTable[pByte(LongInt(Src) + 1)^]; inc(Extra); {$ENDIF} {Move to next column} inc(Src, 8); inc(Dest, ColumnIncrement[Pass] * 3 - 3); inc(Trans, ColumnIncrement[Pass]); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Decodes 8 bit grayscale image followed by an alpha sample} procedure TChunkIDAT.CopyInterlacedGrayscaleAlpha8(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var Col: Integer; begin {Get first column, pointers to the data and enter in loop} Col := ColumnStart[Pass]; Dest := pByte(LongInt(Dest) + Col); Trans := pByte(LongInt(Trans) + Col); repeat {Copy this grayscale value and alpha} Dest^ := Src^; inc(Src); Trans^ := Src^; inc(Src); {Move to next column} inc(Dest, ColumnIncrement[Pass]); inc(Trans, ColumnIncrement[Pass]); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Decodes 16 bit grayscale image followed by an alpha sample} procedure TChunkIDAT.CopyInterlacedGrayscaleAlpha16(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var Col: Integer; begin {Get first column, pointers to the data and enter in loop} Col := ColumnStart[Pass]; Dest := pByte(LongInt(Dest) + Col); Trans := pByte(LongInt(Trans) + Col); repeat {$IFDEF Store16bits} Extra^ := pByte(LongInt(Src) + 1)^; inc(Extra); {$ENDIF} {Copy this grayscale value and alpha, transforming 16 bits into 8} Dest^ := Src^; inc(Src, 2); Trans^ := Src^; inc(Src, 2); {Move to next column} inc(Dest, ColumnIncrement[Pass]); inc(Trans, ColumnIncrement[Pass]); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Decodes an interlaced image} procedure TChunkIDAT.DecodeInterlacedAdam7(Stream: IStream; var ZLIBStream: TZStreamRec2; const Size: Integer; var crcfile: Cardinal); var CurrentPass: Byte; PixelsThisRow: Integer; CurrentRow: Integer; Trans, Data{$IFDEF Store16bits}, Extra{$ENDIF}: pByte; CopyProc: procedure(const Pass: Byte; Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte) of object; begin CopyProc := nil; {Initialize} {Determine method to copy the image data} case Header.ColorType of {R, G, B values for each pixel} COLOR_RGB: case Header.BitDepth of 8: CopyProc := CopyInterlacedRGB8; 16: CopyProc := CopyInterlacedRGB16; end {case Header.BitDepth}; {Palette} COLOR_PALETTE, COLOR_GRAYSCALE: case Header.BitDepth of 1, 4, 8: CopyProc := CopyInterlacedPalette148; 2 : if Header.ColorType = COLOR_PALETTE then CopyProc := CopyInterlacedPalette2 else CopyProc := CopyInterlacedGray2; 16 : CopyProc := CopyInterlacedGrayscale16; end; {RGB followed by alpha} COLOR_RGBALPHA: case Header.BitDepth of 8: CopyProc := CopyInterlacedRGBAlpha8; 16: CopyProc := CopyInterlacedRGBAlpha16; end; {Grayscale followed by alpha} COLOR_GRAYSCALEALPHA: case Header.BitDepth of 8: CopyProc := CopyInterlacedGrayscaleAlpha8; 16: CopyProc := CopyInterlacedGrayscaleAlpha16; end; end {case Header.ColorType}; {Adam7 method has 7 passes to make the final image} FOR CurrentPass := 0 TO 6 DO begin {Calculates the number of pixels and bytes for this pass row} PixelsThisRow := (ImageWidth - ColumnStart[CurrentPass] + ColumnIncrement[CurrentPass] - 1) div ColumnIncrement[CurrentPass]; Row_Bytes := BytesForPixels(PixelsThisRow, Header.ColorType, Header.BitDepth); {Clear buffer for this pass} ZeroMemory(Row_Buffer[not RowUsed], Row_Bytes); {Get current row index} CurrentRow := RowStart[CurrentPass]; {Get a pointer to the current row image data} Data := Ptr(LongInt(Header.FImageData) + Header.BytesPerRow * (ImageHeight - 1 - CurrentRow)); Trans := Ptr(LongInt(Header.FImageAlpha) + ImageWidth * CurrentRow); {$IFDEF Store16bits} Extra := Ptr(LongInt(Header.FExtraImageData) + Header.BytesPerRow * (ImageHeight - 1 - CurrentRow)); {$ENDIF} if Row_Bytes > 0 then {There must have bytes for this interlaced pass} while CurrentRow < ImageHeight do begin {Reads this line and filter} if IDATZlibRead(ZLIBStream, @Row_Buffer[RowUsed][0], Row_Bytes + 1, EndPos, CRCFile) = 0 then break; FilterRow; {Copy image data} CopyProc(CurrentPass, @Row_Buffer[RowUsed][1], Data, Trans {$IFDEF Store16bits}, Extra{$ENDIF}); {Use the other RowBuffer item} RowUsed := not RowUsed; {Move to the next row} inc(CurrentRow, RowIncrement[CurrentPass]); {Move pointer to the next line} dec(Data, RowIncrement[CurrentPass] * Header.BytesPerRow); inc(Trans, RowIncrement[CurrentPass] * ImageWidth); {$IFDEF Store16bits} dec(Extra, RowIncrement[CurrentPass] * Header.BytesPerRow); {$ENDIF} end {while CurrentRow < ImageHeight}; end {FOR CurrentPass}; end; {Copy 8 bits RGB image} procedure TChunkIDAT.CopyNonInterlacedRGB8( Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var I: Integer; begin FOR I := 1 TO ImageWidth DO begin {Copy pixel values} PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 2)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 1)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) )^]; inc(Dest); {Move to next pixel} inc(Src, 3); end {for I} end; {Copy 16 bits RGB image} procedure TChunkIDAT.CopyNonInterlacedRGB16( Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var I: Integer; begin FOR I := 1 TO ImageWidth DO begin //Since windows does not supports 2 bytes for //each R, G, B value, the method will read only 1 byte from it {Copy pixel values} PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 4)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 2)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) )^]; inc(Dest); {$IFDEF Store16bits} {Copy extra pixel values} PByte(Extra)^ := FOwner.GammaTable[pByte(LongInt(Src) + 5)^]; inc(Extra); PByte(Extra)^ := FOwner.GammaTable[pByte(LongInt(Src) + 3)^]; inc(Extra); PByte(Extra)^ := FOwner.GammaTable[pByte(LongInt(Src) + 1)^]; inc(Extra); {$ENDIF} {Move to next pixel} inc(Src, 6); end {for I} end; {Copy types using palettes (1, 4 or 8 bits per pixel)} procedure TChunkIDAT.CopyNonInterlacedPalette148( Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); begin {It's simple as copying the data} CopyMemory(Dest, Src, Row_Bytes); end; {Copy grayscale types using 2 bits for each pixel} procedure TChunkIDAT.CopyNonInterlacedGray2( Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var i: Integer; begin {2 bits is not supported, this routine will converted into 4 bits} FOR i := 1 TO Row_Bytes do begin PByte(Dest)^ := ((Byte(Src^) shr 2) and $F) or ((Byte(Src^)) and $F0); inc(Dest); PByte(Dest)^ := ((Byte(Src^) shl 2) and $F) or ((Byte(Src^) shl 4) and $F0); inc(Dest); inc(Src); end {FOR i} end; {Copy types using palette with 2 bits for each pixel} procedure TChunkIDAT.CopyNonInterlacedPalette2( Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var i: Integer; begin {2 bits is not supported, this routine will converted into 4 bits} FOR i := 1 TO Row_Bytes do begin PByte(Dest)^ := ((Byte(Src^) shr 4) and $3) or ((Byte(Src^) shr 2) and $30); inc(Dest); PByte(Dest)^ := (Byte(Src^) and $3) or ((Byte(Src^) shl 2) and $30); inc(Dest); inc(Src); end {FOR i} end; {Copy grayscale images with 16 bits} procedure TChunkIDAT.CopyNonInterlacedGrayscale16( Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var I: Integer; begin FOR I := 1 TO ImageWidth DO begin {Windows does not supports 16 bits for each pixel in grayscale} {mode, so reduce to 8} Dest^ := Src^; inc(Dest); {$IFDEF Store16bits} Extra^ := pByte(LongInt(Src) + 1)^; inc(Extra); {$ENDIF} {Move to next pixel} inc(Src, 2); end {for I} end; {Copy 8 bits per sample RGB images followed by an alpha byte} procedure TChunkIDAT.CopyNonInterlacedRGBAlpha8( Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var i: Integer; begin FOR I := 1 TO ImageWidth DO begin {Copy pixel values and transparency} Trans^ := pByte(LongInt(Src) + 3)^; PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 2)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 1)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) )^]; inc(Dest); {Move to next pixel} inc(Src, 4); inc(Trans); end {for I} end; {Copy 16 bits RGB image with alpha using 2 bytes for each sample} procedure TChunkIDAT.CopyNonInterlacedRGBAlpha16( Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var I: Integer; begin FOR I := 1 TO ImageWidth DO begin //Copy rgb and alpha values (transforming from 16 bits to 8 bits) {Copy pixel values} Trans^ := pByte(LongInt(Src) + 6)^; PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 4)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) + 2)^]; inc(Dest); PByte(Dest)^ := FOwner.GammaTable[pByte(LongInt(Src) )^]; inc(Dest); {$IFDEF Store16bits} {Copy extra pixel values} PByte(Extra)^ := FOwner.GammaTable[pByte(LongInt(Src) + 5)^]; inc(Extra); PByte(Extra)^ := FOwner.GammaTable[pByte(LongInt(Src) + 3)^]; inc(Extra); PByte(Extra)^ := FOwner.GammaTable[pByte(LongInt(Src) + 1)^]; inc(Extra); {$ENDIF} {Move to next pixel} inc(Src, 8); inc(Trans); end {for I} end; {Copy 8 bits per sample grayscale followed by alpha} procedure TChunkIDAT.CopyNonInterlacedGrayscaleAlpha8( Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var I: Integer; begin FOR I := 1 TO ImageWidth DO begin {Copy alpha value and then gray value} Dest^ := Src^; inc(Src); Trans^ := Src^; inc(Src); inc(Dest); inc(Trans); end; end; {Copy 16 bits per sample grayscale followed by alpha} procedure TChunkIDAT.CopyNonInterlacedGrayscaleAlpha16( Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte); var I: Integer; begin FOR I := 1 TO ImageWidth DO begin {Copy alpha value and then gray value} {$IFDEF Store16bits} Extra^ := pByte(LongInt(Src) + 1)^; inc(Extra); {$ENDIF} Dest^ := Src^; inc(Src, 2); Trans^ := Src^; inc(Src, 2); inc(Dest); inc(Trans); end; end; {Decode non interlaced image} procedure TChunkIDAT.DecodeNonInterlaced(Stream: IStream; var ZLIBStream: TZStreamRec2; const Size: Integer; var crcfile: Cardinal); var j: Cardinal; Trans, Data{$IFDEF Store16bits}, Extra{$ENDIF}: pByte; CopyProc: procedure( Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pByte) of object; begin CopyProc := nil; {Initialize} {Determines the method to copy the image data} case Header.ColorType of {R, G, B values} COLOR_RGB: case Header.BitDepth of 8: CopyProc := CopyNonInterlacedRGB8; 16: CopyProc := CopyNonInterlacedRGB16; end; {Types using palettes} COLOR_PALETTE, COLOR_GRAYSCALE: case Header.BitDepth of 1, 4, 8: CopyProc := CopyNonInterlacedPalette148; 2 : if Header.ColorType = COLOR_PALETTE then CopyProc := CopyNonInterlacedPalette2 else CopyProc := CopyNonInterlacedGray2; 16 : CopyProc := CopyNonInterlacedGrayscale16; end; {R, G, B followed by alpha} COLOR_RGBALPHA: case Header.BitDepth of 8 : CopyProc := CopyNonInterlacedRGBAlpha8; 16 : CopyProc := CopyNonInterlacedRGBAlpha16; end; {Grayscale followed by alpha} COLOR_GRAYSCALEALPHA: case Header.BitDepth of 8 : CopyProc := CopyNonInterlacedGrayscaleAlpha8; 16 : CopyProc := CopyNonInterlacedGrayscaleAlpha16; end; end; {Get the image data pointer} LongInt(Data) := LongInt(Header.FImageData) + Header.BytesPerRow * (ImageHeight - 1); Trans := Header.FImageAlpha; {$IFDEF Store16bits} LongInt(Extra) := LongInt(Header.FExtraImageData) + Header.BytesPerRow * (ImageHeight - 1); {$ENDIF} {Reads each line} FOR j := 0 to ImageHeight - 1 do begin {Read this line Row_Buffer[RowUsed][0] if the filter type for this line} if IDATZlibRead(ZLIBStream, @Row_Buffer[RowUsed][0], Row_Bytes + 1, EndPos, CRCFile) = 0 then break; {Filter the current row} FilterRow; {Copies non interlaced row to image} CopyProc(@Row_Buffer[RowUsed][1], Data, Trans{$IFDEF Store16bits}, Extra {$ENDIF}); {Invert line used} RowUsed := not RowUsed; dec(Data, Header.BytesPerRow); {$IFDEF Store16bits}dec(Extra, Header.BytesPerRow);{$ENDIF} inc(Trans, ImageWidth); end {for I}; end; {Filter the current line} procedure TChunkIDAT.FilterRow; var pp: Byte; vv, left, above, aboveleft: Integer; Col: Cardinal; begin {Test the filter} case Row_Buffer[RowUsed]^[0] of {No filtering for this line} FILTER_NONE: begin end; {AND 255 serves only to never let the Result be larger than one byte} {Sub filter} FILTER_SUB: FOR Col := Offset + 1 to Row_Bytes DO Row_Buffer[RowUsed][Col] := (Row_Buffer[RowUsed][Col] + Row_Buffer[RowUsed][Col - Offset]) and 255; {Up filter} FILTER_UP: FOR Col := 1 to Row_Bytes DO Row_Buffer[RowUsed][Col] := (Row_Buffer[RowUsed][Col] + Row_Buffer[not RowUsed][Col]) and 255; {Average filter} FILTER_AVERAGE: FOR Col := 1 to Row_Bytes DO begin {Obtains up and left pixels} above := Row_Buffer[not RowUsed][Col]; if col - 1 < Offset then left := 0 else Left := Row_Buffer[RowUsed][Col - Offset]; {Calculates} Row_Buffer[RowUsed][Col] := (Row_Buffer[RowUsed][Col] + (left + above) div 2) and 255; end; {Paeth filter} FILTER_PAETH: begin {Initialize} left := 0; aboveleft := 0; {Test each byte} FOR Col := 1 to Row_Bytes DO begin {Obtains above pixel} above := Row_Buffer[not RowUsed][Col]; {Obtains left and top-left pixels} if (col - 1 >= offset) Then begin left := row_buffer[RowUsed][col - offset]; aboveleft := row_buffer[not RowUsed][col - offset]; end; {Obtains current pixel and paeth predictor} vv := row_buffer[RowUsed][Col]; pp := PaethPredictor(left, above, aboveleft); {Calculates} Row_Buffer[RowUsed][Col] := (pp + vv) and $FF; end {for}; end; end {case}; end; {Reads the image data from the stream} function TChunkIDAT.LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; var ZLIBStream: TZStreamRec2; CRCCheck, CRCFile : Cardinal; begin {Get pointer to the header chunk} Header := Owner.Chunks.Item[0] as TChunkIHDR; {Build palette if necessary} if Header.FHasPalette then PreparePalette(); {Copy image width and height} ImageWidth := Header.Width; ImageHeight := Header.Height; {Initialize to calculate CRC} {$IFDEF CheckCRC} CRCFile := update_crc($ffffffff, @ChunkName[0], SizeOf(ChunkName)); {$ENDIF} Owner.GetPixelInfo(Row_Bytes, Offset); {Obtain line information} ZLIBStream := ZLIBInitInflate(Stream); {Initializes decompression} {Calculate ending position for the current IDAT chunk} EndPos := GMIStreamPos(Stream) + Size; {Allocate memory} GetMem(Row_Buffer[false], Row_Bytes + 1); GetMem(Row_Buffer[true], Row_Bytes + 1); ZeroMemory(Row_Buffer[false], Row_bytes + 1); {Set the variable to alternate the Row_Buffer item to use} RowUsed := TRUE; {Call special methods for the different interlace methods} case Owner.InterlaceMethod of imNone: DecodeNonInterlaced(stream, ZLIBStream, Size, crcfile); imAdam7: DecodeInterlacedAdam7(stream, ZLIBStream, size, crcfile); end; {Free memory} ZLIBTerminateInflate(ZLIBStream); {Terminates decompression} FreeMem(Row_Buffer[False], Row_Bytes + 1); FreeMem(Row_Buffer[True], Row_Bytes + 1); {Now checks CRC} GMSafeIStreamRead(Stream, @CRCCheck, SizeOf(CRCCheck)); {$IFDEF CheckCRC} CRCFile := CRCFile xor $ffffffff; CRCCheck := ByteSwap(CRCCheck); Result := CRCCheck = CRCFile; {Handle CRC error} if not Result then begin {In case it coult not load chunk} Owner.RaiseError(EPngInvalidCRC, EPngInvalidCRCText); Exit; end; {$ELSE}Result := TRUE; {$ENDIF} end; const IDATHeader: array[0..3] of AnsiChar = ('I', 'D', 'A', 'T'); BUFFER = 5; {Saves the IDAT chunk to a stream} function TChunkIDAT.SaveToStream(Stream: IStream): Boolean; var ZLIBStream : TZStreamRec2; begin {Get pointer to the header chunk} Header := Owner.Chunks.Item[0] as TChunkIHDR; {Copy image width and height} ImageWidth := Header.Width; ImageHeight := Header.Height; Owner.GetPixelInfo(Row_Bytes, Offset); {Obtain line information} {Allocate memory} GetMem(Encode_Buffer[BUFFER], Row_Bytes); ZeroMemory(Encode_Buffer[BUFFER], Row_Bytes); {Allocate buffers for the filters selected} {Filter none will always be calculated to the other filters to work} GetMem(Encode_Buffer[FILTER_NONE], Row_Bytes); ZeroMemory(Encode_Buffer[FILTER_NONE], Row_Bytes); if pfSub in Owner.Filters then GetMem(Encode_Buffer[FILTER_SUB], Row_Bytes); if pfUp in Owner.Filters then GetMem(Encode_Buffer[FILTER_UP], Row_Bytes); if pfAverage in Owner.Filters then GetMem(Encode_Buffer[FILTER_AVERAGE], Row_Bytes); if pfPaeth in Owner.Filters then GetMem(Encode_Buffer[FILTER_PAETH], Row_Bytes); {Initialize ZLIB} ZLIBStream := ZLIBInitDeflate(Stream, Owner.FCompressionLevel, Owner.MaxIdatSize); {Write data depending on the interlace method} case Owner.InterlaceMethod of imNone: EncodeNonInterlaced(stream, ZLIBStream); imAdam7: EncodeInterlacedAdam7(stream, ZLIBStream); end; {Terminates ZLIB} ZLIBTerminateDeflate(ZLIBStream); {Release allocated memory} FreeMem(Encode_Buffer[BUFFER], Row_Bytes); FreeMem(Encode_Buffer[FILTER_NONE], Row_Bytes); if pfSub in Owner.Filters then FreeMem(Encode_Buffer[FILTER_SUB], Row_Bytes); if pfUp in Owner.Filters then FreeMem(Encode_Buffer[FILTER_UP], Row_Bytes); if pfAverage in Owner.Filters then FreeMem(Encode_Buffer[FILTER_AVERAGE], Row_Bytes); if pfPaeth in Owner.Filters then FreeMem(Encode_Buffer[FILTER_PAETH], Row_Bytes); {Everything went ok} Result := True; end; {Writes the IDAT using the settings} procedure WriteIDAT(Stream: IStream; Data: Pointer; const Length: Cardinal); var ChunkLen, CRC: Cardinal; begin {Writes IDAT header} ChunkLen := ByteSwap(Length); GMSafeIStreamWrite(Stream, @ChunkLen, SizeOf(ChunkLen)); {Chunk length} GMSafeIStreamWrite(Stream, @IDATHeader[0], SizeOf(IDATHeader)); {Idat header} CRC := update_crc($ffffffff, @IDATHeader[0], SizeOf(IDATHeader)); {Crc part for header} {Writes IDAT data and calculates CRC for data} GMSafeIStreamWrite(Stream, Data, Length); CRC := Byteswap(update_crc(CRC, Data, Length) xor $ffffffff); {Writes final CRC} GMSafeIStreamWrite(Stream, @CRC, SizeOf(CRC)); end; {Compress and writes IDAT chunk data} procedure TChunkIDAT.IDATZlibWrite(var ZLIBStream: TZStreamRec2; Buffer: Pointer; const Length: Cardinal); begin with ZLIBStream, ZLIBStream.ZLIB do begin {Set data to be compressed} next_in := Buffer; avail_in := Length; {Compress all the data avaliable to compress} while avail_in > 0 do begin deflate(ZLIB, Z_NO_FLUSH); {The whole buffer was used, save data to stream and restore buffer} if avail_out = 0 then begin {Writes this IDAT chunk} WriteIDAT(fStream, Data, Owner.MaxIdatSize); {Restore buffer} next_out := Data; avail_out := Owner.MaxIdatSize; end {if avail_out = 0}; end {while avail_in}; end {with ZLIBStream, ZLIBStream.ZLIB} end; {Finishes compressing data to write IDAT chunk} procedure TChunkIDAT.FinishIDATZlib(var ZLIBStream: TZStreamRec2); begin with ZLIBStream, ZLIBStream.ZLIB do begin {Set data to be compressed} next_in := nil; avail_in := 0; while deflate(ZLIB,Z_FINISH) <> Z_STREAM_END do begin {Writes this IDAT chunk} WriteIDAT(fStream, Data, Owner.MaxIdatSize - avail_out); {Re-update buffer} next_out := Data; avail_out := Owner.MaxIdatSize; end; if avail_out < Owner.MaxIdatSize then {Writes final IDAT} WriteIDAT(fStream, Data, Owner.MaxIdatSize - avail_out); end {with ZLIBStream, ZLIBStream.ZLIB}; end; {Copy memory to encode RGB image with 1 byte for each color sample} procedure TChunkIDAT.EncodeNonInterlacedRGB8(Src, Dest, Trans: pByte); var I: Integer; begin FOR I := 1 TO ImageWidth DO begin {Copy pixel values} PByte(Dest)^ := FOwner.FInverseGamma[pByte(LongInt(Src) + 2)^]; inc(Dest); PByte(Dest)^ := FOwner.FInverseGamma[pByte(LongInt(Src) + 1)^]; inc(Dest); PByte(Dest)^ := FOwner.FInverseGamma[pByte(LongInt(Src) )^]; inc(Dest); {Move to next pixel} inc(Src, 3); end {for I} end; {Copy memory to encode RGB images with 16 bits for each color sample} procedure TChunkIDAT.EncodeNonInterlacedRGB16(Src, Dest, Trans: pByte); var I: Integer; begin FOR I := 1 TO ImageWidth DO begin //Now we copy from 1 byte for each sample stored to a 2 bytes (or 1 word) //for sample {Copy pixel values} pWORD(Dest)^ := FOwner.FInverseGamma[pByte(LongInt(Src) + 2)^]; inc(Dest, 2); pWORD(Dest)^ := FOwner.FInverseGamma[pByte(LongInt(Src) + 1)^]; inc(Dest, 2); pWORD(Dest)^ := FOwner.FInverseGamma[pByte(LongInt(Src) )^]; inc(Dest, 2); {Move to next pixel} inc(Src, 3); end {for I} end; {Copy memory to encode types using palettes (1, 4 or 8 bits per pixel)} procedure TChunkIDAT.EncodeNonInterlacedPalette148(Src, Dest, Trans: pByte); begin {It's simple as copying the data} CopyMemory(Dest, Src, Row_Bytes); end; {Copy memory to encode grayscale images with 2 bytes for each sample} procedure TChunkIDAT.EncodeNonInterlacedGrayscale16(Src, Dest, Trans: pByte); var I: Integer; begin FOR I := 1 TO ImageWidth DO begin //Now we copy from 1 byte for each sample stored to a 2 bytes (or 1 word) //for sample pWORD(Dest)^ := pByte(LongInt(Src))^; inc(Dest, 2); {Move to next pixel} inc(Src); end {for I} end; {Encode images using RGB followed by an alpha value using 1 byte for each} procedure TChunkIDAT.EncodeNonInterlacedRGBAlpha8(Src, Dest, Trans: pByte); var i: Integer; begin {Copy the data to the destination, including data from Trans pointer} FOR i := 1 TO ImageWidth do begin PByte(Dest)^ := Owner.FInverseGamma[PByte(LongInt(Src) + 2)^]; inc(Dest); PByte(Dest)^ := Owner.FInverseGamma[PByte(LongInt(Src) + 1)^]; inc(Dest); PByte(Dest)^ := Owner.FInverseGamma[PByte(LongInt(Src) )^]; inc(Dest); Dest^ := Trans^; inc(Dest); inc(Src, 3); inc(Trans); end {for i}; end; {Encode images using RGB followed by an alpha value using 2 byte for each} procedure TChunkIDAT.EncodeNonInterlacedRGBAlpha16(Src, Dest, Trans: pByte); var i: Integer; begin {Copy the data to the destination, including data from Trans pointer} FOR i := 1 TO ImageWidth do begin pWord(Dest)^ := Owner.FInverseGamma[PByte(LongInt(Src) + 2)^]; inc(Dest, 2); pWord(Dest)^ := Owner.FInverseGamma[PByte(LongInt(Src) + 1)^]; inc(Dest, 2); pWord(Dest)^ := Owner.FInverseGamma[PByte(LongInt(Src) )^]; inc(Dest, 2); pWord(Dest)^ := PByte(LongInt(Trans) )^; inc(Dest, 2); inc(Src, 3); inc(Trans); end {for i}; end; {Encode grayscale images followed by an alpha value using 1 byte for each} procedure TChunkIDAT.EncodeNonInterlacedGrayscaleAlpha8( Src, Dest, Trans: pByte); var i: Integer; begin {Copy the data to the destination, including data from Trans pointer} FOR i := 1 TO ImageWidth do begin Dest^ := Src^; inc(Dest); Dest^ := Trans^; inc(Dest); inc(Src); inc(Trans); end {for i}; end; {Encode grayscale images followed by an alpha value using 2 byte for each} procedure TChunkIDAT.EncodeNonInterlacedGrayscaleAlpha16( Src, Dest, Trans: pByte); var i: Integer; begin {Copy the data to the destination, including data from Trans pointer} FOR i := 1 TO ImageWidth do begin pWord(Dest)^ := pByte(Src)^; inc(Dest, 2); pWord(Dest)^ := pByte(Trans)^; inc(Dest, 2); inc(Src); inc(Trans); end {for i}; end; {Encode non interlaced images} procedure TChunkIDAT.EncodeNonInterlaced(Stream: IStream; var ZLIBStream: TZStreamRec2); var {Current line} j: Cardinal; {Pointers to image data} Data, Trans: pByte; {Filter used for this line} Filter: Byte; {Method which will copy the data into the buffer} CopyProc: procedure(Src, Dest, Trans: pByte) of object; begin CopyProc := nil; {Initialize to avoid warnings} {Defines the method to copy the data to the buffer depending on} {the image parameters} case Header.ColorType of {R, G, B values} COLOR_RGB: case Header.BitDepth of 8: CopyProc := EncodeNonInterlacedRGB8; 16: CopyProc := EncodeNonInterlacedRGB16; end; {Palette and grayscale values} COLOR_GRAYSCALE, COLOR_PALETTE: case Header.BitDepth of 1, 4, 8: CopyProc := EncodeNonInterlacedPalette148; 16: CopyProc := EncodeNonInterlacedGrayscale16; end; {RGB with a following alpha value} COLOR_RGBALPHA: case Header.BitDepth of 8: CopyProc := EncodeNonInterlacedRGBAlpha8; 16: CopyProc := EncodeNonInterlacedRGBAlpha16; end; {Grayscale images followed by an alpha} COLOR_GRAYSCALEALPHA: case Header.BitDepth of 8: CopyProc := EncodeNonInterlacedGrayscaleAlpha8; 16: CopyProc := EncodeNonInterlacedGrayscaleAlpha16; end; end {case Header.ColorType}; {Get the image data pointer} LongInt(Data) := LongInt(Header.FImageData) + Header.BytesPerRow * (ImageHeight - 1); Trans := Header.FImageAlpha; {Writes each line} FOR j := 0 to ImageHeight - 1 do begin {Copy data into buffer} CopyProc(Data, @Encode_Buffer[BUFFER][0], Trans); {Filter data} Filter := FilterToEncode; {Compress data} IDATZlibWrite(ZLIBStream, @Filter, 1); IDATZlibWrite(ZLIBStream, @Encode_Buffer[Filter][0], Row_Bytes); {Adjust pointers to the actual image data} dec(Data, Header.BytesPerRow); inc(Trans, ImageWidth); end; {Compress and finishes copying the remaining data} FinishIDATZlib(ZLIBStream); end; {Copy memory to encode interlaced images using RGB value with 1 byte for} {each color sample} procedure TChunkIDAT.EncodeInterlacedRGB8(const Pass: Byte; Src, Dest, Trans: pByte); var Col: Integer; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; Src := pByte(LongInt(Src) + Col * 3); repeat {Copy this row} PByte(Dest)^ := FOwner.FInverseGamma[pByte(LongInt(Src) + 2)^]; inc(Dest); PByte(Dest)^ := FOwner.FInverseGamma[pByte(LongInt(Src) + 1)^]; inc(Dest); PByte(Dest)^ := FOwner.FInverseGamma[pByte(LongInt(Src) )^]; inc(Dest); {Move to next column} inc(Src, ColumnIncrement[Pass] * 3); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Copy memory to encode interlaced RGB images with 2 bytes each color sample} procedure TChunkIDAT.EncodeInterlacedRGB16(const Pass: Byte; Src, Dest, Trans: pByte); var Col: Integer; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; Src := pByte(LongInt(Src) + Col * 3); repeat {Copy this row} pWord(Dest)^ := Owner.FInverseGamma[pByte(LongInt(Src) + 2)^]; inc(Dest, 2); pWord(Dest)^ := Owner.FInverseGamma[pByte(LongInt(Src) + 1)^]; inc(Dest, 2); pWord(Dest)^ := Owner.FInverseGamma[pByte(LongInt(Src) )^]; inc(Dest, 2); {Move to next column} inc(Src, ColumnIncrement[Pass] * 3); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Copy memory to encode interlaced images using palettes using bit depths} {1, 4, 8 (each pixel in the image)} procedure TChunkIDAT.EncodeInterlacedPalette148(const Pass: Byte; Src, Dest, Trans: pByte); const BitTable: array[1..8] of Integer = ($1, $3, 0, $F, 0, 0, 0, $FF); StartBit: array[1..8] of Integer = (7 , 0 , 0, 4, 0, 0, 0, 0); var CurBit, Col: Integer; Src2: pByte; begin {Clean the line} fillchar(Dest^, Row_Bytes, #0); {Get first column and enter in loop} Col := ColumnStart[Pass]; with Header.FBitmapInfo.bmiHeader do repeat {Copy data} CurBit := StartBit[biBitCount]; repeat {Adjust pointer to pixel byte bounds} Src2 := pByte(LongInt(Src) + (biBitCount * Col) div 8); {Copy data} PByte(Dest)^ := Byte(Dest^) or (((Byte(Src2^) shr (StartBit[Header.BitDepth] - (biBitCount * Col) mod 8))) and (BitTable[biBitCount])) shl CurBit; {Move to next column} inc(Col, ColumnIncrement[Pass]); {Will read next bits} dec(CurBit, biBitCount); until CurBit < 0; {Move to next byte in source} inc(Dest); until Col >= ImageWidth; end; {Copy to encode interlaced grayscale images using 16 bits for each sample} procedure TChunkIDAT.EncodeInterlacedGrayscale16(const Pass: Byte; Src, Dest, Trans: pByte); var Col: Integer; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; Src := pByte(LongInt(Src) + Col); repeat {Copy this row} pWord(Dest)^ := Byte(Src^); inc(Dest, 2); {Move to next column} inc(Src, ColumnIncrement[Pass]); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Copy to encode interlaced rgb images followed by an alpha value, all using} {one byte for each sample} procedure TChunkIDAT.EncodeInterlacedRGBAlpha8(const Pass: Byte; Src, Dest, Trans: pByte); var Col: Integer; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; Src := pByte(LongInt(Src) + Col * 3); Trans := pByte(LongInt(Trans) + Col); repeat {Copy this row} PByte(Dest)^ := Owner.FInverseGamma[pByte(LongInt(Src) + 2)^]; inc(Dest); PByte(Dest)^ := Owner.FInverseGamma[pByte(LongInt(Src) + 1)^]; inc(Dest); PByte(Dest)^ := Owner.FInverseGamma[pByte(LongInt(Src) )^]; inc(Dest); Dest^ := Trans^; inc(Dest); {Move to next column} inc(Src, ColumnIncrement[Pass] * 3); inc(Trans, ColumnIncrement[Pass]); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Copy to encode interlaced rgb images followed by an alpha value, all using} {two byte for each sample} procedure TChunkIDAT.EncodeInterlacedRGBAlpha16(const Pass: Byte; Src, Dest, Trans: pByte); var Col: Integer; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; Src := pByte(LongInt(Src) + Col * 3); Trans := pByte(LongInt(Trans) + Col); repeat {Copy this row} pWord(Dest)^ := pByte(LongInt(Src) + 2)^; inc(Dest, 2); pWord(Dest)^ := pByte(LongInt(Src) + 1)^; inc(Dest, 2); pWord(Dest)^ := pByte(LongInt(Src) )^; inc(Dest, 2); pWord(Dest)^ := pByte(Trans)^; inc(Dest, 2); {Move to next column} inc(Src, ColumnIncrement[Pass] * 3); inc(Trans, ColumnIncrement[Pass]); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Copy to encode grayscale interlaced images followed by an alpha value, all} {using 1 byte for each sample} procedure TChunkIDAT.EncodeInterlacedGrayscaleAlpha8(const Pass: Byte; Src, Dest, Trans: pByte); var Col: Integer; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; Src := pByte(LongInt(Src) + Col); Trans := pByte(LongInt(Trans) + Col); repeat {Copy this row} Dest^ := Src^; inc(Dest); Dest^ := Trans^; inc(Dest); {Move to next column} inc(Src, ColumnIncrement[Pass]); inc(Trans, ColumnIncrement[Pass]); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Copy to encode grayscale interlaced images followed by an alpha value, all} {using 2 bytes for each sample} procedure TChunkIDAT.EncodeInterlacedGrayscaleAlpha16(const Pass: Byte; Src, Dest, Trans: pByte); var Col: Integer; begin {Get first column and enter in loop} Col := ColumnStart[Pass]; Src := pByte(LongInt(Src) + Col); Trans := pByte(LongInt(Trans) + Col); repeat {Copy this row} pWord(Dest)^ := pByte(Src)^; inc(Dest, 2); pWord(Dest)^ := pByte(Trans)^; inc(Dest, 2); {Move to next column} inc(Src, ColumnIncrement[Pass]); inc(Trans, ColumnIncrement[Pass]); inc(Col, ColumnIncrement[Pass]); until Col >= ImageWidth; end; {Encode interlaced images} procedure TChunkIDAT.EncodeInterlacedAdam7(Stream: IStream; var ZLIBStream: TZStreamRec2); var CurrentPass, Filter: Byte; PixelsThisRow: Integer; CurrentRow : Integer; Trans, Data: pByte; CopyProc: procedure(const Pass: Byte; Src, Dest, Trans: pByte) of object; begin CopyProc := nil; {Initialize to avoid warnings} {Defines the method to copy the data to the buffer depending on} {the image parameters} case Header.ColorType of {R, G, B values} COLOR_RGB: case Header.BitDepth of 8: CopyProc := EncodeInterlacedRGB8; 16: CopyProc := EncodeInterlacedRGB16; end; {Grayscale and palette} COLOR_PALETTE, COLOR_GRAYSCALE: case Header.BitDepth of 1, 4, 8: CopyProc := EncodeInterlacedPalette148; 16: CopyProc := EncodeInterlacedGrayscale16; end; {RGB followed by alpha} COLOR_RGBALPHA: case Header.BitDepth of 8: CopyProc := EncodeInterlacedRGBAlpha8; 16: CopyProc := EncodeInterlacedRGBAlpha16; end; COLOR_GRAYSCALEALPHA: {Grayscale followed by alpha} case Header.BitDepth of 8: CopyProc := EncodeInterlacedGrayscaleAlpha8; 16: CopyProc := EncodeInterlacedGrayscaleAlpha16; end; end {case Header.ColorType}; {Compress the image using the seven passes for ADAM 7} FOR CurrentPass := 0 TO 6 DO begin {Calculates the number of pixels and bytes for this pass row} PixelsThisRow := (ImageWidth - ColumnStart[CurrentPass] + ColumnIncrement[CurrentPass] - 1) div ColumnIncrement[CurrentPass]; Row_Bytes := BytesForPixels(PixelsThisRow, Header.ColorType, Header.BitDepth); ZeroMemory(Encode_Buffer[FILTER_NONE], Row_Bytes); {Get current row index} CurrentRow := RowStart[CurrentPass]; {Get a pointer to the current row image data} Data := Ptr(LongInt(Header.FImageData) + Header.BytesPerRow * (ImageHeight - 1 - CurrentRow)); Trans := Ptr(LongInt(Header.FImageAlpha) + ImageWidth * CurrentRow); {Process all the image rows} if Row_Bytes > 0 then while CurrentRow < ImageHeight do begin {Copy data into buffer} CopyProc(CurrentPass, Data, @Encode_Buffer[BUFFER][0], Trans); {Filter data} Filter := FilterToEncode; {Compress data} IDATZlibWrite(ZLIBStream, @Filter, 1); IDATZlibWrite(ZLIBStream, @Encode_Buffer[Filter][0], Row_Bytes); {Move to the next row} inc(CurrentRow, RowIncrement[CurrentPass]); {Move pointer to the next line} dec(Data, RowIncrement[CurrentPass] * Header.BytesPerRow); inc(Trans, RowIncrement[CurrentPass] * ImageWidth); end {while CurrentRow < ImageHeight} end {CurrentPass}; {Compress and finishes copying the remaining data} FinishIDATZlib(ZLIBStream); end; {Filters the row to be encoded and returns the best filter} function TChunkIDAT.FilterToEncode: Byte; var Run, LongestRun, ii, jj: Cardinal; Last, Above, LastAbove: Byte; begin {Selecting more filters using the Filters property from TGMPngImage} {increases the chances to the file be much smaller, but decreases} {the performace} {This method will creates the same line data using the different} {filter methods and select the best} {Sub-filter} if pfSub in Owner.Filters then for ii := 0 to Row_Bytes - 1 do begin {There is no previous pixel when it's on the first pixel, so} {set last as zero when in the first} if (ii >= Offset) then last := Encode_Buffer[BUFFER]^[ii - Offset] else last := 0; Encode_Buffer[FILTER_SUB]^[ii] := Encode_Buffer[BUFFER]^[ii] - last; end; {Up filter} if pfUp in Owner.Filters then for ii := 0 to Row_Bytes - 1 do Encode_Buffer[FILTER_UP]^[ii] := Encode_Buffer[BUFFER]^[ii] - Encode_Buffer[FILTER_NONE]^[ii]; {Average filter} if pfAverage in Owner.Filters then for ii := 0 to Row_Bytes - 1 do begin {Get the previous pixel, if the current pixel is the first, the} {previous is considered to be 0} if (ii >= Offset) then last := Encode_Buffer[BUFFER]^[ii - Offset] else last := 0; {Get the pixel above} above := Encode_Buffer[FILTER_NONE]^[ii]; {Calculates formula to the average pixel} Encode_Buffer[FILTER_AVERAGE]^[ii] := Encode_Buffer[BUFFER]^[ii] - (above + last) div 2 ; end; {Paeth filter (the slower)} if pfPaeth in Owner.Filters then begin {Initialize} last := 0; lastabove := 0; for ii := 0 to Row_Bytes - 1 do begin {In case this pixel is not the first in the line obtains the} {previous one and the one above the previous} if (ii >= Offset) then begin last := Encode_Buffer[BUFFER]^[ii - Offset]; lastabove := Encode_Buffer[FILTER_NONE]^[ii - Offset]; end; {Obtains the pixel above} above := Encode_Buffer[FILTER_NONE]^[ii]; {Calculate paeth filter for this byte} Encode_Buffer[FILTER_PAETH]^[ii] := Encode_Buffer[BUFFER]^[ii] - PaethPredictor(last, above, lastabove); end; end; {Now calculates the same line using no filter, which is necessary} {in order to have data to the filters when the next line comes} CopyMemory(@Encode_Buffer[FILTER_NONE]^[0], @Encode_Buffer[BUFFER]^[0], Row_Bytes); {If only filter none is selected in the filter list, we don't need} {to proceed and further} if (Owner.Filters = [pfNone]) or (Owner.Filters = []) then begin Result := FILTER_NONE; Exit; end {if (Owner.Filters = [pfNone...}; {Check which filter is the best by checking which has the larger} {sequence of the same byte, since they are best compressed} LongestRun := 0; Result := FILTER_NONE; for ii := FILTER_NONE TO FILTER_PAETH do {Check if this filter was selected} if TPNGFilter(ii) in Owner.Filters then begin Run := 0; {Check if it's the only filter} if Owner.Filters = [TPNGFilter(ii)] then begin Result := ii; Exit; end; {Check using a sequence of four bytes} for jj := 2 to Row_Bytes - 1 do if (Encode_Buffer[ii]^[jj] = Encode_Buffer [ii]^[jj-1]) or (Encode_Buffer[ii]^[jj] = Encode_Buffer [ii]^[jj-2]) then inc(Run); {Count the number of sequences} {Check if this one is the best so far} if (Run > LongestRun) then begin Result := ii; LongestRun := Run; end {if (Run > LongestRun)}; end {if TPNGFilter(ii) in Owner.Filters}; end; {TChunkPLTE implementation} {Returns an item in the palette} function TChunkPLTE.GetPaletteItem(Index: Byte): TRGBQuad; begin {Test if item is valid, if not raise error} if Index > Count - 1 then Owner.RaiseError(EPNGError, EPNGUnknownPalEntryText) else {Returns the item} Result := Header.FBitmapInfo.bmiColors[Index]; end; {Loads the palette chunk from a stream} function TChunkPLTE.LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; type pPalEntry = ^PalEntry; PalEntry = record r, g, b: Byte; end; var j : Integer; {For the FOR} PalColor : pPalEntry; palEntries: TMaxLogPalette; begin {Let ancestor load data and check CRC} Result := inherited LoadFromStream(Stream, ChunkName, Size); if not Result then Exit; {This chunk must be divisible by 3 in order to be valid} if (Size mod 3 <> 0) or (Size div 3 > 256) then begin {Raise error} Result := FALSE; Owner.RaiseError(EPNGInvalidPalette, EPNGInvalidPaletteText); Exit; end {if Size mod 3 <> 0}; {Fill array with the palette entries} FCount := Size div 3; Fillchar(palEntries, sizeof(palEntries), #0); palEntries.palVersion := $300; palEntries.palNumEntries := FCount; PalColor := Data; FOR j := 0 TO FCount - 1 DO with palEntries.palPalEntry[j] do begin peRed := Owner.GammaTable[PalColor.r]; peGreen := Owner.GammaTable[PalColor.g]; peBlue := Owner.GammaTable[PalColor.b]; peFlags := 0; {Move to next palette entry} inc(PalColor); end; Owner.SetPalette(CreatePalette(pLogPalette(@palEntries)^)); end; {Saves the PLTE chunk to a stream} function TChunkPLTE.SaveToStream(Stream: IStream): Boolean; var J: Integer; DataPtr: pByte; FBitmapInfo: TMAXBITMAPINFO; palEntries: TMaxLogPalette; begin {Adjust size to hold all the palette items} if FCount = 0 then FCount := Header.FBitmapInfo.bmiHeader.biClrUsed; ResizeData(FCount * 3); {Get all the palette entries} fillchar(palEntries, sizeof(palEntries), #0); GetPaletteEntries(Header.FImagePalette, 0, 256, palEntries.palPalEntry[0]); {Copy pointer to data} DataPtr := FData; {Copy palette items} FBitmapInfo := Header.FBitmapInfo; FOR j := 0 TO FCount - 1 DO with palEntries.palPalEntry[j] do begin DataPtr^ := Owner.FInverseGamma[peRed]; inc(DataPtr); DataPtr^ := Owner.FInverseGamma[peGreen]; inc(DataPtr); DataPtr^ := Owner.FInverseGamma[peBlue]; inc(DataPtr); end {with FBitmapInfo}; {Let ancestor do the rest of the work} Result := inherited SaveToStream(Stream); end; {Assigns from another PLTE chunk} procedure TChunkPLTE.Assign(Source: TChunk); begin {Copy the number of palette items} if Source is TChunkPLTE then FCount := TChunkPLTE(Source).FCount else Owner.RaiseError(EPNGError, EPNGCannotAssignChunkText); end; {TChunkgAMA implementation} {Assigns from another chunk} procedure TChunkgAMA.Assign(Source: TChunk); begin {Copy the gamma value} if Source is TChunkgAMA then Gamma := TChunkgAMA(Source).Gamma else Owner.RaiseError(EPNGError, EPNGCannotAssignChunkText); end; {Gamma chunk being created} constructor TChunkgAMA.Create(Owner: TGMPngImage); begin {Call ancestor} inherited Create(Owner); Gamma := 1; {Initial value} end; {Returns gamma value} function TChunkgAMA.GetValue: Cardinal; begin {Make sure that the size is four bytes} if DataSize <> 4 then begin {Adjust size and returns 1} ResizeData(4); Result := 1; end {If it's right, read the value} else Result := Cardinal(ByteSwap(pCardinal(Data)^)) end; function Power(Base, Exponent: Extended): Extended; begin if Exponent = 0.0 then Result := 1.0 {Math rule} else if (Base = 0) or (Exponent = 0) then Result := 0 else Result := Exp(Exponent * Ln(Base)); end; {Loading the chunk from a stream} function TChunkgAMA.LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; var i: Integer; Value: Cardinal; begin {Call ancestor and test if it went ok} Result := inherited LoadFromStream(Stream, ChunkName, Size); if not Result then Exit; Value := Gamma; {Build gamma table and inverse table for saving} if Value <> 0 then with Owner do FOR i := 0 TO 255 DO begin GammaTable[I] := Round(Power((I / 255), 1 / (Value / 100000 * 2.2)) * 255); FInverseGamma[Round(Power((I / 255), 1 / (Value / 100000 * 2.2)) * 255)] := I; end end; {Sets the gamma value} procedure TChunkgAMA.SetValue(const Value: Cardinal); begin {Make sure that the size is four bytes} if DataSize <> 4 then ResizeData(4); {If it's right, set the value} pCardinal(Data)^ := ByteSwap(Value); end; {TGMPngImage implementation} {Assigns from another object} procedure TGMPngImage.Assign(Source: TPersistent); begin {Being cleared} if Source = nil then ClearChunks {Assigns contents from another TGMPngImage} else if Source is TGMPngImage then AssignPNG(Source as TGMPngImage) {Copy contents from a TBitmap} {$IFDEF UseDelphi}else if Source is TBitmap then with Source as TBitmap do AssignHandle(Handle, Transparent, ColorToRGB(TransparentColor)){$ENDIF} {Unknown source, let ancestor deal with it} else inherited; end; {Clear all the chunks in the list} procedure TGMPngImage.ClearChunks; var i: Integer; begin {Initialize gamma} InitializeGamma(); {Free all the objects and memory (0 chunks Bug fixed by Noel Sharpe)} for i := 0 TO Integer(Chunks.Count) - 1 do TObject(Chunks.Item[i]).Free; Chunks.Count := 0; end; constructor TGMPngImage.Create(const ARefLifeTime: Boolean); begin inherited Create(ARefLifeTime); {$IFDEF UseDelphi}FCanvas := TCanvas.Create;{$ENDIF} FFilters := [pfSub]; FCompressionLevel := 7; FInterlaceMethod := imNone; FMaxIdatSize := High(Word); {Create chunklist object} FChunkList := TChunkList.Create(Self); end; {Portable Network Graphics object being created as a blank image} constructor TGMPngImage.CreateBlank(ColorType, BitDepth: Cardinal; cx, cy: Integer; const ARefLifeTime: Boolean = True); var NewIHDR: TChunkIHDR; begin {Calls creator} Create(ARefLifeTime); {Checks if the parameters are ok} if not (ColorType in [COLOR_GRAYSCALE, COLOR_RGB, COLOR_PALETTE, COLOR_GRAYSCALEALPHA, COLOR_RGBALPHA]) or not (BitDepth in [1,2,4,8, 16]) or ((ColorType = COLOR_PALETTE) and (BitDepth = 16)) or ((ColorType = COLOR_RGB) and (BitDepth < 8)) then begin RaiseError(EPNGInvalidSpec, EInvalidSpecText); Exit; end; if Bitdepth = 2 then Bitdepth := 4; {Add the basis chunks} InitializeGamma; FBeingCreated := True; Chunks.AddByClass(TChunkIEND); NewIHDR := Chunks.AddByClass(TChunkIHDR) as TChunkIHDR; NewIHDR.FIHDRData.ColorType := ColorType; NewIHDR.FIHDRData.BitDepth := BitDepth; NewIHDR.FIHDRData.Width := cx; NewIHDR.FIHDRData.Height := cy; NewIHDR.PrepareImageData; if NewIHDR.FHasPalette then TChunkPLTE(Chunks.AddByClass(TChunkPLTE)).FCount := 1 shl BitDepth; Chunks.AddByClass(TChunkIDAT); FBeingCreated := False; end; destructor TGMPngImage.Destroy; begin {Free object list} ClearChunks; FChunkList.Free; {$IFDEF UseDelphi}if FCanvas <> nil then FCanvas.Free;{$ENDIF} inherited Destroy; end; function TGMPngImage.Obj: TGMPngImage; begin Result := Self; end; {Returns linesize and byte offset for pixels} procedure TGMPngImage.GetPixelInfo(var LineSize, Offset: Cardinal); begin {There must be an Header chunk to calculate size} if HeaderPresent then begin {Calculate number of bytes for each line} LineSize := BytesForPixels(Header.Width, Header.ColorType, Header.BitDepth); {Calculates byte offset} Case Header.ColorType of {Grayscale} COLOR_GRAYSCALE: If Header.BitDepth = 16 Then Offset := 2 Else Offset := 1 ; {It always smaller or equal one byte, so it occupes one byte} COLOR_PALETTE: offset := 1; {It might be 3 or 6 bytes} COLOR_RGB: offset := 3 * Header.BitDepth Div 8; {It might be 2 or 4 bytes} COLOR_GRAYSCALEALPHA: offset := 2 * Header.BitDepth Div 8; {4 or 8 bytes} COLOR_RGBALPHA: offset := 4 * Header.BitDepth Div 8; else Offset := 0; End ; end else begin {In case if there isn't any Header chunk} Offset := 0; LineSize := 0; end; end; {Returns image height} function TGMPngImage.GetHeight: Integer; begin {There must be a Header chunk to get the size, otherwise returns 0} if HeaderPresent then Result := TChunkIHDR(Chunks.Item[0]).Height else Result := 0; end; {Returns image width} function TGMPngImage.GetWidth: Integer; begin {There must be a Header chunk to get the size, otherwise returns 0} if HeaderPresent then Result := Header.Width else Result := 0; end; function TGMPngImage.Size: TPoint; begin Result := GMPoint(Width, Height); end; {Returns if the image is empty} function TGMPngImage.GetEmpty: Boolean; begin Result := (Chunks.Count = 0); end; {Raises an error} procedure TGMPngImage.RaiseError(ExceptionClass: ExceptClass; Text: String); begin raise ExceptionClass.Create(Text); end; {Set the maximum size for IDAT chunk} procedure TGMPngImage.SetMaxIdatSize(const Value: LongWord); begin {Make sure the size is at least 65535} if Value < High(Word) then FMaxIdatSize := High(Word) else FMaxIdatSize := Value; end; {Draws the image using pixel information from TChunkpHYs} procedure TGMPngImage.DrawUsingPixelInformation(Canvas: TCanvas; Point: TPoint); function Rect(Left, Top, Right, Bottom: Integer): TRect; begin Result.Left := Left; Result.Top := Top; Result.Right := Right; Result.Bottom := Bottom; end; var PPMeterY, PPMeterX: Double; NewSizeX, NewSizeY: Integer; DC: HDC; begin {Get system information} DC := GetDC(0); PPMeterY := GetDeviceCaps(DC, LOGPIXELSY) / 0.0254; PPMeterX := GetDeviceCaps(DC, LOGPIXELSX) / 0.0254; ReleaseDC(0, DC); {In case it does not has pixel information} if not HasPixelInformation then Draw(Canvas, Rect(Point.X, Point.Y, Point.X + Width, Point.Y + Height)) else with PixelInformation do begin NewSizeX := Trunc(Self.Width / (PPUnitX / PPMeterX)); NewSizeY := Trunc(Self.Height / (PPUnitY / PPMeterY)); Draw(Canvas, Rect(Point.X, Point.Y, Point.X + NewSizeX, Point.Y + NewSizeY)); end; end; { // Creates a file stream reading from the filename in the parameter and load procedure TGMPngImage.LoadFromFile(const Filename: String); var FileStream: TFileStream; begin // Test if the file exists if not FileExists(Filename) then begin // In case it does not exists, raise error RaiseError(EPNGNotExists, EPNGNotExistsText); Exit; end; // Creates the file stream to read FileStream := TFileStream.Create(Filename, [fsmRead]); LoadFromStream(FileStream); // Loads the data FileStream.Free; // Free file stream end; // Saves the current png image to a file procedure TGMPngImage.SaveToFile(const Filename: String); var FileStream: TFileStream; begin // Creates the file stream to write FileStream := TFileStream.Create(Filename, [fsmWrite]); SaveToStream(FileStream); // Saves the data FileStream.Free; // Free file stream end; } {Returns if it has the pixel information chunk} function TGMPngImage.HasPixelInformation: Boolean; begin Result := (Chunks.ItemFromClass(TChunkpHYs) as tChunkpHYs) <> nil; end; {Returns the pixel information chunk} function TGMPngImage.GetPixelInformation: TChunkpHYs; begin Result := Chunks.ItemFromClass(TChunkpHYs) as tChunkpHYs; if not Assigned(Result) then begin Result := Chunks.AddByClass(tChunkpHYs) as tChunkpHYs; Result.FUnit := utMeter; end; end; {Returns pointer to the chunk TChunkIHDR which should be the first} function TGMPngImage.GetHeader: TChunkIHDR; begin {If there is a TChunkIHDR returns it, otherwise returns nil} if (Chunks.Count <> 0) and (Chunks.Item[0] is TChunkIHDR) then Result := Chunks.Item[0] as TChunkIHDR else begin {No header, throw error message} RaiseError(EPNGHeaderNotPresent, EPNGHeaderNotPresentText); Result := nil end end; {Draws using partial transparency} procedure TGMPngImage.DrawPartialTrans(DC: HDC; Rect: TRect); {Adjust the rectangle structure} procedure AdjustRect(var Rect: TRect); var t: Integer; begin if Rect.Right < Rect.Left then begin t := Rect.Right; Rect.Right := Rect.Left; Rect.Left := t; end; if Rect.Bottom < Rect.Top then begin t := Rect.Bottom; Rect.Bottom := Rect.Top; Rect.Top := t; end end; type {Access to pixels} TPixelLine = array[Word] of TRGBQuad; pPixelLine = ^TPixelLine; const {Structure used to create the bitmap} BitmapInfoHeader: TBitmapInfoHeader = (biSize: sizeof(TBitmapInfoHeader); biWidth: 100; biHeight: 100; biPlanes: 1; biBitCount: 32; biCompression: BI_RGB; biSizeImage: 0; biXPelsPerMeter: 0; biYPelsPerMeter: 0; biClrUsed: 0; biClrImportant: 0); var {Buffer bitmap creation} FBitmapInfo : TBitmapInfo; BufferDC : HDC; BufferBits : Pointer; OldBitmap, BufferBitmap: HBitmap; Header: TChunkIHDR; {Transparency/palette chunks} TransparencyChunk: TChunktRNS; PaletteChunk: TChunkPLTE; TransValue, PaletteIndex: Byte; CurBit: Integer; Data: PByte; {Buffer bitmap modification} BytesPerRowDest, BytesPerRowSrc, BytesPerRowAlpha: Integer; ImageSource, ImageSourceOrg, AlphaSource : pByteArray; FImageData : pPixelLine; i, j, i2, j2 : Integer; {For bitmap stretching} W, H : Cardinal; Stretch : Boolean; FactorX, FactorY: Double; begin {Prepares the rectangle structure to stretch draw} if (Rect.Right = Rect.Left) or (Rect.Bottom = Rect.Top) then Exit; AdjustRect(Rect); {Gets the width and height} W := Rect.Right - Rect.Left; H := Rect.Bottom - Rect.Top; Header := Self.Header; {Fast access to header} Stretch := (W <> Header.Width) or (H <> Header.Height); if Stretch then FactorX := W / Header.Width else FactorX := 1; if Stretch then FactorY := H / Header.Height else FactorY := 1; {Prepare to create the bitmap} Fillchar(FBitmapInfo, sizeof(FBitmapInfo), #0); BitmapInfoHeader.biWidth := W; BitmapInfoHeader.biHeight := -Integer(H); FBitmapInfo.bmiHeader := BitmapInfoHeader; {Create the bitmap which will receive the background, the applied} {alpha blending and then will be painted on the background} BufferDC := CreateCompatibleDC(0); {In case BufferDC could not be created} if (BufferDC = 0) then RaiseError(EPNGOutMemory, EPNGOutMemoryText); BufferBitmap := CreateDIBSection(BufferDC, FBitmapInfo, DIB_RGB_COLORS, BufferBits, 0, 0); {In case buffer bitmap could not be created} if (BufferBitmap = 0) or (BufferBits = Nil) then begin if BufferBitmap <> 0 then DeleteObject(BufferBitmap); DeleteDC(BufferDC); RaiseError(EPNGOutMemory, EPNGOutMemoryText); end; {Selects new bitmap and release old bitmap} OldBitmap := SelectObject(BufferDC, BufferBitmap); {Draws the background on the buffer image} BitBlt(BufferDC, 0, 0, W, H, DC, Rect.Left, Rect.Top, SRCCOPY); BytesPerRowAlpha := Header.Width; BytesPerRowDest := (((FBitmapInfo.bmiHeader.biBitCount * W) + 31) and not 31) div 8; // <- Number of bytes for each image row in destination BytesPerRowSrc := (((Header.FBitmapInfo.bmiHeader.biBitCount * Header.Width) + 31) and not 31) div 8; // <- Number of bytes for each image row in source FImageData := BufferBits; AlphaSource := Header.FImageAlpha; LongInt(ImageSource) := LongInt(Header.FImageData) + Header.BytesPerRow * LongInt(Header.Height - 1); ImageSourceOrg := ImageSource; case Header.FBitmapInfo.bmiHeader.biBitCount of {R, G, B images} 24: FOR j := 1 TO H DO begin {Process all the pixels in this line} FOR i := 0 TO W - 1 DO begin if Stretch then i2 := trunc(i / FactorX) else i2 := i; {Optmize when we don�t have transparency} if (AlphaSource[i2] <> 0) then if (AlphaSource[i2] = 255) then begin pRGBTriple(@FImageData[i])^ := pRGBTriple(@ImageSource[i2 * 3])^; FImageData[i].rgbReserved := 255; end else with FImageData[i] do begin rgbRed := (255+ImageSource[2+i2*3] * AlphaSource[i2] + rgbRed * (not AlphaSource[i2])) shr 8; rgbGreen := (255+ImageSource[1+i2*3] * AlphaSource[i2] + rgbGreen * (not AlphaSource[i2])) shr 8; rgbBlue := (255+ImageSource[i2*3] * AlphaSource[i2] + rgbBlue * (not AlphaSource[i2])) shr 8; rgbReserved := not ((255 + (not rgbReserved) * (not AlphaSource[i2])) shr 8); end; end; {Move pointers} inc(LongInt(FImageData), BytesPerRowDest); if Stretch then j2 := trunc(j / FactorY) else j2 := j; LongInt(ImageSource) := LongInt(ImageSourceOrg) - BytesPerRowSrc * j2; LongInt(AlphaSource) := LongInt(Header.FImageAlpha) + BytesPerRowAlpha * j2; end; {Palette images with 1 byte for each pixel} 1,4,8: if Header.ColorType = COLOR_GRAYSCALEALPHA then FOR j := 1 TO H DO begin {Process all the pixels in this line} FOR i := 0 TO W - 1 DO with FImageData[i], Header.FBitmapInfo do begin if Stretch then i2 := trunc(i / FactorX) else i2 := i; rgbRed := (255 + ImageSource[i2] * AlphaSource[i2] + rgbRed * (255 - AlphaSource[i2])) shr 8; rgbGreen := (255 + ImageSource[i2] * AlphaSource[i2] + rgbGreen * (255 - AlphaSource[i2])) shr 8; rgbBlue := (255 + ImageSource[i2] * AlphaSource[i2] + rgbBlue * (255 - AlphaSource[i2])) shr 8; end; {Move pointers} LongInt(FImageData) := LongInt(FImageData) + BytesPerRowDest; if Stretch then j2 := trunc(j / FactorY) else j2 := j; LongInt(ImageSource) := LongInt(ImageSourceOrg) - BytesPerRowSrc * j2; LongInt(AlphaSource) := LongInt(Header.FImageAlpha) + BytesPerRowAlpha * j2; end else {Palette images} begin {Obtain pointer to the transparency chunk} TransparencyChunk := TChunktRNS(Chunks.ItemFromClass(TChunktRNS)); PaletteChunk := TChunkPLTE(Chunks.ItemFromClass(TChunkPLTE)); FOR j := 1 TO H DO begin {Process all the pixels in this line} i := 0; repeat CurBit := 0; if Stretch then i2 := trunc(i / FactorX) else i2 := i; Data := @ImageSource[i2]; repeat {Obtains the palette index} case Header.BitDepth of 1: PaletteIndex := (Data^ shr (7-(I Mod 8))) and 1; 2,4: PaletteIndex := (Data^ shr ((1-(I Mod 2))*4)) and $0F; else PaletteIndex := Data^; end; {Updates the image with the new pixel} with FImageData[i] do begin TransValue := TransparencyChunk.PaletteValues[PaletteIndex]; rgbRed := (255 + PaletteChunk.Item[PaletteIndex].rgbRed * TransValue + rgbRed * (255 - TransValue)) shr 8; rgbGreen := (255 + PaletteChunk.Item[PaletteIndex].rgbGreen * TransValue + rgbGreen * (255 - TransValue)) shr 8; rgbBlue := (255 + PaletteChunk.Item[PaletteIndex].rgbBlue * TransValue + rgbBlue * (255 - TransValue)) shr 8; end; {Move to next data} inc(i); inc(CurBit, Header.FBitmapInfo.bmiHeader.biBitCount); until CurBit >= 8; {Move to next source data} //inc(Data); until i >= Integer(W); {Move pointers} LongInt(FImageData) := LongInt(FImageData) + BytesPerRowDest; if Stretch then j2 := trunc(j / FactorY) else j2 := j; LongInt(ImageSource) := LongInt(ImageSourceOrg) - BytesPerRowSrc * j2; end end {Palette images} end {case Header.FBitmapInfo.bmiHeader.biBitCount}; {Draws the new bitmap on the foreground} BitBlt(DC, Rect.Left, Rect.Top, W, H, BufferDC, 0, 0, SRCCOPY); {Free bitmap} SelectObject(BufferDC, OldBitmap); DeleteObject(BufferBitmap); DeleteDC(BufferDC); end; {Draws the image into a canvas} procedure TGMPngImage.Draw(ACanvas: TCanvas; const Rect: TRect); var Header: TChunkIHDR; begin {Quit in case there is no header, otherwise obtain it} if Empty then Exit; Header := Chunks.GetItem(0) as TChunkIHDR; {Copy the data to the canvas} case Self.TransparencyMode of {$IFDEF PartialTransparentDraw} ptmPartial: DrawPartialTrans(ACanvas{$IFDEF UseDelphi}.Handle{$ENDIF}, Rect); {$ENDIF} ptmBit: DrawTransparentBitmap(ACanvas{$IFDEF UseDelphi}.Handle{$ENDIF}, Header.FImageData, Header.FBitmapInfo.bmiHeader, pBitmapInfo(@Header.FBitmapInfo), Rect, {$IFDEF UseDelphi}ColorToRGB({$ENDIF}TransparentColor) {$IFDEF UseDelphi}){$ENDIF} else begin SetStretchBltMode(ACanvas{$IFDEF UseDelphi}.Handle{$ENDIF}, COLORONCOLOR); StretchDiBits(ACanvas{$IFDEF UseDelphi}.Handle{$ENDIF}, Rect.Left, Rect.Top, Rect.Right - Rect.Left, Rect.Bottom - Rect.Top, 0, 0, Header.Width, Header.Height, Header.FImageData, pBitmapInfo(@Header.FBitmapInfo)^, DIB_RGB_COLORS, SRCCOPY) end end {case} end; {Characters for the header} const PngHeader: array[0..7] of AnsiChar = (#137, #80, #78, #71, #13, #10, #26, #10); {Loads the image from a stream of data} procedure TGMPngImage.LoadFromStream(Stream: IStream); var Header : array[0..7] of AnsiChar; HasIDAT : Boolean; {Chunks reading} ChunkCount : LongInt; ChunkLength: Cardinal; ChunkName : TChunkName; begin {Initialize before start loading chunks} ChunkCount := 0; ClearChunks(); {Reads the header} GMSafeIStreamRead(Stream, @Header[0], SizeOf(Header)); {Test if the header matches} if Header <> PngHeader then begin RaiseError(EPNGInvalidFileHeader, EPNGInvalidFileHeaderText); Exit; end; HasIDAT := FALSE; Chunks.Count := 10; {Load chunks} repeat inc(ChunkCount); {Increment number of chunks} if Chunks.Count < ChunkCount then {Resize the chunks list if needed} Chunks.Count := Chunks.Count + 10; {Reads chunk length and invert since it is in network order} {also checks the Read method return, if it returns 0, it} {means that no bytes was readed, probably because it reached} {the end of the file} if GMIStreamRead(Stream, @ChunkLength, SizeOf(ChunkLength)) = 0 then begin {In case it found the end of the file here} Chunks.Count := ChunkCount - 1; RaiseError(EPNGUnexpectedEnd, EPNGUnexpectedEndText); end; ChunkLength := ByteSwap(ChunkLength); {Reads chunk name} GMSafeIStreamRead(Stream, @Chunkname, SizeOf(Chunkname)); {Here we check if the first chunk is the Header which is necessary} {to the file in order to be a valid Portable Network Graphics image} if (ChunkCount = 1) and (ChunkName <> 'IHDR') then begin Chunks.Count := ChunkCount - 1; RaiseError(EPNGIHDRNotFirst, EPNGIHDRNotFirstText); Exit; end; {Has a previous IDAT} if (HasIDAT and (ChunkName = 'IDAT')) or (ChunkName = 'cHRM') then begin dec(ChunkCount); GMHrCheckObj(Stream.Seek(ChunkLength + 4, STREAM_SEEK_CUR, nil), Self); Continue; end; {Tell it has an IDAT chunk} if ChunkName = 'IDAT' then HasIDAT := TRUE; {Creates object for this chunk} Chunks.SetItem(ChunkCount - 1, CreateChunkByClass(Self, ChunkName)); {Check if the chunk is critical and unknown} {$IFDEF ErrorOnUnknownCritical} if (TChunk(Chunks.Item[ChunkCount - 1]).ClassType = TChunk) and ((Byte(ChunkName[0]) AND $20) = 0) and (ChunkName <> '') then begin Chunks.Count := ChunkCount; RaiseError(EPNGUnknownCriticalChunk, EPNGUnknownCriticalChunkText); end; {$ENDIF} {Loads it} try if not TChunk(Chunks.Item[ChunkCount - 1]).LoadFromStream(Stream, ChunkName, ChunkLength) then break; except Chunks.Count := ChunkCount; raise; end; {Terminates when it reaches the IEND chunk} until (ChunkName = 'IEND'); {Resize the list to the appropriate size} Chunks.Count := ChunkCount; {Check if there is data} if not HasIDAT then RaiseError(EPNGNoImageData, EPNGNoImageDataText); end; {Changing height is not supported} procedure TGMPngImage.SetHeight(Value: Integer); begin Resize(Width, Value) end; {Changing width is not supported} procedure TGMPngImage.SetWidth(Value: Integer); begin Resize(Value, Height) end; {$IFDEF UseDelphi} {Saves to clipboard format (thanks to Antoine Pottern)} procedure TGMPngImage.SaveToClipboardGMFormat(var AFormat: Word; var AData: THandle; var APalette: HPalette); begin with TBitmap.Create do try Width := Self.Width; Height := Self.Height; Self.Draw(Canvas, Rect(0, 0, Width, Height)); SaveToClipboardGMFormat(AFormat, AData, APalette); finally Free; end {try} end; {Loads data from clipboard} procedure TGMPngImage.LoadFromClipboardGMFormat(AFormat: Word; AData: THandle; APalette: HPalette); begin with TBitmap.Create do try LoadFromClipboardGMFormat(AFormat, AData, APalette); Self.AssignHandle(Handle, False, 0); finally Free; end {try} end; {Returns if the image is transparent} function TGMPngImage.GetTransparent: Boolean; begin Result := (TransparencyMode <> ptmNone); end; {$ENDIF} {Saving the PNG image to a stream of data} procedure TGMPngImage.SaveToStream(Stream: IStream); var j: Integer; begin {Reads the header} GMSafeIStreamWrite(Stream, @PNGHeader[0], SizeOf(PNGHeader)); {Write each chunk} FOR j := 0 TO Chunks.Count - 1 DO Chunks.Item[j].SaveToStream(Stream) end; {Prepares the Header chunk} procedure BuildHeader(Header: TChunkIHDR; Handle: HBitmap; Info: pBitmap); var DC: HDC; begin {Set width and height} Header.Width := Info.bmWidth; Header.Height := abs(Info.bmHeight); {Set bit depth} if Info.bmBitsPixel >= 16 then Header.BitDepth := 8 else Header.BitDepth := Info.bmBitsPixel; {Set color type} if Info.bmBitsPixel >= 16 then Header.ColorType := COLOR_RGB else Header.ColorType := COLOR_PALETTE; {Set other info} Header.CompressionMethod := 0; {deflate/inflate} Header.InterlaceMethod := 0; {no interlace} {Prepares bitmap headers to hold data} Header.PrepareImageData(); {Copy image data} DC := CreateCompatibleDC(0); GetDIBits(DC, Handle, 0, Header.Height, Header.FImageData, pBitmapInfo(@Header.FBitmapInfo)^, DIB_RGB_COLORS); DeleteDC(DC); end; {Assigns this TGMPngImage to another object} procedure TGMPngImage.AssignTo(Dest: TPersistent); {$IFDEF UseDelphi} function DetectPixelFormat: TPixelFormat; begin with Header do begin {Always use 24bits for partial transparency} if TransparencyMode = ptmPartial then DetectPixelFormat := pf24bit else case BitDepth of {Only supported by COLOR_PALETTE} 1: DetectPixelFormat := pf1bit; 2, 4: DetectPixelFormat := pf4bit; {8 may be palette or r, g, b values} 8, 16: case ColorType of COLOR_RGB, COLOR_GRAYSCALE: DetectPixelFormat := pf24bit; COLOR_PALETTE: DetectPixelFormat := pf8bit; else raise Exception.Create(''); end {case ColorFormat of} else raise Exception.Create(''); end {case BitDepth of} end {with Header} end; var TRNS: TChunkTRNS; BitmapData: PCardinal; PngData: PRGBTriple; AlphaData: PByte; I, J: Integer; {$ENDIF} begin {If the destination is also a TGMPngImage make it assign} {this one} if Dest is TGMPngImage then TGMPngImage(Dest).AssignPNG(Self) {$IFDEF UseDelphi} {In case the destination is a bitmap} else if (Dest is TBitmap) and HeaderPresent then begin {Copies the handle using CopyImage API} TBitmap(Dest).PixelFormat := DetectPixelFormat; TBitmap(Dest).Width := Width; TBitmap(Dest).Height := Height; TBitmap(Dest).Canvas.Draw(0, 0, Self); {Copy transparency mode} if (TransparencyMode = ptmBit) then begin TRNS := Chunks.ItemFromClass(TChunkTRNS) as TChunkTRNS; TBitmap(Dest).TransparentColor := TRNS.TransparentColor; TBitmap(Dest).Transparent := True end {if (TransparencyMode = ptmBit)} else if (TransparencyMode = ptmPartial) then begin TBitmap(Dest).PixelFormat := pf32bit; TBitmap(Dest).AlphaFormat := afIgnored; TBitmap(Dest).Canvas.Draw(0, 0, Self); for I := 0 to Height - 1 do begin BitmapData := TBitmap(Dest).ScanLine[I]; PngData := Scanline[I]; AlphaData := PByte(AlphaScanline[I]); for J := 0 to Width - 1 do begin if not Header.FHasPalette then begin BitmapData^ := (AlphaData^ shl 24) or (Round(PngData^.rgbtRed) shl 16) or (Round(PngData^.rgbtGreen) shl 8) or (Round(PngData^.rgbtBlue)); BitmapData := PCardinal(Cardinal(BitmapData) + 4); PngData := PRGBTriple(Cardinal(PngData) + 3); AlphaData := PByte(Cardinal(AlphaData) + 1); end else begin BitmapData^ := (BitmapData^ and $00FFFFFF) or (AlphaData^ shl 24); BitmapData := PCardinal(Cardinal(BitmapData) + 4); AlphaData := PByte(Cardinal(AlphaData) + 1); end; end; end; TBitmap(Dest).AlphaFormat := afDefined; end; end else {Unknown destination kind} inherited AssignTo(Dest); {$ENDIF} end; {Assigns from a bitmap object} procedure TGMPngImage.AssignHandle(Handle: HBitmap; Transparent: Boolean; TransparentColor: ColorRef); var FBitmapInfo: Windows.TBitmap; {Chunks} Header: TChunkIHDR; PLTE: TChunkPLTE; IDAT: TChunkIDAT; IEND: TChunkIEND; TRNS: TChunkTRNS; i: Integer; palEntries : TMaxLogPalette; begin {Obtain bitmap info} GetObject(Handle, SizeOf(FBitmapInfo), @FBitmapInfo); {Clear old chunks and prepare} ClearChunks(); {Create the chunks} Header := TChunkIHDR.Create(Self); {This method will fill the Header chunk with bitmap information} {and copy the image data} BuildHeader(Header, Handle, @FBitmapInfo); if Header.FHasPalette then PLTE := TChunkPLTE.Create(Self) else PLTE := nil; if Transparent then TRNS := TChunkTRNS.Create(Self) else TRNS := nil; IDAT := TChunkIDAT.Create(Self); IEND := TChunkIEND.Create(Self); {Add chunks} Chunks.Add(Header); if Header.FHasPalette then Chunks.Add(PLTE); if Transparent then Chunks.Add(TRNS); Chunks.Add(IDAT); Chunks.Add(IEND); {In case there is a image data, set the PLTE chunk FCount variable} {to the actual number of palette colors which is 2^(Bits for each pixel)} if Header.FHasPalette then begin PLTE.FCount := 1 shl FBitmapInfo.bmBitsPixel; {Create and set palette} fillchar(palEntries, sizeof(palEntries), 0); palEntries.palVersion := $300; palEntries.palNumEntries := 1 shl FBitmapInfo.bmBitsPixel; for i := 0 to palEntries.palNumEntries - 1 do begin palEntries.palPalEntry[i].peRed := Header.FBitmapInfo.bmiColors[i].rgbRed; palEntries.palPalEntry[i].peGreen := Header.FBitmapInfo.bmiColors[i].rgbGreen; palEntries.palPalEntry[i].peBlue := Header.FBitmapInfo.bmiColors[i].rgbBlue; end; DoSetPalette(CreatePalette(pLogPalette(@palEntries)^), false); end; {In case it is a transparent bitmap, prepares it} if Transparent then TRNS.TransparentColor := TransparentColor; end; {Assigns from another PNG} procedure TGMPngImage.AssignPNG(Source: TGMPngImage); var J: Integer; begin {Copy properties} InterlaceMethod := Source.InterlaceMethod; MaxIdatSize := Source.MaxIdatSize; CompressionLevel := Source.CompressionLevel; Filters := Source.Filters; {Clear old chunks and prepare} ClearChunks(); Chunks.Count := Source.Chunks.Count; {Create chunks and makes a copy from the source} FOR J := 0 TO Chunks.Count - 1 DO with Source.Chunks do begin Chunks.SetItem(J, TChunkClass(TChunk(Item[J]).ClassType).Create(Self)); TChunk(Chunks.Item[J]).Assign(TChunk(Item[J])); end {with}; end; {Returns a alpha data scanline} function TGMPngImage.GetAlphaScanline(const LineIndex: Integer): pByteArray; begin with Header do if (ColorType = COLOR_RGBALPHA) or (ColorType = COLOR_GRAYSCALEALPHA) then LongInt(Result) := LongInt(FImageAlpha) + (LineIndex * LongInt(Width)) else Result := nil; {In case the image does not use alpha information} end; {$IFDEF Store16bits} {Returns a png data extra scanline} function TGMPngImage.GetExtraScanline(const LineIndex: Integer): Pointer; begin with Header do LongInt(Result) := (LongInt(FExtraImageData) + ((LongInt(Height) - 1) * BytesPerRow)) - (LineIndex * BytesPerRow); end; {$ENDIF} {Returns a png data scanline} function TGMPngImage.GetScanline(const LineIndex: Integer): Pointer; begin with Header do PtrInt(Result) := (PtrInt(FImageData) + ((LongInt(Height) - 1) * BytesPerRow)) - (LineIndex * BytesPerRow); end; {function TGMPngImage.GetSupportsPartialTransparency: Boolean; begin Result := TransparencyMode = ptmPartial; end;} {Initialize gamma table} procedure TGMPngImage.InitializeGamma; var i: Integer; begin {Build gamma table as if there was no gamma} FOR i := 0 to 255 do begin GammaTable[i] := i; FInverseGamma[i] := i; end {for i} end; {Returns the transparency mode used by this png} function TGMPngImage.GetTransparencyMode: TPNGTransparencyMode; var TRNS: TChunkTRNS; begin with Header do begin Result := ptmNone; {Default Result} {Gets the TRNS chunk pointer} TRNS := Chunks.ItemFromClass(TChunkTRNS) as TChunkTRNS; {Test depending on the color type} case ColorType of {This modes are always partial} COLOR_RGBALPHA, COLOR_GRAYSCALEALPHA: Result := ptmPartial; {This modes support bit transparency} COLOR_RGB, COLOR_GRAYSCALE: if TRNS <> nil then Result := ptmBit; {Supports booth translucid and bit} COLOR_PALETTE: {A TRNS chunk must be present, otherwise it won't support transparency} if TRNS <> nil then if TRNS.BitTransparency then Result := ptmBit else Result := ptmPartial end {case} end {with Header} end; {Add a text chunk} procedure TGMPngImage.AddtEXt(const Keyword, Text: AnsiString); var TextChunk: TChunkTEXT; begin TextChunk := Chunks.AddByClass(TChunkText) as TChunkTEXT; TextChunk.Keyword := Keyword; TextChunk.Text := Text; end; {Add a text chunk} procedure TGMPngImage.AddzTXt(const Keyword, Text: AnsiString); var TextChunk: TChunkzTXt; begin TextChunk := Chunks.AddByClass(TChunkzTXt) as TChunkzTXt; TextChunk.Keyword := Keyword; TextChunk.Text := Text; end; {Removes the image transparency} procedure TGMPngImage.RemoveTransparency; var TRNS: TChunkTRNS; begin {Removes depending on the color type} with Header do case ColorType of {Palette uses the TChunktRNS to store alpha} COLOR_PALETTE: begin TRNS := Chunks.ItemFromClass(TChunkTRNS) as TChunkTRNS; if TRNS <> nil then Chunks.RemoveChunk(TRNS) end; {Png allocates different memory space to hold alpha information} {for these types} COLOR_GRAYSCALEALPHA, COLOR_RGBALPHA: begin {Transform into the appropriate color type} if ColorType = COLOR_GRAYSCALEALPHA then ColorType := COLOR_GRAYSCALE else ColorType := COLOR_RGB; {Free the pointer data} if FImageAlpha <> nil then FreeMem(FImageAlpha); FImageAlpha := nil end end end; {Generates alpha information} procedure TGMPngImage.CreateAlpha; var TRNS: TChunkTRNS; begin {Generates depending on the color type} with Header do case ColorType of {Png allocates different memory space to hold alpha information} {for these types} COLOR_GRAYSCALE, COLOR_RGB: begin {Transform into the appropriate color type} if ColorType = COLOR_GRAYSCALE then ColorType := COLOR_GRAYSCALEALPHA else ColorType := COLOR_RGBALPHA; {Allocates memory to hold alpha information} GetMem(FImageAlpha, Integer(Width) * Integer(Height)); FillChar(FImageAlpha^, Integer(Width) * Integer(Height), #255); end; {Palette uses the TChunktRNS to store alpha} COLOR_PALETTE: begin {Gets/creates TRNS chunk} if Chunks.ItemFromClass(TChunkTRNS) = nil then TRNS := Chunks.AddByClass(TChunkTRNS) as TChunkTRNS else TRNS := Chunks.ItemFromClass(TChunkTRNS) as TChunkTRNS; {Prepares the TRNS chunk} with TRNS do begin ResizeData(256); Fillchar(PaletteValues[0], 256, 255); FDataSize := 1 shl Header.BitDepth; FBitTransparency := False end {with Chunks.Add}; end; end {case Header.ColorType} end; {Returns transparent color} function TGMPngImage.GetTransparentColor: TColor; var TRNS: TChunkTRNS; begin TRNS := Chunks.ItemFromClass(TChunkTRNS) as TChunkTRNS; {Reads the transparency chunk to get this info} if Assigned(TRNS) then Result := TRNS.TransparentColor else Result := 0 end; {$OPTIMIZATION OFF} procedure TGMPngImage.SetTransparentColor(const Value: TColor); var TRNS: TChunkTRNS; begin if HeaderPresent then {Tests the ColorType} case Header.ColorType of {Not allowed for this modes} COLOR_RGBALPHA, COLOR_GRAYSCALEALPHA: Self.RaiseError( EPNGCannotChangeTransparent, EPNGCannotChangeTransparentText); {Allowed} COLOR_PALETTE, COLOR_RGB, COLOR_GRAYSCALE: begin TRNS := Chunks.ItemFromClass(TChunkTRNS) as TChunkTRNS; if not Assigned(TRNS) then TRNS := Chunks.AddByClass(TChunkTRNS) as TChunkTRNS; {Sets the transparency value from TRNS chunk} TRNS.TransparentColor := {$IFDEF UseDelphi}ColorToRGB({$ENDIF}Value {$IFDEF UseDelphi}){$ENDIF} end {COLOR_PALETTE, COLOR_RGB, COLOR_GRAYSCALE)} end {case} end; {Returns if header is present} function TGMPngImage.HeaderPresent: Boolean; begin Result := ((Chunks.Count <> 0) and (Chunks.Item[0] is TChunkIHDR)) end; {Returns pixel for png using palette and grayscale} function GetByteArrayPixel(const png: TGMPngImage; const X, Y: Integer): TColor; var ByteData: Byte; DataDepth: Byte; begin with png, Header do begin {Make sure the bitdepth is not greater than 8} DataDepth := BitDepth; if DataDepth > 8 then DataDepth := 8; {Obtains the byte containing this pixel} ByteData := pByteArray(png.Scanline[Y])^[X div (8 div DataDepth)]; {Moves the bits we need to the right} ByteData := (ByteData shr ((8 - DataDepth) - (X mod (8 div DataDepth)) * DataDepth)); {Discard the unwanted pixels} ByteData:= ByteData and ($FF shr (8 - DataDepth)); {For palette mode map the palette entry and for grayscale convert and returns the intensity} case ColorType of COLOR_PALETTE: with TChunkPLTE(png.Chunks.ItemFromClass(TChunkPLTE)).Item[ByteData] do Result := rgb(GammaTable[rgbRed], GammaTable[rgbGreen], GammaTable[rgbBlue]); COLOR_GRAYSCALE: begin if BitDepth = 1 then ByteData := GammaTable[Byte(ByteData * 255)] else ByteData := GammaTable[Byte(ByteData * ((1 shl DataDepth) + 1))]; Result := rgb(ByteData, ByteData, ByteData); end; else Result := 0; end {case}; end {with} end; {In case vcl units are not being used} {$IFNDEF UseDelphi} function ColorToRGB(const Color: TColor): COLORREF; begin Result := Color end; {$ENDIF} {Sets a pixel for grayscale and palette pngs} procedure SetByteArrayPixel(const png: TGMPngImage; const X, Y: Integer; const Value: TColor); const ClearFlag: array[1..8] of Integer = (1, 3, 0, 15, 0, 0, 0, $FF); var ByteData: pByte; DataDepth: Byte; ValEntry: Byte; begin with png.Header do begin {Map into a palette entry} ValEntry := GetNearestPaletteIndex(Png.Palette, ColorToRGB(Value)); {16 bits grayscale extra bits are discarted} DataDepth := BitDepth; if DataDepth > 8 then DataDepth := 8; {Gets a pointer to the byte we intend to change} ByteData := @pByteArray(png.Scanline[Y])^[X div (8 div DataDepth)]; {Clears the old pixel data} ByteData^ := ByteData^ and not (ClearFlag[DataDepth] shl ((8 - DataDepth) - (X mod (8 div DataDepth)) * DataDepth)); {Setting the new pixel} ByteData^ := ByteData^ or (ValEntry shl ((8 - DataDepth) - (X mod (8 div DataDepth)) * DataDepth)); end {with png.Header} end; {Returns pixel when png uses RGB} function GetRGBLinePixel(const png: TGMPngImage; const X, Y: Integer): TColor; begin with pRGBLine(png.Scanline[Y])^[X] do Result := RGB(rgbtRed, rgbtGreen, rgbtBlue) end; {Sets pixel when png uses RGB} procedure SetRGBLinePixel(const png: TGMPngImage; const X, Y: Integer; Value: TColor); begin with pRGBLine(png.Scanline[Y])^[X] do begin rgbtRed := GetRValue(Value); rgbtGreen := GetGValue(Value); rgbtBlue := GetBValue(Value) end end; {Returns pixel when png uses grayscale} function GetGrayLinePixel(const png: TGMPngImage; const X, Y: Integer): TColor; var B: Byte; begin B := PByteArray(png.Scanline[Y])^[X]; Result := RGB(B, B, B); end; {Sets pixel when png uses grayscale} procedure SetGrayLinePixel(const png: TGMPngImage; const X, Y: Integer; Value: TColor); begin PByteArray(png.Scanline[Y])^[X] := GetRValue(Value); end; {Resizes the PNG image} procedure TGMPngImage.Resize(const CX, CY: Integer); function Min(const A, B: Integer): Integer; begin if A < B then Result := A else Result := B; end; var Header: TChunkIHDR; Line, NewBytesPerRow: Integer; NewHandle: HBitmap; NewDC: HDC; NewImageData: Pointer; NewImageAlpha: Pointer; NewImageExtra: Pointer; begin if (CX > 0) and (CY > 0) then begin {Gets some actual information} Header := Self.Header; {Creates the new image} NewDC := CreateCompatibleDC(Header.FImageDC); Header.FBitmapInfo.bmiHeader.biWidth := cx; Header.FBitmapInfo.bmiHeader.biHeight := cy; NewHandle := CreateDIBSection(NewDC, pBitmapInfo(@Header.FBitmapInfo)^, DIB_RGB_COLORS, NewImageData, 0, 0); SelectObject(NewDC, NewHandle); {$IFDEF UseDelphi}Canvas.Handle := NewDC;{$ENDIF} NewBytesPerRow := (((Header.FBitmapInfo.bmiHeader.biBitCount * cx) + 31) and not 31) div 8; {Copies the image data} for Line := 0 to Min(CY - 1, Height - 1) do CopyMemory(Ptr(LongInt(NewImageData) + (LongInt(CY) - 1) * NewBytesPerRow - (Line * NewBytesPerRow)), Scanline[Line], Min(NewBytesPerRow, Header.BytesPerRow)); {Build array for alpha information, if necessary} if (Header.ColorType = COLOR_RGBALPHA) or (Header.ColorType = COLOR_GRAYSCALEALPHA) then begin GetMem(NewImageAlpha, CX * CY); Fillchar(NewImageAlpha^, CX * CY, 255); for Line := 0 to Min(CY - 1, Height - 1) do CopyMemory(Ptr(LongInt(NewImageAlpha) + (Line * CX)), AlphaScanline[Line], Min(CX, Width)); FreeMem(Header.FImageAlpha); Header.FImageAlpha := NewImageAlpha; end; {$IFDEF Store16bits} if (Header.BitDepth = 16) then begin GetMem(NewImageExtra, CX * CY); Fillchar(NewImageExtra^, CX * CY, 0); for Line := 0 to Min(CY - 1, Height - 1) do CopyMemory(Ptr(LongInt(NewImageExtra) + (Line * CX)), ExtraScanline[Line], Min(CX, Width)); FreeMem(Header.FExtraImageData); Header.FExtraImageData := NewImageExtra; end; {$ENDIF} {Deletes the old image} DeleteObject(Header.FImageHandle); DeleteDC(Header.FImageDC); {Prepares the header to get the new image} Header.BytesPerRow := NewBytesPerRow; Header.FIHDRData.Width := CX; Header.FIHDRData.Height := CY; Header.FImageData := NewImageData; {Replaces with the new image} Header.FImageHandle := NewHandle; Header.FImageDC := NewDC; end else {The new size provided is invalid} RaiseError(EPNGInvalidNewSize, EInvalidNewSizeText) end; {Sets a pixel} procedure TGMPngImage.SetPixels(const X, Y: Integer; const Value: TColor); begin if ((X >= 0) and (X <= Width - 1)) and ((Y >= 0) and (Y <= Height - 1)) then with Header do begin if ColorType in [COLOR_GRAYSCALE, COLOR_PALETTE] then SetByteArrayPixel(Self, X, Y, Value) else if ColorType in [COLOR_GRAYSCALEALPHA] then SetGrayLinePixel(Self, X, Y, Value) else SetRGBLinePixel(Self, X, Y, Value) end {with} end; {Returns a pixel} function TGMPngImage.GetPixels(const X, Y: Integer): TColor; begin if ((X >= 0) and (X <= Width - 1)) and ((Y >= 0) and (Y <= Height - 1)) then with Header do begin if ColorType in [COLOR_GRAYSCALE, COLOR_PALETTE] then Result := GetByteArrayPixel(Self, X, Y) else if ColorType in [COLOR_GRAYSCALEALPHA] then Result := GetGrayLinePixel(Self, X, Y) else Result := GetRGBLinePixel(Self, X, Y) end {with} else Result := 0 end; {Returns the image palette} function TGMPngImage.GetPalette: HPALETTE; begin Result := Header.FImagePalette; end; {Assigns from another TChunk} procedure TChunkpHYs.Assign(Source: TChunk); begin FPPUnitY := TChunkpHYs(Source).FPPUnitY; FPPUnitX := TChunkpHYs(Source).FPPUnitX; FUnit := TChunkpHYs(Source).FUnit; end; {Loads the chunk from a stream} function TChunkpHYs.LoadFromStream(Stream: IStream; const ChunkName: TChunkName; Size: Integer): Boolean; begin {Let ancestor load the data} Result := inherited LoadFromStream(Stream, ChunkName, Size); if not Result or (Size <> 9) then Exit; {Size must be 9} {Reads data} FPPUnitX := ByteSwap(pCardinal(LongInt(Data))^); FPPUnitY := ByteSwap(pCardinal(LongInt(Data) + 4)^); FUnit := pUnitType(LongInt(Data) + 8)^; end; {Saves the chunk to a stream} function TChunkpHYs.SaveToStream(Stream: IStream): Boolean; begin {Update data} ResizeData(9); {Make sure the size is 9} pCardinal(Data)^ := ByteSwap(FPPUnitX); pCardinal(LongInt(Data) + 4)^ := ByteSwap(FPPUnitY); pUnitType(LongInt(Data) + 8)^ := FUnit; {Let inherited save data} Result := inherited SaveToStream(Stream); end; procedure TGMPngImage.DoSetPalette(Value: HPALETTE; const UpdateColors: boolean); begin if (Header.FHasPalette) then begin {Update the palette entries} if UpdateColors then Header.PaletteToDIB(Value); {Resize the new palette} SelectPalette(Header.FImageDC, Value, False); RealizePalette(Header.FImageDC); {Replaces} DeleteObject(Header.FImagePalette); Header.FImagePalette := Value; end end; {Set palette based on a windows palette handle} procedure TGMPngImage.SetPalette(Value: HPALETTE); begin DoSetPalette(Value, true); end; {Returns the library version} //function TGMPngImage.GetLibraryVersion: String; //begin //Result := LibraryVersion //end; initialization {crc table has not being computed yet} crc_table_computed := FALSE; {$IFDEF UseDelphi}{$IFDEF RegisterGraphic} TPicture.RegisterFileGMFormat('PNG', 'Portable Network Graphics', TGMPngImage); {$ENDIF}{$ENDIF} finalization {$IFDEF UseDelphi}{$IFDEF RegisterGraphic} TPicture.UnregisterGraphicClass(TGMPngImage); {$ENDIF}{$ENDIF} end.