5

I want to be able to draw a ellipse that is empty, on a transparent layer in a ImgView32. Any idea how to do that? So far all I can think about is:

 BL := TBitmapLayer.Create(ImgView.Layers);
    BL.Bitmap.DrawMode := dmTransparent;
    BL.Bitmap.SetSize(imwidth,imheight);
    BL.Bitmap.Canvas.Pen.Width := penwidth;
    BL.Bitmap.Canvas.Pen.Color := pencolor;
    BL.Location := GR32.FloatRect(0, 0, imwidth, imheight);
    BL.Scaled := False;
    BL.OnMouseDown := LayerMouseDown;
    BL.OnMouseUp := LayerMouseUp;
    BL.OnMouseMove := LayerMouseMove;
    BL.OnPaint := LayerOnPaint;

...
BL.Bitmap.Canvas.Pen.Color := clBlue;
BL.Bitmap.Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
BL.Bitmap.Canvas.Ellipse(FStartPoint.X, FStartPoint.Y,FEndPoint.X, FEndPoint.Y); 

The Start and End points are obtained in mouse events.

I actually am trying to draw a dynamic ellipse (on mouse events). So onMouseDown (LayerMouseDown), onMouseUp (LayerMouseUp) and OnMouseMove (LayerMouseMove) events are involved. As a refference please check this question, it deals with drawing a line dynamically. I want to do the same but with Ellipses instead of lines.

So instead of AddLineToLayer I have the AddCircleToLayer procedure The events look like this now:

procedure TForm5.SwapBuffers32;
begin
    TransparentBlt(
      BL.Bitmap.Canvas.Handle, 0, 0, BL.Bitmap.Width, BL.Bitmap.Height,
      bm32.Canvas.Handle, 0, 0, bm32.Width, bm32.Height, clWhite);
end;

procedure TForm5.ImgViewResize(Sender: TObject);
begin
  OffsX := (ImgView.ClientWidth - imwidth) div 2;
  OffsY := (ImgView.ClientHeight - imheight) div 2;
  BL.Location := GR32.FloatRect(OffsX, OffsY, imwidth+OffsX, imheight+OffsY);
end;

