//{$DEFINE NOCLASSES} {$IFDEF FPC} {$DEFINE NOT_USE_KOL_ERR} {$MODE Delphi} {$ASMMODE intel} {$GOTO ON} {$ENDIF} unit KOLGraphicEx; // (c) Copyright 1999, 2000 Dipl. Ing. Mike Lischke (public@lischke-online.de). All rights reserved. // // This package is freeware for non-commercial use only. // Contact author for licenses (shareware@lischke-online.de) and see License.txt which comes with the package. // // GraphicEx - // This unit is an addendum to Graphics.pas, in order to enable your application // to import many common graphic files. // // See help file for a description of supported image types. Additionally, there is a resample routine // (Stretch) based on code from Anders Melander (http://www.melander.dk/delphi/resampler/index.html) // which has been optimized quite a lot to work faster and bug fixed. // // version - 9.9 // // 03-SEP-2000 ml: // EPS with alpha channel, workaround for TIFs with wrong alpha channel indication, // workaround for bad packbits compressed (TIF) images // 28-AUG-2000 ml: // small bugfixes // 27-AUG-2000 ml: // changed all FreeMemory(P) calls back to ... if Assigned(P) then FreeMem(P); ... // 24-AUG-2000 ml: // small bug in LZ77 decoder removed // 18-AUG-2000 ml: // TIF deflate decoding scheme // 15-AUG-2000 ml: // workaround for TIF images without compression, but prediction scheme set (which is not really used in this case) // 12-AUG-2000 ml: // small changes // // For older history please look into the help file. // // Note: The library provides usually only load support for the listed image formats but will perhaps be enhanced // in the future to save those types too. It can be compiled with Delphi 4 or newer versions. // // // (c) Copyright 1999, 2000 Dipl. Ing. Mike Lischke (public@lischke-online.de). All rights reserved. // // This package is freeware for non-commercial use only! // Contact author for licenses (shareware@lischke-online.de) and see License.txt which comes with the package. ////////////////////////////////////////////////// // Converted to KOL by Dimaxx (dimaxx@atnet.ru) // ////////////////////////////////////////////////// interface {$ALIGN OFF} {$I KOLDEF.INC} {$O+} uses Windows, KOL, KOLGraphicCompression, KOLGraphicColor, Errors, {$IFDEF NOT_USE_KOL_ERR}sysutils {$ELSE}Err {$ENDIF} ; type TCardinalArray = array of cardinal; PDWORDArray = ^TDWORDArray; TDWORDArray = array[0..65535] of cardinal; TByteArray = array of byte; TFloatArray = array of single; TImageOptions = set of ( ioTiled, // image consists of tiles not strips (TIF) ioBigEndian, // byte order in values >= words is reversed (TIF, RLA, SGI) ioMinIsWhite, // minimum value in grayscale palette is white not black (TIF) ioReversed, // bit order in bytes is reveresed (TIF) ioUseGamma // gamma correction is used ); // describes the compression used in the image file TCompressionType = ( ctUnknown, // compression type is unknown ctNone, // no compression at all ctRLE, // run length encoding ctPackedBits, // Macintosh packed bits ctLZW, // Lempel-Zif-Welch ctFax3, // CCITT T.4 (1d), also known as fax group 3 ctFaxRLE, // modified Huffman (CCITT T.4 derivative) ctFax4, // CCITT T.6, also known as fax group 4 ctFaxRLEW, // CCITT T.4 with word alignment ctLZ77, // Huffman inflate/deflate ctJPEG, // TIF JPEG compression (new version) ctOJPEG, // TIF JPEG compression (old version) ctThunderscan, // TIF thunderscan compression ctNext, ctIT8CTPAD, ctIT8LW, ctIT8MP, ctIT8BL, ctPixarFilm, ctPixarLog, ctDCS, ctJBIG, ctPCDHuffmann // PhotoCD Hufman compression ); // properties of a particular image which are set while loading an image or when // they are explicitly requested via ReadImageProperties PImageProperties = ^TImageProperties; TImageProperties = packed record Version: cardinal; // TIF, PSP, GIF Options: TImageOptions; // all images Width, // all images Height: cardinal; // all images ColorScheme: TColorScheme; // all images BitsPerSample, // all Images SamplesPerPixel, // all images BitsPerPixel: byte; // all images Compression: TCompressionType; // all images FileGamma: single; // RLA, PNG XResolution, YResolution: single; // given in dpi (TIF, PCX, PSP) Interlaced, // GIF, PNG HasAlpha: boolean; // TIF, PNG // informational data, used internally and/or by decoders // TIF FirstIFD, PlanarConfig, // most of this data is needed in the JPG decoder CurrentRow, TileWidth, TileLength, BytesPerLine: cardinal; RowsPerStrip: TCardinalArray; YCbCrSubSampling, JPEGTables: TByteArray; JPEGColorMode, JPEGTablesMode: Cardinal; CurrentStrip, StripCount, Predictor: integer; // PCD Overview: boolean; // true if image is an overview image Rotate: byte; // describes how the image is rotated (aka landscape vs. portrait image) ImageCount: word; // number of subimages if this is an overview image // GIF LocalColorTable: boolean; // image uses an own color palette instead of the global one // RLA BottomUp: boolean; // images is bottom to top // PSD Channels: byte; // up to 24 channels per image // PNG FilterMode: byte; end; // This is the general base class for all image types implemented in GraphicEx. // It contains some generally used class/data. PGraphicExGraphic = ^TGraphicExGraphic; TGraphicExGraphic = {$IFDEF NOCLASSES} object(TObj) {$ELSE} class {$ENDIF} private FColorManager: PColorManager; FImageProperties: TImageProperties; FBasePosition: cardinal; // stream start position FStream: PStream; // used for local references of the stream the class is currently loading from FBitmap: PBitmap; FCorrupted: Boolean; protected {$IFNDEF USE_GLOBALS} CurrentLineR: array of integer; CurrentLineG: array of integer; CurrentLineB: array of integer; {$ENDIF} protected function GetWidth: Integer; function GetHeight: Integer; public constructor Create; destructor Destroy; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} {$IFNDEF NOCLASSES} class function CanLoad(const Filename: string): boolean; overload; virtual; {$ENDIF} {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFNDEF NOCLASSES} overload; {$ENDIF} virtual; function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; virtual; property Bitmap: PBitmap read FBitmap; property ColorManager: PColorManager read FColorManager; property ImageProperties: TImageProperties read FImageProperties write FImageProperties; property Width: Integer read GetWidth; property Height: Integer read GetHeight; property Corrupted: Boolean read FCorrupted; end; {$IFNDEF NOCLASSES} TGraphicExGraphicClass = class of TGraphicExGraphic; {$ENDIF} // *.bw, *.rgb, *.rgba, *.sgi images TSGIGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic) {$ELSE} class(TGraphicExGraphic) {$ENDIF} private FRowStart, FRowSize: PDWORDArray; // start and length of a line (if compressed) {$IFDEF NOCLASSES} FDecoder: PSGIRLEDecoder; {$ELSE} FDecoder: TDecoder; {$ENDIF} // ...same applies here procedure ReadAndDecode(Red,Green,Blue,Alpha: pointer; Row,BPC: cardinal); public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} end; // *.cel, *.pic images TAutodeskGraphic = {$IFDEF NOCLASSES} object( TGraphicExGraphic ) {$ELSE} class(TGraphicExGraphic) {$ENDIF} public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} end; // *.tif, *.tiff images // one entry in a an IFD (image file directory) TIFDEntry = packed record ATag: word; DataType: word; DataLength: cardinal; Offset: cardinal; end; TTIFFPalette = array[0..787] of word; PTIFFGraphic = ^TTIFFGraphic; TTIFFGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic) {$ELSE} class(TGraphicExGraphic) {$ENDIF} private FIFD: array of TIFDEntry; // the tags of one image file directory FPalette: TTIFFPalette; FYCbCrPositioning: cardinal; FYCbCrCoefficients: TFloatArray; function FindTag(ATag: cardinal; var Index: cardinal): boolean; procedure GetValueList(Stream: PStream; ATag: cardinal; var Values: TByteArray); overload; procedure GetValueList(Stream: PStream; ATag: cardinal; var Values: TCardinalArray); overload; procedure GetValueList(Stream: PStream; ATag: cardinal; var Values: TFloatArray); overload; function GetValue(Stream: PStream; ATag: cardinal; Default: single = 0): single; overload; function GetValue(ATag: cardinal; Default: cardinal = 0): Cardinal; overload; function GetValue(ATag: cardinal; var Size: cardinal; Default: cardinal = 0): cardinal; overload; procedure SortIFD; procedure SwapIFD; public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} end; TEPSGraphic = {$IFDEF NOCLASSES} object(TTIFFGraphic) {$ELSE} class(TTIFFGraphic) {$ENDIF} public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} end; // *.tga; *.vst; *.icb; *.vda; *.win images TTGAGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic) {$ELSE} class(TGraphicExGraphic) {$ENDIF} public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} end; // *.pcx; *.pcc; *.scr images // Note: Due to the badly designed format a PCX/SCR file cannot be part in a larger stream because the position of the // color palette as well as the decoding size can only be determined by the size of the image. // Hence the image must be the only one in the stream or the last one. PPCXGraphic = ^TPCXGraphic; TPCXGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic) {$ELSE} class(TGraphicExGraphic) {$ENDIF} public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} end; // *.pcd images // Note: By default the BASE resolution of a PCD image is loaded with LoadFromStream. TPCDGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic) {$ELSE} class(TGraphicExGraphic) {$ENDIF} public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} end; // *.ppm, *.pgm, *.pbm images PPPMGraphic = ^TPPMGraphic; TPPMGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic) {$ELSE} class(TGraphicExGraphic) {$ENDIF} private FBuffer: array[0..4095] of Char; FIndex: integer; function CurrentChar: Char; function GetChar: Char; function GetNumber: cardinal; function ReadLine: string; public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} end; // *.cut (+ *.pal) images // Note: Also this format should not be used in a stream unless it is the only image or the last one! TCUTGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic) {$ELSE} class(TGraphicExGraphic) {$ENDIF} private FPaletteFile: string; protected procedure LoadPalette; public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} property PaletteFile: string read FPaletteFile write FPaletteFile; end; // *.gif images {$IFDEF NOCLASSES} PGifGraphic = ^TGIFGraphic; {$ENDIF} TGIFGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic) {$ELSE} class(TGraphicExGraphic) {$ENDIF} private function SkipExtensions: byte; public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} function Transparent: Boolean; function Count: Integer; end; // *.rla, *.rpf images // implementation based on code from Dipl. Ing. Ingo Neumann (ingo@upstart.de, ingo_n@dialup.nacamar.de) TRLAGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic) {$ELSE} class(TGraphicExGraphic) {$ENDIF} private procedure SwapHeader(var Header); // start position of the image header in the stream public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} end; // *.psd, *.pdd images PPSDGraphic = ^TPSDGraphic; TPSDGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic) {$ELSE} class(TGraphicExGraphic) {$ENDIF} public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} end; PPSPGraphic = ^TPSPGraphic; // *.psp images (file version 3 and 4) TPSPGraphic = {$IFDEF NOCLASSES} object( TGraphicExGraphic ) {$ELSE} class(TGraphicExGraphic) {$ENDIF} public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} end; // *.png images TChunkType = array[0..3] of Char; // This header is followed by a variable number of data bytes, which are followed by the CRC for this data. // The actual size of this data is given by field length in the chunk header. // CRC is Cardinal (4 byte unsigned integer). TPNGChunkHeader = packed record Length: cardinal; // size of data (entire chunk excluding itself, CRC and type) ChunkType: TChunkType; end; TPNGGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic) {$ELSE} class(TGraphicExGraphic) {$ENDIF} private {$IFDEF NOCLASSES} FDecoder: PLZ77Decoder; {$ELSE} FDecoder: TLZ77Decoder; {$ENDIF} FIDATSize: integer; // remaining bytes in the current IDAT chunk FRawBuffer, // buffer to load raw chunk data and to check CRC FCurrentSource: pointer; // points into FRawBuffer for current position of decoding FHeader: TPNGChunkHeader; // header of the current chunk FCurrentCRC: cardinal; // running CRC for the current chunk FSourceBPP: integer; // bits per pixel used in the file // FPalette: HPALETTE; // used to hold the palette handle until we can set it finally after the pixel format // has been set too (as this destroys the current palette) FTransparency: TByteArray; // If the image is indexed then this array might contain alpha values (depends on file) // each entry corresponding to the same palette index as the index in this array. // For grayscale and RGB images FTransparentColor contains the (only) transparent // color. FTransparentColor: TColor; // transparent color for gray and RGB FBackgroundColor: TColor; // index or color ref procedure ApplyFilter(Filter: byte; Line,PrevLine,Target: PByte; BPP,BytesPerRow: integer); function IsChunk(ChunkType: TChunkType): boolean; function LoadAndSwapHeader: cardinal; procedure LoadBackgroundColor(const Description); procedure LoadIDAT(const Description); procedure LoadTransparency(const Description); procedure ReadDataAndCheckCRC; procedure ReadRow(RowBuffer: pointer; BytesPerRow: integer); function SetupColorDepth(ColorType,BitDepth: integer): integer; public {$IFNDEF NOCLASSES} class {$ENDIF} function CanLoad(Stream: PStream): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} procedure LoadFromStream(Stream: PStream); function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF} property BackgroundColor: TColor read FBackgroundColor; property Transparency: TByteArray read FTransparency; end; // ---------- file format management stuff TFormatType = ( ftAnimation, // format contains an animation (like GIF or AVI) ftLayered, // format supports multiple layers (like PSP, PSD) ftMultiImage, // format can contain more than one image (like TIF or GIF) ftRaster, // format is contains raster data (this is mainly used) ftVector // format contains vector data (like DXF or PSP file version 4) ); TFormatTypes = set of TFormatType; TFilterSortType = ( fstNone, // do not sort entries, list them as they are registered fstBoth, // sort entries first by description then by extension fstDescription, // sort entries by description only fstExtension // sort entries by extension only ); TFilterOption = ( foCompact, // use the compact form in filter strings instead listing each extension on a separate line foIncludeAll, // include the 'All image files' filter string foIncludeExtension // add the extension to the description ); TFilterOptions = set of TFilterOption; // resampling support types TResamplingFilter = (sfBox, sfTriangle, sfHermite, sfBell, sfSpline, sfLanczos3, sfMitchell); // Resampling support routines procedure Stretch(NewWidth,NewHeight: cardinal; Filter: TResamplingFilter; Radius: single; Source,Target: PBitmap;G: PGraphicExGraphic); overload; procedure Stretch(NewWidth,NewHeight: cardinal; Filter: TResamplingFilter; Radius: single; Source: PBitmap; G: PGraphicExGraphic); overload; var Comp2Str: array[TCompressionType] of string = ( 'Unknown','None','RLE','PackedBits','LZW','CCITT Fax3', 'Huffman RLE','CCITT Fax4','Huffman RLE word align', 'LZ77','JPEG','Old JPEG','Thunderscan','Next', 'IT8CTPAD','IT8LW','IT8MP','IT8BL','PixarFilm','PixarLog','DCS','JBIG', 'PCDHuffmann'); //---------------------------------------------------------------------------------------------------------------------- implementation uses {$IFDEF NOT_USE_KOL_ERR}Math, {$ELSE}KOLMath, {$ENDIF} KolZLibBzip; const PNG = 'PNG'; TIF = 'TIF/TIFF'; type // resampling support types TRGBInt = packed record R,G,B: integer; end; PRGBWord = ^TRGBWord; TRGBWord = packed record R,G,B: word; end; PRGBAWord = ^TRGBAWord; TRGBAWord = packed record R,G,B,A: word; end; PBGR = ^TBGR; TBGR = packed record B,G,R: byte; end; PBGRA = ^TBGRA; TBGRA = packed record B,G,R,A: byte; end; PRGB = ^TRGB; TRGB = packed record R,G,B: byte; end; PRGBA = ^TRGBA; TRGBA = packed record R,G,B,A: byte; end; PPixelArray = ^TPixelArray; TPixelArray = array[0..0] of TBGR; TFilterFunction = function(Value: single): single; // contributor for a Pixel PContributor = ^TContributor; TContributor = packed record Weight: integer; // Pixel Weight Pixel: integer; // Source Pixel end; TContributors = array of TContributor; // list of source pixels contributing to a destination pixel TContributorEntry = packed record N: integer; Contributors: TContributors; end; TContributorList = array of TContributorEntry; const DefaultFilterRadius: array[TResamplingFilter] of single = (0.5,1,1,1.5,2,3,2); {$IFDEF USE_GLOBALS} threadvar // globally used cache for current image (speeds up resampling about 10%) CurrentLineR: array of integer; CurrentLineG: array of integer; CurrentLineB: array of integer; {$ENDIF} function Rect(ALeft,ATop,ARight,ABottom: integer): TRect; begin with Result do begin Left:=ALeft; Top:=ATop; Right:=ARight; Bottom:=ABottom; end; end; function StrLIComp(const Str1,Str2: PChar; MaxLen: cardinal): integer; assembler; asm PUSH EDI PUSH ESI PUSH EBX MOV EDI,EDX MOV ESI,EAX MOV EBX,ECX XOR EAX,EAX OR ECX,ECX JE @@4 REPNE SCASB SUB EBX,ECX MOV ECX,EBX MOV EDI,EDX XOR EDX,EDX @@1: REPE CMPSB JE @@4 MOV AL,[ESI-1] CMP AL,'a' JB @@2 CMP AL,'z' JA @@2 SUB AL,20H @@2: MOV DL,[EDI-1] CMP DL,'a' JB @@3 CMP DL,'z' JA @@3 SUB DL,20H @@3: SUB EAX,EDX JE @@1 @@4: POP EBX POP ESI POP EDI end; //---------------------------------------------------------------------------------------------------------------------- {$IFDEF NOT_USE_KOL_ERR} procedure GraphicExError(Code: integer); overload; var E: Exception; begin E:=Exception.Create(int2str(Code)); //E.ErrorCode:=Code; raise E; end; {$ELSE} procedure GraphicExError(Code: integer); overload; var E: Exception; begin E:=Exception.Create(e_Custom,ErrorMsg[Code]); E.ErrorCode:=Code; raise E; end; {$ENDIF} //---------------------------------------------------------------------------------------------------------------------- {$IFDEF NOT_USE_KOL_ERR} procedure GraphicExError(Code: integer; Args: array of const); overload; var E: Exception; begin E:=Exception.CreateFmt(int2str(Code),Args); //E.ErrorCode:=Code; raise E; end; {$ELSE} procedure GraphicExError(Code: integer; Args: array of const); overload; var E: Exception; begin E:=Exception.CreateFmt(e_Custom,ErrorMsg[Code],Args); E.ErrorCode:=Code; raise E; end; {$ENDIF} //---------------------------------------------------------------------------------------------------------------------- procedure Upsample(Width,Height,ScaledWidth: cardinal; Pixels: PChar); // Creates a new image that is a integral size greater than an existing one. var X,Y: cardinal; P,Q,R: PChar; begin for Y:=0 to pred(Height) do begin P:=Pixels+(Height-1-Y)*ScaledWidth+(Width-1); Q:=Pixels+((Height-1-Y) shl 1)*ScaledWidth+((Width-1) shl 1); Q^:=P^; (Q+1)^:=P^; for X:=1 to pred(Width) do begin Dec(P); Dec(Q,2); Q^:=P^; (Q+1)^:=Char((Word(P^)+Word((P+1)^)+1) shr 1); end; end; for Y:=0 to Height-2 do begin P:=Pixels+(Y shl 1)*ScaledWidth; Q:=P+ScaledWidth; R:=Q+ScaledWidth; for X:=0 to Width-2 do begin Q^:=Char((Word(P^)+Word(R^)+1) shr 1); (Q+1)^:=Char((Word(P^)+Word((P+2)^)+Word(R^)+Word((R+2)^)+2) shr 2); Inc(Q,2); Inc(P,2); Inc(R,2); end; Q^:=Char((Word(P^)+Word(R^)+1) shr 1); Inc(P); Inc(Q); Q^:=Char((Word(P^)+Word(R^)+1) shr 1); end; P:=Pixels+(2*Height-2)*ScaledWidth; Q:=Pixels+(2*Height-1)*ScaledWidth; Move(P^,Q^,2*Width); end; //----------------- filter functions for stretching -------------------------------------------------------------------- function HermiteFilter(Value: single): single; // f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 begin if Value<0 then Value:=-Value; if Value<1 then Result:=(2*Value-3)*Sqr(Value)+1 else Result:=0; end; //---------------------------------------------------------------------------------------------------------------------- function BoxFilter(Value: Single): Single; // This filter is also known as 'nearest neighbour' Filter. begin if (Value>-0.5) and (Value<=0.5) then Result:=1 else Result:=0; end; //---------------------------------------------------------------------------------------------------------------------- function TriangleFilter(Value: single): single; // aka 'linear' or 'bilinear' filter begin if Value<0 then Value:=-Value; if Value<1 then Result:=1-Value else Result:=0; end; //---------------------------------------------------------------------------------------------------------------------- function BellFilter(Value: single): single; begin if Value<0 then Value:=-Value; if Value<0.5 then Result:=0.75-Sqr(Value) else if Value<1.5 then begin Value:=Value-1.5; Result:=0.5*Sqr(Value); end else Result:=0; end; //---------------------------------------------------------------------------------------------------------------------- function SplineFilter(Value: single): single; // B-spline filter var Temp: single; begin if Value<0 then Value:=-Value; if Value<1 then begin Temp:=Sqr(Value); Result:=0.5*Temp*Value-Temp+2/3; end else if Value<2 then begin Value:=2-Value; Result:=Sqr(Value)*Value/6; end else Result:=0; end; //---------------------------------------------------------------------------------------------------------------------- function Lanczos3Filter(Value: single): single; //--------------- local function -------------------------------------------- function SinC(Value: single): single; begin if Value<>0 then begin Value:=Value*PI; Result:=Sin(Value)/Value; end else Result:=1; end; //--------------------------------------------------------------------------- begin if Value<0 then Value:=-Value; if Value<3 then Result:=SinC(Value)*SinC(Value/3) else Result:=0; end; //---------------------------------------------------------------------------------------------------------------------- function MitchellFilter(Value: single): single; var Temp,B,C: single; begin B:=1/3; C:=1/3; if Value<0 then Value:=-Value; Temp:=Sqr(Value); if Value<1 then begin Value:=(((12-9*B-6*C)*(Value*Temp))+((-18+12*B+6*C)*Temp)+(6-2*B)); Result:=Value/6; end else if Value<2 then begin Value:=(((-B-6*C)*(Value*Temp))+((6*B+30*C)*Temp)+((-12*B-48*C)*Value)+(8*B+24*C)); Result:=Value/6; end else Result:=0; end; //---------------------------------------------------------------------------------------------------------------------- const FilterList: array[TResamplingFilter] of TFilterFunction = ( BoxFilter,TriangleFilter,HermiteFilter,BellFilter, SplineFilter,Lanczos3Filter,MitchellFilter); //---------------------------------------------------------------------------------------------------------------------- procedure FillLineChache(N,Delta: integer; Line: pointer; G: PGraphicExGraphic); var I: integer; Run: PBGR; begin Run:=Line; for I:=0 to pred(N) do begin G.CurrentLineR[I]:=Run.R; G.CurrentLineG[I]:=Run.G; G.CurrentLineB[I]:=Run.B; Inc(PByte(Run),Delta); end; end; //---------------------------------------------------------------------------------------------------------------------- function ApplyContributors(N: integer; Contributors: TContributors; G: PGraphicExGraphic): TBGR; var RGB: TRGBInt; J,Total,Weight: integer; Pixel: cardinal; Contr: ^TContributor; begin RGB.R:=0; RGB.G:=0; RGB.B:=0; Total:=0; Contr:=@Contributors[0]; for J:=0 to pred(N) do begin Weight:=Contr.Weight; Inc(Total,Weight); Pixel:=Contr.Pixel; Inc(RGB.R,G.CurrentLineR[Pixel]*Weight); Inc(RGB.G,G.CurrentLineG[Pixel]*Weight); Inc(RGB.B,G.CurrentLineB[Pixel]*Weight); Inc(Contr); end; if Total=0 then begin Result.R:=ClampByte(RGB.R shr 8); Result.G:=ClampByte(RGB.G shr 8); Result.B:=ClampByte(RGB.B shr 8); end else begin Result.R:=ClampByte(RGB.R div Total); Result.G:=ClampByte(RGB.G div Total); Result.B:=ClampByte(RGB.B div Total); end; end; //---------------------------------------------------------------------------------------------------------------------- procedure DoStretch(Filter: TFilterFunction; Radius: single; Source,Target: PBitmap; G: PGraphicExGraphic); // This is the actual scaling routine. Target must be allocated already with sufficient size. Source must // contain valid data, Radius must not be 0 and Filter must not be nil. var ScaleX,ScaleY: single; // Zoom scale factors I,J,K,N: integer; // Loop variables Center: single; // Filter calculation variables Width: single; Weight: integer; // Filter calculation variables Left, Right: integer; // Filter calculation variables Work: PBitmap; ContributorList: TContributorList; SourceLine,DestLine: PPixelArray; DestPixel: PBGR; Delta,DestDelta: integer; SourceHeight,SourceWidth,TargetHeight,TargetWidth: integer; begin // shortcut variables SourceHeight:=Source.Height; SourceWidth:=Source.Width; TargetHeight:=Target.Height; TargetWidth:=Target.Width; if (SourceHeight=0) or (SourceWidth=0) or (TargetHeight=0) or (TargetWidth=0) then Exit; // create intermediate image to hold horizontal zoom Work:=NewBitmap(0,0); try Work.PixelFormat:=pf24Bit; Work.Height:=SourceHeight; Work.Width:=TargetWidth; if SourceWidth=1 then ScaleX:=TargetWidth/SourceWidth else ScaleX:=(TargetWidth-1)/(SourceWidth-1); if (SourceHeight=1) or (TargetHeight=1) then ScaleY:=TargetHeight/SourceHeight else ScaleY:=(TargetHeight-1)/(SourceHeight-1); // pre-calculate filter contributions for a row SetLength(ContributorList,TargetWidth); // horizontal sub-sampling if ScaleX<1 then begin // scales from bigger to smaller Width Width:=Radius/ScaleX; for I:=0 to pred(TargetWidth) do begin ContributorList[I].N:=0; SetLength(ContributorList[I].Contributors,Trunc(2*Width+1)); Center:=I/ScaleX; Left:=Floor(Center-Width); Right:=Ceil(Center+Width); for J:=Left to Right do begin Weight:=Round(Filter((Center-J)*ScaleX)*ScaleX*256); if Weight<>0 then begin if J<0 then N:=-J else if J>=SourceWidth then N:=SourceWidth-J+SourceWidth-1 else N:=J; K:=ContributorList[I].N; Inc(ContributorList[I].N); ContributorList[I].Contributors[K].Pixel:=N; ContributorList[I].Contributors[K].Weight:=Weight; end; end; end; end else begin // horizontal super-sampling // scales from smaller to bigger Width for I:=0 to pred(TargetWidth) do begin ContributorList[I].N:=0; SetLength(ContributorList[I].Contributors,Trunc(2*Radius+1)); Center:=I/ScaleX; Left:=Floor(Center-Radius); Right:=Ceil(Center+Radius); for J:=Left to Right do begin Weight:=Round(Filter(Center-J)*256); if Weight<>0 then begin if J<0 then N:=-J else if J>=SourceWidth then N:=SourceWidth-J+SourceWidth-1 else N:=J; K:=ContributorList[I].N; Inc(ContributorList[I].N); ContributorList[I].Contributors[K].Pixel:=N; ContributorList[I].Contributors[K].Weight:=Weight; end; end; end; end; // now apply filter to sample horizontally from Src to Work SetLength(G.CurrentLineR,SourceWidth); SetLength(G.CurrentLineG,SourceWidth); SetLength(G.CurrentLineB,SourceWidth); for K:=0 to pred(SourceHeight) do begin SourceLine:=Source.ScanLine[K]; FillLineChache(SourceWidth,3,SourceLine,G); DestPixel:=Work.ScanLine[K]; for I:=0 to pred(TargetWidth) do with ContributorList[I] do begin DestPixel^:=ApplyContributors(N,ContributorList[I].Contributors,G); // move on to next column Inc(DestPixel); end; end; // free the memory allocated for horizontal filter weights, since we need the stucture again for I:=0 to pred(TargetWidth) do ContributorList[I].Contributors:=nil; ContributorList:=nil; // pre-calculate filter contributions for a column SetLength(ContributorList,TargetHeight); // vertical sub-sampling if ScaleY<1 then begin // scales from bigger to smaller height Width:=Radius/ScaleY; for I:=0 to pred(TargetHeight) do begin ContributorList[I].N:=0; SetLength(ContributorList[I].Contributors,Trunc(2*Width+1)); Center:=I/ScaleY; Left:=Floor(Center-Width); Right:=Ceil(Center+Width); for J:=Left to Right do begin Weight:=Round(Filter((Center-J)*ScaleY)*ScaleY*256); if Weight<>0 then begin if J<0 then N:=-J else if J>=SourceHeight then N:=SourceHeight-J+SourceHeight-1 else N:=J; K:=ContributorList[I].N; Inc(ContributorList[I].N); ContributorList[I].Contributors[K].Pixel:=N; ContributorList[I].Contributors[K].Weight:=Weight; end; end; end end else begin // vertical super-sampling // scales from smaller to bigger height for I:=0 to pred(TargetHeight) do begin ContributorList[I].N:=0; SetLength(ContributorList[I].Contributors,Trunc(2*Radius+1)); Center:=I/ScaleY; Left:=Floor(Center-Radius); Right:=Ceil(Center+Radius); for J:=Left to Right do begin Weight:=Round(Filter(Center-J)*256); if Weight<>0 then begin if J<0 then N:=-J else if J>=SourceHeight then N:=SourceHeight-J+SourceHeight-1 else N:=J; K:=ContributorList[I].N; Inc(ContributorList[I].N); ContributorList[I].Contributors[K].Pixel:=N; ContributorList[I].Contributors[K].Weight:=Weight; end; end; end; end; // apply filter to sample vertically from Work to Target SetLength(G.CurrentLineR,SourceHeight); SetLength(G.CurrentLineG,SourceHeight); SetLength(G.CurrentLineB,SourceHeight); SourceLine:=Work.ScanLine[0]; Delta:=Integer(Work.ScanLine[1])-Integer(SourceLine); DestLine:=Target.ScanLine[0]; DestDelta:=Integer(Target.ScanLine[1])-Integer(DestLine); for K:=0 to pred(TargetWidth) do begin DestPixel:=Pointer(DestLine); FillLineChache(SourceHeight,Delta,SourceLine,G); for I:=0 to pred(TargetHeight) do with ContributorList[I] do begin DestPixel^:=ApplyContributors(N,ContributorList[I].Contributors,G); Inc(Integer(DestPixel),DestDelta); end; Inc(SourceLine); Inc(DestLine); end; // free the memory allocated for vertical filter weights for I:=0 to pred(TargetHeight) do ContributorList[I].Contributors:=nil; // this one is done automatically on exit, but is here for completeness ContributorList:=nil; finally Work.Free; G.CurrentLineR:=nil; G.CurrentLineG:=nil; G.CurrentLineB:=nil; end; end; //---------------------------------------------------------------------------------------------------------------------- procedure Stretch(NewWidth,NewHeight: cardinal; Filter: TResamplingFilter; Radius: single; Source,Target: PBitmap; G: PGraphicExGraphic); // Scales the source bitmap to the given size (NewWidth, NewHeight) and stores the Result in Target. // Filter describes the filter function to be applied and Radius the size of the filter area. // Is Radius = 0 then the recommended filter area will be used (see DefaultFilterRadius). begin if Radius=0 then Radius:=DefaultFilterRadius[Filter]; Target.Handle:=0; Target.PixelFormat:=pf24Bit; Target.Width:=NewWidth; Target.Height:=NewHeight; Source.PixelFormat:=pf24Bit; DoStretch(FilterList[Filter],Radius,Source,Target,G); end; //---------------------------------------------------------------------------------------------------------------------- procedure Stretch(NewWidth,NewHeight: cardinal; Filter: TResamplingFilter; Radius: single; Source: PBitmap; G: PGraphicExGraphic); var Target: PBitmap; begin if Radius=0 then Radius:=DefaultFilterRadius[Filter]; Target:=NewBitmap(0,0); try Target.PixelFormat:=pf24Bit; Target.Width:=NewWidth; Target.Height:=NewHeight; Source.PixelFormat:=pf24Bit; DoStretch(FilterList[Filter],Radius,Source,Target,G); Source.Assign(Target); finally Target.Free; end; end; //----------------- support functions for image loading ---------------------------------------------------------------- procedure SwapShort(P: PWord; Count: cardinal); // swaps high and low byte of 16 bit values // EAX contains P, EDX contains Count asm @@Loop: MOV CX,[EAX] XCHG CH,CL MOV [EAX],CX ADD EAX,2 DEC EDX JNZ @@Loop end; //---------------------------------------------------------------------------------------------------------------------- procedure SwapLong(P: PInteger; Count: cardinal); overload; // swaps high and low bytes of 32 bit values // EAX contains P, EDX contains Count asm @@Loop: MOV ECX,[EAX] BSWAP ECX MOV [EAX],ECX ADD EAX,4 DEC EDX JNZ @@Loop end; //---------------------------------------------------------------------------------------------------------------------- function SwapLong(Value: cardinal): cardinal; overload; // swaps high and low bytes of the given 32 bit value asm BSWAP EAX end; //----------------- various conversion routines ------------------------------------------------------------------------ procedure Depredict1(P: pointer; Count: cardinal); // EAX contains P and EDX Count asm @@1: MOV CL,[EAX] ADD [EAX+1],CL INC EAX DEC EDX JNZ @@1 end; //---------------------------------------------------------------------------------------------------------------------- procedure Depredict3(P: pointer; Count: cardinal); // EAX contains P and EDX Count asm MOV ECX,EDX SHL ECX,1 ADD ECX,EDX // 3*Count @@1: MOV DL,[EAX] ADD [EAX+3],DL INC EAX DEC ECX JNZ @@1 end; //---------------------------------------------------------------------------------------------------------------------- procedure Depredict4(P: pointer; Count: cardinal); // EAX contains P and EDX Count asm SHL EDX,2 // 4*Count @@1: MOV CL,[EAX] ADD [EAX+4],CL INC EAX DEC EDX JNZ @@1 end; //----------------- TGraphicExGraphic ---------------------------------------------------------------------------------- constructor TGraphicExGraphic.Create; begin inherited; FColorManager:=NewColorManager; end; function TGraphicExGraphic.GetWidth: Integer; begin Result := ImageProperties.Width; end; function TGraphicExGraphic.GetHeight: Integer; begin Result := ImageProperties.Height; end; //---------------------------------------------------------------------------------------------------------------------- destructor TGraphicExGraphic.Destroy; begin FColorManager.Free; if Assigned(FBitmap) then FBitmap.Free; inherited; end; //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class function TGraphicExGraphic.CanLoad(const Filename: string): boolean; var Stream: PStream; begin Stream:=NewReadFileStream(Filename); try Result:=CanLoad(Stream); finally Stream.Free; end; end; {$ENDIF} //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TGraphicExGraphic.CanLoad(Stream: PStream): boolean; // Descentants have to override this method and return True if they consider the data in Stream // as loadable by the particular class. // Note: Make sure the stream position is the same on exit as it was on enter! begin Result:=False; end; //---------------------------------------------------------------------------------------------------------------------- function TGraphicExGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; // Initializes the internal image properties structure. // Descentants must override this method to fill in the actual values. // Result is always False to show there is no image to load. begin Finalize(FImageProperties); ZeroMemory(@FImageProperties,sizeof(FImageProperties)); FImageProperties.FileGamma:=1; Result:=False; end; //----------------- TAutodeskGraphic ----------------------------------------------------------------------------------- type TAutodeskHeader = packed record Width,Height,XCoord,YCoord: word; Depth,Compression: byte; DataSize: cardinal; Reserved: array[0..15] of byte; end; //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TAutodeskGraphic.CanLoad(Stream: PStream): boolean; var FileID: word; Header: TAutodeskHeader; LastPosition: cardinal; begin Result:=(Stream.Size-Stream.Position)>(sizeof(FileID)+sizeof(Header)); if Result then begin LastPosition:=Stream.Position; Stream.Read(FileID,sizeof(FileID)); Result:=FileID=$9119; if Result then begin // read image dimensions Stream.Read(Header,sizeof(Header)); Result:=(Header.Depth=8) and (Header.Compression=0); end; Stream.Position:=LastPosition; end; end; //---------------------------------------------------------------------------------------------------------------------- procedure TAutodeskGraphic.LoadFromStream(Stream: PStream); var FileID: word; FileHeader: TAutodeskHeader; I: integer; Pal: array[0..767] of byte; begin // free previous image if Assigned(FBitmap) then FBitmap.Free; FBasePosition:=Stream.Position; if ReadImageProperties(Stream,0) then begin Stream.Position:=FBasePosition; Stream.Read(FileID,2); // read image dimensions Stream.Read(FileHeader,sizeof(FileHeader)); // read palette data Stream.Read(Pal,sizeof(Pal)); // setup bitmap properties FBitmap:=NewBitmap(FileHeader.Width,FileHeader.Height); FBitmap.PixelFormat:=pf8Bit; // assign palette data for I:=0 to 255 do FBitmap.DIBPalEntries[I]:=RGB(Pal[I*3+2] shl 2,Pal[I*3+1] shl 2,Pal[I*3] shl 2); // finally read image data for I:=0 to pred(FBitmap.Height) do Stream.Read(FBitmap.ScanLine[I]^,FileHeader.Width); end else GraphicExError(1{gesInvalidImage},['Autodesk']); end; //---------------------------------------------------------------------------------------------------------------------- function TAutodeskGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; var FileID: word; Header: TAutodeskHeader; begin Result:=inherited ReadImageProperties(Stream,ImageIndex); with FImageProperties do begin Stream.Read(FileID,2); if FileID=$9119 then begin // read image dimensions Stream.Read(Header,sizeof(Header)); ColorScheme:=csIndexed; Width:=Header.Width; Height:=Header.Height; BitsPerSample:=8; SamplesPerPixel:=1; BitsPerPixel:=8; Compression:=ctNone; Result:=True; end; end; end; //----------------- TSGIGraphic ---------------------------------------------------------------------------------------- const SGIMagic = 474; SGI_COMPRESSION_VERBATIM = 0; SGI_COMPRESSION_RLE = 1; type TSGIHeader = packed record Magic: smallint; // IRIS image file magic number Storage, // Storage format BPC: byte; // Number of bytes per pixel channel (1 or 2) Dimension: word; // Number of dimensions // 1 - one single scanline (and one channel) of length XSize // 2 - two dimensional (one channel) of size XSize x YSize // 3 - three dimensional (ZSize channels) of size XSize x YSize XSize, // width of image YSize, // height of image ZSize: word; // number of channels/planes in image (3 for RGB, 4 for RGBA etc.) PixMin, // Minimum pixel value PixMax: cardinal; // Maximum pixel value Dummy: cardinal; // ignored ImageName: array[0..79] of Char; ColorMap: integer; // Colormap ID // 0 - default, almost all images are stored with this flag // 1 - dithered, only one channel of data (pixels are packed), obsolete // 2 - screen (palette) image, obsolete // 3 - no image data, palette only, not displayable Dummy2: array[0..403] of byte; // ignored end; //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TSGIGraphic.CanLoad(Stream: PStream): boolean; // returns True if the data in Stream represents a graphic which can be loaded by this class var Header: TSGIHeader; LastPosition: cardinal; begin Result:=(Stream.Size-Stream.Position)>sizeof(TSGIHeader); if Result then begin LastPosition:=Stream.Position; Stream.Read(Header,sizeof(Header)); // one number as check is too unreliable, hence we take some more fields into the check Result:=(System.Swap(Header.Magic)=SGIMagic) and (Header.BPC in [1,2]) and (System.Swap(Header.Dimension) in [1..3]); Stream.Position:=LastPosition; end; end; //---------------------------------------------------------------------------------------------------------------------- procedure TSGIGraphic.ReadAndDecode(Red,Green,Blue,Alpha: pointer; Row,BPC: cardinal); var Count: cardinal; RawBuffer: pointer; begin with FImageProperties do // compressed image? if Assigned(FDecoder) then begin if Assigned(Red) then begin FStream.Position:=FBasePosition+FRowStart[Row+0*Height]; Count:=BPC*FRowSize[Row+0*Height]; GetMem(RawBuffer,Count); try FStream.Read(RawBuffer^,Count); FDecoder.Decode(RawBuffer,Red,Count,Width); finally if Assigned(RawBuffer) then FreeMem(RawBuffer); end; end; if Assigned(Green) then begin FStream.Position:=FBasePosition+FRowStart[Row+1*Height]; Count:=BPC*FRowSize[Row+1*Height]; GetMem(RawBuffer,Count); try FStream.Read(RawBuffer^,Count); FDecoder.Decode(RawBuffer,Green,Count,Width); finally if Assigned(RawBuffer) then FreeMem(RawBuffer); end; end; if Assigned(Blue) then begin FStream.Position:=FBasePosition+FRowStart[Row+2*Height]; Count:=BPC*FRowSize[Row+2*Height]; GetMem(RawBuffer,Count); try FStream.Read(RawBuffer^,Count); FDecoder.Decode(RawBuffer,Blue,Count,Width); finally if Assigned(RawBuffer) then FreeMem(RawBuffer); end; end; if Assigned(Alpha) then begin FStream.Position:=FBasePosition+FRowStart[Row+3*Height]; Count:=BPC*FRowSize[Row+3*Height]; GetMem(RawBuffer,Count); try FStream.Read(RawBuffer^,Count); FDecoder.Decode(RawBuffer,Alpha,Count,Width); finally if Assigned(RawBuffer) then FreeMem(RawBuffer); end; end; end else begin if Assigned(Red) then begin FStream.Position:=FBasePosition+512+(Row*Width); FStream.Read(Red^,BPC*Width); end; if Assigned(Green) then begin FStream.Position:=FBasePosition+512+(Row*Width)+(Width*Height); FStream.Read(Green^,BPC*Width); end; if Assigned(Blue) then begin FStream.Position:=FBasePosition+512+(Row*Width)+(2*Width*Height); FStream.Read(Blue^,BPC*Width); end; if Assigned(Alpha) then begin FStream.Position:=FBasePosition+512+(Row*Width)+(3*Width*Height); FStream.Read(Alpha^,BPC*Width); end; end; end; //---------------------------------------------------------------------------------------------------------------------- procedure TSGIGraphic.LoadFromStream(Stream: PStream); var Y,Count: cardinal; RedBuffer,GreenBuffer,BlueBuffer,AlphaBuffer: pointer; Header: TSGIHeader; begin // free previous image if Assigned(FBitmap) then FBitmap.Free; // keep stream reference and start position for seek operations FStream:=Stream; FBasePosition:=Stream.Position; if ReadImageProperties(Stream,0) then begin with FImageProperties do begin Stream.Position:=FBasePosition; // read header again, we need some additional information Stream.Read(Header,sizeof(Header)); // SGI images are always stored in big endian style ColorManager.SourceOptions:=[coNeedByteSwap]; with Header do ColorMap:=SwapLong(ColorMap); if Compression=ctRLE then begin Count:=Height*SamplesPerPixel; GetMem(FRowStart,Count*4); GetMem(FRowSize,Count*4); // read line starts and sizes from stream Stream.Read(FRowStart^,Count*sizeof(Cardinal)); SwapLong(Pointer(FRowStart),Count); Stream.Read(FRowSize^,Count*sizeof(Cardinal)); SwapLong(Pointer(FRowSize),Count); {$IFDEF NOCLASSES} new( FDecoder, Create(BitsPerSample) ); {$ELSE} FDecoder:=TSGIRLEDecoder.Create(BitsPerSample); {$ENDIF} end else begin FDecoder:=nil; end; // set pixel format before size to avoid possibly large conversion operation ColorManager.SourceBitsPerSample:=BitsPerSample; ColorManager.TargetBitsPerSample:=8; ColorManager.SourceSamplesPerPixel:=SamplesPerPixel; ColorManager.TargetSamplesPerPixel:=SamplesPerPixel; ColorManager.SourceColorScheme:=ColorScheme; case ColorScheme of csRGBA: ColorManager.TargetColorScheme:=csBGRA; csRGB: ColorManager.TargetColorScheme:=csBGR; else ColorManager.TargetColorScheme:=csIndexed; end; FBitmap:=NewBitmap(Width,Height); FBitmap.PixelFormat:=ColorManager.TargetPixelFormat; RedBuffer:=nil; GreenBuffer:=nil; BlueBuffer:=nil; AlphaBuffer:=nil; try Count:=(BitsPerPixel div 8)*Width; // read lines and put them into the bitmap case ColorScheme of csRGBA: begin GetMem(RedBuffer,Count); GetMem(GreenBuffer,Count); GetMem(BlueBuffer,Count); GetMem(AlphaBuffer,Count); for Y:=0 to pred(Height) do begin ReadAndDecode(RedBuffer,GreenBuffer,BlueBuffer,AlphaBuffer,Y,Header.BPC); ColorManager.ConvertRow([RedBuffer,GreenBuffer,BlueBuffer,AlphaBuffer],FBitmap.ScanLine[Height-Y-1],Width,$FF); end; end; csRGB: begin GetMem(RedBuffer,Count); GetMem(GreenBuffer,Count); GetMem(BlueBuffer,Count); for Y:=0 to pred(Height) do begin ReadAndDecode(RedBuffer,GreenBuffer,BlueBuffer,nil,Y,Header.BPC); ColorManager.ConvertRow([RedBuffer,GreenBuffer,BlueBuffer],FBitmap.ScanLine[Height-Y-1],Width,$FF); end; end; else // any other format is interpreted as being 256 gray scales ColorManager.CreateGrayscalePalette(FBitmap,False); for Y:=0 to pred(Height) do begin ReadAndDecode(FBitmap.ScanLine[Height-Y-1],nil,nil,nil,Y,Header.BPC); end; end; finally if Assigned(RedBuffer) then FreeMem(RedBuffer); if Assigned(GreenBuffer) then FreeMem(GreenBuffer); if Assigned(BlueBuffer) then FreeMem(BlueBuffer); if Assigned(AlphaBuffer) then FreeMem(AlphaBuffer); FDecoder.Free; end; end; end else GraphicExError(1{gesInvalidImage},['SGI, BW or RGB(A)']); FreeMem(FRowStart {,Count*4} ); FreeMem(FRowSize {,Count*4} ); end; //---------------------------------------------------------------------------------------------------------------------- function TSGIGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; var Header: TSGIHeader; begin Result:=inherited ReadImageProperties(Stream,ImageIndex); with FImageProperties do begin Stream.Read(Header,sizeof(Header)); if System.Swap(Header.Magic)=SGIMagic then begin Options:=[ioBigEndian]; BitsPerSample:=Header.BPC*8; Width:=System.Swap(Header.XSize); Height:=System.Swap(Header.YSize); SamplesPerPixel:=System.Swap(Header.ZSize); case SamplesPerPixel of 4: ColorScheme:=csRGBA; 3: ColorScheme:=csRGB; else // all other is considered as being 8 bit gray scale ColorScheme:=csIndexed; end; BitsPerPixel:=BitsPerSample*SamplesPerPixel; if Header.Storage=SGI_COMPRESSION_RLE then Compression:=ctRLE else Compression:=ctNone; Result:=True; end; end; end; //----------------- TTIFFGraphic --------------------------------------------------------------------------------------- const // TIFF tags TIFFTAG_SUBFILETYPE = 254; // subfile data descriptor FILETYPE_REDUCEDIMAGE = $1; // reduced resolution version FILETYPE_PAGE = $2; // one page of many FILETYPE_MASK = $4; // transparency mask TIFFTAG_OSUBFILETYPE = 255; // kind of data in subfile (obsolete by revision 5.0) OFILETYPE_IMAGE = 1; // full resolution image data OFILETYPE_REDUCEDIMAGE = 2; // reduced size image data OFILETYPE_PAGE = 3; // one page of many TIFFTAG_IMAGEWIDTH = 256; // image width in pixels TIFFTAG_IMAGELENGTH = 257; // image height in pixels TIFFTAG_BITSPERSAMPLE = 258; // bits per channel (sample) TIFFTAG_COMPRESSION = 259; // data compression technique COMPRESSION_NONE = 1; // dump mode COMPRESSION_CCITTRLE = 2; // CCITT modified Huffman RLE COMPRESSION_CCITTFAX3 = 3; // CCITT Group 3 fax encoding COMPRESSION_CCITTFAX4 = 4; // CCITT Group 4 fax encoding COMPRESSION_LZW = 5; // Lempel-Ziv & Welch COMPRESSION_OJPEG = 6; // 6.0 JPEG (old version) COMPRESSION_JPEG = 7; // JPEG DCT compression (new version) COMPRESSION_ADOBE_DEFLATE = 8; // new id but same as COMPRESSION_DEFLATE COMPRESSION_NEXT = 32766; // next 2-bit RLE COMPRESSION_CCITTRLEW = 32771; // modified Huffman with word alignment COMPRESSION_PACKBITS = 32773; // Macintosh RLE COMPRESSION_THUNDERSCAN = 32809; // ThunderScan RLE // codes 32895-32898 are reserved for ANSI IT8 TIFF/IT COMPRESSION_DCS = 32947; // Kodak DCS encoding COMPRESSION_JBIG = 34661; // ISO JBIG TIFFTAG_PHOTOMETRIC = 262; // photometric interpretation PHOTOMETRIC_MINISWHITE = 0; // min value is white PHOTOMETRIC_MINISBLACK = 1; // min value is black PHOTOMETRIC_RGB = 2; // RGB color model PHOTOMETRIC_PALETTE = 3; // color map indexed PHOTOMETRIC_MASK = 4; // holdout mask PHOTOMETRIC_SEPARATED = 5; // color separations PHOTOMETRIC_YCBCR = 6; // CCIR 601 PHOTOMETRIC_CIELAB = 8; // 1976 CIE L*a*b* TIFFTAG_THRESHHOLDING = 263; // thresholding used on data (obsolete by revision 5.0) THRESHHOLD_BILEVEL = 1; // b&w art scan THRESHHOLD_HALFTONE = 2; // or dithered scan THRESHHOLD_ERRORDIFFUSE = 3; // usually floyd-steinberg TIFFTAG_CELLWIDTH = 264; // dithering matrix width (obsolete by revision 5.0) TIFFTAG_CELLLENGTH = 265; // dithering matrix height (obsolete by revision 5.0) TIFFTAG_FILLORDER = 266; // data order within a Byte FILLORDER_MSB2LSB = 1; // most significant -> least FILLORDER_LSB2MSB = 2; // least significant -> most TIFFTAG_DOCUMENTNAME = 269; // name of doc. image is from TIFFTAG_IMAGEDESCRIPTION = 270; // info about image TIFFTAG_MAKE = 271; // scanner manufacturer name TIFFTAG_MODEL = 272; // scanner model name/number TIFFTAG_STRIPOFFSETS = 273; // Offsets to data strips TIFFTAG_ORIENTATION = 274; // image FOrientation (obsolete by revision 5.0) ORIENTATION_TOPLEFT = 1; // row 0 top, col 0 lhs ORIENTATION_TOPRIGHT = 2; // row 0 top, col 0 rhs ORIENTATION_BOTRIGHT = 3; // row 0 bottom, col 0 rhs ORIENTATION_BOTLEFT = 4; // row 0 bottom, col 0 lhs ORIENTATION_LEFTTOP = 5; // row 0 lhs, col 0 top ORIENTATION_RIGHTTOP = 6; // row 0 rhs, col 0 top ORIENTATION_RIGHTBOT = 7; // row 0 rhs, col 0 bottom ORIENTATION_LEFTBOT = 8; // row 0 lhs, col 0 bottom TIFFTAG_SAMPLESPERPIXEL = 277; // samples per pixel TIFFTAG_ROWSPERSTRIP = 278; // rows per strip of data TIFFTAG_STRIPBYTECOUNTS = 279; // bytes counts for strips TIFFTAG_MINSAMPLEVALUE = 280; // minimum sample value (obsolete by revision 5.0) TIFFTAG_MAXSAMPLEVALUE = 281; // maximum sample value (obsolete by revision 5.0) TIFFTAG_XRESOLUTION = 282; // pixels/resolution in x TIFFTAG_YRESOLUTION = 283; // pixels/resolution in y TIFFTAG_PLANARCONFIG = 284; // storage organization PLANARCONFIG_CONTIG = 1; // single image plane PLANARCONFIG_SEPARATE = 2; // separate planes of data TIFFTAG_PAGENAME = 285; // page name image is from TIFFTAG_XPOSITION = 286; // x page offset of image lhs TIFFTAG_YPOSITION = 287; // y page offset of image lhs TIFFTAG_FREEOFFSETS = 288; // byte offset to free block (obsolete by revision 5.0) TIFFTAG_FREEBYTECOUNTS = 289; // sizes of free blocks (obsolete by revision 5.0) TIFFTAG_GRAYRESPONSEUNIT = 290; // gray scale curve accuracy GRAYRESPONSEUNIT_10S = 1; // tenths of a unit GRAYRESPONSEUNIT_100S = 2; // hundredths of a unit GRAYRESPONSEUNIT_1000S = 3; // thousandths of a unit GRAYRESPONSEUNIT_10000S = 4; // ten-thousandths of a unit GRAYRESPONSEUNIT_100000S = 5; // hundred-thousandths TIFFTAG_GRAYRESPONSECURVE = 291; // gray scale response curve TIFFTAG_GROUP3OPTIONS = 292; // 32 flag bits GROUP3OPT_2DENCODING = $1; // 2-dimensional coding GROUP3OPT_UNCOMPRESSED = $2; // data not compressed GROUP3OPT_FILLBITS = $4; // fill to byte boundary TIFFTAG_GROUP4OPTIONS = 293; // 32 flag bits GROUP4OPT_UNCOMPRESSED = $2; // data not compressed TIFFTAG_RESOLUTIONUNIT = 296; // units of resolutions RESUNIT_NONE = 1; // no meaningful units RESUNIT_INCH = 2; // english RESUNIT_CENTIMETER = 3; // metric TIFFTAG_PAGENUMBER = 297; // page numbers of multi-page TIFFTAG_COLORRESPONSEUNIT = 300; // color curve accuracy COLORRESPONSEUNIT_10S = 1; // tenths of a unit COLORRESPONSEUNIT_100S = 2; // hundredths of a unit COLORRESPONSEUNIT_1000S = 3; // thousandths of a unit COLORRESPONSEUNIT_10000S = 4; // ten-thousandths of a unit COLORRESPONSEUNIT_100000S = 5; // hundred-thousandths TIFFTAG_TRANSFERFUNCTION = 301; // colorimetry info TIFFTAG_SOFTWARE = 305; // name & release TIFFTAG_DATETIME = 306; // creation date and time TIFFTAG_ARTIST = 315; // creator of image TIFFTAG_HOSTCOMPUTER = 316; // machine where created TIFFTAG_PREDICTOR = 317; // prediction scheme w/ LZW PREDICTION_NONE = 1; // no prediction scheme used before coding PREDICTION_HORZ_DIFFERENCING = 2; // horizontal differencing prediction scheme used TIFFTAG_WHITEPOINT = 318; // image white point TIFFTAG_PRIMARYCHROMATICITIES = 319; // primary chromaticities TIFFTAG_COLORMAP = 320; // RGB map for pallette image TIFFTAG_HALFTONEHINTS = 321; // highlight+shadow info TIFFTAG_TILEWIDTH = 322; // rows/data tile TIFFTAG_TILELENGTH = 323; // cols/data tile TIFFTAG_TILEOFFSETS = 324; // offsets to data tiles TIFFTAG_TILEBYTECOUNTS = 325; // Byte counts for tiles TIFFTAG_BADFAXLINES = 326; // lines w/ wrong pixel count TIFFTAG_CLEANFAXDATA = 327; // regenerated line info CLEANFAXDATA_CLEAN = 0; // no errors detected CLEANFAXDATA_REGENERATED = 1; // receiver regenerated lines CLEANFAXDATA_UNCLEAN = 2; // uncorrected errors exist TIFFTAG_CONSECUTIVEBADFAXLINES = 328; // max consecutive bad lines TIFFTAG_SUBIFD = 330; // subimage descriptors TIFFTAG_INKSET = 332; // inks in separated image INKSET_CMYK = 1; // cyan-magenta-yellow-black TIFFTAG_INKNAMES = 333; // ascii names of inks TIFFTAG_DOTRANGE = 336; // 0% and 100% dot codes TIFFTAG_TARGETPRINTER = 337; // separation target TIFFTAG_EXTRASAMPLES = 338; // info about extra samples EXTRASAMPLE_UNSPECIFIED = 0; // unspecified data EXTRASAMPLE_ASSOCALPHA = 1; // associated alpha data EXTRASAMPLE_UNASSALPHA = 2; // unassociated alpha data TIFFTAG_SAMPLEFORMAT = 339; // data sample format SAMPLEFORMAT_UINT = 1; // unsigned integer data SAMPLEFORMAT_INT = 2; // signed integer data SAMPLEFORMAT_IEEEFP = 3; // IEEE floating point data SAMPLEFORMAT_VOID = 4; // untyped data TIFFTAG_SMINSAMPLEVALUE = 340; // variable MinSampleValue TIFFTAG_SMAXSAMPLEVALUE = 341; // variable MaxSampleValue TIFFTAG_JPEGTABLES = 347; // JPEG table stream // Tags 512-521 are obsoleted by Technical Note #2 which specifies a revised JPEG-in-TIFF scheme. TIFFTAG_JPEGPROC = 512; // JPEG processing algorithm JPEGPROC_BASELINE = 1; // baseline sequential JPEGPROC_LOSSLESS = 14; // Huffman coded lossless TIFFTAG_JPEGIFOFFSET = 513; // Pointer to SOI marker TIFFTAG_JPEGIFBYTECOUNT = 514; // JFIF stream length TIFFTAG_JPEGRESTARTINTERVAL = 515; // restart interval length TIFFTAG_JPEGLOSSLESSPREDICTORS = 517; // lossless proc predictor TIFFTAG_JPEGPOINTTRANSFORM = 518; // lossless point transform TIFFTAG_JPEGQTABLES = 519; // Q matrice offsets TIFFTAG_JPEGDCTABLES = 520; // DCT table offsets TIFFTAG_JPEGACTABLES = 521; // AC coefficient offsets TIFFTAG_YCBCRCOEFFICIENTS = 529; // RGB -> YCbCr transform TIFFTAG_YCBCRSUBSAMPLING = 530; // YCbCr subsampling factors TIFFTAG_YCBCRPOSITIONING = 531; // subsample positioning YCBCRPOSITION_CENTERED = 1; // as in PostScript Level 2 YCBCRPOSITION_COSITED = 2; // as in CCIR 601-1 TIFFTAG_REFERENCEBLACKWHITE = 532; // colorimetry info // tags 32952-32956 are private tags registered to Island Graphics TIFFTAG_REFPTS = 32953; // image reference points TIFFTAG_REGIONTACKPOINT = 32954; // region-xform tack point TIFFTAG_REGIONWARPCORNERS = 32955; // warp quadrilateral TIFFTAG_REGIONAFFINE = 32956; // affine transformation mat // tags 32995-32999 are private tags registered to SGI TIFFTAG_MATTEING = 32995; // use ExtraSamples TIFFTAG_DATATYPE = 32996; // use SampleFormat TIFFTAG_IMAGEDEPTH = 32997; // z depth of image TIFFTAG_TILEDEPTH = 32998; // z depth/data tile // tags 33300-33309 are private tags registered to Pixar // // TIFFTAG_PIXAR_IMAGEFULLWIDTH and TIFFTAG_PIXAR_IMAGEFULLLENGTH // are set when an image has been cropped out of a larger image. // They reflect the size of the original uncropped image. // The TIFFTAG_XPOSITION and TIFFTAG_YPOSITION can be used // to determine the position of the smaller image in the larger one. TIFFTAG_PIXAR_IMAGEFULLWIDTH = 33300; // full image size in x TIFFTAG_PIXAR_IMAGEFULLLENGTH = 33301; // full image size in y // tag 33405 is a private tag registered to Eastman Kodak TIFFTAG_WRITERSERIALNUMBER = 33405; // device serial number // tag 33432 is listed in the 6.0 spec w/ unknown ownership TIFFTAG_COPYRIGHT = 33432; // copyright string // 34016-34029 are reserved for ANSI IT8 TIFF/IT YCbCr convert? JPEGCOLORMODE_RAW = $0000; // no conversion (default) JPEGCOLORMODE_RGB = $0001; // do auto conversion TIFFTAG_JPEGTABLESMODE = 65539; // What to put in JPEGTables JPEGTABLESMODE_QUANT = $0001; // include quantization tbls JPEGTABLESMODE_HUFF = $0002; // include Huffman tbls // Note: default is JPEGTABLESMODE_QUANT or JPEGTABLESMODE_HUFF TIFFTAG_FAXFILLFUNC = 65540; // G3/G4 fill function TIFFTAG_PIXARLOGDATAFMT = 65549; // PixarLogCodec I/O data sz PIXARLOGDATAFMT_8BIT = 0; // regular u_char samples PIXARLOGDATAFMT_8BITABGR = 1; // ABGR-order u_chars PIXARLOGDATAFMT_11BITLOG = 2; // 11-bit log-encoded (raw) PIXARLOGDATAFMT_12BITPICIO = 3; // as per PICIO (1.0==2048) PIXARLOGDATAFMT_16BIT = 4; // signed short samples PIXARLOGDATAFMT_FLOAT = 5; // IEEE float samples // 65550-65556 are allocated to Oceana Matrix TIFFTAG_DCSIMAGERTYPE = 65550; // imager model & filter DCSIMAGERMODEL_M3 = 0; // M3 chip (1280 x 1024) DCSIMAGERMODEL_M5 = 1; // M5 chip (1536 x 1024) DCSIMAGERMODEL_M6 = 2; // M6 chip (3072 x 2048) DCSIMAGERFILTER_IR = 0; // infrared filter DCSIMAGERFILTER_MONO = 1; // monochrome filter DCSIMAGERFILTER_CFA = 2; // color filter array DCSIMAGERFILTER_OTHER = 3; // other filter TIFFTAG_DCSINTERPMODE = 65551; // interpolation mode DCSINTERPMODE_NORMAL = $0; // whole image, default DCSINTERPMODE_PREVIEW = $1; // preview of image (384x256) TIFFTAG_DCSBALANCEARRAY = 65552; // color balance values TIFFTAG_DCSCORRECTMATRIX = 65553; // color correction values TIFFTAG_DCSGAMMA = 65554; // gamma value TIFFTAG_DCSTOESHOULDERPTS = 65555; // toe & shoulder points TIFFTAG_DCSCALIBRATIONFD = 65556; // calibration file desc // Note: quality level is on the ZLIB 1-9 scale. Default value is -1 TIFFTAG_ZIPQUALITY = 65557; // compression quality level TIFFTAG_PIXARLOGQUALITY = 65558; // PixarLog uses same scale // TIFF data types TIFF_NOTYPE = 0; // placeholder TIFF_BYTE = 1; // 8-bit unsigned integer TIFF_ASCII = 2; // 8-bit bytes w/ last byte null TIFF_SHORT = 3; // 16-bit unsigned integer TIFF_LONG = 4; // 32-bit unsigned integer TIFF_RATIONAL = 5; // 64-bit unsigned fraction TIFF_SBYTE = 6; // 8-bit signed integer TIFF_UNDEFINED = 7; // 8-bit untyped data TIFF_SSHORT = 8; // 16-bit signed integer TIFF_SLONG = 9; // 32-bit signed integer TIFF_SRATIONAL = 10; // 64-bit signed fraction TIFF_FLOAT = 11; // 32-bit IEEE floating point TIFF_DOUBLE = 12; // 64-bit IEEE floating point TIFF_BIGENDIAN = $4D4D; TIFF_LITTLEENDIAN = $4949; TIFF_VERSION = 42; type TTIFFHeader = packed record ByteOrder: word; Version: word; FirstIFD: cardinal; end; //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TTIFFGraphic.CanLoad(Stream: PStream): boolean; var Header: TTIFFHeader; LastPosition: Cardinal; begin Result:=(Stream.Size-Stream.Position)>sizeof(Header); if Result then begin LastPosition:=Stream.Position; Stream.Read(Header,sizeof(Header)); Result:=(Header.ByteOrder=TIFF_BIGENDIAN) or (Header.ByteOrder=TIFF_LITTLEENDIAN); if Result then begin if Header.ByteOrder=TIFF_BIGENDIAN then begin Header.Version:=System.Swap(Header.Version); Header.FirstIFD:=SwapLong(Header.FirstIFD); end; Result:=(Header.Version=TIFF_VERSION) and (Integer(Header.FirstIFD)0) then begin // prepare value list SetLength(Values,FIFD[Index].DataLength); // determine whether the data fits into 4 bytes Value:=DataTypeToSize[FIFD[Index].DataType]*FIFD[Index].DataLength; // data fits into one cardinal -> extract it if Value<=4 then begin Shift:=DataTypeToSize[FIFD[Index].DataType]*8; Value:=FIFD[Index].Offset; for I:=0 to pred(FIFD[Index].DataLength) do begin case FIFD[Index].DataType of TIFF_BYTE: Values[I]:=Byte(Value); TIFF_SHORT, TIFF_SSHORT: // no byte swap needed here because values in the IFD are already swapped // (if necessary at all) Values[I]:=Word(Value); TIFF_LONG, TIFF_SLONG: Values[I]:=Value; end; Value:=Value shr Shift; end; end else begin // data of this tag does not fit into one 32 bits value Stream.Position:=FBasePosition+FIFD[Index].Offset; // bytes sized data can be read directly instead of looping through the array if FIFD[Index].DataType in [TIFF_BYTE,TIFF_ASCII,TIFF_SBYTE,TIFF_UNDEFINED] then Stream.Read(Values[0],Value) else begin for I:=0 to High(Values) do begin Stream.Read(Value,DataTypeToSize[FIFD[Index].DataType]); case FIFD[Index].DataType of TIFF_BYTE: Value:=Byte(Value); TIFF_SHORT, TIFF_SSHORT: begin if ioBigEndian in FImageProperties.Options then Value:=System.Swap(Word(Value)) else Value:=Word(Value); end; TIFF_LONG, TIFF_SLONG: if ioBigEndian in FImageProperties.Options then Value:=SwapLong(Value); end; Values[I]:=Value; end; end; end; end; end; //---------------------------------------------------------------------------------------------------------------------- procedure TTIFFGraphic.GetValueList(Stream: PStream; ATag: cardinal; var Values: TCardinalArray); // returns the values of the IFD entry indicated by Tag var Index,Value,Shift: cardinal; I: integer; begin // Values:=nil; if FindTag(ATag,Index) and (FIFD[Index].DataLength>0) then begin // prepare value list SetLength(Values,FIFD[Index].DataLength); // determine whether the data fits into 4 bytes Value:=DataTypeToSize[FIFD[Index].DataType]*FIFD[Index].DataLength; // data fits into one cardinal -> extract it if Value<=4 then begin Shift:=DataTypeToSize[FIFD[Index].DataType]*8; Value:=FIFD[Index].Offset; for I:=0 to pred(FIFD[Index].DataLength) do begin case FIFD[Index].DataType of TIFF_BYTE, TIFF_ASCII, TIFF_SBYTE, TIFF_UNDEFINED: Values[I]:=Byte(Value); TIFF_SHORT, TIFF_SSHORT: // no byte swap needed here because values in the IFD are already swapped // (if necessary at all) Values[I]:=Word(Value); TIFF_LONG, TIFF_SLONG: Values[I]:=Value; end; Value:=Value shr Shift; end; end else begin // data of this tag does not fit into one 32 bits value Stream.Position:=FBasePosition+FIFD[Index].Offset; // even bytes sized data must be read by the loop as it is expanded to cardinals for I:=0 to High(Values) do begin Stream.Read(Value,DataTypeToSize[FIFD[Index].DataType]); case FIFD[Index].DataType of TIFF_BYTE: Value:=Byte(Value); TIFF_SHORT, TIFF_SSHORT: begin if ioBigEndian in FImageProperties.Options then Value:=System.Swap(Word(Value)) else Value:=Word(Value); end; TIFF_LONG, TIFF_SLONG: if ioBigEndian in FImageProperties.Options then Value:=SwapLong(Value); end; Values[I]:=Value; end; end; end; end; //---------------------------------------------------------------------------------------------------------------------- procedure TTIFFGraphic.GetValueList(Stream: PStream; ATag: Cardinal; var Values: TFloatArray); // returns the values of the IFD entry indicated by Tag var Index,Shift,IntValue: cardinal; Value: single; I: integer; IntNominator,IntDenominator,FloatNominator,FloatDenominator: cardinal; begin // Values:=nil; if FindTag(ATag,Index) and (FIFD[Index].DataLength>0) then begin // prepare value list SetLength(Values,FIFD[Index].DataLength); // determine whether the data fits into 4 bytes Value:=DataTypeToSize[FIFD[Index].DataType]*FIFD[Index].DataLength; // data fits into one cardinal -> extract it if Value<=4 then begin Shift:=DataTypeToSize[FIFD[Index].DataType]*8; IntValue:=FIFD[Index].Offset; for I:=0 to pred(FIFD[Index].DataLength) do begin case FIFD[Index].DataType of TIFF_BYTE, TIFF_ASCII, TIFF_SBYTE, TIFF_UNDEFINED: Values[I]:=Byte(IntValue); TIFF_SHORT, TIFF_SSHORT: // no byte swap needed here because values in the IFD are already swapped // (if necessary at all) Values[I]:=Word(IntValue); TIFF_LONG, TIFF_SLONG: Values[I]:=IntValue; end; IntValue:=IntValue shr Shift; end; end else begin // data of this tag does not fit into one 32 bits value Stream.Position:=FBasePosition+FIFD[Index].Offset; // even bytes sized data must be read by the loop as it is expanded to Single for I:=0 to High(Values) do begin case FIFD[Index].DataType of TIFF_BYTE: begin Stream.Read(IntValue,DataTypeToSize[FIFD[Index].DataType]); Value:=Byte(IntValue); end; TIFF_SHORT, TIFF_SSHORT: begin Stream.Read(IntValue,DataTypeToSize[FIFD[Index].DataType]); if ioBigEndian in FImageProperties.Options then Value:=System.Swap(Word(IntValue)) else Value:=Word(IntValue); end; TIFF_LONG, TIFF_SLONG: begin Stream.Read(IntValue,DataTypeToSize[FIFD[Index].DataType]); if ioBigEndian in FImageProperties.Options then Value:=SwapLong(IntValue); end; TIFF_RATIONAL, TIFF_SRATIONAL: begin Stream.Read(FloatNominator,sizeof(FloatNominator)); Stream.Read(FloatDenominator,sizeof(FloatDenominator)); if ioBigEndian in FImageProperties.Options then begin FloatNominator:=SwapLong(Cardinal(FloatNominator)); FloatDenominator:=SwapLong(Cardinal(FloatDenominator)); end; Value:=FloatNominator/FloatDenominator; end; TIFF_FLOAT: begin Stream.Read(IntNominator,sizeof(IntNominator)); Stream.Read(IntDenominator,sizeof(IntDenominator)); if ioBigEndian in FImageProperties.Options then begin IntNominator:=SwapLong(IntNominator); IntDenominator:=SwapLong(IntDenominator); end; Value:=IntNominator/IntDenominator; end; end; Values[I]:=Value; end; end; end; end; //---------------------------------------------------------------------------------------------------------------------- function TTIFFGraphic.GetValue(Stream: PStream; ATag: cardinal; Default: single = 0): single; // returns the value of the IFD entry indicated by Tag or the default value if the entry is not there var Index: cardinal; IntNominator,IntDenominator: cardinal; FloatNominator,FloatDenominator: cardinal; begin Result:=Default; if FindTag(ATag,Index) then begin // if the data length is>1 then Offset is a real offset into the stream, // otherwise it is the value itself and must be shortend depending on the data type if FIFD[Index].DataLength=1 then begin case FIFD[Index].DataType of TIFF_BYTE: Result:=Byte(FIFD[Index].Offset); TIFF_SHORT, TIFF_SSHORT: Result:=Word(FIFD[Index].Offset); TIFF_LONG, TIFF_SLONG: // nothing to do Result:=FIFD[Index].Offset; TIFF_RATIONAL, TIFF_SRATIONAL: begin Stream.Position:=FBasePosition+FIFD[Index].Offset; Stream.Read(FloatNominator,sizeof(FloatNominator)); Stream.Read(FloatDenominator,sizeof(FloatDenominator)); if ioBigEndian in FImageProperties.Options then begin FloatNominator:=SwapLong(Cardinal(FloatNominator)); FloatDenominator:=SwapLong(Cardinal(FloatDenominator)); end; Result:=FloatNominator/FloatDenominator; end; TIFF_FLOAT: begin Stream.Position:=FBasePosition+FIFD[Index].Offset; Stream.Read(IntNominator,sizeof(IntNominator)); Stream.Read(IntDenominator,sizeof(IntDenominator)); if ioBigEndian in FImageProperties.Options then begin IntNominator:=SwapLong(IntNominator); IntDenominator:=SwapLong(IntDenominator); end; Result:=IntNominator/IntDenominator; end; end; end; end; end; //---------------------------------------------------------------------------------------------------------------------- function TTIFFGraphic.GetValue(ATag: cardinal; Default: cardinal=0): cardinal; // returns the value of the IFD entry indicated by Tag or the default value if the entry is not there var Index: cardinal; begin if not FindTag(ATag, Index) then Result:=Default else begin Result:=FIFD[Index].Offset; // if the data length is>1 then Offset is a real offset into the stream, // otherwise it is the value itself and must be shortend depending on the data type if FIFD[Index].DataLength=1 then begin case FIFD[Index].DataType of TIFF_BYTE: Result:=Byte(Result); TIFF_SHORT, TIFF_SSHORT: Result:=Word(Result); TIFF_LONG, TIFF_SLONG: // nothing to do ; else Result:=Default; end; end; end; end; //---------------------------------------------------------------------------------------------------------------------- function TTIFFGraphic.GetValue(ATag: cardinal; var Size: cardinal; Default: cardinal): cardinal; // Returns the value of the IFD entry indicated by Tag or the default value if the entry is not there. // If the tag exists then also the data size is returned. var Index: cardinal; begin if not FindTag(ATag,Index) then begin Result:=Default; Size:=0; end else begin Result:=FIFD[Index].Offset; Size:=FIFD[Index].DataLength; // if the data length is>1 then Offset is a real offset into the stream, // otherwise it is the value itself and must be shortend depending on the data type if FIFD[Index].DataLength=1 then begin case FIFD[Index].DataType of TIFF_BYTE: Result:=Byte(Result); TIFF_SHORT, TIFF_SSHORT: Result:=Word(Result); TIFF_LONG, TIFF_SLONG: // nothing to do ; else Result:=Default; end; end; end; end; //---------------------------------------------------------------------------------------------------------------------- procedure TTIFFGraphic.SortIFD; // Although all entries in the IFD should be sorted there are still files where this is not the case. // Because the lookup for certain tags in the IFD uses binary search it must be made sure the IFD is // sorted (what we do here). //--------------- local function -------------------------------------------- procedure QuickSort(L, R: Integer); var I,J,M: integer; T: TIFDEntry; begin repeat I:=L; J:=R; M:=(L+R) shr 1; repeat while FIFD[I].ATagFIFD[M].ATag do Dec(J); if I<=J then begin T:=FIFD[I]; FIFD[I]:=FIFD[J]; FIFD[J]:=T; Inc(I); Dec(J); end; until I>J; if L=R; end; //--------------- end local functions --------------------------------------- begin QuickSort(0,High(FIFD)); end; //---------------------------------------------------------------------------------------------------------------------- procedure TTIFFGraphic.SwapIFD; // swap the member fields of all entries of the currently loaded IFD from big endian to little endian var I: integer; Size: cardinal; begin for I:=0 to High(FIFD) do with FIFD[I] do begin ATag:=System.Swap(ATag); DataType:=System.Swap(DataType); DataLength:=SwapLong(DataLength); // determine whether the data fits into 4 bytes Size:=DataTypeToSize[FIFD[I].DataType]*FIFD[I].DataLength; if Size>=4 then Offset:=SwapLong(Offset) else case DataType of TIFF_SHORT,TIFF_SSHORT: if DataLength>1 then Offset:=SwapLong(Offset) else Offset:=System.Swap(Word(Offset)); end; end; end; //---------------------------------------------------------------------------------------------------------------------- procedure TTIFFGraphic.LoadFromStream(Stream: PStream); var IFDCount: word; Buffer: pointer; Run: PChar; Pixels,EncodedData,DataPointerCopy: pointer; Offsets,ByteCounts: TCardinalArray; ColorMap: cardinal; StripSize: cardinal; {$IFDEF NOCLASSES} Decoder: PDecoder; {$ELSE} Decoder: TDecoder; {$ENDIF} {$IFDEF NOCLASSES} TIFFLZWDecoder: PTiffLzwDecoder; PackBitsRLEDecoder: PPackBitsRLEDecoder; CCITTMHDecoder: PCCITTMHDecoder; CCITTFax3Decoder: PCCITTFax3Decoder; CCITTFax4Decoder: PCCITTFax4Decoder; LZ77Decoder: PLZ77Decoder; {$ENDIF} // dynamically assigned handler Deprediction: procedure(P: pointer; Count: cardinal); begin // free previous image if Assigned(FBitmap) then FBitmap.Free; Deprediction:=nil; Decoder:=nil; // we need to keep the current stream position because all position information // are relative to this one FBasePosition:=Stream.Position; if ReadImageProperties(Stream,0) then begin with FImageProperties do try // tiled images aren't supported if ioTiled in FImageProperties.Options then Exit; // read data of the first image file directory (IFD) Stream.Position:=FBasePosition+FirstIFD; Stream.Read(IFDCount,sizeof(IFDCount)); if ioBigEndian in Options then IFDCount:=System.Swap(IFDCount); SetLength(FIFD,IFDCount); Stream.Read(FIFD[0],IFDCount*sizeof(TIFDEntry)); if ioBigEndian in Options then SwapIFD; SortIFD; // --- read the data of the directory which are needed to actually load the image: // data organization GetValueList(Stream,TIFFTAG_STRIPOFFSETS,Offsets); GetValueList(Stream,TIFFTAG_STRIPBYTECOUNTS,ByteCounts); // retrive additional tile data if necessary if ioTiled in Options then begin GetValueList(Stream,TIFFTAG_TILEOFFSETS,Offsets); GetValueList(Stream,TIFFTAG_TILEBYTECOUNTS,ByteCounts); end; // determine pixelformat and setup color conversion if ioBigEndian in Options then ColorManager.SourceOptions:=[coNeedByteSwap] else ColorManager.SourceOptions:=[]; ColorManager.SourceBitsPerSample:=FImageProperties.BitsPerSample; if ColorManager.SourceBitsPerSample=16 then ColorManager.TargetBitsPerSample:=8 else ColorManager.TargetBitsPerSample:=ColorManager.SourceBitsPerSample; // the JPEG lib does internally a conversion to RGB if FImageProperties.Compression in [ctOJPEG,ctJPEG] then ColorManager.SourceColorScheme:=csBGR else ColorManager.SourceColorScheme:=FImageProperties.ColorScheme; case ColorManager.SourceColorScheme of csRGBA: ColorManager.TargetColorScheme:=csBGRA; csRGB: ColorManager.TargetColorScheme:=csBGR; csCMY,csCMYK,csCIELab,csYCbCr: ColorManager.TargetColorScheme:=csBGR; csIndexed: begin if HasAlpha then ColorManager.SourceColorScheme:=csGA; // fake indexed images with alpha (used in EPS) // as being grayscale with alpha ColorManager.TargetColorScheme:=csIndexed; end; else ColorManager.TargetColorScheme:=ColorManager.SourceColorScheme; end; ColorManager.SourceSamplesPerPixel:=FImageProperties.SamplesPerPixel; // now that the pixel format is set we can also set the (possibly large) image dimensions FBitmap:=NewBitmap(FImageProperties.Width,FImageProperties.Height); if ColorManager.SourceColorScheme=csCMYK then ColorManager.TargetSamplesPerPixel:=3 else ColorManager.TargetSamplesPerPixel:=FImageProperties.SamplesPerPixel; if ColorManager.SourceColorScheme=csCIELab then ColorManager.SourceOptions:=ColorManager.SourceOptions+[coLabByteRange]; if ColorManager.SourceColorScheme=csGA then FBitmap.PixelFormat:=pf8Bit else FBitmap.PixelFormat:=ColorManager.TargetPixelFormat; if (FImageProperties.Width=0) or (FImageProperties.Height=0) then GraphicExError(1{gesInvalidImage},[TIF]); if ColorManager.TargetColorScheme in [csIndexed,csG,csGA] then begin // load palette data and build palette if ColorManager.TargetColorScheme=csIndexed then begin ColorMap:=GetValue(TIFFTAG_COLORMAP,StripSize,0); if StripSize>0 then begin Stream.Position:=FBasePosition+ColorMap; // number of palette entries is also given by the color map tag // (3 components each (r,g,b) and two bytes per component) Stream.Read(FPalette[0],2*StripSize); ColorManager.CreateColorPalette(FBitmap,[@FPalette[0], @FPalette[StripSize div 3], @FPalette[2*StripSize div 3]], pfPlane16Triple,StripSize div 3,False); end; end else ColorManager.CreateGrayScalePalette(FBitmap,ioMinIsWhite in Options); end else if ColorManager.SourceColorScheme=csYCbCr then ColorManager.SetYCbCrParameters( FYCbCrCoefficients,YCbCrSubSampling[0],YCbCrSubSampling[1]); // intermediate buffer for data FImageProperties.BytesPerLine:=(FImageProperties.BitsPerPixel*FImageProperties.Width+7) div 8; // determine prediction scheme if Compression<>ctNone then begin // Prediction without compression makes no sense at all (as it is to improve // compression ratios). Appearently there are image which are uncompressed but still // have a prediction scheme set. Hence we must check for it. case Predictor of PREDICTION_HORZ_DIFFERENCING: // currently only one prediction scheme is defined case FImageProperties.SamplesPerPixel of 4: Deprediction:=Depredict4; 3: Deprediction:=Depredict3; else Deprediction:=Depredict1; end; end; end; // create decompressor for the image CASE FImageProperties.Compression of ctNone: ; ctLZW: begin {$IFDEF NOCLASSES} new( TIFFLZWDecoder, Create ); Decoder := TIFFLZWDecoder; {$ELSE} Decoder:=TTIFFLZWDecoder.Create; {$ENDIF} end; ctPackedBits: begin {$IFDEF NOCLASSES} new( PackBitsRLEDecoder, Create ); Decoder := PackBitsRLEDecoder; {$ELSE} Decoder:=TPackbitsRLEDecoder.Create; {$ENDIF} end; ctFaxRLE, ctFaxRLEW: begin {$IFDEF NOCLASSES} new( CCITTMHDecoder, Create(GetValue(TIFFTAG_GROUP3OPTIONS), ioReversed in Options,Compression=ctFaxRLEW,Width) ); Decoder := CCITTMHDecoder; {$ELSE} Decoder:= TCCITTMHDecoder.Create(GetValue(TIFFTAG_GROUP3OPTIONS), ioReversed in Options,Compression=ctFaxRLEW,Width); {$ENDIF} end; ctFax3: begin {$IFDEF NOCLASSES} new( CCITTFax3Decoder, Create(GetValue(TIFFTAG_GROUP3OPTIONS),ioReversed in Options,False, Width) ); Decoder := CCITTFax3Decoder; {$ELSE} Decoder:=TCCITTFax3Decoder.Create( GetValue(TIFFTAG_GROUP3OPTIONS),ioReversed in Options,False, Width); {$ENDIF} end; ctFax4: begin {$IFDEF NOCLASSES} new( CCITTFax4Decoder, Create(GetValue(TIFFTAG_GROUP4OPTIONS),ioReversed in Options,False, Width) ); Decoder := CCITTFax4Decoder; {$ELSE} Decoder:=TCCITTFax4Decoder.Create( GetValue(TIFFTAG_GROUP4OPTIONS),ioReversed in Options,False, Width); {$ENDIF} end; ctThunderscan: begin {$IFDEF NOCLASSES} new( LZ77Decoder, Create(Z_PARTIAL_FLUSH,True) ); Decoder := LZ77Decoder; {$ELSE} Decoder:=TThunderDecoder.Create(Width); {$ENDIF} end; ctLZ77: begin {$IFDEF NOCLASSES} new( LZ77Decoder, Create(Z_PARTIAL_FLUSH,True) ); Decoder := LZ77Decoder; {$ELSE} Decoder:=TLZ77Decoder.Create(Z_PARTIAL_FLUSH,True); {$ENDIF} end; else { COMPRESSION_OJPEG, COMPRESSION_CCITTFAX4 COMPRESSION_NEXT COMPRESSION_IT8CTPAD COMPRESSION_IT8LW COMPRESSION_IT8MP COMPRESSION_IT8BL COMPRESSION_PIXARFILM COMPRESSION_PIXARLOG COMPRESSION_DCS COMPRESSION_JBIG} GraphicExError(5{gesUnsupportedFeature},[ErrorMsg[11]{gesCompressionScheme},'compression',TIF]); end; if Assigned(Decoder) then Decoder.DecodeInit; // go for each strip in the image (which might contain more than one line) FImageProperties.CurrentRow:=0; FImageProperties.CurrentStrip:=0; StripCount:=Length(Offsets); while FImageProperties.CurrentStrip0) and (TileLength>0) then Include(Options,ioTiled); // photometric interpretation determines the color space PhotometricInterpretation:=GetValue(TIFFTAG_PHOTOMETRIC); // type of extra information for additional samples per pixel GetValueList(Stream,TIFFTAG_EXTRASAMPLES,ExtraSamples); // determine whether extra samples must be considered HasAlpha:=Length(ExtraSamples)>0; // if any of the extra sample contains an invalid value then consider // it as being not existant to avoid wrong interpretation for badly // written images if HasAlpha then begin for Index:=0 to High(ExtraSamples) do if ExtraSamples[Index]>EXTRASAMPLE_UNASSALPHA then begin HasAlpha:=False; Break; end; end; // currently all bits per sample values are equal BitsPerPixel:=BitsPerSample*SamplesPerPixel; // create decompressor for the image TIFCompression:=GetValue(TIFFTAG_COMPRESSION); case TIFCompression of COMPRESSION_NONE: Compression:=ctNone; COMPRESSION_LZW: Compression:=ctLZW; COMPRESSION_PACKBITS: Compression:=ctPackedBits; COMPRESSION_CCITTRLE: Compression:=ctFaxRLE; COMPRESSION_CCITTRLEW: Compression:=ctFaxRLEW; COMPRESSION_CCITTFAX3: Compression:=ctFax3; COMPRESSION_OJPEG: Compression:=ctOJPEG; COMPRESSION_JPEG: Compression:=ctJPEG; COMPRESSION_CCITTFAX4: Compression:=ctFax4; COMPRESSION_NEXT: Compression:=ctNext; COMPRESSION_THUNDERSCAN: Compression:=ctThunderscan; COMPRESSION_IT8CTPAD: Compression:=ctIT8CTPAD; COMPRESSION_IT8LW: Compression:=ctIT8LW; COMPRESSION_IT8MP: Compression:=ctIT8MP; COMPRESSION_IT8BL: Compression:=ctIT8BL; COMPRESSION_PIXARFILM: Compression:=ctPixarFilm; COMPRESSION_PIXARLOG: Compression:=ctPixarLog; // also a LZ77 clone COMPRESSION_ADOBE_DEFLATE, COMPRESSION_DEFLATE: Compression:=ctLZ77; COMPRESSION_DCS: Compression:=ctDCS; COMPRESSION_JBIG: Compression:=ctJBIG; else Compression:=ctUnknown; end; if PhotometricInterpretation in [PHOTOMETRIC_MINISWHITE..PHOTOMETRIC_CIELAB] then begin ColorScheme:=PhotometricToColorScheme[PhotometricInterpretation]; if (PhotometricInterpretation=PHOTOMETRIC_RGB) and (SamplesPerPixel<4) then ColorScheme:=csRGB; if PhotometricInterpretation=PHOTOMETRIC_MINISWHITE then Include(Options,ioMinIsWhite); // extra work necessary for YCbCr if PhotometricInterpretation=PHOTOMETRIC_YCBCR then begin if FindTag(TIFFTAG_YCBCRSUBSAMPLING,Index) then GetValueList(Stream,TIFFTAG_YCBCRSUBSAMPLING,YCbCrSubSampling) else begin // initialize default values if nothing is given in the file SetLength(YCbCrSubSampling,2); YCbCrSubSampling[0]:=2; YCbCrSubSampling[1]:=2; end; if FindTag(TIFFTAG_YCBCRPOSITIONING,Index) then FYCbCrPositioning:=GetValue(TIFFTAG_YCBCRPOSITIONING) else FYCbCrPositioning:=YCBCRPOSITION_CENTERED; if FindTag(TIFFTAG_YCBCRCOEFFICIENTS,Index) then GetValueList(Stream,TIFFTAG_YCBCRCOEFFICIENTS,FYCbCrCoefficients) else begin // defaults are from CCIR recommendation 601-1 SetLength(FYCbCrCoefficients,3); FYCbCrCoefficients[0]:=0.299; FYCbCrCoefficients[1]:=0.587; FYCbCrCoefficients[2]:=0.114; end; end; end else ColorScheme:=csUnknown; JPEGColorMode:=GetValue(TIFFTAG_JPEGCOLORMODE,JPEGCOLORMODE_RAW); JPEGTablesMode:=GetValue(TIFFTAG_JPEGTABLESMODE,JPEGTABLESMODE_QUANT or JPEGTABLESMODE_HUFF); PlanarConfig:=GetValue(TIFFTAG_PLANARCONFIG); // other image properties XResolution:=GetValue(Stream,TIFFTAG_XRESOLUTION); YResolution:=GetValue(Stream,TIFFTAG_YRESOLUTION); if GetValue(TIFFTAG_RESOLUTIONUNIT,RESUNIT_INCH)=RESUNIT_CENTIMETER then begin // Resolution is given in centimeters. // Although I personally prefer the metric system over the old english one :-) // I still convert to inches because it is an unwritten rule to give image resolutions in dpi. XResolution:=XResolution*2.54; YResolution:=YResolution*2.54; end; // determine prediction scheme Predictor:=GetValue(TIFFTAG_PREDICTOR); // determine fill order in bytes if GetValue(TIFFTAG_FILLORDER,FILLORDER_MSB2LSB)=FILLORDER_LSB2MSB then Include(Options,ioReversed); // finally show that we found and read an image Result:=True; end; end; end; //----------------- TEPSGraphic ---------------------------------------------------------------------------------------- // Note: This EPS implementation does only read embedded pixel graphics in TIF format (preview). // Credits to: // Olaf Stieleke // Torsten Pohlmeyer // CPS Krohn GmbH // for providing the base information about how to read the preview image. type TEPSHeader = packed record Code: cardinal; // alway $C6D3D0C5, if not there then this is not an EPS or it is not a binary EPS PSStart, // Offset PostScript-Code PSLen, // length of PostScript-Code MetaPos, // position of a WMF MetaLen, // length of a WMF TiffPos, // position of TIFF (preview images should be either WMF or TIF but not both) TiffLen: integer; // length of the TIFF Checksum: smallint; end; //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TEPSGraphic.CanLoad(Stream: PStream): boolean; var Header: TEPSHeader; LastPosition: cardinal; begin Result:=(Stream.Size-Stream.Position)>sizeof(Header); if Result then begin LastPosition:=Stream.Position; Stream.Read(Header,sizeof(Header)); Result:=(Header.Code=$C6D3D0C5) and (Header.TiffPos>Integer(LastPosition)+sizeof(Header)) and (Header.TiffLen>0); Stream.Position:=LastPosition; end; end; //---------------------------------------------------------------------------------------------------------------------- procedure TEPSGraphic.LoadFromStream(Stream: PStream); var Header: TEPSHeader; begin // free previous image if Assigned(FBitmap) then FBitmap.Free; Stream.Read(Header,sizeof(Header)); if Header.Code<>$C6D3D0C5 then GraphicExError(1{gesInvalidImage},['EPS']); Stream.Seek(Header.TIFFPos-sizeof(Header),spCurrent); inherited; end; //---------------------------------------------------------------------------------------------------------------------- function TEPSGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; begin Result:=inherited ReadImageProperties(Stream,ImageIndex); end; //----------------- TTGAGraphic -------------------------------------------------------------------------------------- // FILE STRUCTURE FOR THE ORIGINAL TRUEVISION TGA FILE // FIELD 1: NUMBER OF CHARACTERS IN ID FIELD (1 BYTES) // FIELD 2: COLOR MAP TYPE (1 BYTES) // FIELD 3: IMAGE TYPE CODE (1 BYTES) // = 0 NO IMAGE DATA INCLUDED // = 1 UNCOMPRESSED, COLOR-MAPPED IMAGE // = 2 UNCOMPRESSED, TRUE-COLOR IMAGE // = 3 UNCOMPRESSED, BLACK AND WHITE IMAGE (black and white is actually grayscale) // = 9 RUN-LENGTH ENCODED COLOR-MAPPED IMAGE // = 10 RUN-LENGTH ENCODED TRUE-COLOR IMAGE // = 11 RUN-LENGTH ENCODED BLACK AND WHITE IMAGE // FIELD 4: COLOR MAP SPECIFICATION (5 BYTES) // 4.1: COLOR MAP ORIGIN (2 BYTES) // 4.2: COLOR MAP LENGTH (2 BYTES) // 4.3: COLOR MAP ENTRY SIZE (1 BYTES) // FIELD 5:IMAGE SPECIFICATION (10 BYTES) // 5.1: X-ORIGIN OF IMAGE (2 BYTES) // 5.2: Y-ORIGIN OF IMAGE (2 BYTES) // 5.3: WIDTH OF IMAGE (2 BYTES) // 5.4: HEIGHT OF IMAGE (2 BYTES) // 5.5: IMAGE PIXEL SIZE (1 BYTE) // 5.6: IMAGE DESCRIPTOR BYTE (1 BYTE) // bit 0..3: attribute bits per pixel // bit 4..5: image orientation: // 0: bottom left // 1: bottom right // 2: top left // 3: top right // bit 6..7: interleaved flag // 0: two way (even-odd) interleave (e.g. IBM Graphics Card Adapter), obsolete // 1: four way interleave (e.g. AT&T 6300 High Resolution), obsolete // FIELD 6: IMAGE ID FIELD (LENGTH SPECIFIED BY FIELD 1) // FIELD 7: COLOR MAP DATA (BIT WIDTH SPECIFIED BY FIELD 4.3 AND // NUMBER OF COLOR MAP ENTRIES SPECIFIED IN FIELD 4.2) // FIELD 8: IMAGE DATA FIELD (WIDTH AND HEIGHT SPECIFIED IN FIELD 5.3 AND 5.4) const TARGA_NO_COLORMAP = 0; TARGA_COLORMAP = 1; TARGA_EMPTY_IMAGE = 0; TARGA_INDEXED_IMAGE = 1; TARGA_TRUECOLOR_IMAGE = 2; TARGA_BW_IMAGE = 3; TARGA_INDEXED_RLE_IMAGE = 9; TARGA_TRUECOLOR_RLE_IMAGE = 10; TARGA_BW_RLE_IMAGE = 11; type TTargaHeader = packed record IDLength, ColorMapType, ImageType: Byte; ColorMapOrigin, ColorMapSize: Word; ColorMapEntrySize: Byte; XOrigin, YOrigin, Width, Height: Word; PixelSize: Byte; ImageDescriptor: Byte; end; //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TTGAGraphic.CanLoad(Stream: PStream): boolean; var Header: TTargaHeader; LastPosition: cardinal; begin LastPosition:=Stream.Position; Result:=(Stream.Size-Stream.Position)>sizeof(Header); if Result then begin Stream.Read(Header,sizeof(Header)); // Targa images are hard to determine because there is no magic id or something like that. // Hence all we can do is to check if all values from the header are within correct limits. Result:=(Header.ImageType in [TARGA_EMPTY_IMAGE,TARGA_INDEXED_IMAGE,TARGA_TRUECOLOR_IMAGE,TARGA_BW_IMAGE, TARGA_INDEXED_RLE_IMAGE,TARGA_TRUECOLOR_RLE_IMAGE,TARGA_BW_RLE_IMAGE]) and (Header.ColorMapType in [TARGA_NO_COLORMAP,TARGA_COLORMAP]) and (Header.ColorMapEntrySize in [15,16,24,32]) and (Header.PixelSize in [8,15,16,24,32]); end; Stream.Position:=LastPosition; end; //---------------------------------------------------------------------------------------------------------------------- procedure TTGAGraphic.LoadFromStream(Stream: PStream); var Run,RLEBuffer: PChar; I,LineSize: Integer; LineBuffer: Pointer; ReadLength: Integer; Color16: Word; Header: TTargaHeader; FlipV: Boolean; {$IFDEF NOCLASSES} Decoder: PTargaRLEDecoder; //{$ELSE} Decoder: PTargaRLEDecoder; {$ELSE} Decoder: TTargaRLEDecoder; {$ENDIF} begin // free previous image if Assigned(FBitmap) then FBitmap.Free; FBasePosition:=Stream.Position; if ReadImageProperties(Stream,0) then with FImageProperties do begin Stream.Position:=FBasePosition; Stream.Read(Header,sizeof(Header)); FlipV:=(Header.ImageDescriptor and $20)<>0; Header.ImageDescriptor:=Header.ImageDescriptor and $F; // skip image ID Stream.Seek(Header.IDLength,spCurrent); ColorManager.SourceSamplesPerPixel:=SamplesPerPixel; ColorManager.TargetSamplesPerPixel:=SamplesPerPixel; ColorManager.SourceColorScheme:=ColorScheme; ColorManager.SourceOptions:=[]; ColorManager.TargetColorScheme:=csBGR; ColorManager.SourceBitsPerSample:=BitsPerSample; ColorManager.TargetBitsPerSample:=BitsPerSample; FBitmap:=NewBitmap(Header.Width,Header.Height); FBitmap.PixelFormat:=ColorManager.TargetPixelFormat; if (Header.ColorMapType=TARGA_COLORMAP) or (Header.ImageType in [TARGA_BW_IMAGE,TARGA_BW_RLE_IMAGE]) then begin if Header.ImageType in [TARGA_BW_IMAGE,TARGA_BW_RLE_IMAGE] then ColorManager.CreateGrayscalePalette(FBitmap,False) else begin LineSize:=(Header.ColorMapEntrySize div 8)*Header.ColorMapSize; GetMem(LineBuffer,LineSize); try Stream.Read(LineBuffer^,LineSize); case Header.ColorMapEntrySize of 32: ColorManager.CreateColorPalette(FBitmap,[LineBuffer],pfInterlaced8Quad,Header.ColorMapSize,False); 24: ColorManager.CreateColorPalette(FBitmap,[LineBuffer],pfInterlaced8Triple,Header.ColorMapSize,False); else begin // 15 and 16 bits per color map entry (handle both like 555 color format // but make 8 bit from 5 bit per color component) for I:=0 to pred(Header.ColorMapSize) do begin Stream.Read(Color16,2); FBitmap.DIBPalEntries[I]:=Windows.RGB((Color16 and $7C00) shr 7,(Color16 and $3E0) shr 2,(Color16 and $1F) shl 3); end; end; end; finally if Assigned(LineBuffer) then FreeMem(LineBuffer); end; end; end; LineSize:=Width*(Header.PixelSize div 8); case Header.ImageType of TARGA_EMPTY_IMAGE: ; // nothing to do here TARGA_BW_IMAGE, TARGA_INDEXED_IMAGE, TARGA_TRUECOLOR_IMAGE: begin for I:=0 to pred(FBitmap.Height) do begin if FlipV then LineBuffer:=FBitmap.ScanLine[I] else LineBuffer:=FBitmap.ScanLine[Header.Height-(I+1)]; Stream.Read(LineBuffer^,LineSize); end; end; TARGA_BW_RLE_IMAGE, TARGA_INDEXED_RLE_IMAGE, TARGA_TRUECOLOR_RLE_IMAGE: begin RLEBuffer:=nil; {$IFDEF NOCLASSES} new( Decoder, Create(Header.PixelSize) ); {$ELSE} Decoder:=TTargaRLEDecoder.Create(Header.PixelSize); {$ENDIF} try GetMem(RLEBuffer,2*LineSize); for I:=0 to pred(FBitmap.Height) do begin if FlipV then LineBuffer:=FBitmap.ScanLine[I] else LineBuffer:=FBitmap.ScanLine[Header.Height-(I+1)]; ReadLength:=Stream.Read(RLEBuffer^,2*LineSize); Run:=RLEBuffer; Decoder.Decode(Pointer(Run),LineBuffer,2*LineSize,FBitmap.Width); Stream.Position:=Stream.Position-DWORD(ReadLength)+DWORD(Run-RLEBuffer); end; finally if Assigned(RLEBuffer) then FreeMem(RLEBuffer); Decoder.Free; end; end; end; end; end; //---------------------------------------------------------------------------------------------------------------------- function TTGAGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; var Header: TTargaHeader; begin inherited ReadImageProperties(Stream,ImageIndex); with FImageProperties do begin Stream.Read(Header,sizeof(Header)); Header.ImageDescriptor:=Header.ImageDescriptor and $F; Width:=Header.Width; Height:=Header.Height; BitsPerSample:=8; case Header.PixelSize of 8: begin if Header.ImageType in [TARGA_BW_IMAGE,TARGA_BW_RLE_IMAGE] then ColorScheme:=csG else ColorScheme:=csIndexed; SamplesPerPixel:=1; end; 15, 16: // actually, 16 bit are meant being 15 bit begin ColorScheme:=csRGB; BitsPerSample:=5; SamplesPerPixel:=3; end; 24: begin ColorScheme:=csRGB; SamplesPerPixel:=3; end; 32: begin ColorScheme:=csRGBA; SamplesPerPixel:=4; end; end; BitsPerPixel:=SamplesPerPixel*BitsPerSample; if Header.ImageType in [TARGA_BW_RLE_IMAGE,TARGA_INDEXED_RLE_IMAGE,TARGA_TRUECOLOR_RLE_IMAGE] then Compression:=ctRLE else Compression:=ctNone; Width:=Header.Width; Height:=Header.Height; Result:=True; end; end; //----------------- TPCXGraphic ---------------------------------------------------------------------------------------- type TPCXHeader = packed record FileID: Byte; // $0A for PCX files, $CD for SCR files Version: Byte; // 0: version 2.5; 2: 2.8 with palette; 3: 2.8 w/o palette; 5: version 3 Encoding: Byte; // 0: uncompressed; 1: RLE encoded BitsPerPixel: Byte; XMin, YMin, XMax, YMax, // coordinates of the corners of the image HRes, // horizontal resolution in dpi VRes: Word; // vertical resolution in dpi ColorMap: array[0..15] of TRGB; // color table Reserved, ColorPlanes: Byte; // color planes (at most 4) BytesPerLine, // number of bytes of one line of one plane PaletteType: Word; // 1: color or b&w; 2: gray scale Fill: array[0..57] of Byte; end; //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TPCXGraphic.CanLoad(Stream: PStream): boolean; var Header: TPCXHeader; LastPosition: cardinal; begin LastPosition:=Stream.Position; Result:=(Stream.Size-Stream.Position)>sizeof(Header); if Result then begin Stream.Read(Header,sizeof(Header)); Result:=(Header.FileID in [$0A,$0C]) and (Header.Version in [0,2,3,5]) and (Header.Encoding in [0,1]); end; Stream.Position:=LastPosition; end; //---------------------------------------------------------------------------------------------------------------------- procedure TPCXGraphic.LoadFromStream(Stream: PStream); var Header: TPCXHeader; //--------------- local functions ------------------------------------------- procedure MakePalette; const Pal16: array[0..15] of TColor = (clBlack,clMaroon,clGreen,clOlive,clNavy,clPurple,clTeal,clDkGray, clLtGray,clRed,clLime,clYellow,clBlue,clFuchsia,clAqua,clWhite); var PCXPalette: array[0..255] of TRGB; I,OldPos: integer; Marker: byte; begin if (Header.Version<>3) or (FBitmap.PixelFormat=pf1Bit) then begin case FBitmap.PixelFormat of pf1Bit: ColorManager.CreateGrayScalePalette(FBitmap,False); pf4Bit: with Header do begin if PaletteType=2 then ColorManager.CreateGrayScalePalette(FBitmap,False) else ColorManager.CreateColorPalette(FBitmap,[@ColorMap],pfInterlaced8Triple,16,False); end; pf8Bit: begin OldPos:=Stream.Position; // 256 colors with 3 components plus one marker byte Stream.Position:=Stream.Size-769; for I := 1 to 768 do // åñëè ìàðêåðà â ýòîì ìåñòå íåò, äåëàåì ïîïûòêó begin // íàéòè åãî ÷óòü ðàíüøå... Stream.Position := Stream.Position - 2; Stream.Read(Marker,1); if Marker = $0C then break; end; if Marker <> $0C then begin // Åùå ðàç ïîïðîáóåì ñ÷èòàòü ïàëèòðó èç äðóãîãî ìåñòà - èçìåðÿÿ // ðàçåð èçîáðàæåíèÿ Stream.Position := OldPos + Integer( FImageProperties.BytesPerLine ) * Height; Stream.Read(Marker, 1); end; if Marker<>$0C then begin // palette ID is wrong, perhaps gray scale? if Header.PaletteType=2 then ColorManager.CreateGrayScalePalette(FBitmap,False) else ; // ignore palette end else begin Stream.Read(PCXPalette[0],768); ColorManager.CreateColorPalette(FBitmap,[@PCXPalette],pfInterlaced8Triple,256,True); end; Stream.Position:=OldPos; end; end; end else begin // version 2.8 without palette information, just use the system palette // 256 colors will not be correct with this assignment... for I:=0 to 15 do FBitmap.DIBPalEntries[I]:=Pal16[I]; end; end; //--------------- end local functions --------------------------------------- var PCXSize,Size: cardinal; RawBuffer,DecodeBuffer: pointer; Run,Plane1,Plane2,Plane3,Plane4: PByte; Value,Mask: byte; I,J: integer; Line: PByte; Increment: cardinal; NewPixelFormat: TPixelFormat; {$IFDEF NOCLASSES} Decoder: PPcxRLEDecoder; {$ELSE} Decoder: TPCXRLEDecoder; {$ENDIF} begin // free previous image if Assigned(FBitmap) then FBitmap.Free; FBasePosition:=Stream.Position; if ReadImageProperties(Stream,0) then begin Stream.Position:=FBasePosition; Stream.Read(Header,sizeof(Header)); PCXSize:=Stream.Size-Stream.Position; with Header,FImageProperties do begin if not (FileID in [$0A,$CD]) then GraphicExError(1{gesInvalidImage},['PCX or SCR']); ColorManager.SourceColorScheme:=ColorScheme; ColorManager.SourceBitsPerSample:=BitsPerSample; ColorManager.SourceSamplesPerPixel:=SamplesPerPixel; if ColorScheme=csIndexed then ColorManager.TargetColorScheme:=csIndexed else ColorManager.TargetColorScheme:=csBGR; if BitsPerPixel=2 then ColorManager.TargetBitsPerSample:=4 else ColorManager.TargetBitsPerSample:=BitsPerSample; // Note: pixel depths of 2 and 4 bits may not be used with more than one plane // otherwise the image will not show up correctly ColorManager.TargetSamplesPerPixel:=SamplesPerPixel; NewPixelFormat:=ColorManager.TargetPixelFormat; if NewPixelFormat=pfCustom then begin // there can be a special case comprising 4 planes each with 1 bit if (SamplesPerPixel=4) and (BitsPerPixel=4) then NewPixelFormat:=pf4Bit else GraphicExError(2{gesInvalidColorFormat},['PCX']); end; FBitmap:=NewBitmap(Width,Height); FBitmap.PixelFormat:=NewPixelFormat; // 256 colors palette is appended to the actual PCX data if FBitmap.PixelFormat=pf8Bit then Dec(PCXSize,769); if FBitmap.PixelFormat<>pf24Bit then MakePalette; // adjust alignment of line Increment:=SamplesPerPixel*Header.BytesPerLine; // allocate pixel data buffer and decode data if necessary if Compression=ctRLE then begin Size:=Increment*Height; GetMem(DecodeBuffer,Size); GetMem(RawBuffer,PCXSize); try Stream.Read(RawBuffer^,PCXSize); {$IFDEF NOCLASSES} new( Decoder, Create ); {$ELSE} Decoder := TPCXRLEDecoder.Create; {$ENDIF} with Decoder {$IFDEF NOCLASSES}^{$ENDIF} do try Decode(RawBuffer,DecodeBuffer,PCXSize,Size); finally Free; end; finally if Assigned(RawBuffer) then FreeMem(RawBuffer); end; end else begin GetMem(DecodeBuffer,PCXSize); Stream.Read(DecodeBuffer^,PCXSize); end; try Run:=DecodeBuffer; if (SamplesPerPixel=4) and (BitsPerPixel=4) then begin // 4 planes with one bit for I:=0 to pred(Height) do begin Plane1:=Run; PChar(Plane2):=PChar(Run)+Increment div 4; PChar(Plane3):=PChar(Run)+2*(Increment div 4); PChar(Plane4):=PChar(Run)+3*(Increment div 4); Line:=FBitmap.ScanLine[I]; // number of bytes to write Size:=(FBitmap.Width*BitsPerPixel+7) div 8; Mask:=0; while Size>0 do begin Value:=0; for J:=0 to 1 do asm MOV AL,[Value] MOV EDX,[Plane4] // take the 4 MSBs from the 4 runs and build a nibble SHL BYTE PTR [EDX],1 // read MSB and prepare next run at the same time RCL AL,1 // MSB from previous shift is in CF -> move it to AL MOV EDX,[Plane3] // now do the same with the other three runs SHL BYTE PTR [EDX],1 RCL AL,1 MOV EDX,[Plane2] SHL BYTE PTR [EDX],1 RCL AL,1 MOV EDX,[Plane1] SHL BYTE PTR [EDX],1 RCL AL,1 MOV [Value],AL end; Line^:=Value; Inc(Line); Dec(Size); // two runs above (to construct two nibbles -> one byte), now update marker // to know when to switch to next byte in the planes Mask:=(Mask+2) mod 8; if Mask=0 then begin Inc(Plane1); Inc(Plane2); Inc(Plane3); Inc(Plane4); end; end; Inc(Run,Increment); end; end else if FBitmap.PixelFormat=pf24Bit then begin // true color for I:=0 to pred(FBitmap.Height) do begin Line:=FBitmap.ScanLine[I]; Plane1:=Run; PChar(Plane2):=PChar(Run)+Increment div 3; PChar(Plane3):=PChar(Run)+2*(Increment div 3); ColorManager.ConvertRow([Plane1,Plane2,Plane3],Line,FBitmap.Width,$FF); Inc(Run,Increment); end end else begin // other indexed formats for I:=0 to pred(FBitmap.Height) do begin Line:=FBitmap.ScanLine[I]; ColorManager.ConvertRow([Run],Line,FBitmap.Width,$FF); Inc(Run,Increment); end; end; finally if Assigned(DecodeBuffer) then FreeMem(DecodeBuffer); end; end; end; end; //---------------------------------------------------------------------------------------------------------------------- function TPCXGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; var Header: TPCXHeader; begin Result:=inherited ReadImageProperties(Stream,0); Stream.Read(Header,sizeof(Header)); with FImageProperties do begin if Header.FileID in [$0A,$CD] then begin Width:=Header.XMax-Header.XMin+1; Height:=Header.YMax-Header.YMin+1; SamplesPerPixel:=Header.ColorPlanes; BitsPerSample:=Header.BitsPerPixel; BitsPerPixel:=BitsPerSample*SamplesPerPixel; BytesPerLine := Header.BytesPerLine; if BitsPerPixel<=8 then ColorScheme:=csIndexed else ColorScheme:=csRGB; if Header.Encoding=1 then Compression:=ctRLE else Compression:=ctNone; XResolution:=Header.HRes; YResolution:=Header.VRes; Result:=True; end; end; end; //----------------- TPCDGraphic ---------------------------------------------------------------------------------------- const PCD_BEGIN_BASE16 = 8192; PCD_BEGIN_BASE4 = 47104; PCD_BEGIN_BASE = 196608; PCD_BEGIN_ORIENTATION = 194635; PCD_BEGIN = 2048; PCD_MAGIC = 'PCD_IPI'; //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TPCDGraphic.CanLoad(Stream: PStream): boolean; var Header: array[0..$802] of byte; LastPosition: cardinal; begin LastPosition:=Stream.Position; Result:=(Stream.Size-Stream.Position)>3*$800; if Result then begin Stream.Read(Header,Length(Header)); Result:=(StrLComp(@Header[0],'PCD_OPA',7)=0) or (StrLComp(@Header[$800],'PCD',3)=0); end; Stream.Position:=LastPosition; end; //---------------------------------------------------------------------------------------------------------------------- procedure TPCDGraphic.LoadFromStream(Stream: PStream); var C1,C2,YY: PChar; YCbCrData: array[0..2] of PChar; SourceDummy,DestDummy: pointer; Offset,I,X,IX,Y,IY,ImageIndex,Rows,Columns: cardinal; ScanLines: array of pointer; LineBuffer: pointer; Line,Run: PBGR; {$IFDEF NOCLASSES} Decoder: PPCDDecoder; {$ELSE} Decoder: TPCDDecoder; {$ENDIF} begin // free previous image if Assigned(FBitmap) then FBitmap.Free; FBasePosition:=Stream.Position; ImageIndex:=2; // third image is Base resolution if ReadImageProperties(Stream,ImageIndex) then begin with FImageProperties do begin ScanLines:=nil; LineBuffer:=nil; Stream.Position:=FBasePosition; Columns:=192 shl KOL.Min(ImageIndex,2); Rows:=128 shl KOL.Min(ImageIndex,2); Width:=192 shl ImageIndex; Height:=128 shl ImageIndex; // For the rotated mode where we need to turn the image by 90°. We can speed up loading // the image by factor 2 by using a local copy of the Scanline pointers. if Rotate in [1,3] then begin IX:=Height; IY:=Width; end else begin IX:=Width; IY:=Height; end; // since row and columns might be swapped because of rotated images // we determine the final dimensions once more FBitmap:=NewBitmap(IX,IY); if Rotate in [1,3] then begin SetLength(ScanLines,IX); for Y:=0 to pred(IY) do ScanLines[Y]:=FBitmap.ScanLine[Y]; GetMem(LineBuffer,3*IY); end; ZeroMemory(@YCbCrData,sizeof(YCbCrData)); try GetMem(YCbCrData[0],FBitmap.Width*FBitmap.Height); GetMem(YCbCrData[1],FBitmap.Width*FBitmap.Height); GetMem(YCbCrData[2],FBitmap.Width*FBitmap.Height); // advance to image data Offset:=96; if Overview then Offset:=5 else if ImageIndex=1 then Offset:=23 else if ImageIndex=0 then Offset:=4; Stream.Seek(Offset*$800,spCurrent); // color conversion setup ColorManager.SourceColorScheme:=csPhotoYCC; ColorManager.SourceBitsPerSample:=8; ColorManager.SourceSamplesPerPixel:=3; ColorManager.TargetColorScheme:=csBGR; ColorManager.TargetBitsPerSample:=8; ColorManager.TargetSamplesPerPixel:=3; FBitmap.PixelFormat:=pf24Bit; // PhotoYCC format uses CCIR Recommendation 709 coefficients and is subsampled // by factor 2 vertically and horizontally ColorManager.SetYCbCrParameters([0.2125,0.7154,0.0721],2,2); if False then begin // if Overview then ... no info yet about overview image structure end else begin YY:=YCbCrData[0]; C1:=YCbCrData[1]; C2:=YCbCrData[2]; I:=0; while I=3 then begin // Inc(Y,3*(ImageIndex-3)); {$IFDEF NOCLASSES} new( Decoder, Create( Stream ) ); {$ELSE} Decoder:=TPCDDecoder.Create(Stream); {$ENDIF} SourceDummy:=@YCbCrData; DestDummy:=nil; try // recover luminance deltas for 1536 x 1024 image Upsample(768,512,Width,YCbCrData[0]); Upsample(384,256,Width,YCbCrData[1]); Upsample(384,256,Width,YCbCrData[2]); Stream.Seek(4*$800,spCurrent); Decoder.Decode(SourceDummy,DestDummy,Width,1024); if ImageIndex>=4 then begin // recover luminance deltas for 3072 x 2048 image Upsample(1536,1024,FBitmap.Width,YCbCrData[0]); Upsample(768,512,FBitmap.Width,YCbCrData[1]); Upsample(768,512,FBitmap.Width,YCbCrData[2]); Offset:=(Stream.Position-FBasePosition) div $800+12; Stream.Seek(FBasePosition+Offset*$800,spBegin); Decoder.Decode(SourceDummy,DestDummy,Width,2048); if ImageIndex=5 then begin // recover luminance deltas for 6144 x 4096 image (vaporware) Upsample(3072,2048,FBitmap.Width,YCbCrData[1]); Upsample(1536,1024,FBitmap.Width,YCbCrData[1]); Upsample(1536,1024,FBitmap.Width,YCbCrData[2]); end; end; finally Decoder.Free; end; end; Upsample(FBitmap.Width shr 1,FBitmap.Height shr 1,FBitmap.Width,YCbCrData[1]); Upsample(FBitmap.Width shr 1,FBitmap.Height shr 1,FBitmap.Width,YCbCrData[2]); // transfer luminance and chrominance channels YY:=YCbCrData[0]; C1:=YCbCrData[1]; C2:=YCbCrData[2]; try case Rotate of 1: // rotate -90° begin for Y:=0 to pred(FBitmap.Height) do begin ColorManager.ConvertRow([YY,C1,C2],LineBuffer,FBitmap.Width,$FF); Inc(YY,FBitmap.Width); Inc(C1,FBitmap.Width); Inc(C2,FBitmap.Width); Run:=LineBuffer; for X:=0 to pred(FBitmap.Width) do begin PChar(Line):=PChar(ScanLines[FBitmap.Width-Integer(X)-1])+Y*3; Line^:=Run^; Inc(Run); end; end; end; 3: // rotate 90° begin for Y:=0 to pred(Height) do begin ColorManager.ConvertRow([YY,C1,C2],LineBuffer,FBitmap.Width,$FF); Inc(YY,FBitmap.Width); Inc(C1,FBitmap.Width); Inc(C2,FBitmap.Width); Run:=LineBuffer; for X:=0 to pred(FBitmap.Width) do begin PChar(Line):=PChar(ScanLines[X])+(FBitmap.Height-Integer(Y)-1)*3; Line^:=Run^; Inc(Run); end; end; end; else for Y:=0 to pred(Height) do begin ColorManager.ConvertRow([YY,C1,C2],FBitmap.ScanLine[Y],FBitmap.Width,$FF); Inc(YY,FBitmap.Width); Inc(C1,FBitmap.Width); Inc(C2,FBitmap.Width); end; end; finally ScanLines:=nil; if Assigned(LineBuffer) then FreeMem(LineBuffer); end; end; finally if Assigned(YCbCrData[2]) then FreeMem(YCbCrData[2]); if Assigned(YCbCrData[1]) then FreeMem(YCbCrData[1]); if Assigned(YCbCrData[0]) then FreeMem(YCbCrData[0]); end; end; end; end; //---------------------------------------------------------------------------------------------------------------------- function TPCDGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; var Header: array[0..$17FF] of byte; Temp: cardinal; begin if ImageIndex>5 then ImageIndex:=5; Result:=inherited ReadImageProperties(Stream,ImageIndex) and ((Stream.Size-FBasePosition)>3*$800); with FImageProperties do begin Stream.Read(Header,Length(Header)); try Overview:=StrLComp(@Header[0],'PCD_OPA',7)=0; // determine if image is a PhotoCD image if Overview or (StrLComp(@Header[$800],'PCD',3)=0) then begin Rotate:=Header[$0E02] and 3; // image sizes are fixed, depending on the given image index if Overview then ImageIndex:=0; Width:=192 shl ImageIndex; Height:=128 shl ImageIndex; if (Rotate=1) or (Rotate=3) then begin Temp:=Width; Width:=Height; Height:=Temp; end; ColorScheme:=csPhotoYCC; BitsPerSample:=8; SamplesPerPixel:=3; BitsPerPixel:=BitsPerSample*SamplesPerPixel; if ImageIndex>2 then Compression:=ctPCDHuffmann else Compression:=ctNone; ImageCount:=(Header[10] shl 8) or Header[11]; Result:=True; end; finally end; end; end; //----------------- TPPMGraphic ---------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TPPMGraphic.CanLoad(Stream: PStream): boolean; var Buffer: array[0..9] of Char; LastPosition: cardinal; begin LastPosition:=Stream.Position; Result:=(Stream.Size-Stream.Position)>10; if Result then begin Stream.Read(Buffer,sizeof(Buffer)); Result:=(Buffer[0]='P') and (Buffer[1] in ['1'..'6']); end; Stream.Position:=LastPosition; end; //---------------------------------------------------------------------------------------------------------------------- function TPPMGraphic.CurrentChar: Char; begin if FIndex=sizeof(FBuffer) then Result:=#0 else Result:=FBuffer[FIndex]; end; //---------------------------------------------------------------------------------------------------------------------- function TPPMGraphic.GetChar: Char; // buffered I/O begin if FIndex=sizeof(FBuffer) then begin if FStream.Position=FStream.Size then GraphicExError(3{gesStreamReadError},['PPM']); FIndex:=0; FStream.Read(FBuffer,sizeof(FBuffer)); end; Result:=FBuffer[FIndex]; Inc(FIndex); end; //---------------------------------------------------------------------------------------------------------------------- function TPPMGraphic.GetNumber: Cardinal; // reads the next number from the stream (and skips all characters which are not in 0..9) var Ch: Char; begin // skip all non-numbers repeat Ch:=GetChar; // skip comments if Ch='#' then begin ReadLine; Ch:=GetChar; end; until Ch in ['0'..'9']; // read the number characters and convert meanwhile Result:=0; repeat Result:=10*Result+Ord(Ch)-$30; Ch:=GetChar; until not (Ch in ['0'..'9']); end; //---------------------------------------------------------------------------------------------------------------------- function TPPMGraphic.ReadLine: string; // reads one text line from stream and skips comments var Ch: Char; I: integer; begin Result:=''; repeat Ch:=GetChar; if Ch in [#13,#10] then Break else Result:=Result+Ch; until False; // eat #13#10 combination if (Ch=#13) and (CurrentChar=#10) then GetChar; // delete comments I:=Pos('#',Result); if I>0 then Delete(Result,I,MaxInt); end; //---------------------------------------------------------------------------------------------------------------------- procedure TPPMGraphic.LoadFromStream(Stream: PStream); var Buffer: string; Line24: PBGR; Line8: PByte; X,Y,W,H: integer; Pixel: byte; begin // free previous image if Assigned(FBitmap) then FBitmap.Free; FBasePosition:=Stream.Position; // copy reference for buffered access FStream:=Stream; if ReadImageProperties(Stream,0) then begin with FImageProperties do begin Stream.Position:=FBasePosition; // set index pointer to end of buffer to cause reload FIndex:=sizeof(FBuffer); Buffer:=ReadLine; case Str2Int(Buffer[2]) of 1: // PBM ASCII format (black & white) begin W:=GetNumber; H:=GetNumber; FBitmap:=NewBitmap(W,H); FBitmap.PixelFormat:=pf1Bit; ColorManager.TargetSamplesPerPixel:=1; ColorManager.TargetBitsPerSample:=1; ColorManager.CreateGrayScalePalette(FBitmap,True); // read image data for Y:=0 to pred(FBitmap.Height) do begin Line8:=FBitmap.ScanLine[Y]; Pixel:=0; for X:=1 to FBitmap.Width do begin Pixel:=(Pixel shl 1) or (GetNumber and 1); if (X mod 8)=0 then begin Line8^:=Pixel; Inc(Line8); Pixel:=0; end; end; if (FBitmap.Width mod 8)<>0 then Line8^:=Pixel shl (8-(FBitmap.Width mod 8)); end; end; 2: // PGM ASCII form (gray scale) begin W:=GetNumber; H:=GetNumber; FBitmap:=NewBitmap(W,H); FBitmap.PixelFormat:=pf8Bit; // skip maximum color value GetNumber; ColorManager.TargetSamplesPerPixel:=1; ColorManager.TargetBitsPerSample:=8; ColorManager.CreateGrayScalePalette(FBitmap,False); // read image data for Y:=0 to pred(FBitmap.Height) do begin Line8:=FBitmap.ScanLine[Y]; for X:=0 to pred(FBitmap.Width) do begin Line8^:=GetNumber; Inc(Line8); end; end; end; 3: // PPM ASCII form (true color) begin W:=GetNumber; H:=GetNumber; FBitmap:=NewBitmap(W,H); FBitmap.PixelFormat:=pf24Bit; // skip maximum color value GetNumber; for Y:=0 to pred(FBitmap.Height) do begin Line24:=FBitmap.ScanLine[Y]; for X:=0 to pred(FBitmap.Width) do begin Line24.R:=GetNumber; Line24.G:=GetNumber; Line24.B:=GetNumber; Inc(Line24); end; end; end; 4: // PBM binary format (black & white) begin W:=GetNumber; H:=GetNumber; FBitmap:=NewBitmap(W,H); FBitmap.PixelFormat:=pf1Bit; ColorManager.TargetSamplesPerPixel:=1; ColorManager.TargetBitsPerSample:=1; ColorManager.CreateGrayScalePalette(FBitmap,True); // read image data for Y:=0 to pred(FBitmap.Height) do begin Line8:=FBitmap.ScanLine[Y]; for X:=0 to pred(FBitmap.Width div 8) do begin Line8^:=Byte(GetChar); Inc(Line8); end; if (FBitmap.Width mod 8)<>0 then Line8^:=Byte(GetChar); end; end; 5: // PGM binary form (gray scale) begin W:=GetNumber; H:=GetNumber; FBitmap:=NewBitmap(W,H); FBitmap.PixelFormat:=pf8Bit; // skip maximum color value GetNumber; ColorManager.TargetSamplesPerPixel:=1; ColorManager.TargetBitsPerSample:=8; ColorManager.CreateGrayScalePalette(FBitmap,False); // read image data for Y:=0 to pred(FBitmap.Height) do begin Line8:=FBitmap.ScanLine[Y]; for X:=0 to pred(FBitmap.Width) do begin Line8^:=Byte(GetChar); Inc(Line8); end; end; end; 6: // PPM binary form (true color) begin W:=GetNumber; H:=GetNumber; FBitmap:=NewBitmap(W,H); FBitmap.PixelFormat:=pf24Bit; // skip maximum color value GetNumber; // Pixel values are store linearly (but RGB instead BGR). // There's one allowed white space which will automatically be skipped by the first // GetChar call below // now read the pixels for Y:=0 to pred(FBitmap.Height) do begin Line24:=FBitmap.ScanLine[Y]; for X:=0 to pred(FBitmap.Width) do begin Line24.R:=Byte(GetChar); Line24.G:=Byte(GetChar); Line24.B:=Byte(GetChar); Inc(Line24); end; end; end; end; end; end else GraphicExError(1{gesInvalidImage},['PBM, PGM or PPM']); end; //---------------------------------------------------------------------------------------------------------------------- function TPPMGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; var Buffer: string; begin FStream := Stream; Result:=inherited ReadImageProperties(Stream,ImageIndex); with FImageProperties do begin // set index pointer to end of buffer to cause reload FIndex:=sizeof(FBuffer); Buffer:=ReadLine; Compression:=ctNone; if Buffer[1]='P' then begin case Str2Int(Buffer[2]) of 1: // PBM ASCII format (black & white) begin Width:=GetNumber; Height:=GetNumber; SamplesPerPixel:=1; BitsPerSample:=1; ColorScheme:=csIndexed; BitsPerPixel:=SamplesPerPixel*BitsPerSample; end; 2: // PGM ASCII form (gray scale) begin Width:=GetNumber; Height:=GetNumber; // skip maximum color value GetNumber; SamplesPerPixel:=1; BitsPerSample:=8; ColorScheme:=csIndexed; BitsPerPixel:=SamplesPerPixel*BitsPerSample; end; 3: // PPM ASCII form (true color) begin Width:=GetNumber; Height:=GetNumber; // skip maximum color value GetNumber; SamplesPerPixel:=3; BitsPerSample:=8; ColorScheme:=csRGB; BitsPerPixel:=SamplesPerPixel*BitsPerSample; end; 4: // PBM binary format (black & white) begin Width:=GetNumber; Height:=GetNumber; SamplesPerPixel:=1; BitsPerSample:=1; ColorScheme:=csIndexed; BitsPerPixel:=SamplesPerPixel*BitsPerSample; end; 5: // PGM binary form (gray scale) begin Width:=GetNumber; Height:=GetNumber; // skip maximum color value GetNumber; SamplesPerPixel:=1; BitsPerSample:=8; ColorScheme:=csIndexed; BitsPerPixel:=SamplesPerPixel*BitsPerSample; end; 6: // PPM binary form (true color) begin Width:=GetNumber; Height:=GetNumber; // skip maximum color value GetNumber; SamplesPerPixel:=3; BitsPerSample:=8; ColorScheme:=csRGB; BitsPerPixel:=SamplesPerPixel*BitsPerSample; end; end; Result:=True; end; end; end; //----------------- TCUTGraphic ---------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TCUTGraphic.CanLoad(Stream: PStream): boolean; // Note: cut files cannot be determined from stream because the only information // is width and height of the image at stream/image start which is by no means // enough to identify a cut (or any other) image. begin Result:=False; end; //---------------------------------------------------------------------------------------------------------------------- procedure TCUTGraphic.LoadFromStream(Stream: PStream); var Buffer: PByte; Run,Line: pointer; {$IFDEF NOCLASSES} Decoder: PCUTRLEDECODER; {$ELSE} Decoder: TCUTRLEDecoder; {$ENDIF} CUTSize: cardinal; Y: integer; begin // free previous image if Assigned(FBitmap) then FBitmap.Free; FBasePosition:=Stream.Position; if ReadImageProperties(Stream,0) then begin with FImageProperties do begin Stream.Position:=FBasePosition+6; FBitmap:=NewBitmap(Width,Height); FBitmap.PixelFormat:=pf8Bit; LoadPalette; CutSize:=Stream.Size-Stream.Position; {$IFDEF NOCLASSES} new( Decoder, Create ); {$ELSE} Decoder:=TCUTRLEDecoder.Create; {$ENDIF} Buffer:=nil; try GetMem(Buffer,CutSize); Stream.Read(Buffer^,CUTSize); Run:=Buffer; for Y:=0 to pred(FBitmap.Height) do begin Line:=FBitmap.ScanLine[Y]; Decoder.Decode(Run,Line,0,FBitmap.Width); end; finally Decoder.Free; if Assigned(Buffer) then FreeMem(Buffer); end; end; end; end; //---------------------------------------------------------------------------------------------------------------------- function TCUTGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; var Dummy: word; begin inherited ReadImageProperties(Stream,ImageIndex); with FImageProperties do begin Stream.Read(Dummy,sizeof(Dummy)); Width:=Dummy; Stream.Read(Dummy,sizeof(Dummy)); Height:=Dummy; ColorScheme:=csIndexed; BitsPerSample:=8; SamplesPerPixel:=1; BitsPerPixel:=BitsPerSample*SamplesPerPixel; Compression:=ctRLE; Result:=True; end; end; //---------------------------------------------------------------------------------------------------------------------- type // the palette file header is actually more complex than the // image file's header, funny... PHaloPaletteHeader = ^THaloPaletteHeader; THaloPaletteHeader = packed record ID: array[0..1] of Char; // should be 'AH' Version,Size: word; FileType,SubType: byte; BrdID,GrMode: word; MaxIndex,MaxRed,MaxGreen,MaxBlue: word; // colors = MaxIndex + 1 Signature: array[0..7] of Char; // 'Dr. Halo' Filler: array[0..11] of byte; end; //---------------------------------------------------------------------------------------------------------------------- procedure TCUTGraphic.LoadPalette; var Header: PHaloPaletteHeader; I: integer; Buffer: array[0..511] of byte; Run: PWord; R,G,B: byte; Temp: PStream; begin if FileExists(FPaletteFile) then begin Temp:=NewReadFileStream(FPaletteFile); try // quite strange file organization here, we need always to load 512 bytes blocks // and skip occasionally some bytes Temp.Read(Buffer,sizeof(Buffer)); Header:=@Buffer; Run:=@Buffer; Inc(PByte(Run),sizeof(Header^)); for I:=0 to Header.MaxIndex do begin // load next 512 bytes buffer if necessary if (Integer(Run)-Integer(@Buffer))>506 then begin Temp.Read(Buffer,sizeof(Buffer)); Run:=@Buffer; end; B:=Run^; Inc(Run); G:=Run^; Inc(Run); R:=Run^; Inc(Run); FBitmap.DIBPalEntries[I]:=Windows.RGB(R,G,B); end; finally Temp.Free; end; end else begin // no external palette so use gray scale for I:=0 to 255 do FBitmap.DIBPalEntries[I]:=Windows.RGB(I,I,I); end; end; //----------------- TGIFGraphic ---------------------------------------------------------------------------------------- const // logical screen descriptor packed field masks GIF_GLOBALCOLORTABLE = $80; GIF_COLORRESOLUTION = $70; GIF_GLOBALCOLORTABLESORTED = $08; GIF_COLORTABLESIZE = $07; // image flags GIF_LOCALCOLORTABLE = $80; GIF_INTERLACED = $40; GIF_LOCALCOLORTABLESORTED= $20; // block identifiers GIF_PLAINTEXT = $01; GIF_GRAPHICCONTROLEXTENSION = $F9; GIF_COMMENTEXTENSION = $FE; GIF_APPLICATIONEXTENSION = $FF; GIF_IMAGEDESCRIPTOR = Ord(','); GIF_EXTENSIONINTRODUCER = Ord('!'); GIF_TRAILER = Ord(';'); type TGIFHeader = packed record Signature: array[0..2] of Char; // magic ID 'GIF' Version: array[0..2] of Char; // '87a' or '89a' end; TLogicalScreenDescriptor = packed record ScreenWidth: Word; ScreenHeight: Word; PackedFields, BackgroundColorIndex, // index into global color table AspectRatio: Byte; // actual ratio = (AspectRatio + 15) / 64 end; TImageDescriptor = packed record //Separator: Byte; // leave that out since we always read one bye ahead Left: Word; // X position of image with respect to logical screen Top: Word; // Y position Width: Word; Height: Word; PackedFields: Byte; end; //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TGIFGraphic.CanLoad(Stream: PStream): boolean; var Header: TGIFHeader; LastPosition: cardinal; begin LastPosition:=Stream.Position; Result:=(Stream.Size-Stream.Position)>(sizeof(TGIFHeader)+sizeof(TLogicalScreenDescriptor)+sizeof(TImageDescriptor)); if Result then begin Stream.Read(Header,sizeof(Header)); Result:=UpperCase(Header.Signature)='GIF'; end; Stream.Position:=LastPosition; end; function TGIFGraphic.Transparent: boolean; begin Result := ImageProperties.HasAlpha; end; function TGIFGraphic.Count: Integer; begin Result := ImageProperties.ImageCount; end; //---------------------------------------------------------------------------------------------------------------------- function TGIFGraphic.SkipExtensions: byte; // Skips all blocks until an image block has been found in the data stream. // Result is the image block ID if an image block could be found. var Increment: byte; begin // iterate through the blocks until first image is found repeat FStream.Read(Result,1); if Result=GIF_EXTENSIONINTRODUCER then begin // skip any extension FStream.Read(Result,1); case Result of GIF_PLAINTEXT: begin // block size of text grid data FStream.Read(Increment,1); FStream.Seek(Increment,spCurrent); // skip variable lengthed text block repeat // block size FStream.Read(Increment,1); if Increment=0 then Break; FStream.Seek(Increment,spCurrent); until False; end; GIF_GRAPHICCONTROLEXTENSION: begin // block size FStream.Read(Increment,1); // skip block and its terminator FStream.Seek(Increment+1,spCurrent); end; GIF_COMMENTEXTENSION: repeat // block size FStream.Read(Increment,1); if Increment=0 then Break; FStream.Seek(Increment,spCurrent); until False; GIF_APPLICATIONEXTENSION: begin // application id and authentication code plus potential application data repeat FStream.Read(Increment,1); if Increment=0 then Break; FStream.Seek(Increment,spCurrent); until False; end; end; end; until (Result=GIF_IMAGEDESCRIPTOR) or (Result=GIF_TRAILER); end; //---------------------------------------------------------------------------------------------------------------------- procedure TGIFGraphic.LoadFromStream(Stream: PStream); var Header: TGIFHeader; ScreenDescriptor: TLogicalScreenDescriptor; ImageDescriptor: TImageDescriptor; I,NE: cardinal; R,G,B,BlockID,InitCodeSize: byte; RawData,Run: PByte; TargetBuffer,TargetRun,Line: pointer; Pass,Increment,Marker: integer; {$IFDEF NOCLASSES} Decoder: PGIFLZWDecoder; {$ELSE} Decoder: TGIFLZWDecoder; {$ENDIF} begin // free previous image if Assigned(FBitmap) then FBitmap.Free; FBasePosition:=Stream.Position; FStream:=Stream; if ReadImageProperties(Stream,0) then begin with FImageProperties do begin Stream.Position:=FBasePosition; Stream.Read(Header,sizeof(Header)); FBitmap:=NewBitmap(Width,Height); FBitmap.PixelFormat:=pf8Bit; // general information Stream.Read(ScreenDescriptor,sizeof(ScreenDescriptor)); // read global color table if given if (ScreenDescriptor.PackedFields and GIF_GLOBALCOLORTABLE)<>0 then begin // the global color table immediately follows the screen descriptor NE:=2 shl (ScreenDescriptor.PackedFields and GIF_COLORTABLESIZE); for I:=0 to pred(NE) do begin Stream.Read(B,1); Stream.Read(G,1); Stream.Read(R,1); FBitmap.DIBPalEntries[I]:=Windows.RGB(R,G,B); end; end; BlockID:=SkipExtensions; // image found? if BlockID=GIF_IMAGEDESCRIPTOR then begin Stream.Read(ImageDescriptor,sizeof(TImageDescriptor)); // if there is a local color table then override the already set one if (ImageDescriptor.PackedFields and GIF_LOCALCOLORTABLE)<>0 then begin // the global color table immediately follows the image descriptor NE:=2 shl (ImageDescriptor.PackedFields and GIF_COLORTABLESIZE); for I:=0 to pred(NE) do begin Stream.Read(B,1); Stream.Read(G,1); Stream.Read(R,1); FBitmap.DIBPalEntries[I]:=Windows.RGB(R,G,B); end; end; Stream.Read(InitCodeSize,1); // decompress data in one step // 1) count data Marker:=Stream.Position; Pass:=0; Increment:=0; repeat if Stream.Read(Increment,1)=0 then Break; Inc(Pass,Increment); Stream.Seek(Increment,spCurrent); until Increment=0; // 2) allocate enough memory GetMem(RawData,Pass); // add one extra line of extra memory for badly coded images GetMem(TargetBuffer,FBitmap.Width*(FBitmap.Height+1)); try // 3) read and decode data Stream.Position:=Marker; Increment:=0; Run:=RawData; repeat if Stream.Read(Increment,1)=0 then Break; Stream.Read(Run^,Increment); Inc(Run,Increment); until Increment=0; {$IFDEF NOCLASSES} new( Decoder, Create( InitCodeSize, FBitmap.Width ) ); {$ELSE} Decoder:=TGIFLZWDecoder.Create(InitCodeSize, FBitmap.Width); {$ENDIF} try Run:=RawData; Decoder.Decode(Pointer(Run),TargetBuffer,Pass,FBitmap.Width*FBitmap.Height); finally Decoder.Free; if Decoder.GIFCorrupted then FCorrupted := TRUE; end; // finally transfer image data if (ImageDescriptor.PackedFields and GIF_INTERLACED)=0 then begin TargetRun:=TargetBuffer; for I:=0 to pred(FBitmap.Height) do begin Line:=FBitmap.ScanLine[I]; Move(TargetRun^,Line^,FBitmap.Width); Inc(PByte(TargetRun),FBitmap.Width); end; end else begin TargetRun:=TargetBuffer; // interlaced image, need to move in four passes for Pass:=0 to 3 do begin // determine start line and increment of the pass case Pass of 0: begin I:=0; Increment:=8; end; 1: begin I:=4; Increment:=8; end; 2: begin I:=2; Increment:=4; end; else I:=1; Increment:=2; end; while I0 then begin BitsPerSample:=(ScreenDescriptor.PackedFields and GIF_COLORTABLESIZE)+1; // the global color table immediately follows the screen descriptor Stream.Seek(3*(1 shl BitsPerSample),spCurrent); end; BlockID:=SkipExtensions; // image found? if BlockID=GIF_IMAGEDESCRIPTOR then begin Stream.Read(ImageDescriptor,sizeof(TImageDescriptor)); Width:=ImageDescriptor.Width; if Width=0 then Width:=ScreenDescriptor.ScreenWidth; Height:=ImageDescriptor.Height; if Height=0 then Height:=ScreenDescriptor.ScreenHeight; // if there is a local color table then override the already set one LocalColorTable:=(ImageDescriptor.PackedFields and GIF_LOCALCOLORTABLE)<>0; if LocalColorTable then BitsPerSample:=(ImageDescriptor.PackedFields and GIF_LOCALCOLORTABLE)+1; Interlaced:=(ImageDescriptor.PackedFields and GIF_INTERLACED)<>0; end; BitsPerPixel:=SamplesPerPixel*BitsPerSample; Result:=True; end; end; end; //----------------- TRLAGraphic ---------------------------------------------------------------------------------------- // This implementation is based on code from Dipl. Ing. Ingo Neumann (ingo@upstart.de, ingo_n@dialup.nacamar.de). type TRLAWindow = packed record Left,Right,Bottom,Top: smallint; end; TRLAHeader = packed record Window, // overall image size Active_window: TRLAWindow; // size of non-zero portion of image (we use this as actual image size) Frame, // frame number if part of a sequence Storage_type, // type of image channels (0 - integer data, 1 - float data) Num_chan, // samples per pixel (usually 3: r, g, b) Num_matte, // number of matte channels (usually only 1) Num_aux, // number of auxiliary channels, usually 0 Revision: smallint; // always $FFFE Gamma: array[0..15] of Char; // gamma single value used when writing the image Red_pri: array[0..23] of Char; // used chromaticity for red channel (typical format: "%7.4f %7.4f") Green_pri: array[0..23] of Char; // used chromaticity for green channel Blue_pri: array[0..23] of Char; // used chromaticity for blue channel White_pt: array[0..23] of Char; // used chromaticity for white point Job_num: integer; // rendering speciifc Name: array[0..127] of Char; // original file name Desc: array[0..127] of Char; // a file description ProgramName: array[0..63] of Char; // name of program which created the image Machine: array[0..31] of Char; // name of computer on which the image was rendered User: array[0..31] of Char; // user who ran the creation program of the image Date: array[0..19] of Char; // creation data of image (ex: Sep 30 12:29 1993) Aspect: array[0..23] of Char; // aspect format of the file (external resource) Aspect_ratio: array[0..7] of Char; // float number Width /Height Chan: array[0..31] of Char; // color space (can be: rgb, xyz, sampled or raw) Field: smallint; // 0 - non-field rendered data, 1 - field rendered data Time: array[0..11] of Char; // time needed to create the image (used when rendering) Filter: array[0..31] of Char; // filter name to post-process image data Chan_bits, // bits per sample Matte_type, // type of matte channel (see aux_type) Matte_bits, // precision of a pixel's matte channel (1..32) Aux_type, // type of aux channel (0 - integer data; 4 - single (float) data Aux_bits: smallint; // bits precision of the pixel's aux channel (1..32 bits) Aux: array[0..31] of Char; // auxiliary channel as either range or depth Space: array[0..35] of Char; // unused Next: integer; // offset for next header if multi-frame image end; //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TRLAGraphic.CanLoad(Stream: PStream): boolean; var Header: TRLAHeader; LastPosition: cardinal; begin LastPosition:=Stream.Position; Result:=(Stream.Size-Stream.Position)>sizeof(Header); if Result then begin Stream.Read(Header,sizeof(Header)); Result:=(System.Swap(Word(Header.Revision))=$FFFE) and ((LowerCase(Header.Chan)='rgb') or (LowerCase(Header.Chan)='xyz')); end; Stream.Position:=LastPosition; end; //---------------------------------------------------------------------------------------------------------------------- procedure TRLAGraphic.LoadFromStream(Stream: PStream); var Offsets: TCardinalArray; RLELength: word; Line: pointer; Y,I: Integer; // RLE buffers RawBuffer,RedBuffer,GreenBuffer,BlueBuffer,AlphaBuffer: pointer; {$IFDEF NOCLASSES} Decoder: PRLADecoder; {$ELSE} Decoder: TRLADecoder; {$ENDIF} begin // free previous image if Assigned(FBitmap) then FBitmap.Free; FBasePosition:=Stream.Position; if ReadImageProperties(Stream,0) then begin with FImageProperties do begin // dimension of image, top might be larger than bottom denoting a bottom up image FBitmap:=NewBitmap(Width,Height); ColorManager.SourceSamplesPerPixel:=SamplesPerPixel; ColorManager.TargetSamplesPerPixel:=SamplesPerPixel; ColorManager.SourceBitsPerSample:=BitsPerSample; if BitsPerSample>8 then ColorManager.TargetBitsPerSample:=8 else ColorManager.TargetBitsPerSample:=BitsPerSample; ColorManager.SourceColorScheme:=ColorScheme; if ColorScheme=csRGBA then ColorManager.TargetColorScheme:=csBGRA else ColorManager.TargetColorScheme:=csBGR; FBitmap.PixelFormat:=ColorManager.TargetPixelFormat; if FileGamma<>1 then begin ColorManager.SetGamma(FileGamma); ColorManager.TargetOptions:=ColorManager.TargetOptions+[coApplyGamma]; Include(Options,ioUseGamma); end; // each scanline is organized in RLE compressed strips whose location in the stream // is determined by the offsets table SetLength(Offsets,Height); Stream.Read(Offsets[0],Height*sizeof(Cardinal)); for I:=0 to pred(Height) do SwapLong(@Offsets[I],1); // setup intermediate storage {$IFDEF NOCLASSES} new( Decoder, Create ); {$ELSE} Decoder:=TRLADecoder.Create; {$ENDIF} RawBuffer:=nil; RedBuffer:=nil; GreenBuffer:=nil; BlueBuffer:=nil; AlphaBuffer:=nil; try GetMem(RedBuffer,Width); GetMem(GreenBuffer,Width); GetMem(BlueBuffer,Width); GetMem(AlphaBuffer,Width); // no go for each scanline for Y:=0 to pred(Height) do begin Stream.Position:=FBasePosition+Offsets[Y]; if BottomUp then Line:=FBitmap.ScanLine[Integer(Height)-Y-1] else Line:=FBitmap.ScanLine[Y]; // read channel data to decode // red Stream.Read(RLELength,sizeof(RLELength)); RLELength:=System.Swap(RLELength); ReallocMem(RawBuffer,RLELength); Stream.Read(RawBuffer^,RLELength); Decoder.Decode(RawBuffer,RedBuffer,RLELength,Width); // green Stream.Read(RLELength,sizeof(RLELength)); RLELength:=System.Swap(RLELength); ReallocMem(RawBuffer,RLELength); Stream.Read(RawBuffer^,RLELength); Decoder.Decode(RawBuffer,GreenBuffer,RLELength,Width); // blue Stream.Read(RLELength,sizeof(RLELength)); RLELength:=System.Swap(RLELength); ReallocMem(RawBuffer,RLELength); Stream.Read(RawBuffer^,RLELength); Decoder.Decode(RawBuffer,BlueBuffer,RLELength,Width); if ColorManager.TargetColorScheme=csBGR then begin ColorManager.ConvertRow([RedBuffer,GreenBuffer,BlueBuffer],Line,Width,$FF); end else begin // alpha Stream.Read(RLELength,sizeof(RLELength)); RLELength:=System.Swap(RLELength); ReallocMem(RawBuffer,RLELength); Stream.Read(RawBuffer^,RLELength); Decoder.Decode(RawBuffer,AlphaBuffer,RLELength,Width); ColorManager.ConvertRow([RedBuffer,GreenBuffer,BlueBuffer,AlphaBuffer],Line,Width,$FF); end; end; finally if Assigned(RawBuffer) then FreeMem(RawBuffer); if Assigned(RedBuffer) then FreeMem(RedBuffer); if Assigned(GreenBuffer) then FreeMem(GreenBuffer); if Assigned(BlueBuffer) then FreeMem(BlueBuffer); if Assigned(AlphaBuffer) then FreeMem(AlphaBuffer); Decoder.Free; end; end; end; end; //---------------------------------------------------------------------------------------------------------------------- function TRLAGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; var Header: TRLAHeader; begin Result:=inherited ReadImageProperties(Stream,ImageIndex); with FImageProperties do begin Stream.Read(Header,sizeof(Header)); // data is always given in big endian order, so swap data which needs this SwapHeader(Header); Options:=[ioBigEndian]; SamplesPerPixel:=Header.num_chan; if Header.num_matte=1 then Inc(SamplesPerPixel); BitsPerSample:=Header.Chan_bits; BitsPerPixel:=SamplesPerPixel*BitsPerSample; if LowerCase(Header.Chan)='rgb' then begin if Header.num_matte>0 then ColorScheme:=csRGBA else ColorScheme:=csRGB; end else if LowerCase(Header.Chan)='xyz' then Exit; try FileGamma:=Str2Double(Header.Gamma); except end; Compression:=ctRLE; // dimension of image, top might be larger than bottom denoting a bottom up image Width:=Header.Active_window.Right-Header.Active_window.Left+1; Height:=Abs(Header.Active_window.Bottom-Header.Active_window.Top)+1; BottomUp:=(Header.Active_window.Bottom-Header.Active_window.Top)<0; Result:=True; end; end; //---------------------------------------------------------------------------------------------------------------------- procedure TRLAGraphic.SwapHeader(var Header); // separate swap method to ease reading the main flow of the LoadFromStream method begin with TRLAHeader(Header) do begin SwapShort(@Window,4); SwapShort(@Active_window,4); Frame:=System.Swap(Frame); Storage_type:=System.Swap(Storage_type); Num_chan:=System.Swap(Num_chan); Num_matte:=System.Swap(Num_matte); Num_aux:=System.Swap(Num_aux); Revision:=System.Swap(Revision); Job_num:=SwapLong(Job_num); Field:=System.Swap(Field); Chan_bits:=System.Swap(Chan_bits); Matte_type:=System.Swap(Matte_type); Matte_bits:=System.Swap(Matte_bits); Aux_type:=System.Swap(Aux_type); Aux_bits:=System.Swap(Aux_bits); Next:=SwapLong(Next); end; end; //----------------- TPSDGraphic ---------------------------------------------------------------------------------------- const // color modes PSD_BITMAP = 0; PSD_GRAYSCALE = 1; PSD_INDEXED = 2; PSD_RGB = 3; PSD_CMYK = 4; PSD_MULTICHANNEL = 7; PSD_DUOTONE = 8; PSD_LAB = 9; PSD_COMPRESSION_NONE = 0; PSD_COMPRESSION_RLE = 1; // RLE compression (same as TIFF packed bits) type TPSDHeader = packed record Signature: array[0..3] of Char; // always '8BPS' Version: word; // always 1 Reserved: array[0..5] of byte; // reserved, always 0 Channels: word; // 1..24, number of channels in the image (including alpha) Rows, Columns: cardinal; // 1..30000, size of image Depth: word; // 1,8,16 bits per channel Mode: word; // color mode (see constants above) end; //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TPSDGraphic.CanLoad(Stream: PStream): boolean; var Header: TPSDHeader; LastPosition: cardinal; begin LastPosition:=Stream.Position; Result:=(Stream.Size-Stream.Position)>sizeof(Header); if Result then begin Stream.Read(Header,sizeof(Header)); Result:=(UpperCase(Header.Signature)='8BPS') and (System.Swap(Header.Version)=1); end; Stream.Position:=LastPosition; end; //---------------------------------------------------------------------------------------------------------------------- procedure TPSDGraphic.LoadFromStream(Stream: PStream); var Header: TPSDHeader; Count: cardinal; {$IFDEF NOCLASSES} Decoder: PPackbitsRLEDecoder; {$ELSE} Decoder: TDecoder; {$ENDIF} RLELength: array[0..65535] of word; Y: integer; BPS: cardinal; // bytes per sample either 1 or 2 for 8 bits per channel and 16 bits per channel respectively ChannelSize: integer; // size of one channel (taking BPS into account) NChannels: Integer; Increment: integer; // pointer increment from one line to next // RLE buffers Line,RawBuffer, // all image data compressed Buffer: pointer; // all image data uncompressed Run1, // running pointer in Buffer 1 Run2, // etc. Run3, Run4: PByte; RawPalette: array[0..767] of byte; begin // free previous image if Assigned(FBitmap) then FBitmap.Free; FBasePosition:=Stream.Position; if ReadImageProperties(Stream,0) then begin with FImageProperties do begin Stream.Position:=FBasePosition; Stream.Read(Header,sizeof(Header)); // initialize color manager ColorManager.SourceOptions:=[coNeedByteSwap]; ColorManager.SourceBitsPerSample:=BitsPerSample; if BitsPerSample=16 then ColorManager.TargetBitsPerSample:=8 else ColorManager.TargetBitsPerSample:=BitsPerSample; ColorManager.SourceSamplesPerPixel:=SamplesPerPixel; ColorManager.TargetSamplesPerPixel:=SamplesPerPixel; if (SamplesPerPixel = 2) and (ColorScheme = csG) then ColorManager.TargetSamplesPerPixel := 1; // color space ColorManager.SourceColorScheme:=ColorScheme; case ColorScheme of csG,csIndexed: ColorManager.TargetColorScheme:=ColorScheme; csRGB: ColorManager.TargetColorScheme:=csBGR; csRGBA: ColorManager.TargetColorScheme:=csBGRA; csCMYK: begin ColorManager.TargetColorScheme:=csBGR; ColorManager.TargetSamplesPerPixel:=3; end; csCIELab: begin // PSD uses 0..255 for a and b so we need to convert them to -128..127 ColorManager.SourceOptions:=ColorManager.SourceOptions+[coLabByteRange,coLabChromaOffset]; ColorManager.TargetColorScheme:=csBGR; end; end; FBitmap:=NewDibBitmap(Width,Height, {); FBitmap.PixelFormat:=}ColorManager.TargetPixelFormat); // size of palette Stream.Read(Count,sizeof(Count)); Count:=SwapLong(Count); // setup the palette if necessary, color data immediately follows header case ColorScheme of csG: ColorManager.CreateGrayscalePalette(FBitmap,ioMinIsWhite in Options); csIndexed: begin Stream.Read(RawPalette,Count); Count:=Count div 3; ColorManager.CreateColorPalette(FBitmap,[@RawPalette,@RawPalette[Count],@RawPalette[2*Count]],pfPlane8Triple,Count,False); end; end; // skip resource and layers section Stream.Read(Count,sizeof(Count)); Count:=SwapLong(Count); Stream.Seek(Count,spCurrent); Stream.Read(Count,sizeof(Count)); Count:=SwapLong(Count); // +2 in order to skip the following compression value Stream.Seek(Count+2,spCurrent); // now read out image data RawBuffer:=nil; if Compression=ctPackedBits then begin {$IFDEF NOCLASSES} new( Decoder, Create ); {$ELSE} Decoder:=TPackbitsRLEDecoder.Create; {$ENDIF} //+++++++++++++++ by VK: if not clear buffer before using it, //process can be VERY slow even for small images! FillChar( RLELength, SizeOf( RLELength ), 0 ); Stream.Read(RLELength,2*Height*Channels); SwapShort(@RLELength[0],Height*Channels); end else Decoder:=nil; try case ColorScheme of csG,csIndexed: begin // very simple format here, we don't need the color conversion manager if Assigned(Decoder) then begin // determine whole compressed size Count:=0; for Y:=0 to pred(Height) do Inc(Count,RLELength[Y]); GetMem(RawBuffer,Count); try Stream.Read(RawBuffer^,Count); Run1:=RawBuffer; for Y:=0 to pred(Height) do begin Count:=RLELength[Y]; Line:=FBitmap.ScanLine[Y]; Decoder.Decode(Pointer(Run1),Line,Count,Width); Inc(Run1,Count); end; finally if Assigned(RawBuffer) then FreeMem(RawBuffer); end; end else // uncompressed data for Y:=0 to pred(Height) do begin Stream.Read(FBitmap.ScanLine[Y]^,Width); end; end; csRGB,csRGBA,csCMYK,csCIELab: begin // Data is organized in planes. This means first all red rows, then // all green and finally all blue rows. BPS:=BitsPerSample div 8; ChannelSize:=BPS*Width*Height; GetMem(Buffer,Channels*ChannelSize); try // first run: load image data and decompress it if necessary if Assigned(Decoder) then begin // determine whole compressed size Count:=0; for Y:=0 to High(RLELength) do Inc(Count,RLELength[Y]); Count:=Count*Cardinal(BPS); GetMem(RawBuffer,Count); try Stream.Read(RawBuffer^,Count); NChannels := Channels; Decoder.Decode(RawBuffer,Buffer,Count,NChannels*ChannelSize); finally if Assigned(RawBuffer) then FreeMem(RawBuffer); end; end else Stream.Read(Buffer^,Channels*ChannelSize); Increment:=BPS*Width; // second run: put data into image (convert color space if necessary) case ColorScheme of csRGB: begin Run1:=Buffer; Run2:=Run1; Inc(Run2,ChannelSize); Run3:=Run2; Inc(Run3,ChannelSize); for Y:=0 to pred(Height) do begin ColorManager.ConvertRow([Run1,Run2,Run3],FBitmap.ScanLine[Y],Width,$FF); Inc(Run1,Increment); Inc(Run2,Increment); Inc(Run3,Increment); end; end; csRGBA: begin Run1:=Buffer; Run2:=Run1; Inc(Run2,ChannelSize); Run3:=Run2; Inc(Run3,ChannelSize); Run4:=Run3; Inc(Run4,ChannelSize); for Y:=0 to pred(Height) do begin ColorManager.ConvertRow([Run1,Run2,Run3,Run4],FBitmap.ScanLine[Y],Width,$FF); Inc(Run1,Increment); Inc(Run2,Increment); Inc(Run3,Increment); Inc(Run4,Increment); end; end; csCMYK: begin // Photoshop CMYK values are given with 0 for maximum values, but the // (general) CMYK conversion works with 255 as maxium value. Hence we must reverse // all entries in the buffer. Run1:=Buffer; for Y:=1 to 4*ChannelSize do begin Run1^:=255-Run1^; Inc(Run1); end; Run1:=Buffer; Run2:=Run1; Inc(Run2,ChannelSize); Run3:=Run2; Inc(Run3,ChannelSize); Run4:=Run3; Inc(Run4,ChannelSize); for Y:=0 to pred(Height) do begin ColorManager.ConvertRow([Run1,Run2,Run3,Run4],FBitmap.ScanLine[Y],Width,$FF); Inc(Run1,Increment); Inc(Run2,Increment); Inc(Run3,Increment); Inc(Run4,Increment); end; end; csCIELab: begin Run1:=Buffer; Run2:=Run1; Inc(Run2,ChannelSize); Run3:=Run2; Inc(Run3,ChannelSize); for Y:=0 to pred(Height) do begin ColorManager.ConvertRow([Run1,Run2,Run3],FBitmap.ScanLine[Y],Width,$FF); Inc(Run1,Increment); Inc(Run2,Increment); Inc(Run3,Increment); end; end; end; finally if Assigned(Buffer) then FreeMem(Buffer); end; end; end; finally Decoder.Free; end; end; end else GraphicExError(1{gesInvalidImage},['PSD or PDD']); end; //---------------------------------------------------------------------------------------------------------------------- function TPSDGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; var Header: TPSDHeader; Dummy: word; Count: cardinal; begin Result:=inherited ReadImageProperties(Stream,ImageIndex); with FImageProperties do begin Stream.Read(Header,sizeof(Header)); if Header.Signature='8BPS' then begin with Header do begin // PSD files are big endian only Channels:=System.Swap(Channels); Rows:=SwapLong(Rows); Columns:=SwapLong(Columns); Depth:=System.Swap(Depth); Mode:=System.Swap(Mode); end; Options:=[ioBigEndian]; // initialize color manager BitsPerSample:=Header.Depth; Channels:=Header.Channels; // 1..24 channels are supported in PSD files, we can only use 4. // The documentation states that main image data (rgb(a), cmyk etc.) is always // written as first channels in their component order. if Channels>4 then SamplesPerPixel:=4 else SamplesPerPixel:=Channels; BitsPerPixel:=SamplesPerPixel*BitsPerSample; // color space case Header.Mode of PSD_DUOTONE, // duo tone should be handled as grayscale PSD_GRAYSCALE: ColorScheme:=csG; PSD_BITMAP: // B&W begin ColorScheme:=csG; Include(Options,ioMinIsWhite); end; PSD_INDEXED: // 8 bits only are assumed because 16 bit wouldn't make sense here ColorScheme:=csIndexed; PSD_MULTICHANNEL, PSD_RGB: if Header.Channels=3 then ColorScheme:=csRGB else ColorScheme:=csRGBA; PSD_CMYK: ColorScheme:=csCMYK; PSD_LAB: ColorScheme:=csCIELab; end; Width:=Header.Columns; Height:=Header.Rows; // size of palette Stream.Read(Count,sizeof(Count)); Count:=SwapLong(Count); // skip palette (count is always given, might be 0 however, e.g. for RGB) Stream.Seek(Count,spCurrent); // skip resource and layers section Stream.Read(Count,sizeof(Count)); Count:=SwapLong(Count); Stream.Seek(Count,spCurrent); Stream.Read(Count,sizeof(Count)); Count:=SwapLong(Count); Stream.Seek(Count,spCurrent); Stream.Read(Dummy,sizeof(Dummy)); if System.Swap(Dummy)=1 then Compression:=ctPackedBits else Compression:=ctNone; Result:=True; end; end; end; //----------------- TPSPGraphic ---------------------------------------------------------------------------------------- const // block identifiers PSP_IMAGE_BLOCK = 0; // General Image Attributes Block (main) PSP_CREATOR_BLOCK = 1; // Creator Data Block (main) PSP_COLOR_BLOCK = 2; // Color Palette Block (main and sub) PSP_LAYER_START_BLOCK = 3; // Layer Bank Block (main) PSP_LAYER_BLOCK = 4; // Layer Block (sub) PSP_CHANNEL_BLOCK = 5; // Channel Block (sub) PSP_SELECTION_BLOCK = 6; // Selection Block (main) PSP_ALPHA_BANK_BLOCK = 7; // Alpha Bank Block (main) PSP_ALPHA_CHANNEL_BLOCK = 8; // Alpha Channel Block (sub) PSP_THUMBNAIL_BLOCK = 9; // Thumbnail Block (main) PSP_EXTENDED_DATA_BLOCK = 10; // Extended Data Block (main) PSP_TUBE_BLOCK = 11; // Picture Tube Data Block (main) PSP_ADJUSTMENT_EXTENSION_BLOCK = 12; // Adjustment Layer Extension Block (sub) PSP_VECTOR_EXTENSION_BLOCK = 13; // Vector Layer Extension Block (sub) PSP_SHAPE_BLOCK = 14; // Vector Shape Block (sub) PSP_PAINTSTYLE_BLOCK = 15; // Paint Style Block (sub) PSP_COMPOSITE_IMAGE_BANK_BLOCK = 16; // Composite Image Bank (main) PSP_COMPOSITE_ATTRIBUTES_BLOCK = 17; // Composite Image Attributes (sub) PSP_JPEG_BLOCK = 18; // JPEG Image Block (sub) // bitmap types PSP_DIB_IMAGE = 0; // Layer color bitmap PSP_DIB_TRANS_MASK = 1; // Layer transparency mask bitmap PSP_DIB_USER_MASK = 2; // Layer user mask bitmap PSP_DIB_SELECTION= 3; // Selection mask bitmap PSP_DIB_ALPHA_MASK = 4; // Alpha channel mask bitmap PSP_DIB_THUMBNAIL = 5; // Thumbnail bitmap PSP_DIB_THUMBNAIL_TRANS_MASK = 6; // Thumbnail transparency mask PSP_DIB_ADJUSTMENT_LAYER = 7; // Adjustment layer bitmap PSP_DIB_COMPOSITE = 8; // Composite image bitmap PSP_DIB_COMPOSITE_TRANS_MASK = 9; // Composite image transparency // composite image type PSP_IMAGE_COMPOSITE = 0; // Composite Image PSP_IMAGE_THUMBNAIL = 1; // Thumbnail Image // graphic contents flags PSP_GC_RASTERLAYERS = 1; // At least one raster layer PSP_GC_VectorLayers = 2; // At least one vector layer PSP_GC_ADJUSTMENTLAYERS = 4; // At least one adjustment layer // Additional attributes PSP_GC_THUMBNAIL = $01000000; // Has a thumbnail PSP_GC_THUMBNAILTRANSPARENCY = $02000000; // Thumbnail transp. PSP_GC_COMPOSITE = $04000000; // Has a composite image PSP_GC_COMPOSITETRANSPARENCY = $08000000; // Composite transp. PSP_GC_FLATIMAGE = $10000000; // Just a background PSP_GC_SELECTION = $20000000; // Has a selection PSP_GC_FLOATINGSELECTIONLAYER = $40000000; // Has float. selection PSP_GC_ALPHACHANNELS = $80000000; // Has alpha channel(s) // character style flags PSP_STYLE_ITALIC = 1; // Italic property bit PSP_STYLE_STRUCK = 2; // Strike-out property bit PSP_STYLE_UNDERLINED = 4; // Underlined property bit // layer flags PSP_LAYER_VISIBLEFLAG = 1; // Layer is visible PSP_LAYER_MASKPRESENCEFLAG = 2; // Layer has a mask // Shape property flags PSP_SHAPE_ANTIALIASED = 1; // Shape is anti-aliased PSP_SHAPE_Selected = 2; // Shape is selected PSP_SHAPE_Visible = 4; // Shape is visible // Polyline node type flags PSP_NODE_UNCONSTRAINED = 0; // Default node type PSP_NODE_SMOOTH = 1; // Node is smooth PSP_NODE_SYMMETRIC = 2; // Node is symmetric PSP_NODE_ALIGNED = 4; // Node is aligned PSP_NODE_ACTIVE = 8; // Node is active PSP_NODE_LOCKED = 16; // Node is locked (PSP doc says 0x16 here, but this seems to be a typo) PSP_NODE_SELECTED = 32; // Node is selected (PSP doc says 0x32 here) PSP_NODE_VISIBLE = 64; // Node is visible (PSP doc says 0x64 here) PSP_NODE_CLOSED = 128; // Node is closed (PSP doc says 0x128 here) // Blend modes LAYER_BLEND_NORMAL = 0; LAYER_BLEND_DARKEN = 1; LAYER_BLEND_LIGHTEN = 2; LAYER_BLEND_HUE = 3; LAYER_BLEND_SATURATION = 4; LAYER_BLEND_COLOR = 5; LAYER_BLEND_LUMINOSITY = 6; LAYER_BLEND_MULTIPLY = 7; LAYER_BLEND_SCREEN = 8; LAYER_BLEND_DISSOLVE = 9; LAYER_BLEND_OVERLAY = 10; LAYER_BLEND_HARD_LIGHT = 11; LAYER_BLEND_SOFT_LIGHT = 12; LAYER_BLEND_DIFFERENCE = 130; LAYER_BLEND_DODGE = 14; LAYER_BLEND_BURN = 15; LAYER_BLEND_EXCLUSION = 16; LAYER_BLEND_ADJUST = 255; // Adjustment layer types PSP_ADJUSTMENT_NONE = 0; // Undefined adjustment layer type PSP_ADJUSTMENT_LEVEL = 1; // Level adjustment PSP_ADJUSTMENT_CURVE = 2; // Curve adjustment PSP_ADJUSTMENT_BRIGHTCONTRAST = 3; // Brightness-contrast adjustment PSP_ADJUSTMENT_COLORBAL = 4; // Color balance adjustment PSP_ADJUSTMENT_HSL = 5; // HSL adjustment PSP_ADJUSTMENT_CHANNELMIXER = 6; // Channel mixer adjustment PSP_ADJUSTMENT_INVERT = 7; // Invert adjustment PSP_ADJUSTMENT_THRESHOLD = 8; // Threshold adjustment PSP_ADJUSTMENT_POSTER = 9; // Posterize adjustment // Vector shape types PSP_VST_Unknown = 0; // Undefined vector type PSP_VST_TEXT = 1; // Shape represents lines of text PSP_VST_POLYLINE = 2; // Shape represents a multiple segment line PSP_VST_ELLIPSE = 3; // Shape represents an ellipse (or circle) PSP_VST_POLYGON = 4; // Shape represents a closed polygon // Text element types PSP_TET_UNKNOWN = 0; // Undefined text element type PSP_TET_CHAR = 1; // A single character code PSP_TET_CHARSTYLE = 2; // A character style change PSP_TET_LINESTYLE = 3; // A line style change // Text alignment types PSP_TAT_LEFT = 0; // Left text alignment PSP_TAT_CENTER = 1; // Center text alignment PSP_TAT_RIGHT = 2; // Right text alignment // Paint style types PSP_STYLE_NONE = 0; // Undefined paint style PSP_STYLE_COLOR = 1; // Paint using color (RGB or palette index) PSP_STYLE_GRADIENT = 2; // Paint using gradient // Channel types PSP_CHANNEL_COMPOSITE = 0; // Channel of single channel bitmap PSP_CHANNEL_RED = 1; // Red channel of 24 bit bitmap PSP_CHANNEL_GREEN = 2; // Green channel of 24 bit bitmap PSP_CHANNEL_BLUE = 3; // Blue channel of 24 bit bitmap // Resolution metrics PSP_METRIC_UNDEFINED = 0; // Metric unknown PSP_METRIC_INCH = 1; // Resolution is in inches PSP_METRIC_CM = 2; // Resolution is in centimeters // Compression types PSP_COMP_NONE = 0; // No compression PSP_COMP_RLE = 1; // RLE compression PSP_COMP_LZ77 = 2; // LZ77 compression PSP_COMP_JPEG = 3; // JPEG compression (only used by thumbnail and composite image) // Picture tube placement mode PSP_TPM_Random = 0; // Place tube images in random intervals PSPS_TPM_Constant = 1; // Place tube images in constant intervals // Tube selection mode PSP_TSM_RANDOM =0; // Randomly select the next image in tube to display PSP_TSM_INCREMENTAL = 1; // Select each tube image in turn PSP_TSM_ANGULAR = 2; // Select image based on cursor direction PSP_TSM_PRESSURE = 3; // Select image based on pressure (from pressure-sensitive pad) PSP_TSM_VELOCITY = 4; // Select image based on cursor speed // Extended data field types PSP_XDATA_TRNS_INDEX = 0; // Transparency index field // Creator field types PSP_CRTR_FLD_TITLE = 0; // Image document title field PSP_CRTR_FLD_CRT_DATE = 1; // Creation date field PSP_CRTR_FLD_MOD_DATE = 2; // Modification date field PSP_CRTR_FLD_ARTIST = 3; // Artist name field PSP_CRTR_FLD_CPYRGHT = 4; // Copyright holder name field PSP_CRTR_FLD_DESC = 5; // Image document description field PSP_CRTR_FLD_APP_ID = 6; // Creating app id field PSP_CRTR_FLD_APP_VER = 7; // Creating app version field // Creator application identifier PSP_CREATOR_APP_UNKNOWN = 0; // Creator application unknown PSP_CREATOR_APP_PAINT_SHOP_PRO = 1; // Creator is Paint Shop Pro // Layer types (file version 3) PSP_LAYER_NORMAL = 0; // Normal layer PSP_LAYER_FLOATING_SELECTION = 1; // Floating selection layer // Layer types (file version 4) PSP_LAYER_UNDEFINED = 0; // Undefined layer type PSP_LAYER_RASTER = 1; // Standard raster layer PSP_LAYER_FLOATINGRASTERSELECTION = 2; // Floating selection (raster layer) PSP_LAYER_Vector = 3; // Vector layer PSP_LAYER_ADJUSTMENT = 4; // Adjustment layer MagicID = 'Paint Shop Pro Image File'; type // These block header structures are here for informational purposes only because the data of those // headers is read member by member to generalize code for the different file versions TPSPBlockHeader3 = packed record // block header file version 3 HeaderIdentifier: array[0..3] of Char; // i.e. "~BK" followed by a zero byte BlockIdentifier: word; // one of the block identifiers InitialChunkLength, // length of the first sub chunk header or similar TotalBlockLength: cardinal; // length of this block excluding this header end; TPSPBlockHeader4 = packed record // block header file version 4 HeaderIdentifier: array[0..3] of Char; // i.e. "~BK" followed by a zero byte BlockIdentifier: word; // one of the block identifiers TotalBlockLength: cardinal; // length of this block excluding this header end; TPSPColorPaletteInfoChunk = packed record EntryCount: cardinal; // number of entries in the palette end; TPSPColorPaletteChunk = array[0..255] of TRGBQuad; // might actually be shorter TPSPChannelInfoChunk = packed record CompressedSize, UncompressedSize: cardinal; BitmapType, // one of the bitmap types ChannelType: word; // one of the channel types end; // PSP defines a channel content chunk which is just a bunch of bytes (size is CompressedSize). // There is no sense to define this record type here. TPSPFileHeader = packed record Signature: array[0..31] of Char; // the string "Paint Shop Pro Image File\n\x1a", padded with zeroes MajorVersion, MinorVersion: word; end; TPSPImageAttributes = packed record Width,Height: integer; Resolution: double; // Number of pixels per metric ResolutionMetric: byte; // Metric used for resolution (one of the metric constants) Compression, // compression type of image (not thumbnail, it has its own compression) BitDepth, // The bit depth of the color bitmap in each Layer of the image document // (must be 1, 4, 8 or 24). PlaneCount: word; // Number of planes in each layer of the image document (usually 1) ColorCount: cardinal; // number of colors in each layer (2^bit depth) GreyscaleFlag: boolean; // Indicates whether the color bitmap in each layer of image document is a // greyscale (False = not greyscale, True = greyscale). TotalImageSize: cardinal; // Sum of the sizes of all layer color bitmaps. ActiveLayer: integer; // Identifies the layer that was active when the image document was saved. LayerCount: word; // Number of layers in the document. GraphicContents: cardinal; // A series of flags that helps define the image's graphic contents. end; TPSPLayerInfoChunk = packed record //LayerName: array[0..255] of Char; // Name of layer (in ASCII text). Has been replaced in version 4 // by a Delphi like short string (length word and variable length string) LayerType: byte; // Type of layer. ImageRectangle, // Rectangle defining image border. SavedImageRectangle: TRect; // Rectangle within image rectangle that contains "significant" data // (only the contents of this rectangle are saved to the file). LayerOpacity: byte; // Overall layer opacity. BlendingMode: byte; // Mode to use when blending layer. Visible: boolean; // TRUE if layer was visible at time of save, FALSE otherwise. TransparencyProtected: boolean; // TRUE if transparency is protected. LinkGroupIdentifier: byte; // Identifies group to which this layer belongs. MaskRectangle, // Rectangle defining user mask border. SavedMaskRectangle: TRect; // Rectangle within mask rectangle that contains "significant" data // (only the contents of this rectangle are saved to the file). MaskLinked: boolean; // TRUE if mask linked to layer (i.e., mask moves relative to layer) MaskDisabled: boolean; // TRUE if mask is disabled, FALSE otherwise. InvertMask: boolean; // TRUE if mask should be inverted when the layer is merged, FALSE otherwise. BlendRangeCount: word; // Number of valid source-destination field pairs to follow (note, there are // currently always 5 such pairs, but they are not necessarily all valid). SourceBlendRange1, // First source blend range value. DestinationBlendRange1, // First destination blend range value. SourceBlendRange2, DestinationBlendRange2, SourceBlendRange3, DestinationBlendRange3, SourceBlendRange4, DestinationBlendRange4, SourceBlendRange5, DestinationBlendRange5: array[0..3] of byte; // these fields are obsolete since file version 4 because there's an own chunk for them // BitmapCount: Word; // Number of bitmaps to follow. // ChannelCount: Word; // Number of channels to follow. end; //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TPSPGraphic.CanLoad(Stream: PStream): boolean; var Header: TPSPFileHeader; LastPosition: cardinal; begin LastPosition:=Stream.Position; Result:=(Stream.Size-Stream.Position)>sizeof(Header); if Result then begin Stream.Read(Header,sizeof(Header)); Result:=(StrLIComp(Header.Signature,MagicID,Length(MagicID))=0) and (Header.MajorVersion>=3); end; Stream.Position:=LastPosition; end; //---------------------------------------------------------------------------------------------------------------------- procedure TPSPGraphic.LoadFromStream(Stream: PStream); var Header: TPSPFileHeader; Image: TPSPImageAttributes; // to use the code below for file 3 and 4 I read the parts of the block header // separately instead as a structure HeaderIdentifier: array[0..3] of Char; // i.e. "~BK" followed by a zero byte BlockIdentifier: word; // one of the block identifiers InitialChunkLength, // length of the first sub chunk header or similar TotalBlockLength: cardinal; // length of this block excluding this header LastPosition,ChunkSize: cardinal; LayerInfo: TPSPLayerInfoChunk; ChannelInfo: TPSPChannelInfoChunk; LayerName: string; NameLength: word; NextLayerPosition, NextMainBlock: integer; // file version 4 specific data BitmapCount,ChannelCount: word; // load and decoding of image data R,G,B,C: PByte; RedBuffer,GreenBuffer,BlueBuffer,CompBuffer: pointer; X,Y,Index,RowSize: integer; // size in bytes of one scanline // other data RawPalette: array[0..1023] of byte; //--------------- local functions ------------------------------------------- function ReadBlockHeader: boolean; // Fills in the block header variables according to the file version. // Returns True if a block header could be read otherwise False (stream end). begin Result:=Stream.Position3 then Stream.Read(ChunkSize,sizeof(ChunkSize)); Stream.Read(ChannelInfo,sizeof(ChannelInfo)); case ChannelInfo.ChannelType of PSP_CHANNEL_COMPOSITE: // single channel bitmap (indexed or transparency mask) begin if Assigned(CompBuffer) then FreeMem(CompBuffer); GetMem(CompBuffer,ChannelInfo.UncompressedSize); if Image.Compression<>PSP_COMP_NONE then ReadAndDecompress(CompBuffer) else Stream.Read(CompBuffer^,ChannelInfo.CompressedSize); end; PSP_CHANNEL_RED: // red channel of 24 bit bitmap begin if Assigned(RedBuffer) then FreeMem(RedBuffer); GetMem(RedBuffer,ChannelInfo.UncompressedSize); if Image.Compression<>PSP_COMP_NONE then ReadAndDecompress(RedBuffer) else Stream.Read(RedBuffer^,ChannelInfo.CompressedSize); end; PSP_CHANNEL_GREEN: begin if Assigned(GreenBuffer) then FreeMem(GreenBuffer); GetMem(GreenBuffer,ChannelInfo.UncompressedSize); if Image.Compression<>PSP_COMP_NONE then ReadAndDecompress(GreenBuffer) else Stream.Read(GreenBuffer^,ChannelInfo.CompressedSize); end; PSP_CHANNEL_BLUE: begin if Assigned(BlueBuffer) then FreeMem(BlueBuffer); GetMem(BlueBuffer,ChannelInfo.UncompressedSize); if Image.Compression<>PSP_COMP_NONE then ReadAndDecompress(BlueBuffer) else Stream.Read(BlueBuffer^,ChannelInfo.CompressedSize); end; end; end; //--------------- end local functions --------------------------------------- var RowIncrement: Integer; RowWidth: Integer; RowOffset: Integer; begin // free previous image if Assigned(FBitmap) then FBitmap.Free; FBasePosition:=Stream.Position; if ReadImageProperties(Stream,0) then begin Stream.Position:=FBasePosition; RedBuffer:=nil; GreenBuffer:=nil; BlueBuffer:=nil; CompBuffer := nil; //*// with FImageProperties do try // Note: To be robust with future PSP images any reader must be able to skip data // which it doesn't know instead of relying on the size of known structures. // Hence there's some extra work needed with the stream (mainly to keep the // current position before a chunk is read and advancing the stream using the // chunk size field). Stream.Read(Header,sizeof(Header)); // read general image attribute block ReadBlockHeader; LastPosition:=Stream.Position; if Version>3 then Stream.Read(ChunkSize,sizeof(ChunkSize)); Stream.Read(Image,sizeof(Image)); Stream.Position:=LastPosition+TotalBlockLength; //FBitmap:=NewBitmap(Width,Height); with Image do begin ColorManager.SourceOptions:=[]; ColorManager.SourceBitsPerSample:=BitsPerSample; ColorManager.TargetBitsPerSample:=BitsPerSample; ColorManager.SourceSamplesPerPixel:=SamplesPerPixel; ColorManager.TargetSamplesPerPixel:=SamplesPerPixel; ColorManager.SourceColorScheme:=ColorScheme; if ColorScheme=csRGB then ColorManager.TargetColorScheme:=csBGR else ColorManager.TargetColorScheme:=ColorScheme; //FBitmap.PixelFormat:=ColorManager.TargetPixelFormat; FBitmap:=NewDibBitmap(Width,Height,ColorManager.TargetPixelFormat); end; // set bitmap properties RowSize:=0; // make compiler quiet case BitsPerSample of 1: RowSize:=(Image.Width+7) div 8; 4: RowSize:=Image.Width div 2+1; 8: RowSize:=Image.Width; else GraphicExError(2{gesInvalidColorFormat},['PSP']); end; // go through main blocks and read what is needed repeat if not ReadBlockHeader then Break; NextMainBlock:=Stream.Position+TotalBlockLength; // no more blocks? if HeaderIdentifier[0]<>'~' then Break; case BlockIdentifier of PSP_COMPOSITE_IMAGE_BANK_BLOCK: begin // composite image block, if present then it must appear before the layer start block // and represents a composition of several layers // do not need to read anything further //Break; end; PSP_LAYER_START_BLOCK: repeat if not ReadBlockHeader then Break; // calculate start of next (layer) block in case we need to skip this one NextLayerPosition:=Stream.Position+TotalBlockLength; // if all layers have been considered the break loop to continue with other blocks if necessary if BlockIdentifier<>PSP_LAYER_BLOCK then Break; // layer information chunk if Version>3 then begin LastPosition:=Stream.Position; Stream.Read(ChunkSize,sizeof(ChunkSize)); Stream.Read(NameLength,sizeof(NameLength)); SetLength(LayerName,NameLength); if NameLength>0 then Stream.Read(LayerName[1],NameLength); Stream.Read(LayerInfo,sizeof(LayerInfo)); Stream.Position:=LastPosition+ChunkSize; // continue only with undefined or raster chunks if not (LayerInfo.LayerType in [PSP_LAYER_UNDEFINED,PSP_LAYER_RASTER]) then begin Stream.Position:=NextLayerPosition; Continue; end; // in file version 4 there's also an additional bitmap chunk which replaces // two fields formerly located in the LayerInfo chunk LastPosition:=Stream.Position; Stream.Read(ChunkSize,sizeof(ChunkSize)); end else begin SetLength(LayerName,256); Stream.Read(LayerName[1],256); Stream.Read(LayerInfo,sizeof(LayerInfo)); // continue only with normal (raster) chunks if LayerInfo.LayerType<>PSP_LAYER_NORMAL then begin Stream.Position:=NextLayerPosition; Continue; end; end; {if LayerInfo.Visible = 0 then begin Stream.Position:=NextLayerPosition; Continue; end;} Stream.Read(BitmapCount,sizeof(BitmapCount)); Stream.Read(ChannelCount,sizeof(ChannelCount)); // But now we can reliably say whether we have an alpha channel or not. // This kind of information can only be read very late and causes us to // possibly reallocate the entire image (because it is copied by the VCL // when changing the pixel format). // I don't know another way (preferably before the size of the image is set). //if BitmapCount > 0 then begin //Dec( BitmapCount ); if ChannelCount>3 then begin ColorManager.TargetColorScheme:=csBGRA; FBitmap.PixelFormat:=pf32Bit; end; if Version>3 then Stream.Position:=LastPosition+ChunkSize; // allocate memory for all channels and read raw data for X:=0 to pred(ChannelCount) do ReadChannelData; R:=RedBuffer; G:=GreenBuffer; B:=BlueBuffer; C:=CompBuffer; TRY RowWidth := LayerInfo.SavedImageRectangle.Right - LayerInfo.SavedImageRectangle.Left; CASE ImageProperties.BitsPerSample of 1: RowIncrement:=(RowWidth +7) div 8; 4: RowIncrement:=RowWidth div 2+1; 8: RowIncrement:=RowWidth; else RowIncrement := RowSize; end; RowOffset := LayerInfo.SavedImageRectangle.Left * ((BitsPerSample + 7) div 8); if ColorManager.TargetColorScheme in [csIndexed,csG] then begin //for Y:=0 to pred(Height) do for Y:= LayerInfo.SavedImageRectangle.Top to LayerInfo.SavedImageRectangle.Bottom do begin //ColorManager.ConvertRow([C],FBitmap.ScanLine[Y],Width,$FF); ColorManager.ConvertRow([C], Pointer( Integer( FBitmap.ScanLine[Y] ) + RowOffset ), RowWidth,$FF); Inc( C, RowIncrement ); //Inc(C,RowSize); end; end else begin //for Y:=0 to pred(Height) do for Y:= LayerInfo.SavedImageRectangle.Top to LayerInfo.SavedImageRectangle.Bottom-1 do begin //ColorManager.ConvertRow([R,G,B,C],FBitmap.ScanLine[Y],Width,$FF); ColorManager.ConvertRow([R,G,B,C], Pointer( Integer( FBitmap.ScanLine[Y] ) + RowOffset ), RowWidth,$FF); Inc( R, RowIncrement ); //Inc(R,RowSize); Inc( G, RowIncrement ); //Inc(G,RowSize); Inc( B, RowIncrement ); //Inc(B,RowSize); Inc( C, RowIncrement ); //Inc(C,RowSize); end; end; FINALLY {if Assigned(RedBuffer) then FreeMem(RedBuffer); if Assigned(GreenBuffer) then FreeMem(GreenBuffer); if Assigned(BlueBuffer) then FreeMem(BlueBuffer); if Assigned(CompBuffer) then FreeMem(CompBuffer); RedBuffer := nil; GreenBuffer := nil; BlueBuffer := nil; CompBuffer := nil;} END; // after the raster layer has been read there's no need to loop further asm nop end; Break; //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! end; until False; // layer loop PSP_COLOR_BLOCK: // color palette block (this is also present for gray scale and b&w images) begin if Version>3 then Stream.Read(ChunkSize,sizeof(ChunkSize)); Stream.Read(Index,sizeof(Index)); Stream.Read(RawPalette,Index*sizeof(TRGBQuad)); ColorManager.CreateColorPalette(FBitmap,[@RawPalette],pfInterlaced8Quad,Index,True); end; end; // explicitly set stream position to next main block as we might have read a block only partially Stream.Position:=NextMainBlock; until False; // main block loop finally if Assigned(RedBuffer) then FreeMem(RedBuffer); if Assigned(GreenBuffer) then FreeMem(GreenBuffer); if Assigned(BlueBuffer) then FreeMem(BlueBuffer); if Assigned(CompBuffer) then FreeMem(BlueBuffer); end; end else GraphicExError(1{gesInvalidImage},['PSP']); end; //---------------------------------------------------------------------------------------------------------------------- function TPSPGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; var Header: TPSPFileHeader; Image: TPSPImageAttributes; // to use the code below for file 3 and 4 I read the parts of the block header // separately instead as a structure HeaderIdentifier: array[0..3] of Char; // i.e. "~BK" followed by a zero byte BlockIdentifier: word; // one of the block identifiers InitialChunkLength, // length of the first sub chunk header or similar TotalBlockLength: cardinal; // length of this block excluding this header LastPosition,ChunkSize: cardinal; //--------------- local functions ------------------------------------------- function ReadBlockHeader: Boolean; // Fills in the block header variables according to the file version. // Returns True if a block header could be read otherwise False (stream end). begin Result:=Stream.Position=3) then begin Version:=Header.MajorVersion; // read general image attribute block ReadBlockHeader; LastPosition:=Stream.Position; if Header.MajorVersion>3 then Stream.Read(ChunkSize,sizeof(ChunkSize)); Stream.Read(Image,sizeof(Image)); Stream.Position:=LastPosition+TotalBlockLength; if Image.BitDepth=24 then begin BitsPerSample:=8; SamplesPerPixel:=3; ColorScheme:=csRGB; // an alpha channel might exist, this is determined by the layer's channel count end else begin BitsPerSample:=Image.BitDepth; SamplesPerPixel:=1; if Image.GreyscaleFlag then ColorScheme:=csG else ColorScheme:=csIndexed; end; BitsPerPixel:=BitsPerSample*SamplesPerPixel; Width:=Image.Width; Height:=Image.Height; case Image.Compression of PSP_COMP_NONE: Compression:=ctNone; PSP_COMP_RLE: Compression:=ctRLE; PSP_COMP_LZ77: Compression:=ctLZ77; PSP_COMP_JPEG: Compression:=ctJPEG; else Compression:=ctUnknown; end; XResolution:=Image.Resolution; if Image.ResolutionMetric=PSP_METRIC_CM then XResolution:=XResolution*2.54; YResolution:=XResolution; Result:=True; end; end; end; //----------------- TPNGGraphic ---------------------------------------------------------------------------------------- const PNGMagic: array[0..7] of Byte = (137,80,78,71,13,10,26,10); // recognized and handled chunk types IHDR = 'IHDR'; IDAT = 'IDAT'; IEND = 'IEND'; PLTE = 'PLTE'; gAMA = 'gAMA'; tRNS = 'tRNS'; bKGD = 'bKGD'; CHUNKMASK = $20; // used to check bit 5 in chunk types type // The following chunks structures are those which appear in the data field of the general chunk structure // given above. // chunk type: 'IHDR' PIHDRChunk = ^TIHDRChunk; TIHDRChunk = packed record Width, Height: cardinal; BitDepth, // bits per sample (allowed are 1,2,4,8 and 16) ColorType, // combination of: // 1 - palette used // 2 - colors used // 4 - alpha channel used // allowed values are: // 0 - gray scale (allowed bit depths are: 1,2,4,8,16) // 2 - RGB (8,16) // 3 - palette (1,2,4,8) // 4 - gray scale with alpha (8,16) // 6 - RGB with alpha (8,16) Compression, // 0 - LZ77, others are not yet defined Filter, // filter mode 0 is the only one currently defined Interlaced: byte; // 0 - not interlaced, 1 - Adam7 interlaced end; //---------------------------------------------------------------------------------------------------------------------- {$IFNDEF NOCLASSES} class {$ENDIF} function TPNGGraphic.CanLoad(Stream: PStream): boolean; var Magic: array[0..7] of byte; LastPosition: cardinal; begin LastPosition:=Stream.Position; Result:=(Stream.Size-Stream.Position)>sizeof(Magic); if Result then begin Stream.Read(Magic,sizeof(Magic)); Result:=CompareMem(@Magic,@PNGMagic,8); end; Stream.Position:=LastPosition; end; //---------------------------------------------------------------------------------------------------------------------- function TPNGGraphic.IsChunk(ChunkType: TChunkType): boolean; // determines, independant of the cruxial 5ths bits in each "letter", whether the // current chunk type in the header is the same as the given chunk type const Mask = not $20202020; begin Result:=(Cardinal(FHeader.ChunkType) and Mask)=(Cardinal(ChunkType) and Mask); end; //---------------------------------------------------------------------------------------------------------------------- function TPNGGraphic.LoadAndSwapHeader: cardinal; // read next chunk header and swap fields to little endian, // returns the intial CRC value for following checks begin FStream.Read(FHeader,sizeof(FHeader)); Result:=CRC32(0,@FHeader.ChunkType,4); FHeader.Length:=SwapLong(FHeader.Length); end; //---------------------------------------------------------------------------------------------------------------------- function PaethPredictor(A,B,C: byte): byte; var P,PA,PB,PC: integer; begin // a = left, b = above, c = upper left P:=A+B-C; // initial estimate PA:=Abs(P-A); // distances to a, b, c PB:=Abs(P-B); PC:=Abs(P-C); // 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; //---------------------------------------------------------------------------------------------------------------------- procedure TPNGGraphic.ApplyFilter(Filter: byte; Line,PrevLine,Target: PByte; BPP,BytesPerRow: integer); // Applies the filter given in Filter to all bytes in Line (eventually using PrevLine). // Note: The filter type is assumed to be of filter mode 0, as this is the only one currently // defined in PNG. // In opposition to the PNG documentation different identifiers are used here. // Raw refers to the current, not yet decoded value. Decoded refers to the current, already // decoded value (this one is called "raw" in the docs) and Prior is the current value in the // previous line. For the Paeth prediction scheme a fourth pointer is used (PriorDecoded) to describe // the value in the previous line but less the BPP value (Prior[x - BPP]). var I: integer; Raw,Decoded,Prior,PriorDecoded,TargetRun: PByte; begin case Filter of 0: Move(Line^,Target^,BytesPerRow); // no filter, just copy data 1: begin // subtraction filter Raw:=Line; TargetRun:=Target; // Transfer BPP bytes without filtering. This mimics the effect of bytes left to the // scanline being zero. Move(Raw^,TargetRun^,BPP); // now do rest of the line Decoded:=TargetRun; Inc(Raw,BPP); Inc(TargetRun,BPP); Dec(BytesPerRow,BPP); while BytesPerRow>0 do begin TargetRun^:=Byte(Raw^+Decoded^); Inc(Raw); Inc(Decoded); Inc(TargetRun); Dec(BytesPerRow); end; end; 2: begin // Up filter Raw:=Line; Prior:=PrevLine; TargetRun:=Target; while BytesPerRow>0 do begin TargetRun^:=Byte(Raw^+Prior^); Inc(Raw); Inc(Prior); Inc(TargetRun); Dec(BytesPerRow); end; end; 3: begin // average filter // first handle BPP virtual pixels to the left Raw:=Line; Decoded:=Line; Prior:=PrevLine; TargetRun:=Target; for I:=0 to pred(BPP) do begin TargetRun^:=Byte(Raw^+Floor(Prior^/2)); Inc(Raw); Inc(Prior); Inc(TargetRun); end; Dec(BytesPerRow,BPP); // now do rest of line while BytesPerRow>0 do begin TargetRun^:=Byte(Raw^+Floor((Decoded^+Prior^)/2)); Inc(Raw); Inc(Decoded); Inc(Prior); Inc(TargetRun); Dec(BytesPerRow); end; end; 4: begin // paeth prediction // again, start with first BPP pixel which would refer to non-existing pixels to the left Raw:=Line; Decoded:=Target; Prior:=PrevLine; PriorDecoded:=PrevLine; TargetRun:=Target; for I:=0 to pred(BPP) do begin TargetRun^:=Byte(Raw^+PaethPredictor(0,Prior^,0)); Inc(Raw); Inc(Prior); Inc(TargetRun); end; Dec(BytesPerRow,BPP); // finally do rest of line while BytesPerRow>0 do begin TargetRun^:=Byte(Raw^+PaethPredictor(Decoded^,Prior^,PriorDecoded^)); Inc(Raw); Inc(Decoded); Inc(Prior); Inc(PriorDecoded); Inc(TargetRun); Dec(BytesPerRow); end; end; end; end; //---------------------------------------------------------------------------------------------------------------------- procedure TPNGGraphic.LoadFromStream(Stream: PStream); var Description: TIHDRChunk; begin // free previous image if Assigned(FBitmap) then FBitmap.Free; FBasePosition:=Stream.Position; FDecoder:=nil; FStream:=Stream; if ReadImageProperties(Stream,0) then begin with FImageProperties do begin FBitmap:=NewBitmap(Width,Height); Stream.Position:=FBasePosition+8; // skip magic FBackgroundColor:=clWhite; FTransparentColor:=clNone; // first chunk must be an IHDR chunk FCurrentCRC:=LoadAndSwapHeader; FRawBuffer:=nil; ColorManager.SourceOptions:=[coNeedByteSwap]; try // read IHDR chunk ReadDataAndCheckCRC; Move(FRawBuffer^,Description,sizeof(Description)); SwapLong(@Description,2); // currently only one compression type is supported by PNG (LZ77) if Compression=ctLZ77 then begin {$IFDEF NOCLASSES} new( FDecoder, Create(Z_PARTIAL_FLUSH,False) ); {$ELSE} FDecoder:=TLZ77Decoder.Create(Z_PARTIAL_FLUSH,False); {$ENDIF} FDecoder.DecodeInit; end else GraphicExError(5{gesUnsupportedFeature},[ErrorMsg[11]{gesCompressionScheme},PNG]); // setup is done, now go for the chunks repeat FCurrentCRC:=LoadAndSwapHeader; if IsChunk(IDAT) then begin LoadIDAT(Description); // After reading the image data the next chunk header has already been loaded // so continue with code below instead trying to load a new chunk header. end else if IsChunk(PLTE) then begin // palette chunk if (FHeader.Length mod 3)<>0 then GraphicExError(9{gesInvalidPalette},[PNG]); ReadDataAndCheckCRC; // load palette only if the image is indexed colors if Description.ColorType=3 then begin // first setup pixel format before actually creating a palette FSourceBPP:=SetupColorDepth(Description.ColorType,Description.BitDepth); ColorManager.CreateColorPalette(FBitmap,[FRawBuffer],pfInterlaced8Triple,FHeader.Length div 3,True); end; Continue; end else if IsChunk(gAMA) then begin ReadDataAndCheckCRC; // the file gamme given here is a scaled cardinal (e.g. 0.45 is expressed as 45000) ColorManager.SetGamma(SwapLong(PCardinal(FRawBuffer)^)/100000); ColorManager.TargetOptions:=ColorManager.TargetOptions+[coApplyGamma]; Include(Options,ioUseGamma); Continue; end else if IsChunk(bKGD) then begin LoadBackgroundColor(Description); Continue; end else if IsChunk(tRNS) then begin LoadTransparency(Description); Continue; end; // Skip unknown or unsupported chunks (+4 because of always present CRC). // IEND will be skipped as well, but this chunk is empty, so the stream will correctly // end on the first byte after the IEND chunk. Stream.Seek(FHeader.Length+4,spCurrent); if IsChunk(IEND) then Break; // Note: According to the specs an unknown, but as critical marked chunk is a fatal error. if (Byte(FHeader.ChunkType[0]) and CHUNKMASK)=0 then GraphicExError(10{gesUnknownCriticalChunk}); until False; finally if Assigned(FDecoder) then begin FDecoder.DecodeEnd; FDecoder.Free; end; if Assigned(FRawBuffer) then FreeMem(FRawBuffer); end; end; end else GraphicExError(1{gesInvalidImage},[PNG]); end; //---------------------------------------------------------------------------------------------------------------------- function TPNGGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; var Magic: array[0..7] of byte; Description: TIHDRChunk; begin Result:=inherited ReadImageProperties(Stream,ImageIndex); FStream:=Stream; with FImageProperties do begin Stream.Read(Magic,8); if CompareMem(@Magic,@PNGMagic,8) then begin // first chunk must be an IHDR chunk FCurrentCRC:=LoadAndSwapHeader; if IsChunk(IHDR) then begin Include(Options,ioBigEndian); // read IHDR chunk ReadDataAndCheckCRC; Move(FRawBuffer^,Description,sizeof(Description)); SwapLong(@Description,2); if (Description.Width=0) or (Description.Height=0) then Exit; Width:=Description.Width; Height:=Description.Height; if Description.Compression=0 then Compression:=ctLZ77 else Compression:=ctUnknown; BitsPerSample:=Description.BitDepth; SamplesPerPixel:=1; case Description.ColorType of 0: ColorScheme:=csG; 2: begin ColorScheme:=csRGB; SamplesPerPixel:=3; end; 3: ColorScheme:=csIndexed; 4: ColorScheme:=csGA; 6: begin ColorScheme:=csRGBA; SamplesPerPixel:=4; end; else ColorScheme:=csUnknown; end; BitsPerPixel:=SamplesPerPixel*BitsPerSample; FilterMode:=Description.Filter; Interlaced:=Description.Interlaced<>0; HasAlpha:=ColorScheme in [csGA,csRGBA,csBGRA]; // find gamma repeat FCurrentCRC:=LoadAndSwapHeader; if IsChunk(gAMA) then begin ReadDataAndCheckCRC; // the file gamme given here is a scaled cardinal (e.g. 0.45 is expressed as 45000) FileGamma:=SwapLong(PCardinal(FRawBuffer)^)/100000; Break; end; Stream.Seek(FHeader.Length+4,spCurrent); if IsChunk(IEND) then Break; until False; Result:=True; end; end; end; end; //---------------------------------------------------------------------------------------------------------------------- procedure TPNGGraphic.LoadBackgroundColor(const Description); // loads the data from the current chunk (must be a bKGD chunk) and fills the bitmpap with that color var Run: PWord; R,G,B: byte; begin ReadDataAndCheckCRC; with TIHDRChunk(Description) do begin case ColorType of 0,4: // G(A) begin case BitDepth of 2: FBackgroundColor:=MulDiv16(System.Swap(PWord(FRawBuffer)^),15,3); 16: FBackgroundColor:=MulDiv16(System.Swap(PWord(FRawBuffer)^),255,65535); else // 1,4,8 bits gray scale FBackgroundColor:=Byte(System.Swap(PWord(FRawBuffer)^)); end; end; 2,6: // RGB(A) begin Run:=FRawBuffer; if BitDepth=16 then begin R:=MulDiv16(System.Swap(Run^),255,65535); Inc(Run); G:=MulDiv16(System.Swap(Run^),255,65535); Inc(Run); B:=MulDiv16(System.Swap(Run^),255,65535); end else begin R:=Byte(System.Swap(Run^)); Inc(Run); G:=Byte(System.Swap(Run^)); Inc(Run); B:=Byte(System.Swap(Run^)); end; FBackgroundColor:=Windows.RGB(R,G,B); end; else // indexed color scheme (3) FBackgroundColor:=PByte(FRawBuffer)^; end; end; end; //---------------------------------------------------------------------------------------------------------------------- procedure TPNGGraphic.LoadIDAT(const Description); // loads image data from the current position of the stream const // interlace start and offsets 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); PassMask: array[0..6] of byte = ($80,$08,$88,$22,$AA,$55,$FF); var Row: Integer; TargetBPP: integer; RowBuffer: array[Boolean] of PChar; // I use PChar here instead of simple pointer to ease pointer math below EvenRow: boolean; // distincts between the two rows we need to hold for filtering Pass,BytesPerRow,InterlaceRowBytes,InterlaceWidth: integer; begin RowBuffer[False]:=nil; RowBuffer[True]:=nil; try // we can set the dimensions too without // initiating color conversions if FBitmap=nil then FBitmap:=NewBitmap(TIHDRChunk(Description).Width,TIHDRChunk(Description).Height); // adjust pixel format etc. if not yet done if FBitmap.PixelFormat=pfDevice then FSourceBPP:=SetupColorDepth(TIHDRChunk(Description).ColorType,TIHDRChunk(Description).BitDepth); if TIHDRChunk(Description).BitDepth=16 then TargetBPP:=FSourceBPP div 2 else TargetBPP:=FSourceBPP; // set background and transparency color, these values must be set after the // bitmap is actually valid (although, not filled) FBitmap.Canvas.Brush.Color:=FBackgroundColor; FBitmap.Canvas.FillRect(Rect(0,0,FBitmap.Width,FBitmap.Height)); // determine maximum number of bytes per row and consider there's one filter byte at the start of each row BytesPerRow:=TargetBPP*((FBitmap.Width*TIHDRChunk(Description).BitDepth+7) div 8)+1; RowBuffer[True]:=AllocMem(BytesPerRow); RowBuffer[False]:=AllocMem(BytesPerRow); // there can be more than one IDAT chunk in the file but then they must directly // follow each other (handled in ReadRow) EvenRow:=True; // prepare interlaced images if TIHDRChunk(Description).Interlaced=1 then begin for Pass:=0 to 6 do begin // prepare next interlace run if FBitmap.Width<=ColumnStart[Pass] then Continue; InterlaceWidth:=(FBitmap.Width+ColumnIncrement[Pass]-1-ColumnStart[Pass]) div ColumnIncrement[Pass]; InterlaceRowBytes:=TargetBPP*((InterlaceWidth*TIHDRChunk(Description).BitDepth+7) div 8)+1; Row:=RowStart[Pass]; while RowFileCRC then GraphicExError(6{gesInvalidCRC},[PNG]); end; //---------------------------------------------------------------------------------------------------------------------- procedure TPNGGraphic.ReadRow(RowBuffer: pointer; BytesPerRow: integer); // reads and decodes one scanline var LocalBuffer: pointer; PendingOutput: integer; begin LocalBuffer:=RowBuffer; PendingOutput:=BytesPerRow; repeat // read pending chunk data if available input has dropped to zero if FDecoder.AvailableInput=0 then begin FIDATSize:=0; // read all following chunks until enough data is available or there is no further IDAT chunk while FIDATSize=0 do begin // finish if the current chunk is not an IDAT chunk if not IsChunk(IDAT) then Exit; ReadDataAndCheckCRC; FCurrentSource:=FRawBuffer; FIDATSize:=FHeader.Length; // prepare next chunk (plus CRC) FCurrentCRC:=LoadAndSwapHeader; end; end; // this decode call will advance Source and Target accordingly FDecoder.Decode(FCurrentSource,LocalBuffer, FIDATSize-(Integer(FCurrentSource)-Integer(FRawBuffer)), PendingOutput); if FDecoder.ZLibResult=Z_STREAM_END then begin if (FDecoder.AvailableOutput<>0) or (FDecoder.AvailableInput<>0) then GraphicExError(8{gesExtraCompressedData},[PNG]); Break; end; if FDecoder.ZLibResult<>Z_OK then GraphicExError(7{gesCompression},[PNG]); PendingOutput:=BytesPerRow-(Integer(LocalBuffer)-Integer(RowBuffer)); until PendingOutput=0; end; //---------------------------------------------------------------------------------------------------------------------- function TPNGGraphic.SetupColorDepth(ColorType,BitDepth: integer): integer; begin Result:=0; // determine color scheme and setup related stuff, // Note: The calculated BPP value is always at least 1 even for 1 bits per pixel etc. formats // and used in filter calculation. case ColorType of 0: // gray scale (allowed bit depths are: 1, 2, 4, 8, 16 bits) if BitDepth in [1,2,4,8,16] then begin ColorManager.SourceColorScheme:=csG; ColorManager.TargetColorScheme:=csG; ColorManager.SourceSamplesPerPixel:=1; ColorManager.TargetSamplesPerPixel:=1; ColorManager.SourceBitsPerSample:=BitDepth; // 2 bits values are converted to 4 bits values because DIBs don't know the former variant case BitDepth of 2: ColorManager.TargetBitsPerSample:=4; 16: ColorManager.TargetBitsPerSample:=8; else ColorManager.TargetBitsPerSample:=BitDepth; end; FBitmap.PixelFormat:=ColorManager.TargetPixelFormat; ColorManager.CreateGrayscalePalette(FBitmap,False); Result:=(BitDepth+7) div 8; end else GraphicExError(2{gesInvalidColorFormat},[PNG]); 2: // RGB if BitDepth in [8,16] then begin ColorManager.SourceSamplesPerPixel:=3; ColorManager.TargetSamplesPerPixel:=3; ColorManager.SourceColorScheme:=csRGB; ColorManager.TargetColorScheme:=csBGR; ColorManager.SourceBitsPerSample:=BitDepth; ColorManager.TargetBitsPerSample:=8; FBitmap.PixelFormat:=pf24Bit; Result:=BitDepth*3 div 8; end else GraphicExError(2{gesInvalidColorFormat},[PNG]); 3: // palette if BitDepth in [1,2,4,8] then begin ColorManager.SourceColorScheme:=csIndexed; ColorManager.TargetColorScheme:=csIndexed; ColorManager.SourceSamplesPerPixel:=1; ColorManager.TargetSamplesPerPixel:=1; ColorManager.SourceBitsPerSample:=BitDepth; // 2 bits values are converted to 4 bits values because DIBs don't know the former variant if BitDepth=2 then ColorManager.TargetBitsPerSample:=4 else ColorManager.TargetBitsPerSample:=BitDepth; FBitmap.PixelFormat:=ColorManager.TargetPixelFormat; Result:=1; end else GraphicExError(2{gesInvalidColorFormat},[PNG]); 4: // gray scale with alpha, // For the moment this format is handled without alpha, but might later be converted // to RGBA with gray pixels or use a totally different approach. if BitDepth in [8,16] then begin ColorManager.SourceSamplesPerPixel:=1; ColorManager.TargetSamplesPerPixel:=1; ColorManager.SourceBitsPerSample:=BitDepth; ColorManager.TargetBitsPerSample:=8; ColorManager.SourceColorScheme:=csGA; ColorManager.TargetColorScheme:=csIndexed; FBitmap.PixelFormat:=pf8Bit; ColorManager.CreateGrayScalePalette(FBitmap,False); Result:=2*BitDepth div 8; end else GraphicExError(2{gesInvalidColorFormat},[PNG]); 6: // RGB with alpha (8, 16) if BitDepth in [8,16] then begin ColorManager.SourceSamplesPerPixel:=4; ColorManager.TargetSamplesPerPixel:=4; ColorManager.SourceColorScheme:=csRGBA; ColorManager.TargetColorScheme:=csBGRA; ColorManager.SourceBitsPerSample:=BitDepth; ColorManager.TargetBitsPerSample:=8; FBitmap.PixelFormat:=pf32Bit; Result:=BitDepth*4 div 8; end else GraphicExError(2{gesInvalidColorFormat},[PNG]); else GraphicExError(2{gesInvalidColorFormat},[PNG]); end; end; //---------------------------------------------------------------------------------------------------------------------- end.