Recent

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

TRon

  • Hero Member
  • *****
  • Posts: 2673
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #15 on: April 12, 2024, 12:30:56 am »
Quote
Here is my piece of code about the essence of my application:
Though highly appreciated there is still a lot missing so probably have some errors in my reply because I did not comprehend fully what your code does.

Code: Pascal  [Select][+][-]
  1. tilesourceimg:=TPicture.Create;
  2. tileosurceimg.LoadFromFile(fileOpener.FileName);
  3. ...
  4. freeandnil(tileosurceimg)
  5.  
The use of tilesourceimg to load the picture(s) seem redundant to me because the image you created (TCustomImage) harbours/exposes the same loading functionality.

Code: Pascal  [Select][+][-]
  1. tile[sourcetilenumber].canvas.copyrect(Rect(0,0,tile[sourcetilenumber].width,tile[sourcetilenumber].height),tileosurceimg.bitmap.canvas,Rect(0,0,tileosurceimg.width,tileosurceimg.height));
  2.  
That line seems redundant to me since the image is already create with the scrollbox (sourcetiles) as owner. It is suffice to set the left and top coordinates to place it at the location that you want the tile to appear in the scrollbox. The only exception to that would be if your images need to be drawn scaled in the scrollbox but there are better solutions for that.

Quote
Second, we select the source image:
Almost all of that code seems redundant to me. When you click the mouse on a tile then the sender of that method is the tile so you already know which tile is 'pressed'.

I understand that you want to know the index of the image so that you can use that later on in your code but (and I forgot to make use of that in my own example as well) you can (ab)use the Tag property of the (custom) image component at creation time and set it to the current of your tiles array.

And to make sure you understand, you should not (have to) paint anything in the OnMouseUp event.

You can make use of the OnMouseEnter and onMouseLeave events to set/unset the active index in your tile array.

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;
  5.  
If you make use of a global variable for/to the index to your tile array then it is possible to use that variable to get the 'active' tile, and use it's own canvas to draw (a focused rectangle) on top the existing image. It will only do that for the image that is active as long as you check for that.

There is absolutely no use for using the mouse-coordinates there and that is even not counting that the global mouse variable uses screen coordinates (instead of relative component coordinates).

Besides that you also need to take the scrollbar range (both horizontal and vertical) into account in order to retrieve the absolute coordinates of the image on the scrollbox.

In my example code I used:
Code: Pascal  [Select][+][-]
  1. procedure TTileManager.DoTilePaint(Sender: TObject);
  2. var
  3.   Item: TTileManager.TTile absolute Sender;
  4. begin
  5.   if not assigned(Sender)
  6.     then exit;
  7.  
  8.   if Item = ActiveTile // <-- this item (tile) is active so we want to draw a rectangle
  9.     then Item.Canvas.DrawFocusRect(Item.Canvas.ClipRect); // <-- use the image's own canvas and dimensions
  10. end;
  11.  

I would really advise to study my example a bit longer and play with things.

You can easily do that by adding a TMemo component to the form (form, not the scrollbox) align it to a side of the form (leave/set the scrollbox to client (again)) and use multiple memo1.append('some message') at different places to see/identify what the LCL actually does behind the scenes.

Quote
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.
Most likely the coordinates are a bit off but you also seem to be drawing at the wrong location(s) at the wrong time(s). imho your image-click-draw related code seems much too complicated and can be simplified (a lot).

Please don't be too upset with my comments, as they are only meant to try make you code better/cleaner. In my experience it works a lot quicker when not beating things around the bush.
« Last Edit: April 12, 2024, 12:35:30 am by TRon »

Tomi

  • Full Member
  • ***
  • Posts: 125
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #16 on: April 12, 2024, 02:32:32 pm »
TRon: So, I need to remove some unnecessary lines from the code and rearrange slightly.
Yes, I tend to overcomplicate things sometimes. Maybe it stems from my lack of knowledge.
But I'm starting to understand what you wrote, I just don't know how most of the controls of Lazarus work yet. E.g. the ScrollBox. :D

Jamie: I didn't know TDrawGrid, but I'll look into it now. I don't know much about possibilities of Lazarus yet...

TRon

  • Hero Member
  • *****
  • Posts: 2673
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #17 on: April 12, 2024, 06:36:43 pm »
TRon: So, I need to remove some unnecessary lines from the code and rearrange slightly.
Well, it is not a requirement of course but to me it seems that you do not fully grasp the concept of placing components on a scrollbox.

