/// GDI+ library API access
// - adds GIF, TIF, PNG and JPG pictures read/write support as standard TGraphic
// - make available most useful GDI+ drawing methods
// - allows Antialiased rending of any EMF file using GDI+
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynGdiPlus;

{
    This file is part of Synopse framework.

    Synopse framework. Copyright (C) 2022 Arnaud Bouchez
      Synopse Informatique - https://synopse.info

  *** BEGIN LICENSE BLOCK *****
  Version: MPL 1.1/GPL 2.0/LGPL 2.1

  The contents of this file are subject to the Mozilla Public License Version
  1.1 (the "License"); you may not use this file except in compliance with
  the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL

  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the License.

  The Original Code is Synopse framework.

  The Initial Developer of the Original Code is Arnaud Bouchez.

  Portions created by the Initial Developer are Copyright (C) 2022
  the Initial Developer. All Rights Reserved.

  Contributor(s):
  - Andre Heider (dhewg)
  - Pierre le Riche
  - sllimr7139

  Alternatively, the contents of this file may be used under the terms of
  either the GNU General Public License Version 2 or later (the "GPL"), or
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  in which case the provisions of the GPL or the LGPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of either the GPL or the LGPL, and not to allow others to
  use your version of this file under the terms of the MPL, indicate your
  decision by deleting the provisions above and replace them with the notice
  and other provisions required by the GPL or the LGPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the MPL, the GPL or the LGPL.

  ***** END LICENSE BLOCK *****

}

