Results 1 to 35 of 35

Thread: [RESOLVED] 'Clickable' areas

  1. #1

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Resolved [RESOLVED] 'Clickable' areas

    Hi,

    To make 'Clickable' areas I've always just put everything in a TableLayoutPanel and put Buttons, PictureBoxes, Panels etc. in the cells in the area I want to respond to a 'Click'.

    Suppose, as in my current case, the square nature of the TLP cell isn't compatible with the areas I wish to detect, some areas aren't completely covered, or two or more areas fall within the same TPL cell.

    There are only eighty-odd areas I need to detect but the nature of the task in hand doesn't allow the shape of the detectable areas to change, they're fixed, and many of them fall within at least one other.

    Short of making the cells ridiculously small, and therefore huge in number, is there something else I can do ?


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  2. #2
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,348

    Re: 'Clickable' areas

    You don't need any controls. Just create a Rectangle for each clickable area and put them into a collection. Handle the MouseClick event of the form and then loop through your list of Rectangles and call Contains on each one, passing e.Location as the Point. If Contains returns True for a Rectangle then you know the user clicked in that area and you can then do whatever is appropriate.

    Which type of collection to use depends on how you want to react to a click. One option would be to use a Dictionary where the Rectangles are the keys and the values are whatever you want to use to react to a click. That might be data that you pass to another method or it might be a delegate that you invoke. That's up to you.

  3. #3
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,348

    Re: 'Clickable' areas

    I've provided code on how to do this before but I'm not sure exactly when. You're free to search for it if you want to.

  4. #4

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Quote Originally Posted by jmcilhinney View Post
    You don't need any controls. Just create a Rectangle for each clickable area and put them into a collection. Handle the MouseClick event of the form and then loop through your list of Rectangles and call Contains on each one, passing e.Location as the Point. If Contains returns True for a Rectangle then you know the user clicked in that area and you can then do whatever is appropriate.
    Thanks John,
    I can see how that would work for straight sided 'Shapes' and will (try to) remember it if there's a next time, but how do I cope with curves, almost all my areas have two straight edges and two which are arcs, and two are complete circles.

    Picture a dartboard.


    Poppa.

    PS.

    I like the 'Contains' suggestion, I had something similar in mind but doubt if I'd've thought of that.

    Pop.
    Last edited by Poppa Mintin; Apr 19th, 2018 at 08:18 AM. Reason: PS
    Along with the sunshine there has to be a little rain sometime.

  5. #5
    Frenzied Member
    Join Date
    May 2014
    Location
    Central Europe
    Posts
    1,372

    Re: 'Clickable' areas

    you can use graphicpaths and its .isVisible Method

  6. #6
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,348

    Re: 'Clickable' areas

    As suggested, GraphicsPath.IsVisible will do basically the same thing as Rectangle.Contains. The GraphicsPath objects will be a bit harder to construct, but the structure of a dartboard would make it a fairly orderly process to create them all.

    It also seems that a Dictionary(Of GraphicsPath, Integer) is the collection you need, where the values are the scores for each section. Your hit test in the MouseClick event handler would look something like this:
    vb.net Code:
    1. For Each gp In myDictionary.Keys
    2.     If gp.IsVisible(e.Location) Then
    3.         Dim score = myDictionary(gp)
    4.  
    5.         'Use score here.
    6.  
    7.         Exit For
    8.     End If
    9. Next

  7. #7
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: 'Clickable' areas

    If it is like a dartboard, in the past I've just used Angle and distance from the center to determine what area was hit.
    The logic was something along the lines of .
    Code:
    'Assume there are 20 slices around 360 degree circle so we'll normalize the angle to a number from 0 to 19.
    'but we have to offset the angle by 1/2 since the center of a segment is straight up, not the edge of a segment.
    'Angles are in radians, so 2*Pi radians per 360 degrees.
    'To divide the circle into 20 parts the angle arc is (2 * PI) / 20  i.e. 360 degrees divided by 20.
    'In other words, PI/10, i.e. .3141592653...
    
    Dim Triple, Double As Boolean  'they are initialized locally as False
    Dim OffTheBoard as Boolean
    
    arc = Math.Pi / 10
    
    dx = x - centerX
    dy = y - centerY
    distance = Sqr(dx * dx + dy * dy)
    If Distance less than 30 then
      you hit the bullseye
    
    ElseIf distance less than 50 then
      you hit the outer bullseye
    
    Else
      '       (Angle        - 1/2 arc) \ arc  'change angle from 0 to 359 to 0 to 19
      Slice = (Atan2(dx,dy) - arc/2) \ arc
      
      If distance is < 150 then
        'you hit the inner segment
      elseif distance is < 170 then
        Triple = True
      elseIf distance is < 300 then
        'you hit the outer segment
      elseIf distance is < 310 then
        Double = True
      else
       OffTheBoard = True
      end if
    
      If Not OffTheBoard Then
        'Convert 0 to 19 to score value
        'If one of the booleans are set, double or triple the score
      End If
    It may seem like a fair amount of code, but it is not much compared setting up all the segments as graphic paths, and of course it is quick since it comes down to a couple of calculations, and then just selecting what to do based on the results.
    The distance to the triple and double ring areas will be the same for all 20 segments, you just need to determine what they are for your graphic. Also, the size of your bullseye and size of the outer bullseye and where the center of the bullsEye is in your graphic.

    p.s. It might be Atan(dy,dx), but in this case it may not matter since you will be mapping the values 0 to 19 to a segment. If you put in the Atan code, then click on the various segments and display the 0 to 19 value, you can determine which is 0 and which direction 1 to 19 go (clockwise or counter clockwise) and map your scores accordingly.
    Last edited by passel; Apr 19th, 2018 at 10:18 AM.

  8. #8
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: 'Clickable' areas

    Another option I've used in the distant past, is just to create a separate graphic the same size as your image, which you would keep hidden and color the 80 different shapes with 80 unique colors. Then when a click occurs, you just look up the color of the pixel in the same position in your lookup map and use it as the key to which item was clicked on.

    I haven't used that in quite awhile, as it seemed more reliable in the old days when you had a system with limited predefined color values that weren't subject to being modified by color memory depth or color compression schemes.

  9. #9

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Thanks guys, that's given me something to think about.

    I may be a bit tardy replying to this (or any) thread, I'm in a hotel in Cyprus, picked as ok because it says it has WiFi, yes it has WiFi, which has 'No connection to the internet' far more often than it's 'Connected', the hotel is far from the madding crowd so no other options are available.

    Trying to connect to MSDN to read-up on graphicpaths took nearly three hours after I'd managed to get posts #5 & 6, then the connection lasted nearly 15 minutes !


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  10. #10
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,348

    Re: 'Clickable' areas

    It's not going to help you on this occasion but you can install MSDN documentation locally, so you don't need a connection to read it. You need some room for it, of course, but if you only install what you need then you can keep it to a minimum.

  11. #11

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Quote Originally Posted by passel View Post
    Another option I've used in the distant past, is just to create a separate graphic the same size as your image, which you would keep hidden and color the 80 different shapes with 80 unique colors. Then when a click occurs, you just look up the color of the pixel in the same position in your lookup map and use it as the key to which item was clicked on.
    Thanks Passel, I like this idea.

    In a test project I've set-up your suggestion. I have two PictureBoxes of exactly the same size, PictureBox1.Image is the 'Visible' graphic and PictureBox2.Image has the colour-coded graphic.

    Originally PictureBox1 was located exactly over PictureBox2 so that the cursor location was the same for them both.
    When I couldn't get the result I'm after, I shifted them so that they're side-by-side. Still no joy, maybe you meant not 'Visible = False', but PictureBox2 is located off screen, so I made PictureBox2 visible, but still 'On screen' in case that was the problem.
    Still not getting sensible results.

    I changed the PictureBox from which I'm trying to read the pixels; to PictureBox1 to hopefully eliminate transposition errors but the results are still wrong... Not just wrong, non-existant.
    The original relevant line used to be: -
    " Dim bit_Map As Bitmap = CType(PictureBox2.Image, Bitmap)".

    I've added some labels so I can monitor results without having to step though all the time, the only value which changes with different clicks is Label4... The cursor's Y position, just there to check that the subroutine is in fact being called.
    Code:
        Private Sub PictureBox1_Click(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseClick
            Dim colVal, posX, posY As Int32
            Dim bit_Map As Bitmap = CType(PictureBox1.Image, Bitmap)
            Dim colour As Color
    
            posX = e.X
            posY = e.Y
            colour = bit_Map.GetPixel(posX, posY)
            colVal = colour.ToArgb
            colour = Color.FromArgb(colVal)
    
            ' Display the selected colour.
            Label1.Text = "R:" & colour.R.ToString
            Label2.Text = "G:" & colour.G.ToString
            Label3.Text = "B:" & colour.B.ToString
            Label4.Text = posY.ToString
    
        End Sub
    Clearly I'm doing something wrong, or not doing something I ought...


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  12. #12
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: 'Clickable' areas

    Have you allowed for the SizeMode of the PictureBox? If that's Zoom, Center or Stretch then the pixel positions e.X, e.Y won't necessarily correspond to the pixels in the original image. You could use the picture box's DrawToBitmap method to get a bitmap which will give you pixel colours corresponding to the click location, regardless of the size mode.

    BB

  13. #13

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Quote Originally Posted by boops boops View Post
    Have you allowed for the SizeMode of the PictureBox? If that's Zoom, Center or Stretch then the pixel positions e.X, e.Y won't necessarily correspond to the pixels in the original image. You could use the picture box's DrawToBitmap method to get a bitmap which will give you pixel colours corresponding to the click location, regardless of the size mode.BB
    Would that apply even though both PictureBoxes are exactly the same size and both the images have SizeMode selected to Stretchimage, and the graphics are identical except for the colours ?

    I think my problem is more in the area of not actually getting the color because wherever I Click on the PictureBox the color value returned is exactly the same. The cursor Y location is the only value which changes.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  14. #14

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Latest up-date:

    Trying to prove to myself that I'm looking at the correct pixel in PictureBox2, I've added a small (40x40Px) third PictureBox, (PictureBox3), Background color yellow to contrast with every other colour in PictureBox2.
    In the Click Subroutine, I place this PictureBox at the pixel that I'm trying to read in PictureBox2.
    As near as I can tell, the top left corner of this PictureBox is in PictureBox2 exactly where the cursor was clicked in PictureBox1, so I'm pretty sure I'm trying to read the correct pixel.
    Code:
         Private Sub PictureBox1_Click(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseClick
            Dim colVal As Int32
            Dim getCol, col3 As Color
            Dim here As Point = New Point((PictureBox2.Location.X + e.X), (PictureBox2.Location.Y + e.Y))
    
            Using bit_Map As New Bitmap(PictureBox2.Image)
                getCol = bit_Map.GetPixel(here.X, here.Y)
            End Using
            colVal = getCol.ToArgb
            col3 = Color.FromArgb(colVal)
    
            ' Display the selected getCol.
            Label1.Text = "R:" & col3.R.ToString
            Label2.Text = "G:" & col3.G.ToString
            Label3.Text = "B:" & col3.B.ToString
            Label4.Text = e.Y.ToString
            PictureBox3.Location = here
        End Sub
    The colors returned are always: -

    R:252
    G:0
    B:60


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  15. #15
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: 'Clickable' areas

    Forget about using 2 picture boxes for the moment, because that has nothing do with the problem. Here's what I think is happening.

    Suppose the Image in PictureBox2 is originally 1000x1000 pixels, and its bottom right hand corner is Color.SkyBluePink. PictureBox2 is 100x100 pixels. Because it's set to SizeMode.StretchImage, you can see a SkyBluePink patch in the bottom right corner of the picture box. Click on it and you get:
    e.X = 99
    e.Y = 99
    which is the bottom right hand corner of the picture box.
    But then, ignoring unnecessary complications in your code, you write the equivalent of:
    Code:
    getCol = PictureBox2.Image.GetPixel(e.X, e.Y)
    PictureBox2.Image is a reference to the original image, not to the shrunken version you see in the picture box. So you get the color at pixel (99, 99) of the image, which isn't SkyBluePink at all but Color.UnwashedNappy. The SkyBluePink colour you thought you were going to get, but didn't, is to be found at pixel (999, 999) of the image.

    Here's a quick way to test if this is what is actually happening. Set the SizeMode of both picture boxes to AutoSize. If I'm not mistaken, you should get the right colour when you click on it.

    BB

    edit: by the way, adding the picture box location to the coordinates as in your last code example is a mistake. Just use e.X and e.Y.

    edit2: I just looked at your last post again and noticed you were PictureBox3 for some or other purpose. Careless of me to miss that but I think it doesn't clarify anything. The main thing is to remember that the values of e.Location, e.X and e.Y are in the coordinates of whichever picture box was clicked. In other words, the top left corner of the picture box (actually client area, excludes border) is always (0, 0).

  16. #16

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Quote Originally Posted by boops boops View Post
    But then, ignoring unnecessary complications in your code, you write the equivalent of:
    Code:
    getCol = PictureBox2.Image.GetPixel(e.X, e.Y)
    Here's a quick way to test if this is what is actually happening. Set the SizeMode of both picture boxes to AutoSize. If I'm not mistaken, you should get the right colour when you click on it.
    BB
    Thanks BB,

    I tried AutoSize but that blew the PictureBox out of the ballpark. Took over the whole form.
    So... Back to the drawing board, resize both images to the same size as they are on my form and try again. (Use AutoSize)

    Brilliant, exactly the result I want. All I have to do now is do it all again for the size I want in the application I have in mind.

    Thanks again.


    Code:
    getCol = PictureBox2.Image.GetPixel(e.X, e.Y)
    Results in: -
    Error BC30456 'GetPixel' is not a member of 'Image'.
    So I've left it as it was... But tidy.
    Code:
     Private Sub PictureBox1_Click(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseClick
            Dim bit_Map As New Bitmap(PictureBox2.Image)
            Dim getCol As Color = bit_Map.GetPixel(e.X, e.Y)
    
            ' Display the selected getCol.
            Label1.Text = "R:" & getCol.R.ToString
            Label2.Text = "G:" & getCol.G.ToString
            Label3.Text = "B:" & getCol.B.ToString
        End Sub
    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  17. #17
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: 'Clickable' areas

    I tried AutoSize but that blew the PictureBox out of the ballpark. Took over the whole form.
    So... Back to the drawing board, resize both images to the same size as they are on my form and try again. (Use AutoSize)
    So, AutoSize didn't work so you had to manually resize the images?

    It sounds like you were shrinking the pictures down by using stretch mode originally.
    I don't think I would want to restrict the display size to a particular size, if that would be your original intention for flexibility.
    If it were me, since shrinking the picture will modify some of the colors, I would be a bit wary.
    Most likely, I would keep the mapping bitmap its original size and scale the x,y value so you don't modify the colors of your original image.
    If you know the dimensions of the original image and you multiply by the clicked position by the scalefactor of original/displayed width or height when looking up the value, you should handle any click without having to resize your original image.
    I have to go, so can't give any additional details right now if you thing you need them.

  18. #18
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: 'Clickable' areas

    Poppa was indeed using Stretch as he said in post #13 and that is the cause of the problem.

    Poppa, I'll use the term "colour map" to refer to the full-size image with colour areas. You could avoid blurring by repainting your colour map at the desired size using InterpolationMode.NearestNeighbor. But this unnecessary because the colour map doesn't have to be visible. In fact, it doesn't even have to be a PictureBox: you just pick pixel colors from the colour map itself.

    The image with clickable areas will be in PictureBox1 with SizeMode.StretchImage. For testing purposes, use any multicoloured image as PictureBox1.Image. PictureBox1 can be of any size, although the smaller it is the lower the clicking resolution. Use a label (Label1) to show the results. Here's how you can pick a colour by clicking on the image.
    Code:
     Private ClickedColor As Color
    
     Private Sub PictureBox1_MouseClick(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseClick
          Using bmp As New Bitmap(PictureBox1.Image)
             Dim x As Integer = CInt(Math.Floor(e.X * bmp.Width / PictureBox1.ClientSize.Width))
             Dim y As Integer = CInt(Math.Floor(e.Y * bmp.Height / PictureBox1.ClientSize.Height))
             ClickedColor = bmp.GetPixel(x, y)
          End Using
          Label1.BackColor = ClickedColor
          Label1.Text = ClickedColor.ToString
       End Sub
    I hope you can see that what this does is to scale up the values of e.X and e.Y to match the bitmap size. The purpose of Cint(Math.Floor... is to prevent rounding up, which could produce an invalid x and y value if you click at the right or bottom edge.

    Finally, how do you go about creating your colour map from the clickable areas? Assuming all the areas are defined geometrically (made up of lines, rectangles, ellipses, arcs etc.): you don't. It's unnecessary. You just create a GraphicsPath for your dartboard, and use that both for painting it and for clicking. You don't need a colour map because GraphicsPath.IsVisible does the same job, as DigitalShaman recommended in Post #5 above. I can give you a suitable code example if no one else does first.

    Of course, if your dartboard starts off as a raster image instead of something you draw geometrically ... that's a different kettle of fish.

    BB

  19. #19

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    I'm stuck without the Internet on my laptop again. I can only get to VBforums on my phone.
    No suitable pubs in the area
    Along with the sunshine there has to be a little rain sometime.

  20. #20

  21. #21

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Quote Originally Posted by Poppa Mintin View Post
    I'm stuck without the Internet on my laptop again. I can only get to VBforums on my phone.
    No suitable pubs in the area
    OK, I've got internet, but I don't know for how long !

    I had an idea... (Not always good news !)

    Instead of using two PictureBoxes, use just one (PictureBox1), set the Background image as the 'Code' image and the Image as the 'Viewed'.

    I tried that, with both images set to sizemode. Worked first time...

    Now here's the odd bit. As a test I added a button which makes PictureBox1 100px wider. Until I click this button it all works as it ought, I figure whatever size the PictureBox is the background image will follow, but no... it all goes wrong after clicking.
    Using a breakpoint, I checked the size of the PictureBox After the line I've highlighted in red.
    Code:
    Private Sub PictureBox1_Click(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseClick
            Dim bit_Map As New Bitmap(PictureBox1.Backgroundimage)
            Dim getCol As Color = bit_Map.GetPixel(e.X, e.Y)
    
            ' Display the selected getCol.
            Label1.Text = "R:" & getCol.R.ToString
            Label2.Text = "G:" & getCol.G.ToString
            Label3.Text = "B:" & getCol.B.ToString
        End Sub
    Yes, PictureBox1 width is now 700px, (from 600)... Well I could see that !
    But when I check the width of the backbgound image, it's still 600px !

    What's going on here ?


    Poppa.
    Last edited by Poppa Mintin; Apr 25th, 2018 at 03:56 AM. Reason: typo !
    Along with the sunshine there has to be a little rain sometime.

  22. #22
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: 'Clickable' areas

    Quote Originally Posted by Poppa Mintin View Post
    Yes, PictureBox1 width is now 700px, (from 600)... Well I could see that !
    But when I check the width of the backbgound image, it's still 600px !

    What's going on here ?
    Maybe you forgot to set the BackgroundImageLayout property to Stretch. I hope you weren't expecting the original background image object to change size!

    BB

  23. #23

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Quote Originally Posted by boops boops View Post
    Maybe you forgot to set the BackgroundImageLayout property to Stretch. I hope you weren't expecting the original background image object to change size!

    BB
    Oh bother !
    The line:
    I tried that, with both images set to sizemode. Worked first time...
    Ought to have said:
    I tried that, with both images set to Stretch. Worked first time...
    I figured that the Background image will stretch with the PictureBox, and since I'm reading the BitMap each time I call the PictureBox Click subroutine I did expect the Bitmap to change.

    I tried changing the code thus:
    Code:
    Private Sub PictureBox1_Click(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseClick
            Dim bit_Map As Bitmap
            bit_Map is Nothing 'Ensure we get a new bitmap.
            bit_Map = New Bitmap(PictureBox1.Backgroundimage)
            Dim getCol As Color = bit_Map.GetPixel(e.X, e.Y)
    But the Background image width remains at 600px

    (I still have major problem connecting to the internet at this hotel)


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  24. #24
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: 'Clickable' areas

    You could reduce your bitmap to the "stretched" size like this:
    Code:
        bit_Map = New Bitmap(PictureBox1.Backgroundimage, PictureBox1.ClientSize)
    This produces a scaled copy of the original image. But it uses the default InterpolationMode, which is a quick&dirty blur that could result in some ambiguous pixels at the colour boundaries. To avoid that possibility, you could rescale the image using NearestNeighbor like this:
    Code:
          Dim bit_Map As New Bitmap(PictureBox1.ClientSize.Width, PictureBox1.ClientSize.Height)
          Using g As Graphics = Graphics.FromImage(bit_Map)
             g.InterpolationMode = Drawing2D.InterpolationMode.NearestNeighbor
             g.DrawImage(PictureBox1.BackgroundImage, PictureBox1.ClientRectangle)
          End Using
    BB

  25. #25
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: 'Clickable' areas

    What I was referring to in post #17 is you can just leave your "code" map a single size and scale the X,Y values of where you click on your stretched map to the coordinates of that fixed size map.
    A little bit of math will be much better than creating extra bitmaps on the fly and since you are using Stretch mode, the X,Y values will map easily, i.e. you don't have to account for the offsets and proportional stretch that occurs if you're using Zoom mode.

    As a test case, I just docked a picturebox in a form to fill the form and set its sizemode to stretch. I added an image to the picturebox at design time and I loaded a copy of the same bitmap into my "map" bitmap in code at startup.

    So, the displayed image will be stretched to whatever size I make the form, and I just convert the click coordinates on that picturebox to a percentage, and multiply that percentage by the original width and height to get it into the original scale and check the pixel's color.

    No reason to scale the code map when it is much less work for the computer and memory resources to just scale the coordinate values for your lookup.

    p.s. This is really just a repeat of the example boops boops posted in post #18. The primary difference is it creates the copy only once since there is no need to create a new bitmap on every click. I assume that was done by boops boops just as an expedient for the example. Perhaps by reiterating the approach a second time, you'll see what we're accomplishing here. Storing the code map in a standalone bitmap will likely be better than having to convert the image to a bitmap in order to get to the getpixel method, and certainly can't take anymore memory than using the BackgroundImage property of the picturebox.
    Code:
    Public Class Form1
      Private mapBitmap As Bitmap
    
      Private Sub Form1_Shown(sender As Object, e As System.EventArgs) Handles Me.Shown
        mapBitmap = New Bitmap(PictureBox1.Image)
      End Sub
    
      Private Sub PictureBox1_MouseClick(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseClick
        Dim pbx As PictureBox = DirectCast(sender, PictureBox)
        Dim pctX As Double = e.X / pbx.ClientSize.Width
        Dim pctY As Double = e.Y / pbx.ClientSize.Height
        Dim pixX As Integer = CInt(Math.Floor(pctX * mapBitmap.Width))
        Dim pixY As Integer = CInt(Math.Floor(pctY * mapBitmap.Height))
    
        Dim ClickedColor = mapBitmap.GetPixel(pixX, pixY)
        Label1.BackColor = ClickedColor
        Label1.Text = ClickedColor.ToString
    
      End Sub
    End Class
    Last edited by passel; Apr 26th, 2018 at 09:53 AM.

  26. #26
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: 'Clickable' areas

    Quote Originally Posted by passel View Post
    p.s. This is really just a repeat of the example boops boops posted in post #18. The primary difference is it creates the copy only once since there is no need to create a new bitmap on every click. I assume that was done by boops boops just as an expedient for the example. Perhaps by reiterating the approach a second time, you'll see what we're accomplishing here. Storing the code map in a standalone bitmap will likely be better than having to convert the image to a bitmap in order to get to the getpixel method, and certainly can't take anymore memory than using the BackgroundImage property of the picturebox.
    I expect you are right, it's better to create a standalone bitmap instead of creating a new one each time. Actually I have no idea how long it takes to recast an image as a bitmap. I've always assumed it was just a matter of altering a few pointers and header bytes, but I could be wrong: maybe there is an array.copy involved too. Still, the code map (or colour map as I called it) isnt likely to be multiple megabytes in size, so I would guess will only take a handful of microseconds to convert as opposed to the millisecond scale on which a click event is processed.

    All the same, creating a colour map and a dictionary or some other strucutre to link each segment of the graphic to a unique colour must take a bit of work. Imagining how I would do it myself, I'm certain I would do it using GraphicsPaths, but I assume that Poppa is doing this for the fun of it and would like to do it his way. The experience of being stuck in Cyprus at this time of year with only unsuitable pubs to go to ... well, it must be kinda harrowing.

    BB

  27. #27

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Back home !

    Hi guys

    I've not been able to get online for more than a few minutes at a time until now; so I gave up trying to follow your suggestions. Trying to understand MSDN with an internet connection which lasts for nearly ten minutes isn't exactly the best thing for ones temper.

    So as ol' blue eyes said... "I did it my way" and just made four Background and Image pairs in different sizes, so I just measure the screen size and use the most appropriate pair.

    The project works for all the screen sizes my machine will accommodate (16 different sizes from 800 x 600 to 1920 x 1080) so I expect I'll leave it at that. I will study all your answers and try 'em out in a test project and maybe chop out three sets of the completed project if I find I can make one of your suggestions work.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  28. #28

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Quote Originally Posted by boops boops View Post
    Poppa, I'll use the term "colour map" to refer to the full-size image with colour areas. You could avoid blurring by repainting your colour map at the desired size using InterpolationMode.NearestNeighbor. But this unnecessary because the colour map doesn't have to be visible. In fact, it doesn't even have to be a PictureBox: you just pick pixel colors from the colour map itself.

    The image with clickable areas will be in PictureBox1 with SizeMode.StretchImage. For testing purposes, use any multicoloured image as PictureBox1.Image. PictureBox1 can be of any size, although the smaller it is the lower the clicking resolution. Use a label (Label1) to show the results. Here's how you can pick a colour by clicking on the image.
    Code:
     Private ClickedColor As Color
    
     Private Sub PictureBox1_MouseClick(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseClick
          Using bmp As New Bitmap(PictureBox1.Image)
             Dim x As Integer = CInt(Math.Floor(e.X * bmp.Width / PictureBox1.ClientSize.Width))
             Dim y As Integer = CInt(Math.Floor(e.Y * bmp.Height / PictureBox1.ClientSize.Height))
             ClickedColor = bmp.GetPixel(x, y)
          End Using
          Label1.BackColor = ClickedColor
          Label1.Text = ClickedColor.ToString
       End Sub
    I hope you can see that what this does is to scale up the values of e.X and e.Y to match the bitmap size. The purpose of Cint(Math.Floor... is to prevent rounding up, which could produce an invalid x and y value if you click at the right or bottom edge.

    BB
    Wow ! I should've actually tried this, I think I was getting frustrated by the time post #18 came in and not paying enough attention. (Sorry BB)

    I just worked out what you were telling me, tried it in a test project and it works very well, after some trial and error until the penny dropped and I realised what you were doing.

    I'm definitely going to use this method, it'll tidy up some bits I wasn't too happy with.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  29. #29

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Quote Originally Posted by boops boops View Post
    Poppa, I'll use the term "colour map" to refer to the full-size image with colour areas. You could avoid blurring by repainting your colour map at the desired size using InterpolationMode.NearestNeighbor. But this unnecessary because the colour map doesn't have to be visible.
    In fact, it doesn't even have to be a PictureBox: you just pick pixel colors from the colour map itself.
    BB
    I'm struggling to find how to do this.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  30. #30

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Hmmm...

    I guess that's what they call 'Sod's Law' !

    The very next thing I tried worked, I'm well pleased.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  31. #31
    Hyperactive Member
    Join Date
    Aug 2014
    Posts
    285

    Re: 'Clickable' areas

    Didn't read all the posts, but if you're having a ton of clickables, jmcil's initial idea with rectangles is the way to go. Catch the mouseclick and do all your click-related stuff in a specific sub, checking which rect contains your mouse etc.

    As for irregularly shaped objects, there's no straight answer With circles you would use a rectangle and then measure how far the click was from the center of that rectangle and check if it's within the circles radius (it's pretty basic math).
    With the really odd shaped objects which are hard (or sometimes impossible) to make sense of mathematically (ie not circles/rectangles etc) I don't really have a good answer outside of pixel detection, but what I always end up doing in my games is designing so that I don't have to deal with it.

    I don't know what kind of program you're developing, but unless you're working on hit detection for a new Counter-Strike or something, I find it hard to believe you need that kind of precision. Very few applications bothers with precise mouse detection (even the start button on my windows10 is a circle but the bounding box is a rectangle).

  32. #32

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Quote Originally Posted by Nirwanda View Post
    Didn't read all the posts
    This says it all.

    Thanks anyway.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  33. #33

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Quote Originally Posted by boops boops View Post
    The image with clickable areas will be in PictureBox1 with SizeMode.StretchImage. For testing purposes, use any multicoloured image as PictureBox1.Image. PictureBox1 can be of any size, although the smaller it is the lower the clicking resolution. Use a label (Label1) to show the results. Here's how you can pick a colour by clicking on the image.
    Code:
     Private ClickedColor As Color
    
     Private Sub PictureBox1_MouseClick(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseClick
          Using bmp As New Bitmap(PictureBox1.Image)
             Dim x As Integer = CInt(Math.Floor(e.X * bmp.Width / PictureBox1.ClientSize.Width))
             Dim y As Integer = CInt(Math.Floor(e.Y * bmp.Height / PictureBox1.ClientSize.Height))
             ClickedColor = bmp.GetPixel(x, y)
          End Using
          Label1.BackColor = ClickedColor
          Label1.Text = ClickedColor.ToString
       End Sub
    I hope you can see that what this does is to scale up the values of e.X and e.Y to match the bitmap size. The purpose of Cint(Math.Floor... is to prevent rounding up, which could produce an invalid x and y value if you click at the right or bottom edge.
    BB
    Thanks BB, this has proved to be quite brilliant and extremely accurate.

    Just a comment with regard to the section I've highlighted in blue. For a while now I've adopted the use of the Integer divide, since John Mc. showed it to me.

    I tried it in this application and it works perfectly well for the 16 different screen resolutions my machine will accommodate (800x600 to 1920x1080, 1280x600 being the most difficult resolution when I was using four different sets of images).

    So this code ...
    Code:
                
                Dim x As Integer = CInt(Math.Floor(e.X * bmp.Width / PictureBox1.ClientSize.Width))
                Dim y As Integer = CInt(Math.Floor(e.Y * bmp.Height / PictureBox1.ClientSize.Height))
    ... is simplified in this manner.
    Code:
                Dim x As Int32 = e.X * bmp.Width \ PictureBox1.ClientSize.Width
                Dim y As Int32 = e.Y * bmp.Height \ PictureBox1.ClientSize.Height
    A bit tidier and more readable don't you think ?


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  34. #34

    Thread Starter
    PowerPoster Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Bottesford, North Lincolnshire, England.
    Posts
    2,429

    Re: 'Clickable' areas

    Quote Originally Posted by Poppa Mintin View Post
    Hmmm...
    I guess that's what they call 'Sod's Law' !
    The very next thing I tried worked, I'm well pleased.
    Poppa.
    Hmmm... Turns out I didn't get it right at all...
    It's so much easier than I thought, just use the coded file in rescourses as the bitmap without having to do anything with it.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  35. #35
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: 'Clickable' areas

    Quote Originally Posted by Poppa Mintin View Post
    A bit tidier and more readable don't you think ?
    Poppa.
    You're right, the code is neater and the integer division prevents a potential index error in this case. I was being over-cautious.

    BB

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width