XylemFlow 8 Posted November 22, 2022 TCanvas doesn't appear to provide a way to switch between 'alternate' and 'winding' fill modes. The default is alternate. This is important when paths intersect themselves and prevents me rendering certain objects how I would like. I created a new feature request for this on the quality page a while ago. It got the following comment. Quote Under Windows, it's easy to fix for D2D Canvas the fill mode can be set by a call to Path.SetFillMode(D2D1_FILL_MODE_WINDING); for GDIP there's also an option P := TGPGraphicPath.Create(FillModeWinding); for GPU Canvas, it's a bit more complex, the rendering is based on a Stencill Buffer in "invert" mode...it's possible to change that but you have to take into account the clockwise direction of the lines to know when the stencill buffer must be enabled or disabled. It's also possible to use Tesselation. I'd like to try to do that as my application is currently Windows only. However, how do I actually go about calling implementation specific functions like Path.SetFillMode(D2D1_FILL_MODE_WINDING) in my code? I know how to find out the current implementation of a TCanvas with ACanvas.ClassName. Share this post Link to post
XylemFlow 8 Posted November 23, 2022 I made some progress on this by copying FMX.Canvas.D2D.pas locally and adding Path.SetFillMode(D2D1_FILL_MODE_WINDING); in TCanvasD2D.CreatePathGeometry just after Geometry.Open(Path). It works but modifying Delphi code is a horrible solution and to be able to switch fill mode at run time I would also need to add a property for it in TCanvas in FMX.Graphics.pas. Then I would need to modify FMX.Canvas.GDI.pas and FMX.Canvas.GPU.pas to make the same change. It's not a huge amount of work though. The interface for fill mode is already there. I wonder why they didn't implement such a basic feature? Share this post Link to post
Lajos Juhász 293 Posted November 23, 2022 2 hours ago, XylemFlow said: I wonder why they didn't implement such a basic feature? Most probably there was no QP ticket to implement this logic? Share this post Link to post
vfbb 285 Posted November 23, 2022 @XylemFlow You can use the Skia4Delphi for that. I made a small example of how to do this: unit Unit1; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs; type TForm1 = class(TForm) procedure FormPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.fmx} uses {$IFDEF SKIA} Skia, Skia.FMX, Skia.FMX.Graphics, {$ENDIF} System.Math; {$IFDEF SKIA} procedure DrawPath(const ACanvas: ISkCanvas; const APathData: TPathData; const AFillType: TSkPathFillType; const AFill: TBrush; const AStroke: TStrokeBrush); var LPaint: ISkPaint; LPath: ISkPath; LPathBuilder: ISkPathBuilder; begin LPathBuilder := TSkPathBuilder.Create(AFillType); LPathBuilder.AddPath(APathData.ToSkPath); LPath := LPathBuilder.Detach; if (AFill <> nil) and (AFill.Kind = TBrushKind.Solid) then begin LPaint := TSkPaint.Create; LPaint.AntiAlias := True; LPaint.Color := AFill.Color; ACanvas.DrawPath(LPath, LPaint); end; if (AStroke <> nil) and (AStroke.Kind = TBrushKind.Solid) then begin LPaint := TSkPaint.Create(TSkPaintStyle.Stroke); LPaint.AntiAlias := True; LPaint.Color := AStroke.Color; LPaint.StrokeWidth := AStroke.Thickness; ACanvas.DrawPath(LPath, LPaint); end; end; {$ENDIF} procedure TForm1.FormPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF); function CircleRect(const ACenterX, ACenterY, ARadius: Single): TRectF; begin Result := TRectF.Create(ACenterX - ARadius, ACenterY - ARadius, ACenterX + ARadius, ACenterY + ARadius); end; var LBitmap: TBitmap; LPathData: TPathData; LRadius: Single; begin LRadius := Min(ARect.Width, ARect.Height) / 4; LPathData := TPathData.Create; LPathData.AddEllipse(CircleRect(ARect.CenterPoint.X - LRadius / 2, ARect.CenterPoint.Y - LRadius / 2, LRadius)); LPathData.AddEllipse(CircleRect(ARect.CenterPoint.X - LRadius / 2, ARect.CenterPoint.Y + LRadius / 2, LRadius)); LPathData.AddEllipse(CircleRect(ARect.CenterPoint.X + LRadius / 2, ARect.CenterPoint.Y - LRadius / 2, LRadius)); LPathData.AddEllipse(CircleRect(ARect.CenterPoint.X + LRadius / 2, ARect.CenterPoint.Y + LRadius / 2, LRadius)); Canvas.Fill.Kind := TBrushKind.Solid; Canvas.Fill.Color := TAlphaColors.Cadetblue; Canvas.Stroke.Kind := TBrushKind.Solid; Canvas.Stroke.Color := TAlphaColors.Chocolate; Canvas.Stroke.Thickness := 8; {$IFDEF SKIA} if Canvas is TSkCanvasCustom then begin DrawPath(TSkCanvasCustom(Canvas).Canvas, LPathData, TSkPathFillType.Winding, Canvas.Fill, Canvas.Stroke); end else begin // Fallback if you remove the "GlobalUseSkia := True;" from .dpr LBitmap := TBitmap.Create(Round(ARect.Width), Round(ARect.Height)); try LBitmap.SkiaDraw( procedure(const ACanvas: ISkCanvas) begin DrawPath(TSkCanvasCustom(Canvas).Canvas, LPathData, TSkPathFillType.Winding, Canvas.Fill, Canvas.Stroke); end); Canvas.DrawBitmap(LBitmap, LBitmap.BoundsF, ARect, 1); finally LBitmap.Free; end; end; {$ELSE} Canvas.FillPath(LPathData, 1); Canvas.DrawPath(LPathData, 1); {$ENDIF} end; end. The result with FMX pure: The result after enable Skia in the project: The project is attached. WindingPath.7z Share this post Link to post
Anders Melander 1783 Posted November 23, 2022 35 minutes ago, vfbb said: You can use the Skia4Delphi for that. How is that relevant? Share this post Link to post
vfbb 285 Posted November 23, 2022 47 minutes ago, Anders Melander said: How is that relevant? He needs to draw paths in Winding mode but doesn't want to change the FMX source. This is one of the alternatives, but there are other possibilities. 1 Share this post Link to post