{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER

interface

uses
  {$ifdef MSWINDOWS}
  Windows,
  CommCtrl,
  Messages,
  {$endif}
  {$ifdef FPC}
  LCLType,
  LCLIntf,
  LResources,
  SynFPCMetaFile,
  {$endif}
  Classes,
  SysUtils,
  {$ifdef ISDELPHIXE2}
  VCL.Graphics,
  {$else}
  Graphics,
  {$endif}
  ActiveX;

{.$define USEENCODERS}
{ if defined, the GDI+ encoder list will be used - seems not necessary }

{.$define USEDPI}
{ if defined, the DrawAt() method is available, which respect dpi on drawing
  - should not be useful on most applications }

{$define NOTSYNPICTUREREGISTER}
{ if NOT defined, the TSynPicture type is registered to handle PNG JPG TIF in TGraphic }


{$MINENUMSIZE 4}

type
{$ifdef DELPHI5OROLDER}
  // Delphi 5 doesn't have those base types defined :(
  PPointer = ^Pointer;
  PPChar = ^PChar;
  IntegerArray  = array[0..$effffff] of Integer;
  PIntegerArray = ^IntegerArray;
{$endif}

  /// GDI+ line drawing smoothing types
  TSmoothingMode = (
    smDefault, smHighSpeed, smHighQuality, smNone, smAntiAlias);

  /// GDI+ text rendering smoothing types
  TTextRenderingHint = (
    trhDefault, trhSingleBitPerPixelGridFit, trhSingleBitPerPixel,
    trhAntiAliasGridFit, trhAntiAlias, trhClearTypeGridFit);

  /// GDI+ available coordinates units
  TUnit = (
     uWorld, uDisplay, uPixel, uPoint, uInch, uDocument, uMillimeter, uGdi);

  /// GDI+ types of conversion from EMF to EMF+
  TEmfType = (
     etEmf0, etEmf1, etEmf2, { for Delphi 5: no etEmfOnly=3 syntax }
     etEmfOnly, etEmfPlusOnly, etEmfPlusDual);

  /// GDI+ available filling modes
  TFillMode = (
    fmAlternate, fmWinding);

  /// GDI+ lock mode for GdipFull.BitmapLockBits
  TLockModeOption = (
    lmRead, lmWrite, lmUserInputBuf);

  /// GDI+ lock mode settings for GdipFull.BitmapLockBits
  TLockModeOptions = set of TLockModeOption;

  /// GDI+ error codes
  TGdipStatus = (
    stOk,
    stGenericError,
    stInvalidParameter,
    stOutOfMemory,
    stObjectBusy,
    stInsufficientBuffer,
    stNotImplemented,
    stWin32Error,
    stWrongState,
    stAborted,
    stFileNotFound,
    stValueOverflow,
    stAccessDenied,
    stUnknownImageFormat,
    stFontFamilyNotFound,
    stFontStyleNotFound,
    stNotTrueTypeFont,
    stUnsupportedGdiplusVersion,
    stGdiplusNotInitialized,
    stPropertyNotFound,
    stPropertyNotSupported);

  PGdipRect = ^TGdipRect;

  /// GDI+ integer coordinates rectangles
  // - use width and height instead of right and bottom
  TGdipRect = record
    X, Y, Width, Height: Integer;
  end;
  PGdipRectF = ^TGdipRectF;

  /// GDI+ floating point coordinates rectangles
  // - use width and height instead of right and bottom
  TGdipRectF = record
    X, Y, Width, Height: Single;
  end;
  PGdipPointF = ^TGdipPointF;

  /// GDI+ floating point coordinates for a point
  TGdipPointF = record
    X,Y: Single;
  end;
  PGdipPointFArray = ^TGdipPointFArray;

  /// GDI+ floating point coordinates for an array of points
  TGdipPointFArray = array[0..1000] of TGdipPointF;

  /// data as retrieved by GdipFull.BitmapLockBits
  TGdipBitmapData = record
    Width: Cardinal;
    Height: Cardinal;
    Stride: Integer;
    PixelFormat: Integer;
    Scan0: Pointer;
    Reserved: Cardinal;
  end;
  PGdipBitmapData = ^TGdipBitmapData;

  TGpipImageAttributes = Pointer;

  TColorAdjustType = (
    ColorAdjustTypeDefault,
    ColorAdjustTypeBitmap,
    ColorAdjustTypeBrush,
    ColorAdjustTypePen,
    ColorAdjustTypeText,
    ColorAdjustTypeCount,
    ColorAdjustTypeAny
  );

  TColorMatrix = packed array[0..4, 0..4] of Single;
  PColorMatrix = ^TColorMatrix;

  TColorMatrixFlags = (
    ColorMatrixFlagsDefault,
    ColorMatrixFlagsSkipGrays,
    ColorMatrixFlagsAltGray
  );

  TColorChannelFlags = (
    ColorChannelFlagsC,
    ColorChannelFlagsM,
    ColorChannelFlagsY,
    ColorChannelFlagsK,
    ColorChannelFlagsLast
  );

  TColorMap = packed record
    oldColor: Cardinal;
    newColor: Cardinal;
  end;
  PColorMap = ^TColorMap;

  TWrapMode = (
    WrapModeTile,
    WrapModeTileFlipX,
    WrapModeTileFlipY,
    WrapModeTileFlipXY,
    WrapModeClamp
  );

  TColorPalette = packed record
    Flags: UINT;
    Count: UINT;
    Entries: array [0..0] of Cardinal;
  end;
  PColorPalette = ^TColorPalette;

type
  /// an object wrapper to handle gdi+ image attributes
  TImageAttributes = class
  private
    fAttr: TGpipImageAttributes;
  public
    constructor Create; overload;
    constructor Create(clone: TImageAttributes); overload;
    destructor Destroy; override;
    function SetToIdentity(adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function Reset(adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function SetColorMatrix(const colormatrix: TColorMatrix; flags: TColorMatrixFlags = ColorMatrixFlagsDefault; adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function ClearColorMatrix(adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function SetThreshold(threshold: Single; adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function ClearThreshold(adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function SetGamma(gamma: Single; adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function ClearGamma(adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function SetNoOp(adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function ClearNoOp(adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function SetColorKey(colorLow, colorHigh: Cardinal; adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function ClearColorKey(adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function SetOutputChannel(channelFlags: TColorChannelFlags; adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function ClearOutputChannel(adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function SetOutputChannelColorProfile(const colorProfileName: PWideChar; adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function ClearOutputChannelColorProfile(adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function SetRemapTable(mapSize: Cardinal; map: PColorMap; adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function ClearRemapTable(adjusttype: TColorAdjustType = ColorAdjustTypeDefault): TGdipStatus;
    function SetWrapMode(wrap: TWrapMode; color: Cardinal = $ff000000; clamp: Boolean = false): TGdipStatus;
    function GetAdjustedPalette(colorPalette: PColorPalette; colortype: TColorAdjustType): TGdipStatus;
  end;

  /// an object wrapper to load dynamically a library
  TSynLibrary = class
  protected
    fHandle: HMODULE;
    /// helper to load all needed procedure entries from a dynamic library
    // - return the HMODULE on success, i.e. if all procedure Names were found
    // - procedure definitions must be defined in inherited, and pointer-aligned,
    // i.e. the object must be bounded by {$A-} {$A+} compiler directives
    class function Load(const aDllFileName: TFileName; Addr: PPointer;
      Names: PPChar): HMODULE;
    /// unload the library
    procedure UnLoad;
  public
    /// return TRUE if the library and all procedures were found
    function Exists: boolean;
  end;

  TGDIPlusHookProc = function(out token: THandle): Integer; stdcall;
  TGDIPlusUnhookProc = procedure(token: THandle); stdcall;

{$A-} { all stdcall pointers in protected section below must be pointer-aligned }
  /// handle picture related GDI+ library calls
  TGDIPlus = class(TSynLibrary)
  protected
    Startup: function(var Token: THandle; var Input, Output): TGdipStatus; stdcall;
    Shutdown: procedure(Token: THandle); stdcall;
    DeleteGraphics: function(graphics: THandle): TGdipStatus; stdcall;
    CreateFromHDC: function(hdc: HDC; out Graphics: THandle): TGdipStatus; stdcall;
    LoadImageFromStream: function(stream: IStream; out image: THandle): TGdipStatus; stdcall;
    LoadImageFromFile: function(filename: PWideChar; out image: THandle): TGdipStatus; stdcall;
    DrawImageRect: function(graphics, image: THandle; x,y,width,height: integer): TGdipStatus; stdcall;
    DrawImageRectRect: function(graphics, image: THandle; xd,yd,wd,hd, xs,ys,ws,hs: integer;
      u: TUnit=uPixel; imageAttributes: TGpipImageAttributes=nil; callback: Pointer=nil;
      calldata: Pointer=nil): TGdipStatus; stdcall;
{$ifdef USEDPI}
    DrawImage: function(graphics, image: THandle; x,y: integer): TGdipStatus; stdcall;
{$endif}
    DisposeImage: function(image: THandle): TGdipStatus; stdcall;
    GetImageRawFormat: function(image: THandle; var format: TGUID): TGdipStatus; stdcall;
    GetImageWidth: function(image: THandle; var width: cardinal): TGdipStatus; stdcall;
    GetImageHeight: function(image: THandle; var height: cardinal): TGdipStatus; stdcall;
    SaveImageToStream: function(image: THandle; stream: IStream;
      clsidEncoder: PGUID; encoderParams: pointer): TGdipStatus; stdcall;
{$ifdef USEENCODERS}
    GetImageEncodersSize: function(out numEncoders: cardinal;
      out size: cardinal): TGdipStatus; stdcall;
    GetImageEncoders: function(numEncoders, size: cardinal;
      encoders: pointer): TGdipStatus; stdcall;
{$endif}
    CreateBitmapFromHBITMAP: function(hbm: HBITMAP; hpal: HPALETTE;
          out bitmap: THandle): TGdipStatus; stdcall;
    CreateBitmapFromGdiDib: function(bmi, bits: pointer; out bitmap: THandle): TGdipStatus; stdcall;
    BitmapSetResolution: function(bitmap: THandle; XDPI,YDPI: single): TGdipStatus; stdcall;
    GetFrameCount: function(image: THandle; dimensionID: PGUID; var count: UINT): TGdipStatus; stdcall;
    SelectActiveFrame: function(image: THandle; dimensionID: PGUID; frameIndex: UINT): TGdipStatus; stdcall;
    CreateImageAttributes: function(out imageattr: TGpipImageAttributes): TGdipStatus; stdcall;
    CloneImageAttributes: function(imageattr: TGpipImageAttributes; out cloneImageattr: TGpipImageAttributes): TGdipStatus; stdcall;
    DisposeImageAttributes: function(imageattr: TGpipImageAttributes): TGdipStatus; stdcall;
    SetImageAttributesToIdentity: function(imageattr: TGpipImageAttributes; adjusttype: TColorAdjustType): TGdipStatus; stdcall;
    ResetImageAttributes: function(imageattr: TGpipImageAttributes; adjusttype: TColorAdjustType): TGdipStatus; stdcall;
    SetImageAttributesColorMatrix: function(imageattr: TGpipImageAttributes; colortype: TColorAdjustType; enableFlag: Bool; colorMatrix: PColorMatrix; grayMatrix: PColorMatrix; flags: TColorMatrixFlags): TGdipStatus; stdcall;
    SetImageAttributesThreshold: function(imageattr: TGpipImageAttributes; colortype: TColorAdjustType; enableFlag: Bool; threshold: Single): TGdipStatus; stdcall;
    SetImageAttributesGamma: function(imageattr: TGpipImageAttributes; colortype: TColorAdjustType; enableFlag: Bool; gamma: Single): TGdipStatus; stdcall;
    SetImageAttributesNoOp: function(imageattr: TGpipImageAttributes; colortype: TColorAdjustType; enableFlag: Bool): TGdipStatus; stdcall;
    SetImageAttributesColorKeys: function(imageattr: TGpipImageAttributes; colortype: TColorAdjustType; enableFlag: Bool; colorLow, colorHigh: Cardinal): TGdipStatus; stdcall;
    SetImageAttributesOutputChannel: function(imageattr: TGpipImageAttributes; colortype: TColorAdjustType; enableFlag: Bool; channelFlags: TColorChannelFlags): TGdipStatus; stdcall;
    SetImageAttributesOutputChannelColorProfile: function(imageattr: TGpipImageAttributes; colortype: TColorAdjustType; enableFlag: Bool; const colorProfileFilename: PWideChar): TGdipStatus; stdcall;
    SetImageAttributesRemapTable: function(imageattr: TGpipImageAttributes; colortype: TColorAdjustType; enableFlag: Bool; mapSize: UINT; const map: PColorMap): TGdipStatus; stdcall;
    SetImageAttributesWrapMode: function(imageattr: TGpipImageAttributes; wrap: TWrapMode; argb: Cardinal; clamp: Bool): TGdipStatus; stdcall;
    GetImageAttributesAdjustedPalette: function(imageattr: TGpipImageAttributes; out colorPalette: PColorPalette; colortype: TColorAdjustType): TGdipStatus; stdcall;
  protected
    fToken: THandle;
    fStartupHook: record
      Hook: TGDIPlusHookProc;
      Unhook: TGDIPlusUnhookProc;
    end;
    fStartupHookToken: THandle;
    {$ifdef USEENCODERS}
    function GetEncoderClsid(format: PAnsiChar; out pClsid: TGUID): integer;
    {$endif}
  public
    /// load the GDI+ library and all needed procedures
    // - returns TRUE on success
    // - library is loaded dynamically, therefore the executable is able
    // to launch before Windows XP, but GDI + functions (e.g. GIF, PNG, TIFF
    // and JPG pictures support) won't be available in such case
    constructor Create(const aDllFileName: TFileName); reintroduce;
    // Registers the .jpg .jpeg .gif .png .tif .tiff file extensions to the program
    // - TPicture can now load such files
    // - you can just launch Gdip.RegisterPictures to initialize the GDI+ library
    procedure RegisterPictures;
    /// draw the corresponding EMF metafile into a given device context
    // - this default implementation uses GDI drawing only
    // - use TGDIPlusFull overridden method for true GDI+ AntiAliaised drawing
    procedure DrawAntiAliased(Source: TMetafile; Dest: HDC; R: TRect;
      aSmoothing: TSmoothingMode=smAntiAlias;
      aTextRendering: TTextRenderingHint=trhClearTypeGridFit); overload; virtual;
    /// draw the corresponding EMF metafile into a bitmap created by the method
    // - this default TGDIPlus implementation uses GDI drawing only
    // - use a TGDIPlusFull instance for true GDI+ AntiAliaised drawing
    // - you can specify a zoom factor by the ScaleX and ScaleY parameters in
    // percent: e.g. 100 means 100%, i.e. no scaling
    // - returned image is a DIB (device-independent bitmap)
    function DrawAntiAliased(Source: TMetafile; ScaleX: integer=100; ScaleY: integer=100;
      aSmoothing: TSmoothingMode=smAntiAlias;
      aTextRendering: TTextRenderingHint=trhClearTypeGridFit): TBitmap; overload;
    /// unload the GDI+ library
    destructor Destroy; override;
  end;
{$A+}

  /// allowed types for image saving
  TGDIPPictureType = ( gptGIF, gptPNG, gptJPG, gptBMP, gptTIF );

  /// the optional TIFF compression levels
  // - use e.g. ord(evCompressionCCITT4) to save a TIFF picture as CCITT4
  TGDIPPEncoderValue = (
    evColorTypeCMYK,
    evColorTypeYCCK,
    evCompressionLZW,
    evCompressionCCITT3,
    evCompressionCCITT4,
    evCompressionRle,
    evCompressionNone,
    evScanMethodInterlaced,
    evScanMethodNonInterlaced,
    evVersionGif87,
    evVersionGif89,
    evRenderProgressive,
    evRenderNonProgressive,
    evTransformRotate90,
    evTransformRotate180,
    evTransformRotate270,
    evTransformFlipHorizontal,
    evTransformFlipVertical,
    evMultiFrame,
    evLastFrame,
    evFlush,
    evFrameDimensionTime,
    evFrameDimensionResolution,
    evFrameDimensionPage);

  /// GIF, PNG, TIFF and JPG pictures support using GDI+ library
  // - cf @http://msdn.microsoft.com/en-us/library/ms536393
  // for available image formats
  TSynPicture = class(TGraphic)
  protected
    fHasContent: boolean;
    fHeight,
    fWidth: cardinal;
    fImage: THandle;
    fStream: IStream;
    fGlobal: THandle;
    fGlobalLen: integer;
    fAssignedFromBitmap: boolean;
    function GetEmpty: Boolean; override;
    function GetHeight: Integer; override;
    function GetWidth: Integer; override;
    procedure SetHeight(Value: Integer); override;
    procedure SetWidth(Value: Integer); override;
    {$ifndef FPC}
    procedure Clear;
    {$endif}
    procedure fImageSet;
    procedure BitmapSetResolution(DPI: single);
    {$ifdef FPC}
    function GetTransparent: Boolean; override;
    procedure SetTransparent(Value: Boolean); override;
    {$endif}
    function SaveAsIStream(out Stream: IStream; Format: TGDIPPictureType;
      CompressionQuality: integer=80; IfBitmapSetResolution: single=0): TGdipStatus;
  public
    constructor CreateFromFile(const FileName: string);
    constructor CreateFromBuffer(Buffer: pointer; Len: integer);
    destructor Destroy; override;
    {$ifdef FPC}
    procedure Clear; override;
    {$endif}
    procedure Assign(Source: TPersistent); override;
    procedure Draw(ACanvas: TCanvas; const Rect: TRect); overload; override;
    procedure Draw(ACanvas: TCanvas; const dst, src: TRect; attributes: TImageAttributes=nil; u: TUnit=uPixel); reintroduce; overload;
{$ifdef USEDPI}
    /// since method use dpi -> can drop content if drawing with different dpi
    procedure DrawAt(ACanvas: TCanvas; X,Y: integer);
{$endif}
    function LoadFromIStream(Stream: IStream): TGdipStatus;
    procedure LoadFromStream(Stream: TStream); override;
    procedure LoadFromFile(const FileName: string); override;
    procedure LoadFromBuffer(Buffer: pointer; Len: integer);
    procedure SaveToStream(Stream: TStream); override;
    procedure SaveInternalToStream(Stream: TStream);
    procedure LoadFromResourceName(Instance: THandle; const ResName: string);{$ifdef FPC} override;{$endif}
    {$ifdef FPC}
    procedure LoadFromClipboardFormat(FormatID: TClipboardFormat); override;
    procedure SaveToClipboardFormat(FormatID: TClipboardFormat); override;
    {$else}
    procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle;
      APalette: HPALETTE); override;
    procedure SaveToClipboardFormat(var AFormat: Word; var AData: THandle;
      var APalette: HPALETTE); override;
    {$endif}
    /// save the picture into any GIF/PNG/JPG/TIFF format
    // - CompressionQuality is used for gptJPG format saving
    // and is expected to be from 0 to 100; for gptTIF format, use
    // ord(TGDIPPEncoderValue) to define the parameter; by default, will use
    // ord(evCompressionLZW) to save the TIFF picture with LZW - for gptTIF,
    // only valid values are ord(evCompressionLZW), ord(evCompressionCCITT3),
    // ord(evCompressionCCITT4), ord(evCompressionRle) and ord(evCompressionNone)
    function SaveAs(Stream: TStream; Format: TGDIPPictureType;
      CompressionQuality: integer=80; IfBitmapSetResolution: single=0): TGdipStatus;
    /// create a bitmap from the corresponding picture
    // - kind of returned image is DIB (device-independent bitmap)
    function ToBitmap: TBitmap;
    /// guess the picture type from its internal format
    // - return gptBMP if no format is found
    function GetImageFormat: TGDIPPictureType;
    /// return TRUE if the supplied filename is a picture handled by
    // TSynPicture
    class function IsPicture(const FileName: TFileName): TGraphicClass;
    /// calculate a TRect which fit the specified maximum pixel number
    // - if any side of the picture is bigger than the specified pixel number,
    // the TRect is sized down in order than the biggest size if this value
    function RectNotBiggerThan(MaxPixelsForBiggestSide: Integer): TRect;
    /// return the GDI+ native image handle
    property NativeImage: THandle read fImage;
  end;

  /// sub class to handle .PNG file extension
  TPngImage = class(TSynPicture)
  end;

  /// sub class to handle .JPG file extension
  TJpegImage = class(TSynPicture)
  protected
    fCompressionQuality: integer;
  public
    constructor Create; override;
    /// implements the saving feature
    procedure SaveToStream(Stream: TStream); override;
    /// the associated encoding quality (from 0 to 100)
    // - set to 80 by default
    property CompressionQuality: integer read fCompressionQuality write fCompressionQuality;
  end;

  /// sub class to handle .GIF file extension
  TGifImage = class(TSynPicture)
  end;

  /// sub class to handle .TIF file extension
  // - GDI+ seems not able to load all Tiff file formats, depending on the
  // Windows version and third-party libraries installed
  // - this overridden class implements multiple pages
  TTiffImage = class(TSynPicture)
  protected
    fActivePage: integer;
    procedure SelectPage(index: integer);
  public
    /// extract a page from the TIFF and assign it to a bitmap
    procedure ExtractPage(index: integer; wBMP: TBitmap);
    /// retrieve the number of pages in the TIFF file
    function GetPageCount:integer;
    /// multi-page
    // - default Frame/Page Index is 0
    property ActivePageIndex: integer read fActivePage write SelectPage;
  end;

  // Region combine mode (used by SetClipRegion, etc.)
  TGDIPCombineMode = (cmReplace, cmIntersect, cmUnion, cmXor, cmExclude, cmComplement);

{$A-}
  /// handle most GDI+ library calls
  // - an instance of this object is initialized by this unit: you don't have
  // to create a new instance
  TGDIPlusFull = class(TGDIPlus)
  protected
    DrawLine: function(graphics, pen: THandle; x1,y1,x2,y2: integer): TGdipStatus; stdcall;
    CreatePen: function(color: Cardinal; width: Single; units: TUnit; out pen: THandle): TGdipStatus; stdcall;
    DeletePen: function(pen: THandle): TGdipStatus; stdcall;
    Flush: function(graphics: THandle; intention: Integer=0): TGdipStatus; stdcall;
    SetSmoothingMode: function(graphics: THandle; mode: TSmoothingMode): TGdipStatus; stdcall;
    SetTextRenderingHint: function(graphics: THandle; mode: TTextRenderingHint): TGdipStatus; stdcall;
    SetPenBrushFill: function(Pen, Brush: THandle): TGdipStatus; stdcall;
    SetPenColor: function(Pen: THandle; Color: Cardinal): TGdipStatus; stdcall;
    SetPenWidth: function(Pen: THandle; Width: Single): TGdipStatus; stdcall;
    DeleteBrush: function(brush: THandle): TGdipStatus; stdcall;
    CreateSolidFill: function(color: Cardinal; var brush: THandle): TGdipStatus; stdcall;
    FillRectangle: function(graphics, brush: THandle; x, y, width, height: Integer): TGdipStatus; stdcall;
    FillEllipse: function(graphics, brush: THandle; x, y, width, height: Integer): TGdipStatus; stdcall;
    DrawEllipse: function(graphics, pen: THandle; x, y, width, height: Integer): TGdipStatus; stdcall;
    DrawCurve: function(graphics, pen: THandle; Points: Pointer; Count: Integer): TGdipStatus; stdcall;
    GraphicsClear: function(Graphics: THandle; Color: Cardinal): TGdipStatus; stdcall;
    SetPageUnit: function(Graphics: THandle; units: TUnit): TGdipStatus; stdcall;
    DrawRectangle: function(Graphics, Pen: THandle; X, Y, Width, Height: Integer): TGdipStatus; stdcall;
    SetPenDashStyle: function(Pen: THandle; dashStyle: Integer): TGdipStatus; stdcall;
    DrawPolygon: function(graphics, pen: THandle; points: pointer; count: integer): TGdipStatus; stdcall;
    FillPolygon: function(graphics, brush: THandle; points: pointer; count: Integer; fillMode: TFillMode): TGdipStatus; stdcall;
    SetWorldTransform: function(graphics, matrix: THandle): TGdipStatus; stdcall;
    GetWorldTransform: function(graphics, matrix: THandle): TGdipStatus; stdcall;
    CreateMatrix: function(out matrix: THandle): TGdipStatus; stdcall;
    CreateMatrix2: function(m11,m12,m21,m22,dx,dy: Single; out matrix: THandle): TGdipStatus; stdcall;
    DeleteMatrix: function(matrix: THandle): TGdipStatus; stdcall;
    SetMatrixElements: function(matrix: THandle; m11,m12,m21,m22,dx,dy: Single): TGdipStatus; stdcall;
    MultiplyMatrix: function(matrix, matrix2: THandle; order: Integer=0): TGdipStatus; stdcall;
    ScaleMatrix: function(matrix: THandle; scaleX, scaleY: Single; order: integer=0): TGdipStatus; stdcall;
    TranslateMatrix: function(matrix: THandle; offsetX, offsetY: Single; order: integer=0): TGdipStatus; stdcall;
    DrawLines: function(Graphics, Pen: THandle; Points: Pointer; Count: Integer): TGdipStatus; stdcall;
    RecordMetafile: function (DC: HDC; emfType: TEmfType; frameRect: PGdipRect;
      frameUnit: TUnit; description: PWideChar; var out_metafile: THandle): TGdipStatus; stdcall;
    RecordMetafileStream: function (strm: IStream; DC: HDC; emfType: TEmfType; const frameRect: TGdipRect;
      frameUnit: TUnit; description: PWideChar; var out_metafile: THandle): TGdipStatus; stdcall;
    PlayRecord: function(metafile: THandle; RecType, flags, RecSize: cardinal; Rec: Pointer): TGdipStatus; stdcall;
    EnumerateMetaFile: function(graphics, metafile: THandle; Dest: PGdipRect;
      callback, data: pointer; imageAttributes: TGpipImageAttributes=nil): TGdipStatus; stdcall;
    ResetWorldTransform: function(graphics: THandle): TGdipStatus; stdcall;
    RotateTransform: function(graphics: THandle; angle: Single; order: Integer=0): TGdipStatus; stdcall;
    TranslateTransform: function(graphics: THandle; dx,dy: Single; order: integer=0): TGdipStatus; stdcall;
    CreateFromImage: function(image: THandle; out graphics: THandle): TGdipStatus; stdcall;
    CreateFontFrom: function(aHDC: HDC; out font: THandle): TGdipStatus; stdcall;
    DeleteFont: function(font: THandle): TGdipStatus; stdcall;
    CreateFontFromLogfont: function(hdc: HDC; logfont: PLOGFONTW; out font: THandle): TGdipStatus; stdcall;
    DrawString: function(graphics: THandle; text: PWideChar; length: integer; font: THandle;
      Dest: PGdipRectF; format, brush: THandle): TGdipStatus; stdcall;
    MeasureString: function(graphics: THandle; text: PWideChar; length: integer; font: THandle;
      Dest: PGdipRectF; format: THandle; bound: PGdipRectF;
      codepointsFitted, linesFilled: PInteger): TGdipStatus; stdcall;
    DrawDriverString: function(graphics: THandle; text: PWideChar;
      length: integer; font, brush: THandle; positions: PGdipPointFArray; flag: integer; matrix: THandle): TGdipStatus; stdcall;
    CreatePath: function(brushmode: TFillMode; var path: THandle): TGdipStatus; stdcall;
    DeletePath: function(path: THandle): TGdipStatus; stdcall;
    DrawPath: function(graphics, pen, path: THandle): TGdipStatus; stdcall;
    FillPath: function(graphics, brush, path: THandle): TGdipStatus; stdcall;
    AddPathLine: function(path: THandle; X1,Y1,X2,Y2: integer): TGdipStatus; stdcall;
    AddPathLines: function(path: THandle; Points: pointer; Count: integer): TGdipStatus; stdcall;
    AddPathArc: function(path: THandle; X,Y,width,height: Integer; StartAndle, SweepAngle: single): TGdipStatus; stdcall;
    AddPathCurve: function(path: THandle; Points: pointer; Count: integer): TGdipStatus; stdcall;
    AddPathClosedCurve: function(): TGdipStatus; stdcall;
    AddPathEllipse: function(path: THandle; X,Y,width,height: Integer): TGdipStatus; stdcall;
    AddPathPolygon: function(path: THandle; Points: pointer; Count: integer): TGdipStatus; stdcall;
    AddPathRectangle: function(path: THandle; X,Y,width,height: Integer): TGdipStatus; stdcall;
    ClosePath: function(path: THandle): TGdipStatus; stdcall;
    DrawArc: function(graphics, pen: THandle; X,Y,width,height: Integer; StartAndle, SweepAngle: single): TGdipStatus; stdcall;
    DrawBezier: function(graphics, pen: THandle; X1,Y1,X2,Y2,X3,Y3,X4,Y4: Integer): TGdipStatus; stdcall;
    DrawPie: function(graphics, pen: THandle; X,Y,width,height: Integer; StartAndle, SweepAngle: single): TGdipStatus; stdcall;
    CreateBitmapFromScan0: function(width, height, stride, format: integer; scan0: PByte;
      out bitmap: THandle): TGdipStatus; stdcall;
    BitmapLockBits: function(Bitmap: THandle; const Rect: PGdipRect;
      Flags: TLockModeOptions; Format: integer; out LockedBitmapData: TGdipBitmapData): TGdipStatus; stdcall;
    BitmapUnlockBits: function(Bitmap: THandle; const LockedBitmapData: TGdipBitmapData): TGdipStatus; stdcall;
    GetClip: function (graphics: THandle; Region: THandle): TGdipStatus; stdcall;
    SetClipRegion: function(graphics: THandle; region: THandle; CombineMode: TGDIPCombineMode): TGdipStatus; stdcall;
    SetClipRectI: function(graphics: THandle; X,Y,width,height: Integer; CombineMode: TGDIPCombineMode): TGdipStatus; stdcall;
    ResetClip: function(graphics: THandle): TGdipStatus; stdcall;
    CreateRegion: function(out Region: THandle): TGdipStatus; stdcall;
    DeleteRegion: function(Region: THandle): TGdipStatus; stdcall;
  protected
    /// this function is available only with GDI+ version 1.1
    fConvertToEmfPlus: function(graphics, image: THandle; var flag: BOOL;
      emftype: TEmfType; description: PWideChar; var out_metafile: integer): TGdipStatus; stdcall;
    fConvertToEmfPlusTested: Boolean;
    fForceInternalConvertToEmfPlus: boolean;
    fUseDrawString: boolean;
    function getNativeConvertToEmfPlus: boolean;
  public
    /// load the GDI+ library and all needed procedures
    // - returns TRUE on success
    // - library is loaded dynamically, therefore the executable is able
    // to launch before Windows XP, but GDI + functions (e.g. GIF, PNG, TIFF
    // and JPG pictures support or AntiAliased drawing) won't be available
    // - if no GdiPlus.dll file name is available, it will search the system
    // for the most recent version of GDI+ (either GDIPLUS.DLL in the current
    // directory, either the Office 2003 version, either the OS version - 1.1 is
    // available only since Vista and Seven; XP only shipped with version 1.1)
    constructor Create(aDllFileName: TFileName='');
    /// draw the corresponding EMF metafile into a given device context
    // - this overridden implementation handles GDI+ AntiAliased drawing
    // - if GDI+ is not available, it will use default GDI32 function
    procedure DrawAntiAliased(Source: TMetafile; Dest: HDC; R: TRect;
      aSmoothing: TSmoothingMode=smAntiAlias;
      aTextRendering: TTextRenderingHint=trhClearTypeGridFit); overload; override;
    procedure DrawAntiAliased(Source: TMetafile; Dest: HDC;
      const dst, src: TRect; attributes: TImageAttributes=nil; u: TUnit=uPixel;
      aSmoothing: TSmoothingMode=smAntiAlias; aTextRendering: TTextRenderingHint=trhClearTypeGridFit); overload;
    /// convert a supplied EMF metafile into a EMF+ (i.e. GDI+ metafile)
    // - i.e. allows antialiased drawing of the EMF metafile
    // - if GDI+ is not available or conversion failed, return 0
    // - return a metafile handle, to be released after use (e.g. with
    // DrawImageRect) by DisposeImage()
    function ConvertToEmfPlus(Source: TMetafile; Dest: HDC;
      aSmoothing: TSmoothingMode=smAntiAlias;
      aTextRendering: TTextRenderingHint=trhClearTypeGridFit): THandle;
    /// internal method used for GDI32 metafile loading
    function MetaFileToStream(Source: TMetafile): IStream;
    /// return true if DrawAntiAliased() method will use native GDI+ conversion,
    // i.e. if GDI+ installed version is 1.1
    property NativeConvertToEmfPlus: boolean read getNativeConvertToEmfPlus;
    /// can be set to true if to force DrawAntiAliased() method NOT to use
    //  native GDI+ 1.1 conversion, even if available
    // - we found out that GDI+ 1.1 was not as good as our internal conversion
    // function written in Delphi, e.g. for underlined fonts or font fallback
    // - programs can set this property to true to avoid using GDI+ 1.1
    property ForceInternalConvertToEmfPlus: boolean
      read fForceInternalConvertToEmfPlus write fForceInternalConvertToEmfPlus;
    /// if TRUE, text will be rendered using DrawString and not DrawDriverString
    // if the content has some chars non within 0000-05ff Unicode BMP range
    // - less accurate for individual character positioning (e.g. justification),
    // but will handle UniScribe positioning and associated font fallback
    // - is disable by default, and enabled only for chars >= $600
    // - note that the GdipConvertToEmfPlus() GDI+ 1.1 method does not handle
    // font fallback, so our internal conversion is more accurate thanks to
    // this parameter
    property ForceUseDrawString: boolean read fUseDrawString write fUseDrawString;
  end;
{$A+}

const
  /// the corresponding file extension for every saving format type
  GDIPPictureExt: array [TGDIPPictureType] of TFileName =
    ('.gif','.png','.jpg','.bmp','.tif');

/// retrieve a ready to be displayed name of the supplied Graphic Class
function PictureName(Pic: TGraphicClass): string;

/// helper to save a specified graphic into GIF/PNG/JPG/TIFF format
// - CompressionQuality is only used for gptJPG format saving
// and is expected to be from 0 to 100
// - if MaxPixelsForBiggestSide is set to something else than 0, the resulting
// picture biggest side won't exceed this pixel number
// - this method is thread-safe (using GdipLock/GdipUnlock globals)
procedure SaveAs(Graphic: TPersistent; Stream: TStream;
  Format: TGDIPPictureType; CompressionQuality: integer=80;
  MaxPixelsForBiggestSide: cardinal=0; BitmapSetResolution: single=0); overload;

/// helper to save a specified graphic into GIF/PNG/JPG/TIFF format
// - CompressionQuality is only used for gptJPG format saving
// and is expected to be from 0 to 100
// - if MaxPixelsForBiggestSide is set to something else than 0, the resulting
// picture biggest side won't exceed this pixel number
// - this method is thread-safe (using GdipLock/GdipUnlock globals)
procedure SaveAs(Graphic: TPersistent; const FileName: TFileName;
  Format: TGDIPPictureType; CompressionQuality: integer=80;
  MaxPixelsForBiggestSide: cardinal=0; BitmapSetResolution: single=0); overload;

/// helper to save a specified graphic into GIF/PNG/JPG/TIFF format
// - CompressionQuality is only used for gptJPG format saving
// and is expected to be from 0 to 100
// - if MaxPixelsForBiggestSide is set to something else than 0, the resulting
// picture biggest side won't exceed this pixel number
// - this method is thread-safe (using GdipLock/GdipUnlock globals)
procedure SaveAsRawByteString(Graphic: TPersistent;
  out DataRawByteString{$ifdef HASCODEPAGE}: RawByteString{$endif};
  Format: TGDIPPictureType; CompressionQuality: integer=80;
  MaxPixelsForBiggestSide: cardinal=0; BitmapSetResolution: single=0);

/// helper to save a specified TBitmap into GIF/PNG/JPG/TIFF format
// - CompressionQuality is only used for gptJPG format saving
// and is expected to be from 0 to 100
// - if MaxPixelsForBiggestSide is set to something else than 0, the resulting
// picture biggest side won't exceed this pixel number
// - this method is thread-safe (using GdipLock/GdipUnlock globals)
function BitmapToRawByteString(Bitmap: TBitmap;
  out DataRawByteString{$ifdef HASCODEPAGE}: RawByteString{$endif};
  Format: TGDIPPictureType; CompressionQuality: integer=80;
  MaxPixelsForBiggestSide: cardinal=0; BitmapSetResolution: single=0): TGdipStatus;

/// helper to load a specified graphic from GIF/PNG/JPG/TIFF format content
// - this method is thread-safe (using GdipLock/GdipUnlock globals)
function LoadFromRawByteString(const Picture: {$ifdef HASCODEPAGE}RawByteString{$else}AnsiString{$endif}): TBitmap;

/// helper function to create a bitmap from any GIF/PNG/JPG/TIFF/EMF/WMF file
// - if file extension if .EMF, the file is drawn with a special antialiased
// GDI+ drawing method (if the global Gdip var is a TGDIPlusFull instance)
// - this method is thread-safe (using GdipLock/GdipUnlock globals)
function LoadFrom(const FileName: TFileName): TBitmap; overload;

/// helper function to create a bitmap from any EMF content
// - the file is drawn with a special antialiased
// GDI+ drawing method (if the global Gdip var is a TGDIPlusFull instance)
// - this method is thread-safe (using GdipLock/GdipUnlock globals)
function LoadFrom(const MetaFile: TMetaFile): TBitmap; overload;

/// recompress a JPEG binary in-place
// - no sizing is done, but a bitmap is created from the supplied JPEG, and
// re-compressed as JPEG using the specified quality
// - may be used to ensure a JPEG binary is a JPEG is a JPEG
// - this method is thread-safe (using GdipLock/GdipUnlock globals)
function JpegRecompress(const jpeg: {$ifdef HASCODEPAGE}RawByteString{$else}AnsiString{$endif};
  quality: integer=80): {$ifdef HASCODEPAGE}RawByteString{$else}AnsiString{$endif};

/// draw the specified GDI TMetaFile (emf) using the GDI-plus antialiaised engine
// - by default, no font fall-back is implemented (for characters not included
// within the font glyphs), but you may force it via the corresponding parameter
// (used to set the TGDIPlusFull.ForceUseDrawString property)
// - this method is thread-safe (using GdipLock/GdipUnlock globals)
procedure DrawEmfGdip(aHDC: HDC; Source: TMetaFile; var R: TRect;
  ForceInternalAntiAliased: boolean; ForceInternalAntiAliasedFontFallBack: boolean=false);

var
  /// GDI+ library instance
  // - only initialized at program startup if the NOTSYNPICTUREREGISTER is NOT
  // defined (which is not the default)
  // - Gdip.Exists return FALSE if the GDI+ library is not available in this
  // operating system (e.g. on Windows 2000) nor the current executable folder
  // - you can run ExpectGDIPlusFull to ensure you use GDI+ 1.1
  Gdip: TGDIPlus = nil;

/// will set global Gdip instance from a TGDIPlusFull, if available
// - this GDI+ 1.1 version (i.e. gdiplus11.dll) allows proper TMetaFile
// antialiasing, as requested by LoadFrom and DrawEmfGdip functions
procedure ExpectGDIPlusFull(ForceInternalAntiAliased: boolean=true;
  ForceInternalAntiAliasedFontFallBack: boolean=true);

/// test function
procedure GdipTest(const JpegFile: TFileName);

/// enter global critical section for safe use of SynGdiPlus from multiple threads
procedure GdipLock;

/// leave global critical section for safe use of SynGdiPlus from multiple threads
procedure GdipUnlock;

var
  /// mutex used by GdipLock/GdipUnlock
  GdipCS: TRTLCriticalSection;


implementation

{
// Common GDI+ color constants
const
  aclAliceBlue            = $FFF0F8FF;
  aclAntiqueWhite         = $FFFAEBD7;
  aclAqua                 = $FF00FFFF;
  aclAquamarine           = $FF7FFFD4;
  aclAzure                = $FFF0FFFF;
  aclBeige                = $FFF5F5DC;
  aclBisque               = $FFFFE4C4;
  aclBlack                = $FF000000;
  aclBlanchedAlmond       = $FFFFEBCD;
  aclBlue                 = $FF0000FF;
  aclBlueViolet           = $FF8A2BE2;
  aclBrown                = $FFA52A2A;
  aclBurlyWood            = $FFDEB887;
  aclCadetBlue            = $FF5F9EA0;
  aclChartreuse           = $FF7FFF00;
  aclChocolate            = $FFD2691E;
  aclCoral                = $FFFF7F50;
  aclCornflowerBlue       = $FF6495ED;
  aclCornsilk             = $FFFFF8DC;
  aclCrimson              = $FFDC143C;
  aclCyan                 = $FF00FFFF;
  aclDarkBlue             = $FF00008B;
  aclDarkCyan             = $FF008B8B;
  aclDarkGoldenrod        = $FFB8860B;
  aclDarkGray             = $FFA9A9A9;
  aclDarkGreen            = $FF006400;
  aclDarkKhaki            = $FFBDB76B;
  aclDarkMagenta          = $FF8B008B;
  aclDarkOliveGreen       = $FF556B2F;
  aclDarkOrange           = $FFFF8C00;
  aclDarkOrchid           = $FF9932CC;
  aclDarkRed              = $FF8B0000;
  aclDarkSalmon           = $FFE9967A;
  aclDarkSeaGreen         = $FF8FBC8B;
  aclDarkSlateBlue        = $FF483D8B;
  aclDarkSlateGray        = $FF2F4F4F;
  aclDarkTurquoise        = $FF00CED1;
  aclDarkViolet           = $FF9400D3;
  aclDeepPink             = $FFFF1493;
  aclDeepSkyBlue          = $FF00BFFF;
  aclDimGray              = $FF696969;
  aclDodgerBlue           = $FF1E90FF;
  aclFirebrick            = $FFB22222;
  aclFloralWhite          = $FFFFFAF0;
  aclForestGreen          = $FF228B22;
  aclFuchsia              = $FFFF00FF;
  aclGainsboro            = $FFDCDCDC;
  aclGhostWhite           = $FFF8F8FF;
  aclGold                 = $FFFFD700;
  aclGoldenrod            = $FFDAA520;
  aclGray                 = $FF808080;
  aclGreen                = $FF008000;
  aclGreenYellow          = $FFADFF2F;
  aclHoneydew             = $FFF0FFF0;
  aclHotPink              = $FFFF69B4;
  aclIndianRed            = $FFCD5C5C;
  aclIndigo               = $FF4B0082;
  aclIvory                = $FFFFFFF0;
  aclKhaki                = $FFF0E68C;
  aclLavender             = $FFE6E6FA;
  aclLavenderBlush        = $FFFFF0F5;
  aclLawnGreen            = $FF7CFC00;
  aclLemonChiffon         = $FFFFFACD;
  aclLightBlue            = $FFADD8E6;
  aclLightCoral           = $FFF08080;
  aclLightCyan            = $FFE0FFFF;
  aclLightGoldenrodYellow = $FFFAFAD2;
  aclLightGray            = $FFD3D3D3;
  aclLightGreen           = $FF90EE90;
  aclLightPink            = $FFFFB6C1;
  aclLightSalmon          = $FFFFA07A;
  aclLightSeaGreen        = $FF20B2AA;
  aclLightSkyBlue         = $FF87CEFA;
  aclLightSlateGray       = $FF778899;
  aclLightSteelBlue       = $FFB0C4DE;
  aclLightYellow          = $FFFFFFE0;
  aclLime                 = $FF00FF00;
  aclLimeGreen            = $FF32CD32;
  aclLinen                = $FFFAF0E6;
  aclMagenta              = $FFFF00FF;
  aclMaroon               = $FF800000;
  aclMediumAquamarine     = $FF66CDAA;
  aclMediumBlue           = $FF0000CD;
  aclMediumOrchid         = $FFBA55D3;
  aclMediumPurple         = $FF9370DB;
  aclMediumSeaGreen       = $FF3CB371;
  aclMediumSlateBlue      = $FF7B68EE;
  aclMediumSpringGreen    = $FF00FA9A;
  aclMediumTurquoise      = $FF48D1CC;
  aclMediumVioletRed      = $FFC71585;
  aclMidnightBlue         = $FF191970;
  aclMintCream            = $FFF5FFFA;
  aclMistyRose            = $FFFFE4E1;
  aclMoccasin             = $FFFFE4B5;
  aclNavajoWhite          = $FFFFDEAD;
  aclNavy                 = $FF000080;
  aclOldLace              = $FFFDF5E6;
  aclOlive                = $FF808000;
  aclOliveDrab            = $FF6B8E23;
  aclOrange               = $FFFFA500;
  aclOrangeRed            = $FFFF4500;
  aclOrchid               = $FFDA70D6;
  aclPaleGoldenrod        = $FFEEE8AA;
  aclPaleGreen            = $FF98FB98;
  aclPaleTurquoise        = $FFAFEEEE;
  aclPaleVioletRed        = $FFDB7093;
  aclPapayaWhip           = $FFFFEFD5;
  aclPeachPuff            = $FFFFDAB9;
  aclPeru                 = $FFCD853F;
  aclPink                 = $FFFFC0CB;
  aclPlum                 = $FFDDA0DD;
  aclPowderBlue           = $FFB0E0E6;
  aclPurple               = $FF800080;
  aclRed                  = $FFFF0000;
  aclRosyBrown            = $FFBC8F8F;
  aclRoyalBlue            = $FF4169E1;
  aclSaddleBrown          = $FF8B4513;
  aclSalmon               = $FFFA8072;
  aclSandyBrown           = $FFF4A460;
  aclSeaGreen             = $FF2E8B57;
  aclSeaShell             = $FFFFF5EE;
  aclSienna               = $FFA0522D;
  aclSilver               = $FFC0C0C0;
  aclSkyBlue              = $FF87CEEB;
  aclSlateBlue            = $FF6A5ACD;
  aclSlateGray            = $FF708090;
  aclSnow                 = $FFFFFAFA;
  aclSpringGreen          = $FF00FF7F;
  aclSteelBlue            = $FF4682B4;
  aclTan                  = $FFD2B48C;
  aclTeal                 = $FF008080;
  aclThistle              = $FFD8BFD8;
  aclTomato               = $FFFF6347;
  aclTransparent          = $00FFFFFF;
  aclTurquoise            = $FF40E0D0;
  aclViolet               = $FFEE82EE;
  aclWheat                = $FFF5DEB3;
  aclWhite                = $FFFFFFFF;
  aclWhiteSmoke           = $FFF5F5F5;
  aclYellow               = $FFFFFF00;
  aclYellowGreen          = $FF9ACD32;
}

{ TImageAttributes }

constructor TImageAttributes.Create;
begin
  inherited Create;
  Gdip.CreateImageAttributes(fAttr);
end;

constructor TImageAttributes.Create(clone: TImageAttributes);
begin
  inherited Create;
  Gdip.CloneImageAttributes(clone.fAttr, fAttr)
end;

destructor TImageAttributes.Destroy;
begin
  Gdip.DisposeImageAttributes(fAttr);
  inherited;
end;

function TImageAttributes.SetToIdentity(adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesToIdentity(fAttr, adjusttype);
end;

function TImageAttributes.Reset(adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.ResetImageAttributes(fAttr, adjusttype);
end;

function TImageAttributes.SetColorMatrix(const colormatrix: TColorMatrix; flags: TColorMatrixFlags; adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesColorMatrix(fAttr, adjusttype, true, @colormatrix, nil, flags);
end;

function TImageAttributes.ClearColorMatrix(adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesColorMatrix(fAttr, adjusttype, false, nil, nil, ColorMatrixFlagsDefault);
end;

function TImageAttributes.SetThreshold(threshold: Single; adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesThreshold(fAttr, adjusttype, true, threshold);
end;

function TImageAttributes.ClearThreshold(adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesThreshold(fAttr, adjusttype, false, 0.0);
end;

function TImageAttributes.SetGamma(gamma: Single; adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesGamma(fAttr, adjusttype, true, gamma);
end;

function TImageAttributes.ClearGamma(adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesGamma(fAttr, adjusttype, false, 0.0);
end;

function TImageAttributes.SetNoOp(adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesNoOp(fAttr, adjusttype, true);
end;

function TImageAttributes.ClearNoOp(adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesNoOp(fAttr, adjusttype, false);
end;

function TImageAttributes.SetColorKey(colorLow, colorHigh: Cardinal; adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesColorKeys(fAttr, adjusttype, true, colorLow, colorHigh);
end;

function TImageAttributes.ClearColorKey(adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesColorKeys(fAttr, adjusttype, false, 0, 0);
end;

function TImageAttributes.SetOutputChannel(channelFlags: TColorChannelFlags; adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesOutputChannel(fAttr, adjusttype, true, channelFlags);
end;

function TImageAttributes.ClearOutputChannel(adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesOutputChannel(fAttr, adjusttype, false, ColorChannelFlagsLast);
end;

function TImageAttributes.SetOutputChannelColorProfile(const colorProfileName: PWideChar; adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesOutputChannelColorProfile(fAttr, adjusttype, true, colorProfileName);
end;

function TImageAttributes.ClearOutputChannelColorProfile(adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesOutputChannelColorProfile(fAttr, adjusttype, false, nil);
end;

function TImageAttributes.SetRemapTable(mapSize: Cardinal; map: PColorMap; adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesRemapTable(fAttr, adjusttype, true, mapSize, map);
end;

function TImageAttributes.ClearRemapTable(adjusttype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.SetImageAttributesRemapTable(fAttr, adjusttype, false, 0, nil);
end;

function TImageAttributes.SetWrapMode(wrap: TWrapMode; color: Cardinal; clamp: Boolean): TGdipStatus;
begin
  result := Gdip.SetImageAttributesWrapMode(fAttr, wrap, color, clamp);
end;

function TImageAttributes.GetAdjustedPalette(colorPalette: PColorPalette; colortype: TColorAdjustType): TGdipStatus;
begin
  result := Gdip.GetImageAttributesAdjustedPalette(fAttr, colorPalette, colortype);
end;


{ TSynLibrary }

function TSynLibrary.Exists: boolean;
begin
  result := (self<>nil) and (fHandle<>0);
end;

function ProcLoad(H: HMODULE; Addr: PPointer; Names: PPChar): boolean;
begin
  result := false;
  if Names<>nil then
    repeat
      Addr^ := GetProcAddress(H,Names^);
      if Addr^=nil then
        exit;
      inc(Addr);
      inc(Names);
    until Names^=nil;
  result := true;
end;

class function TSynLibrary.Load(const aDllFileName: TFileName; Addr: PPointer;
  Names: PPChar): HMODULE;
var H: HMODULE;
begin
  result := 0;
  H := SafeLoadLibrary(aDllFileName);
  if (H<>0) and ProcLoad(H,Addr,Names) then
    result := H;
end;

procedure TSynLibrary.UnLoad;
begin
  if fHandle=0 then
    exit;
  FreeLibrary(fHandle);
  fHandle := 0;
end;


{ TGDIPlus }

{$ifdef USEENCODERS}
type
  ImageCodecInfo = record
    Clsid             : TGUID;
    FormatID          : TGUID;
    CodecName         : PWCHAR;
    DllName           : PWCHAR;
    FormatDescription : PWCHAR;
    FilenameExtension : PWCHAR;
    MimeType          : PWCHAR;
    Flags             : DWORD;
    Version           : DWORD;
    SigCount          : DWORD;
    SigSize           : DWORD;
    SigPattern        : PBYTE;
    SigMask           : PBYTE;
  end;
  TImageCodecInfo = ImageCodecInfo;
  PImageCodecInfo = ^TImageCodecInfo;
  TImageCodecInfoArray = array[byte] of TImageCodecInfo;


function StrWCompAnsi(Str1: PWord; Str2: PByte): integer;
asm // to avoid widestring usage + compatibility with Delphi 2009/2010/XE
  if Str1<>Str2 then
  if Str1<>nil then
  if Str2<>nil then begin
    if Str1^=Str2^ then
      repeat
        if Str1^=0 then break;
        inc(Str1);
        inc(Str2);
      until Str1^<>Str2^;
    result := Str1^-Str2^;
    exit;
  end else
  result := 1 else  // Str2=''
  result := -1 else // Str1=''
  result := 0;      // Str1=Str2
end;

function TGDIPlus.GetEncoderClsid(format: PAnsiChar; out pClsid: TGUID): integer;
var num, size: cardinal;
    ImageCodecInfo: AnsiString;
    P: ^TImageCodecInfoArray;
begin
  num  := 0; // number of image encoders
  size := 0; // size of the image encoder array in bytes
  result := -1;
  if (GetImageEncodersSize(num, size)<>stOk) or (size=0) then
    exit;
  SetLength(ImageCodecInfo, size);
  P := pointer(ImageCodecInfo);
  if GetImageEncoders(num, size, P)<>stOk then
    exit;
  for result := 0 to num-1 do
    if StrWCompAnsi(pointer(P^[result].MimeType),pointer(format))=0 then begin
      pClsid := P^[result].Clsid;
      exit;
    end;
  result := -1;
end;

const
  MimeTypes: array[TGDIPPictureType] of PAnsiChar =
   ('image/gif','image/png','image/jpeg','image/bmp','image/tiff');

var
  Encoders: array[TGDIPPictureType] of TGUID;

{$else}

const
  Encoders: array[TGDIPPictureType] of TGUID =
   ('{557CF402-1A04-11D3-9A73-0000F81EF32E}',
    '{557CF406-1A04-11D3-9A73-0000F81EF32E}',
    '{557CF401-1A04-11D3-9A73-0000F81EF32E}',
    '{557CF400-1A04-11D3-9A73-0000F81EF32E}',
    '{557CF405-1A04-11D3-9A73-0000F81EF32E}');
{$endif}

const
  FrameDimensionPage: TGUID = '{7462dc86-6180-4c7e-8e3f-ee7333a7a483}';

  GdiPProcNames: array[0..33{$ifdef USEDPI}+1{$endif}
      {$ifdef USEENCODERS}+2{$endif}] of PChar =
    ('GdiplusStartup','GdiplusShutdown',
     'GdipDeleteGraphics','GdipCreateFromHDC',
     'GdipLoadImageFromStream','GdipLoadImageFromFile',
     'GdipDrawImageRectI','GdipDrawImageRectRectI',
{$ifdef USEDPI} 'GdipDrawImageI', {$endif}
     'GdipDisposeImage', 'GdipGetImageRawFormat',
     'GdipGetImageWidth','GdipGetImageHeight','GdipSaveImageToStream',
{$ifdef USEENCODERS} 'GdipGetImageEncodersSize','GdipGetImageEncoders', {$endif}
     'GdipCreateBitmapFromHBITMAP','GdipCreateBitmapFromGdiDib','GdipBitmapSetResolution',
     'GdipImageGetFrameCount','GdipImageSelectActiveFrame',
     'GdipCreateImageAttributes', 'GdipCloneImageAttributes', 'GdipDisposeImageAttributes',
     'GdipSetImageAttributesToIdentity', 'GdipResetImageAttributes',
     'GdipSetImageAttributesColorMatrix', 'GdipSetImageAttributesThreshold',
     'GdipSetImageAttributesGamma', 'GdipSetImageAttributesNoOp',
     'GdipSetImageAttributesColorKeys', 'GdipSetImageAttributesOutputChannel',
     'GdipSetImageAttributesOutputChannelColorProfile', 'GdipSetImageAttributesRemapTable',
     'GdipSetImageAttributesWrapMode', 'GdipGetImageAttributesAdjustedPalette',
     nil);

constructor TGDIPlus.Create(const aDllFileName: TFileName);
var Input: record
      Version: Integer;               // Must be one
      DebugEventCallback: Pointer;    // Only for debug builds
      SuppressBackgroundThread: Bool; // True if replacing GDI+ background processing
      SuppressExternalCodecs: Bool;   // True if only using internal codecs
    end;
{$ifdef USEENCODERS}
    Format: TGDIPPictureType;
{$endif}
begin
  if fHandle=0 then begin
    fHandle := Load(aDllFileName,@@Startup,@GdiPProcNames);
    if fHandle=0 then
      exit;
  end;
  fillchar(Input,sizeof(Input),0);
  Input.Version := 1;
  // see http://mikevdm.com/BlogEntry/Key/GdiplusShutdown-Hangs-Mysteriously
  Input.SuppressBackgroundThread := true;
  if Startup(fToken,Input,fStartupHook)<>stOk then begin
    fToken := 0;
    UnLoad;
    exit;
  end;
  if Assigned(fStartupHook.Hook) then // may be nil e.g. for Win64
    fStartupHook.Hook(fStartupHookToken);
{$ifdef USEENCODERS}
  for Format := low(Format) to high(Format) do
    GetEncoderClsid(MimeTypes[Format],Encoders[Format]);
{$endif}
end;

procedure TGDIPlus.DrawAntiAliased(Source: TMetafile; Dest: HDC; R: TRect;
  aSmoothing: TSmoothingMode; aTextRendering: TTextRenderingHint);
begin
  dec(R.Right);
  dec(R.Bottom); // Metafile rect includes right and bottom coords
  PlayEnhMetaFile(Dest,Source.Handle,R); // use GDI drawing by default
end;

function TGDIPlus.DrawAntiAliased(Source: TMetafile; ScaleX,ScaleY: integer;
  aSmoothing: TSmoothingMode; aTextRendering: TTextRenderingHint): TBitmap;
var R: TRect;
begin
  result := nil;
  if Source=nil then // self=nil is OK below
    Exit;
  R.Left := 0;
  R.Right := (Source.Width*ScaleX)div 100;
  R.Top := 0;
  R.Bottom := (Source.Height*ScaleY)div 100;
  result := TBitmap.Create;
  result.PixelFormat := pf24bit; // create as DIB (device-independent bitmap)
  result.Width := R.Right;
  result.Height := R.Bottom;
  if Self=nil then begin // no GDI+ available -> use GDI drawing
    Dec(R.Right);  // Metafile rect includes right and bottom coords
    Dec(R.Bottom);
    PlayEnhMetaFile(Result.Canvas.Handle,Source.Handle,R);
  end else
    DrawAntiAliased(Source,Result.Canvas.Handle,R,aSmoothing,aTextRendering);
end;

destructor TGDIPlus.Destroy;
begin
  if fToken<>0 then begin
    if Assigned(fStartupHook.UnHook) then // may be nil e.g. for Win64
      fStartupHook.UnHook(fStartupHookToken);
    Shutdown(fToken);
    fToken := 0;
  end;
  UnLoad;
  inherited Destroy;
end;

const
  PicturesExt: array[0..5] of TFileName =
    ('jpg','jpeg','png','gif','tif','tiff');
  PictureClasses: array[0..5] of TGraphicClass =
    (TJpegImage,TJpegImage,TPngImage,TGifImage,TTiffImage,TTiffImage);

function PictureName(Pic: TGraphicClass): string;
var i: integer;
begin
  result := '';
  if Pic<>nil then
    if Pic.InheritsFrom(TIcon) or Pic.InheritsFrom(TBitmap) or
       Pic.InheritsFrom(TMetaFile) then
      result := copy(Pic.ClassName,2,maxInt) else
      for i := 0 to high(PictureClasses) do
        if Pic.InheritsFrom(PictureClasses[i]) then
          result := copy(Pic.ClassName,2,length(Pic.ClassName)-6);
end;

procedure TGDIPlus.RegisterPictures;
var i: integer;
begin
  // launch Gdip.RegisterPictures to initialize the GDI+ library if necessary
  if (Self=nil) and (Gdip=nil) then
      Gdip := TGDIPlus.Create('gdiplus.dll');
  // register JPG and PNG pictures as TGraphic
  if GetClass('TTiffImage')=nil then begin
    RegisterClass(TJpegImage);
    RegisterClass(TPngImage);
    RegisterClass(TGifImage);
    RegisterClass(TTiffImage);
    for i := 0 to high(PicturesExt) do
      TPicture.RegisterFileFormat(PicturesExt[i],
        PictureName(PictureClasses[i]),PictureClasses[i]);
  end;
end;


{ TSynPicture }

procedure TSynPicture.Assign(Source: TPersistent);
var S: TMemoryStream;
begin
  if (Source<>nil) and Source.InheritsFrom(TPicture) then
    Source := TPicture(Source).Graphic;
  if (Source=nil) or not Gdip.Exists or
     (Source.InheritsFrom(TSynPicture) and not TSynPicture(Source).fHasContent) then
    Clear else
  if Source.InheritsFrom(TBitmap) then begin // direct bitmap creation
    Clear;
    with TBitmap(Source) do
      if Gdip.CreateBitmapFromHBITMAP(Handle,Palette,fImage)<>stOk then begin
       Clear;
       exit;
      end;
    fAssignedFromBitmap := true;
    fImageSet;
  end else
  if Source.InheritsFrom(TGraphic) then begin // loading from a temp stream
    S := TMemoryStream.Create;
    try
      TGraphic(Source).SaveToStream(S);
      S.Seek(0,soFromBeginning);
      LoadFromStream(S);
    finally
      S.Free;
    end;
  end else
    Clear;
end;

procedure TSynPicture.Clear;
begin
  {$ifdef FPC}
  inherited Clear;
  {$endif}
  fHasContent := false;
  fAssignedFromBitmap := false;
  fWidth := 0;
  fHeight := 0;
  if fImage<>0 then begin
    Gdip.DisposeImage(fImage);
    fImage := 0;
  end;
  fStream := nil;
  if fGlobal<>0 then begin
    GlobalFree(fGlobal); // fDeleteOnRelease=false -> manual release
    fGlobal := 0;
  end;
  fGlobalLen := 0;
end;

constructor TSynPicture.CreateFromFile(const FileName: string);
begin
  inherited Create;
  LoadFromFile(FileName);
end;

constructor TSynPicture.CreateFromBuffer(Buffer: pointer; Len: integer);
begin
  inherited Create;
  LoadFromBuffer(Buffer,Len);
end;

destructor TSynPicture.Destroy;
begin
  Clear;
  inherited;
end;

procedure TSynPicture.Draw(ACanvas: TCanvas; const Rect: TRect);
var graphics: THandle;
begin
  if (self=nil) or not fHasContent or (fImage=0) or (ACanvas=nil) or
     not Gdip.Exists then
    exit;
  if (Gdip.CreateFromHDC(ACanvas.Handle,graphics)=stOk) and (graphics<>0) then
  try
    Gdip.DrawImageRect(graphics, fImage,
      Rect.Left, Rect.Top, Rect.Right - Rect.Left, Rect.Bottom - Rect.Top);
  finally
    Gdip.DeleteGraphics(graphics);
  end;
end;

procedure TSynPicture.Draw(ACanvas: TCanvas; const dst, src: TRect; attributes: TImageAttributes; u: TUnit);
var graphics: THandle;
    ia: TGpipImageAttributes;
begin
  if (self=nil) or not fHasContent or (fImage=0) or (ACanvas=nil) or not Gdip.Exists then
    exit;
  if Assigned(attributes) then
    ia := attributes.fAttr else
    ia := nil;
  if (Gdip.CreateFromHDC(ACanvas.Handle,graphics)=stOk) and (graphics<>0) then
  try
    Gdip.DrawImageRectRect(graphics, fImage,
      dst.Left, dst.Top, dst.Right - dst.Left, dst.Bottom - dst.Top,
      src.Left, src.Top, src.Right - src.Left, src.Bottom - src.Top, u, ia);
  finally
    Gdip.DeleteGraphics(graphics);
  end;
end;

{$ifdef USEDPI}
procedure TSynPicture.DrawAt(ACanvas: TCanvas; X, Y: integer);
var graphics: THandle;
begin
  if (self=nil) or not fHasContent or (fImage=0) or (ACanvas=nil) or
     not Gdip.Exists then
    exit;
  graphics := 0;
  Gdip.SetStatus(Gdip.CreateFromHDC(ACanvas.Handle,graphics));
  if graphics<>0 then
  try
    Gdip.SetStatus(Gdip.DrawImage(graphics,fImage,X,Y));
  finally
    Gdip.SetStatus(Gdip.DeleteGraphics(graphics));
  end;
end;
{$endif}

procedure TSynPicture.BitmapSetResolution(DPI: single);
begin
  if (fImage<>0) and fAssignedFromBitmap and (DPI<>0) then
    Gdip.BitmapSetResolution(fImage,DPI,DPI);
end;

procedure TSynPicture.fImageSet;
begin
  if fImage=0 then
    exit;
  if (Gdip.GetImageWidth(fImage,fWidth)<>stOk) or
     (Gdip.GetImageHeight(fImage,fHeight)<>stOk) or
     (fWidth=0) or (fHeight=0) then
    Clear else
    fHasContent := true;
end;

function TSynPicture.GetEmpty: Boolean;
begin
  result := not fHasContent;
end;

function TSynPicture.GetHeight: Integer;
begin
  result := fHeight;
end;

function TSynPicture.GetImageFormat: TGDIPPictureType;
const // only the TGUID.D1 is relevant here
  RawFormat: array[TGDIPPictureType] of cardinal =
    ($b96b3cb0, $b96b3caf, $b96b3cae, $b96b3cab, $b96b3cb1);
var id: TGUID;
begin
  if Gdip.Exists and fHasContent and (fImage<>0) and
     (Gdip.GetImageRawFormat(fImage,id)=stOk) then
    for result := low(result) to high(result) do
      if id.D1=RawFormat[result] then
        exit;
  result := gptBMP; // by default, returns bitmap
end;

function TSynPicture.GetWidth: Integer;
begin
  result := fWidth;
end;

class function TSynPicture.IsPicture(const FileName: TFileName): TGraphicClass;
var Ext: TFileName;
    i: Integer;
begin
  result := nil;
  Ext := ExtractFileExt(FileName);
  if Ext='' then
    exit;
  Delete(Ext,1,1); // '.bmp' -> 'bmp'
  if SameText(Ext,'BMP') then
    result := TBitmap else
  if SameText(Ext,'EMF') then
    result := TMetafile else
  if SameText(Ext,'WMF') then
    result := TMetafile else
  if SameText(Ext,'ICO') then
    result := TIcon else
  for i := 0 to high(PicturesExt) do
    if SameText(Ext,PicturesExt[i]) then begin
      result := PictureClasses[i];
      exit;
    end;
end;

{$ifdef FPC}
procedure TSynPicture.LoadFromClipboardFormat(FormatID: TClipboardFormat);
begin // not implemented
end;
procedure TSynPicture.SaveToClipboardFormat(FormatID: TClipboardFormat);
begin // not implemented
end;
{$else}
procedure TSynPicture.LoadFromClipboardFormat(AFormat: Word;
  AData: THandle; APalette: HPALETTE);
begin // not implemented
end;
procedure TSynPicture.SaveToClipboardFormat(var AFormat: Word;
  var AData: THandle; var APalette: HPALETTE);
begin // not implemented
end;
{$endif}

procedure TSynPicture.LoadFromFile(const FileName: string);
var FS: TFileStream;
begin // don't use direct GDI+ file oriented API: it's better having a local
  // copy of the untouched data in memory (e.g. for further jpeg saving)
  Clear;
  if not Gdip.Exists or not FileExists(FileName) then
    exit;
  FS := TFileStream.Create(FileName,fmOpenRead or fmShareDenyNone);
  try
    LoadFromStream(FS);
  finally
    FS.Free;
  end;
end;

procedure TSynPicture.LoadFromResourceName(Instance: THandle;
  const ResName: string);
var  Stream: TCustomMemoryStream;
begin
  if FindResource(Instance,PChar(ResName),RT_RCDATA) <> 0 then begin
    Stream := TResourceStream.Create(Instance, ResName, RT_RCDATA);
    try
      LoadFromStream(Stream);
    finally
      Stream.Free;
    end;
  end else
    Clear;
end;

function TSynPicture.LoadFromIStream(Stream: IStream): TGdipStatus;
begin
  if not Gdip.Exists then begin
    result := stInValidParameter;
    exit;
  end;
  result := Gdip.LoadImageFromStream(Stream,fImage);
  if result=stOk then begin
    fStream := Stream;
    fImageSet;
  end else
    Clear;
end;

procedure TSynPicture.LoadFromStream(Stream: TStream);
var P: pointer;
begin
  Clear;
  if not Gdip.Exists or (Stream=nil) then
    exit;
  fGlobalLen := Stream.Size - Stream.Position;
  if fGlobalLen=0 then
    exit;
  fGlobal := GlobalAlloc(GMEM_MOVEABLE,fGlobalLen);
  if fGlobal=0 then
    exit;
  P := GlobalLock(fGlobal);
  Stream.Read(P^,fGlobalLen);
  GlobalUnlock(fGlobal);
  CreateStreamOnHGlobal(fGlobal,false,fStream); // fDeleteOnRelease=false
  LoadFromIStream(fStream);
end;

procedure TSynPicture.LoadFromBuffer(Buffer: pointer; Len: integer);
var P: pointer;
begin
  Clear;
  if not Gdip.Exists or (Buffer=nil) or (Len=0) then
    exit;
  fGlobalLen := Len;
  fGlobal := GlobalAlloc(GMEM_MOVEABLE,Len);
  if fGlobal=0 then
    exit;
  P := GlobalLock(fGlobal);
  Move(Buffer^,P^,Len);
  GlobalUnlock(fGlobal);
  CreateStreamOnHGlobal(fGlobal,false,fStream); // fDeleteOnRelease=false
  LoadFromIStream(fStream);
end;

function TSynPicture.RectNotBiggerThan(MaxPixelsForBiggestSide: Integer): TRect;
begin
  result.Left := 0;
  result.Top := 0;
  result.Bottom := fHeight;
  result.Right := fWidth;
  if not fHasContent or (result.Bottom=0) or (result.Right=0) then
    exit;
  if result.Right>result.Bottom then begin
    if result.Right>MaxPixelsForBiggestSide then begin
      result.Bottom := (result.Bottom*MaxPixelsForBiggestSide) div result.Right;
      result.Right := MaxPixelsForBiggestSide;
    end;
  end else
    if result.Bottom>MaxPixelsForBiggestSide then begin
      result.Right := (result.Right*MaxPixelsForBiggestSide) div result.Bottom;
      result.Bottom := MaxPixelsForBiggestSide;
    end;
end;

type
  EncoderParameter = record
    Guid           : TGUID;   // GUID of the parameter
    NumberOfValues : ULONG;   // number of values for this parameter
    Type_          : ULONG;   // value type, like ValueTypeLONG  etc.
    Value          : Pointer; // a pointer to the parameter values
  end;
  TEncoderParameter = EncoderParameter;
  PEncoderParameter = ^TEncoderParameter;
  EncoderParameters = record
    Count     : UINT;  // number of parameters in this structure
    Parameter : array[0..0] of TEncoderParameter;  // parameter values
  end;
  TEncoderParameters = EncoderParameters;
  PEncoderParameters = ^TEncoderParameters;

const
  EncoderParameterValueTypeLong = 4;    // 32-bit unsigned int
  EncoderQuality: TGUID = '{1d5be4b5-fa4a-452d-9cdd-5db35105e7eb}';
  EncoderCompression: TGUID = '{e09d739d-ccd4-44ee-8eba-3fbf8be4fc58}';

function TSynPicture.SaveAsIStream(out Stream: IStream; Format: TGDIPPictureType;
  CompressionQuality: integer; IfBitmapSetResolution: single): TGdipStatus;
var Params: TEncoderParameters;
    PParams: pointer;
begin
  if not Gdip.Exists or (fImage=0) then begin
    result := stInvalidParameter;
    exit;
  end;
  if IfBitmapSetResolution<>0 then
    BitmapSetResolution(IfBitmapSetResolution);
  Params.Count := 1;
  Params.Parameter[0].Type_ := EncoderParameterValueTypeLong;
  Params.Parameter[0].NumberOfValues := 1;
  Params.Parameter[0].Value := @CompressionQuality;
  PParams := nil;
  case Format of
  gptJPG: if CompressionQuality>=0 then begin
    Params.Parameter[0].Guid := EncoderQuality;
    PParams := @Params;
  end;
  gptTIF: begin
    if not (TGDIPPEncoderValue(CompressionQuality) in [
        evCompressionLZW, evCompressionCCITT3, evCompressionCCITT4,
        evCompressionRle, evCompressionNone]) then
      // default tiff compression is LZW
      CompressionQuality := ord(evCompressionLZW);
    Params.Parameter[0].Guid := EncoderCompression;
    PParams := @Params;
  end;
  end;
  CreateStreamOnHGlobal(0,true,Stream); // fDeleteOnRelease=true
  result := Gdip.SaveImageToStream(fImage,Stream,@Encoders[Format],PParams);
end;

function TSynPicture.SaveAs(Stream: TStream; Format: TGDIPPictureType;
  CompressionQuality: integer; IfBitmapSetResolution: single): TGdipStatus;
var fStream: IStream;
    Len,Dummy: {$ifdef FPC}QWord;{$else}{$ifdef ISDELPHIXE8}LargeUInt{$else}Int64{$endif};{$endif}
    tmp: pointer;
begin
  if Stream=nil then
    result := stInvalidParameter else
    result := SaveAsIStream(fStream, Format, CompressionQuality, IfBitmapSetResolution);
  if result<>stOk then
    exit;
  fStream.Seek(0,STREAM_SEEK_END,Len);
  fStream.Seek(0,STREAM_SEEK_SET,Dummy);
  Getmem(tmp,Len);
  try
    fStream.Read(tmp,Len,nil);
    fStream := nil; // release ASAP
    Stream.WriteBuffer(tmp^,Len);
  finally
    Freemem(tmp);
  end;
end;

procedure TSynPicture.SaveInternalToStream(Stream: TStream);
var P: pointer;
    F: TGDIPPictureType;
begin
  if not Gdip.Exists or (Stream=nil) or (fImage=0) then
    exit;
  if (fGlobal<>0) and not fAssignedFromBitmap then begin
    // e.g. for a true .jpg file -> just save as it was loaded :)
    P := GlobalLock(fGlobal);
    Stream.WriteBuffer(P^,fGlobalLen);
    GlobalUnLock(fGlobal);
  end else begin
    // should come from a bitmap -> save in the expected format
    if InheritsFrom(TJpegImage) then
      F := gptJPG else
    if InheritsFrom(TGifImage) then
      F := gptGIF else
    if InheritsFrom(TPngImage) then
      F := gptPNG else
    if InheritsFrom(TTiffImage) then
      F := gptTIF else
      F := GetImageFormat;
    SaveAs(Stream,F);
  end;
end;

procedure TSynPicture.SaveToStream(Stream: TStream);
begin
  SaveInternalToStream(Stream);
end;

procedure TSynPicture.SetHeight(Value: Integer);
begin // not implemented
end;

procedure TSynPicture.SetWidth(Value: Integer);
begin // not implemented
end;

function TSynPicture.ToBitmap: TBitmap;
begin
  if not fHasContent then
    result := nil else begin
    result := TBitmap.Create;
    result.PixelFormat := pf24bit; // create as DIB (device-independent bitmap)
    result.Width := Width;
    result.Height := Height;
    result.Canvas.Lock;
    try
      result.Canvas.Draw(0,0,self);
    finally
      result.Canvas.Unlock;
    end;
  end;
end;

{$ifdef FPC}
function TSynPicture.GetTransparent: Boolean;
begin // not implemented
  result := false;
end;

procedure TSynPicture.SetTransparent(Value: Boolean);
begin // not implemented
end;
{$endif}


{ TJpegImage }

constructor TJpegImage.Create;
begin
  inherited;
  fCompressionQuality := 80; // default quality
end;

procedure TJpegImage.SaveToStream(Stream: TStream);
begin
  SaveAs(Stream,gptJPG,fCompressionQuality);
end;


{ TTiffImage }

procedure TTiffImage.ExtractPage(index: integer; wBMP: TBitmap);
var wFrames: integer;
  wStrm: TMemoryStream;
begin
  wFrames := GetPageCount;
  if (wFrames>0) and (cardinal(index)<cardinal(wFrames)) then begin
    try
      Gdip.SelectActiveFrame(fImage, @FrameDimensionPage, index);
      wStrm := TMemoryStream.Create;
      try
        SaveAs(wstrm,gptBMP);
        wStrm.Position := 0;
        wBmp.LoadFromStream(wStrm);
      finally
        wStrm.Free;
      end;
    finally
      Gdip.SelectActiveFrame(fImage, @FrameDimensionPage, fActivePage);
    end;
  end else
    raise ERangeError.Create('Invalid Page Index');
end;

function TTiffImage.GetPageCount: integer;
var wCount: UINT;
begin
  if fImage<>0 then begin
    Gdip.GetFrameCount(fImage, @FrameDimensionPage, wCount);
    result := wCount;
  end else
    result := 0;
end;

procedure TTiffImage.SelectPage(index: integer);
var wFrames: integer;
begin
  wFrames := GetPageCount;
  if (wFrames>0) and (cardinal(index)<cardinal(wFrames)) then begin
    Gdip.SelectActiveFrame(fImage, @FrameDimensionPage, index);
    fActivePage := index;
  end else
    raise ERangeError.Create('Invalid Page Index');
end;


procedure SaveAs(Graphic: TPersistent; Stream: TStream;
  Format: TGDIPPictureType; CompressionQuality: integer=80;
  MaxPixelsForBiggestSide: cardinal=0; BitmapSetResolution: single=0); overload;
var Bmp: TBitmap;
    R: TRect;
    Pic: TSynPicture;
begin
  GdipLock;
  try
    if Gdip = nil then
      Gdip := TGDIPlus.Create('gdiplus.dll');
    if Graphic.InheritsFrom(TSynPicture) then
      Pic := TSynPicture(Graphic) else
      Pic := TSynPicture.Create;
    try
      if Pic<>Graphic then
        Pic.Assign(Graphic); // will do the conversion
      if (MaxPixelsForBiggestSide=0) or
         ((Pic.fWidth<=MaxPixelsForBiggestSide) and (Pic.fHeight<=MaxPixelsForBiggestSide)) then
        // no resize necessary
        Pic.SaveAs(Stream,Format,CompressionQuality,BitmapSetResolution) else begin
        // resize to the maximum side specified parameter
        Bmp := TBitmap.Create;
        try
          Bmp.PixelFormat := pf24bit; // create as DIB (device-independent bitmap)
          R := Pic.RectNotBiggerThan(MaxPixelsForBiggestSide);
          Bmp.Width := R.Right;
          Bmp.Height := R.Bottom;
          Bmp.Canvas.Lock;
          try
            Pic.Draw(Bmp.Canvas,R);
          finally
            Bmp.Canvas.Unlock;
          end;
          SynGdiPlus.SaveAs(Bmp,Stream,Format,CompressionQuality,0,BitmapSetResolution);
        finally
          Bmp.Free;
        end;
      end;
    finally
      if Pic<>Graphic then
        Pic.Free;
    end;
  finally
    GdipUnlock;
  end;
end;

procedure SaveAs(Graphic: TPersistent; const FileName: TFileName;
  Format: TGDIPPictureType; CompressionQuality: integer=80;
  MaxPixelsForBiggestSide: cardinal=0; BitmapSetResolution: single=0); overload;
var Stream: TStream;
begin
  Stream := TFileStream.Create(Filename, fmCreate);
  try
    SaveAs(Graphic,Stream,Format,CompressionQuality,MaxPixelsForBiggestSide,
      BitmapSetResolution);
  finally
    Stream.Free;
  end;
end;

{$ifndef HASCODEPAGE}
type RawByteString = AnsiString;
{$endif}

procedure SaveAsRawByteString(Graphic: TPersistent;
  out DataRawByteString{$ifdef HASCODEPAGE}: RawByteString{$endif};
  Format: TGDIPPictureType; CompressionQuality: integer=80;
  MaxPixelsForBiggestSide: cardinal=0; BitmapSetResolution: single=0);{$ifndef FPC}overload;{$endif}
{$ifdef UNICODE}
var Stream: TMemoryStream;
begin
  Stream := TMemoryStream.Create;
{$else}
var Stream: TStringStream;
begin
  Stream := TStringStream.Create('');
{$endif}
  GdipLock;
  try
    if Gdip = nil then
      Gdip := TGDIPlus.Create('gdiplus.dll');
    SaveAs(Graphic,Stream,Format,CompressionQuality,MaxPixelsForBiggestSide,
      BitmapSetResolution);
{$ifdef UNICODE}
    SetString(RawByteString(DataRawByteString),PAnsiChar(Stream.Memory),
      Stream.Seek(0,soFromCurrent));
{$else}
    string(DataRawByteString) := Stream.DataString;
{$endif}
  finally
    GdipUnlock;
    Stream.Free;
  end;
end;

function BitmapToRawByteString(Bitmap: TBitmap;
  out DataRawByteString{$ifdef HASCODEPAGE}: RawByteString{$endif};
  Format: TGDIPPictureType; CompressionQuality: integer=80;
  MaxPixelsForBiggestSide: cardinal=0; BitmapSetResolution: single=0): TGdipStatus;
var Pic: TSynPicture;
    fStream: IStream;
    Len,Dummy: {$ifdef FPC}QWord;{$else}{$ifdef ISDELPHIXE8}LargeUInt{$else}Int64{$endif};{$endif}
begin
  if Bitmap=nil then begin
    result := stInvalidParameter;
    exit;
  end;
  GdipLock;
  try
    if Gdip = nil then
      Gdip := TGDIPlus.Create('gdiplus.dll');
    Pic := TSynPicture.Create;
    try
      Pic.Assign(Bitmap); // will do the conversion
      result := Pic.SaveAsIStream(fStream,Format,CompressionQuality,BitmapSetResolution);
      if result<>stOk then
        exit;
      fStream.Seek(0,STREAM_SEEK_END,Len);
      fStream.Seek(0,STREAM_SEEK_SET,Dummy);
      {$ifdef HASCODEPAGE}
      SetLength(DataRawByteString,Len);
      {$else}
      SetLength(AnsiString(DataRawByteString),Len);
      {$endif}
      fStream.Read(pointer(DataRawByteString),Len,nil);
      fStream := nil; // release ASAP
    finally
      Pic.Free;
    end;
  finally
    GdipUnlock;
  end;
end;

function LoadFromRawByteString(const Picture: RawByteString): TBitmap;
begin
  result := nil;
  if Picture='' then
    exit;
  GdipLock;
  try
    if Gdip = nil then
      Gdip := TGDIPlus.Create('gdiplus.dll');
    with TSynPicture.Create do
    try
      LoadFromBuffer(pointer(Picture),length(Picture));
      result := ToBitmap;
    finally
      Free;
    end;
  finally
    GdipUnlock;
  end;
end;

function JpegRecompress(const jpeg: {$ifdef HASCODEPAGE}RawByteString{$else}AnsiString{$endif};
  quality: integer=80): {$ifdef HASCODEPAGE}RawByteString{$else}AnsiString{$endif};
var bitmap: TBitmap;
begin
  result := '';
  GdipLock;
  try
    if Gdip = nil then
      Gdip := TGDIPlus.Create('gdiplus.dll');
    bitmap := SynGdiPlus.LoadFromRawByteString(jpeg);
    if bitmap <> nil then
      try
        SaveAsRawByteString(bitmap,result,gptJPG,quality);
      finally
        bitmap.Free;
      end;
  finally
    GdipUnlock;
  end;
end;

procedure ExpectGDIPlusFull(ForceInternalAntiAliased,
  ForceInternalAntiAliasedFontFallBack: boolean);
begin
  GdipLock;
  try
    if (Gdip<>nil) and not Gdip.InheritsFrom(TGDIPlusFull) then
      FreeAndNil(Gdip); // we need the TGDIPlusFull features
    if Gdip=nil then
      // if specific gdiplus11.dll is not available then load OS default
      Gdip := TGDIPlusFull.Create;
    with Gdip as TGDIPlusFull do begin
      ForceInternalConvertToEmfPlus := ForceInternalAntiAliased;
      ForceUseDrawString := ForceInternalAntiAliasedFontFallBack;
    end;
  finally
    GdipUnlock;
  end;
end;

function LoadFrom(const FileName: TFileName): TBitmap;
var P: TSynPicture;
    MF: TMetafile;
    Ext: TFileName;
begin
  result := nil;
  if not FileExists(FileName) then
    exit;
  GdipLock;
  try
    ExpectGDIPlusFull(true,true);
    Ext := ExtractFileExt(FileName);
    if SameText(Ext,'.WMF') or SameText(Ext,'.EMF') then begin
      // EMF will be loaded and rendered using GDI+ anti-aliasing
      MF := TMetaFile.Create;
      try
        MF.LoadFromFile(FileName);
        result := LoadFrom(MF);  // will initialize TGDIPlusFull if needed
      finally
        MF.Free;
      end;
    end else begin
      // non vectorial pictures will be loaded via GDI+
      P := TSynPicture.Create;
      try
        P.LoadFromFile(FileName);
        result := P.ToBitmap;
      finally
        P.Free;
      end;
    end;
  finally
    GdipUnlock;
  end;
end;

function LoadFrom(const MetaFile: TMetaFile): TBitmap;
begin
  GdipLock;
  try
    ExpectGDIPlusFull(true,true);
    result := TGDIPlusFull(gdip).DrawAntiAliased(MetaFile);
  finally
    GdipUnlock;
  end;
end;

procedure DrawEmfGdip(aHDC: HDC; Source: TMetaFile; var R: TRect;
  ForceInternalAntiAliased, ForceInternalAntiAliasedFontFallBack: boolean);
begin
  GdipLock;
  try
    ExpectGDIPlusFull(ForceInternalAntiAliased,ForceInternalAntiAliasedFontFallBack);
    Gdip.DrawAntiAliased(Source,aHDC,R);
  finally
    GdipUnlock;
  end;
end;

procedure GdipTest(const JpegFile: TFileName);
var B: TBitmap;
    FN: TFileName;
    P: TSynPicture;
    F: TGDIPPictureType;
begin
  FN := ExtractFilePath(paramstr(0))+'GdipTest\';
{$ifndef DELPHI5OROLDER}
  if not DirectoryExists(FN) then
    CreateDirectory(pointer(FN),nil);
{$endif}
  B := LoadFrom(JpegFile);
  B.SaveToFile(FN+'Original.bmp');
  FN := FN+'Test';
  for F := low(F) to high(F) do
    SaveAs(B,FN+GDIPPictureExt[F],F);
  B.Free;
  P := TSynPicture.Create;
  try
    for F := low(F) to high(F) do begin
      P.LoadFromFile(FN+GDIPPictureExt[F]);
      P.SaveToFile(FN+'-copy'+GDIPPictureExt[F]);
      B := P.ToBitmap;
      if B<>nil then
      try
        B.SaveToFile(FN+GDIPPictureExt[F]+'.bmp');
      finally
        B.Free;
      end;
    end;
  finally
    P.Free;
  end;
end;


{ TGDIPlusFull }

function ReadRegString(Key: THandle; const Path, Value: string): string;
var Size, typ: DWORD;
    tmp: array[byte] of char;
    k: HKey;
begin
  result := '';
  if RegOpenKeyEx(Key, pointer(Path), 0, KEY_QUERY_VALUE, k)=ERROR_SUCCESS then
  try
    Size := 250;
    typ := REG_SZ;
    if RegQueryValueEx(k, pointer(Value), nil, @typ, @tmp, @Size)=ERROR_SUCCESS then
      result := tmp;
  finally
    RegCloseKey(k);
  end;
end;


{$ifdef DELPHI6OROLDER}
function GetFileVersion(const AFileName: string): Cardinal;
var FileName: string;
    InfoSize, Wnd: THandle;
    VerBuf: Pointer;
    FI: PVSFixedFileInfo;
    VerSize: cardinal;
begin
  result := Cardinal(-1);
  // GetFileVersionInfo modifies the filename parameter data while parsing.
  // Copy the string const into a local variable to create a writeable copy.
  FileName := AFileName;
  UniqueString(FileName);
  InfoSize := GetFileVersionInfoSize(PChar(FileName), Wnd);
  if InfoSize <> 0 then
  begin
    GetMem(VerBuf, InfoSize);
    try
      if GetFileVersionInfo(PChar(FileName), Wnd, InfoSize, VerBuf) then
        if VerQueryValue(VerBuf, '\', Pointer(FI), VerSize) then
          result := FI.dwFileVersionMS;
    finally
      FreeMem(VerBuf);
    end;
  end;
end;
{$endif}

constructor TGDIPlusFull.Create(aDllFileName: TFileName);
const GdiPFullProcNames: array[0..70] of PChar =
  ('GdipDrawLineI','GdipCreatePen1','GdipDeletePen','GdipFlush',
   'GdipSetSmoothingMode','GdipSetTextRenderingHint',
   'GdipSetPenBrushFill','GdipSetPenColor','GdipSetPenWidth',
   'GdipDeleteBrush','GdipCreateSolidFill',
   'GdipFillRectangleI', 'GdipFillEllipseI', 'GdipDrawEllipseI',
   'GdipDrawCurveI', 'GdipGraphicsClear',
   'GdipSetPageUnit','GdipDrawRectangleI', 'GdipSetPenDashStyle',
   'GdipDrawPolygonI','GdipFillPolygonI',
   'GdipSetWorldTransform', 'GdipGetWorldTransform',
   'GdipCreateMatrix','GdipCreateMatrix2','GdipDeleteMatrix',
   'GdipSetMatrixElements', 'GdipMultiplyMatrix',
   'GdipScaleMatrix','GdipTranslateMatrix',
   'GdipDrawLinesI','GdipRecordMetafileI','GdipRecordMetafileStreamI',
   'GdipPlayMetafileRecord','GdipEnumerateMetafileDestRectI',
   'GdipResetWorldTransform', 'GdipRotateWorldTransform',
   'GdipTranslateWorldTransform',
   'GdipGetImageGraphicsContext',
   'GdipCreateFontFromDC','GdipDeleteFont', 'GdipCreateFontFromLogfontW',
   'GdipDrawString','GdipMeasureString','GdipDrawDriverString',
   'GdipCreatePath','GdipDeletePath','GdipDrawPath','GdipFillPath',
   'GdipAddPathLineI','GdipAddPathLine2I','GdipAddPathArcI','GdipAddPathCurveI',
   'GdipAddPathClosedCurveI','GdipAddPathEllipseI','GdipAddPathPolygonI',
   'GdipAddPathRectangleI','GdipClosePathFigure',
   'GdipDrawArcI','GdipDrawBezierI','GdipDrawPieI',
   'GdipCreateBitmapFromScan0', 'GdipBitmapLockBits', 'GdipBitmapUnlockBits',
   'GdipGetClip','GdipSetClipRegion', 'GdipSetClipRectI', 'GdipResetClip',
   'GdipCreateRegion', 'GdipDeleteRegion',
   nil);
{$ifndef WIN64}
   Office2003Version= $B0000; // Office 2003 = Office 11 ($B)
var i: integer;
{$endif}
begin
  if (aDllFileName='') or not FileExists(aDllFileName) then begin
    // first search gdiplus11.dll / gdiplus.dll in the same directory
    aDllFileName := ExtractFilePath(paramstr(0))+'gdiplus11.dll';
    if not FileExists(aDllFileName) then
      aDllFileName := ExtractFilePath(paramstr(0))+'gdiplus.dll';
    // if not available in the excutable folder, search for Office 2003/2007
    if not FileExists(aDllFileName) then begin
      {$ifdef WIN64}
      aDllFileName := ''; // Office is a 32 bit app with 32 bit dlls :)
      {$else}
      aDllFileName := ReadRegString(HKEY_CLASSES_ROOT,
        'Applications\Winword.exe\shell\edit\command','');
      if aDllFileName<>'' then begin
        delete(aDllFileName,1,1);
        i := pos('"',aDllFileName);
        if i>0 then
          SetLength(aDllFileName,i-1); // 'WinWord.exe' with full path
        if GetFileVersion(aDllFileName)<Office2003Version then
          aDllFileName := '' else begin // no GDI+ 1.1 available in oldest Office
          aDllFileName := ExtractFilePath(aDllFileName)+'gdiplus.dll';
          if not FileExists(aDllFileName) then
            aDllFileName := '';
        end;
      end;
      {$endif}
    end;
  end;
  if aDllFileName='' then
    aDllFileName := 'gdiplus.dll'; // load default OS version
  fHandle := Load(aDllFileName,@@Startup,@GdiPProcNames);
  if fHandle<>0 then
    if not ProcLoad(fHandle,@@DrawLine,@GdiPFullProcNames) then
      UnLoad else
      inherited Create(aDllFileName);
end;

function ColorRefToARGB(rgb: COLORREF): cardinal;
{$ifdef PUREPASCAL}
begin
  if integer(rgb)<0 then
    rgb := GetSysColor(rgb and $ff);
//  result := TCOLORREF(rgb).B+TCOLORREF(rgb).G shl 8+TCOLORREF(rgb).R shl 16+$FF000000;
  result := (rgb shr 16) or (rgb and $ff00) or (rgb and $ff)shl 16 or $FF000000;
end;
{$else}
asm
    test eax,eax
    jnl @n
    and eax,$ff
    push eax
    call GetSysColor
@n: bswap eax
    mov al,$ff
    ror eax,8
end;
{$endif}

procedure Points16To32(PW: PWordArray; var PI: PIntegerArray; n: integer);
var i: integer;
begin
  GetMem(PI,n*8);
  for i := 0 to n*2-1 do
    PI^[i] := PW^[i];
end;


{ TGDIPlusEnum }

type
  {$ifdef FPC}
  TEMRExtTextOut = TEMREXTTEXTOUTA;
  {$endif}

  /// expected font specifications
  TFontSpec = packed record
    angle: smallint; // -360..+360
    ascent, descent: word;
    underline: boolean;
    strikeout: boolean;
  end;

  /// one DC state properties
  TGDIPlusEnumState = record
    pen, brush, font, ClipRegion: THandle;
    move: {$ifdef FPC}TPointL{$else}TPoint{$endif};
    WinSize, ViewSize: TSize;
    WinOrg, ViewOrg: {$ifdef FPC}TPointL{$else}TPoint{$endif};
    fontColor, fontAlign: integer;
    fontSpec: TFontSpec;
    BkMode, BkColor: cardinal;
  end;

  /// internal data used by EnumEMFFunc() callback function
  TGDIPlusEnum = object
  public
    gdip: TGDIPlusFull;
    graphics: THandle;
    //metafile: integer;
    destDC: HDC;
    destMatrix: THandle;
    // contains the GDI+ objects, corresponding to the GDI32 THandleTable
    obj: array of packed record
      // GDI+ handle
      handle: THandle;
      // either OBJ_PEN, OBJ_FONT or OBJ_BRUSH
      kind: integer;
    end;
    // caching pens could make drawing somewhat faster
    CachedPen: array of packed record
      color: cardinal;
      width: Cardinal;
      handle: THandle;
    end;
    // caching brushes could make drawing somewhat faster
    CachedBrush: array of packed record
      color: cardinal;
      handle: THandle;
    end;
    // caching fonts could make drawing somewhat faster
    CachedFont: array of packed record
      handle: THandle;
      objfont: TFontSpec;
      LogFont: TLogFontW;
    end;
    // the DC states, as stored by SaveDC / RestoreDC methods
    nDC: integer;
    DC: array[0..10] of TGDIPlusEnumState;
    // if TRUE, DrawText() will use DrawString and not DrawDriverString
    UseDrawString: boolean;
    procedure SaveDC;
    procedure RestoreDC;
    procedure ScaleMatrix(matrixOrg: THandle);
    procedure CreatePenObj(index: integer; Color, Width, Style: Cardinal);
    procedure DeleteObj(index: integer);
    procedure EnumerateEnd;
    function GetCachedPen(color, width: Cardinal): THandle;
    function GetCachedSolidBrush(color: Cardinal): THandle;
    function GetCachedFontIndex(aLogFont: PLogFontW): integer;
    procedure SetCachedFontSpec(aHandle: THandle; var aObjFont: TFontSpec);
    /// helper function do draw directly a bitmap from *s to *d
    procedure DrawBitmap(xs,ys,ws,hs, xd,yd,wd,hd: integer; Bmi, bits: pointer);
    /// helper function to draw some text
    procedure DrawText(var EMR: TEMRExtTextOut);
  end;

const
  GdipRectFNull: TGdipRectF = (X:0;Y:0;Width:0;Height:0);

function DXTextWidth(DX: PIntegerArray; n: Integer): integer;
var i: integer;
begin
  result := 0;
  for i := 0 to n-1 do
    inc(result,DX^[i]);
end;

procedure SetPositions(X,Y: single; D: PGdipPointFArray; DX: PIntegerArray; n: Integer);
var i: integer;
begin
  for i := 0 to n-1 do begin
    D^[i].X := X;
    D^[i].Y := Y;
    X := X+DX^[i];
  end;
end;

procedure NormalizeRect(var Rect: {$ifdef FPC}TRectL{$else}TRect{$endif});
var tmp: integer;
begin // GDI+ can't draw twisted rects -> normalize such values
  if Rect.Right<Rect.Left then begin
    tmp := Rect.Left;
    Rect.Left := Rect.Right;
    Rect.Right := tmp;
  end;
  if Rect.Bottom<Rect.Top then begin
    tmp := Rect.Top;
    Rect.Top := Rect.Bottom;
    Rect.Bottom := tmp;
  end;
end;

// var tempWC: PWideChar; // for debug

/// EMF enumeration callback function, called from GDI
// - draw most content with GDI+ functions
function EnumEMFFunc(DC: HDC; var Table: THandleTable; Rec: PEnhMetaRecord;
   NumObjects: DWord; var Ref: TGDIPlusEnum): LongBool; stdcall;
var X: TXForm;
    matrixOrg, path: THandle;
    P32: PIntegerArray;
begin
  result := true;
  with Ref.DC[Ref.nDC] do
  case Rec^.iType of
  EMR_HEADER: begin
    if pointer(Ref.obj)=nil then
      SetLength(Ref.obj,PEnhMetaHeader(Rec)^.nHandles);
    GetWorldTransform(Ref.DestDC,X);
    Ref.Gdip.CreateMatrix2(X.eM11,X.eM12,X.eM21,X.eM22,X.eDx,X.eDy,Ref.destMatrix);
  end;
  EMR_SAVEDC:
    Ref.SaveDC;
  EMR_RESTOREDC:
    Ref.RestoreDC;
  EMR_SETWINDOWEXTEX:
    WinSize := PEMRSetWindowExtEx(Rec)^.szlExtent;
  EMR_SETWINDOWORGEX:
    WinOrg := PEMRSetWindowOrgEx(Rec)^.ptlOrigin;
  EMR_SETVIEWPORTEXTEX:
    ViewSize := PEMRSetViewPortExtEx(Rec)^.szlExtent;
  EMR_SETVIEWPORTORGEX:
    ViewOrg := PEMRSetViewPortOrgEx(Rec)^.ptlOrigin;
  EMR_SETBKMODE:
    BkMode := PEMRSetBkMode(Rec)^.iMode;
  EMR_SETBKCOLOR:
    BkColor := PEMRSetBkColor(Rec)^.crColor;
  EMR_SETWORLDTRANSFORM: begin
      with PEMRSetWorldTransform(Rec)^.xform do
        Ref.gdip.CreateMatrix2(eM11,eM12,eM21,eM22,eDx,eDy,matrixOrg);
      Ref.ScaleMatrix(matrixOrg);
      Ref.gdip.DeleteMatrix(matrixOrg);
    end;
  EMR_EXTCREATEFONTINDIRECTW:
    {$ifdef FPC}
    with PEMRExtCreateFontIndirectW(Rec)^ do begin
    {$else}
    with PEMRExtCreateFontIndirect(Rec)^ do begin
    {$endif}
      Ref.DeleteObj(ihFont-1);
      with Ref.obj[ihFont-1] do begin
        kind := OBJ_FONT;
        with Ref.CachedFont[Ref.GetCachedFontIndex(@elfw.elfLogFont)] do begin
          font := handle;
          fontspec := objfont;
        end;
        handle := font;
      end;
    end;
  EMR_CREATEPEN:
    with PEMRCreatePen(Rec)^ do begin
      Ref.DeleteObj(ihPen-1);
      Ref.CreatePenObj(ihPen-1, lopn.lopnColor,lopn.lopnWidth.X, lopn.lopnStyle);
    end;
  EMR_CREATEBRUSHINDIRECT:
    with PEMRCreateBrushIndirect(Rec)^ do begin
      Ref.DeleteObj(ihBrush-1);
      with Ref.obj[ihBrush-1] do begin
        kind := OBJ_BRUSH;
        if lb.lbStyle=BS_NULL then
          brush := 0 else begin
          handle := Ref.GetCachedSolidBrush(lb.lbColor);
          brush := handle;
        end;
      end;
    end;
  EMR_DELETEOBJECT:
    Ref.DeleteObj(PEMRDeleteObject(Rec)^.ihObject-1);
  EMR_SELECTOBJECT:
    if integer(PEMRSelectObject(Rec)^.ihObject)<0 then // stock object?
      case PEMRSelectObject(Rec)^.ihObject and $7fffffff of
        NULL_BRUSH: brush := 0;
        NULL_PEN:   pen := 0;
      end else
      if PEMRSelectObject(Rec)^.ihObject-1<cardinal(length(Ref.obj)) then // avoid GPF
      with Ref.Obj[PEMRSelectObject(Rec)^.ihObject-1] do
      case Kind of
        OBJ_PEN: pen := Handle;
        OBJ_BRUSH: brush := Handle;
        OBJ_FONT: begin
          font := Handle;
          Ref.SetCachedFontSpec(Handle,fontspec);
        end;
      end;
  EMR_SETTEXTCOLOR:
    fontColor := PEMRSetTextColor(Rec)^.crColor;
  EMR_SETTEXTALIGN:
    fontAlign := PEMRSetTextAlign(Rec)^.iMode;
  EMR_EXTTEXTOUTW:
    {$ifdef FPC}
    Ref.DrawText(PEMRExtTextOutW(Rec)^);
    {$else}
    Ref.DrawText(PEMRExtTextOut(Rec)^);
    {$endif}
  EMR_MOVETOEX:
    move := PEMRMoveToEx(Rec)^.ptl;
  EMR_LINETO: begin
      with PEMRLineTo(Rec)^.ptl do
        Ref.gdip.DrawLine(Ref.graphics,pen,Move.X,Move.Y,X,Y);
      move := PEMRLineTo(Rec)^.ptl;
    end;
  EMR_RECTANGLE: begin
      NormalizeRect(PEMRRectangle(Rec)^.rclBox);
      if brush<>0 then
        with PEMRRectangle(Rec)^.rclBox do
          Ref.gdip.FillRectangle(Ref.graphics,brush,Left,Top,Right-Left,Bottom-Top);
      with PEMRRectangle(Rec)^.rclBox do
        Ref.gdip.DrawRectangle(Ref.graphics,pen,Left,Top,Right-Left,Bottom-Top);
    end;
  EMR_ROUNDRECT: // perform RoundRect by hand -> just say: GDI+ does not work!
    with PEMRRoundRect(Rec)^ do begin
      NormalizeRect(rclBox);
      Ref.gdip.CreatePath(fmAlternate,path);
      Ref.gdip.AddPathArc(path,rclBox.Left,rclBox.Top,szlCorner.cx,szlCorner.cy,180,90);
      Ref.gdip.AddPathArc(path,rclBox.Right-szlCorner.cx,rclBox.Top,szlCorner.cx,szlCorner.cy,270,90);
      Ref.gdip.AddPathArc(path,rclBox.Right-szlCorner.cx,rclBox.Bottom-szlCorner.cy,szlCorner.cx,szlCorner.cy,0,90);
      Ref.gdip.AddPathArc(path,rclBox.Left,rclBox.Bottom-szlCorner.cy,szlCorner.cx,szlCorner.cy,90,90);
      Ref.gdip.ClosePath(path);
      if brush<>0 then
        Ref.gdip.FillPath(Ref.graphics,brush,path);
      if pen<>0 then
        Ref.gdip.DrawPath(Ref.graphics,pen,path);
      Ref.gdip.DeletePath(path);
    end;
  EMR_ELLIPSE: begin
      NormalizeRect(PEMREllipse(Rec)^.rclBox);
      if brush<>0 then
        with PEMREllipse(Rec)^.rclBox do
          Ref.gdip.FillEllipse(Ref.graphics, brush, Left,Top,Right-Left,Bottom-Top);
      with PEMREllipse(Rec)^.rclBox do
        Ref.gdip.DrawEllipse(Ref.graphics, pen, Left,Top,Right-Left,Bottom-Top);
    end;
  EMR_POLYGON:
    with PEMRPolygon(Rec)^ do begin
      if brush<>0 then
        Ref.gdip.FillPolygon(Ref.graphics,Brush,@aptl,cptl,fmAlternate);
      if pen<>0 then
        Ref.gdip.DrawPolygon(Ref.graphics,Pen,@aptl,cptl);
    end;
  EMR_POLYGON16:
    with PEMRPolygon16(Rec)^ do begin
      Points16To32(@apts,P32,cpts);
      if brush<>0 then
        Ref.gdip.FillPolygon(Ref.graphics,Brush,P32,cpts,fmAlternate);
      if pen<>0 then
        Ref.gdip.DrawPolygon(Ref.graphics,Pen,P32,cpts);
      freemem(P32);
    end;
  EMR_POLYLINE:
    with PEMRPolyLine(Rec)^ do begin
      Ref.gdip.DrawLines(Ref.graphics,Pen,@aptl,cptl);
      move := aptl[cptl-1];
    end;
  EMR_POLYLINE16:
    with PEMRPolyLine16(Rec)^ do begin
      Points16To32(@apts,P32,cpts);
      Ref.gdip.DrawLines(Ref.graphics,Pen,P32,cpts);
      {$ifdef FPC}
      move := PPointL(PAnsiChar(P32)+(cpts-1)*8)^;
      {$else}
      move := PPoint(PAnsiChar(P32)+(cpts-1)*8)^;
      {$endif}
      FreeMem(P32);
    end;
  EMR_POLYBEZIER:
     with PEMRPolyBezier(Rec)^ do begin
       Ref.gdip.DrawCurve(Ref.graphics,Pen,@aptl,cptl);
       move := aptl[cptl-1];
     end;
  EMR_POLYBEZIER16:
    with PEMRPolyBezier16(Rec)^ do begin
      Points16To32(@apts,P32,cpts);
      Ref.gdip.DrawCurve(Ref.graphics,Pen,P32,cpts);
      {$ifdef FPC}
      move := PPointL(PAnsiChar(P32)+(cpts-1)*8)^;
      {$else}
      move := PPoint(PAnsiChar(P32)+(cpts-1)*8)^;
      {$endif}
      FreeMem(P32);
    end;
  EMR_BITBLT: begin
      NormalizeRect(PEMRBitBlt(Rec)^.rclBounds);
      with PEMRBitBlt(Rec)^ do // only handle RGB bitmaps (no palette)
        if (offBmiSrc<>0) and (offBitsSrc<>0) and (iUsageSrc=DIB_RGB_COLORS) then
          Ref.DrawBitmap(xSrc,ySrc,cxDest,cyDest, xDest,yDest,cxDest,cyDest,
            pointer(PAnsiChar(Rec)+offBmiSrc),pointer(PAnsiChar(Rec)+offBitsSrc)) else
      case PEMRBitBlt(Rec)^.dwRop of // we only handle PATCOPY = fillrect
        PATCOPY: with PEMRBitBlt(Rec)^.rclBounds do
          Ref.gdip.FillRectangle(Ref.graphics, brush,Left,Top,Right-Left+1,Bottom-Top+1);
      end;
    end;
  EMR_STRETCHBLT: begin
      NormalizeRect(PEMRStretchBlt(Rec)^.rclBounds);
      with PEMRStretchBlt(Rec)^ do // only handle RGB bitmaps (no palette)
        if (offBmiSrc<>0) and (offBitsSrc<>0) and (iUsageSrc=DIB_RGB_COLORS) then
          Ref.DrawBitmap(xSrc,ySrc,cxSrc,cySrc, xDest,yDest,cxDest,cyDest,
            pointer(PAnsiChar(Rec)+offBmiSrc),pointer(PAnsiChar(Rec)+offBitsSrc)) else
      case PEMRStretchBlt(Rec)^.dwRop of // we only handle PATCOPY = fillrect
        PATCOPY: with PEMRStretchBlt(Rec)^.rclBounds do
          Ref.gdip.FillRectangle(Ref.graphics,brush,Left,Top,Right-Left+1,Bottom-Top+1);
      end;
    end;
  EMR_STRETCHDIBITS: begin
      NormalizeRect(PEMRStretchDIBits(Rec)^.rclBounds);
      with PEMRStretchDIBits(Rec)^ do
        if (offBmiSrc<>0) and (offBitsSrc<>0) and (iUsageSrc=DIB_RGB_COLORS) then
          Ref.DrawBitmap(xSrc,ySrc,cxSrc,cySrc, xDest,yDest,cxDest,cyDest,
            pointer(PAnsiChar(Rec)+offBmiSrc),pointer(PAnsiChar(Rec)+offBitsSrc));
    end;
  EMR_INTERSECTCLIPRECT: begin
      with PEMRIntersectClipRect(Rec)^.rclClip do
        Ref.Gdip.SetClipRectI(Ref.graphics,Left,Top,Right-Left,Bottom-Top,cmIntersect);
    end;
  EMR_EXCLUDECLIPRECT: begin
      with PEMRExcludeClipRect(Rec)^.rclClip do
        Ref.Gdip.SetClipRectI(Ref.graphics,Left,Top,Right-Left,Bottom-Top,cmExclude);
  end;
  EMR_OFFSETCLIPRGN, EMR_SELECTCLIPPATH, EMR_EXTSELECTCLIPRGN: begin
      //Clipping functionality is not fully implemented yet, so when any of the more advanced clipping operations are
      //encountered the clipping region is simply discarded
      with PEMRExtSelectClipRgn(Rec)^ do
        Ref.Gdip.ResetClip(Ref.graphics);
    end
  end;
  case Rec^.iType of
    EMR_HEADER, EMR_SETWINDOWEXTEX, EMR_SETWINDOWORGEX,
    EMR_SETVIEWPORTEXTEX, EMR_SETVIEWPORTORGEX:
      Ref.ScaleMatrix(0);
  end;
end;

procedure TGDIPlusFull.DrawAntiAliased(Source: TMetafile; Dest: HDC;
  R: TRect; aSmoothing: TSmoothingMode; aTextRendering: TTextRenderingHint);
var Img, graphics: THandle;
begin
  Img := ConvertToEmfPlus(Source,Dest,aSmoothing,aTextRendering);
  if Img=0 then
    inherited else
  try
    CreateFromHDC(Dest,graphics);
    DrawImageRect(graphics,Img,R.Left,R.top,R.Right-R.Left,R.Bottom-R.Top);
  finally
    DeleteGraphics(graphics);
    DisposeImage(Img);
  end;
end;

procedure TGDIPlusFull.DrawAntiAliased(Source: TMetafile; Dest: HDC;
  const dst, src: TRect; attributes: TImageAttributes; u: TUnit;
  aSmoothing: TSmoothingMode; aTextRendering: TTextRenderingHint);
var Img, graphics: THandle;
    ia: TGpipImageAttributes;
begin
  Img := ConvertToEmfPlus(Source,Dest,aSmoothing,aTextRendering);
  if Img=0 then
    inherited DrawAntiAliased(Source,Dest,dst,aSmoothing,aTextRendering) else begin
    if Assigned(attributes) then
      ia := attributes.fAttr else
      ia:= nil;
    try
      CreateFromHDC(Dest,graphics);
      DrawImageRectRect(graphics,Img,
        dst.Left,dst.top,dst.Right-dst.Left,dst.Bottom-dst.Top,
        src.Left,src.top,src.Right-src.Left,src.Bottom-src.Top, u, ia);
    finally
      DeleteGraphics(graphics);
      DisposeImage(Img);
    end;
  end;
end;

function TGDIPlusFull.MetaFileToStream(Source: TMetafile): IStream;
var Length: cardinal;
    hGlobal: THandle;
begin
  Length := GetEnhMetaFileBits(Source.Handle, 0, nil);
  hGlobal := GlobalAlloc(GMEM_MOVEABLE, Length+128);
  if GetEnhMetaFileBits(Source.Handle, Length, GlobalLock(hGlobal))<>length then
    assert(false);
  GlobalUnlock(hGlobal);
  CreateStreamOnHGlobal(hGlobal, true, result); // fDeleteOnRelease=true
end;

function TGDIPlusFull.ConvertToEmfPlus(Source: TMetafile; Dest: HDC;
  aSmoothing: TSmoothingMode; aTextRendering: TTextRenderingHint): THandle;
var Ref: TGDIPlusEnum;
    flag: BOOL;
    EmfPlusImg: integer;
    pstm: IStream;
    Img: TSynPicture;
    GR: TGdipRect;
begin
  result := 0;
  if not Exists or (Source=nil) or (Dest=0) then
    exit;
  GR.X := 0;
  GR.Y := 0;
  GR.Width := Source.Width;
  GR.Height := Source.Height;
  fillchar(Ref,sizeof(Ref),0);
  if NativeConvertToEmfPlus then begin
    // let GDI+ 1.1 make the conversion
    pstm := MetaFileToStream(Source);
    try
      Img := TSynPicture.Create;
      try
        if Img.LoadFromIStream(pstm)<>stOk then
          exit;
        flag := false;
        CreateFromHDC(Dest,Ref.graphics);
        SetSmoothingMode(Ref.graphics,aSmoothing);
        SetTextRenderingHint(Ref.graphics,aTextRendering);
        try
          if fConvertToEmfPlus(Ref.graphics,Img.NativeImage,flag,
            etEmfPlusOnly,nil,EmfPlusImg)=stOk then
              result := EmfPlusImg;
        finally
          DeleteGraphics(Ref.graphics);
        end;
      finally
        Img.Free;
      end;
    finally
      pstm :=  nil; // will also release hGlobal
    end;
  end else begin
    // our manual (and not 100% complete yet) conversion
    Ref.UseDrawString := ForceUseDrawString;
    with Ref.DC[0] do begin
      Int64(WinSize) := PInt64(@GR.Width)^;
      ViewSize := WinSize;
    end;
    Ref.gdip := self;
    Ref.destDC := CreateCompatibleDC(Dest);
    RecordMetafile(Ref.destDC,etEmfPlusOnly,@GR,uPixel,nil,result);
    CreateFromImage(result,Ref.graphics);
    SetSmoothingMode(Ref.graphics,aSmoothing);
    SetTextRenderingHint(Ref.graphics,aTextRendering);
    try
      EnumEnhMetaFile(Ref.destDC,Source.Handle,@EnumEMFFunc,@Ref,TRect(GR));
    finally
      Ref.EnumerateEnd;
    end;
  end;
end;

function TGDIPlusFull.getNativeConvertToEmfPlus: boolean;
begin
  if (self=nil) or ForceInternalConvertToEmfPlus then
    result := false else begin
    if not fConvertToEmfPlusTested then begin
      fConvertToEmfPlusTested := true;
      fConvertToEmfPlus := GetProcAddress(fHandle,'GdipConvertToEmfPlus');
    end;
    result := (@fConvertToEmfPlus<>nil);
  end;
end;


{ TGDIPlusEnum }

procedure TGDIPlusEnum.CreatePenObj(index: integer; Color, Width, Style: Cardinal);
begin
  if cardinal(index)<=Cardinal(high(Obj)) then
  with Obj[index] do begin
    kind := OBJ_PEN;
    gdip.CreatePen(ColorRefToARGB(Color),Width,uWorld,handle);
    if Style in [PS_DASH..PS_DASHDOTDOT] then
      gdip.SetPenDashStyle(handle,PS_DASH); // force PS_DASH on GDI+
    DC[nDC].pen := handle;
  end;
end;

procedure TGDIPlusEnum.DeleteObj(index: integer);
begin
  if cardinal(index)<cardinal(length(Obj)) then
  with Obj[index] do begin
    if handle<>0 then
    case kind of
    OBJ_EXTPEN, OBJ_PEN: begin
      gdip.DeletePen(handle);
      with DC[nDC] do
        if pen=handle then
          pen := 0;
    end;
    OBJ_BRUSH, OBJ_FONT:
      ; // brushs and font are taken from Cached*[] -> deleted in EnumerateEnd
    else Exit;
    end;
    handle := 0;
    kind := 0;
  end;
end;

procedure TGDIPlusEnum.DrawBitmap(xs, ys, ws, hs, xd, yd, wd, hd: integer;
  Bmi, bits: pointer);
var Img: THandle;
begin
  if not gdip.Exists or (graphics=0) then
    exit;
  if gdip.CreateBitmapFromGdiDib(Bmi,bits,Img)=stOk then
  try
    gdip.DrawImageRectRect(graphics, Img, xd,yd,wd,hd, xs,ys,ws,hs);
  finally
    gdip.DisposeImage(Img);
  end;
end;

function Ceil(const X: single): integer; // avoid dependency to Math.pas unit
begin
  result := Integer(Trunc(X));
  if Frac(X)>0 then
    inc(result);
end;

procedure TGDIPlusEnum.DrawText(var EMR: TEMRExtTextOut);
var DF,RF: TGdipRectF;
    flags, i: integer;
    matrixOrg: THandle;
    Text: PWideChar;
    DrawString: boolean;
    F32: array of TGdipPointF;
    P: TPoint;
    Siz: TSize;
    PrevClipRgn: THandle;
begin
  with DC[nDC], EMR do begin
    Text := PWideChar(PAnsiChar(@EMR)+emrtext.offString);
    DrawString := false;
    if UseDrawString then // DrawDriverString() does not implement font fall-back
      for i := 0 to emrtext.nChars-1 do
        if Text[i]>#$5ff then begin
          DrawString := true;
          break;
        end;
    SetLength(F32,emrtext.nChars);
    if DrawString or (emrtext.offDx=0) then begin // if emf content is not correct -> best guess
      gdip.MeasureString(graphics,Text,emrtext.nChars,font,@GdipRectFNull,0,@RF,nil,nil);
      Siz.cx := Ceil(RF.Width);
      flags := 5; // RealizedAdvance is set -> F32 = 1st glyph position
    end else begin
      Siz.cx := DXTextWidth(pointer(PAnsiChar(@EMR)+emrtext.offDx),emrText.nChars);
      if emrtext.fOptions and ETO_GLYPH_INDEX<>0 then
        flags := 0 else // F32 is an array of glyph indexes
        flags := 1; // F32 is an array of every individual glyph position
    end;
    //Determine the text bounding rectangle
    DF.Width := Siz.cx;
    DF.Height := fontspec.descent+fontspec.ascent;
    DF.X := emrtext.ptlReference.X;
    DF.Y := emrtext.ptlReference.Y;
    if fontAlign and TA_CENTER=TA_CENTER then
      DF.X := DF.X-Siz.cx/2 else
    if fontAlign and TA_RIGHT<>0 then
      DF.X := DF.X-Siz.cx;
    if fontAlign and TA_BASELINE<>0 then
      DF.Y := DF.Y-fontspec.ascent else
    if fontAlign and TA_BOTTOM<>0 then
      DF.Y := RF.Y-fontspec.descent-fontspec.ascent;
    //Determine the text baseline start coordinate
    RF := DF;
    RF.Y := DF.Y + fontspec.ascent;
    //Determine the glyph coordinates
    if DrawString or (emrtext.offDx=0) then
      PGdipPointF(F32)^ := PGdipPointF(@RF)^ else
      SetPositions(RF.X,RF.Y,pointer(F32),pointer(PAnsiChar(@EMR)+emrtext.offDx),emrText.nChars);
    NormalizeRect(emrtext.rcl);
    if (emrtext.fOptions and ETO_CLIPPED<>0) then begin
      //Update the clipping region.  The previous clipping region is saved so it can be restored later
      gdip.CreateRegion(PrevClipRgn);
      gdip.GetClip(graphics, PrevClipRgn);
      gdip.SetClipRectI(graphics, emrtext.rcl.Left, emrtext.rcl.Top,
        emrtext.rcl.Right-emrtext.rcl.Left, emrtext.rcl.Bottom-emrtext.rcl.Top, cmIntersect);
    end;
    //Fill the clipping rectangle if applicable
    if (emrtext.fOptions and ETO_OPAQUE<>0) then
      gdip.FillRectangle(graphics,GetCachedSolidBrush(bkColor),
        emrtext.rcl.Left, emrtext.rcl.Top,
        emrtext.rcl.Right-emrtext.rcl.Left, emrtext.rcl.Bottom-emrtext.rcl.Top);
    if fontspec.angle<>0 then begin // manual rotate text -> GDI+ does not work :(
      gdip.CreateMatrix(matrixOrg);
      gdip.GetWorldTransform(graphics,matrixOrg);
      gdip.TranslateTransform(graphics,
        emrtext.ptlReference.X,emrtext.ptlReference.Y);
      gdip.RotateTransform(graphics,-fontspec.angle);
      gdip.TranslateTransform(graphics,-emrtext.ptlReference.X,-emrtext.ptlReference.Y);
    end;
    //If the background is opaque it must be filled with the background colour
    if BkMode = OPAQUE then
      gdip.FillRectangle(graphics,GetCachedSolidBrush(bkColor),Trunc(DF.X),Trunc(DF.Y),
        Ceil(DF.Width),Ceil(DF.Height));
    if DrawString then
      gdip.DrawString(graphics,Text,emrtext.nChars,font,@DF,0,GetCachedSolidBrush(fontColor)) else
      gdip.DrawDriverString(graphics,Text,emrtext.nChars,font,GetCachedSolidBrush(fontColor),pointer(F32),flags,0);
    // Draw*String doesn't handle those -> GDI+ does not work :(
    if fontspec.underline or fontSpec.strikeout then begin
      Siz.cy := fontspec.ascent shr 4;
      if Siz.cy<1 then
        Siz.cy := 1;
      P.X := Trunc(RF.X);
      P.Y := Trunc(RF.Y);
      if fontSpec.strikeout then
        dec(P.Y,(fontspec.ascent*6) shr 4) else
        inc(P.Y,Siz.cy);
      if Siz.cy<4 then
        Siz.cy := 1 else
        Siz.cy := Siz.cy shr 1;
      gdip.DrawLine(graphics,GetCachedPen(fontColor,Siz.cy),
        P.X, P.Y, P.X+Siz.Cx-1, P.Y);
    end;
    if fontspec.angle<>0 then begin
      gdip.SetWorldTransform(graphics,matrixOrg); // restore previous
      gdip.DeleteMatrix(matrixOrg);
    end;
    if (emrtext.fOptions and ETO_CLIPPED<>0) then begin
      //Restore the clipping region
      gdip.SetClipRegion(graphics, PrevClipRgn, cmReplace);
      gdip.DeleteRegion(PrevClipRgn);
    end;
  end;
end;

procedure TGDIPlusEnum.EnumerateEnd;
var i: integer;
begin
  for i := 1 to nDC do
    gdip.DeleteRegion(DC[nDC].ClipRegion);
  for i := 0 to high(obj) do
    DeleteObj(i);
  for i := 0 to high(CachedPen) do
    gdip.DeletePen(CachedPen[i].handle);
  for i := 0 to high(CachedBrush) do
    gdip.DeleteBrush(CachedBrush[i].handle);
  for i := 0 to high(CachedFont) do
    gdip.DeleteFont(CachedFont[i].handle);
  gdip.DeleteMatrix(destMatrix);
  Finalize(obj);
  DeleteDC(destDC);
  gdip.DeleteGraphics(graphics);
end;

function AnsiICompW(u1, u2: PWideChar): integer;
var c1,c2: integer;
begin // faster than lstrcmpiW
  repeat
    c1 := integer(u1^);
    c2 := integer(u2^);
    result := c1-c2;
    if result<>0 then begin
      if (c1>255) or (c2>255) then exit;
      if c1 in [Ord('a')..Ord('z')] then dec(c1,32);
      if c2 in [Ord('a')..Ord('z')] then dec(c2,32);
      result := c1-c2;
      if result<>0 then exit;
    end;
    if (c1=0) or (c2=0) then break;
    inc(u1);
    inc(u2);
  until false;
end;

function TGDIPlusEnum.GetCachedFontIndex(aLogFont: PLogFontW): integer;
var HF: HFONT;
    TM: TTextMetric;
    Old: HGDIOBJ;
    LF: TLogFontW;
    n: integer;
begin
  // DrawDriverString error with underline or strikeout -> GDI+ does not work :(
  move(aLogFont^,LF,sizeof(LF)); // faster than LF := LogFont
  LF.lfUnderline := 0;
  LF.lfStrikeOut := 0;
  // search if not already in cache
  n := length(CachedFont);
  for result := 0 to n-1 do
  with CachedFont[result] do
    if CompareMem(@LogFont,@LF,sizeof(TLogFontW)-LF_FACESIZE) and
       (AnsiICompW(LogFont.lfFaceName,LF.lfFaceName)=0) and
       (objfont.underline=boolean(aLogFont^.lfUnderline)) and
       (objfont.strikeout=boolean(aLogFont^.lfStrikeOut)) then
      exit;
  // not available in cache -> create now
  result := n;
  SetLength(CachedFont,result+1);
  with CachedFont[result] do begin
    LogFont := LF;
    gdip.CreateFontFromLogfont(DestDC,@LogFont,handle);
    HF := CreateFontIndirectW(LogFont);
    Old := SelectObject(destDC,HF);
    GetTextMetrics(destDC,TM);
    SelectObject(destDC,Old);
    DeleteObject(HF);
    objfont.ascent := TM.tmAscent;
    objfont.descent := TM.tmDescent;
    objfont.angle := LogFont.lfOrientation div 10;
    objfont.underline := boolean(aLogFont^.lfUnderline);
    objfont.strikeout := boolean(aLogFont^.lfStrikeOut);
  end;
end;

function TGDIPlusEnum.GetCachedPen(color, width: Cardinal): THandle;
var i,n: integer;
begin
  for i := 0 to high(CachedPen) do
    if (CachedPen[i].color=color) and (CachedPen[i].width=width) then begin
      result := CachedPen[i].handle;
      exit;
    end;
  gdip.CreatePen(ColorRefToARGB(color),width,uPixel,result);
  n := length(CachedPen);
  SetLength(CachedPen,n+1);
  CachedPen[n].color := color;
  CachedPen[n].width := width;
  CachedPen[n].handle := result;
end;

function TGDIPlusEnum.GetCachedSolidBrush(color: Cardinal): THandle;
var i,n: integer;
begin
  for i := 0 to high(CachedBrush) do
    if CachedBrush[i].color=color then begin
      result := CachedBrush[i].handle;
      exit;
    end;
  gdip.CreateSolidFill(ColorRefToARGB(color),result);
  n := length(CachedBrush);
  SetLength(CachedBrush,n+1);
  CachedBrush[n].color := color;
  CachedBrush[n].handle := result;
end;

procedure TGDIPlusEnum.RestoreDC;
begin
  assert(nDC>0);
  with DC[nDC] do begin
    gdip.SetClipRegion(graphics, ClipRegion, cmReplace);
    gdip.DeleteRegion(ClipRegion);
  end;
  dec(nDC);
  ScaleMatrix(0);
//  with DC[nDC] do
//    Gdip.SetWorldTransform(Graphics,destMatrix);
end;

procedure TGDIPlusEnum.SaveDC;
begin
  Assert(nDC<high(DC));
  DC[nDC+1] := DC[nDC];
  inc(nDC);
  with DC[nDC] do begin
    gdip.CreateRegion(ClipRegion);
    gdip.GetClip(graphics, ClipRegion);
  end;
end;

procedure TGDIPlusEnum.ScaleMatrix(matrixOrg: THandle);
var P: TPoint;
    matrix: THandle;
begin
  with DC[nDC] do begin
    P.X := MulDiv(ViewOrg.x, WinSize.cx, ViewSize.cx) - WinOrg.x;
    P.Y := MulDiv(ViewOrg.y, WinSize.cy, ViewSize.cy) - WinOrg.y;
    Gdip.CreateMatrix2(ViewSize.cx/WinSize.cx,0,0,ViewSize.cy/WinSize.cy,
      P.X,P.Y,matrix);
    Gdip.MultiplyMatrix(matrix,destMatrix);
    if matrixOrg<>0 then
      Gdip.MultiplyMatrix(matrix,matrixOrg);
    Gdip.SetWorldTransform(Graphics,matrix);
    Gdip.DeleteMatrix(matrix);
  end;
end;

procedure TGDIPlusEnum.SetCachedFontSpec(aHandle: THandle; var aObjFont: TFontSpec);
var i: integer;
begin
  for i := 0 to high(CachedFont) do
    if CachedFont[i].handle=aHandle then begin
      aObjFont := CachedFont[i].objfont;
      Exit;
    end;
  Int64(aObjFont) := 0;
end;

procedure GdipLock;
begin
  EnterCriticalSection(GdipCS);
end;

procedure GdipUnlock;
begin
  LeaveCriticalSection(GdipCS);
end;

initialization
  Windows.InitializeCriticalSection(GdipCS);
{$ifndef NOTSYNPICTUREREGISTER}
  Gdip.RegisterPictures; // will initialize the Gdip library if necessary
//  GdipTest('d:\Data\Pictures\Sample Pictures\Tree.jpg');
{$endif}

finalization
  Gdip.Free;
  Windows.DeleteCriticalSection(GdipCS);
end.
