//{$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} MZLib;

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 <dkelly@etsinc.com)
    COMPRESSION_IT8CTPAD = 32895;                // IT8 CT w/padding
    COMPRESSION_IT8LW = 32896;                   // IT8 Linework RLE
    COMPRESSION_IT8MP = 32897;                   // IT8 Monochrome picture
    COMPRESSION_IT8BL = 32898;                   // IT8 Binary line art
    // compression codes 32908-32911 are reserved for Pixar
    COMPRESSION_PIXARFILM = 32908;               // Pixar companded 10bit LZW
    COMPRESSION_PIXARLOG = 32909;                // Pixar companded 11bit ZIP
    COMPRESSION_DEFLATE = 32946;                 // Deflate compression (LZ77)
    // compression code 32947 is reserved for Oceana Matrix <dev@oceana.com>
    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 <dkelly@etsinc.com)
  TIFFTAG_IT8SITE = 34016;                       // site name
  TIFFTAG_IT8COLORSEQUENCE = 34017;              // color seq. [RGB,CMYK,etc]
  TIFFTAG_IT8HEADER = 34018;                     // DDES Header
  TIFFTAG_IT8RASTERPADDING = 34019;              // raster scanline padding
  TIFFTAG_IT8BITSPERRUNLENGTH = 34020;           // # of bits in short run
  TIFFTAG_IT8BITSPEREXTENDEDRUNLENGTH = 34021;   // # of bits in long run
  TIFFTAG_IT8COLORTABLE = 34022;                 // LW colortable
  TIFFTAG_IT8IMAGECOLORINDICATOR = 34023;        // BP/BL image color switch
  TIFFTAG_IT8BKGCOLORINDICATOR = 34024;          // BP/BL bg color switch
  TIFFTAG_IT8IMAGECOLORVALUE = 34025;            // BP/BL image color value
  TIFFTAG_IT8BKGCOLORVALUE = 34026;              // BP/BL bg color value
  TIFFTAG_IT8PIXELINTENSITYRANGE = 34027;        // MP pixel intensity value
  TIFFTAG_IT8TRANSPARENCYINDICATOR = 34028;      // HC transparency switch
  TIFFTAG_IT8COLORCHARACTERIZATION = 34029;      // color character. table
  // tags 34232-34236 are private tags registered to Texas Instruments
  TIFFTAG_FRAMECOUNT = 34232;                    // Sequence Frame Count
  // tag 34750 is a private tag registered to Pixel Magic
  TIFFTAG_JBIGOPTIONS = 34750;                   // JBIG options
  // tags 34908-34914 are private tags registered to SGI
  TIFFTAG_FAXRECVPARAMS = 34908;                 // encoded class 2 ses. parms
  TIFFTAG_FAXSUBADDRESS = 34909;                 // received SubAddr string
  TIFFTAG_FAXRECVTIME = 34910;                   // receive time (secs)
  // tag 65535 is an undefined tag used by Eastman Kodak
  TIFFTAG_DCSHUESHIFTVALUES = 65535;             // hue shift correction data

  // The following are 'pseudo tags' that can be used to control codec-specific functionality.
  // These tags are not written to file.  Note that these values start at $FFFF + 1 so that they'll
  // never collide with Aldus-assigned tags.

  TIFFTAG_FAXMODE = 65536;                       // Group 3/4 format control
    FAXMODE_CLASSIC = $0000;                     // default, include RTC
    FAXMODE_NORTC = $0001;                       // no RTC at end of data
    FAXMODE_NOEOL = $0002;                       // no EOL code at end of row
    FAXMODE_BYTEALIGN = $0004;                   // Byte align row
    FAXMODE_WORDALIGN = $0008;                   // Word align row
    FAXMODE_CLASSF = FAXMODE_NORTC;              // TIFF class F
  TIFFTAG_JPEGQUALITY = 65537;                   // compression quality level
  // Note: quality level is on the IJG 0-100 scale.  Default value is 75
  TIFFTAG_JPEGCOLORMODE = 65538;                 // Auto RGB<=>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 <dev@oceana.com>
  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)<Integer(Stream.Size-LastPosition));
      end;
      Stream.Position:=LastPosition;
    end;
end;

