'How to render a chessboard grid on a Bitmap?
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):

= 
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.
Solution 1:[1]
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()?.
Solution 2:[2]
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;
Solution 3:[3]
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;
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Community |
| Solution 2 | Said Amazigh |
| Solution 3 |
