Recent

Author Topic: How can I draw a rectangle onto an image of a ScrollBox?  (Read 11922 times)

Tomi

  • Full Member
  • ***
  • Posts: 130
How can I draw a rectangle onto an image of a ScrollBox?
« on: April 08, 2024, 03:34:18 pm »
Hello!

I have a scrollbox with one or more images on it in my program. I would like to draw a rectangle onto image on place of mouse click.
I tried achieve this directly in OnPaint event of the scrollbox and then of the image, but I failed with my attempts: I could not see the rectangle.
So, I have the scrollbox, and some images: image[0], image[1], etc. on it. When I clicking one of these images, the program performs the
Code: Pascal  [Select][+][-]
  1. tile[i].invalidate;
command and in the OnPaint event of the images I try draw a rectangle with simply the DrawFocusRect() function or so:
Code: Pascal  [Select][+][-]
  1. therectangle:=TImage.Create(tile[actualtileimage]);
  2.  therectangle.Left:=tile[actualtileimage].Left+4; //As a test, now I add the upper left corner coordinates of the image,
  3.  therectangle.Top:=tile[actualtileimage].Top+4; //but I want to change this to mouse coordinates.
  4.  therectangle.Width:=tilewidth;
  5.  therectangle.Height:=tileheight;            therectangle.canvas.drawfocusrect(Rect(therectangle.Left,therectangle.Top,therectangle.Left+tilewidth,therectangle.Top+tileheight));

but nothing happened. Maybe I add wrong coordinates or the scrollbox or the images are always cover the rectangle?
How can I solve this problem, if it possible?
« Last Edit: April 14, 2024, 10:05:19 am by Tomi »

TRon

  • Hero Member
  • *****
  • Posts: 3797
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #1 on: April 08, 2024, 08:41:54 pm »
My apologies upfront in case I've read this wrong but I do not see a scrollbox canvas to which you draw onto ? You seem to draw to the rectangle canvas but the code that draws the image to the scrollbox or the image placed on the scrollbox seems missing ?
I do not have to remember anything anymore thanks to total-recall.

Tomi

  • Full Member
  • ***
  • Posts: 130
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #2 on: April 09, 2024, 02:23:24 pm »
Hello TRon!

Originally I tried draw directly onto the canvas of the ScrollBox, but nothing happened, therefore I tried with that code I wrote in the first post.
So, I tried these in the OnPaint event of the ScrollBox:

Code: Pascal  [Select][+][-]
  1. canvas.drawfocusrect(Rect(imgscrollbox.Left+20,imgscrollbox.Top+20,imgscrollbox.Left+20+tilewidth,imgscrollbox.Top+20+tileheight)); //As a test value, I tried draw at its corners +20
and the other:
Code: Pascal  [Select][+][-]
  1. canvas.drawfocusrect(Rect(ScreenToClient(mouse.cursorpos).X+imgscrollbox.HorzScrollBar.Position,ScreenToClient(mouse.cursorpos).Y+imgscrollbox.VertScrollBar.Position-imgscrollbox.Top,ScreenToClient(mouse.cursorpos).X+imgscrollbox.HorzScrollBar.Position+tilewidth,ScreenToClient(mouse.cursorpos).Y+imgscrollbox.VertScrollBar.Position-imgscrollbox.Top+tileheight));

Handoko

  • Hero Member
  • *****
  • Posts: 5386
  • My goal: build my own game engine using Lazarus
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #3 on: April 09, 2024, 04:56:40 pm »
If you have a scrollbox and 2 images inside it, you need to correctly decide which canvas you want to draw on.