//----------------------------------------------------------------------------------------------------------------------

function TTIFFGraphic.FindTag(ATag: cardinal; var Index: cardinal): boolean;
// looks through the currently loaded IFD for the entry indicated by Tag;
// returns True and the index of the entry in Index if the entry is there
// otherwise the result is False and Index undefined
// Note: The IFD is sorted so we can use a binary search here.
var L,H,I,C: integer;
begin
  Result:=False;
  L:=0;
  H:=High(FIFD);
  while L<=H do
  begin
    I:=(L+H) shr 1;
    C:=Integer(FIFD[I].ATag)-Integer(ATag);
    if C<0 then L:=I+1 else
    begin
      H:=I-1;
      if C=0 then
      begin
        Result:=True;
        L:=I;
      end;
    end;
  end;
  Index:=L;
end;

//----------------------------------------------------------------------------------------------------------------------

const
  DataTypeToSize: array[TIFF_NOTYPE..TIFF_SLONG] of byte = (0,1,1,2,4,8,1,1,2,4);

procedure TTIFFGraphic.GetValueList(Stream: PStream; ATag: cardinal; var Values: TByteArray);
// returns the values of the IFD entry indicated by Tag
var Index,Value,Shift: cardinal;
    I: Integer;
begin
  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:
            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].ATag<FIFD[M].ATag do Inc(I);
        while FIFD[J].ATag>FIFD[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<J then QuickSort(L,J);
      L:=I;
    until I>=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.CurrentStrip<FImageProperties.StripCount do
      begin
        Stream.Position:=FBasePosition+Offsets[CurrentStrip];
        if CurrentStrip<Length(RowsPerStrip) then
          StripSize:=FImageProperties.BytesPerLine*RowsPerStrip[FImageProperties.CurrentStrip]
        else
          StripSize:=FImageProperties.BytesPerLine*RowsPerStrip[High(RowsPerStrip)];
        GetMem(Buffer,StripSize+{!reserve}100);
        Run:=Buffer;
        try
          // decompress strip if necessary
          if Assigned(Decoder) then
          begin
            GetMem(EncodedData,ByteCounts[FImageProperties.CurrentStrip]{reserve!}+100);
            try
              DataPointerCopy:=EncodedData;
              Stream.Read(EncodedData^,ByteCounts[CurrentStrip]);
              // need pointer copies here because they could get modified
              // while decoding
              Decoder.Decode(DataPointerCopy,Pointer(Run),ByteCounts[CurrentStrip],StripSize);
            finally
              if Assigned(EncodedData) then FreeMem(EncodedData);
            end;
          end
          else
          begin
            Stream.Read(Buffer^,StripSize);
          end;
          Run:=Buffer;
          // go for each line (row) in the strip
          while (FImageProperties.CurrentRow<FImageProperties.Height) and
                (Run-Buffer<Integer(StripSize)) do
          begin
            Pixels:=FBitmap.ScanLine[FImageProperties.CurrentRow];
            // depredict strip if necessary
            if Assigned(Deprediction) then Deprediction(Run,Width-1);
            // any color conversion comes last
            ColorManager.ConvertRow([Run],Pixels,FImageProperties.Width,$FF);
            Inc(PChar(Run),BytesPerLine);
            Inc(CurrentRow);
          end;
        finally
          if Assigned(Buffer) then FreeMem(Buffer);
        end;
        Inc(CurrentStrip);
      end;
    finally
      if Assigned(Decoder) then Decoder.DecodeEnd;
      Decoder.Free;
    end;
  end
  else GraphicExError(1{gesInvalidImage},[TIF]);
end;

//----------------------------------------------------------------------------------------------------------------------

const
  PhotometricToColorScheme: array[PHOTOMETRIC_MINISWHITE..PHOTOMETRIC_CIELAB] of TColorScheme = (
    csG,csG,csRGBA,csIndexed,csUnknown,csCMYK,csYCbCr,csUnknown,csCIELab);

function TTIFFGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
// Reads all relevant TIF properties of the image of index ImageIndex (zero based).
// Returns True if the image ImageIndex could be read, otherwise False.
var IFDCount: word;
    ExtraSamples,LocalBitsPerSample: TCardinalArray;
    PhotometricInterpretation: byte;
    TIFCompression: word;
    Index,IFDOffset: cardinal;
    Header: TTIFFHeader;
begin
  // clear image properties
  Result:=inherited ReadImageProperties(Stream,ImageIndex);
  with FImageProperties do
  begin
    // rewind stream to header position
    Stream.Position:=FBasePosition;
    Stream.Read(Header,sizeof(Header));
    if Header.ByteOrder=TIFF_BIGENDIAN then
    begin
      Options:=Options+[ioBigEndian];
      Header.Version:=System.Swap(Header.Version);
      Header.FirstIFD:=SwapLong(Header.FirstIFD);
    end;
    FImageProperties.Version:=Header.Version;
    FirstIFD:=Header.FirstIFD;
    if FImageProperties.Version=TIFF_VERSION then
    begin
      IFDOffset:=Header.FirstIFD;
      // advance to next IFD until we have the desired one
      repeat
        Stream.Position:=FBasePosition+IFDOffset;
        // number of entries in this directory
        Stream.Read(IFDCount,sizeof(IFDCount));
        if Header.ByteOrder=TIFF_BIGENDIAN then IFDCount:=System.Swap(IFDCount);
        // if we already have the desired image then get out of here
        if ImageIndex=0 then Break;
        Dec(ImageIndex);
        // advance to offset for next IFD
        Stream.Seek(IFDCount*sizeof(TIFDEntry),spCurrent);
        Stream.Read(IFDOffset,sizeof(IFDOffset));
        // no further image available, but the required index is still not found
        if IFDOffset=0 then Exit;
      until False;
      SetLength(FIFD,IFDCount);
      Stream.Read(FIFD[0],IFDCount*sizeof(TIFDEntry));
      if Header.ByteOrder=TIFF_BIGENDIAN then
        SwapIFD;
      SortIFD;
      Width:=GetValue(TIFFTAG_IMAGEWIDTH);
      Height:=GetValue(TIFFTAG_IMAGELENGTH);
      if (Width=0) or (Height=0) then Exit;
      // data organization
      GetValueList(Stream,TIFFTAG_ROWSPERSTRIP,RowsPerStrip);
      // some images rely on the default size ($FFFFFFFF) if only one stripe is in the image,
      // make sure there's a valid value also in this case
      if (Length(RowsPerStrip)=0) or (RowsPerStrip[0]=$FFFFFFFF) then
      begin
        SetLength(RowsPerStrip,1);
        RowsPerStrip[0]:=Height;
      end;
      // number of color components per pixel (1 for b&w, 16 and 256 colors, 3 for RGB, 4 for CMYK etc.)
      SamplesPerPixel:=GetValue(TIFFTAG_SAMPLESPERPIXEL,1);
      // number of bits per color component
      GetValueList(Stream,TIFFTAG_BITSPERSAMPLE,LocalBitsPerSample);
      if Length(LocalBitsPerSample)=0 then BitsPerSample:=1 else BitsPerSample:=LocalBitsPerSample[0];
      // determine whether image is tiled and retrive tile data if necessary
      TileWidth:=GetValue(TIFFTAG_TILEWIDTH,0);
      TileLength:=GetValue(TIFFTAG_TILELENGTH,0);
      if (TileWidth>0) 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<Rows do
          begin
            Stream.Read(YY^,Columns);
            Inc(YY,Width);
            Stream.Read(YY^,Columns);
            Inc(YY,Width);
            Stream.Read(C1^,Columns shr 1);
            Inc(C1,Width);
            Stream.Read(C2^,Columns shr 1);
            Inc(C2,Width);
            Inc(I,2);
          end;
          // Y stands here for maximum number of upsample calls
//          Y:=5;
          if ImageIndex>=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 I<DWORD(FBitmap.Height) do
              begin
                Line:=FBitmap.ScanLine[I];
                Move(TargetRun^,Line^,FBitmap.Width);
                Inc(PByte(TargetRun),FBitmap.Width);
                Inc(I,Increment);
              end;
            end;
          end;
        finally
          if Assigned(TargetBuffer) then FreeMem(TargetBuffer);
          if Assigned(RawData) then FreeMem(RawData);
        end;
      end;
    end;
  end
  else GraphicExError(1{gesInvalidImage},['GIF']);
end;

//----------------------------------------------------------------------------------------------------------------------

function TGIFGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): Boolean;
var Header: TGIFHeader;
    ScreenDescriptor: TLogicalScreenDescriptor;
    ImageDescriptor: TImageDescriptor;
    BlockID: integer;
