3

I have written a routine which should add a dotted border to a bitmap:

procedure AddDottedBorderToBitmap(aBM: Vcl.Graphics.TBitmap);
var
  c: TCanvas;
begin
  c := aBM.Canvas;
  c.Pen.Color := clBlack;
  c.Pen.Mode  := pmXor;
  c.Pen.Style := psDot;

  c.MoveTo(0, 0);
  c.LineTo(0, aBM.Height - 1);
  c.LineTo(aBM.Width - 1, aBM.Height - 1);
  c.LineTo(aBM.Width - 1, 0);
  c.LineTo(0, 0);
end;

But when enlarging the result, the resulting borderline instead of dots seems to be made of small dashes:

enter image description here

Is this correct? If not, how can I get real dots instead of dashes?

user1580348
  • 5,721
  • 4
  • 43
  • 105

2 Answers2

6

It may seem simple to use DrawFocusRect, but if you need to draw something else than rectangles, you may want to read ahead.

The pen style psDot does not mean that every second pixel is colored and the other cleared. If you think about it, the higher the resolution the harder it would be to see the difference of dotted vs. gray solid f.ex. There is another pen style psAlternate which alternates the pixels. The docs say:

psAlternate

The pen sets every other pixel. (This style is applicable only for cosmetic pens.) This style is only valid for pens created with the ExtCreatePen API function. (See MS Windows SDK docs.) This applies to both VCL and VCL.NET.

To define the pen and use it we do as follows

var
  c: TCanvas;
  oldpenh, newpenh: HPEN; // pen handles
  lbrush: TLogBrush;      // logical brush

...

  c := pbx.Canvas; // pbx is a TPintBox, but can be anything with a canvas

  lbrush.lbStyle := BS_SOLID;
  lbrush.lbColor := clBlack;
  lbrush.lbHatch := 0;

  // create the pen
  newpenh := ExtCreatePen(PS_COSMETIC or PS_ALTERNATE, 1, lbrush, 0, nil);
  try
    // select it
    oldpenh := SelectObject(c.Handle, newpenh);

    // use the pen
    c.MoveTo(0, 0);
    c.LineTo(0, pbx.Height - 1);
    c.LineTo(pbx.Width - 1, pbx.Height - 1);
    c.LineTo(pbx.Width - 1, 0);
    c.LineTo(0, 0);

    c.Ellipse(3, 3, pbx.width-3, pbx.Height-3);

    // revert to the old pen
    SelectObject(c.Handle, oldpenh);
 finally
    // delete the pen
    DeleteObject(newpenh);
 end;

And finally what it looks like (the magnifier is at x 10)

enter image description here

Tom Brunberg
  • 20,312
  • 8
  • 37
  • 54
  • 3
    @user1580348 I wouldn't call this a hint, I'd make it the accepted answer :-) – Jerry Dodge Jun 20 '17 at 23:18
  • @Jerry Thank you for your support. – Tom Brunberg Jun 21 '17 at 06:40
  • @Kobik Thank you for your edit, I also added try - finally – Tom Brunberg Jun 21 '17 at 06:41
  • @TomBrunberg, if you choose to use try/finally here, you will also need to protect the `oldpenh := SelectObject(c.Handle, newpenh)` / `SelectObject(c.Handle, oldpenh);` with try/finally block to make it consistent ;) – kobik Jun 21 '17 at 10:58
  • @kobik Yes, indeed, but you sound (*...if you choose to use...*) as you wouldn't bother with these try - finally? – Tom Brunberg Jun 21 '17 at 11:17
  • @TomBrunberg, I would probably continued with pure API calls on `c.Handle` to draw the rectangle (since you already use API and not accessing `c.Pen.Handle`...). but since `c.MoveTo` (etc) may raise exception in `RequiredState` so I *would* use try/finally blocks here. – kobik Jun 21 '17 at 11:36
2

DrawFocusRect it's a Windows API call that make a border like you need.

procedure AddDottedBorderToBitmap(aBM: Vcl.Graphics.TBitmap);
begin
  DrawFocusRect(aBM.canvas.Handle,Rect(0,0,aBM.Width,aBM.Height));
end;
Kohull
  • 674
  • 4
  • 10
  • Thank you, this works very well! But just out of curiosity: Why there are small dashes instead of real dots (like in your example) in the result from my code? – user1580348 Jun 20 '17 at 17:52