4

I am displaying transparent images on top of another "underneath" image.

In this case the bottom (solid) image is a chessboard grid and the top image is a lion (transparent):

enter image description here

enter image description here = enter image description here

The reason is to show transparency areas much better as typically you would not see which areas are transparent.

The problem is, bitmaps can be any size in dimensions, so the grid would also need to be the same size as the bitmap.

A dirty approach if you like would be to create a larger version of the chessboard grid above to a size such as 2000x2000, then depending on the size of the bitmaps you are working with you could resize the canvas of the grid to match. This is not ideal because it means storing the large chessboard grid bitmap with your application, and then it means resizing it which may not give the correct results depending on aspect ratio etc.

The correct approach I feel would be to render the chessboard grid programmatically, something like:

procedure RenderGrid(Source: TBitmap; Height, Width: Integer;
  Size: Integer; Color1, Color2: TColor);
begin

end;

This would allow customising the grid with different sizes and colors, and not worry about the overhead of storing a large chessboard grid bitmap and having to resize it.

However I am not sure how you could draw the grid onto a bitmap? One thought I had was that you need to loop through each alternating row of the bitmap and color it that way? I am not sure.

This involves math and calculations which I am not good with. I would appreciate if you could enlighten me on the most effective way of rendering the grid on a bitmap.

  • You've somewhat complicated the question. A chess/checkerboard is defined as an 8x8 square grid, so the `size` is irrelevant, and so is the height. Each square would be exactly `Width div 8` in size (both dimensions), so you really only need to pass in the `Source` and two `TColor` parameters. (Also, I think the `Source` is really the `Dest`, as in the destination image that will contain the board, isn't it?) – Ken White Jul 17 '12 at 20:30
  • @KenWhite I wasn't sure what the grid was called so I just used the name chessboard which I see now can be misleading. Also I see what you mean with Source/Dest, infact it could of just been named in the example as ABitmap or something. –  Jul 17 '12 at 20:42

4 Answers4

5
procedure RenderGrid(Source: TBitmap; Height, Width: Integer;
  Size: Integer; Color1, Color2: TColor);
var
  y: Integer;
  x: Integer;
begin
  Source.SetSize(Width, Height);
  for y := 0 to Height div Size do
    for x := 0 to Width div Size do
    begin
      if Odd(x) xor Odd(y) then
        Source.Canvas.Brush.Color := Color1
      else
        Source.Canvas.Brush.Color := Color2;
      Source.Canvas.FillRect(Rect(x*Size, y*Size, (x+1)*Size, (y+1)*Size));
    end;
end;
Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • Surely making a simple grid and then blitting it multiple times (presumably available in GDI somehow as a single repeated operation) would be a lot faster? – mj2008 Jul 18 '12 at 08:13
  • 1
    @mj2008: Yes, there is room for optimisation. For one thing, I considered NGLN's approach, but eventually decided to keep it simple. After all, I don't think performance is a major issue in this case for a computer made after the early 90's. – Andreas Rejbrand Jul 18 '12 at 10:05
  • 1
    This works very good. I noticed both you and NGLN used the ODD keyword which I have not seen before - seems I am frequently discovering new methods and keywords each day. –  Jul 19 '12 at 18:42
3

Once upon a time, I profiled this specific need. Considering your RenderGrid signature, it is likely that the Bitmap parameter's image will be drawn after the bitmap is drawn. Then the best performance is got by painting the whole bitmap in Color1, and only paint the squares for Color2:

procedure RenderGrid(Target: TBitmap; Height, Width: Integer; Size: Integer;
  Color1, Color2: TColor);
var
  Col: Integer;
  Row: Integer;
begin
  Target.SetSize(Width, Height)
  Target.Canvas.Brush.Color := Color1;
  Target.Canvas.FillRect(Rect(0, 0, Width, Height));
  Target.Canvas.Brush.Color := Color2;
  for Col := 0 to Width div Size do
    for Row := 0 to Height div Size do
      if Odd(Col + Row) then
        Target.Canvas.FillRect(Bounds(Col * Size, Row * Size, Size, Size));
end;

Update

But since you are speaking about large bitmaps, the routine shown below is even another 20% faster. It creates a small bitmap with only 4 squares, say a chessboard of 2 x 2, and lets the target's brush property spread it out automatically. *)

procedure RenderGrid(Target: TBitmap; Height, Width: Integer; Size: Integer;
  Color1, Color2: TColor);
var
  Tmp: TBitmap;
begin
  Tmp := TBitmap.Create;
  try
    Tmp.Canvas.Brush.Color := Color1;
    Tmp.Width := 2 * Size;
    Tmp.Height := 2 * Size;
    Tmp.Canvas.Brush.Color := Color2;
    Tmp.Canvas.FillRect(Rect(0, 0, Size, Size));
    Tmp.Canvas.FillRect(Bounds(Size, Size, Size, Size));
    Target.Canvas.Brush.Bitmap := Tmp;
    if Target.Width * Target.Height = 0 then
      Target.SetSize(Width, Height)
    else
    begin
      Target.SetSize(Width, Height)
      Target.Canvas.FillRect(Rect(0, 0, Width, Height));
    end;
  finally
    Tmp.Free;
  end;
