Recent

Author Topic: Rendering PaintBox using PaintBox.Canvas.ClipRect is buggy  (Read 734 times)

furious programming

  • Hero Member
  • *****
  • Posts: 858
Rendering PaintBox using PaintBox.Canvas.ClipRect is buggy
« on: April 04, 2023, 10:10:34 pm »
I have a PaintBox embedded in a ScrollBox that can be stretched. The PaintBox can be large in size (e.g. 256×4096), which makes the ScrollBox.VerticalScrollBar useful for scrolling. In PaintBox, I render the grid, but for efficient rendering, instead of repainting the entire component, I only repaint the grid cells that fit in PaintBox.Canvas.ClipRect — of course, the rendering is in the PaintBox.OnPaint event.

Everything works very efficiently, but there is a problem — if I grab the ScrollBox slider with my mouse and move it up/down quickly, holes fall out in the PaintBox. The faster I scroll the ScrollBox, the bigger the holes fall out. Interestingly, the background is rendered (clMenu color), but the cells are clipped. Each cell is a separate TPortableNetworkGraphic, so painting the cell is limited to PaintBox.Canvas.Draw call.

The PaintBox.OnPaint code looks like this:

Code: Pascal  [Select][+][-]
  1. procedure TFormMain.PaintBoxStampsPaint(ASender: TObject);
  2. var
  3.   PaintBox: TPaintBox absolute ASender;
  4. var
  5.   VisibleRect:      TRect;
  6.   VisibleCellFirst: Integer;
  7.   VisibleCellLast:  Integer;
  8. var
  9.   ItemIndex: Integer;
  10.   ItemRect:  TRect;
  11.   ItemStyle: TTextStyle;
  12. begin
  13.   // Get the rect that has to be repainted.
  14.   VisibleRect      := PaintBox.Canvas.ClipRect;
  15.  
  16.   // Calculate the index of the first and last cell (checked, calculations are valid)
  17.   VisibleCellFirst := (VisibleRect.Top div FontsEditor.Buffers.CellHeight) * FontsEditor.Grid.SizeX;
  18.   VisibleCellLast  := VisibleCellFirst + (VisibleRect.Height div FontsEditor.Buffers.CellHeight) * FontsEditor.Grid.SizeX - 1;
  19.  
  20.   if VisibleRect.Bottom mod FontsEditor.Buffers.CellHeight > 0 then
  21.     VisibleCellLast += FontsEditor.Grid.SizeX;
  22.  
  23.   VisibleCellLast := Min(VisibleCellLast, FontsEditor.Grid.ItemIndexLast);
  24.  
  25.   // Paint the background under all cells that have to be repainted.
  26.   PaintBox.Canvas.Brush.Color := clMenu;
  27.   PaintBox.Canvas.FillRect(VisibleRect);
  28.  
  29.   // Paint all cells that intersects with the "PaintBox.Canvas.ClipRect" rect.
  30.   for ItemIndex := VisibleCellFirst to VisibleCellLast do
  31.   begin
  32.     // Get calculated rect of the cell (checked, calculations are valid)
  33.     ItemRect := FontsEditor.Grid.ItemRect[ItemIndex];
  34.     // Get the cell graphic and draw it.
  35.     PaintBox.Canvas.Draw(ItemRect.Left, ItemRect.Top, FontsEditor.Buffers.Cell[ItemIndex]);
  36.  
  37.     // Render the item index inside the cell.
  38.     ItemStyle := PaintBox.Canvas.TextStyle;
  39.     ItemStyle.Alignment := taCenter;
  40.     ItemStyle.Layout := tlCenter;
  41.     PaintBox.Canvas.TextRect(ItemRect, 0, 0, ItemIndex.ToString(), ItemStyle);
  42.   end;
  43. end;
  44.  

You can see the results of this code in the attachments:
  • After the program window appears on the screen (correct appearance).
  • After scrolling with the mouse wheel (also correct appearance).
  • After scrolling with the ScrollBox slider — it contains empty bars with the background color (clMenu), but the graphics are not completely painted?
Why do these things happen?
« Last Edit: April 05, 2023, 03:21:19 pm by furious programming »
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

