7

I have an irregularly-shaped picture like a heart or any random shape. I can make it transparent visually, but I need to make it clickable only on the shape area. I heard that I should use "Region" for this, but I can't figure out how.

I tried to search all pixels that aren't null, transparent, or empty and create a point array with them, but I can't create/reshape the current control region. I'm trying to make a custom control that you can select a button or picture, and they are irregularly-shaped and close to one another.

Here is what I'm dealing with: enter image description here

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

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

The problem is, by default when we add any Picture with a PictureBox, it will create a Rectangle around that picture starting from the boundaries of it. So if I place two pictures (as in the figure) close together, one's empty zone prevents me from clicking the other's.

Also it raises the wrong object's ClickEvent because of this problem.

I'm trying to set "Raise Event Region" that I assume we called it Graphic Region just where the picture exists. I can collect positions of pixels with a loop which determines which coordinates of that picture has "Color" (meaning it is part of the picture, the area that I want clickable) but I can't limit that area with that 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?

Mohammad Javad
  • 573
  • 1
  • 7
  • 29
İsmail Barış
  • 142
  • 1
  • 11
  • Webforms, Windows Forms, WPF, GTK...? – jdphenix May 09 '15 at 00:23
  • 1
    No worries. It'll just help you get an answer. – jdphenix May 09 '15 at 00:28
  • If needed I can provide images that demostres my work, what I'm trying to do – İsmail Barış May 09 '15 at 00:37
  • Can you explain, where those shapes come from? To work with actual 'Regions', which is a good idea imo, you need to have code that creates them. Think of this as a series of draw/fill methods that build up the region. To be precide a Graphicspath, which can be turned into a Region, which can finally be applied to a Control, which will then be limited and clickable only withinthe Region.. – TaW May 09 '15 at 07:39
  • I have updated my answer to include a solution, which directly answers your question. But working with transparent images looks more promising, imo than providing vector outlines, in addition to the actual images you need anyway.. – TaW May 09 '15 at 11:40
  • Sorry for that late answe, yes I made progress but few problems still occours, such as corrupted image display, when I edit/alter region of picture somehow although "clicking" problem solved, image borders corrupted a lot. I can't working on my project for a long time due to personal stuff,the time a fixed the problem I will share the solve and example code. Thanks for every help by the way thank you all! – İsmail Barış Aug 03 '15 at 03:56
  • Finally I menaged to do it, but since I need to check every pixel in the bitmap, It has performance issues. so next goal is process bitmap and save object's final state as a file and use it on run-time. I'll share the code soon. Still I need help about saving object's last state ( image with manipulated region) – İsmail Barış Oct 07 '15 at 07:11

3 Answers3

6

These are two approaches to this problem:

  • Work with Regions.

  • Work with transparent Images.

The first method involves creating controls, e.g PictureBoxes or Panels , which have the shape of the image and are only clickable inside that shape.

This is nice, provided you have access to the vector outline that makes up the shape.

Here is an example that restricts the visible&clickable Region of a Panel to an irregularly-shaped blob 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 making a Region from the points contained in it will not work; imagine a Region as a list of vector shapes, these are made up of points, but only to create containing vectors, not pixels..

You could trace around the shapes but that is a lot of work, and imo not worth it.

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

This will assume that you have images (probably PNGs), which are transparent at all spots where no clicks should be accepted.

The simplest and most efficient way will be to put them in a list together with the points where they shall be located; then, whenever they have changed, draw them all into one image, which you can assign to a PictureBox.Image.

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

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

We search through 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(e.X - pt.X, e.Y - 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];
    }

}

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

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();
}

Aside: This way of drawing all the images into one, is also the only one that lets you overlap the images freely. Winforms does not support real transparency with overlapping Controls.. And testing one Pixel (at most) for each of your shapes is also very fast.

TaW
  • 53,122
  • 8
  • 69
  • 111
  • In 2. Case I should patch images all over again if I need to change part of it right ? – İsmail Barış May 09 '15 at 16:07
  • Exactly. This is a fast operation, so don't worry! You need to maintain the search list with its points and should take care to keep only one copy of each image.. – TaW May 09 '15 at 17:12
  • List>Image> will good choice to store images than ImageList ? – İsmail Barış May 09 '15 at 20:12
  • Store while the program runs? Yes, all images are independent. They won't compile into the exe, but that's probably not what you want anyway. – TaW May 09 '15 at 20:25
1

Here is a Winforms example of handling an image mask. When the user clicks on the mask image it pops up a message box. This basic technique can obviously be modified to suit.

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 resource of a heart shape that I free-handed.

jdphenix
  • 15,022
  • 3
  • 41
  • 74
  • This looks like it will only handle pixels within the pictureBox that match the color of mask. Even if he's only working with solid color images, that doesn't allow for any other mask color objects anywhere in frame. – mjw May 09 '15 at 01:00
  • In this case we assume that, Image is painted black right? By the way thanks for answer, I'll improve my question, this is so far best answer but not exacly what I mean. – İsmail Barış May 09 '15 at 01:00
  • One possibility I can think of would be subclassing `PictureBox` and modifying the handler for messages to allow clicks through, then using the subclass to display the "real" image. Just a minute (or two) – jdphenix May 09 '15 at 01:05
  • Can you re-check main post ? I think this explains better. – İsmail Barış May 09 '15 at 01:10
  • The link in @mjw comment explains how to do it with WinForms - you need to modify the associated control's `Region` property with `GraphicsPath` to draw where a click can be registered for that control – jdphenix May 09 '15 at 01:36
  • Can you give me a simple small example How to use it ? Or any link. Thank you both so much for helping me so far. – İsmail Barış May 09 '15 at 03:27
0

Have you tried an image map?

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

That should give you what you need to get started making a map to overlay on your image.

mjw
  • 1,196
  • 1
  • 12
  • 19
  • Ah, my mistake. Here's an Image Map project for win forms: http://www.codeproject.com/Articles/2820/C-Windows-Forms-ImageMap-Control – mjw May 09 '15 at 00:30
  • Let's say I have a custom image and assume that, it is angel wings, can this prevent raise click event when i click empty area within wings? Or let say I have a circle and another circle inside of it,will inner circle won't prevent with its empty area to click outer circle. This is my problem – İsmail Barış May 09 '15 at 00:36
  • Generally I believe you'll need to detect where the mouse clicks happen and determine if the clicked coordinate is within the borders of the angel wings or whatever your image may be. The linked project above explains it a bit better, but you'll need to define a set of polygons that make up your image outline and use that as the clickable region. – mjw May 09 '15 at 00:41