My example has the intention to show two different methods:
1 - using the scrollbox's own automated solution to contain multiple components (timage in our example)
2 - pretend that there are components on the scrollbox by only drawing them (the actual bitmap of the timage) to the surface of the canvas. My example used tfakeimage for that.


Quote
Yes, I tend to overcomplicate things sometimes. Maybe it stems from my lack of knowledge.
Which is not a problem. The only difference is that whenever I do not know about a component I create s small self contained example and start playing with the component sometime just twiddling with the properties in order to see what happens. That is also why I do not know details about all components that are available simply because I have not been able to play with them yet as that takes an enormous amount of time and by the time you are up to date about a component then the component gets a new update where it is possible things work differently or behaviour changed.

Quote
But I'm starting to understand what you wrote, I just don't know how most of the controls of Lazarus work yet. E.g. the ScrollBox. :D
See above 2 answered paragraphs... at least I hope that will be able to shed a light.

And in case that was not enough then ask.

I do not know a lot about tscrollbox either (for example I do not know much about the behaviour of the (auto) arrangement of child components). I learn as I go along or someone asks a question or shows an example.

Just keep in mind that it is very difficult to look into someone else his personal code and line of thought by only using some small snippets and not having the full source-code in front. That is why it is usually faster to play with a small example that can be shared.

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2237
  • Fifty shades of code.
    • Delphi & FreePascal
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #18 on: April 12, 2024, 11:54:03 pm »
Very good answers so far, I personal would replace TImage with TPanel (remove default borderstyle) and draw whatever on it, utilise TPanels click/mouse over/etc event to simplify the progress.
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

Tomi

  • Full Member
  • ***
  • Posts: 125
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #19 on: April 13, 2024, 10:54:55 am »
Okay, I start to understand! As TRon mentioned ("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.") I used wrong coordinates, because I didn't take it into account.
So, when I click into a tile image in the ScrollBox, the coordinates begin to Left and Top corner of the image and that is the 0,0 point and when I want to draw e.g. in this corner, I have to add this code: Rect(0,0,tilewidth,tileheight).
On the basis of it, this is the solution:
Code: [Select]
procedure TForm1.tilePaint(Sender: TObject);
begin
  if tiletoanother=true then
     tile[actualtileimage].canvas.drawfocusrect(Rect(ScreenToClient(mouse.cursorpos).X+sourcetiles.HorzScrollBar.Position,tile[actualtileimage].ScreenToClient(mouse.cursorpos).Y,ScreenToClient(mouse.cursorpos).X+sourcetiles.HorzScrollBar.Position+tilewidth,tile[actualtileimage].ScreenToClient(mouse.cursorpos).Y+tileheight));
end;
« Last Edit: April 13, 2024, 11:23:59 am by Tomi »

Handoko

  • Hero Member
  • *****
  • Posts: 5207
  • My goal: build my own game engine using Lazarus
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #20 on: April 13, 2024, 11:42:15 am »
Okay, I start to understand! .... So left/top coordinate 0,0 means either 0,0 of the scrollbox canvas or the image canvas.

Correct!

And that's the reason why my example used multiple lines that point to the (0, 0) and have length = 1000 pixels so you can know where is the origin coordinate. Unfortunately, you didn't get it.

Tomi

  • Full Member
  • ***
  • Posts: 125
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #21 on: April 14, 2024, 10:04:38 am »
The bumpy road of programming... :D
It was a bit difficult for me to understand the characteristics of a modern development tool with some Turbo Pascal background behind me, but I learned a lot again - thanks for it!

TRon

  • Hero Member
  • *****
  • Posts: 2673
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #22 on: April 14, 2024, 07:44:29 pm »
Okay, I start to understand! As TRon mentioned ("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.")
Indeed and as pointed out by Handoko.

Things depend on how you implement them and for that it is advisable to use the road of the least resistance (you seem to ignore that road) :-)

Thus, whenever you use mouseevents such as OnMouseUp, OnMouseDown and/or OnMouseMove the x and y coordinates that are passed along those events are those coordinates that belong to the component which "fired" the event.

The same goes for OnMouseEnter and OnMouseLeve in that the events are fired when the corresponding component fires the event(s).

So the x and Y coordinates of a OnMouseClick for a TImage correspond to the x and y coordinates within that component, top-left corresponding to x,y of 0,0. At the same time when that event is fired you also know which image was clicked because the image that was clicked sens a pointer of itself by using the sender parameter.

