From 2eb97a373256ca7bb6969a81abdb99ec58ca80b9 Mon Sep 17 00:00:00 2001
From: wp_xxyyzz <wp_xxyyzz@8e941d3f-bd1b-0410-a28a-d453659cc2b4>
Date: Fri, 14 Mar 2025 19:20:10 +0000
Subject: [PATCH] LazMapViewer: Fix drawing of missing tiles. Issue #39111.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@9682 8e941d3f-bd1b-0410-a28a-d453659cc2b4
---
 .../addons/bgra_drawingengine/mvde_bgra.pas   |   3 +-
 .../source/addons/plugins/mvplugins.pas       |   2 +-
 components/lazmapviewer/source/mvengine.pas   | 109 +++++++++++++-----
 .../lazmapviewer/source/mvmapviewer.pas       |  12 +-
 4 files changed, 94 insertions(+), 32 deletions(-)

diff --git a/components/lazmapviewer/source/addons/bgra_drawingengine/mvde_bgra.pas b/components/lazmapviewer/source/addons/bgra_drawingengine/mvde_bgra.pas
index f3823984f..8e5ef33e2 100644
--- a/components/lazmapviewer/source/addons/bgra_drawingengine/mvde_bgra.pas
+++ b/components/lazmapviewer/source/addons/bgra_drawingengine/mvde_bgra.pas
@@ -234,7 +234,8 @@ end;
 
 procedure TMvBGRADrawingEngine.FillRect(X1, Y1, X2, Y2: Integer);
 begin
-  FBuffer.CanvasBGRA.FillRect(X1, Y1, X2, Y2);
+//  FBuffer.CanvasBGRA.FillRect(X1, Y1, X2, Y2);
+  FBuffer.FillRect(X1, Y1, X2, Y2, BrushColor);
 end;
 
 function TMvBGRADrawingEngine.GetPenStyle: TPenStyle;
diff --git a/components/lazmapviewer/source/addons/plugins/mvplugins.pas b/components/lazmapviewer/source/addons/plugins/mvplugins.pas
index df0fc420d..5addb3eb3 100644
--- a/components/lazmapviewer/source/addons/plugins/mvplugins.pas
+++ b/components/lazmapviewer/source/addons/plugins/mvplugins.pas
@@ -51,7 +51,7 @@ type
     procedure SetPosition(AValue: TTileInfoPosition);
   protected
     procedure AfterDrawTile(AMapView: TMapView; ADrawingEngine: TMvCustomDrawingEngine;
-      ATileID: TTileID; ARect: TRect; var Handled: Boolean); override;
+      ATileID: TTileID; ARect: TRect; var {%H-}Handled: Boolean); override;
   public
     constructor Create(AOwner: TComponent); override;
   published
diff --git a/components/lazmapviewer/source/mvengine.pas b/components/lazmapviewer/source/mvengine.pas
index 1b441bee0..66699e774 100644
--- a/components/lazmapviewer/source/mvengine.pas
+++ b/components/lazmapviewer/source/mvengine.pas
@@ -123,7 +123,7 @@ type
       function DegreesToPixelsEPSG3395(const AWin: TMapWindow; APt: TRealPoint): TPoint;
       function DegreesToPixelsEPSG3857(const AWin: TMapWindow; APt: TRealPoint): TPoint;
       procedure Redraw(const aWin: TMapWindow; const PaintOnly: Boolean = False);
-      function CalculateVisibleTiles(const aWin: TMapWindow; out Area: TArea): Boolean;
+      function CalculateVisibleTiles(const aWin: TMapWindow; out AFullyCovered: Boolean): TArea;
       function IsCurrentWin(const aWin: TMapWindow) : boolean;
     protected
       procedure AdjustZoomCenter(var AWin: TMapWindow);
@@ -355,36 +355,58 @@ begin
   CalculateWin(AWin);
 end;
 
-// Returns whether the view area is not covered fully with a tiles
-function TMapViewerEngine.CalculateVisibleTiles(const aWin: TMapWindow; out
-  Area: TArea): Boolean;
+{ Calculates the range of tile ids needed to cover the window with a map.
+  Returns AFullyCovered=false when not the entire windows is covered by the
+  map tiles. }
+function TMapViewerEngine.CalculateVisibleTiles(const AWin: TMapWindow;
+  out AFullyCovered: Boolean): TArea;
 var
-  MaxX, MaxY, startX, startY: int64;
-  WorldMax: Int64;
+  maxX, maxY, startX, startY: Int64;
+  worldMax: Int64;
 begin
-  Area := Default(TArea);
-  Result := (aWin.X <= 0) and (aWin.Y <= 0);
-  WorldMax := Int64(1) shl AWin.Zoom - 1;
-  MaxX := Int64(aWin.Width) div TileSize.CX + 1;
-  MaxY := Int64(aWin.Height) div TileSize.CY + 1;
-  if (MaxX > WorldMax) or (MaxY > WorldMax) then
+  Result := Default(TArea);
+
+  AFullyCovered := true;
+  if (AWin.X > 0) or (AWin.Y > 0) then
+    AFullyCovered := false;
+
+  worldMax := Int64(1) shl AWin.Zoom - 1;
+  maxX := Int64(AWin.Width) div TileSize.CX + 1;
+  maxY := Int64(AWin.Height) div TileSize.CY + 1;
+  if maxY > worldMax then
   begin
