-
-
Save CynicRus/8b31691e88b6354710419a58a80527db to your computer and use it in GitHub Desktop.
GDI+ Canvas for Lazarus
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| unit gdiplus_canvas; | |
| {$mode ObjFPC}{$H+} | |
| interface | |
| uses | |
| {$IFDEF MSWINDOWS}Windows, gdipapi,{$ENDIF}lclintf, lcltype, Classes, SysUtils, Controls, | |
| StdCtrls, GraphType, Graphics, FPImage, FPCanvas, Math; | |
| type | |
| {$IFDEF MSWINDOWS} | |
| { TGDIPlusCanvas } | |
| TGDIPlusCanvas = class(TFPCustomCanvas) | |
| private | |
| FAutoRedraw: boolean; | |
| FCopyMode: TCopyMode; | |
| FGpGraphics: GpGraphics; | |
| FGpPen: GpPen; | |
| FGpBrush: GpBrush; | |
| FGpFont: GpFont; | |
| FFontFamily: GpFontFamily; | |
| FImage: GpBitmap; | |
| FGpClipRegion: GpRegion; | |
| FLazBrush: TBrush; | |
| FLazFont: TFont; | |
| FLazPen: TPen; | |
| FOnChange: TNotifyEvent; | |
| FOnChanging: TNotifyEvent; | |
| FPath: GpPath; | |
| FRegion: TRegion; | |
| FTextStyle: TTextStyle; | |
| FPenChanged: boolean; | |
| FBrushChanged: boolean; | |
| FFontChanged: boolean; | |
| // API compatibility | |
| FCurrentPos: TPoint; | |
| FMemDC: HDC; | |
| FBitmap: HBITMAP; | |
| FOwnsBitmap: boolean; | |
| // Для кэширования измерений текста | |
| FLastMeasuredText: string; | |
| FLastTextSize: TSize; | |
| // для кэширования шрифтов | |
| FLastFontName: UnicodeString; | |
| FLastFontSize: Integer; | |
| FLastFontStyle: Integer; // GDI+ FontStyle flags | |
| // Для Save/RestoreHandleState | |
| FStateToken: ULONG; | |
| // AutoRedraw | |
| FPresentDC: HDC; | |
| FPresentX: Integer; | |
| FPresentY: Integer; | |
| // Для Lock/Unlock | |
| FLock: TRTLCriticalSection; | |
| procedure InitFromDC(DC: HDC); | |
| procedure InitFromBitmap(ABitmap: HBITMAP; AOwns: boolean); | |
| function FPColorToARGB(const Color: TFPColor): ARGB; | |
| function GetHandle: HDC; | |
| function GetPixel(X, Y: integer): TColor; | |
| procedure SetAutoRedraw(AValue: boolean); | |
| procedure SetHandle(AValue: HDC); | |
| procedure SetLazBrush(AValue: TBrush); | |
| procedure SetLazFont(AValue: TFont); | |
| procedure SetLazPen(AValue: TPen); | |
| procedure SetPixel(X, Y: integer; AValue: TColor); | |
| procedure SetRegion(AValue: TRegion); | |
| procedure UpdateBrush; | |
| procedure UpdatePen; | |
| procedure UpdateFont; | |
| procedure CheckStatus(Status: TStatus); | |
| procedure ApplyClipping; | |
| procedure ApplyDrawingMode; | |
| procedure ApplyInterpolation; | |
| function CreateGDIPBitmap(Image: TFPCustomImage): GpBitmap; | |
| procedure PenChanging(APen: TObject); virtual; | |
| procedure FontChanging(AFont: TObject); virtual; | |
| procedure BrushChanging(ABrush: TObject); virtual; | |
| procedure RegionChanging(ARegion: TObject); virtual; | |
| procedure BrushChanged(ABrush: TObject); | |
| procedure FontChanged(AFont: TObject); | |
| procedure PenChanged(APen: TObject); | |
| procedure RegionChanged(ARegion: TObject); | |
| protected | |
| function DoCreateDefaultFont: TFPCustomFont; override; | |
| function DoCreateDefaultPen: TFPCustomPen; override; | |
| function DoCreateDefaultBrush: TFPCustomBrush; override; | |
| procedure SetColor(x, y: integer; const Value: TFPColor); override; | |
| function GetColor(x, y: integer): TFPColor; override; | |
| procedure SetHeight(AValue: integer); override; | |
| function GetHeight: integer; override; | |
| procedure SetWidth(AValue: integer); override; | |
| function GetWidth: integer; override; | |
| procedure DoTextOut(x, y: integer; Text: ansistring); override; | |
| procedure DoGetTextSize(Text: ansistring; var w, h: integer); override; | |
| function DoGetTextHeight(Text: ansistring): integer; override; | |
| function DoGetTextWidth(Text: ansistring): integer; override; | |
| procedure DoRectangle(const Bounds: TRect); override; | |
| procedure DoRectangleFill(const Bounds: TRect); override; | |
| procedure DoEllipseFill(const Bounds: TRect); override; | |
| procedure DoEllipse(const Bounds: TRect); override; | |
| procedure DoPolygonFill(const points: array of TPoint); override; | |
| procedure DoPolygon(const points: array of TPoint); override; | |
| procedure DoPolyline(const points: array of TPoint); override; | |
| procedure DoFloodFill(x, y: integer); override; | |
| procedure DoLine(x1, y1, x2, y2: integer); override; | |
| procedure DoCopyRect(x, y: integer; canvas: TFPCustomCanvas; | |
| const SourceRect: TRect); override; | |
| procedure DoDraw(x, y: integer; const image: TFPCustomImage); override; | |
| procedure DoRadialPie(x1, y1, x2, y2, StartAngle16Deg, Angle16DegLength: integer); | |
| override; | |
| procedure DoPolyBezier(Points: PPoint; NumPts: integer; | |
| Filled, Continuous: boolean); override; | |
| function GetClipRect: TRect; override; | |
| procedure SetClipRect(const AValue: TRect); override; | |
| function GetClipping: boolean; override; | |
| procedure SetClipping(const AValue: boolean); override; | |
| public | |
| constructor Create(DC: HDC); | |
| constructor CreateFromBitmap(ABitmap: HBitmap); overload; | |
| constructor CreateSize(AWidth, AHeight: integer); | |
| destructor Destroy; override; | |
| procedure StretchDraw(x, y, w, h: integer; Source: TFPCustomImage); override; | |
| procedure StretchDraw(const DestRect: TRect; SrcGraphic: TGraphic); | |
| procedure Erase; override; | |
| function GetBitmap: HBITMAP; | |
| procedure Arc(ALeft, ATop, ARight, ABottom, Angle16Deg, Angle16DegLength: integer); | |
| virtual; | |
| procedure Arc(ALeft, ATop, ARight, ABottom, SX, SY, EX, EY: integer); virtual; | |
| procedure ArcTo(ALeft, ATop, ARight, ABottom, SX, SY, EX, EY: integer); virtual; | |
| procedure AngleArc(X, Y: integer; Radius: longword; StartAngle, SweepAngle: single); | |
| procedure BrushCopy(ADestRect: TRect; ABitmap: Graphics.TBitmap; | |
| ASourceRect: TRect; ATransparentColor: TColor); virtual; | |
| procedure Chord(x1, y1, x2, y2, Angle16Deg, Angle16DegLength: integer); virtual; | |
| procedure Chord(x1, y1, x2, y2, SX, SY, EX, EY: integer); virtual; | |
| procedure CopyRect(const Dest: TRect; SrcCanvas: TCanvas; const Source: TRect) | |
| virtual; reintroduce; | |
| procedure Draw(X, Y: integer; SrcGraphic: TGraphic); virtual; reintroduce; | |
| procedure DrawFocusRect(const ARect: TRect); virtual; | |
| procedure Ellipse(const ARect: TRect); | |
| procedure Ellipse(x1, y1, x2, y2: integer); virtual; | |
| procedure FillRect(const ARect: TRect); virtual; | |
| procedure FillRect(X1, Y1, X2, Y2: integer); | |
| procedure FloodFill(X, Y: integer; FillColor: TColor; | |
| FillStyle: TFillStyle); virtual; | |
| procedure Frame3d(var ARect: TRect; const FrameWidth: integer; | |
| const Style: TGraphicsBevelCut); virtual; | |
| procedure Frame3D(var ARect: TRect; TopColor, BottomColor: TColor; | |
| const FrameWidth: integer); overload; | |
| procedure Frame(const ARect: TRect); virtual; | |
| procedure Frame(X1, Y1, X2, Y2: integer); | |
| procedure FrameRect(const ARect: TRect); virtual; | |
| procedure FrameRect(X1, Y1, X2, Y2: integer); | |
| function GetTextMetrics(out TM: TLCLTextMetric): boolean; virtual; | |
| procedure GradientFill(ARect: TRect; AStart, AStop: TColor; | |
| ADirection: TGradientDirection); | |
| procedure Pie(EllipseX1, EllipseY1, EllipseX2, EllipseY2, StartX, | |
| StartY, EndX, EndY: integer); virtual; | |
| procedure PolyBezier(const Points: array of TPoint; Filled: boolean = False; | |
| Continuous: boolean = True); | |
| procedure Polygon(const Points: array of TPoint; Winding: boolean; | |
| StartIndex: integer = 0; NumPts: integer = -1); | |
| procedure Polygon(Points: PPoint; NumPts: integer; | |
| Winding: boolean = False); virtual; | |
| procedure Polygon(const Points: array of TPoint); | |
| procedure Polyline(const Points: array of TPoint; StartIndex: integer; | |
| NumPts: integer = -1); | |
| procedure Polyline(Points: PPoint; NumPts: integer); virtual; | |
| procedure Polyline(const Points: array of TPoint); | |
| procedure Rectangle(X1, Y1, X2, Y2: integer); virtual; | |
| procedure Rectangle(const ARect: TRect); | |
| procedure RoundRect(X1, Y1, X2, Y2: integer; RX, RY: integer); virtual; | |
| procedure RoundRect(const Rect: TRect; RX, RY: integer); | |
| procedure TextOut(X, Y: integer; const Text: string); virtual; | |
| procedure TextRect(const ARect: TRect; X, Y: integer; const Text: string); | |
| procedure TextRect(ARect: TRect; X, Y: integer; const Text: string; | |
| const Style: TTextStyle); virtual; | |
| function TextExtent(const Text: string): TSize; virtual; | |
| function TextHeight(const Text: string): integer; virtual; | |
| function TextWidth(const Text: string): integer; virtual; | |
| function TextFitInfo(const Text: string; MaxWidth: integer): integer; | |
| procedure DrawToCanvas(DestCanvas: TCanvas; X, Y: integer); | |
| procedure MoveTo(X, Y: integer); virtual; | |
| procedure LineTo(X, Y: integer); virtual; | |
| procedure Lock; virtual; | |
| procedure Unlock; virtual; | |
| procedure Refresh; virtual; | |
| procedure SaveHandleState; virtual; | |
| procedure RestoreHandleState; virtual; | |
| function HandleAllocated: boolean; virtual; | |
| function TryLock: boolean; virtual; | |
| procedure RealizeAntialiasing; virtual; | |
| procedure RealizeAutoRedraw; virtual; | |
| property Pixels[X, Y: integer]: TColor read GetPixel write SetPixel; | |
| property Handle: HDC read GetHandle write SetHandle; | |
| property TextStyle: TTextStyle read FTextStyle write FTextStyle; | |
| property PenPos: TPoint read FCurrentPos; | |
| published | |
| property AutoRedraw: boolean read FAutoRedraw write SetAutoRedraw; | |
| property Brush: TBrush read FLazBrush write SetLazBrush; | |
| property CopyMode: TCopyMode read FCopyMode write FCopyMode default cmSrcCopy; | |
| property Font: TFont read FLazFont write SetLazFont; | |
| property Height: integer read GetHeight; | |
| property Pen: TPen read FLazPen write SetLazPen; | |
| property Region: TRegion read FRegion write SetRegion; | |
| property Width: integer read GetWidth; | |
| property OnChange: TNotifyEvent read FOnChange write FOnChange; | |
| property OnChanging: TNotifyEvent read FOnChanging write FOnChanging; | |
| end; | |
| var | |
| GdiPlusToken: ULONG; | |
| StartupInput: TGdiplusStartupInput; | |
| {$ENDIF} | |
| implementation | |
| {$IFDEF MSWINDOWS} | |
| { Helper Initialization Procedures } | |
| procedure TGDIPlusCanvas.InitFromDC(DC: HDC); | |
| begin | |
| FMemDC := DC; | |
| FBitmap := 0; | |
| FOwnsBitmap := False; | |
| FPresentDC := 0; | |
| FPresentX := 0; | |
| FPresentY := 0; | |
| CheckStatus(GdipCreateFromHDC(FMemDC, FGpGraphics)); | |
| end; | |
| procedure TGDIPlusCanvas.InitFromBitmap(ABitmap: HBITMAP; AOwns: boolean); | |
| var | |
| bm: BITMAP; | |
| bmi: BITMAPINFO; | |
| pBits: Pointer; | |
| srcDC: HDC; | |
| begin | |
| GetObject(ABitmap, SizeOf(BITMAP), @bm); | |
| FillChar(bmi, SizeOf(bmi), 0); | |
| with bmi.bmiHeader do | |
| begin | |
| biSize := SizeOf(BITMAPINFOHEADER); | |
| biWidth := bm.bmWidth; | |
| biHeight := -bm.bmHeight; // Top-down! | |
| biPlanes := 1; | |
| biBitCount := 32; | |
| biCompression := BI_RGB; | |
| end; | |
| FMemDC := CreateCompatibleDC(0); | |
| FBitmap := CreateDIBSection(FMemDC, bmi, DIB_RGB_COLORS, pBits, 0, 0); | |
| if FBitmap = 0 then | |
| raise Exception.Create('CreateDIBSection failed'); | |
| SelectObject(FMemDC, FBitmap); | |
| if AOwns then | |
| begin | |
| srcDC := CreateCompatibleDC(0); | |
| SelectObject(srcDC, ABitmap); | |
| BitBlt(FMemDC, 0, 0, bm.bmWidth, bm.bmHeight, srcDC, 0, 0, SRCCOPY); | |
| DeleteDC(srcDC); | |
| DeleteObject(ABitmap); | |
| end | |
| else | |
| begin | |
| srcDC := CreateCompatibleDC(0); | |
| SelectObject(srcDC, ABitmap); | |
| BitBlt(FMemDC, 0, 0, bm.bmWidth, bm.bmHeight, srcDC, 0, 0, SRCCOPY); | |
| DeleteDC(srcDC); | |
| end; | |
| FOwnsBitmap := True; | |
| FPresentDC := 0; | |
| FPresentX := 0; | |
| FPresentY := 0; | |
| CheckStatus(GdipCreateBitmapFromHBITMAP(FBitmap, 0, FImage)); | |
| CheckStatus(GdipGetImageGraphicsContext(FImage, FGpGraphics)); | |
| //CheckStatus(GdipSetTextRenderingHint(FGpGraphics, TextRenderingHintAntiAlias)); | |
| CheckStatus(GdipSetTextRenderingHint(FGpGraphics, TextRenderingHintClearTypeGridFit)); | |
| CheckStatus(GdipSetSmoothingMode(FGpGraphics, SmoothingModeHighQuality)); // для фигур | |
| CheckStatus(GdipSetPixelOffsetMode(FGpGraphics, PixelOffsetModeHighQuality)); // четкость пикселей | |
| CheckStatus(GdipSetCompositingQuality(FGpGraphics, CompositingQualityHighQuality)); | |
| end; | |
| { Constructors & Destructor } | |
| constructor TGDIPlusCanvas.Create(DC: HDC); | |
| begin | |
| inherited Create; | |
| FLazFont := TFont(inherited Font); | |
| FLazPen := TPen(inherited Pen); | |
| FLazBrush := TBrush(inherited Brush); | |
| FLazFont.OnChanging := @FontChanging; | |
| FLazFont.OnChange := @FontChanged; | |
| FLazPen.OnChanging := @PenChanging; | |
| FLazPen.OnChange := @PenChanged; | |
| FLazBrush.OnChanging := @BrushChanging; | |
| FLazBrush.OnChange := @BrushChanged; | |
| FRegion := TRegion.Create; | |
| FRegion.OnChanging := @RegionChanging; | |
| FRegion.OnChange := @RegionChanged; | |
| FPenChanged := True; | |
| FBrushChanged := True; | |
| FFontChanged := True; | |
| FCurrentPos := Point(0, 0); | |
| InitFromDC(DC); | |
| CheckStatus(GdipCreateSolidFill(MakeColor(255, 0, 0, 0), FGpBrush)); | |
| CheckStatus(GdipCreatePen1(MakeColor(255, 0, 0, 0), 1.0, UnitPixel, FGpPen)); | |
| CheckStatus(GdipCreateFontFamilyFromName('Arial', nil, FFontFamily)); | |
| CheckStatus(GdipCreatePath(FillModeAlternate, FPath)); | |
| CheckStatus(GdipSetSmoothingMode(FGpGraphics, SmoothingModeAntiAlias)); | |
| ApplyDrawingMode; | |
| ApplyInterpolation; | |
| Windows.InitializeCriticalSection(FLock); | |
| end; | |
| constructor TGDIPlusCanvas.CreateFromBitmap(ABitmap: HBitmap); | |
| begin | |
| inherited Create; | |
| FLazFont := TFont(inherited Font); | |
| FLazPen := TPen(inherited Pen); | |
| FLazBrush := TBrush(inherited Brush); | |
| FLazFont.OnChanging := @FontChanging; | |
| FLazFont.OnChange := @FontChanged; | |
| FLazPen.OnChanging := @PenChanging; | |
| FLazPen.OnChange := @PenChanged; | |
| FLazBrush.OnChanging := @BrushChanging; | |
| FLazBrush.OnChange := @BrushChanged; | |
| FRegion := TRegion.Create; | |
| FRegion.OnChanging := @RegionChanging; | |
| FRegion.OnChange := @RegionChanged; | |
| FPenChanged := True; | |
| FBrushChanged := True; | |
| FFontChanged := True; | |
| FCurrentPos := Point(0, 0); | |
| InitFromBitmap(ABitmap, False); | |
| CheckStatus(GdipCreateSolidFill(MakeColor(255, 0, 0, 0), FGpBrush)); | |
| CheckStatus(GdipCreatePen1(MakeColor(255, 0, 0, 0), 1.0, UnitPixel, FGpPen)); | |
| CheckStatus(GdipCreateFontFamilyFromName('Arial', nil, FFontFamily)); | |
| CheckStatus(GdipCreatePath(FillModeAlternate, FPath)); | |
| CheckStatus(GdipSetSmoothingMode(FGpGraphics, SmoothingModeAntiAlias)); | |
| ApplyDrawingMode; | |
| ApplyInterpolation; | |
| Windows.InitializeCriticalSection(FLock); | |
| end; | |
| constructor TGDIPlusCanvas.CreateSize(AWidth, AHeight: integer); | |
| var | |
| ScreenDC: HDC; | |
| begin | |
| inherited Create; | |
| FLazFont := TFont(inherited Font); | |
| FLazPen := TPen(inherited Pen); | |
| FLazBrush := TBrush(inherited Brush); | |
| FLazFont.OnChanging := @FontChanging; | |
| FLazFont.OnChange := @FontChanged; | |
| FLazPen.OnChanging := @PenChanging; | |
| FLazPen.OnChange := @PenChanged; | |
| FLazBrush.OnChanging := @BrushChanging; | |
| FLazBrush.OnChange := @BrushChanged; | |
| FRegion := TRegion.Create; | |
| FRegion.OnChanging := @RegionChanging; | |
| FRegion.OnChange := @RegionChanged; | |
| FPenChanged := True; | |
| FBrushChanged := True; | |
| FFontChanged := True; | |
| FCurrentPos := Point(0, 0); | |
| ScreenDC := GetDC(0); | |
| try | |
| FBitmap := CreateCompatibleBitmap(ScreenDC, AWidth, AHeight); | |
| finally | |
| ReleaseDC(0, ScreenDC); | |
| end; | |
| if FBitmap = 0 then | |
| raise Exception.Create('CreateCompatibleBitmap failed'); | |
| InitFromBitmap(FBitmap, True); | |
| CheckStatus(GdipCreateSolidFill(MakeColor(255, 0, 0, 0), FGpBrush)); | |
| CheckStatus(GdipCreatePen1(MakeColor(255, 0, 0, 0), 1.0, UnitPixel, FGpPen)); | |
| CheckStatus(GdipCreateFontFamilyFromName('Arial', nil, FFontFamily)); | |
| CheckStatus(GdipCreatePath(FillModeAlternate, FPath)); | |
| CheckStatus(GdipSetSmoothingMode(FGpGraphics, SmoothingModeAntiAlias)); | |
| Windows.InitializeCriticalSection(FLock); | |
| end; | |
| destructor TGDIPlusCanvas.Destroy; | |
| begin | |
| if Assigned(FGpGraphics) then GdipDeleteGraphics(FGpGraphics); | |
| if Assigned(FGpPen) then GdipDeletePen(FGpPen); | |
| if Assigned(FGpBrush) then GdipDeleteBrush(FGpBrush); | |
| if Assigned(FGpFont) then GdipDeleteFont(FGpFont); | |
| if Assigned(FFontFamily) then GdipDeleteFontFamily(FFontFamily); | |
| if Assigned(FImage) then GdipDisposeImage(FImage); | |
| if Assigned(FGpClipRegion) then GdipDeleteRegion(FGpClipRegion); | |
| if Assigned(FPath) then GdipDeletePath(FPath); | |
| if (FMemDC <> 0) and (FBitmap <> 0) then | |
| begin | |
| DeleteDC(FMemDC); | |
| FMemDC := 0; | |
| end; | |
| if FOwnsBitmap and (FBitmap <> 0) then | |
| begin | |
| DeleteObject(FBitmap); | |
| FBitmap := 0; | |
| end; | |
| Windows.DeleteCriticalSection(FLock); | |
| inherited Destroy; | |
| end; | |
| procedure TGDIPlusCanvas.PenChanging(APen: TObject); | |
| begin | |
| if Assigned(FOnChanging) then | |
| FOnChanging(Self); | |
| end; | |
| procedure TGDIPlusCanvas.FontChanging(AFont: TObject); | |
| begin | |
| if Assigned(FOnChanging) then | |
| FOnChanging(Self); | |
| end; | |
| procedure TGDIPlusCanvas.BrushChanging(ABrush: TObject); | |
| begin | |
| if Assigned(FOnChanging) then | |
| FOnChanging(Self); | |
| end; | |
| procedure TGDIPlusCanvas.RegionChanging(ARegion: TObject); | |
| begin | |
| if Assigned(FOnChanging) then | |
| FOnChanging(Self); | |
| end; | |
| procedure TGDIPlusCanvas.BrushChanged(ABrush: TObject); | |
| begin | |
| FBrushChanged := True; | |
| if Assigned(FOnChange) then | |
| FOnChange(Self); | |
| end; | |
| procedure TGDIPlusCanvas.FontChanged(AFont: TObject); | |
| begin | |
| FFontChanged := True; | |
| if Assigned(FOnChange) then | |
| FOnChange(Self); | |
| end; | |
| procedure TGDIPlusCanvas.PenChanged(APen: TObject); | |
| begin | |
| FPenChanged := True; | |
| if Assigned(FOnChange) then | |
| FOnChange(Self); | |
| end; | |
| procedure TGDIPlusCanvas.RegionChanged(ARegion: TObject); | |
| begin | |
| SetRegion(FRegion); | |
| if Assigned(FOnChange) then | |
| FOnChange(Self); | |
| end; | |
| function TGDIPlusCanvas.FPColorToARGB(const Color: TFPColor): ARGB; | |
| begin | |
| Result := (Color.Alpha shl 24) or (Color.Red shl 16) or | |
| (Color.Green shl 8) or Color.Blue; | |
| end; | |
| function TGDIPlusCanvas.GetHandle: HDC; | |
| {var | |
| dc: HDC; | |
| begin | |
| if FGpGraphics <> nil then | |
| begin | |
| CheckStatus(GdipGetDC(FGpGraphics, dc)); | |
| Result := dc; | |
| end | |
| else | |
| Result := 0;} | |
| begin | |
| Result := FMemDC; | |
| end; | |
| function TGDIPlusCanvas.GetPixel(X, Y: integer): TColor; | |
| var | |
| color: ARGB; | |
| r, g, b: byte; | |
| dc: HDC; | |
| begin | |
| if FImage <> nil then | |
| begin | |
| CheckStatus(GdipBitmapGetPixel(FImage, X, Y, color)); | |
| end | |
| else if FGpGraphics <> nil then | |
| begin | |
| dc := GetHandle; | |
| try | |
| Result := Windows.GetPixel(dc, X, Y); | |
| if Result = CLR_INVALID then | |
| Result := clBlack; | |
| Exit; | |
| finally | |
| //GdipReleaseDC(FGpGraphics, dc); | |
| end; | |
| end | |
| else | |
| begin | |
| Result := clBlack; | |
| Exit; | |
| end; | |
| r := (color shr 16) and $FF; | |
| g := (color shr 8) and $FF; | |
| b := color and $FF; | |
| Result := RGB(r, g, b); | |
| end; | |
| procedure TGDIPlusCanvas.SetAutoRedraw(AValue: boolean); | |
| begin | |
| if FAutoRedraw = AValue then Exit; | |
| FAutoRedraw := AValue; | |
| RealizeAutoRedraw; | |
| end; | |
| procedure TGDIPlusCanvas.SetHandle(AValue: HDC); | |
| begin | |
| // Если канвас оффскрин (есть FImage/FBitmap/FMemDC), | |
| // то Handle используется как target для AutoRedraw/present. | |
| if (FImage <> nil) and (FMemDC <> 0) and (FBitmap <> 0) then | |
| begin | |
| FPresentDC := AValue; | |
| FPresentX := 0; | |
| FPresentY := 0; | |
| Exit; | |
| end; | |
| // Иначе (мы были созданы от DC) — действительно меняем GpGraphics | |
| if FGpGraphics <> nil then | |
| begin | |
| GdipDeleteGraphics(FGpGraphics); | |
| FGpGraphics := nil; | |
| end; | |
| FMemDC := AValue; | |
| CheckStatus(GdipCreateFromHDC(AValue, FGpGraphics)); | |
| ApplyClipping; | |
| end; | |
| procedure TGDIPlusCanvas.SetLazBrush(AValue: TBrush); | |
| begin | |
| if FLazBrush = AValue then Exit; | |
| FLazBrush.Assign(AValue); | |
| UpdateBrush; | |
| end; | |
| procedure TGDIPlusCanvas.SetLazFont(AValue: TFont); | |
| begin | |
| if FLazFont = AValue then Exit; | |
| FLazFont.Assign(AValue); | |
| UpdateFont; | |
| end; | |
| procedure TGDIPlusCanvas.SetLazPen(AValue: TPen); | |
| begin | |
| if FLazPen = AValue then Exit; | |
| FLazPen.Assign(AValue); | |
| UpdatePen; | |
| end; | |
| procedure TGDIPlusCanvas.SetPixel(X, Y: integer; AValue: TColor); | |
| var | |
| AColor: ARGB; | |
| r, g, b: byte; | |
| dc: HDC; | |
| begin | |
| r := GetRValue(AValue); | |
| g := GetGValue(AValue); | |
| b := GetBValue(AValue); | |
| if FImage <> nil then | |
| begin | |
| AColor := MakeColor(255, r, g, b); | |
| CheckStatus(GdipBitmapSetPixel(FImage, X, Y, AColor)); | |
| end | |
| else if FGpGraphics <> nil then | |
| begin | |
| dc := GetHandle; | |
| try | |
| Windows.SetPixel(dc, X, Y, AValue); | |
| finally | |
| //GdipReleaseDC(FGpGraphics, dc); | |
| end; | |
| end; | |
| end; | |
| procedure TGDIPlusCanvas.SetRegion(AValue: TRegion); | |
| begin | |
| if FRegion = AValue then Exit; | |
| FRegion := AValue; | |
| if FRegion <> nil then | |
| begin | |
| if FGpClipRegion <> nil then | |
| GdipDeleteRegion(FGpClipRegion); | |
| CheckStatus(GdipCreateRegionHrgn(FRegion.Handle, FGpClipRegion)); | |
| end | |
| else | |
| begin | |
| if FGpClipRegion <> nil then | |
| begin | |
| GdipDeleteRegion(FGpClipRegion); | |
| FGpClipRegion := nil; | |
| end; | |
| end; | |
| ApplyClipping; | |
| end; | |
| procedure TGDIPlusCanvas.CheckStatus(Status: TStatus); | |
| begin | |
| if Status <> Ok then | |
| raise Exception.Create('GDI+ error: ' + IntToStr(integer(Status))); | |
| end; | |
| // Resource update methods | |
| procedure TGDIPlusCanvas.UpdateBrush; | |
| var | |
| color, foreColor, backColor: ARGB; | |
| patternBitmap: GpBitmap; | |
| textureBrush: GpTexture; | |
| hatchStyle: THatchStyle; | |
| begin | |
| if not FBrushChanged then Exit; | |
| if FGpBrush <> nil then | |
| GdipDeleteBrush(FGpBrush); | |
| case FLazBrush.Style of | |
| bsSolid: | |
| begin | |
| color := MakeColor(255, GetRValue(FLazBrush.Color), | |
| GetGValue(FLazBrush.Color), GetBValue(FLazBrush.Color)); | |
| CheckStatus(GdipCreateSolidFill(color, FGpBrush)); | |
| end; | |
| bsClear: | |
| begin | |
| color := MakeColor(0, 0, 0, 0); | |
| CheckStatus(GdipCreateSolidFill(color, FGpBrush)); | |
| end; | |
| bsCross, bsDiagCross, bsFDiagonal, bsBDiagonal, bsHorizontal, bsVertical: | |
| begin | |
| // Convert TBrush style to GDI+ HatchStyle | |
| case FLazBrush.Style of | |
| bsCross: hatchStyle := HatchStyleCross; | |
| bsDiagCross: hatchStyle := HatchStyleDiagonalCross; | |
| bsFDiagonal: hatchStyle := HatchStyleForwardDiagonal; | |
| bsBDiagonal: hatchStyle := HatchStyleBackwardDiagonal; | |
| bsHorizontal: hatchStyle := HatchStyleHorizontal; | |
| bsVertical: hatchStyle := HatchStyleVertical; | |
| else | |
| hatchStyle := HatchStyleHorizontal; | |
| end; | |
| foreColor := MakeColor(255, GetRValue(FLazBrush.Color), | |
| GetGValue(FLazBrush.Color), GetBValue(FLazBrush.Color)); | |
| backColor := MakeColor(255, 255, 255, 255); | |
| CheckStatus(GdipCreateHatchBrush(integer(hatchStyle), foreColor, | |
| backColor, FGpBrush)); | |
| end; | |
| bsPattern, bsImage: | |
| begin | |
| if FLazBrush.Bitmap <> nil then | |
| begin | |
| // Create bitmap from TBitmap | |
| CheckStatus(GdipCreateBitmapFromHBITMAP(FLazBrush.Bitmap.Handle, 0, | |
| patternBitmap)); | |
| // Create texture brush | |
| CheckStatus(GdipCreateTexture(patternBitmap, WrapModeTile, textureBrush)); | |
| FGpBrush := textureBrush; | |
| GdipDisposeImage(patternBitmap); | |
| end | |
| else | |
| begin | |
| // Default to solid brush if bitmap is nil | |
| color := MakeColor(255, GetRValue(FLazBrush.Color), | |
| GetGValue(FLazBrush.Color), GetBValue(FLazBrush.Color)); | |
| CheckStatus(GdipCreateSolidFill(color, FGpBrush)); | |
| end; | |
| end; | |
| else | |
| begin | |
| // Default for unsupported styles | |
| color := MakeColor(255, GetRValue(FLazBrush.Color), | |
| GetGValue(FLazBrush.Color), GetBValue(FLazBrush.Color)); | |
| CheckStatus(GdipCreateSolidFill(color, FGpBrush)); | |
| end; | |
| end; | |
| FBrushChanged := False; | |
| end; | |
| procedure TGDIPlusCanvas.UpdatePen; | |
| var | |
| color: ARGB; | |
| dashStyle: TDashStyle; | |
| dashArray: array[0..3] of single; | |
| dashOffset: single; | |
| begin | |
| if not FPenChanged then Exit; | |
| if FGpPen <> nil then | |
| GdipDeletePen(FGpPen); | |
| color := MakeColor(255, GetRValue(FLazPen.Color), GetGValue(FLazPen.Color), | |
| GetBValue(FLazPen.Color)); | |
| CheckStatus(GdipCreatePen1(color, FLazPen.Width, UnitPixel, FGpPen)); | |
| // Set pen style | |
| case FLazPen.Style of | |
| psSolid: dashStyle := DashStyleSolid; | |
| psDash: dashStyle := DashStyleDash; | |
| psDot: dashStyle := DashStyleDot; | |
| psDashDot: dashStyle := DashStyleDashDot; | |
| psDashDotDot: dashStyle := DashStyleDashDotDot; | |
| psInsideFrame: | |
| begin | |
| dashStyle := DashStyleSolid; | |
| CheckStatus(GdipSetPenMode(FGpPen, PenAlignmentInset)); | |
| end; | |
| psPattern: | |
| begin | |
| dashStyle := DashStyleCustom; | |
| // Custom dash pattern for psPattern | |
| dashArray[0] := 3.0; | |
| dashArray[1] := 1.0; | |
| dashArray[2] := 1.0; | |
| dashArray[3] := 1.0; | |
| dashOffset := 0.0; | |
| CheckStatus(GdipSetPenDashArray(FGpPen, @dashArray[0], 4)); | |
| CheckStatus(GdipSetPenDashOffset(FGpPen, dashOffset)); | |
| end; | |
| else | |
| dashStyle := DashStyleSolid; | |
| end; | |
| if FLazPen.Style <> psPattern then | |
| CheckStatus(GdipSetPenDashStyle(FGpPen, dashStyle)); | |
| // Set join and end cap styles | |
| case FLazPen.JoinStyle of | |
| pjsRound: CheckStatus(GdipSetPenLineJoin(FGpPen, LineJoinRound)); | |
| pjsBevel: CheckStatus(GdipSetPenLineJoin(FGpPen, LineJoinBevel)); | |
| pjsMiter: CheckStatus(GdipSetPenLineJoin(FGpPen, LineJoinMiter)); | |
| end; | |
| case FLazPen.EndCap of | |
| pecRound: CheckStatus(GdipSetPenEndCap(FGpPen, LineCapRound)); | |
| pecSquare: CheckStatus(GdipSetPenEndCap(FGpPen, LineCapSquare)); | |
| pecFlat: CheckStatus(GdipSetPenEndCap(FGpPen, LineCapFlat)); | |
| end; | |
| FPenChanged := False; | |
| end; | |
| procedure TGDIPlusCanvas.UpdateFont; | |
| var | |
| style, fontSize: integer; | |
| fontName: UnicodeString; | |
| pFontFamily: GpFontFamily; | |
| begin | |
| style := FontStyleRegular; | |
| if fsBold in FLazFont.Style then style := style or FontStyleBold; | |
| if fsItalic in FLazFont.Style then style := style or FontStyleItalic; | |
| if fsUnderline in FLazFont.Style then style := style or FontStyleUnderline; | |
| if fsStrikeOut in FLazFont.Style then style := style or FontStyleStrikeout; | |
| if not FLazFont.IsDefault then | |
| begin | |
| fontName := UnicodeString(FLazFont.Name); | |
| fontSize := FLazFont.Size; | |
| end | |
| else | |
| begin | |
| fontName := 'Arial'; | |
| if FLazFont.Size <= 0 then fontSize := 10 else fontSize := FLazFont.Size; | |
| end; | |
| if (not FFontChanged) and | |
| (FGpFont <> nil) and | |
| (fontName = FLastFontName) and | |
| (fontSize = FLastFontSize) and | |
| (style = FLastFontStyle) then | |
| Exit; | |
| if (FGpFont <> nil) and | |
| (fontName = FLastFontName) and | |
| (fontSize = FLastFontSize) and | |
| (style = FLastFontStyle) then | |
| begin | |
| FFontChanged := False; | |
| Exit; | |
| end; | |
| if FGpFont <> nil then | |
| begin | |
| GdipDeleteFont(FGpFont); | |
| FGpFont := nil; | |
| end; | |
| if FFontFamily <> nil then | |
| begin | |
| GdipDeleteFontFamily(FFontFamily); | |
| FFontFamily := nil; | |
| end; | |
| if GdipCreateFontFamilyFromName(PWideChar(WideString(fontName)), nil, pFontFamily) <> Ok then | |
| begin | |
| fontName := 'Arial'; | |
| CheckStatus(GdipCreateFontFamilyFromName(PWideChar(WideString(fontName)), nil, pFontFamily)); | |
| end; | |
| FFontFamily := pFontFamily; | |
| CheckStatus(GdipCreateFont(FFontFamily, Single(fontSize), style, integer(UnitPoint), FGpFont)); | |
| // обновляем кэш-ключ | |
| FLastFontName := fontName; | |
| FLastFontSize := fontSize; | |
| FLastFontStyle := style; | |
| // сброс | |
| FFontChanged := False; | |
| // сброс кэша измерений текста — иначе при смене font “старые размеры” | |
| FLastMeasuredText := ''; | |
| FLastTextSize.cx := 0; | |
| FLastTextSize.cy := 0; | |
| end; | |
| // Drawing primitives implementation | |
| procedure TGDIPlusCanvas.DoLine(x1, y1, x2, y2: integer); | |
| begin | |
| UpdatePen; | |
| CheckStatus(GdipDrawLineI(FGpGraphics, FGpPen, x1, y1, x2, y2)); | |
| end; | |
| procedure TGDIPlusCanvas.DoCopyRect(x, y: integer; canvas: TFPCustomCanvas; | |
| const SourceRect: TRect); | |
| var | |
| srcDC, dstDC: HDC; | |
| w, h: integer; | |
| begin | |
| w := SourceRect.Right - SourceRect.Left; | |
| h := SourceRect.Bottom - SourceRect.Top; | |
| if (w <= 0) or (h <= 0) then Exit; | |
| // source | |
| if canvas is TGDIPlusCanvas then | |
| srcDC := TGDIPlusCanvas(canvas).Handle | |
| else if canvas is TCanvas then | |
| srcDC := TCanvas(canvas).Handle | |
| else | |
| raise Exception.Create('Unsupported canvas type for CopyRect'); | |
| // destination | |
| dstDC := Handle; | |
| BitBlt(dstDC, x, y, w, h, srcDC, SourceRect.Left, SourceRect.Top, SRCCOPY); | |
| if canvas is TGDIPlusCanvas then | |
| GdipReleaseDC(TGDIPlusCanvas(canvas).FGpGraphics, srcDC); | |
| GdipReleaseDC(FGpGraphics, dstDC); | |
| end; | |
| procedure TGDIPlusCanvas.MoveTo(X, Y: integer); | |
| begin | |
| FCurrentPos := Point(X, Y); | |
| end; | |
| procedure TGDIPlusCanvas.LineTo(X, Y: integer); | |
| begin | |
| DoLine(FCurrentPos.X, FCurrentPos.Y, X, Y); | |
| FCurrentPos := Point(X, Y); | |
| end; | |
| procedure TGDIPlusCanvas.ArcTo(ALeft, ATop, ARight, ABottom, SX, SY, EX, EY: integer); | |
| begin | |
| Arc(ALeft, ATop, ARight, ABottom, SX, SY, EX, EY); | |
| FCurrentPos := Point(EX, EY); | |
| end; | |
| procedure TGDIPlusCanvas.DrawToCanvas(DestCanvas: TCanvas; X, Y: integer); | |
| var | |
| bmpData: TBitmapData; | |
| bmpInfo: BITMAP; | |
| destBits: PByte; | |
| srcBits: PByte; | |
| row: integer; | |
| rect: TGPRect; | |
| begin | |
| if (DestCanvas = nil) or (FImage = nil) or (FBitmap = 0) then Exit; | |
| // Синхронизируем GpBitmap → HBITMAP | |
| rect.X := 0; | |
| rect.Y := 0; | |
| rect.Width := Width; | |
| rect.Height := Height; | |
| if GdipBitmapLockBits(FImage, @rect, ImageLockModeRead, PixelFormat32bppARGB, @bmpData) = Ok then | |
| begin | |
| try | |
| GetObject(FBitmap, SizeOf(bmpInfo), @bmpInfo); | |
| destBits := bmpInfo.bmBits; | |
| if destBits <> nil then | |
| begin | |
| srcBits := bmpData.Scan0; | |
| for row := 0 to Height - 1 do | |
| begin | |
| Move(srcBits^, destBits^, Width * 4); | |
| Inc(srcBits, bmpData.Stride); | |
| Inc(destBits, bmpInfo.bmWidthBytes); | |
| end; | |
| end; | |
| finally | |
| GdipBitmapUnlockBits(FImage, @bmpData); | |
| end; | |
| end; | |
| BitBlt(DestCanvas.Handle, X, Y, Width, Height, FMemDC, 0, 0, SRCCOPY); | |
| end; | |
| procedure TGDIPlusCanvas.DoPolyline(const points: array of TPoint); | |
| var | |
| gdiPoints: array of TGPPoint; | |
| i: integer; | |
| begin | |
| SetLength(gdiPoints, Length(points)); | |
| for i := 0 to High(points) do | |
| gdiPoints[i] := MakePoint(points[i].X, points[i].Y); | |
| UpdatePen; | |
| CheckStatus(GdipDrawLinesI(FGpGraphics, FGpPen, @gdiPoints[0], Length(points))); | |
| end; | |
| procedure TGDIPlusCanvas.DoPolygon(const points: array of TPoint); | |
| var | |
| gdiPoints: array of TGPPoint; | |
| i: integer; | |
| begin | |
| SetLength(gdiPoints, Length(points)); | |
| for i := 0 to High(points) do | |
| gdiPoints[i] := MakePoint(points[i].X, points[i].Y); | |
| UpdatePen; | |
| CheckStatus(GdipDrawPolygonI(FGpGraphics, FGpPen, @gdiPoints[0], Length(points))); | |
| end; | |
| procedure TGDIPlusCanvas.DoPolygonFill(const points: array of TPoint); | |
| var | |
| gdiPoints: array of TGPPoint; | |
| i: integer; | |
| begin | |
| SetLength(gdiPoints, Length(points)); | |
| for i := 0 to High(points) do | |
| gdiPoints[i] := MakePoint(points[i].X, points[i].Y); | |
| UpdateBrush; | |
| CheckStatus(GdipFillPolygonI(FGpGraphics, FGpBrush, @gdiPoints[0], | |
| Length(points), FillModeAlternate)); | |
| end; | |
| procedure TGDIPlusCanvas.DoFloodFill(x, y: integer); | |
| var | |
| ARegion: GpRegion; | |
| ABrush: GpBrush; | |
| begin | |
| GdipCreateRegion(ARegion); | |
| try | |
| GdipCreateSolidFill(FPColorToARGB(Brush.FPColor), ABrush); | |
| try | |
| GdipSetInfinite(ARegion); | |
| CheckStatus(GdipFillRegion(FGpGraphics, ABrush, ARegion)); | |
| finally | |
| GdipDeleteBrush(ABrush); | |
| end; | |
| finally | |
| GdipDeleteRegion(ARegion); | |
| end; | |
| end; | |
| // Image operations | |
| function TGDIPlusCanvas.CreateGDIPBitmap(Image: TFPCustomImage): GpBitmap; | |
| var | |
| x, y: integer; | |
| Data: array of ARGB; | |
| scan0: Pointer; | |
| begin | |
| SetLength(Data, Image.Width * Image.Height); | |
| for y := 0 to Image.Height - 1 do | |
| for x := 0 to Image.Width - 1 do | |
| Data[y * Image.Width + x] := FPColorToARGB(Image.Colors[x, y]); | |
| GdipCreateBitmapFromScan0(Image.Width, Image.Height, | |
| Image.Width * 4, PixelFormat32bppARGB, @Data[0], Result); | |
| end; | |
| procedure TGDIPlusCanvas.DoDraw(x, y: integer; const image: TFPCustomImage); | |
| var | |
| gdiBitmap: GpBitmap; | |
| begin | |
| gdiBitmap := CreateGDIPBitmap(image); | |
| try | |
| CheckStatus(GdipDrawImageI(FGpGraphics, gdiBitmap, x, y)); | |
| finally | |
| GdipDisposeImage(gdiBitmap); | |
| end; | |
| end; | |
| procedure TGDIPlusCanvas.StretchDraw(x, y, w, h: integer; Source: TFPCustomImage); | |
| var | |
| gdiBitmap: GpBitmap; | |
| destRect: TGPRect; | |
| begin | |
| gdiBitmap := CreateGDIPBitmap(Source); | |
| try | |
| destRect := MakeRect(x, y, w, h); | |
| CheckStatus(GdipDrawImageRectI(FGpGraphics, gdiBitmap, destRect.X, | |
| destRect.Y, destRect.Width, destRect.Height)); | |
| finally | |
| GdipDisposeImage(gdiBitmap); | |
| end; | |
| end; | |
| // Advanced shapes | |
| procedure TGDIPlusCanvas.DoRadialPie(x1, y1, x2, y2, StartAngle16Deg, | |
| Angle16DegLength: integer); | |
| var | |
| startAngle, sweepAngle: single; | |
| begin | |
| startAngle := StartAngle16Deg * 16.0; // Convert 16-degree units to degrees | |
| sweepAngle := Angle16DegLength * 16.0; | |
| UpdateBrush; | |
| CheckStatus(GdipFillPieI(FGpGraphics, FGpBrush, x1, y1, x2 - x1, | |
| y2 - y1, startAngle, sweepAngle)); | |
| end; | |
| procedure TGDIPlusCanvas.DoPolyBezier(Points: PPoint; NumPts: integer; | |
| Filled, Continuous: boolean); | |
| var | |
| gdiPoints: array of TGPPoint; | |
| i: integer; | |
| begin | |
| SetLength(gdiPoints, NumPts); | |
| for i := 0 to NumPts - 1 do | |
| gdiPoints[i] := MakePoint(Points[i].X, Points[i].Y); | |
| if Filled then | |
| begin | |
| GdipResetPath(FPath); | |
| CheckStatus(GdipAddPathBeziers(FPath, @gdiPoints[0], NumPts)); | |
| UpdateBrush; | |
| CheckStatus(GdipFillPath(FGpGraphics, FGpBrush, FPath)); | |
| end | |
| else | |
| begin | |
| UpdatePen; | |
| CheckStatus(GdipDrawBeziersI(FGpGraphics, FGpPen, @gdiPoints[0], NumPts)); | |
| end; | |
| end; | |
| // Clipping and region management | |
| procedure TGDIPlusCanvas.ApplyClipping; | |
| begin | |
| if FGpGraphics = nil then Exit; | |
| if Clipping and (FGpClipRegion <> nil) then | |
| CheckStatus(GdipSetClipRegion(FGpGraphics, FGpClipRegion, CombineModeReplace)) | |
| else | |
| CheckStatus(GdipResetClip(FGpGraphics)); | |
| end; | |
| procedure TGDIPlusCanvas.ApplyDrawingMode; | |
| begin | |
| if FGpGraphics = nil then Exit; | |
| case FCopyMode of | |
| cmSrcCopy: | |
| CheckStatus(GdipSetCompositingMode(FGpGraphics, CompositingModeSourceCopy)); | |
| else | |
| // Для остальных CopyMode точного соответствия в GDI+ нет, | |
| CheckStatus(GdipSetCompositingMode(FGpGraphics, CompositingModeSourceOver)); | |
| end; | |
| end; | |
| procedure TGDIPlusCanvas.ApplyInterpolation; | |
| begin | |
| if FGpGraphics = nil then Exit; | |
| CheckStatus(GdipSetInterpolationMode(FGpGraphics, InterpolationModeHighQualityBicubic)); | |
| CheckStatus(GdipSetPixelOffsetMode(FGpGraphics, PixelOffsetModeHighQuality)); | |
| end; | |
| function TGDIPlusCanvas.GetClipRect: TRect; | |
| var | |
| r: TGPRect; | |
| begin | |
| r := MakeRect(0, 0, 0, 0); | |
| CheckStatus(GdipGetClipBoundsI(FGpGraphics, @r)); | |
| Result := Classes.Rect(r.X, r.Y, r.X + r.Width, r.Y + r.Height); | |
| end; | |
| procedure TGDIPlusCanvas.SetClipRect(const AValue: TRect); | |
| var | |
| ARegion: GpRegion; | |
| ARect: TGPRect; | |
| begin | |
| ARect := MakeRect(AValue.Left, AValue.Top, | |
| AValue.Right - AValue.Left, AValue.Bottom - AValue.Top); | |
| CheckStatus(GdipCreateRegionRectI(@ARect, ARegion)); | |
| try | |
| CheckStatus(GdipSetClipRegion(FGpGraphics, ARegion, CombineModeReplace)); | |
| finally | |
| GdipDeleteRegion(ARegion); | |
| end; | |
| end; | |
| function TGDIPlusCanvas.GetClipping: boolean; | |
| begin | |
| Result := inherited GetClipping; | |
| end; | |
| procedure TGDIPlusCanvas.SetClipping(const AValue: boolean); | |
| begin | |
| inherited SetClipping(AValue); | |
| ApplyClipping; | |
| end; | |
| // Additional required methods | |
| procedure TGDIPlusCanvas.Erase; | |
| var | |
| color: ARGB; | |
| begin | |
| color := FPColorToARGB(Brush.FPColor); | |
| CheckStatus(GdipGraphicsClear(FGpGraphics, color)); | |
| end; | |
| function TGDIPlusCanvas.GetBitmap: HBITMAP; | |
| begin | |
| if FImage <> nil then | |
| begin | |
| GdipCreateHBITMAPFromBitmap(FImage, Result, 0); | |
| end | |
| else | |
| Result := 0; | |
| end; | |
| // Default resource creation | |
| function TGDIPlusCanvas.DoCreateDefaultFont: TFPCustomFont; | |
| begin | |
| Result := TFont.Create; | |
| Result.Name := 'Arial'; | |
| Result.Size := 12; | |
| end; | |
| function TGDIPlusCanvas.DoCreateDefaultPen: TFPCustomPen; | |
| begin | |
| Result := TPen.Create; | |
| end; | |
| function TGDIPlusCanvas.DoCreateDefaultBrush: TFPCustomBrush; | |
| begin | |
| Result := TBrush.Create; | |
| end; | |
| // Property accessors | |
| procedure TGDIPlusCanvas.SetColor(x, y: integer; const Value: TFPColor); | |
| var | |
| r, g, b: byte; | |
| col: TColor; | |
| begin | |
| r := Value.Red shr 8; | |
| g := Value.Green shr 8; | |
| b := Value.Blue shr 8; | |
| col := RGB(r, g, b); | |
| SetPixel(x, y, col); | |
| end; | |
| function TGDIPlusCanvas.GetColor(x, y: integer): TFPColor; | |
| var | |
| color: TColor; | |
| r, g, b: byte; | |
| begin | |
| color := GetPixel(x, y); | |
| r := GetRValue(color); | |
| g := GetGValue(color); | |
| b := GetBValue(color); | |
| Result.Red := r shl 8; | |
| Result.Green := g shl 8; | |
| Result.Blue := b shl 8; | |
| Result.Alpha := $FFFF; | |
| end; | |
| // Size management | |
| procedure TGDIPlusCanvas.SetHeight(AValue: integer); | |
| begin | |
| raise Exception.Create('SetHeight not supported for TGDIPlusCanvas'); | |
| end; | |
| function TGDIPlusCanvas.GetHeight: integer; | |
| var | |
| w, h: UINT; | |
| p: TPoint; | |
| begin | |
| if FImage <> nil then | |
| begin | |
| CheckStatus(GdipGetImageHeight(FImage, h)); | |
| Result := h; | |
| end | |
| else | |
| begin | |
| lclintf.GetDeviceSize(GetHandle, p); | |
| Result := p.y; | |
| end; | |
| end; | |
| procedure TGDIPlusCanvas.SetWidth(AValue: integer); | |
| begin | |
| raise Exception.Create('SetWidth not supported for TGDIPlusCanvas'); | |
| end; | |
| function TGDIPlusCanvas.GetWidth: integer; | |
| var | |
| w, h: UINT; | |
| p: TPoint; | |
| begin | |
| if FImage <> nil then | |
| begin | |
| CheckStatus(GdipGetImageWidth(FImage, w)); | |
| Result := w; | |
| end | |
| else | |
| begin | |
| lclintf.GetDeviceSize(GetHandle, p); | |
| Result := p.x; | |
| end; | |
| end; | |
| procedure TGDIPlusCanvas.DoTextOut(x, y: integer; Text: ansistring); | |
| var | |
| abrush: GpSolidFill; | |
| strFormat: GpStringFormat; | |
| rect: TGPRectF; | |
| wideText: widestring; | |
| color: ARGB; | |
| begin | |
| UpdateFont; | |
| wideText := WideString(Text); | |
| color := MakeColor(255, GetRValue(FLazFont.Color), GetGValue(FLazFont.Color), | |
| GetBValue(FLazFont.Color)); | |
| CheckStatus(GdipCreateSolidFill(color, abrush)); | |
| CheckStatus(GdipCreateStringFormat(0, 0, strFormat)); | |
| rect.X := x; | |
| rect.Y := y; | |
| rect.Width := 10000; | |
| rect.Height := 10000; | |
| try | |
| if FTextStyle.Alignment <> taLeftJustify then | |
| begin | |
| case FTextStyle.Alignment of | |
| taRightJustify: CheckStatus(GdipSetStringFormatAlign(strFormat, | |
| StringAlignmentFar)); | |
| taCenter: CheckStatus(GdipSetStringFormatAlign(strFormat, | |
| StringAlignmentCenter)); | |
| end; | |
| end; | |
| CheckStatus(GdipDrawString(FGpGraphics, pwidechar(wideText), | |
| Length(wideText), FGpFont, @rect, strFormat, abrush)); | |
| finally | |
| GdipDeleteStringFormat(strFormat); | |
| GdipDeleteBrush(abrush); | |
| end; | |
| end; | |
| procedure TGDIPlusCanvas.DoGetTextSize(Text: ansistring; var w, h: integer); | |
| var | |
| strFormat: GpStringFormat; | |
| rectF: TGPRectF; | |
| boundingBox: TGPRectF; | |
| wideText: widestring; | |
| begin | |
| if Text = '' then | |
| begin | |
| w := 0; | |
| h := 0; | |
| Exit; | |
| end; | |
| if Text = FLastMeasuredText then | |
| begin | |
| w := FLastTextSize.cx; | |
| h := FLastTextSize.cy; | |
| Exit; | |
| end; | |
| UpdateFont; | |
| wideText := WideString(Text); | |
| CheckStatus(GdipCreateStringFormat(0, 0, strFormat)); | |
| rectF := MakeRect(0.0, 0.0, 10000.0, 10000.0); | |
| CheckStatus(GdipMeasureString(FGpGraphics, pwidechar(wideText), Length(wideText), | |
| FGpFont, @rectF, strFormat, @boundingBox, nil, nil)); | |
| w := Ceil(boundingBox.Width); | |
| h := Ceil(boundingBox.Height); | |
| GdipDeleteStringFormat(strFormat); | |
| FLastMeasuredText := Text; | |
| FLastTextSize.cx := w; | |
| FLastTextSize.cy := h; | |
| end; | |
| function TGDIPlusCanvas.DoGetTextHeight(Text: ansistring): integer; | |
| var | |
| w, h: integer; | |
| begin | |
| DoGetTextSize(Text, w, h); | |
| Result := h; | |
| end; | |
| function TGDIPlusCanvas.DoGetTextWidth(Text: ansistring): integer; | |
| var | |
| w, h: integer; | |
| begin | |
| DoGetTextSize(Text, w, h); | |
| Result := w; | |
| end; | |
| procedure TGDIPlusCanvas.DoRectangle(const Bounds: TRect); | |
| begin | |
| UpdatePen; | |
| UpdateBrush; | |
| CheckStatus(GdipDrawRectangleI(FGpGraphics, FGpPen, Bounds.Left, | |
| Bounds.Top, Bounds.Right - Bounds.Left, Bounds.Bottom - Bounds.Top)); | |
| end; | |
| procedure TGDIPlusCanvas.DoRectangleFill(const Bounds: TRect); | |
| begin | |
| UpdatePen; | |
| UpdateBrush; | |
| CheckStatus(GdipFillRectangleI(FGpGraphics, FGpBrush, Bounds.Left, | |
| Bounds.Top, Bounds.Right - Bounds.Left, Bounds.Bottom - Bounds.Top)); | |
| end; | |
| procedure TGDIPlusCanvas.DoEllipse(const Bounds: TRect); | |
| begin | |
| UpdatePen; | |
| CheckStatus(GdipDrawEllipseI(FGpGraphics, FGpPen, Bounds.Left, | |
| Bounds.Top, Bounds.Right - Bounds.Left, Bounds.Bottom - Bounds.Top)); | |
| end; | |
| procedure TGDIPlusCanvas.DoEllipseFill(const Bounds: TRect); | |
| begin | |
| UpdateBrush; | |
| CheckStatus(GdipFillEllipseI(FGpGraphics, FGpBrush, Bounds.Left, | |
| Bounds.Top, Bounds.Right - Bounds.Left, Bounds.Bottom - Bounds.Top)); | |
| end; | |
| procedure TGDIPlusCanvas.Arc(ALeft, ATop, ARight, ABottom, Angle16Deg, Angle16DegLength: integer); | |
| var | |
| startAngle, sweepAngle: single; | |
| begin | |
| startAngle := Angle16Deg * 16.0; // Convert 16-degree units to degrees | |
| sweepAngle := Angle16DegLength * 16.0; | |
| UpdatePen; | |
| CheckStatus(GdipDrawArcI(FGpGraphics, FGpPen, ALeft, ATop, ARight - | |
| ALeft, ABottom - ATop, startAngle, sweepAngle)); | |
| end; | |
| procedure TGDIPlusCanvas.Arc(ALeft, ATop, ARight, ABottom, SX, SY, EX, EY: integer); | |
| var | |
| startAngle, sweepAngle: single; | |
| centerX, centerY: integer; | |
| startRad, endRad: double; | |
| begin | |
| centerX := (ALeft + ARight) div 2; | |
| centerY := (ATop + ABottom) div 2; | |
| // Calculate angles from center | |
| startRad := ArcTan2(SY - centerY, SX - centerX); | |
| endRad := ArcTan2(EY - centerY, EX - centerX); | |
| // Convert to degrees | |
| startAngle := startRad * 180 / PI; | |
| sweepAngle := ((endRad - startRad) * 180 / PI); | |
| // Ensure sweep is positive | |
| if sweepAngle <= 0 then | |
| sweepAngle := sweepAngle + 360; | |
| UpdatePen; | |
| CheckStatus(GdipDrawArcI(FGpGraphics, FGpPen, ALeft, ATop, ARight - | |
| ALeft, ABottom - ATop, startAngle, sweepAngle)); | |
| end; | |
| procedure TGDIPlusCanvas.AngleArc(X, Y: integer; Radius: longword; | |
| StartAngle, SweepAngle: single); | |
| begin | |
| UpdatePen; | |
| CheckStatus(GdipDrawArcI(FGpGraphics, FGpPen, X - Radius, Y - | |
| Radius, Radius * 2, Radius * 2, StartAngle, SweepAngle)); | |
| end; | |
| // Chord methods | |
| procedure TGDIPlusCanvas.Chord(x1, y1, x2, y2, Angle16Deg, Angle16DegLength: integer); | |
| var | |
| startAngle, sweepAngle: single; | |
| begin | |
| startAngle := Angle16Deg * 16.0; // Convert 16-degree units to degrees | |
| sweepAngle := Angle16DegLength * 16.0; | |
| UpdatePen(); | |
| CheckStatus(GdipDrawPieI(FGpGraphics, FGpPen, x1, y1, x2 - x1, y2 - | |
| y1, startAngle, sweepAngle)); | |
| end; | |
| procedure TGDIPlusCanvas.Chord(x1, y1, x2, y2, SX, SY, EX, EY: integer); | |
| var | |
| startAngle, sweepAngle: single; | |
| centerX, centerY: integer; | |
| startRad, endRad: double; | |
| begin | |
| centerX := (x1 + x2) div 2; | |
| centerY := (y1 + y2) div 2; | |
| // Calculate angles from center | |
| startRad := ArcTan2(SY - centerY, SX - centerX); | |
| endRad := ArcTan2(EY - centerY, EX - centerX); | |
| // Convert to degrees | |
| startAngle := startRad * 180 / PI; | |
| sweepAngle := ((endRad - startRad) * 180 / PI); | |
| // Ensure sweep is positive | |
| if sweepAngle <= 0 then | |
| sweepAngle := sweepAngle + 360; | |
| UpdatePen(); | |
| UpdateBrush(); | |
| CheckStatus(GdipDrawPieI(FGpGraphics, FGpPen, x1, y1, x2 - x1, y2 - | |
| y1, startAngle, sweepAngle)); | |
| end; | |
| // BrushCopy method | |
| procedure TGDIPlusCanvas.BrushCopy(ADestRect: TRect; ABitmap: Graphics.TBitmap; | |
| ASourceRect: TRect; ATransparentColor: TColor); | |
| var | |
| gdiBitmap: GpBitmap; | |
| destRect, srcRect: TGPRect; | |
| imageAttr: GpImageAttributes; | |
| colorMatrix: TColorMatrix; | |
| r, g, b: byte; | |
| begin | |
| CheckStatus(GdipCreateBitmapFromHBITMAP(ABitmap.Handle, 0, gdiBitmap)); | |
| try | |
| // Set up source and destination rectangles | |
| srcRect := MakeRect(ASourceRect.Left, ASourceRect.Top, ASourceRect.Right - | |
| ASourceRect.Left, ASourceRect.Bottom - ASourceRect.Top); | |
| destRect := MakeRect(ADestRect.Left, ADestRect.Top, ADestRect.Right - | |
| ADestRect.Left, ADestRect.Bottom - ADestRect.Top); | |
| // Set up image attributes for transparency | |
| CheckStatus(GdipCreateImageAttributes(imageAttr)); | |
| try | |
| // Set transparent color | |
| r := GetRValue(ATransparentColor); | |
| g := GetGValue(ATransparentColor); | |
| b := GetBValue(ATransparentColor); | |
| CheckStatus(GdipSetImageAttributesColorKeys(imageAttr, | |
| ColorAdjustTypeBitmap, True, MakeColor(255, r, g, b), | |
| MakeColor(255, r, g, b))); | |
| // Draw bitmap with transparency | |
| CheckStatus(GdipDrawImageRectRectI(FGpGraphics, gdiBitmap, | |
| destRect.X, destRect.Y, destRect.Width, destRect.Height, | |
| srcRect.X, srcRect.Y, srcRect.Width, srcRect.Height, UnitPixel, | |
| imageAttr, nil, nil)); | |
| finally | |
| GdipDisposeImageAttributes(imageAttr); | |
| end; | |
| finally | |
| GdipDisposeImage(gdiBitmap); | |
| end; | |
| end; | |
| // CopyRect method (different from DoCopyRect) | |
| procedure TGDIPlusCanvas.CopyRect(const Dest: TRect; SrcCanvas: TCanvas; const Source: TRect); | |
| var | |
| srcDC: HDC; | |
| w, h, destWidth, destHeight: integer; | |
| Bitmap: GpBitmap; | |
| begin | |
| // Get dimensions | |
| w := Source.Right - Source.Left; | |
| h := Source.Bottom - Source.Top; | |
| destWidth := Dest.Right - Dest.Left; | |
| destHeight := Dest.Bottom - Dest.Top; | |
| // Get source DC | |
| srcDC := SrcCanvas.Handle; | |
| // Create a bitmap from the source DC | |
| CheckStatus(GdipCreateBitmapFromHBITMAP(srcDC, 0, Bitmap)); | |
| try | |
| // Draw the bitmap with stretching if necessary | |
| CheckStatus(GdipDrawImageRectRectI(FGpGraphics, Bitmap, Dest.Left, | |
| Dest.Top, destWidth, destHeight, Source.Left, Source.Top, w, | |
| h, UnitPixel, nil, nil, nil)); | |
| finally | |
| GdipDisposeImage(Bitmap); | |
| end; | |
| end; | |
| // Draw method | |
| procedure TGDIPlusCanvas.Draw(X, Y: integer; SrcGraphic: TGraphic); | |
| var | |
| Bitmap: GpBitmap; | |
| tempBitmap: Graphics.TBitmap; | |
| begin | |
| if SrcGraphic is Graphics.TBitmap then | |
| begin | |
| CheckStatus(GdipCreateBitmapFromHBITMAP(Graphics.TBitmap(SrcGraphic).Handle, | |
| 0, Bitmap)); | |
| try | |
| CheckStatus(GdipDrawImageI(FGpGraphics, Bitmap, X, Y)); | |
| finally | |
| GdipDisposeImage(Bitmap); | |
| end; | |
| end | |
| else | |
| begin | |
| tempBitmap := Graphics.TBitmap.Create; | |
| try | |
| tempBitmap.Width := SrcGraphic.Width; | |
| tempBitmap.Height := SrcGraphic.Height; | |
| tempBitmap.Canvas.Draw(0, 0, SrcGraphic); | |
| CheckStatus(GdipCreateBitmapFromHBITMAP(tempBitmap.Handle, 0, Bitmap)); | |
| try | |
| CheckStatus(GdipDrawImageI(FGpGraphics, Bitmap, X, Y)); | |
| finally | |
| GdipDisposeImage(Bitmap); | |
| end; | |
| finally | |
| tempBitmap.Free; | |
| end; | |
| end; | |
| end; | |
| // DrawFocusRect method | |
| procedure TGDIPlusCanvas.DrawFocusRect(const ARect: TRect); | |
| var | |
| oldPen: GpPen; | |
| dashArray: array[0..1] of single; | |
| begin | |
| oldPen := FGpPen; | |
| CheckStatus(GdipCreatePen1(MakeColor(255, 0, 0, 0), 1, UnitPixel, FGpPen)); | |
| dashArray[0] := 1.0; | |
| dashArray[1] := 1.0; | |
| CheckStatus(GdipSetPenDashArray(FGpPen, @dashArray[0], 2)); | |
| CheckStatus(GdipDrawRectangleI(FGpGraphics, FGpPen, ARect.Left, | |
| ARect.Top, ARect.Right - ARect.Left, ARect.Bottom - ARect.Top)); | |
| GdipDeletePen(FGpPen); | |
| FGpPen := oldPen; | |
| end; | |
| // StretchDraw with TRect parameter | |
| procedure TGDIPlusCanvas.StretchDraw(const DestRect: TRect; SrcGraphic: TGraphic); | |
| var | |
| Bitmap: GpBitmap; | |
| tempBitmap: TBitmap; | |
| w, h: integer; | |
| begin | |
| w := DestRect.Right - DestRect.Left; | |
| h := DestRect.Bottom - DestRect.Top; | |
| if SrcGraphic is TBitmap then | |
| begin | |
| CheckStatus(GdipCreateBitmapFromHBITMAP(TBitmap(SrcGraphic).Handle, 0, Bitmap)); | |
| try | |
| CheckStatus(GdipDrawImageRectI(FGpGraphics, Bitmap, DestRect.Left, | |
| DestRect.Top, w, h)); | |
| finally | |
| GdipDisposeImage(Bitmap); | |
| end; | |
| end | |
| else | |
| begin | |
| tempBitmap := TBitmap.Create; | |
| try | |
| tempBitmap.Width := SrcGraphic.Width; | |
| tempBitmap.Height := SrcGraphic.Height; | |
| tempBitmap.Canvas.Draw(0, 0, SrcGraphic); | |
| CheckStatus(GdipCreateBitmapFromHBITMAP(tempBitmap.Handle, 0, Bitmap)); | |
| try | |
| CheckStatus(GdipDrawImageRectI(FGpGraphics, Bitmap, | |
| DestRect.Left, DestRect.Top, w, h)); | |
| finally | |
| GdipDisposeImage(Bitmap); | |
| end; | |
| finally | |
| tempBitmap.Free; | |
| end; | |
| end; | |
| end; | |
| // Additional rectangle and ellipse methods with TRect parameter | |
| procedure TGDIPlusCanvas.Ellipse(const ARect: TRect); | |
| begin | |
| UpdatePen; | |
| UpdateBrush; | |
| Ellipse(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom); | |
| end; | |
| procedure TGDIPlusCanvas.Ellipse(x1, y1, x2, y2: integer); | |
| begin | |
| DoEllipse(Rect(x1, y1, x2, y2)); | |
| end; | |
| // Fill methods | |
| procedure TGDIPlusCanvas.FillRect(const ARect: TRect); | |
| begin | |
| UpdateBrush; | |
| CheckStatus(GdipFillRectangleI(FGpGraphics, FGpBrush, ARect.Left, | |
| ARect.Top, ARect.Right - ARect.Left, ARect.Bottom - ARect.Top)); | |
| end; | |
| procedure TGDIPlusCanvas.FillRect(X1, Y1, X2, Y2: integer); | |
| begin | |
| FillRect(Classes.Rect(X1, Y1, X2, Y2)); | |
| end; | |
| procedure TGDIPlusCanvas.FloodFill(X, Y: integer; FillColor: TColor; | |
| FillStyle: TFillStyle); | |
| var | |
| ABrush: GpBrush; | |
| begin | |
| CheckStatus(GdipCreateSolidFill(MakeColor(255, GetRValue(FillColor), | |
| GetGValue(FillColor), GetBValue(FillColor)), ABrush)); | |
| CheckStatus(GdipFillRectangleI(FGpGraphics, ABrush, X, Y, GetWidth - | |
| X, GetHeight - Y)); | |
| GdipDeleteBrush(ABrush); | |
| end; | |
| procedure TGDIPlusCanvas.Frame3d(var ARect: TRect; const FrameWidth: integer; | |
| const Style: TGraphicsBevelCut); | |
| begin | |
| UpdatePen; | |
| CheckStatus(GdipDrawRectangleI(FGpGraphics, FGpPen, ARect.Left, | |
| ARect.Top, ARect.Width, ARect.Height)); | |
| InflateRect(ARect, -FrameWidth, -FrameWidth); | |
| end; | |
| procedure TGDIPlusCanvas.Frame3D(var ARect: TRect; TopColor, BottomColor: TColor; | |
| const FrameWidth: integer); | |
| var | |
| TopBrush, BottomBrush: GpBrush; | |
| begin | |
| CheckStatus(GdipCreateSolidFill(MakeColor(255, GetRValue(TopColor), | |
| GetGValue(TopColor), GetBValue(TopColor)), TopBrush)); | |
| CheckStatus(GdipCreateSolidFill(MakeColor(255, GetRValue(BottomColor), | |
| GetGValue(BottomColor), GetBValue(BottomColor)), BottomBrush)); | |
| CheckStatus(GdipFillRectangleI(FGpGraphics, TopBrush, ARect.Left, | |
| ARect.Top, ARect.Width, FrameWidth)); | |
| CheckStatus(GdipFillRectangleI(FGpGraphics, BottomBrush, ARect.Left, | |
| ARect.Bottom - FrameWidth, ARect.Width, FrameWidth)); | |
| GdipDeleteBrush(TopBrush); | |
| GdipDeleteBrush(BottomBrush); | |
| InflateRect(ARect, -FrameWidth, -FrameWidth); | |
| end; | |
| procedure TGDIPlusCanvas.Frame(const ARect: TRect); | |
| begin | |
| UpdatePen; | |
| CheckStatus(GdipDrawRectangleI(FGpGraphics, FGpPen, ARect.Left, | |
| ARect.Top, ARect.Width, ARect.Height)); | |
| end; | |
| procedure TGDIPlusCanvas.Frame(X1, Y1, X2, Y2: integer); | |
| begin | |
| Frame(Rect(X1, Y1, X2, Y2)); | |
| end; | |
| procedure TGDIPlusCanvas.FrameRect(const ARect: TRect); | |
| begin | |
| UpdatePen; | |
| UpdateBrush; | |
| CheckStatus(GdipFillRectangleI(FGpGraphics, FGpBrush, ARect.Left, | |
| ARect.Top, ARect.Width, 1)); | |
| CheckStatus(GdipFillRectangleI(FGpGraphics, FGpBrush, ARect.Left, | |
| ARect.Bottom - 1, ARect.Width, 1)); | |
| CheckStatus(GdipFillRectangleI(FGpGraphics, FGpBrush, ARect.Left, | |
| ARect.Top, 1, ARect.Height)); | |
| CheckStatus(GdipFillRectangleI(FGpGraphics, FGpBrush, ARect.Right - 1, | |
| ARect.Top, 1, ARect.Height)); | |
| end; | |
| procedure TGDIPlusCanvas.FrameRect(X1, Y1, X2, Y2: integer); | |
| begin | |
| FrameRect(Rect(X1, Y1, X2, Y2)); | |
| end; | |
| function TGDIPlusCanvas.GetTextMetrics(out TM: TLCLTextMetric): boolean; | |
| begin | |
| FillChar(TM, SizeOf(TM), 0); | |
| TM.Height := DoGetTextHeight('Wg'); | |
| Result := True; | |
| end; | |
| procedure TGDIPlusCanvas.GradientFill(ARect: TRect; AStart, AStop: TColor; | |
| ADirection: TGradientDirection); | |
| var | |
| LinBrush: GpLineGradient; | |
| StartColor, EndColor: ARGB; | |
| RectF: TGPRectF; | |
| Mode: TLinearGradientMode; | |
| begin | |
| StartColor := MakeColor(255, GetRValue(AStart), GetGValue(AStart), GetBValue(AStart)); | |
| EndColor := MakeColor(255, GetRValue(AStop), GetGValue(AStop), GetBValue(AStop)); | |
| RectF := MakeRect(single(ARect.Left), single(ARect.Top), single(ARect.Right - ARect.Left), | |
| single(ARect.Bottom - ARect.Top)); | |
| if ADirection = gdHorizontal then | |
| Mode := LinearGradientModeHorizontal | |
| else | |
| Mode := LinearGradientModeVertical; | |
| CheckStatus(GdipCreateLineBrushFromRect(@RectF, StartColor, EndColor, | |
| Mode, WrapModeTile, LinBrush)); | |
| try | |
| CheckStatus(GdipFillRectangle(FGpGraphics, LinBrush, RectF.X, | |
| RectF.Y, RectF.Width, RectF.Height)); | |
| finally | |
| GdipDeleteBrush(LinBrush); | |
| end; | |
| end; | |
| procedure TGDIPlusCanvas.Pie(EllipseX1, EllipseY1, EllipseX2, EllipseY2, | |
| StartX, StartY, EndX, EndY: integer); | |
| var | |
| StartAngle, SweepAngle: single; | |
| CenterX, CenterY: single; | |
| begin | |
| CenterX := (EllipseX1 + EllipseX2) / 2; | |
| CenterY := (EllipseY1 + EllipseY2) / 2; | |
| StartAngle := ArcTan2(StartY - CenterY, StartX - CenterX) * 180 / Pi; | |
| SweepAngle := ArcTan2(EndY - CenterY, EndX - CenterX) * 180 / Pi - StartAngle; | |
| if SweepAngle < 0 then SweepAngle := SweepAngle + 360; | |
| UpdateBrush; | |
| CheckStatus(GdipFillPieI(FGpGraphics, FGpBrush, EllipseX1, EllipseY1, | |
| EllipseX2 - EllipseX1, EllipseY2 - EllipseY1, StartAngle, SweepAngle)); | |
| end; | |
| procedure TGDIPlusCanvas.PolyBezier(const Points: array of TPoint; | |
| Filled: boolean = False; Continuous: boolean = True); | |
| begin | |
| DoPolyBezier(@Points[0], Length(Points), Filled, Continuous); | |
| end; | |
| procedure TGDIPlusCanvas.Polygon(const Points: array of TPoint; Winding: boolean; | |
| StartIndex: integer = 0; NumPts: integer = -1); | |
| var | |
| GdiPoints: array of TGPPoint; | |
| i, Count: integer; | |
| AFillMode: FillMode; | |
| begin | |
| if NumPts < 0 then NumPts := Length(Points) - StartIndex; | |
| SetLength(GdiPoints, NumPts); | |
| for i := 0 to NumPts - 1 do | |
| GdiPoints[i] := MakePoint(Points[StartIndex + i].X, Points[StartIndex + i].Y); | |
| UpdateBrush; | |
| if Winding then | |
| AFillMode := FillModeWinding | |
| else | |
| AFillMode := FillModeAlternate; | |
| CheckStatus(GdipFillPolygonI(FGpGraphics, FGpBrush, @GdiPoints[0], NumPts, AFillMode)); | |
| end; | |
| procedure TGDIPlusCanvas.Polygon(Points: PPoint; NumPts: integer; | |
| Winding: boolean = False); | |
| var | |
| GdiPoints: array of TGPPoint; | |
| AFillMode: FillMode; | |
| i: integer; | |
| begin | |
| UpdateBrush; | |
| if Winding then | |
| AFillMode := FillModeWinding | |
| else | |
| AFillMode := FillModeAlternate; | |
| SetLength(GdiPoints, NumPts); | |
| for i := 0 to NumPts - 1 do | |
| begin | |
| GdiPoints[i].X := Points[i].X; | |
| GdiPoints[i].Y := Points[i].Y; | |
| end; | |
| CheckStatus(GdipFillPolygonI(FGpGraphics, FGpBrush, @GdiPoints[0], NumPts, AFillMode)); | |
| end; | |
| procedure TGDIPlusCanvas.Polygon(const Points: array of TPoint); | |
| begin | |
| DoPolygon(Points); | |
| end; | |
| procedure TGDIPlusCanvas.Polyline(const Points: array of TPoint; | |
| StartIndex: integer; NumPts: integer = -1); | |
| var | |
| GdiPoints: array of TGPPoint; | |
| i, Count: integer; | |
| begin | |
| if NumPts < 0 then NumPts := Length(Points) - StartIndex; | |
| SetLength(GdiPoints, NumPts); | |
| for i := 0 to NumPts - 1 do | |
| GdiPoints[i] := MakePoint(Points[StartIndex + i].X, Points[StartIndex + i].Y); | |
| UpdatePen; | |
| CheckStatus(GdipDrawLinesI(FGpGraphics, FGpPen, @GdiPoints[0], NumPts)); | |
| end; | |
| procedure TGDIPlusCanvas.Polyline(Points: PPoint; NumPts: integer); | |
| var | |
| GdiPoints: array of TGPPoint; | |
| i: integer; | |
| begin | |
| UpdatePen; | |
| SetLength(GdiPoints, NumPts); | |
| for i := 0 to NumPts - 1 do | |
| begin | |
| GdiPoints[i].X := Points[i].X; | |
| GdiPoints[i].Y := Points[i].Y; | |
| end; | |
| CheckStatus(GdipDrawLinesI(FGpGraphics, FGpPen, @GdiPoints[0], NumPts)); | |
| end; | |
| procedure TGDIPlusCanvas.Polyline(const Points: array of TPoint); | |
| begin | |
| DoPolyline(Points); | |
| end; | |
| procedure TGDIPlusCanvas.Rectangle(X1, Y1, X2, Y2: integer); | |
| begin | |
| DoRectangle(Rect(X1, Y1, X2, Y2)); | |
| end; | |
| procedure TGDIPlusCanvas.Rectangle(const ARect: TRect); | |
| begin | |
| UpdatePen; | |
| UpdateBrush; | |
| if (self.Brush.Style <> bsClear) and (self.Pen.Style = psClear) then | |
| DoRectangleFill(ARect) | |
| else | |
| DoRectangle(ARect); | |
| end; | |
| procedure TGDIPlusCanvas.RoundRect(X1, Y1, X2, Y2: integer; RX, RY: integer); | |
| var | |
| Path: GpPath; | |
| W, H: integer; | |
| begin | |
| UpdatePen; | |
| UpdateBrush; | |
| W := X2 - X1; | |
| H := Y2 - Y1; | |
| if RX > W div 2 then RX := W div 2; | |
| if RY > H div 2 then RY := H div 2; | |
| CheckStatus(GdipCreatePath(FillModeAlternate, Path)); | |
| try | |
| CheckStatus(GdipAddPathLineI(Path, X1 + RX, Y1, X2 - RX, Y1)); | |
| CheckStatus(GdipAddPathArcI(Path, X2 - 2 * RX, Y1, 2 * RX, 2 * RY, 270, 90)); | |
| CheckStatus(GdipAddPathLineI(Path, X2, Y1 + RY, X2, Y2 - RY)); | |
| CheckStatus(GdipAddPathArcI(Path, X2 - 2 * RX, Y2 - 2 * RY, 2 * RX, 2 * RY, 0, 90)); | |
| CheckStatus(GdipAddPathLineI(Path, X2 - RX, Y2, X1 + RX, Y2)); | |
| CheckStatus(GdipAddPathArcI(Path, X1, Y2 - 2 * RY, 2 * RX, 2 * RY, 90, 90)); | |
| CheckStatus(GdipAddPathLineI(Path, X1, Y2 - RY, X1, Y1 + RY)); | |
| CheckStatus(GdipAddPathArcI(Path, X1, Y1, 2 * RX, 2 * RY, 180, 90)); | |
| CheckStatus(GdipClosePathFigure(Path)); | |
| CheckStatus(GdipDrawPath(FGpGraphics, FGpPen, Path)); | |
| if (Brush.Style <> bsClear) and (FGpBrush <> nil) then | |
| CheckStatus(GdipFillPath(FGpGraphics, FGpBrush, Path)); | |
| finally | |
| GdipDeletePath(Path); | |
| end; | |
| end; | |
| procedure TGDIPlusCanvas.RoundRect(const Rect: TRect; RX, RY: integer); | |
| begin | |
| RoundRect(Rect.Left, Rect.Top, Rect.Right, Rect.Bottom, RX, RY); | |
| end; | |
| procedure TGDIPlusCanvas.TextOut(X, Y: integer; const Text: string); | |
| begin | |
| DoTextOut(X, Y, ansistring(Text)); | |
| end; | |
| procedure TGDIPlusCanvas.TextRect(const ARect: TRect; X, Y: integer; const Text: string); | |
| begin | |
| TextRect(ARect, X, Y, Text, FTextStyle); | |
| end; | |
| procedure TGDIPlusCanvas.TextRect(ARect: TRect; X, Y: integer; | |
| const Text: string; const Style: TTextStyle); | |
| var | |
| gBrush: GpSolidFill; | |
| StrFormat: GpStringFormat; | |
| RectF: TGPRectF; | |
| WideText: widestring; | |
| Color: ARGB; | |
| begin | |
| UpdateFont; | |
| WideText := WideString(Text); | |
| Color := MakeColor(255, GetRValue(FLazFont.Color), GetGValue(FLazFont.Color), | |
| GetBValue(FLazFont.Color)); | |
| CheckStatus(GdipCreateSolidFill(Color, gBrush)); | |
| CheckStatus(GdipCreateStringFormat(0, 0, StrFormat)); | |
| RectF := MakeRect(single(ARect.Left), single(ARect.Top), single(ARect.Right - ARect.Left), | |
| single(ARect.Bottom - ARect.Top)); | |
| //RectF := MakeRect(single(ARect.Left), single(ARect.Top), single(ARect.Width), | |
| //single(ARect.Height)); | |
| if Style.Alignment = taRightJustify then | |
| CheckStatus(GdipSetStringFormatAlign(StrFormat, StringAlignmentFar)) | |
| else if Style.Alignment = taCenter then | |
| CheckStatus(GdipSetStringFormatAlign(StrFormat, StringAlignmentCenter)); | |
| if Style.Layout = tlBottom then | |
| CheckStatus(GdipSetStringFormatLineAlign(StrFormat, StringAlignmentFar)) | |
| else if Style.Layout = tlCenter then | |
| CheckStatus(GdipSetStringFormatLineAlign(StrFormat, StringAlignmentCenter)); | |
| if Style.Clipping then | |
| CheckStatus(GdipSetStringFormatFlags(StrFormat, StringFormatFlagsNoWrap or | |
| StringFormatFlagsNoClip)); | |
| CheckStatus(GdipDrawString(FGpGraphics, pwidechar(WideText), | |
| Length(WideText), FGpFont, @RectF, StrFormat, gBrush)); | |
| GdipDeleteStringFormat(StrFormat); | |
| GdipDeleteBrush(gBrush); | |
| end; | |
| function TGDIPlusCanvas.TextExtent(const Text: string): TSize; | |
| var | |
| W, H: integer; | |
| begin | |
| DoGetTextSize(ansistring(Text), W, H); | |
| Result.cx := W; | |
| Result.cy := H; | |
| end; | |
| function TGDIPlusCanvas.TextHeight(const Text: string): integer; | |
| begin | |
| Result := DoGetTextHeight(ansistring(Text)); | |
| end; | |
| function TGDIPlusCanvas.TextWidth(const Text: string): integer; | |
| begin | |
| Result := DoGetTextWidth(ansistring(Text)); | |
| end; | |
| function TGDIPlusCanvas.TextFitInfo(const Text: string; MaxWidth: integer): integer; | |
| var | |
| WideText: widestring; | |
| StrFormat: GpStringFormat; | |
| RectF: TGPRectF; | |
| CharsFitted, LinesFilled: integer; | |
| begin | |
| UpdateFont; | |
| WideText := WideString(Text); | |
| CheckStatus(GdipCreateStringFormat(0, 0, StrFormat)); | |
| RectF := MakeRect(single(0), single(0), single(MaxWidth), single(10000)); | |
| CheckStatus(GdipMeasureString(FGpGraphics, pwidechar(WideText), | |
| Length(WideText), FGpFont, @RectF, StrFormat, nil, @CharsFitted, @LinesFilled)); | |
| Result := CharsFitted; | |
| GdipDeleteStringFormat(StrFormat); | |
| end; | |
| procedure TGDIPlusCanvas.Lock; | |
| begin | |
| EnterCriticalSection(FLock); | |
| end; | |
| procedure TGDIPlusCanvas.Unlock; | |
| begin | |
| LeaveCriticalSection(FLock); | |
| end; | |
| function TGDIPlusCanvas.TryLock: Boolean; | |
| begin | |
| Result := TryEnterCriticalSection(FLock); | |
| end; | |
| procedure TGDIPlusCanvas.Refresh; | |
| begin | |
| if FAutoRedraw then RealizeAutoRedraw; | |
| end; | |
| procedure TGDIPlusCanvas.SaveHandleState; | |
| begin | |
| CheckStatus(GdipSaveGraphics(FGpGraphics, FStateToken)); | |
| end; | |
| procedure TGDIPlusCanvas.RestoreHandleState; | |
| begin | |
| CheckStatus(GdipRestoreGraphics(FGpGraphics, FStateToken)); | |
| end; | |
| function TGDIPlusCanvas.HandleAllocated: boolean; | |
| begin | |
| Result := (FGpGraphics <> nil); | |
| end; | |
| procedure TGDIPlusCanvas.RealizeAntialiasing; | |
| begin | |
| CheckStatus(GdipSetSmoothingMode(FGpGraphics, SmoothingModeAntiAlias)); | |
| CheckStatus(GdipSetTextRenderingHint(FGpGraphics, TextRenderingHintClearTypeGridFit)); | |
| //CheckStatus(GdipSetTextRenderingHint(FGpGraphics, TextRenderingHintAntiAlias)); | |
| end; | |
| procedure TGDIPlusCanvas.RealizeAutoRedraw; | |
| var | |
| w, h: Integer; | |
| begin | |
| if not FAutoRedraw then Exit; | |
| if (FMemDC = 0) or (FBitmap = 0) then Exit; | |
| if (FPresentDC = 0) then Exit; | |
| w := GetWidth; | |
| h := GetHeight; | |
| if (w <= 0) or (h <= 0) then Exit; | |
| BitBlt(FPresentDC, FPresentX, FPresentY, w, h, FMemDC, 0, 0, SRCCOPY); | |
| end; | |
| initialization | |
| StartupInput.GdiplusVersion := 1; | |
| GdiplusStartup(GdiPlusToken, @StartupInput, nil); | |
| finalization | |
| GdiplusShutdown(GdiPlusToken); | |
| {$ENDIF} | |
| end. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment