Jump to content
XylemFlow

Draw a path with winding fill mode?

Recommended Posts

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

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
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

@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:

image.thumb.png.ec0816ffe8ce2cc7bf22c0831739c9db.png

 

The result after enable Skia in the project:

image.thumb.png.dfae01dfe6f9e2bc95833dbd0ce17e96.png

 

The project is attached.

WindingPath.7z

Share this post


Link to post
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.

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×