   {%mainunit orca_scene2d.pas}

{*************************************************************************
                Copyright (c) PilotLogic Software House

  Package pl_ORCA
  This file is part of CodeTyphon Studio (https://www.pilotlogic.com/)

 ***** BEGIN LICENSE BLOCK *****
 * Version: LGPL-2.1 with link exception (Modified LGPL)
 *
 * The contents of this file are subject to the 
 * GNU LIBRARY GENERAL PUBLIC LICENSE Version 2.1 (the "License")
 * with the following modification:
 * 
 * As a special exception, the copyright holders of this library give you
 * permission to link this library with independent modules to produce an
 * executable, regardless of the license terms of these independent modules,
 * and to copy and distribute the resulting executable under terms of your choice,
 * provided that you also meet, for each linked independent module, the terms
 * and conditions of the license of that module. An independent module is a
 * module which is not derived from or based on this library. If you modify this
 * library, you may extend this exception to your version of the library, but
 * you are not obligated to do so. If you do not wish to do so, delete this
 * exception statement from your version.
 *
 * 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.
 *
 * ***** END LICENSE BLOCK *****
 
***************************************************************************}


Type

   TD2FilterGdiPlus = Class(TD2Filter)
   Published
      Class Function GetFileTypes: String; Override;
      Class Function GetImageSize(Const AFileName: String): TD2Point; Override;
      Function LoadFromFile(Const AFileName: String; Const Rotate: Single; Var Bitmap: TD2Bitmap): Boolean; Override;
      Function SaveToFile(Const AFileName: String; Var Bitmap: TD2Bitmap; Const Params: String = ''): Boolean; Override;
      Function LoadFromStream(Const AStream: TStream; Var Bitmap: TD2Bitmap): Boolean; Override;
      Function SaveToStream(Const AStream: TStream; Var Bitmap: TD2Bitmap; Const Format: String; Const Params: String = ''): Boolean; Override;
      Function LoadThumbnailFromFile(Const AFileName: String; Const AFitWidth, AFitHeight: Single; Const UseEmbedded: Boolean; Var Bitmap: TD2Bitmap): Boolean; Override;
   End;

   TD2CanvasGdiPlus = Class(TD2Canvas)
   Private
      FBufferHandle: Cardinal;
      FBitmapInfo: TBitmapInfo;
      FGraphics: TGPGraphics;
      FGPPen: TGPPen;
      FGPPenBrush: TGPBrush;
      FGPBrush: TGPBrush;
      FGPFamily: TGPFontFamily;
      FFontCollection: TGPPrivateFontCollection;
      FFontScale: Single;
   Protected
      Procedure UpdateBitmap(ABitmap: TD2Bitmap);
      Procedure DestroyBitmap(Sender: TObject);
      Procedure ApplyFill(ARect: TD2Rect; Const AOpacity: Single);
      Procedure ApplyStroke(ARect: TD2Rect; Const AOpacity: Single);
      Procedure FontChanged(Sender: TObject); Override;
      Procedure IntFillPath(P: TGPGraphicsPath; R: TD2Rect; Opacity: Single);
      Procedure IntFillRect(R: TD2Rect; Opacity: Single);
   Public
      Constructor Create(Const AWidth, AHeight: Integer); Override;
      Constructor CreateFromBitmap(Const ABitmap: TD2Bitmap); Override;
      Destructor Destroy; Override;
      Class Function GetBitmapScanline(Bitmap: TD2Bitmap; y: Integer): PD2ColorArray; Override;
      Procedure FreeBuffer; Override;
      Procedure ResizeBuffer(Const AWidth, AHeight: Integer); Override;
      Procedure FlushBuffer(Const X, Y: Integer; Const DC: Cardinal); Override;
      Procedure FlushBufferRect(Const X, Y: Integer; Const DC: Cardinal; Const ARect: TD2Rect); Override;
      Procedure Clear(Const Color: Cardinal); Override;
      Procedure ClearRect(Const ARect: TD2Rect; Const AColor: TD2Color = 0); Override;
      Procedure SetMatrix(Const M: TD2Matrix); Override;
      Procedure MultyMatrix(Const M: TD2Matrix); Override;
      Function SaveCanvas: Cardinal; Override;
      Procedure RestoreCanvas(Const AState: Cardinal); Override;
      Procedure SetClipRects(Const ARects: Array Of TD2Rect); Override;
      Procedure IntersectClipRect(Const ARect: TD2Rect); Override;
      Procedure ExcludeClipRect(Const ARect: TD2Rect); Override;
      Procedure ResetClipRect; Override;
      Procedure DrawLine(Const APt1, APt2: TD2Point; Const AOpacity: Single); Override;
      Procedure FillRect(Const ARect: TD2Rect; Const xRadius, yRadius: Single; Const ACorners: TD2Corners; Const AOpacity: Single; Const ACornerType: TD2CornerType = d2CornerRound); Override;
      Procedure DrawRect(Const ARect: TD2Rect; Const xRadius, yRadius: Single; Const ACorners: TD2Corners; Const AOpacity: Single; Const ACornerType: TD2CornerType = d2CornerRound); Override;
      Procedure FillEllipse(Const ARect: TD2Rect; Const AOpacity: Single); Override;
      Procedure DrawEllipse(Const ARect: TD2Rect; Const AOpacity: Single); Override;
      Function LoadFontFromStream(AStream: TStream): Boolean; Override;
      Procedure FillText(Const ARect, AClipRect: TD2Rect; Const AText: WideString; Const WordWrap: Boolean; Const AOpacity: Single; Const ATextAlign: TD2TextAlign; Const AVTextAlign: TD2TextAlign = d2TextAlignCenter); Override;
      Procedure MeasureText(Var ARect: TD2Rect; AClipRect: TD2Rect; Const AText: WideString; Const WordWrap: Boolean; Const ATextAlign: TD2TextAlign; Const AVTextAlign: TD2TextAlign = d2TextAlignCenter); Override;
      Function TextToPath(Path: TD2PathData; Const ARect: TD2Rect; Const AText: WideString; Const WordWrap: Boolean; Const ATextAlign: TD2TextAlign; Const AVTextAlign: TD2TextAlign = d2TextAlignCenter): Boolean; Override;
      Function PtInPath(Const APoint: TD2Point; Const ARect: TD2Rect; Const APath: TD2PathData): Boolean; Override;
      Procedure FillPath(Const APath: TD2PathData; Const ARect: TD2Rect; Const AOpacity: Single); Override;
      Procedure DrawPath(Const APath: TD2PathData; Const ARect: TD2Rect; Const AOpacity: Single); Override;
      Procedure DrawBitmap(Const ABitmap: TD2Bitmap; Const SrcRect, DstRect: TD2Rect; Const AOpacity: Single; Const HighSpeed: Boolean = False); Override;
      Procedure DrawThumbnail(Const ABitmap: TD2Bitmap; Const Width, Height: Single); Override;

   End;

   //==================================================================
   //==================================================================


Procedure InitGDIP;
Begin
   If GdipLibrary <> 0 Then
      Exit;
   LoadGdiplus;
   StartupInput.DebugEventCallback := nil;
   StartupInput.SuppressBackgroundThread := False;
   StartupInput.SuppressExternalCodecs := False;
   StartupInput.GdiplusVersion := 1;
   GdiplusStartup(gdiplusToken, @StartupInput, nil);
End;

Procedure FreeGDIP;
Begin
   If GdipLibrary = 0 Then
      Exit;
   If assigned(GenericSansSerifFontFamily) Then
      GenericSansSerifFontFamily.Free;
   If assigned(GenericSerifFontFamily) Then
      GenericSerifFontFamily.Free;
   If assigned(GenericMonospaceFontFamily) Then
      GenericMonospaceFontFamily.Free;
   If assigned(GenericTypographicStringFormatBuffer) Then
      GenericTypographicStringFormatBuffer.Free;
   If assigned(GenericDefaultStringFormatBuffer) Then
      GenericDefaultStringFormatBuffer.Free;
   GdiplusShutdown(gdiplusToken);
   FreeGdiplus;
End;

Function GPRectFromRect(Const R: TD2Rect): TGPRectF;
Begin
   Result.X := R.Left;
   Result.Y := R.Top;
   Result.Width := R.Right - R.Left;
   Result.Height := R.Bottom - R.Top;
End;

Function GPRectFromRectTruncated(Const R: TD2Rect): TGPRectF;
Begin
   Result.X := Trunc(R.Left);
   Result.Y := Trunc(R.Top);
   Result.Width := Trunc(R.Right) - Trunc(R.Left);
   Result.Height := Trunc(R.Bottom) - Trunc(R.Top);
End;

Type

   TExStreamAdapter = Class(TInterfacedObject, IStream)
   Public
      FStream: TStream;
      FOwnership: TStreamOwnership;
   Public
      Constructor Create(Stream: TStream; Ownership: TStreamOwnership = soReference);
      Destructor Destroy; Override;
      Function Read(pv: Pointer; cb: DWORD; pcbRead: PDWORD): HRESULT; Virtual; Stdcall;
      Function Write(pv: Pointer; cb: DWORD; pcbWritten: PDWORD): HRESULT; Virtual; Stdcall;
      Function Seek(dlibMove: LargeInt; dwOrigin: DWORD; out libNewPosition: LargeUInt): HResult; Virtual; Stdcall;
      Function SetSize(libNewSize: LargeUInt): HRESULT; Virtual; Stdcall;
      Function CopyTo(stm: IStream; cb: LargeUInt; out cbRead: LargeUInt; out cbWritten: LargeUInt): HRESULT; Virtual; Stdcall;
      Function Commit(grfCommitFlags: DWORD): HRESULT; Virtual; Stdcall;
      Function Revert: HResult; Virtual; Stdcall;
      Function LockRegion(libOffset: LargeUInt; cb: LargeUInt; dwLockType: DWORD): HRESULT; Virtual; Stdcall;
      Function UnlockRegion(libOffset: LargeUInt; cb: LargeUInt; dwLockType: DWORD): HRESULT; Virtual; Stdcall;
      Function Stat(out statstg: TStatStg; grfStatFlag: DWORD): HRESULT; Virtual; Stdcall;
      Function Clone(out stm: IStream): HResult; Virtual; Stdcall;
      Property Stream: TStream read FStream;
      Property StreamOwnership: TStreamOwnership read FOwnership write FOwnership;
   End;

   //====================== TExStreamAdapter =====================================

Constructor TExStreamAdapter.Create(Stream: TStream; Ownership: TStreamOwnership);
Begin
   Inherited Create;
   FStream := Stream;
   FOwnership := Ownership;
End;

Destructor TExStreamAdapter.Destroy;
Begin
   If FOwnership = soOwned Then
   Begin
      FStream.Free;
      FStream := nil;
   End;
   Inherited Destroy;
End;

Function TExStreamAdapter.SetSize(libNewSize: LargeUInt): HRESULT;
Begin
   Try
      FStream.Size := libNewSize;
      If libNewSize <> FStream.Size Then
         Result := E_FAIL
      Else
         Result := S_OK;
   Except
      Result := E_UNEXPECTED;
   End;
End;

Function TExStreamAdapter.Read(pv: Pointer; cb: DWORD; pcbRead: PDWORD): HResult;
Var
   xNR: DWORD;
Begin
   Try
      If pv = nil Then
      Begin
         Result := STG_E_INVALIDPOINTER;
         Exit;
      End;

      xNR := FStream.Read(pv^, cb);
      If pcbRead <> nil Then
         pcbRead^ := xNR;
      Result := S_OK;
   Except
      Result := S_FALSE;
   End;
End;

Function TExStreamAdapter.Write(pv: Pointer; cb: DWORD; pcbWritten: PDWORD): HResult;
Var
   xNW: DWORD;
Begin
   Try
      If pv = nil Then
      Begin
         Result := STG_E_INVALIDPOINTER;
         Exit;
      End;
      xNW := FStream.Write(pv^, cb);

      If pcbWritten <> nil Then
         pcbWritten^ := xNW;

      Result := S_OK;
   Except
      Result := STG_E_CANTSAVE;
   End;
End;

Function TExStreamAdapter.Commit(grfCommitFlags: DWORD): HRESULT;
Begin
   Result := S_OK;
End;

Function TExStreamAdapter.LockRegion(libOffset: LargeUInt; cb: LargeUInt; dwLockType: DWORD): HRESULT;
Begin
   Result := STG_E_INVALIDFUNCTION;
End;

Function TExStreamAdapter.Seek(dlibMove: LargeInt; dwOrigin: DWORD; out libNewPosition: LargeUInt): HResult;
Var
   xNPos: LargeInt;
Begin
   Try
      If (dwOrigin < STREAM_SEEK_SET) Or (dwOrigin > STREAM_SEEK_END) Then
      Begin
         Result := STG_E_INVALIDFUNCTION;
         Exit;
      End;
      xNPos := FStream.Seek(dlibMove, dwOrigin);
      If @libNewPosition <> nil Then
         libNewPosition := xNPos;
      Result := S_OK;
   Except
      Result := STG_E_INVALIDPOINTER;
   End;
End;

Function TExStreamAdapter.UnlockRegion(libOffset: LargeUInt; cb: LargeUInt; dwLockType: DWORD): HRESULT;
Begin
   Result := STG_E_INVALIDFUNCTION;
End;


Function TExStreamAdapter.CopyTo(stm: IStream; cb: LargeUInt; out cbRead: LargeUInt; out cbWritten: LargeUInt): HRESULT;
Const
   MaxBufSize = 1024 * 1024;
Var
   Buffer: Pointer;
   BufSize, N, I, R: PtrInt;//Integer;
   BytesRead, BytesWritten, W: LargeInt;
Begin
   Result := S_OK;
   BytesRead := 0;
   BytesWritten := 0;
   Try
      If cb > MaxBufSize Then
         BufSize := MaxBufSize
      Else
         BufSize := PtrInt(cb);
      GetMem(Buffer, BufSize);
      Try
         While cb > 0 Do
         Begin
            If cb > MaxInt Then
               I := MaxInt
            Else
               I := cb;
            While I > 0 Do
            Begin
               If I > BufSize Then
                  N := BufSize
               Else
                  N := I;
               R := FStream.Read(Buffer^, N);
               If R = 0 Then
                  Exit;
               Inc(BytesRead, R);
               W := 0;
               Result := stm.Write(Buffer, R, @W);
               Inc(BytesWritten, W);
               If (Result = S_OK) And (PtrInt(W) <> R) Then
                  Result := E_FAIL;
               If Result <> S_OK Then
                  Exit;
               Dec(I, R);
               Dec(cb, R);
            End;
         End;
      Finally
         FreeMem(Buffer);
         If (@cbWritten <> nil) Then
            cbWritten := BytesWritten;
         If (@cbRead <> nil) Then
            cbRead := BytesRead;
      End;
   Except
      Result := E_UNEXPECTED;
   End;
End;

Function TExStreamAdapter.Revert: HResult;
Begin
   Result := STG_E_REVERTED;
End;

Function DateTimeToFileTime(FileTime: TDateTime): TFileTime;
Var
   LocalFileTime, Ft: TFileTime;
   SystemTime: TSystemTime;
Begin
   Result.dwLowDateTime := 0;
   Result.dwHighDateTime := 0;
   DateTimeToSystemTime(FileTime, SystemTime);
   SystemTimeToFileTime(SystemTime, LocalFileTime);
   LocalFileTimeToFileTime(LocalFileTime, Ft);
   Result := Ft;
End;

Function TExStreamAdapter.Clone(out stm: IStream): HResult;
Begin
   Result := E_NOTIMPL;
End;

Function TExStreamAdapter.Stat(out statstg: TStatStg; grfStatFlag: DWORD): HRESULT;
Begin
   Result := S_OK;
   Try
      If (@statstg <> nil) Then
      Begin
         FillChar(statstg, SizeOf(statstg), 0);
         With statstg Do
         Begin
            dwType := STGTY_STREAM;
            cbSize := Stream.Size;
            mTime := DateTimeToFileTime(now);
            cTime := DateTimeToFileTime(now);
            aTime := DateTimeToFileTime(now);
            grfLocksSupported := LOCK_WRITE;
         End;
      End;
   Except
      Result := E_UNEXPECTED;
   End;
End;
//==================== TD2FilterGdiPlus ===============================

Class Function TD2FilterGdiPlus.GetFileTypes: String;
Begin
   Result := '*.bmp;*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.gif;*.ico';
End;

Class Function TD2FilterGdiPlus.GetImageSize(Const AFileName: String): TD2Point;
Var
   img: TGPImage;
   S: TStream;
   adapter: TExStreamAdapter;
Begin
   S := TFileStream.Create(AFileName, fmOpenRead Or fmShareDenyNone);
   adapter := TExStreamAdapter.Create(S);
   Try
      img := TGPImage.Create(adapter, True);
      Result := d2Point(img.GetWidth, img.GetHeight);
      img.Free;
   Finally
      S.Free;
   End;
End;


Function TD2FilterGdiPlus.LoadThumbnailFromFile(Const AFileName: String; Const AFitWidth, AFitHeight: Single; Const UseEmbedded: Boolean;
   Var Bitmap: TD2Bitmap): Boolean;
Var
   R: TD2Rect;
   thumb, img: TGPImage;
   bmp: TGPBitmap;
   xGR: TGPGraphics;
   scale: Single;
   S: TStream;
   adapter: TExStreamAdapter;
Begin
   Result := False;
   S := TFileStream.Create(AFileName, fmOpenRead Or fmShareDenyNone);
   adapter := TExStreamAdapter.Create(S);
   Try
      img := TGPImage.Create(adapter, True);
      If img.GetWidth * img.GetHeight <> 0 Then
      Begin
         R := d2Rect(0, 0, img.GetWidth, img.GetHeight);
         scale := d2FitRect(R, d2Rect(0, 0, AFitWidth, AFitHeight));
         If scale = 0 Then
            scale := 0.001;
         If scale < 1 Then
            scale := 1;

         If UseEmbedded Then
         Begin
            thumb := img.GetThumbnailImage(trunc(img.GetWidth / scale), trunc(img.GetHeight / scale));
            If thumb <> nil Then
            Begin
               Bitmap.SetSize(thumb.GetWidth, thumb.GetHeight);
               bmp := TGPBitmap.Create(Bitmap.Width, Bitmap.Height, Bitmap.Width * 4, PixelFormat32bppPARGB, Pbyte(bitmap.Scanline[0]));
               xGR := TGPGraphics.Create(bmp);
               xGR.DrawImage(thumb, 0, 0, Bitmap.Width, Bitmap.Height);
               xGR.Free;
               bmp.Free;
               thumb.Free;
            End;
         End Else Begin
            Bitmap.SetSize(trunc(d2RectWidth(R)), trunc(d2RectHeight(R)));
            bmp := TGPBitmap.Create(Bitmap.Width, Bitmap.Height, Bitmap.Width * 4, PixelFormat32bppPARGB, Pbyte(bitmap.Scanline[0]));
            bmp.SetResolution(img.GetHorizontalResolution / scale, img.GetVerticalResolution / scale);
            xGR := TGPGraphics.Create(bmp);
            xGR.SetInterpolationMode(TInterpolationMode(QualityModeHigh));
            xGR.DrawImage(img, 0, 0);
            xGR.Free;
            bmp.Free;
         End;
         Result := True;
      End;
      img.Free;
   Finally
      S.Free;
   End;
End;

Function TD2FilterGdiPlus.LoadFromFile(Const AFileName: String; Const Rotate: Single; Var Bitmap: TD2Bitmap): Boolean;
Var
   img: TGPImage;
   bmp: TGPBitmap;
   Graphics: TGPGraphics;
   M, M2: TD2Matrix;
   Pts: Array [1..4] Of TD2Point;
   GM: TGPMatrix;
   R: TD2Rect;
   BD: TBitmapData;
   S: TStream;
   adapter: TExStreamAdapter;
Begin
   Result := False;
   S := TFileStream.Create(AFileName, fmOpenRead Or fmShareDenyNone);
   adapter := TExStreamAdapter.Create(S);
   Try
      img := TGPImage.Create(adapter, True);
      If img.GetWidth * img.GetHeight <> 0 Then
      Begin
         If (frac(Rotate) = 0) And (Trunc(rotate) Mod 90 = 0) Then
         Begin
            If (Trunc(rotate) Mod 360 = 90) Or (Trunc(rotate) Mod 360 = 270) Then
               Bitmap.SetSize(img.GetHeight, img.GetWidth)
            Else
               Bitmap.SetSize(img.GetWidth, img.GetHeight);

            bmp := TGPBitmap.Create(adapter, True);
            If Trunc(rotate) Mod 360 = 90 Then
               bmp.RotateFlip(Rotate90FlipNone);
            If Trunc(rotate) Mod 360 = 180 Then
               bmp.RotateFlip(Rotate180FlipNone);
            If Trunc(rotate) Mod 360 = 270 Then
               bmp.RotateFlip(Rotate270FlipNone);

            If bmp.LockBits(MakeRect(0, 0, Bitmap.Width, Bitmap.Height), ImageLockModeRead, PixelFormat32bppPARGB, BD) = OK Then
            Begin
               d2MoveLongword(BD.Scan0, Bitmap.StartLine, Bitmap.Width * Bitmap.Height);
               bmp.UnlockBits(BD);
            End;
            bmp.Free;
         End Else Begin
            M := IdentityMatrix;
            M.m31 := -(img.GetWidth / 2);
            M.m32 := -(img.GetHeight / 2);
            M := d2MatrixMultiply(M, d2CreateRotationMatrix(d2DegToRad(Rotate)));

            Pts[1] := d2PointFromVector(d2VectorTransform(d2Vector(0, 0), M));
            Pts[2] := d2PointFromVector(d2VectorTransform(d2Vector(img.GetWidth, 0), M));
            Pts[3] := d2PointFromVector(d2VectorTransform(d2Vector(img.GetWidth, img.GetHeight), M));
            Pts[4] := d2PointFromVector(d2VectorTransform(d2Vector(0, img.GetHeight), M));
            R := d2NormalizeRect(Pts);

            M2 := IdentityMatrix;
            M2.m31 := d2RectWidth(R) / 2;
            M2.m32 := d2RectHeight(R) / 2;
            M := d2MatrixMultiply(M, M2);

            Bitmap.SetSize(Trunc(d2RectWidth(R)), Trunc(d2RectHeight(R)));

            bmp := TGPBitmap.Create(Bitmap.Width, Bitmap.Height, Bitmap.Width * 4, PixelFormat32bppPARGB, Pbyte(Bitmap.StartLine));
            Graphics := TGPGraphics.Create(bmp);
            With M Do
               GM := TGPMatrix.Create(m11, m12, m21, m22, m31, m32);

            Graphics.SetTransform(GM);
            GM.Free;
            Graphics.DrawImage(img, 0, 0, img.GetWidth, img.GetHeight);
            Graphics.Free;
            bmp.Free;
         End;
         Result := True;
      End;
      img.Free;
   Finally
      S.Free;
   End;
End;


Function TD2FilterGdiPlus.LoadFromStream(Const AStream: TStream; Var Bitmap: TD2Bitmap): Boolean;
Var
   img: TGPImage;
   bmp: TGPBitmap;
   xGR: TGPGraphics;
   adapter: TExStreamAdapter;
Begin
   Result := False;
   adapter := TExStreamAdapter.Create(AStream);
   img := TGPImage.Create(adapter, True);
   If img.GetWidth * img.GetHeight <> 0 Then
   Begin
      Bitmap.SetSize(img.GetWidth, img.GetHeight);
      bmp := TGPBitmap.Create(Bitmap.Width, Bitmap.Height, Bitmap.Width * 4, PixelFormat32bppPARGB, Pbyte(bitmap.Scanline[0]));
      xGR := TGPGraphics.Create(bmp);
      xGR.DrawImage(img, 0, 0, Bitmap.Width, Bitmap.Height);
      xGR.Free;
      bmp.Free;
      Result := True;
   End;
   img.Free;
End;

Function TD2FilterGdiPlus.SaveToStream(Const AStream: TStream; Var Bitmap: TD2Bitmap; Const Format: String; Const Params: String = ''): Boolean;
Var
   bmp: TGPBitmap;
   adapter: TExStreamAdapter;
   encoderClsid: TGUID;
   encoderParameters: TEncoderParameters;
   IntValue: Integer;
   S, param, Name, Value: Ansistring;
   encoder: Ansistring;
   i: Integer;
Begin
   encoder := 'image/png';
   If LowerCase(Format) = 'jpeg' Then
      encoder := 'image/jpeg';
   If LowerCase(Format) = 'jpg' Then
      encoder := 'image/jpeg';
   If LowerCase(Format) = 'png' Then
      encoder := 'image/png';
   If LowerCase(Format) = 'bmp' Then
      encoder := 'image/bmp';
   If LowerCase(Format) = 'tif' Then
      encoder := 'image/tiff';
   If LowerCase(Format) = 'tiff' Then
      encoder := 'image/tiff';
   If LowerCase(Format) = 'gif' Then
      encoder := 'image/gif';

   If GetEncoderClsid(encoder, encoderClsid) >= 0 Then
   Begin
      adapter := TExStreamAdapter.Create(AStream);
      bmp := TGPBitmap.Create(Bitmap.Width, Bitmap.Height, Bitmap.Width * 4, PixelFormat32bppPARGB, Pbyte(bitmap.Scanline[0]));

      If Params <> '' Then
      Begin
         S := Params;
         i := 0;
         While S <> '' Do
         Begin
            param := d2GetToken(S, ' ');
            Name := d2GetToken(param, '=');
            Value := d2GetToken(param, '');
            If CompareText(Name, 'quality') = 0 Then
            Begin
               encoderParameters.Parameter[i].Guid := EncoderQuality;
               encoderParameters.Parameter[i].NumberOfValues := 1;
               encoderParameters.Parameter[i].Type_ := EncoderParameterValueTypeLong;
               IntValue := StrToInt(Value);
               encoderParameters.Parameter[i].Value := @IntValue;
            End;
            Inc(i);
         End;
         encoderParameters.Count := i;

         bmp.Save(adapter, encoderClsid, @encoderParameters);
      End Else
         bmp.Save(adapter, encoderClsid);
      bmp.Free;
   End;
End;

Function TD2FilterGdiPlus.SaveToFile(Const AFileName: String; Var Bitmap: TD2Bitmap; Const Params: String = ''): Boolean;
Var
   S, param, Name, Value: Ansistring;
   bmp: TGPBitmap;
   encoderClsid: TGUID;
   encoderType: Ansistring;
   i, IntValue: Integer;
   encoderParameters: TEncoderParameters;
Begin
   encoderType := '';
   If (LowerCase(ExtractFileExt(AFileName)) = '.jpg') Or (LowerCase(ExtractFileExt(AFileName)) = '.jpeg') Then
      encoderType := 'image/jpeg';

   If (LowerCase(ExtractFileExt(AFileName)) = '.bmp') Then
      encoderType := 'image/bmp';

   If (LowerCase(ExtractFileExt(AFileName)) = '.png') Then
      encoderType := 'image/png';

   If (LowerCase(ExtractFileExt(AFileName)) = '.tif') Or (LowerCase(ExtractFileExt(AFileName)) = '.tiff') Then
      encoderType := 'image/tiff';

   If (LowerCase(ExtractFileExt(AFileName)) = '.gif') Then
      encoderType := 'image/gif';

   If GetEncoderClsid(encoderType, encoderClsid) >= 0 Then
   Begin
      bmp := TGPBitmap.Create(Bitmap.Width, Bitmap.Height, Bitmap.Width * 4, PixelFormat32bppPARGB, Pbyte(bitmap.Scanline[0]));

      If Params <> '' Then
      Begin
         S := Params;
         i := 0;
         While S <> '' Do
         Begin
            param := d2GetToken(S, ' ');
            Name := d2GetToken(param, '=');
            Value := d2GetToken(param, '');
            If CompareText(Name, 'quality') = 0 Then
            Begin
               encoderParameters.Parameter[i].Guid := EncoderQuality;
               encoderParameters.Parameter[i].NumberOfValues := 1;
               encoderParameters.Parameter[i].Type_ := EncoderParameterValueTypeLong;
               IntValue := StrToInt(Value);
               encoderParameters.Parameter[i].Value := @IntValue;
            End;
            Inc(i);
         End;
         encoderParameters.Count := i;

         bmp.Save(AFileName, encoderClsid, @encoderParameters);
      End Else
         bmp.Save(AFileName, encoderClsid);
      bmp.Free;
   End;
End;

//============================= TD2CanvasGdiPlus ======================================

Const
   imgColorMatrix: TColorMatrix =
      ((1, 0.0, 0.0, 0.0, 0.0),
      (0.0, 1.0, 0.0, 0.0, 0.0),
      (0.0, 0.0, 1.0, 0.0, 0.0),
      (0.0, 0.0, 0.0, 0.1, 0.0),
      (0.0, 0.0, 0.0, 0.0, 1.0));

Var
   ColorArray: Array [0..100] Of TGPColor;
   OffsetArray: Array [0..100] Of Single;

Function d2StyleToGPStyle(S: TD2FontStyle): Integer;
Begin
   Case S Of
      d2FontRegular: Result := FontStyleRegular;
      d2FontBold: Result := FontStyleBold;
      d2FontItalic: Result := FontStyleItalic;
      d2FontBoldItalic: Result := FontStyleBoldItalic;
      d2FontUnderline: Result := FontStyleUnderline;
      d2FontStrikeout: Result := FontStyleStrikeout;
   End;
End;

Constructor TD2CanvasGdiPlus.Create(Const AWidth, AHeight: Integer);
Begin
   FBuffered := True;
   Inherited;
   FGPPen := TGPPen.Create($FF000000);
   FGPPenBrush := TGPSolidBrush.Create($FF000000);
   FGPBrush := TGPSolidBrush.Create(InvalideCanvasState);
   FGPFamily := TGPFontFamily.Create('Tahoma');
   FFontScale := 1;
End;

Constructor TD2CanvasGdiPlus.CreateFromBitmap(Const ABitmap: TD2Bitmap);
Begin
   Inherited;
   FBitmap := ABitmap;
   UpdateBitmap(FBitmap);
   FGraphics := TGPGraphics.Create(TGPBitmap(FBitmap.Handle));
   FGraphics.SetSmoothingMode(SmoothingModeHighQuality);
   FGraphics.SetPixelOffsetMode(PixelOffsetModeHalf);
   FGraphics.SetTextContrast(2);
   FGPPen := TGPPen.Create($FF000000);
   FGPPenBrush := TGPSolidBrush.Create($FF000000);
   FGPBrush := TGPSolidBrush.Create(InvalideCanvasState);
   FGPFamily := TGPFontFamily.Create('Tahoma');
   FFontScale := 96 / FGraphics.GetDpiX;
End;

Destructor TD2CanvasGdiPlus.Destroy;
Begin
   FreeAndNil(FFontCollection);
   FreeAndNil(FGraphics);
   FreeAndNil(FGPFamily);
   FreeAndNil(FGPBrush);
   FreeAndNil(FGPPenBrush);
   FreeAndNil(FGPPen);
   Inherited;
End;

Procedure TD2CanvasGdiPlus.ResizeBuffer(Const AWidth, AHeight: Integer);
Begin
   If (AWidth = FWidth) And (AHeight = FHeight) Then
      Exit;
   FreeBuffer;
   FWidth := AWidth;
   FHeight := AHeight;
   If FWidth <= 0 Then
      FWidth := 1;
   If FHeight <= 0 Then
      FHeight := 1;
   FResized := True;
   If FBuffered Then
   Begin
      With FBitmapInfo.bmiHeader Do
      Begin
         biSize := SizeOf(TBitmapInfoHeader);
         biPlanes := 1;
         biBitCount := 32;
         biCompression := BI_RGB;
         biWidth := AWidth;
         If biWidth <= 0 Then
            biWidth := 1;
         biHeight := -AHeight;
         If biHeight >= 0 Then
            biHeight := -1;
      End;

      FBufferHandle := CreateDIBSection(0, FBitmapInfo, DIB_RGB_COLORS, Pointer(FBufferBits), 0, 0);
      If FBufferBits = nil Then
         Raise Exception.Create('Can''t allocate the DIB handle');

      FHandle := CreateCompatibleDC(0);
      If FHandle = 0 Then
      Begin
         DeleteObject(FBufferHandle);
         FHandle := 0;
         FBufferBits := nil;
         Raise Exception.Create('Can''t create compatible DC');
      End;

      If SelectObject(FHandle, FBufferHandle) = 0 Then
      Begin
         DeleteDC(FHandle);
         DeleteObject(FBufferHandle);
         FHandle := 0;
         FBufferHandle := 0;
         FBufferBits := nil;
         Raise Exception.Create('Can''t select an object into DC');
      End;
   End;
   FGraphics := TGPGraphics.Create(FHandle);
   FGraphics.SetSmoothingMode(TSmoothingMode(QualityModeHigh));
   FGraphics.SetInterpolationMode(InterpolationModeHighQuality);
   FGraphics.SetPixelOffsetMode(PixelOffsetModeHalf);
   FGraphics.SetCompositingMode(CompositingModeSourceOver);
   FGraphics.SetTextContrast(2);
   FFontScale := 96 / FGraphics.GetDpiX;
End;

Procedure TD2CanvasGdiPlus.FlushBuffer(Const X, Y: Integer; Const DC: Cardinal);
Begin
   If FBufferHandle = 0 Then
      Exit;
   If DC = 0 Then
      Exit;
   Windows.BitBlt(DC, X, Y, FWidth, FHeight, FHandle, 0, 0, SRCCOPY);
End;

Procedure TD2CanvasGdiPlus.FreeBuffer;
Begin
   FreeAndNil(FGraphics);
   If FBuffered Then
   Begin
      If FBufferHandle = 0 Then
         Exit;
      If FHandle <> 0 Then
         DeleteDC(FHandle);
      FHandle := 0;
      If FBufferHandle <> 0 Then
         DeleteObject(FBufferHandle);
      FBufferHandle := 0;
   End;
End;

Procedure TD2CanvasGdiPlus.FlushBufferRect(Const X, Y: Integer; Const DC: Cardinal; Const ARect: TD2Rect);
Var
   R: TRect;
Begin
   If FBufferHandle = 0 Then
      Exit;
   If DC = 0 Then
      Exit;
   R := Rect(trunc(ARect.left), trunc(ARect.top), trunc(ARect.right) + 1, trunc(ARect.bottom) + 1);
   With R Do
   Begin
      Windows.BitBlt(DC, X + R.left, Y + R.top, R.right - R.left, R.bottom - R.top, FHandle, R.left, R.top, SRCCOPY);
   End;
End;

Procedure TD2CanvasGdiPlus.Clear(Const Color: Cardinal);
Begin
   If Not (FBuffered Or (FBitmap <> nil)) Then
      Exit;
   FGraphics.Clear(Color);
End;

Procedure TD2CanvasGdiPlus.ClearRect(Const ARect: TD2Rect; Const AColor: TD2Color);
Var
   R: TRect;
Begin
   If Not (FBuffered Or (FBitmap <> nil)) Then
      Exit;
   R := Rect(Trunc(ARect.Left), Trunc(ARect.Top), Round(ARect.Right), Round(ARect.Bottom));
   If R.Left < 0 Then
      R.Left := 0;
   If R.Top < 0 Then
      R.Top := 0;
   If R.Top < 0 Then
      R.Top := 0;
   If R.Right > FWidth Then
      R.Right := FWidth;
   If R.Bottom > FHeight Then
      R.Bottom := FHeight;
   If R.Bottom < R.Top Then
      R.Bottom := R.Top;
   If R.Right < R.Left Then
      R.Right := R.Left;
   If (R.Right < 0) Or (R.Top < 0) Or (R.Left > FWidth) Or (R.Top > FHeight) Then
      Exit;
   d2FillLongwordRect(FBufferBits, FWidth, FHeight, R.Left, R.Top, R.Right, R.Bottom, AColor);
End;

Class Function TD2CanvasGdiPlus.GetBitmapScanline(Bitmap: TD2Bitmap; y: Integer): PD2ColorArray;
Begin
   If (y >= 0) And (y < Bitmap.Height) And (Bitmap.StartLine <> nil) Then
      Result := @PD2ColorArray(Bitmap.StartLine)[(y) * Bitmap.Width]
   Else
      Result := nil;
End;

Procedure TD2CanvasGdiPlus.SetMatrix(Const M: TD2Matrix);
Var
   GM: TGPMatrix;
Begin
   FMatrix := M;
   With FMatrix Do
   Begin
      GM := TGPMatrix.Create(m11, m12, m21, m22, m31, m32);
      GM.Shear(m13, m23);
   End;
   FGraphics.SetTransform(GM);
   GM.Free;
End;

Procedure TD2CanvasGdiPlus.MultyMatrix(Const M: TD2Matrix);
Var
   GM: TGPMatrix;
Begin
   Inherited;
   With M Do
   Begin
      GM := TGPMatrix.Create(m11, m12, m21, m22, m31, m32);
      GM.Shear(m13, m23);
   End;
   FGraphics.MultiplyTransform(GM);
   GM.Free;
End;

Function TD2CanvasGdiPlus.SaveCanvas: Cardinal;
Var
   i: Integer;
Begin
   Result := InvalideCanvasState;
   If FGraphics = nil Then
      Exit;

   If Length(FSaveData) > 0 Then
      For i := 0 To High(FSaveData) Do
         If FSaveData[i].Index = InvalideCanvasState Then
         Begin
            Result := i;
            Break;
         End;
   If Result = InvalideCanvasState Then
   Begin
      SetLength(FSaveData, Length(FSaveData) + 1);
      Result := High(FSaveData);
   End;
   FSaveData[Result].Index := FGraphics.Save;
   FSaveData[Result].Matrix := FMatrix;
   FSaveData[Result].Stroke := TD2Brush.Create(d2BrushSolid, InvalideCanvasState);
   FSaveData[Result].Stroke.Assign(Stroke);
   FSaveData[Result].Fill := TD2Brush.Create(d2BrushSolid, $FF000000);
   FSaveData[Result].Fill.Assign(Fill);
   FSaveData[Result].StrokeThickness := FStrokeThickness;
   FSaveData[Result].StrokeCap := StrokeCap;
   FSaveData[Result].StrokeJoin := StrokeJoin;
   FSaveData[Result].StrokeDash := StrokeDash;
   If StrokeDash <> d2DashSolid Then
   Begin
      SetLength(FSaveData[Result].Dash, Length(FDash));
      Move(FDash[0], FSaveData[Result].Dash[0], SizeOf(FDash[0]) * Length(FDash));
   End;
   FSaveData[Result].DashOffset := FDashOffset;
   FSaveData[Result].Font := TD2Font.Create;
   FSaveData[Result].Font.Assign(Font);
End;

Procedure TD2CanvasGdiPlus.RestoreCanvas(Const AState: Cardinal);
Begin
   If FGraphics = nil Then
      Exit;

   If (AState >= 0) And (AState < Length(FSaveData)) Then
   Begin
      FGraphics.Restore(FSaveData[AState].Index);

      FSaveData[AState].Index := InvalideCanvasState;

      FMatrix := FSaveData[AState].Matrix;
      Stroke.Assign(FSaveData[AState].Stroke);
      FSaveData[AState].Stroke.Free;
      Fill.Assign(FSaveData[AState].Fill);
      FSaveData[AState].Fill.Free;
      SetMatrix(FMatrix);
      FStrokeThickness := FSaveData[AState].StrokeThickness;
      FStrokeCap := FSaveData[AState].StrokeCap;
      FStrokeJoin := FSaveData[AState].StrokeJoin;
      FStrokeDash := FSaveData[AState].StrokeDash;
      If StrokeDash <> d2DashSolid Then
      Begin
         SetLength(FDash, Length(FSaveData[AState].Dash));
         Move(FSaveData[AState].Dash[0], FDash[0], SizeOf(FDash[0]) * Length(FDash));
      End;
      FDashOffset := FSaveData[AState].DashOffset;
      Font.Assign(FSaveData[AState].Font);
      FSaveData[AState].Font.Free;
   End;
End;

Procedure TD2CanvasGdiPlus.SetClipRects(Const ARects: Array Of TD2Rect);
Var
   i: Integer;
   R: TGPRegion;
   GM: TGPMatrix;
Begin
   R := TGPRegion.Create;
   R.MakeEmpty;
   For i := 0 To High(ARects) Do
   Begin
      R.Union(GPRectFromRect(ARects[i]));
   End;
   FGraphics.SetClip(R);
   R.Free;
End;

Procedure TD2CanvasGdiPlus.IntersectClipRect(Const ARect: TD2Rect);
Begin
   FGraphics.IntersectClip(MakeRect(ARect.Left, ARect.Top, ARect.Right - ARect.Left, ARect.Bottom - ARect.Top));
End;

Procedure TD2CanvasGdiPlus.ExcludeClipRect(Const ARect: TD2Rect);
Begin
   FGraphics.ExcludeClip(GPRectFromRect(ARect));
End;

Procedure TD2CanvasGdiPlus.ResetClipRect;
Begin
   FGraphics.ResetClip;
End;

Procedure TD2CanvasGdiPlus.ApplyFill(ARect: TD2Rect; Const AOpacity: Single);
Var
   C: TD2Color;
   i: Integer;
   Count: Integer;
   B: TD2Bitmap;
   P: TGPGraphicsPath;
   M: TGPMatrix;
   CM: TColorMatrix;
   ImageAttributes: TGPImageAttributes;
Begin
   If FGPBrush <> nil Then
      FreeAndNil(FGPBrush);
   If (FFill.Style = d2BrushResource) And (FFill.Resource <> nil) And (FFill.Resource.Brush <> nil) Then
      FFill.Assign(FFill.Resource.Brush);

   With FFill Do
   Begin
      Case Style Of
         d2BrushSolid:
         Begin
            FGPBrush := TGPSolidBrush.Create(d2Opacity(SolidColor, AOpacity));
         End;
         d2BrushGradient:
         Begin
            If Gradient.Points.Count > 1 Then
            Begin
               Count := 0;

               If Gradient.Points[0].Offset > 0 Then
               Begin
                  ColorArray[Count] := d2Opacity(Gradient.Points[0].IntColor, AOpacity);
                  OffsetArray[Count] := 0;
                  Count := Count + 1;
               End;
               For i := 0 To Gradient.Points.Count - 1 Do
               Begin
                  ColorArray[i + Count] := d2Opacity(Gradient.Points[i].IntColor, AOpacity);
                  OffsetArray[i + Count] := Gradient.Points[i].Offset;
               End;
               If Gradient.Points[Gradient.Points.Count - 1].Offset < 1 Then
               Begin
                  Count := Count + 1;
                  ColorArray[Gradient.Points.Count + Count - 1] := d2Opacity(Gradient.Points[Gradient.Points.Count - 1].IntColor, AOpacity);
                  OffsetArray[Gradient.Points.Count + Count - 1] := 1;
               End;

               If Gradient.Style = d2LinearGradient Then
               Begin
                  FGPBrush := TGPLinearGradientBrush.Create(MakePoint(ARect.Left + Gradient.StartPosition.X * ARect.Right, ARect.Top + Gradient.StartPosition.Y * ARect.Bottom),
                     MakePoint(ARect.Left + Gradient.StopPosition.X * ARect.Right, ARect.Top + Gradient.StopPosition.Y * ARect.Bottom), SolidColor, SolidColor);
                  TGPLinearGradientBrush(FGPBrush).SetWrapMode(WrapModeTileFlipX);
                  TGPLinearGradientBrush(FGPBrush).SetInterpolationColors(PGPColor(@ColorArray), PSingle(@OffsetArray), Gradient.Points.Count + Count);
               End Else Begin
                  P := TGPGraphicsPath.Create;
                  P.AddEllipse(GPRectFromRect(ARect));
                  FGPBrush := TGPPathGradientBrush.Create(P);
                  P.Free;
                  With Gradient.RadialTransform Do
                     M := TGPMatrix.Create(Matrix.m11, Matrix.m12, Matrix.m21, Matrix.m22, Matrix.m31, Matrix.m32);
                  TGPPathGradientBrush(FGPBrush).SetTransform(M);
                  M.Free;
                  TGPPathGradientBrush(FGPBrush).SetWrapMode(WrapModeClamp);
                  TGPPathGradientBrush(FGPBrush).SetInterpolationColors(PARGB(@ColorArray), PSingle(@OffsetArray), Gradient.Points.Count + Count);
               End;
            End Else
               FGPBrush := TGPSolidBrush.Create(d2Opacity(SolidColor, AOpacity));
         End;
         d2BrushResource:
         Begin
            FGPBrush := TGPSolidBrush.Create($00000000);
         End;
         d2BrushVisual:
         Begin
            FGPBrush := TGPSolidBrush.Create($00000000);
         End;
         d2BrushBitmap:
         Begin
            B := Bitmap.Bitmap;
            If (B <> nil) And (B.ResourceBitmap <> nil) Then
               B := B.ResourceBitmap;
            If (B <> nil) And (B.Width > 0) And (B.Height > 0) Then
            Begin
               UpdateBitmap(B);
               If (B.Handle <> 0) Then
               Begin
                  CM := imgColorMatrix;
                  CM[3][3] := AOpacity;
                  If Aopacity <> 1 Then
                  Begin
                     ImageAttributes := TGPImageAttributes.Create;
                     ImageAttributes.SetColorMatrix(CM, ColorMatrixFlagsDefault, ColorAdjustTypeBitmap);
                  End Else
                     ImageAttributes := nil;
                  If Bitmap.WrapMode <> d2WrapTileStretch Then
                  Begin
                     FGPBrush := TGPTextureBrush.Create(TGPBitmap(B.Handle), GPRectFromRect(d2Rect(0, 0, B.Width, B.Height)), ImageAttributes);
                     TGPTextureBrush(FGPBrush).SetWrapMode(TWrapMode(Bitmap.WrapMode));
                  End Else Begin
                     FGPBrush := TGPTextureBrush.Create(TGPBitmap(B.Handle), GPRectFromRect(d2Rect(0, 0, B.Width, B.Height)), ImageAttributes);
                     TGPTextureBrush(FGPBrush).SetWrapMode(WrapModeClamp);
                     TGPTextureBrush(FGPBrush).ScaleTransform((d2RectWidth(ARect) + (StrokeThickness / 2)) / B.Width,
                        (d2RectHeight(ARect) + (StrokeThickness / 2)) / B.Height);
                  End;
                  If Aopacity <> 1 Then
                     ImageAttributes.Free;
               End Else
                  FGPBrush := TGPSolidBrush.Create($00000000);
            End Else
               FGPBrush := TGPSolidBrush.Create($00000000);
         End;
         Else FGPBrush := TGPSolidBrush.Create($00000000);
      End;
   End;
End;

Procedure TD2CanvasGdiPlus.ApplyStroke(ARect: TD2Rect; Const AOpacity: Single);
Var
   i: Integer;
Begin
   If FGPPen <> nil Then
      FreeAndNil(FGPPen);
   If FGPPenBrush <> nil Then
      FreeAndNil(FGPPenBrush);
   If (FStroke.Style = d2BrushResource) And (FStroke.Resource <> nil) And (FStroke.Resource.Brush <> nil) Then
      FStroke.Assign(FStroke.Resource.Brush);

   With FStroke Do
   Begin
      Case Style Of
         d2BrushSolid:
         Begin
            FGPPenBrush := TGPSolidBrush.Create(d2Opacity(SolidColor, AOpacity));
         End;
         d2BrushGradient:
         Begin
            If Gradient.Points.Count > 1 Then
            Begin
               For i := 0 To Gradient.Points.Count - 1 Do
               Begin
                  ColorArray[i] := d2Opacity(Gradient.Points[i].IntColor, AOpacity);
                  OffsetArray[i] := Gradient.Points[i].Offset;
               End;
               FGPPenBrush := TGPLinearGradientBrush.Create(MakePoint(ARect.Left + Gradient.StartPosition.X * ARect.Right, ARect.Top + Gradient.StartPosition.Y * ARect.Bottom),
                  MakePoint(ARect.Left + Gradient.StopPosition.X * ARect.Right, ARect.Top + Gradient.StopPosition.Y * ARect.Bottom), SolidColor, SolidColor);
               TGPLinearGradientBrush(FGPPenBrush).SetInterpolationColors(PGPColor(@ColorArray), PSingle(@OffsetArray), Gradient.Points.Count);
               If Abs(FStroke.Gradient.StartPosition.X - FStroke.Gradient.StopPosition.X) < Abs(FStroke.Gradient.StartPosition.Y - FStroke.Gradient.StopPosition.Y) Then
                  TGPLinearGradientBrush(FGPPenBrush).ScaleTransform(d2RectHeight(ARect), d2RectWidth(ARect))
               Else
                  TGPLinearGradientBrush(FGPPenBrush).ScaleTransform(d2RectWidth(ARect), d2RectHeight(ARect));
            End Else
               FGPPenBrush := TGPSolidBrush.Create(d2Opacity(SolidColor, AOpacity));
         End;
         d2BrushVisual:
         Begin
            FGPPenBrush := TGPSolidBrush.Create($00000000);
         End;
         d2BrushBitmap:
         Begin
            If (Bitmap.Bitmap <> nil) And (Bitmap.Bitmap.Width > 0) And (Bitmap.Bitmap.Height > 0) Then
            Begin
               UpdateBitmap(Bitmap.Bitmap);
               If (Bitmap.Bitmap.Handle <> 0) Then
               Begin
                  If Bitmap.WrapMode <> d2WrapTileStretch Then
                     FGPPenBrush := TGPTextureBrush.Create(TGPBitmap(Bitmap.Bitmap.Handle), TWrapMode(Bitmap.WrapMode))
                  Else Begin
                     FGPPenBrush := TGPTextureBrush.Create(TGPBitmap(Bitmap.Bitmap.Handle), WrapModeClamp);
                     TGPTextureBrush(FGPPenBrush).ScaleTransform(d2RectWidth(ARect) / Bitmap.Bitmap.Width, d2RectHeight(ARect) / Bitmap.Bitmap.Height);
                  End;
               End Else
                  FGPPenBrush := TGPSolidBrush.Create($00000000);
            End Else
               FGPPenBrush := TGPSolidBrush.Create($00000000);
         End;
         Else FGPPenBrush := TGPSolidBrush.Create($00000000);
      End;
   End;

   FGPPen := TGPPen.Create(FGPPenBrush);
   Case StrokeCap Of
      d2CapFlat: FGPPen.SetLineCap(LineCapFlat, LineCapFlat, TDashCap(LineCapFlat));
      d2CapRound: FGPPen.SetLineCap(LineCapRound, LineCapRound, TDashCap(LineCapRound));
   End;
   If Length(FDash) > 0 Then
   Begin
      FGPPen.SetDashOffset(FDashOffset);
      FGPPen.SetDashPattern(@FDash[0], Length(FDash));
   End Else
      FGPPen.SetDashStyle(DashStyleSolid);
   Case StrokeJoin Of
      d2JoinMiter: FGPPen.SetLineJoin(LineJoinMiter);
      d2JoinRound: FGPPen.SetLineJoin(LineJoinRound);
      d2JoinBevel: FGPPen.SetLineJoin(LineJoinBevel);
   End;
   FGPPen.SetWidth(StrokeThickness);
End;

Procedure TD2CanvasGdiPlus.FontChanged(Sender: TObject);
Begin
   FreeAndNil(FGPFamily);
   FGPFamily := TGPFontFamily.Create(FFont.Family, FFontCollection);
   If Not FGPFamily.IsAvailable Then
   Begin
      FGPFamily.Free;
      FGPFamily := TGPFontFamily.Create(FFont.Family);
      If Not FGPFamily.IsAvailable Then
      Begin
         FGPFamily.Free;
         FGPFamily := TGPFontFamily.GenericSansSerif.Clone;
      End;
   End;
   If FGraphics <> nil Then
   Begin
      If FFont.ClearType Then
         FGraphics.SetTextRenderingHint(TextRenderingHintClearTypeGridFit)
      Else
         FGraphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
   End;
End;

Procedure TD2CanvasGdiPlus.DrawLine(Const APt1, APt2: TD2Point; Const AOpacity: Single);
Begin
   If FStroke.Style <> d2BrushNone Then
   Begin
      ApplyStroke(d2Rect(APt1.X, APt1.Y, APt2.X, APt2.Y), AOpacity);
      FGraphics.DrawLine(FGPPen, APt1.X, APt1.Y, APt2.X, APt2.Y);
   End;
End;

Procedure TD2CanvasGdiPlus.IntFillRect(R: TD2Rect; Opacity: Single);
Var
   Save: Cardinal;
   GPR: TGPRectF;
   Visual: TD2VisualObject;
   M: TD2Matrix;
   SaveOpacity: Single;
   SaveStroke: TD2Brush;
   SaveStrokeThickness: Single;
Begin
   If FFill.Style <> d2BrushNone Then
   Begin
      If FFill.Style = d2BrushVisual Then
      Begin
         Save := SaveCanvas;
         FGraphics.IntersectClip(GPRectFromRect(R));
         If FFill.Visual.VisualObject <> nil Then
         Begin
            Visual := FFill.Visual.VisualObject;
            SaveOpacity := Visual.Opacity;
            SaveStroke := TD2Brush.Create(d2BrushSolid, $FF000000);
            SaveStroke.Assign(Stroke);
            SaveStrokeThickness := StrokeThickness;
            Visual.Opacity := Opacity;

            Visual.PaintTo(Self, R);

            Visual.Opacity := SaveOpacity;
            Stroke.Assign(SaveStroke);
            SaveStroke.Free;
            StrokeThickness := SaveStrokeThickness;
         End;
         RestoreCanvas(Save);
      End Else Begin
         ApplyFill(R, Opacity);
         FGraphics.FillRectangle(FGPBrush, GPRectFromRect(R));
      End;
   End;
End;

Procedure TD2CanvasGdiPlus.IntFillPath(P: TGPGraphicsPath; R: TD2Rect; Opacity: Single);
Var
   Save: Cardinal;
   Rg: TGPRegion;
   Visual: TD2VisualObject;
   M: TD2Matrix;
   SaveOpacity: Single;
   SaveStroke: TD2Brush;
   SaveStrokeThickness: Single;
Begin
   If FFill.Style <> d2BrushNone Then
   Begin
      If FFill.Style = d2BrushVisual Then
      Begin
         Save := SaveCanvas;
         Rg := TGPRegion.Create(P);
         FGraphics.IntersectClip(Rg);
         Rg.Free;
         If FFill.Visual.VisualObject <> nil Then
         Begin
            Visual := FFill.Visual.VisualObject;
            SaveOpacity := Visual.Opacity;
            SaveStroke := TD2Brush.Create(d2BrushSolid, $FF000000);
            SaveStroke.Assign(Stroke);
            SaveStrokeThickness := StrokeThickness;
            Visual.Opacity := Opacity;

            Visual.PaintTo(Self, R);

            Visual.Opacity := SaveOpacity;
            Stroke.Assign(SaveStroke);
            SaveStroke.Free;
            StrokeThickness := SaveStrokeThickness;
         End;
         RestoreCanvas(Save);
      End Else Begin
         ApplyFill(R, Opacity);
         FGraphics.FillPath(FGPBrush, P);
      End;
   End;
End;

Procedure TD2CanvasGdiPlus.DrawRect(Const ARect: TD2Rect; Const xRadius, yRadius: Single; Const ACorners: TD2Corners; Const AOpacity: Single; Const ACornerType: TD2CornerType = d2CornerRound);
Var
   Path: TGPGraphicsPath;
   x1, x2, y1, y2: Single;
   R: TD2Rect;
Begin
   If FStroke.Style <> d2BrushNone Then
   Begin
      R := ARect;
      ApplyStroke(R, AOpacity);
      If (xRadius < Epsilon) And (yRadius < Epsilon) Then
      Begin
         FGraphics.DrawRectangle(FGPPen, GPRectFromRect(R));
      End Else Begin
         x1 := xRadius;
         If d2RectWidth(R) - (x1 * 2) < 0 Then
            x1 := (xRadius * (d2RectWidth(R) / (x1 * 2)));
         x2 := x1 * CurveKappaInv;
         y1 := yRadius;
         If d2RectHeight(R) - (y1 * 2) < 0 Then
            y1 := (yRadius * (d2RectHeight(R) / (y1 * 2)));
         y2 := y1 * CurveKappaInv;
         Path := TGPGraphicsPath.Create;
         If d2CornerTopLeft In ACorners Then
         Begin
            Case ACornerType Of
               d2CornerBevel: Path.AddLine(R.Left, R.Top + y1, R.Left + x1, R.Top);
               d2CornerInnerRound: Path.AddBezier(R.Left, R.Top + y1, R.Left + x2, R.Top + y1, R.Left + x1, R.Top + y2, R.Left + x1, R.Top);
               d2CornerInnerLine:
               Begin
                  Path.AddLine(R.Left, R.Top + y1, R.Left + x2, R.Top + y1);
                  Path.AddLine(R.Left + x2, R.Top + y1, R.Left + x1, R.Top + y2);
                  Path.AddLine(R.Left + x1, R.Top + y2, R.Left + x1, R.Top);
               End;
               Else Path.AddBezier(R.Left, R.Top + y1, R.Left, R.Top + (y2), R.Left + x2, R.Top, R.Left + x1, R.Top)
            End;
         End Else Begin
            Path.AddLine(R.Left, R.Top + y1, R.Left, R.Top);
            Path.AddLine(R.Left, R.Top, R.Left + x1, R.Top);
         End;
         If d2CornerTopRight In ACorners Then
         Begin
            Case ACornerType Of
               d2CornerBevel: Path.AddLine(R.Right - x1, R.Top, R.Right, R.Top + y1);
               d2CornerInnerRound: Path.AddBezier(R.Right - x1, R.Top, R.Right - x1, R.Top + y2, R.Right - x2, R.Top + y1, R.Right, R.Top + y1);
               d2CornerInnerLine:
               Begin
                  Path.AddLine(R.Right - x1, R.Top, R.Right - x1, R.Top + y2);
                  Path.AddLine(R.Right - x1, R.Top + y2, R.Right - x2, R.Top + y1);
                  Path.AddLine(R.Right - x2, R.Top + y1, R.Right, R.Top + y1);
               End;
               Else Path.AddBezier(R.Right - x1, R.Top, R.Right - x2, R.Top, R.Right, R.Top + (y2), R.Right, R.Top + y1)
            End;
         End Else Begin
            Path.AddLine(R.Right - x1, R.Top, R.Right, R.Top);
            Path.AddLine(R.Right, R.Top, R.Right, R.Top + y1);
         End;
         If d2CornerBottomRight In ACorners Then
         Begin
            Case ACornerType Of
               d2CornerBevel: Path.AddLine(R.Right, R.Bottom - y1, R.Right - x1, R.Bottom);
               d2CornerInnerRound: Path.AddBezier(R.Right, R.Bottom - y1, R.Right - x2, R.Bottom - y1, R.Right - x1, R.Bottom - y2, R.Right - x1, R.Bottom);
               d2CornerInnerLine:
               Begin
                  Path.AddLine(R.Right, R.Bottom - y1, R.Right - x2, R.Bottom - y1);
                  Path.AddLine(R.Right - x2, R.Bottom - y1, R.Right - x1, R.Bottom - y2);
                  Path.AddLine(R.Right - x1, R.Bottom - y2, R.Right - x1, R.Bottom);
               End;
               Else Path.AddBezier(R.Right, R.Bottom - y1, R.Right, R.Bottom - (y2), R.Right - x2, R.Bottom, R.Right - x1, R.Bottom)
            End;
         End Else Begin
            Path.AddLine(R.Right, R.Bottom - y1, R.Right, R.Bottom);
            Path.AddLine(R.Right, R.Bottom, R.Right - x1, R.Bottom);
         End;
         If d2CornerBottomLeft In ACorners Then
         Begin
            Case ACornerType Of
               d2CornerBevel: Path.AddLine(R.Left + x1, R.Bottom, R.Left, R.Bottom - y1);
               d2CornerInnerRound: Path.AddBezier(R.Left + x1, R.Bottom, R.Left + x1, R.Bottom - y2, R.Left + x2, R.Bottom - y1, R.Left, R.Bottom - y1);
               d2CornerInnerLine:
               Begin
                  Path.AddLine(R.Left + x1, R.Bottom, R.Left + x1, R.Bottom - y2);
                  Path.AddLine(R.Left + x1, R.Bottom - y2, R.Left + x2, R.Bottom - y1);
                  Path.AddLine(R.Left + x2, R.Bottom - y1, R.Left, R.Bottom - y1);
               End;
               Else Path.AddBezier(R.Left + x1, R.Bottom, R.Left + x2, R.Bottom, R.Left, R.Bottom - (y2), R.Left, R.Bottom - y1)
            End;
         End Else Begin
            Path.AddLine(R.Left + x1, R.Bottom, R.Left, R.Bottom);
            Path.AddLine(R.Left, R.Bottom, R.Left, R.Bottom - y1);
         End;
         {$IFDEF FLATTEN}
      Path.Flatten();
         {$ENDIF}
         Path.CloseFigure;
         FGraphics.DrawPath(FGPPen, Path);
         Path.Free;
      End;
   End;
End;

Procedure TD2CanvasGdiPlus.FillRect(Const ARect: TD2Rect; Const xRadius, yRadius: Single; Const ACorners: TD2Corners; Const AOpacity: Single; Const ACornerType: TD2CornerType = d2CornerRound);
Var
   Path: TGPGraphicsPath;
   x1, x2, y1, y2: Single;
   R: TD2Rect;
Begin
   If FFill.Style <> d2BrushNone Then
   Begin
      R := ARect;
      If ((xRadius = 0) And (yRadius = 0)) Or (ACorners = []) Then
      Begin
         IntFillRect(R, AOpacity);
      End Else Begin
         x1 := xRadius;
         If d2RectWidth(R) - (x1 * 2) < 0 Then
            x1 := (xRadius * (d2RectWidth(R) / (x1 * 2)));
         x2 := x1 * CurveKappaInv;
         y1 := yRadius;
         If d2RectHeight(R) - (y1 * 2) < 0 Then
            y1 := (yRadius * (d2RectHeight(R) / (y1 * 2)));
         y2 := y1 * CurveKappaInv;
         Path := TGPGraphicsPath.Create;
         If d2CornerTopLeft In ACorners Then
         Begin
            Case ACornerType Of
               d2CornerBevel: Path.AddLine(R.Left, R.Top + y1, R.Left + x1, R.Top);
               d2CornerInnerRound: Path.AddBezier(R.Left, R.Top + y1, R.Left + x2, R.Top + y1, R.Left + x1, R.Top + y2, R.Left + x1, R.Top);
               d2CornerInnerLine:
               Begin
                  Path.AddLine(R.Left, R.Top + y1, R.Left + x2, R.Top + y1);
                  Path.AddLine(R.Left + x2, R.Top + y1, R.Left + x1, R.Top + y2);
                  Path.AddLine(R.Left + x1, R.Top + y2, R.Left + x1, R.Top);
               End;
               Else Path.AddBezier(R.Left, R.Top + y1, R.Left, R.Top + (y2), R.Left + x2, R.Top, R.Left + x1, R.Top)
            End;
         End Else Begin
            Path.AddLine(R.Left, R.Top + y1, R.Left, R.Top);
            Path.AddLine(R.Left, R.Top, R.Left + x1, R.Top);
         End;
         If d2CornerTopRight In ACorners Then
         Begin
            Case ACornerType Of
               d2CornerBevel: Path.AddLine(R.Right - x1, R.Top, R.Right, R.Top + y1);
               d2CornerInnerRound: Path.AddBezier(R.Right - x1, R.Top, R.Right - x1, R.Top + y2, R.Right - x2, R.Top + y1, R.Right, R.Top + y1);
               d2CornerInnerLine:
               Begin
                  Path.AddLine(R.Right - x1, R.Top, R.Right - x1, R.Top + y2);
                  Path.AddLine(R.Right - x1, R.Top + y2, R.Right - x2, R.Top + y1);
                  Path.AddLine(R.Right - x2, R.Top + y1, R.Right, R.Top + y1);
               End;
               Else Path.AddBezier(R.Right - x1, R.Top, R.Right - x2, R.Top, R.Right, R.Top + (y2), R.Right, R.Top + y1)
            End;
         End Else Begin
            Path.AddLine(R.Right - x1, R.Top, R.Right, R.Top);
            Path.AddLine(R.Right, R.Top, R.Right, R.Top + y1);
         End;
         If d2CornerBottomRight In ACorners Then
         Begin
            Case ACornerType Of
               d2CornerBevel: Path.AddLine(R.Right, R.Bottom - y1, R.Right - x1, R.Bottom);
               d2CornerInnerRound: Path.AddBezier(R.Right, R.Bottom - y1, R.Right - x2, R.Bottom - y1, R.Right - x1, R.Bottom - y2, R.Right - x1, R.Bottom);
               d2CornerInnerLine:
               Begin
                  Path.AddLine(R.Right, R.Bottom - y1, R.Right - x2, R.Bottom - y1);
                  Path.AddLine(R.Right - x2, R.Bottom - y1, R.Right - x1, R.Bottom - y2);
                  Path.AddLine(R.Right - x1, R.Bottom - y2, R.Right - x1, R.Bottom);
               End;
               Else Path.AddBezier(R.Right, R.Bottom - y1, R.Right, R.Bottom - (y2), R.Right - x2, R.Bottom, R.Right - x1, R.Bottom)
            End;
         End Else Begin
            Path.AddLine(R.Right, R.Bottom - y1, R.Right, R.Bottom);
            Path.AddLine(R.Right, R.Bottom, R.Right - x1, R.Bottom);
         End;
         If d2CornerBottomLeft In ACorners Then
         Begin
            Case ACornerType Of
               d2CornerBevel: Path.AddLine(R.Left + x1, R.Bottom, R.Left, R.Bottom - y1);
               d2CornerInnerRound: Path.AddBezier(R.Left + x1, R.Bottom, R.Left + x1, R.Bottom - y2, R.Left + x2, R.Bottom - y1, R.Left, R.Bottom - y1);
               d2CornerInnerLine:
               Begin
                  Path.AddLine(R.Left + x1, R.Bottom, R.Left + x1, R.Bottom - y2);
                  Path.AddLine(R.Left + x1, R.Bottom - y2, R.Left + x2, R.Bottom - y1);
                  Path.AddLine(R.Left + x2, R.Bottom - y1, R.Left, R.Bottom - y1);
               End;
               Else Path.AddBezier(R.Left + x1, R.Bottom, R.Left + x2, R.Bottom, R.Left, R.Bottom - (y2), R.Left, R.Bottom - y1)
            End;
         End Else Begin
            Path.AddLine(R.Left + x1, R.Bottom, R.Left, R.Bottom);
            Path.AddLine(R.Left, R.Bottom, R.Left, R.Bottom - y1);
         End;
         {$IFDEF FLATTEN}
      Path.Flatten();
         {$ENDIF}
         Path.CloseFigure;
         IntFillPath(Path, ARect, AOpacity);
         Path.Free;
      End;
   End;
End;

Procedure TD2CanvasGdiPlus.DrawEllipse(Const ARect: TD2Rect; Const AOpacity: Single);
Var
   R: TD2Rect;
   P: TGPGraphicsPath;
Begin
   If FStroke.Style <> d2BrushNone Then
   Begin
      ApplyStroke(ARect, AOpacity);
      R := d2Rect(ARect.left, ARect.Top, ARect.Right, ARect.Bottom);
      FGraphics.DrawEllipse(FGPPen, GPRectFromRect(R));
   End;
End;

Procedure TD2CanvasGdiPlus.FillEllipse(Const ARect: TD2Rect; Const AOpacity: Single);
Var
   R: TD2Rect;
   P: TGPGraphicsPath;
Begin
   If FFill.Style <> d2BrushNone Then
   Begin
      P := TGPGraphicsPath.Create();
      R := d2Rect(ARect.left, ARect.Top, ARect.Right - ARect.Left, ARect.Bottom - ARect.Top);
      P.AddEllipse(R.Left, R.Top, R.Right, R.Bottom);
      IntFillPath(P, R, AOpacity);
      P.Free;
   End;
End;

Function TD2CanvasGdiPlus.LoadFontFromStream(AStream: TStream): Boolean;
Var
   Stream: TMemoryStream;
Begin
   If FFontCollection = nil Then
      FFontCollection := TGPPrivateFontCollection.Create;
   Stream := TMemoryStream.Create;
   Stream.CopyFrom(AStream, AStream.Size);
   FFontCollection.AddMemoryFont(Stream.Memory, Stream.Size);
   Stream.Free;
End;

Procedure TD2CanvasGdiPlus.FillText(Const ARect, AClipRect: TD2Rect; Const AText: WideString; Const WordWrap: Boolean; Const AOpacity: Single; Const ATextAlign: TD2TextAlign; Const AVTextAlign: TD2TextAlign = d2TextAlignCenter);
Var
   StringFormat: TGPStringFormat;
   Font: TGPFont;
   S: TD2FontStyle;
Begin
   If (FFill.Style <> d2BrushNone) And (AText <> '') Then
   Begin
      StringFormat := TGPStringFormat.Create(StringFormatFlagsNoClip);
      If Not WordWrap Then
         StringFormat.SetFormatFlags(StringFormat.GetFormatFlags Or StringFormatFlagsNoWrap);
      If Not FGPFamily.IsStyleAvailable(d2StyleToGPStyle(FFont.Style)) Then
      Begin
         Font := nil;
         For S := d2FontRegular To d2FontStrikeout Do
         Begin
            If FGPFamily.IsStyleAvailable(d2StyleToGPStyle(S)) Then
            Begin
               Font := TGPFont.Create(FGPFamily, (FFont.Size * 0.75 * FFontScale), d2StyleToGPStyle(S));
               Break;
            End;
         End;
         If Font = nil Then
            Font := TGPFont.Create(FGPFamily, (FFont.Size * 0.75 * FFontScale), d2StyleToGPStyle(FFont.Style));
      End Else
         Font := TGPFont.Create(FGPFamily, (FFont.Size * 0.75 * FFontScale), d2StyleToGPStyle(FFont.Style), UnitPoint);

      Case ATextAlign Of
         d2TextAlignCenter: stringFormat.SetAlignment(StringAlignmentCenter);
         d2TextAlignNear: stringFormat.SetAlignment(StringAlignmentNear);
         d2TextAlignFar: stringFormat.SetAlignment(StringAlignmentFar);
      End;

      Case AVTextAlign Of
         d2TextAlignCenter: stringFormat.SetLineAlignment(StringAlignmentCenter);
         d2TextAlignNear: stringFormat.SetLineAlignment(StringAlignmentNear);
         d2TextAlignFar: stringFormat.SetLineAlignment(StringAlignmentFar);
      End;

      If AOpacity = 1 Then
         ApplyFill(ARect, AOpacity * 0.99)
      Else
         ApplyFill(ARect, AOpacity);

      If (frac(FMatrix.m11) <> 0) Or (frac(FMatrix.m22) <> 0) Then
      Begin
         FGraphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
         FGraphics.DrawString(AText, -1, Font, GPRectFromRect(ARect), StringFormat, FGPBrush);
         If FFont.ClearType Then
            FGraphics.SetTextRenderingHint(TextRenderingHintClearTypeGridFit)
         Else
            FGraphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
      End Else Begin
         FGraphics.DrawString(AText, -1, Font, GPRectFromRect(ARect), StringFormat, FGPBrush);
      End;

      Font.Free;
      StringFormat.Free;
   End;
End;

Procedure TD2CanvasGdiPlus.MeasureText(Var ARect: TD2Rect; AClipRect: TD2Rect; Const AText: WideString; Const WordWrap: Boolean; Const ATextAlign, AVTextAlign: TD2TextAlign);
Var
   StringFormat: TGPStringFormat;
   Font: TGPFont;
   GR: TGPRectF;
   charRanges: Array[0..2] Of TCharacterRange;
   pCharRangeRegions: Array Of TGPRegion;
   i, Count: Integer;
   S: TD2FontStyle;
   B: TGPBitmap;
   G: TGPGraphics;
Begin
   If Not WordWrap Then
      ARect.Right := ARect.Left;
   If Length(AText) = 0 Then
      Exit;

   If FGraphics = nil Then
   Begin
      B := TGPBitmap.Create(1, 1, PixelFormat32bppARGB);
      G := TGPGraphics.Create(B);
      G.SetSmoothingMode(TSmoothingMode(QualityModeHigh));
      G.SetInterpolationMode(InterpolationModeHighQuality);
      G.SetPixelOffsetMode(PixelOffsetModeHalf);
      If FFont.ClearType Then
         G.SetTextRenderingHint(TextRenderingHintClearTypeGridFit)
      Else
         G.SetTextRenderingHint(TextRenderingHintAntiAlias);
      FGraphics := G;
   End Else Begin
      G := nil;
   End;

   If (frac(FMatrix.m11) <> 0) Or (frac(FMatrix.m22) <> 0) Then
      FGraphics.SetTextRenderingHint(TextRenderingHintAntiAlias);

   StringFormat := TGPStringFormat.Create(StringFormatFlagsMeasureTrailingSpaces Or StringFormatFlagsNoClip);
   If Not WordWrap Then
      StringFormat.SetFormatFlags(StringFormat.GetFormatFlags Or StringFormatFlagsNoWrap);

   If Not FGPFamily.IsStyleAvailable(d2StyleToGPStyle(FFont.Style)) Then
   Begin
      Font := nil;
      For S := d2FontRegular To d2FontStrikeout Do
      Begin
         If FGPFamily.IsStyleAvailable(d2StyleToGPStyle(S)) Then
         Begin
            Font := TGPFont.Create(FGPFamily, (FFont.Size * 0.75 * FFontScale), d2StyleToGPStyle(S));
            Break;
         End;
      End;
      If Font = nil Then
         Font := TGPFont.Create(FGPFamily, (FFont.Size * 0.75 * FFontScale), d2StyleToGPStyle(FFont.Style));
   End Else
      Font := TGPFont.Create(FGPFamily, (FFont.Size * 0.75 * FFontScale), d2StyleToGPStyle(FFont.Style));

   Case ATextAlign Of
      d2TextAlignCenter: stringFormat.SetAlignment(StringAlignmentCenter);
      d2TextAlignNear: stringFormat.SetAlignment(StringAlignmentNear);
      d2TextAlignFar: stringFormat.SetAlignment(StringAlignmentFar);
   End;

   Case AVTextAlign Of
      d2TextAlignCenter: stringFormat.SetLineAlignment(StringAlignmentCenter);
      d2TextAlignNear: stringFormat.SetLineAlignment(StringAlignmentNear);
      d2TextAlignFar: stringFormat.SetLineAlignment(StringAlignmentFar);
   End;


   charRanges[0] := MakeCharacterRange(0, Length(AText));
   StringFormat.SetMeasurableCharacterRanges(1, @charRanges);
   Count := StringFormat.GetMeasurableCharacterRangeCount;
   SetLength(pCharRangeRegions, Count);
   If Count > 0 Then
      For i := 0 To Count - 1 Do
         pCharRangeRegions[i] := TGPRegion.Create;

   FGraphics.MeasureCharacterRanges(AText, -1, Font, GPRectFromRect(ARect), StringFormat, Count, pCharRangeRegions);

   For i := 0 To Count - 1 Do
   Begin
      pCharRangeRegions[i].GetBounds(GR, FGraphics);

      If i = 0 Then
         ARect := d2Rect(GR.X, GR.Y, GR.X + GR.Width, GR.Y + GR.Height)
      Else
         ARect := d2UnionRect(ARect, d2Rect(GR.X, GR.Y, GR.X + GR.Width, GR.Y + GR.Height));
   End;

   If Count > 0 Then
      For i := 0 To Count - 1 Do
         pCharRangeRegions[i].Free;

   SetLength(pCharRangeRegions, 0);

   Font.Free;
   StringFormat.Free;

   If G <> nil Then
   Begin
      G.Free;
      B.Free;
      FGraphics := nil;
   End Else Begin
      If FFont.ClearType Then
         FGraphics.SetTextRenderingHint(TextRenderingHintClearTypeGridFit)
      Else
         FGraphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
   End;
End;

Procedure TD2CanvasGdiPlus.DrawPath(Const APath: TD2PathData; Const ARect: TD2Rect; Const AOpacity: Single);
Var
   B: TD2Rect;
   i: Integer;
   W, H, NewW, NewH: Single;
   CP1, CP2, CP, SP: TD2Point;
Begin
   If Not APath.IsEmpty Then
   Begin
      B := APath.GetBounds;
      W := d2RectWidth(B);
      H := d2RectHeight(B);
      If W * H = 0 Then
         Exit;
      ApplyStroke(APath.GetBounds, AOpacity);
      NewW := d2RectWidth(ARect);
      NewH := d2RectHeight(ARect);
      i := 0;
      While i < Length(APath.PathData) Do
      Begin
         Case APath.PathData[i].Kind Of

            d2PathPointMoveTo:
            Begin
               CP.X := ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW;
               CP.Y := ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH;
               SP := CP;
            End;

            d2PathPointLineTo:
            Begin
               FGraphics.DrawLine(FGPPen, CP.X, CP.Y,
                  ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW,
                  ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH);
               CP.X := ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW;
               CP.Y := ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH;
            End;

            d2PathPointCurveTo:
            Begin
               CP1 := d2Point(ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW, ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH);
               Inc(i);
               CP2 := d2Point(ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW, ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH);
               Inc(i);
               FGraphics.DrawBezier(FGPPen, CP.X, CP.Y,
                  CP1.X,
                  CP1.Y,
                  CP2.X,
                  CP2.Y,
                  ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW,
                  ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH
                  );
               CP.X := ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW;
               CP.Y := ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH;
            End;

            d2PathPointClose:
            Begin
               FGraphics.DrawLine(FGPPen, CP.X, CP.Y, SP.X, SP.Y);
            End;
         End;
         Inc(i);
      End;
   End;
End;

Function TD2CanvasGdiPlus.PtInPath(Const APoint: TD2Point; Const ARect: TD2Rect; Const APath: TD2PathData): Boolean;
Var
   B: TD2Rect;
   i: Integer;
   W, H, NewW, NewH: Single;
   CP, CP1, CP2: TD2Point;
   P: TGPGraphicsPath;
Begin
   Result := False;
   If Not d2PtInRect(APoint, ARect) Then
      Result := False
   Else Begin
      B := APath.GetBounds;
      W := d2RectWidth(B);
      H := d2RectHeight(B);
      If W * H = 0 Then
         Exit;
      NewW := d2RectWidth(ARect);
      NewH := d2RectHeight(ARect);
      P := TGPGraphicsPath.Create;
      i := 0;
      While i < Length(APath.PathData) Do
      Begin
         Case APath.PathData[i].Kind Of

            d2PathPointMoveTo:
            Begin
               CP.X := ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW;
               CP.Y := ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH;
               P.StartFigure;
            End;

            d2PathPointLineTo:
            Begin
               P.AddLine(CP.X, CP.Y,
                  ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW,
                  ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH);
               CP.X := ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW;
               CP.Y := ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH;
            End;

            d2PathPointCurveTo:
            Begin
               CP1 := d2Point(ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW, ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH);
               Inc(i);
               CP2 := d2Point(ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW, ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH);
               Inc(i);
               P.AddBezier(CP.X, CP.Y,
                  CP1.X,
                  CP1.Y,
                  CP2.X,
                  CP2.Y,
                  ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW,
                  ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH
                  );
               CP.X := ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW;
               CP.Y := ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH;
            End;

            d2PathPointClose:
            Begin
               P.CloseFigure;
            End;
         End;
         Inc(i);
      End;

      Result := P.IsVisible(APoint.X, APoint.Y);

      P.Free;
   End;
End;


Procedure TD2CanvasGdiPlus.FillPath(Const APath: TD2PathData; Const ARect: TD2Rect; Const AOpacity: Single);
Var
   B: TD2Rect;
   i: Integer;
   W, H, NewW, NewH: Single;
   CP, CP1, CP2: TD2Point;
   P: TGPGraphicsPath;
Begin
   If Not APath.IsEmpty Then
   Begin
      B := APath.GetBounds;
      W := d2RectWidth(B);
      H := d2RectHeight(B);
      If W * H = 0 Then
         Exit;
      NewW := d2RectWidth(ARect);
      NewH := d2RectHeight(ARect);
      P := TGPGraphicsPath.Create;
      i := 0;
      While i < Length(APath.PathData) Do
      Begin
         Case APath.PathData[i].Kind Of

            d2PathPointMoveTo:
            Begin
               CP.X := ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW;
               CP.Y := ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH;
               P.StartFigure;
            End;

            d2PathPointLineTo:
            Begin
               P.AddLine(CP.X, CP.Y,
                  ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW,
                  ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH);
               CP.X := ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW;
               CP.Y := ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH;
            End;

            d2PathPointCurveTo:
            Begin
               CP1 := d2Point(ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW, ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH);
               Inc(i);
               CP2 := d2Point(ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW, ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH);
               Inc(i);
               P.AddBezier(CP.X, CP.Y,
                  CP1.X,
                  CP1.Y,
                  CP2.X,
                  CP2.Y,
                  ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW,
                  ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH
                  );
               CP.X := ARect.Left + (APath.PathData[i].Point.X - B.Left) / W * NewW;
               CP.Y := ARect.Top + (APath.PathData[i].Point.Y - B.Top) / H * NewH;
            End;

            d2PathPointClose:
            Begin
               P.CloseFigure;
            End;
         End;
         Inc(i);
      End;

      IntFillPath(P, ARect, AOpacity);
      P.Free;
   End;
End;

Procedure TD2CanvasGdiPlus.DrawThumbnail(Const ABitmap: TD2Bitmap; Const Width, Height: Single);
Var
   scale: Single;
Begin
   UpdateBitmap(ABitmap);
   If ABitmap.Handle = 0 Then
      Exit;
   scale := Width / ABitmap.Width;
   If FBitmap <> nil Then
   Begin
      Fgraphics.ScaleTransform(scale, scale);
      Fgraphics.DrawImage(TGPBitmap(ABitmap.Handle), 0, 0, ABitmap.Width, ABitmap.Height);
   End;
End;

Procedure TD2CanvasGdiPlus.DrawBitmap(Const ABitmap: TD2Bitmap; Const SrcRect, DstRect: TD2Rect; Const AOpacity: Single; Const HighSpeed: Boolean = False);
Var
   CM: TColorMatrix;
   ImageAttributes: TGPImageAttributes;
   //Pts:array [1..4] of TD2Point;
Begin
   If HighSpeed Then
      FGraphics.SetInterpolationMode(InterpolationModeNearestNeighbor)
   Else
      FGraphics.SetInterpolationMode(TInterpolationMode(QualityModeHigh));
   If (AOpacity < 1) Then
   Begin
      If (AOpacity = 0) Then
         Exit;
      CM := ImgColorMatrix;
      CM[3][3] := AOpacity;
      UpdateBitmap(ABitmap);
      If ABitmap.Handle = 0 Then
         Exit;
      ImageAttributes := TGPImageAttributes.Create;
      ImageAttributes.SetColorMatrix(
         CM,
         ColorMatrixFlagsDefault,
         ColorAdjustTypeBitmap);
      FGraphics.DrawImage(TGPBitmap(ABitmap.Handle), MakeRect(DstRect.Left, DstRect.Top, DstRect.Right - DstRect.Left, DstRect.Bottom - DstRect.Top),
         SrcRect.Left, SrcRect.Top, SrcRect.Right - SrcRect.Left, SrcRect.Bottom - SrcRect.Top,
         UnitPixel, ImageAttributes);
      ImageAttributes.Free;
   End Else Begin
      UpdateBitmap(ABitmap);
      If ABitmap.Handle = 0 Then
         Exit;
      FGraphics.DrawImage(TGPBitmap(ABitmap.Handle), MakeRect(DstRect.Left, DstRect.Top, DstRect.Right - DstRect.Left, DstRect.Bottom - DstRect.Top),
         SrcRect.Left, SrcRect.Top, SrcRect.Right - SrcRect.Left, SrcRect.Bottom - SrcRect.Top,
         UnitPixel);
   End;
End;

Procedure TD2CanvasGdiPlus.UpdateBitmap(ABitmap: TD2Bitmap);
Begin
   If ABitmap = nil Then
      Exit;
   If ABitmap.IsEmpty Then
      Exit;

   If ABitmap.Handle = 0 Then
   Begin
      ABitmap.Handle := Cardinal(TGPBitmap.Create(ABitmap.Width, ABitmap.Height, ABitmap.Width * 4, PixelFormat32bppPARGB, Pbyte(ABitmap.StartLine)));
      ABitmap.NeedUpdate := True;
      ABitmap.OnDestroyHandle := DestroyBitmap;
   End;

   If (ABitmap.Width <> TGPBitmap(ABitmap.Handle).GetWidth) Or (ABitmap.Height <> TGPBitmap(ABitmap.Handle).GetHeight) Then
   Begin
      TGPBitmap(ABitmap.Handle).Destroy;
      ABitmap.Handle := Cardinal(TGPBitmap.Create(ABitmap.Width, ABitmap.Height, ABitmap.Width * 4, PixelFormat32bppPARGB, Pbyte(ABitmap.StartLine)));
      ABitmap.NeedUpdate := True;
   End;

   ABitmap.NeedUpdate := False;
End;

Procedure TD2CanvasGdiPlus.DestroyBitmap(Sender: TObject);
Begin
   If (Sender <> nil) Then
   Begin
      If (TD2Bitmap(Sender).Handle <> 0) Then
         TGPBitmap(TD2Bitmap(Sender).Handle).Free;
      TD2Bitmap(Sender).Handle := 0;
   End;
End;


Function TD2CanvasGdiPlus.TextToPath(Path: TD2PathData; Const ARect: TD2Rect; Const AText: WideString;
   Const WordWrap: Boolean; Const ATextAlign: TD2TextAlign; Const AVTextAlign: TD2TextAlign = d2TextAlignCenter): Boolean;
Var
   StringFormat: TGPStringFormat;
   GPPath: TGPGraphicsPath;
   Bmp: TGPBitmap;
   FGraphics: TGPGraphics;
   Bits: Cardinal;
   i: Integer;
   SP, CP1, CP2: TD2Point;
   Data: TPathData;
   SavePoints: PGPPointF;
Begin
   Result := False;

   If (AText = '') Then
      exit;
   If Not FGPFamily.IsStyleAvailable(d2StyleToGPStyle(FFont.Style)) Then
      Exit;

   Path.Clear;
   Bmp := TGPBitmap.Create(1, 1, 1 * 4, PixelFormat32bppARGB, @Bits);
   FGraphics := TGPGraphics.Create(Bmp);

   StringFormat := TGPStringFormat.Create(0);
   If Not WordWrap Then
      StringFormat.SetFormatFlags(StringFormat.GetFormatFlags Or StringFormatFlagsNoWrap Or StringFormatFlagsMeasureTrailingSpaces);
   GPPath := TGPGraphicsPath.Create;

   Case ATextAlign Of
      d2TextAlignCenter: stringFormat.SetAlignment(StringAlignmentCenter);
      d2TextAlignNear: stringFormat.SetAlignment(StringAlignmentNear);
      d2TextAlignFar: stringFormat.SetAlignment(StringAlignmentFar);
   End;

   Case AVTextAlign Of
      d2TextAlignCenter: stringFormat.SetLineAlignment(StringAlignmentCenter);
      d2TextAlignNear: stringFormat.SetLineAlignment(StringAlignmentNear);
      d2TextAlignFar: stringFormat.SetLineAlignment(StringAlignmentFar);
   End;

   GPPath.AddString(AText, -1, FGPFamily, d2StyleToGPStyle(FFont.Style), FFont.Size, GPRectFromRect(ARect), StringFormat);

   Data := TPathData.Create;
   GPPath.GetPathData(Data);
   SavePoints := Data.Points;

   i := 0;

   While i < Data.Count Do
   Begin
      If PByteArray(Data.Types)[i] = Byte(PathPointTypeStart) Then
      Begin
         SP := d2Point(Data.Points^.X, Data.Points^.Y);
         Path.MoveTo(d2Point(Data.Points^.X, Data.Points^.Y));
      End;

      If PByteArray(Data.Types)[i] And Byte(PathPointTypeBezier) = Byte(PathPointTypeBezier) Then
      Begin
         CP1 := d2Point(Data.Points^.X, Data.Points^.Y);
         Inc(i);
         Inc(Data.Points);
         CP2 := d2Point(Data.Points^.X, Data.Points^.Y);
         Inc(i);
         Inc(Data.Points);
         Path.CurveTo(CP1, CP2, d2Point(Data.Points^.X, Data.Points^.Y));
      End;

      If PByteArray(Data.Types)[i] And Byte(PathPointTypeLine) = Byte(PathPointTypeLine) Then
      Begin
         Path.LineTo(d2Point(Data.Points^.X, Data.Points^.Y));
      End;

      If PByteArray(Data.Types)[i] And Byte(PathPointTypeCloseSubpath) = Byte(PathPointTypeCloseSubpath) Then
      Begin
         Path.ClosePath;
      End;

      Inc(i);
      Inc(Data.Points);
   End;

   Result := True;

   Data.Points := SavePoints;
   Data.Free;
   StringFormat.Free;
   GPPath.Free;

   FGraphics.Free;
   Bmp.Free;
End;
