How to create a clickable region of irregular shape in C #

I have an irregularly shaped image, such as a heart or any random shape. I can make it transparent visually, but I need to make it clickable only on the form area. I heard that for this I have to use the "Region", but I can’t figure out how to do this.

I tried to find all the pixels that are not zero, transparent or empty, and create an array of point objects with them, but I cannot create / change the current control area. I am trying to make a custom control so that you can select a button or image and they are irregular in shape and close to each other.

Here is what I mean: enter image description here

As you can see in the picture, there are 8 different parts (provided that the right and left sides are combined). As you can see, they are close to each other, and some of them even fit into the empty space between others.

My goal, for example, if I clicked on Pectorals (the red zone in the figure), it will change to the color version and a bunch of other code will be launched.

The problem is that by default when adding an image with a PictureBox it will create a Rectangle around this image, starting at its borders. Therefore, if I place two photos (as in the picture) close to each other, one empty area does not allow me to click the other.

Also due to this problem, an invalid ClickEvent object ClickEvent .

I am trying to install the "Raise Event Region", which I assume we called it the Graphic Region only where the image exists. I can collect pixel positions with a loop that determines which coordinates of this image have “color” (this means that this is part of the image, the area I want to click into), but I can not limit this area to this data.

An example of what I'm trying to achieve: https://www.youtube.com/watch?v=K_JzL4kzCoE

What is the best way to do this?

+6
source share
3 answers

These are two approaches to this problem:

  • Work with Regions .

  • Work with transparent Images .

The first way involves creating controls, such as PictureBoxes or Panels , which are in the form of an image and are only available inside that form.

This is good if you have access to the vector path that makes up the shape.

Here is an example that restricts the visible & clickable Region from Panel to an irregular blob shape created from a list of trace points:

enter image description here

 List<Point> points = new List<Point>(); points.Add(new Point(50,50));points.Add(new Point(60,65));points.Add(new Point(40,70)); points.Add(new Point(50,90));points.Add(new Point(30,95));points.Add(new Point(20,60)); points.Add(new Point(40,55)); using (GraphicsPath gp = new GraphicsPath()) { gp.AddClosedCurve(points.ToArray()); panel1.Region = new Region(gp); } 

Unfortunately, creating a Region from the points it contains will not work; Imagine Region as a list of vector shapes, they are made up of dots, but only to create containing vectors, not pixels.

You can trace around the figures, but this is a lot of work, and imo is not worth it.

So, if you don't have vector shapes: go for the second method:

This assumes that you have images (possibly PNGs) that are transparent in all places where you don’t need to accept clicks.

The easiest and most effective way is to place them on the list along with the points where they should be located; then when they have changed, draw them all on one image, which you can assign PictureBox.Image .

Here is the Mouseclick event that will search for the topmost Image in the list of images to find the one that was clicked. To combine them with their locations, I use the Tuple list:

 List<Tuple<Image, Point>> imgList = new List<Tuple<Image, Point>>(); 

We look at this list in each Mouseclick :

 private void pictureBox1_MouseClick(object sender, MouseEventArgs e) { int found = -1; // I search backward because I drew forward: for (int i = imageList1.Images.Count - 1; i >= 0; i--) { Bitmap bmp = (Bitmap) imgList[i].Item1; Point pt = (Point) imgList[i].Item2; Point pc = new Point(eX - pt.X, eY - pt.Y); Rectangle bmpRect = new Rectangle(pt.X, pt.Y, bmp.Width, bmp.Height); // I give a little slack (11) but you could also check for > 0! if (bmpRect.Contains(e.Location) && bmp.GetPixel(pc.X, pc.Y).A > 11) { found = i; break; } } // do what you want with the found image.. // I show the image in a 2nd picBox and its name in the form text: if (found >= 0) { pictureBox2.Image = imageList1.Images[found]; Text = imageList1.Images.Keys[found]; } } 

This is how I combined the images into one. Note that for testing, I added them to the ImageList object. This has serious drawbacks, as all images are scaled to a common size. You probably want to create your own list!

 Bitmap patchImages() { Bitmap bmp = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height); imgList.Clear(); Random R = new Random(1); using (Graphics G = Graphics.FromImage(bmp) ) { foreach (Image img in imageList1.Images) { // for testing: put each at a random spot Point pt = new Point(R.Next(333), R.Next(222)); G.DrawImage(img, pt); // also add to the searchable list: imgList.Add(new Tuple<Image, Point>(img, pt)); } } return bmp; } 

I called it at startup:

 private void Form1_Load(object sender, EventArgs e) { pictureBox1.Image = patchImages(); } 

In addition: this way of drawing all the images in one is also the only one that allows you to overlay images freely. Winforms does not support real transparency with overlapping controls. And testing one Pixel (no more) for each of your shapes is also very fast.

+4
source

Here is an example of Winforms for image mask processing. When the user clicks on the mask image, a message box appears. Obviously, this basic technique can be changed.

 public partial class Form1 : Form { readonly Color mask = Color.Black; public Form1() { InitializeComponent(); } private void pictureBox1_Click(object sender, EventArgs e) { var me = e as MouseEventArgs; using (var bmp = new Bitmap(pictureBox1.Image)) { if (me.X < pictureBox1.Image.Width && me.Y < pictureBox1.Image.Height) { var colorAtMouse = bmp.GetPixel(me.X, me.Y); if (colorAtMouse.ToArgb() == mask.ToArgb()) { MessageBox.Show("Mask clicked!"); } } } } } 

pictureBox1 has an Image loaded from a heart shape resource, which I am free.

+1
source

Have you tried the image card?

http://www.w3schools.com/TAGS/tag_map.asp

This should give you what you need to create a map to overlay on your image.

0
source

Source: https://habr.com/ru/post/986851/


All Articles