furious programming

  • Hero Member
  • *****
  • Posts: 858
Re: Rendering PaintBox using PaintBox.Canvas.ClipRect is buggy
« Reply #1 on: April 04, 2023, 10:19:18 pm »
For the test, I replaced the TPaintBox with a TPanel, but the effect is exactly the same.
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

Josh

  • Hero Member
  • *****
  • Posts: 1274
Re: Rendering PaintBox using PaintBox.Canvas.ClipRect is buggy
« Reply #2 on: April 04, 2023, 10:22:22 pm »
Hi

Sounds like the paint event is being called in rapid succession, to the point where it has not finished running; before its called again.

what happens if you limit the re-entrent by using a global var ie.

Code: Pascal  [Select][+][-]
  1. var Am_Painting:Boolean=false;
  2.  
  3. procedure PaintBoxStampsPaint(ASender: TObject);
  4. ....
  5. begin
  6.   if Am_Painting then exit;
  7.   Am_Painting:=true;
  8.  .....
  9.   Am_Painting:=false;
  10. end;
The best way to get accurate information on the forum is to post something wrong and wait for corrections.

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2069
  • Fifty shades of code.
    • Delphi & FreePascal
Re: Rendering PaintBox using PaintBox.Canvas.ClipRect is buggy
« Reply #3 on: April 04, 2023, 10:26:39 pm »
Or doing "Invalidate" after your painting?
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

furious programming

  • Hero Member
  • *****
  • Posts: 858
Re: Rendering PaintBox using PaintBox.Canvas.ClipRect is buggy
« Reply #4 on: April 04, 2023, 10:31:37 pm »
Or doing "Invalidate" after your painting?

This fixed the problem.  8)

Code: Pascal  [Select][+][-]
  1. procedure TFormMain.PaintBoxStampsPaint(ASender: TObject);
  2. var
  3.   PaintBox: TPaintBox absolute ASender;
  4. var
  5.   VisibleRect:      TRect;
  6.   VisibleCellFirst: Integer;
  7.   VisibleCellLast:  Integer;
  8. var
  9.   ItemIndex: Integer;
  10.   ItemRect:  TRect;
  11.   ItemStyle: TTextStyle;
  12. begin
  13.   VisibleRect      := PaintBox.Canvas.ClipRect;
  14.   VisibleCellFirst := (VisibleRect.Top div FontsEditor.Buffers.CellHeight) * FontsEditor.Grid.SizeX;
  15.   VisibleCellLast  := VisibleCellFirst + (VisibleRect.Height div FontsEditor.Buffers.CellHeight) * FontsEditor.Grid.SizeX - 1;
  16.  
  17.   if VisibleRect.Bottom mod FontsEditor.Buffers.CellHeight > 0 then
  18.     VisibleCellLast += FontsEditor.Grid.SizeX;
  19.  
  20.   VisibleCellLast := Min(VisibleCellLast, FontsEditor.Grid.ItemIndexLast);
  21.  
  22.   PaintBox.Canvas.Brush.Color := clMenu;
  23.   PaintBox.Canvas.FillRect(VisibleRect);
  24.  
  25.   for ItemIndex := VisibleCellFirst to VisibleCellLast do
  26.   begin
  27.     ItemRect := FontsEditor.Grid.ItemRect[ItemIndex];
  28.     PaintBox.Canvas.Draw(ItemRect.Left, ItemRect.Top, FontsEditor.Buffers.Cell[ItemIndex]);
  29.  
  30.     ItemStyle := PaintBox.Canvas.TextStyle;
  31.     ItemStyle.Alignment := taCenter;
  32.     ItemStyle.Layout := tlCenter;
  33.     PaintBox.Canvas.TextRect(ItemRect, 0, 0, ItemIndex.ToString(), ItemStyle);
  34.   end;
  35.  
  36.   PaintBox.Invalidate();
  37. end;
  38.  
« Last Edit: April 04, 2023, 10:34:06 pm by furious programming »
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

furious programming

  • Hero Member
  • *****
  • Posts: 858