-    Result := False;
-    MaxX := Min(WorldMax, MaxX);
-    MaxY := Min(WorldMax, MaxY);
+    maxY := worldMax;
+    AFullyCovered := false;
   end;
-  startX := -aWin.X div TileSize.CX;
-  startY := -aWin.Y div TileSize.CY;
-  if (startX < 0) or (startY < 0) then
+  if (not Cyclic) and (maxX > worldMax) then
   begin
-    startX := Max(0, startX);
-    startY := Max(0, startY);
-    Result := False;
+    maxX := worldMax;
+    AFullyCovered := false;
+  end;
+
+  startX := Max(0, -AWin.X div TileSize.CX);
+  startY := Max(0, -AWin.Y div TileSize.CY);
+
+  Result.Left := startX;
+  if Cyclic then
+    Result.Right := Result.Left + maxX
+  else
+  if startX + maxX < worldMax then
+    Result.Right := startX + maxX
+  else
+  begin
+    Result.Right := worldMax;
+    AFullyCovered := false;
+  end;
+
+  Result.Top := startY;
+  if startY + maxY < worldMax then
+    Result.Bottom := startY + maxY
+  else
+  begin
+    Result.Bottom := worldMax;
+    AFullyCovered := false;
   end;
-  Area.Left := startX;
-  Area.Right := startX + MaxX;
-  Area.Top := startY;
-  Area.Bottom := startY + MaxY;
 end;
 
 procedure TMapViewerEngine.CalculateWin(var AWin: TMapWindow);
@@ -1096,6 +1118,7 @@ var
   iTile, numTiles, XShift: Integer;
   Tiles: TTileIdArray = nil;
   foundInCache: Boolean;
+  fullyCovered: Boolean;
 
   procedure AddJob;
   var
@@ -1112,6 +1135,34 @@ var
     end;
   end;
 
+  procedure EraseAround;
+  var
+    x, y: Integer;
+  begin
+    // Erase part above the map
+    y := AWin.Y;
+    if y > 0 then
+      EraseBackground(Rect(0, 0, AWin.Width, y));
+
+    // Erase part below the map
+    y := AWin.Y + (numTiles - 1 - TilesVis.Top) * TileSize.CX;
+    if y < AWin.Height then
+      EraseBackground(Rect(0, y, AWin.Width, AWin.Height));
+
+    if not Cyclic then
+    begin
+      // Erase part at the left of the map
+      x := AWin.X;
+      if x > 0 then
+        EraseBackground(Rect(0, 0, x, AWin.Height));
+
+      // Erase part at the right of the map
+      x := AWin.X + (numTiles - 1 - TilesVis.Left) * TileSize.CX;
+      if x < AWin.Width then
+        EraseBackground(Rect(x, 0, AWin.Width, AWin.Height));
+    end;
+  end;
+  (*
   procedure EraseAround;
   var
     T, L, B, R: Integer;
@@ -1144,6 +1195,7 @@ var
         AWin.Height)
       );
   end;
+  *)
 
 begin
   if not(Active) then
@@ -1154,12 +1206,15 @@ begin
     Exit;
   end;
 
-  if not CalculateVisibleTiles(AWin, TilesVis) then
+  numTiles := 1 shl AWin.Zoom;
+  TilesVis := CalculateVisibleTiles(AWin, fullyCovered);
+  if not fullyCovered then
     EraseAround;
+//  if not CalculateVisibleTiles(AWin, TilesVis) then
+//    EraseAround;
 
   SetLength(Tiles, (TilesVis.Bottom - TilesVis.Top + 1) * (TilesVis.Right - TilesVis.Left + 1));
   iTile := Low(Tiles);
-  numTiles := 1 shl AWin.Zoom;
   XShift := IfThen(aWin.X > 0, numTiles - aWin.X div TileSize.CX - 1, 0);
   for Y := TilesVis.Top to TilesVis.Bottom do
     for X := TilesVis.Left to TilesVis.Right do
diff --git a/components/lazmapviewer/source/mvmapviewer.pas b/components/lazmapviewer/source/mvmapviewer.pas
index 9af15cc12..efbaadfb3 100644
--- a/components/lazmapviewer/source/mvmapviewer.pas
+++ b/components/lazmapviewer/source/mvmapviewer.pas
@@ -3116,11 +3116,10 @@ const
     Canvas.FillRect(0, 0, ClientWidth, ClientHeight);
   end;
 
-  procedure FullRedraw;
+  procedure ObjectsDraw;
   var
     W: Integer;
   begin
-    Engine.Redraw;
     W := ClientWidth;
     if Cyclic then
       W := Min(1 shl Zoom * TileSize.CX, W);
@@ -3129,13 +3128,20 @@ const
     if Assigned(FBeforeDrawObjectsEvent) then
       FBeforeDrawObjectsEvent(Self);
 
-    DrawObjects(Default(TTileId), 0, 0, W - 1, ClientHeight);
+    DrawObjects(Default(TTileID), 0, 0, W - 1, ClientHeight);
 
     GetPluginManager.AfterDrawObjects(Self);
     if Assigned(FAfterDrawObjectsEvent) then
       FAfterDrawObjectsEvent(Self);
+  end;
+
+  procedure FullRedraw;
+  begin
+    Engine.Redraw;
+    ObjectsDraw;
 
     DrawingEngine.PaintToCanvas(Canvas);
+
     if DebugTiles then     // DebugTiles is deprecated
       DrawCenter;