Try this code below. You can't see a rectangle if you do not put the coordinates correctly. So I use lines instead.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, Forms, Controls, Graphics, ExtCtrls, StdCtrls;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     chkScrollBox: TCheckBox;
  16.     chkImage: TCheckBox;
  17.     Image1: TImage;
  18.     ImageList1: TImageList;
  19.     ScrollBox1: TScrollBox;
  20.     procedure CheckBoxChange(Sender: TObject);
  21.     procedure Image1Paint(Sender: TObject);
  22.     procedure ScrollBox1Paint(Sender: TObject);
  23.   private
  24.     procedure DrawLines(C: TCanvas);
  25.   end;
  26.  
  27. var
  28.   Form1: TForm1;
  29.  
  30. implementation
  31.  
  32. {$R *.lfm}
  33.  
  34. { TForm1 }
  35.  
  36. procedure TForm1.ScrollBox1Paint(Sender: TObject);
  37. begin
  38.   if chkScrollBox.Checked then
  39.     DrawLines(ScrollBox1.Canvas);
  40. end;
  41.  
  42. procedure TForm1.Image1Paint(Sender: TObject);
  43. begin
  44.   if chkImage.Checked then
  45.     DrawLines(Image1.Canvas);
  46. end;
  47.  
  48. procedure TForm1.CheckBoxChange(Sender: TObject);
  49. begin
  50.   ScrollBox1.Invalidate;
  51. end;
  52.  
  53. procedure TForm1.DrawLines(C: TCanvas);
  54. const
  55.   len = 1000; // length of the line
  56. var
  57.   r: Single; // angle in radian
  58. begin
  59.   r := 0;
  60.   repeat
  61.     C.Line(0, 0, Round(Cos(r)*len), Round(Sin(r)*len));
  62.     r := r + 0.02;
  63.   until r > Pi/2;
  64. end;
  65.  
  66. end.

TRon

  • Hero Member
  • *****
  • Posts: 3797
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #4 on: April 09, 2024, 09:10:41 pm »
Thank you very much for the excellent example Handoko.

So, I tried these in the OnPaint event of the ScrollBox:
As noted by Handoko, make sure the coordinates makes sense.

So far I can see that you seem to use profound way of calculating the coordinates of a rectangle but they make no sense as they are coordinates related to other components (or even the screen).

The coordinates of a rectangle inside a scrollbox/image are absolute to that same scrollbox/image. So left/top coordinate 0,0 means either 0,0 of the scrollbox canvas or the image canvas. Any coordinate/pixel that does not fit onto the canvas will be clipped. So i suspect that your rectangle isn't visible because you draw it outside the visible area of your image/scrollbox canvas.

Note how Handoko's example draw lines with a length of 1000 pixels but that the lines are 'cut-off' (because the pixels of the lines that are situated outside the (available) area of the canvas are clipped).

edit:
For example the onpaint event from Handoko's example can be adjusted to read:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Image1Paint(Sender: TObject);
  2. begin
  3.   if chkImage.Checked then
  4.     DrawLines(Image1.Canvas);
  5.   Image1.Canvas.DrawFocusRect(Image1.Canvas.ClipRect);
  6. end;
  7.  
To draw a focused rectangle to the image canvas.

BTW: if you speak of images did you actually mean a TImage component or an image of a (bitmap) picture that you manually draw to the canvas of the scrolbox ?

attached a picture of a scrollbox withouth using components (just drawing).
« Last Edit: April 10, 2024, 02:40:19 am by TRon »
I do not have to remember anything anymore thanks to total-recall.

Tomi

  • Full Member
  • ***
  • Posts: 130
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #5 on: April 10, 2024, 02:53:04 pm »
Thank you for your answer, Handoko and TRon!
Maybe I confused with using of coordinates of the components.
So, I would like draw only on the images of the ScrollBox and I need to know the coordinates of the mouse on the images when I clicking on it.
Most recently I tried this solution:
in the mouse clicking event:
Code: Pascal  [Select][+][-]
  1. actualtileimage:=i;
  2. tileimage[i].invalidate;

and in the OnPaint event of the images:

Code: Pascal  [Select][+][-]
  1. rbx:=tileimage[actualtileimage].ScreenToClient(mouse.cursorpos).X+tilelist.HorzScrollBar.Position;
  2. rby:=(tileimage[actualtileimage].ScreenToClient(mouse.cursorpos).Y+tilelist.VertScrollBar.Position)-tilelist.Top;
  3. tileimage[actualtileimage].canvas.DrawFocusRect(Rect(rbx,rby,rbx+tilewidth,rby+tileheight));

Where tileimage[actualtileimage] is the actual image, on which clicking happened, and
tilelist is the name of the ScrollBox.
Now, the rectangle is visible in some places, but still not everywhere I clicked.
But now I see Handoko's example, too...