end;

To optimize this even further: cache this small bitmap (Tmp), and reuse it when its size hasn't been changed.

*) See also: How to color a bitmap without calling FillRect()?.

Community
  • 1
  • 1
NGLN
  • 43,011
  • 8
  • 105
  • 200
  • I tested the attachment from the post you linked to, the result was: `6147 Paint 1: Elk vlakje apart, while-loops voor X en Y, elke loop brush instellen. 3104 Paint 2: In één keer bitmap uit geheugen met Canvas.CopyRect. 3136 Paint 3: Eerst wit, daarna grijze vakjes apart met for-loops voor Col en Row. 5397 Paint 4: Eerst alle witte vlakjes, daarna grijs, for-loops voor Col en Row. 6131 Paint 5: Elk vlakje apart, for-loops voor Col en Row, elke loop brush instellen. 3151 Paint 6: In één keer bitmap uit geheugen met Windows.BitBlt. ` Not sure what it means as I do not understand dutch? –  Jul 19 '12 at 18:41
  • @Blobby Try [a translation](http://translate.google.nl/#nl|en|6147%20Paint%201%3A%20Elk%20vlakje%20apart%2C%20while-loops%20voor%20X%20en%20Y%2C%20elke%20loop%20brush%20instellen.%20). But there's really no need for it, it just says what the code does, and since you have the code, you just read that instead. Now, the results say so much as painting from memory or painting like the code in the answer above is the fastest. The latter gives slight flickering, but since you are not painting directly on the screen with this routine, that's no concern. – NGLN Jul 19 '12 at 19:48
0

For Firemonkey use this function

procedure PaintChessBrush(const Canvas: TCanvas; const AColor: TAlphaColor; const ARect: TRectF; const AOpacity: Single; const AChessStep: Single = 10);

  procedure MakeChessBrush(ABrushBitmap: TBrushBitmap; const AChessStep: Single);
  var
    BitmapTmp: TBitmap;
  begin
       BitmapTmp := ABrushBitmap.Bitmap;
       with BitmapTmp do
       begin
            SetSize(Trunc(2 * AChessStep), Trunc(2 * AChessStep));
            Clear(TAlphaColorRec.White);
            ClearRect(RectF(0, 0, AChessStep, AChessStep), TAlphaColorRec.Lightgray);
            ClearRect(RectF(AChessStep, AChessStep, 2 * AChessStep, 2 * AChessStep), TAlphaColorRec.Lightgray);
       end;

       ABrushBitmap.WrapMode := TWrapMode.Tile;
  end;

var
  State: TCanvasSaveState;
begin
     State := Canvas.SaveState;
     try
          MakeChessBrush(Canvas.Fill.Bitmap, AChessStep);
          Canvas.Fill.Kind := TBrushKind.Bitmap;
          Canvas.FillRect(ARect, 0, 0, AllCorners, AOpacity);

          Canvas.Fill.Kind := TBrushKind.Solid;
          Canvas.Fill.Color := AColor;
          Canvas.FillRect(ARect, 0, 0, AllCorners, AOpacity);
     finally
        Canvas.RestoreState(State);
     end;
end;
Mazighus
  • 21
  • 5
-1

You'll get better performance with this approach. Just don't pass CellSize = 0.

// Color1, Color2 in RRGGBB format (i.e. Red = $00FF0000)
procedure RenderGrid(Source: TBitmap; CellSize: Integer; Color1, Color2: TColorRef);
var
  I, J: Integer;
  Pixel: ^TColorRef;
  UseColor1: Boolean;
begin
  Source.PixelFormat := pf32bit;
  Pixel := Source.ScanLine[Source.Height - 1];
  for I := 0 to Source.Height - 1 do
  begin
    UseColor1 := (I div CellSize) mod 2 = 0;
    for J := 0 to Source.Width - 1 do
    begin
      if J mod CellSize = 0 then UseColor1 := not UseColor1;

      if UseColor1 then
        Pixel^ := Color1
      else
        Pixel^ := Color2;
      Inc(Pixel);
    end;
  end;
end;
Lawrence Barsanti
  • 31,929
  • 10
  • 46
  • 68
  • 3
    No, this is not a good example of the use of `ScanLine()`. In fact, it performs worse. It takes about 260% longer than [Andreas' routine](http://stackoverflow.com/a/11530079/757830), and about 330% longer than mine. Sorry, -1. – NGLN Jul 19 '12 at 21:18
  • Its been a while since I've done this stuff. I updated it so ScanLine is only called once. I'm sure if you profile it again, you see much better performance. – Lawrence Barsanti Jul 20 '12 at 13:10
  • 1
    Barely. Besides, the problem with this routine is that you set _every_ pixel. Agreed, use `Scanline` when dealing with individual pixels, but in this specific case there really is no need to. – NGLN Jul 20 '12 at 15:16