/// 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)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)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)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.Right0 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)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(nDC0 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.