TRon

  • Hero Member
  • *****
  • Posts: 3797
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #6 on: April 10, 2024, 03:09:51 pm »
So, I would like draw only on the images of the ScrollBox and I need to know the coordinates of the mouse on the images when I clicking on it.
Perhaps I still do not understand you correctly because if you work with TImage components that are placed on the scrollbox then my first thought would be to use the onmouseenter and onmouseleave event of the TImage component. And if you so want to then can also use Onmousemove which should provide mouse-coordinates that are "inside" the TImage component itself.

But... it is still unclear to me that whenever you refer to an image if you meant a) a TImage component or b) just a drawing of an image (also referred to as a bitmap. Do you understand the difference between these two separate things ?

Let me try ask in another way: if you read this in the context of your own code
Code: Pascal  [Select][+][-]
  1. var
  2.   x : ????; // <-- what type is this ?
  3.   index: integer = 0;
  4. begin
  5.   x := tileimage[index]
  6. end;
  7.  
then what type is x ?
« Last Edit: April 10, 2024, 03:16:38 pm by TRon »
I do not have to remember anything anymore thanks to total-recall.

Tomi

  • Full Member
  • ***
  • Posts: 130
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #7 on: April 10, 2024, 04:25:09 pm »
then what type is x ?
I declare the tile images as follows:

Code: Pascal  [Select][+][-]
  1. type Ttileimages = class(TCustomimage)
  2.   private
  3.   public
  4. end;
  5. (...)
  6. tileimage: array of Ttileimages;

TRon

  • Hero Member
  • *****
  • Posts: 3797
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #8 on: April 10, 2024, 06:37:23 pm »
I declare the tile images as follows:
Ok, thank you.

I just tested with TImage components and that works as I suspected/wrote in my previous reply.

There are too many variables in your code to just show an example but, the basics are:
- create the images at runtime, owner and parent being the scrollbox and after that draw something to your image(s)
- then assign the (shared) TImage events: OnMouseEnter, OnMouseLeave and OnPaint
- Then, if so wanted, reposition your Images on the scrollbox. I used a custom procedure that positioned all the images inside the scrollbox based on a row and column count (only placing the left and top coordinates, using a static tile width and height).
- optionally set the scrollbar ranges (I did that based on the number of rows and columns icw the tile width and height but, it could be anything you want to as long as the image components themselves are positioned in the scrollbox as you want them placed. The scrollbox component itself has some provision for placement of child components as well.

reposition:
Code: Pascal  [Select][+][-]
  1. procedure Reposition(aTileArray: array of TImage; aScrollBox: TScrollBox; aRows, aColumns: integer);
  2. const
  3.   TileWidth  = 32;
  4.   TileHeight = 32;
  5. var
  6.   Tile   : TImage;
  7.   RowIdx : integer = 0;
  8.   ColIdx : integer = 0;
  9. begin
  10.   // for every tile
  11.   for Tile in aTileArray do
  12.   begin
  13.     // set bounds
  14.     Tile.SetBounds(ColIdx * TileWidth, RowIdx * TileHeight, TileWidth, TileHeight);
  15.  
  16.     // update row/col indices
  17.     inc(ColIdx);
  18.     if ColIdx >= aColumns then
  19.     begin
  20.       inc(RowIdx);
  21.       ColIdx := 0;
  22.     end;
  23.   end;
  24.  
  25.   // adjust ScrollBox ranges
  26.   aScrollBox.HorzScrollBar.Range := aColumns * TileWidth;
  27.   aScrollBox.VertScrollBar.Range := aRows    * TileHeight;
  28.  
  29.   // invalidate ScrollBox to force a visual update
  30.   aScrollBox.Invalidate;
  31. end;
  32.  

The events:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.TileMouseEnter(Sender: TObject);
  2. begin
  3.   ActiveImage := Sender as TImage;
  4.   ActiveImage.Invalidate;
  5. end;
  6.  
  7. procedure TForm1.TileMouseLeave(Sender: TObject);
  8. begin
  9.   ActiveImage := nil;
  10.   (Sender as TImage).Invalidate;
  11. end;
  12.  
  13. procedure TForm1.TilePaint(Sender: TObject);
  14. var
  15.   aTile : TImage absolute Sender;
  16. begin
  17.   if not assigned(Sender) then exit;
  18.   if aTile = ActiveImage
  19.     then aTile.Canvas.DrawFocusRect(aTile.ClientRect);
  20. end;
  21.  
Where activeImage is a variable on the form.
Code: Pascal  [Select][+][-]
  1. type
  2.   TForm1 = class(TForm)
  3.   ...
  4.    public
  5.     ActiveImage : TImage;
  6.   ...
  7.   end;
  8.  

That will more or less produce the same image/result as I posted in one of my previous posts.
« Last Edit: April 10, 2024, 07:02:33 pm by TRon »
I do not have to remember anything anymore thanks to total-recall.

Handoko

  • Hero Member
  • *****
  • Posts: 5386
  • My goal: build my own game engine using Lazarus
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #9 on: April 10, 2024, 07:24:59 pm »
I saw tile in the code, is Tomi trying to write a game map editor?

I have a very basic code showing how to do it. It's lack of many important features, but it is very lightweight. Because instead of using TImage, mine uses TBitmap.

https://forum.lazarus.freepascal.org/index.php/topic,43999.msg309134.html#msg309134

TRon

  • Hero Member
  • *****
  • Posts: 3797
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #10 on: April 10, 2024, 07:35:18 pm »
I saw tile in the code, is Tomi trying to write a game map editor?
Indeed Tomi's code seem to suggest something like that, though it could just as well be something like a frame selector or thumbnail viewer.

Your code in the link looks nice btw Handoko but, as I sidenote, I personally prefer to do things like that using a separate TileManager class/object in order to not clutter the Form too much with custom methods. As a bonus a separate class can be re-used (if not implemented too specific for a one trick pony job).
I do not have to remember anything anymore thanks to total-recall.

Handoko

  • Hero Member
  • *****
  • Posts: 5386
  • My goal: build my own game engine using Lazarus
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #11 on: April 10, 2024, 07:49:27 pm »
Your suggestion is good. But my main focus when writing the code was, to make the code as simple as possible so everyone could understand it easily.

TRon

  • Hero Member
  • *****
  • Posts: 3797
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #12 on: April 11, 2024, 12:45:00 am »
But my main focus when writing the code was, to make the code as simple as possible so everyone could understand it easily.
That is more than fair enough. It is always difficult to know when and where and to stop.

@Tomi:
I have attached a small (bad) example. If that isn't enough to help you out then please show more of your code.
I do not have to remember anything anymore thanks to total-recall.

Tomi

  • Full Member
  • ***
  • Posts: 130
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #13 on: April 11, 2024, 02:42:44 pm »
is Tomi trying to write a game map editor?
Exactly, Handoko!
I would like make a small application which can help me create a nice background image from small various images. Besides this application must be able to create a "terrain map" based on this background image.
My plan with this as follows: the background image can be assembled from tiles (it's pretty much done) and the user can save this as a common BMP image. This background has an array and its elements represent the tiles with values 0 or 1, depend on the user which value set on it with mouse click from a menu list.
This is the "terrain map", which can save as a common TXT file with the background BMP. Now, I working on making the selected tiles visible on the source images with a rectangle, when the user clicking on it.
So, the expected result the big BMP background and a TXT file, which contains the terrain map, for example:
0,0,0,1,0,0,0,0
0,0,0,1,0,0,0,0
0,0,1,1,0,0,0,0
0,1,1,0,0,0,0,0
0,1,0,0,0,0,0,0
0,1,0,0,0,0,0,0
0,1,1,0,0,0,0,0
0,0,1,0,0,0,0,0
where the 1 numbers represent e.g. a river, which are obstacles in the game.
So far I made my games from set of numbers like that, but to build up on a terrain and the background in a game the basis of it gives a rather ugly result, because the river looks like an artificial canal because of the square bends. But now, I will make a nice backround image separated from this terrain map.
But I will also check your code, Handoko; maybe your application does a similar thing.
And special thanks to you too, TRon, for your example. I will download it now.

Tomi

  • Full Member
  • ***
  • Posts: 130
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #14 on: April 11, 2024, 04:35:28 pm »
(...) show more of your code.
Here is my piece of code about the essence of my application:

Code: Pascal  [Select][+][-]
  1. sourcetiles: TScrollBox;
  2. (...)
  3. type Ttiles = class(TCustomimage)
  4.   private
  5.   public
  6. end;
  7. (...)
  8. tile: array of Ttiles;

First, we open the source image(s):

Code: Pascal  [Select][+][-]
  1. if fileOpener.Execute then
  2.    begin
  3.      tilesourceimg:=TPicture.Create;
  4.      tileosurceimg.LoadFromFile(fileOpener.FileName);
  5.      //
  6.      setlength(tile,sourcetilenumber+1);
  7.      tile[sourcetilenumber]:=Ttiles.Create(sourcetiles);
  8.      tile[sourcetilenumber].Parent:=sourcetiles;
  9.      tile[sourcetilenumber].OnMouseUp:=@tilemouseclick;
  10.      tile[sourcetilenumber].OnPaint:=@tilePaint;
  11.      if sourcetilenumber>0 then tile[sourcetilenumber].Top:=tile[sourcetilenumber-1].Top+tile[sourcetilenumber-1].height+2;
  12.      tile[sourcetilenumber].width:=tileosurceimg.width;
  13.      tile[sourcetilenumber].height:=tileosurceimg.height;
  14.      tile[sourcetilenumber].canvas.copyrect(Rect(0,0,tile[sourcetilenumber].width,tile[sourcetilenumber].height),tileosurceimg.bitmap.canvas,Rect(0,0,tileosurceimg.width,tileosurceimg.height));
  15.      inc(sourcetilenumber);
  16.      freeandnil(tileosurceimg);
  17.    end;

Second, we select the source image:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.tilemouseclick(Sender: TObject;
  2.   Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  3. var i: byte;
  4. begin
  5.   if sourcetilenumber>0 then
  6.   begin
  7.     for i:=0 to sourcetilenumber-1 do
  8.     begin
  9.          if (X>=tile[i].Left) and (X<=tile[i].Left+tile[i].width) and ((ScreenToClient(mouse.cursorpos).Y+sourcetiles.VertScrollBar.Position)-sourcetiles.Top>=tile[i].Top) and ((ScreenToClient(mouse.cursorpos).Y+sourcetiles.VertScrollBar.Position)-sourcetiles.Top<=tile[i].Top+tile[i].height) then
  10.          begin
  11.             if tiletoanother=true then
  12.             begin
  13.                 FreeAndNil(tiletoanothercanvas);
  14.                 tiletoanother:=false;
  15.             end;
  16.             tiletoanothercanvas:=Tbitmap.Create;
  17.             tiletoanothercanvas.width:=tilewidth;
  18.             tiletoanothercanvas.height:=tileheight;
  19.             tiletoanothercanvas.canvas.copyrect(Rect(0,0,tilewidth,tileheight),tile[i].canvas,Rect(X,Y,X+tilewidth,Y+tileheight));
  20.             tiletoanother:=true;
  21.             actualtileimage:=i;
  22.             tile[i].invalidate;
  23.             Break;
  24.          end;
  25.     end;
  26.   end;
  27. end;

Finally, we try to draw rectangle on selected image at the mouse coordinates:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.tilePaint(Sender: TObject);
  2. begin
  3.   if tiletoanother=true then tile[actualtileimage].canvas.drawfocusrect(Rect((mouse.cursorpos).X+sourcetiles.HorzScrollBar.Position,((mouse.cursorpos).Y+sourcetiles.VertScrollBar.Position)-sourcetiles.Top,(mouse.cursorpos).X+sourcetiles.HorzScrollBar.Position+tilewidth,((mouse.cursorpos).Y+sourcetiles.VertScrollBar.Position)-sourcetiles.Top+tileheight));
  4. end;

But the problem is that the rectangle always can be seen only on the first source image and if we have more than one tile, we can't see it on the another tiles when we clicking on it.
« Last Edit: April 11, 2024, 04:37:36 pm by Tomi »

 

TinyPortal © 2005-2018