Re: [SOLVED] Rendering PaintBox using PaintBox.Canvas.ClipRect is buggy
« Reply #5 on: April 04, 2023, 11:01:54 pm »
Hmm, it is not obvious to me why, but I've changed the calculations to this:

Code: Pascal  [Select][+][-]
  1. procedure TFormMain.PaintBoxStampsPaint(ASender: TObject);
  2. var
  3.   PaintBox: TPaintBox absolute ASender;
  4. var
  5.   VisibleRect:      TRect;
  6.   VisibleRowFirst:  Integer;
  7.   VisibleRowLast:   Integer;
  8.   VisibleCellFirst: Integer;
  9.   VisibleCellLast:  Integer;
  10. var
  11.   ItemIndex: Integer;
  12.   ItemRect:  TRect;
  13.   ItemStyle: TTextStyle;
  14. begin
  15.   VisibleRect      := PaintBox.Canvas.ClipRect;
  16.   VisibleRowFirst  := VisibleRect.Top div FontsEditor.Buffers.CellHeight;
  17.   VisibleRowLast   := VisibleRect.Bottom div FontsEditor.Buffers.CellHeight;
  18.   VisibleRowLast   += Ord(VisibleRect.Bottom mod FontsEditor.Buffers.CellHeight > 0);
  19.   VisibleCellFirst := VisibleRowFirst * FontsEditor.Grid.SizeX;
  20.   VisibleCellLast  := VisibleCellFirst + (VisibleRowLast * FontsEditor.Grid.SizeX) + FontsEditor.Grid.SizeX - 1;
  21.   VisibleCellLast  := Min(VisibleCellLast, FontsEditor.Grid.ItemIndexLast);
  22.  
  23.   PaintBox.Canvas.Brush.Color := clMenu;
  24.   PaintBox.Canvas.FillRect(VisibleRect);
  25.  
  26.   for ItemIndex := VisibleCellFirst to VisibleCellLast do
  27.   begin
  28.     ItemRect := FontsEditor.Grid.ItemRect[ItemIndex];
  29.     PaintBox.Canvas.Draw(ItemRect.Left, ItemRect.Top, FontsEditor.Buffers.Cell[ItemIndex]);
  30.  
  31.     ItemStyle := PaintBox.Canvas.TextStyle;
  32.     ItemStyle.Alignment := taCenter;
  33.     ItemStyle.Layout := tlCenter;
  34.     PaintBox.Canvas.TextRect(ItemRect, 0, 0, ItemIndex.ToString(), ItemStyle);
  35.   end;
  36. end;
  37.  

and now calling the PaintBox.Invalidate is not needed. Even more, if I call it at the end, the rendering loops forever. Strange.
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

wp

  • Hero Member
  • *****
  • Posts: 11916
Re: [SOLVED] Rendering PaintBox using PaintBox.Canvas.ClipRect is buggy
« Reply #6 on: April 04, 2023, 11:07:21 pm »
Even more, if I call it at the end, the rendering loops forever. Strange.
Not at all. The OnPaint event is fired when the Paintbox is told by the OS to repaint itself. But when you call Paintbox.Invalidate you tell the OS to repaint the Paintbox. Therefore, the PaintBox gets into the OnPaint handler again where it calls Paintbox.Invalidate. Etc...

furious programming

  • Hero Member
  • *****
  • Posts: 858
Re: [SOLVED] Rendering PaintBox using PaintBox.Canvas.ClipRect is buggy
« Reply #7 on: April 05, 2023, 12:03:03 am »
Not at all.

I checked it — after changing the calculation code, the rendering looped endlessly, just because of the PaintBox.Invalidate call at the end of this event. After removing it, the problem went away.

I didn't like it from the start because I use the Invalidate method whenever I want to repaint the control (and never inside the OnPaint event). I have no idea why there was a problem with under-rendered areas in previous calculations, and why adding Invalidate fixed this problem, or why after changing the calculation code, Invalidate not only is it not needed, it is also harmful.

But that's not important. It is important that the rendering works now. 8)
« Last Edit: April 05, 2023, 12:04:16 pm by furious programming »
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

 

TinyPortal © 2005-2018