procedure TForm5.LayerMouseDown(Sender: TObject; Buttons: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  FStartPoint := Point(X-OffsX, Y-OffsY);
  FDrawingLine := true;
end;

procedure TForm5.LayerMouseMove(Sender: TObject; Shift: TShiftState; X,  Y: Integer);
begin
  if FDrawingLine then
  begin
    SwapBuffers32;
      if RadioGroup1.ItemIndex=0 then
      begin
        BL.Bitmap.Canvas.Pen.Color := pencolor;
        BL.Bitmap.Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
        BL.Bitmap.Canvas.LineTo(X-OffsX, Y-OffsY);
      end
      else
      begin
        BL.Bitmap.Canvas.Pen.Color := pencolor;
        BL.Bitmap.Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
        SwapBuffers32;
        BL.Bitmap.Canvas.Ellipse(FStartPoint.X, FStartPoint.Y,X-OffsX, Y-OffsY);
      end;
  end;
end;

procedure TForm5.LayerMouseUp(Sender: TObject; Buttons: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
      if RadioGroup1.ItemIndex=0 then
      begin
        FDrawingLine := false;
        FEndPoint := Point(X-OffsX, Y-OffsY);
        AddLineToLayer;
        SwapBuffers32;
      end
      else
      begin
        FDrawingLine := false;
        FEndPoint := Point(X-OffsX, Y-OffsY);
        AddCircleToLayer;
        SwapBuffers32;
      end
end;

procedure TForm5.LayerOnPaint(Sender: TObject; Buffer: TBitmap32);
begin
  SwapBuffers32;
end;

procedure TForm5.AddLineToLayer;
begin
  bm32.Canvas.Pen.Color := pencolor;
  bm32.Canvas.Pen.Width := penwidth;
  bm32.Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
  bm32.Canvas.LineTo(FEndPoint.X, FEndPoint.Y);
end;

procedure TForm5.AddCircleToLayer;
begin
  bm32.Canvas.Pen.Color := pencolor;
  bm32.Canvas.Pen.Width := penwidth;
  bm32.Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
  bm32.Canvas.Ellipse(FStartPoint.X, FStartPoint.Y,FEndPoint.X, FEndPoint.Y);
  SwapBuffers32;
end;

But when I use this code, the circle(ellipse) is filled with white up (like in this image) enter image description here until I start drawing the next ellipse (so onMouseMove and onMouseUp the ellipse is filled). And only when I do another onMouseDown, then the previous circle gets emptyied, but the new ellipse is also filled with white (like in this image) enter image description here

Also if you try doing more ellipses one after the other, and onTop of the older ones, you will notice that there will be traces of the onMouseMove ellipses, like in this image:

enter image description here

So there must be something I am missing with this code.

Please help me solve this.

Community
  • 1
  • 1
user1137313
  • 2,390
  • 9
  • 44
  • 91
  • Well the answer was pretty simple actually... set the brush.Color to 0 before drawing the ellipse... It makes the ellipse empty. Amazingly simple :) – user1137313 Feb 17 '15 at 22:37
  • Setting `Brush.Color` to 0 will actually set it to black. Setting `Brush.Style` to `bsClear` should do the trick, though. – adlabac Feb 18 '15 at 11:59
  • Did you try `Brush.Style := bsClear`? – adlabac Feb 23 '15 at 11:34

2 Answers2

5

If you are using the latest GR32 code from the trunk you can also use this code snippet to define the ellipse

Points := Ellipse(Center.X, Center.Y, Radius.X, Radius.Y);

or even simpler

Points := Ellipse(Center, Radius);

where Points is defined as

Points: TArrayOfFloatPoint;

This generates a polygon of an ellipse with the center at Center and the independent x and y related radius defined by Radius.

Once you have the polygon, you can render it using any vector renderer. For example you can use the built-in VPR renderer with

PolygonFS(Bitmap, Points, SomeColor32);

to render a filled ellipse.

However, if you only want the frame to be rendered you can use this

PolylineFS(Bitmap, Points, AnotherColor32, True, PenWidth);

The parameters for this are

  1. Bitmap = TBitmap32 instance to render to
  2. Points = Polygon points (as defined above)
  3. AnotherColor32 = color, which is used for the rendering
  4. True = close polygon (otherwise your ellipse will have a gap between start and end point
  5. PenWidth = Width of the frame

If you like you can also render this in one call like

PolylineFS(Bitmap, Ellipse(Center, Radius), AnotherColor32, True, PenWidth);

In order to get an arbitrary (rotated) ellipse you need to transform the polygon prior to rendering. You can use

TransformPolygon(Points, Transformation);

for this which gets a TTransformation instance as second parameter. This can include all common operations like rotate, skew, scale and translate.

If you use this, you can also start with a simpler circle as polygon input and scale the circle to result in an ellipse.

The above code makes it necessary to include the units GR32_VectorUtils, GR32_Polygons into your project, like

uses
  GR32_VectorUtils, GR32_Polygons;

The advantage is that you don't rely on GDI for rendering and thus you can pick the renderer out of the available renderer from GR32. Some include a ClearType like effect and improves visibility on LCD screens. Not to mention the antialiasing quality and the ability to control the gamma for the rendering.

CWBudde
  • 1,783
  • 1
  • 24
  • 28
  • Your input is always appreciated CWBudde. Thank you again for a true Graphics32 approach. Please ellaborate a bit about the picking of available renderers. For example if I want the frame to be smoother? What about controlling the gamma and the antialias and cleartype? I am sure others will beneffit from this too. – user1137313 Feb 20 '15 at 06:51
  • CWBudde, please take a look at this question too: http://stackoverflow.com/questions/28623999/delphi-graphics32-combining-normal-layers-with-drawing-layers . I could use your input there... – user1137313 Feb 20 '15 at 07:53
1

So when drawing the circle/ellipse set the brush color to 0 like:

procedure TForm5.AddCircleToLayer;
begin
  bm32.Canvas.Pen.Color := pencolor;
  bm32.Canvas.Pen.Width := penwidth;
  bm32.Canvas.Brush.Color := 0; // this here does the magic
  bm32.Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
  bm32.Canvas.Ellipse(FStartPoint.X, FStartPoint.Y,FEndPoint.X, FEndPoint.Y);
  SwapBuffers32;
end;

Also do the same in the LayerMouseMove event

user1137313
  • 2,390
  • 9
  • 44
  • 91