Thus as long as you attach a handler to the OnMouseClick event you do not even have to check for mouse coordinates whether or not they belong to a particular Image component because you already know which image was clicked.

The same logic can be applied to TScrollbox. These mouseevents are only fired when the mouse event happens inside the scrollbox and also here the x/y coordinates correspond to the left top of the component itself, thus 0,0 being left top onside the scrollbox.

But, there is one small thing differs in comparison to other components and that is that it is a container with a scrollable
view. The x and y coordinates passed in these mouse events are those of the visible area of the scrollbox (the part of the scrollbox where you are looking at at that exact moment). The issue there is that the (left-top) coordinates of the components themselves that are placed inside the scrollbox are not corresponding to their real coordinates inside the scrollbox because the possibility arise that the current view is 'shifted' from the original 0,0 top left coordinates of the client-area of the scrollbox. Ergo you need to compensate for that using the scollbar range and only when you want to 'calculate' the placement of a particular component that is situated inside that scrollbox.

As you can see for yourself the latter only applies when you do not physically placed the components inside the scrollbox (whether done manually by using the visual designer or at runtime) but in case you only draw something to the surface (client area) of the scrollbox. Then there would be need to do manually check for mouse coordinates.

So I believe that you still do not fully grasp the concept because the last solution shown is doing all kinds of calculations and there is absolutely no need for any calculation whatsoever. The event happened, and it happened inside the clicked component thus as long as every timage placed on the client area of the scrollbox  has that event assigned then it becomes easy.

This is also why I opted to create a small test project to tinker with because when a scrollbox is placed onto a form, and the visual designer is used to place several image components on the client area of the scrollbox, then assign a (single) mouse event to/for all these images then you can are able to come up with the same conclusion in about 2 seconds.

Just saying  :)

Handoko

  • Hero Member
  • *****
  • Posts: 5207
  • My goal: build my own game engine using Lazarus
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #23 on: April 15, 2024, 08:01:12 am »
It was a bit difficult for me to understand the characteristics of a modern development tool with some Turbo Pascal background behind me, but I learned a lot again - thanks for it!

I downloaded and tested TRon's code, it should offer what you need to know. But I think that is the disadvantage of Object Oriented Programming, OOP often makes simple thing hard to understand especially in the eyes of non-skilled programmers.

Here I wrote a super simple demo showing how do draw focus rectangle on clicked item using TScrollBox + TImage:
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, ExtCtrls;
  9.  
  10. const
  11.   ColumnCount = 5;
  12.   RowCount    = 4;
  13.   ColumnWidth = 100;
  14.   RowHeight   = 70;
  15.  
  16. type
  17.  
  18.   { TForm1 }
  19.  
  20.   TForm1 = class(TForm)
  21.     Image1: TImage;
  22.     ImageList1: TImageList;
  23.     ScrollBox1: TScrollBox;
  24.     procedure FormCreate(Sender: TObject);
  25.     procedure ItemClick(Sender: TObject);
  26.     procedure ItemPaint(Sender: TObject);
  27.   private
  28.     FGrid:         array [0..RowCount-1, 0..ColumnWidth-1] of TImage;
  29.     FSelectedItem: TImage;
  30.   end;
  31.  
  32. var
  33.   Form1: TForm1;
  34.  
  35. implementation
  36.  
  37. {$R *.lfm}
  38.  
  39. { TForm1 }
  40.  
  41. procedure TForm1.FormCreate(Sender: TObject);
  42. var
  43.   NewImage: TImage;
  44.   i, j:     Integer;
  45. begin
  46.   ScrollBox1.Anchors := [akLeft, akRight, akTop, akBottom];
  47.   FSelectedItem      := nil;
  48.   for i := 0 to RowCount-1 do
  49.     for j := 0 to ColumnCount-1 do
  50.     begin
  51.       NewImage            := TImage.Create(ScrollBox1);
  52.       NewImage.Parent     := ScrollBox1;
  53.       NewImage.Left       := j * ColumnWidth;
  54.       NewImage.Top        := i * RowHeight;
  55.       NewImage.Width      := ColumnWidth;
  56.       NewImage.Height     := RowHeight;
  57.       NewImage.Images     := ImageList1;
  58.       NewImage.ImageIndex := 0;
  59.       NewImage.OnClick    := @ItemClick;
  60.       NewImage.OnPaint    := @ItemPaint;
  61.       FGrid[i, j]         := NewImage;
  62.     end;
  63. end;
  64.  
  65. procedure TForm1.ItemClick(Sender: TObject);
  66. begin
  67.   if not(Sender is TImage) then Exit;
  68.   FSelectedItem := TImage(Sender);
  69.   ScrollBox1.Invalidate;
  70. end;
  71.  
  72. procedure TForm1.ItemPaint(Sender: TObject);
  73. var
  74.   aRect: TRect;
  75. begin
  76.   if not(Sender is TImage) then Exit;
  77.   if not(Sender = FSelectedItem) then Exit;
  78.   aRect := FSelectedItem.ClientRect;
  79.   aRect.Inflate(-2, -2);
  80.   FSelectedItem.Canvas.DrawFocusRect(aRect);
  81. end;
  82.  
  83. end.