begin
  Result:=inherited ReadImageProperties(Stream,ImageIndex);
  with FImageProperties do
  begin
    Stream.Read(Header,sizeof(Header));
    if UpperCase(Header.Signature)='GIF' then
    begin
      Version:=Str2Int(Copy(Header.Version,1,2));
      ColorScheme:=csIndexed;
      SamplesPerPixel:=1;
      // might be overwritten
      BitsPerSample:=8;
      Compression:=ctLZW;
      // general information
      Stream.Read(ScreenDescriptor,sizeof(ScreenDescriptor));
      // skip global color table if given
      if (ScreenDescriptor.PackedFields and GIF_GLOBALCOLORTABLE)<>0 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.Position<Stream.Size;
    //TotalBlockLength := 0;
    if Result then
      begin
        Stream.Read(HeaderIdentifier,sizeof(HeaderIdentifier));
        Stream.Read(BlockIdentifier,sizeof(BlockIdentifier));
        if Header.MajorVersion=3 then Stream.Read(InitialChunkLength,sizeof(InitialChunkLength));
        Stream.Read(TotalBlockLength,sizeof(TotalBlockLength));
      end;
  end;
  //---------------------------------------------------------------------------
  procedure ReadAndDecompress(Target: pointer);
  // reads a stream of data from file stream and decompresses it into Target
  var RawBuffer,Source: pointer;
      {$IFDEF NOCLASSES} Decoder: PDecoder;
      PSPRLEDecoder: PPSPRLEDecoder;
      LZ77Decoder: PLZ77Decoder;
      {$ELSE} Decoder: TDecoder; {$ENDIF}
  begin
    Decoder:=nil;
    GetMem(RawBuffer,ChannelInfo.CompressedSize);
    try
      Stream.Read(RawBuffer^,ChannelInfo.CompressedSize);
      // pointer might be advanced while decoding, so use a copy
      Source:=RawBuffer;
      case Image.Compression of
        PSP_COMP_RLE:
          begin
            {$IFDEF NOCLASSES}
            new( PSPRLEDecoder, Create ); Decoder := PSPRLEDecoder;
            {$ELSE} Decoder:=TPSPRLEDecoder.Create; {$ENDIF}
            Decoder.Decode(Source,Target,ChannelInfo.CompressedSize,ChannelInfo.UncompressedSize);
          end;
        PSP_COMP_LZ77:
          begin
            {$IFDEF NOCLASSES}
            new( LZ77Decoder, Create(Z_FINISH,False) ); Decoder := LZ77Decoder;
            {$ELSE} Decoder:=TLZ77Decoder.Create(Z_FINISH,False); {$ENDIF}
            Decoder.DecodeInit;
            Decoder.Decode(Source,Target,ChannelInfo.CompressedSize,ChannelInfo.UncompressedSize);
          end;
        PSP_COMP_JPEG: ; // here just for completeness, used only in thumbnails and composite images
      end;
      Decoder.DecodeEnd;
    finally
      if Assigned(RawBuffer) then FreeMem(RawBuffer);
      Decoder.Free;
    end;
  end;
  //---------------------------------------------------------------------------
  procedure ReadChannelData;
  // Reads the actual data of one channel from the current stream position.
  // Decompression is done by the way.
  begin
    ReadBlockHeader;
    if Header.MajorVersion>3 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<Stream.Size;
    if Result then
      begin
        Stream.Read(HeaderIdentifier,sizeof(HeaderIdentifier));
        Stream.Read(BlockIdentifier,sizeof(BlockIdentifier));
        if Header.MajorVersion=3 then Stream.Read(InitialChunkLength,sizeof(InitialChunkLength));
        Stream.Read(TotalBlockLength,sizeof(TotalBlockLength));
      end;
  end;
  //--------------- end local functions ---------------------------------------

