1
votes

I'm using TCanvas.ClearRect to clear an area of a canvas. However, I've discovered that this doesn't seem to work when a clipping rectangle is being used. Why is this and how can I resolve it? Note that I can't use FillRect instead because there are cases where I may want to clear an area to fully transparent, which I believe can only be done with ClearRect. I'm using Delphi 10.4.

In the example below I would expect Button1 and Button2 to give the same behaviour. However, button2, which sets the clipping rectangle to the size of the whole image, doesn't perform the clearRect. Note also that Button1 (without clip) doesn't work immediately after using Button2, but does work on the second try.

Note that SaveState is needed prior to calling IntersectClipRect as mentioned in the documentation. See the example here. http://docwiki.embarcadero.com/Libraries/Rio/en/FMX.Graphics.TCanvas.IntersectClipRect

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Objects,
  FMX.StdCtrls, FMX.Controls.Presentation, System.UIConsts;

type
  TForm1 = class(TForm)
    Image1: TImage;
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
    procedure Draw;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  MyBitmap: TBitmap;

implementation

{$R *.fmx}

procedure TForm1.Button1Click(Sender: TObject);
var
  MyRect: TRectF;
begin
  Draw;

  //the rectangle to be cleared
  MyRect.Create(10,30,100,80);

  if Image1.Bitmap.Canvas.BeginScene then begin
    try
      Image1.Bitmap.Canvas.ClearRect(MyRect, TAlphaColorRec.Red);
    finally
      Image1.Bitmap.Canvas.EndScene;
    end;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  MyRect: TRectF;
  LSave : TCanvasSaveState;
begin
  Draw;

  //the rectangle to be cleared
  MyRect.Create(10,30,100,80);

  LSave := Image1.Bitmap.Canvas.SaveState;

  try
    Image1.Bitmap.Canvas.IntersectClipRect(Image1.Bitmap.Bounds);

    if Image1.Bitmap.Canvas.BeginScene then begin
      try
        Image1.Bitmap.Canvas.ClearRect(MyRect, TAlphaColorRec.Red);
      finally
        Image1.Bitmap.Canvas.EndScene;
      end;
    end;

  finally
    Image1.Bitmap.Canvas.RestoreState(LSave);
  end;
end;

procedure TForm1.Draw;
begin
  Image1.Bitmap.Clear(TAlphaColorRec.Blue);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  //creates an empty bitmap
  Image1.Bitmap := TBitmap.Create(Trunc(Image1.Width), Trunc(Image1.Height));
  //loads the initial bitmap (to be manipulated) to Image1
  Draw;
end;

end.

form code:

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 349
  ClientWidth = 346
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop]
  OnCreate = FormCreate
  DesignerMasterStyle = 0
  object Image1: TImage
    MultiResBitmap = <
      item
      end>
    Position.X = 8.000000000000000000
    Position.Y = 8.000000000000000000
    Size.Width = 329.000000000000000000
    Size.Height = 273.000000000000000000
    Size.PlatformDefault = False
  end
  object Button1: TButton
    Position.X = 48.000000000000000000
    Position.Y = 304.000000000000000000
    Text = 'ClearRect'
    OnClick = Button1Click
  end
  object Button2: TButton
    Position.X = 208.000000000000000000
    Position.Y = 304.000000000000000000
    Size.Width = 105.000000000000000000
    Size.Height = 22.000000000000000000
    Size.PlatformDefault = False
    Text = 'ClearRect Clipped'
    OnClick = Button2Click
  end
end

Result From clicking Button1. MyRect is the red region. No clipping rect is set.

Result from clicking Button1

Result from clicking Button2. MyRect is the same as above. Clipping rect is set to the whole image area and so should make no difference to what gets drawn.

Result from clicking Button2

1
Why not just MyRec.Intersect(Image1.Bitmap.Bounds) ? - Uwe Raabe
I'm not sure what you mean. That would make the rectangle larger than in my example. I want to clear a specific rectangle area. - XylemFlow
How can an intersection of two rectangles be larger than any of them? - Uwe Raabe
OK, you're right. But it my example the rectangle I want to clear is completely inside the Bounds of the image, so intersecting them would make no difference. You haven't said why you think it would resolve the issue. Have you managed to replicate my example? I should maybe add my form code, which would include the image dimensions. - XylemFlow
Ok, indeed I misunderstood and also wasn't aware of the need for SaveState in conjunction with IntersectClipRect. I will remove my previous comment after a while. - Tom Brunberg

1 Answers

2
votes

I've now solved this issue. It's not caused by a bug in Delphi but just requires a very specific order of the commands. The example given above is fixed by moving IntersectClipRect after BeginScene. The commands must be given in the order SaveState, BeginScene, IntersectClipRect, EndScene, RestoreState. It's also important to use try finally to make sure that both EndScene and RestoreState always get called.

procedure TForm1.Button2Click(Sender: TObject);
var
  MyRect: TRectF;
  LSave : TCanvasSaveState;
begin
  Draw;

  //the rectangle to be cleared
  MyRect.Create(10,30,100,80);

  LSave := Image1.Bitmap.Canvas.SaveState;

  try
    if Image1.Bitmap.Canvas.BeginScene then begin
      Image1.Bitmap.Canvas.IntersectClipRect(Image1.Bitmap.Bounds);

      try
        Image1.Bitmap.Canvas.ClearRect(MyRect, TAlphaColorRec.Red);
      finally
        Image1.Bitmap.Canvas.EndScene;
      end;
    end;

  finally
    Image1.Bitmap.Canvas.RestoreState(LSave);
  end;
end;