I tend to overcomplicate things sometimes. Maybe it stems from my lack of knowledge.

There are usually more than one ways solving the same problem in programming. Your case can be done using TBitmap, TDrawGrid, TPanel, TScrollBox or whatever. It's hard to say which one is better, just choose the one you like.
« Last Edit: April 15, 2024, 08:03:14 am by Handoko »

jcmontherock

  • Full Member
  • ***
  • Posts: 239
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #24 on: April 15, 2024, 04:00:46 pm »
Answer to the first question:
« Last Edit: April 16, 2024, 11:04:40 am by jcmontherock »
Windows 11 UTF8-64 - Lazarus 3.2-64 - FPC 3.2.2

Handoko

  • Hero Member
  • *****
  • Posts: 5207
  • My goal: build my own game engine using Lazarus
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #25 on: April 15, 2024, 05:43:47 pm »
Nice!

I downloaded and test your ScrollPaint.zip but I believed you provided the wrong code.

TRon

  • Hero Member
  • *****
  • Posts: 2673
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #26 on: April 15, 2024, 06:20:57 pm »
I downloaded and tested TRon's code, it should offer what you need to know. But I think that is the disadvantage of Object Oriented Programming, OOP often makes simple thing hard to understand especially in the eyes of non-skilled programmers.
It does offer that and perhaps you are right that OOP can be difficult for someone unfamiliar. I my defense at the time I created the example I did not know whether TS was only painting on the scrollbox or actually placing components on it (which makes the code even more difficult to understand because I tried to make generic solution that was able to do both).

Quote
Here I wrote a super simple demo showing how do draw focus rectangle on clicked item using TScrollBox + TImage:
Thank you for that example Handoko  8-)

It is what I had in mind (though my code uses hovering over an image to draw the focused rectangle) and it can't get any more simpler than that :)

I downloaded and test your ScrollPaint.zip but I believed you provided the wrong code.
Indeed that looks a whole of a lot like the first example code that you posted Handoko...

@jcmontherock: you might perhaps want to (double) check that.
« Last Edit: April 15, 2024, 06:23:33 pm by TRon »

Tomi

  • Full Member
  • ***
  • Posts: 125
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #27 on: April 16, 2024, 10:59:57 am »
Woa... thanks for deep explanation, experts!
Handoko, the problem was in my case that there was one (or more) big source image on which I had to draw a certain size rectangle to indicate the clipping area. So, not the exact client rect, but a smaller, flexible area, depend on the size of tiles used for assembly the other image, which is the terrain BMP.
But the important thing is I have to add the name of the component not only at the draw command, but also at the coordinates of drawing, as you did in your example:
Code: Pascal  [Select][+][-]
  1. aRect := FSelectedItem.ClientRect;
where FSelectedItem is the name of the TImage component.
But I will publish my application if it will be done and you will see better its structure.

jcmontherock

  • Full Member
  • ***
  • Posts: 239
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #28 on: April 16, 2024, 11:08:24 am »
Yes, sorry...  >:(
« Last Edit: April 16, 2024, 11:10:58 am by jcmontherock »
Windows 11 UTF8-64 - Lazarus 3.2-64 - FPC 3.2.2

Handoko

  • Hero Member
  • *****
  • Posts: 5207
  • My goal: build my own game engine using Lazarus
Re: How can I draw a rectangle onto an image of a ScrollBox?
« Reply #29 on: April 23, 2024, 07:30:55 am »
But I will publish my application if it will be done and you will see better its structure.

Any news about the application? I am curious to see what you're currently working on. Some screenshots maybe.

 

TinyPortal © 2005-2018