begin
  Result:=inherited ReadImageProperties(Stream,ImageIndex);
  with FImageProperties do
  begin
    Stream.Read(Header, sizeof(Header));
    if (StrLIComp(Header.Signature,MagicID,Length(MagicID))=0) and (Header.MajorVersion>=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 Row<FBitmap.Height do
        begin
          ReadRow(RowBuffer[EvenRow],InterlaceRowBytes);
          ApplyFilter(Byte(RowBuffer[EvenRow]^),Pointer(RowBuffer[EvenRow]+1),
                      Pointer(RowBuffer[not EvenRow]+1),Pointer(RowBuffer[EvenRow]+1),
                      FSourceBPP,InterlaceRowBytes-1);
          ColorManager.ConvertRow([Pointer(RowBuffer[EvenRow]+1)],FBitmap.ScanLine[Row],FBitmap.Width,PassMask[Pass]);
          EvenRow:=not EvenRow;
          // continue with next row in interlaced order
          Inc(Row,RowIncrement[Pass]);
        end;
      end;
    end
    else
    begin
      for Row:=0 to pred(FBitmap.Height) do
      begin
        ReadRow(RowBuffer[EvenRow],BytesPerRow);
        ApplyFilter(Byte(RowBuffer[EvenRow]^),Pointer(RowBuffer[EvenRow]+1),
                    Pointer(RowBuffer[not EvenRow]+1),Pointer(RowBuffer[EvenRow]+1),
                    FSourceBPP,BytesPerRow-1);
        ColorManager.ConvertRow([Pointer(RowBuffer[EvenRow]+1)],FBitmap.ScanLine[Row],FBitmap.Width,$FF);
        EvenRow:=not EvenRow;
      end;
    end;
    // in order to improve safe failness we read all remaining but not read IDAT chunks here
    while IsChunk(IDAT) do
    begin
      ReadDataAndCheckCRC;
      FCurrentCRC:=LoadAndSwapHeader;
    end;
  finally
    if Assigned(RowBuffer[True]) then FreeMem(RowBuffer[True]);
    if Assigned(RowBuffer[False]) then FreeMem(RowBuffer[False]);
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TPNGGraphic.LoadTransparency(const Description);
// reads the data of the current transparency chunk
var Run: PWord;
    R,G,B: byte;
begin
  ReadDataAndCheckCRC;
  with TIHDRChunk(Description) do
  begin
    case ColorType of
      0: // gray
        begin
          case BitDepth of
             2: R:=MulDiv16(System.Swap(PWord(FRawBuffer)^),15,3);
            16: R:=MulDiv16(System.Swap(PWord(FRawBuffer)^),255,65535);
          else // 1,4,8 bits gray scale
            R:=Byte(System.Swap(PWord(FRawBuffer)^));
          end;
          FTransparentColor:=Windows.RGB(R,R,R);
        end;
      2:  // RGB
        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;
          FTransparentColor:=Windows.RGB(R,G,B);
        end;
      4,6:
        // formats with full alpha channel, they shouldn't have a transparent color
    else
      // Indexed color scheme (3), with at most 256 alpha values (for each palette entry).
      SetLength(FTransparency,255);
      // read the values (at most 256)...
      Move(FRawBuffer^,FTransparency[0],KOL.Max(FHeader.Length,256));
      // ...and set default values (255, fully opaque) for non-supplied values
      if FHeader.Length<256 then FillChar(FTransparency[FHeader.Length],256-FHeader.Length,$FF);
    end;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TPNGGraphic.ReadDataAndCheckCRC;
// Allocates memory in FRawBuffer and reads the next Header.Length bytes from Stream.
// Furthermore, the CRC value following the data is read as well and compared with
// the CRC value which is calculated here.
var FileCRC: cardinal;
begin
  ReallocMem(FRawBuffer,FHeader.Length);
  FStream.Read(FRawBuffer^,FHeader.Length);
  FStream.Read(FileCRC,sizeof(FileCRC));
  FileCRC:=SwapLong(FileCRC);
  // The type field of a chunk is included in the CRC, this serves as initial value
  // for the calculation here and is determined in LoadAndSwapHeader.
  FCurrentCRC:=CRC32(FCurrentCRC,FRawBuffer,FHeader.Length);
  if FCurrentCRC<>FileCRC 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.

