4

I have an image that you click 2 points on and it creates a line in between each point. In the end what this department is asking for is the ability to calculate the length of those lines and where those lines occurred. They are currently doing this by hand w/ paper/pen/ruler. This is one of the images I'm working with.

Example Image

Those cracks in the middle are considered "area 7".

So I needed a way to, aside from saving my x & y location to measure the lines later, also add the location they occurred to my list. Below is the only way I knew to do this but its proving to be more of a mess. Because the picture isn't an exact square or rectangle there are a lot of overlapping areas and a lot of areas where there will be voids, that the zones don't get covered very well.

Is there a better way to do this? (currently I'm just using a messagebox to show where I've clicked, I haven't went as far as doing anything with the data yet until I get this right.)

    if (e.Button.Equals(MouseButtons.Left))
    {
        Rectangle zone1 = new Rectangle(35, 30, 770, 30);
        if (zone1.Contains(e.Location))
        {
            MessageBox.Show("Zone1");                  
        }
        Rectangle zone2 = new Rectangle(890, 40, 330, 300);
        if (zone2.Contains(e.Location))
        {
            MessageBox.Show("Zone2");
        }
        Rectangle zone3 = new Rectangle(340, 340, 850, 60);
        if (zone3.Contains(e.Location))
        {
            MessageBox.Show("Zone3");
        }
        Rectangle zone4 = new Rectangle(100, 25, 75, 300);
        if (zone4.Contains(e.Location))
        {
            MessageBox.Show("Zone4");
        }
        //4-1 trying to cover areas missed in zone4
        Rectangle zone41 = new Rectangle(255, 270, 120, 240);
        if (zone41.Contains(e.Location))
        {
            MessageBox.Show("Zone4-1");
        }
        Rectangle zone5 = new Rectangle(310, 100, 180, 150);
        if (zone5.Contains(e.Location))
        {
            MessageBox.Show("Zone5");
        }
        //5-1 trying to cover areas missed in zone5
        Rectangle zone51 = new Rectangle(220, 80, 60, 45);
        if (zone51.Contains(e.Location))
        {
            MessageBox.Show("Zone5-1");
        }
        Rectangle zone6 = new Rectangle(635, 35, 250, 210);
        if (zone6.Contains(e.Location))
        {
            MessageBox.Show("Zone6");
        }
    }
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
Lee
  • 133
  • 11
  • Is the image generated from vector data you have access to, or are you essentially expected to do feature recognition? If the latter, is this a one-off sort of thing, or does it need to be automated? If it's a one-off, you could manually generate an image for each region (say, fill in each region in black for the corresponding image) and test per pixel whether the place the user clicked was in that region. The answer depends a lot on your constraints. – adv12 Jan 10 '17 at 18:47
  • Re: including "C#" in the title, actually I thought that was frowned upon. But maybe the "rules" have changed. – adv12 Jan 10 '17 at 18:50
  • 2
    _"Editing this to include C# in title as requested"_ - that's odd, because the [general consensus is that tags should not be included in titles](http://meta.stackexchange.com/questions/19190/should-questions-include-tags-in-their-titles) – stuartd Jan 10 '17 at 18:50
  • Someone years ago drew the image (probably in MS Paint) to represent the part they braze in the furnace. They then just print off tons of pages. Any "voids" on the part that weren't brazed together properly they draw a line on the paper. So each person may have 10+ pages at the end of the day they turn in. So they will create these lines on the image, submit to a table on the SQL server then reset and start a new image for the next part. I hope that helps some. – Lee Jan 10 '17 at 18:51
  • @adv12 I don't know, it was an edit request on my post suggesting I make that change. – Lee Jan 10 '17 at 18:53
  • It sounds like you have been given an unreasonable assignment. – adv12 Jan 10 '17 at 18:56
  • @adv12 lol tell me about it brother. But I gotta figure something out. It seems like this would be easier using wpf but I don't have time to learn it (on a deadline). I'm thinking it might be better just to splice the image up and then fit them together when the form loads? So I can just say anything on picturebox1 clicked is zone 4, picturebox2 clicked is zone 1..etc. Unfortunately it create a lot more code to draw for each picturebox but it would work I guess better than this? – Lee Jan 10 '17 at 19:02
  • Is your image fixed or dynamic? If it's fixed, and you have control of it, then you could just write the zone number to one of the color channels (or perhaps a different image that's the same size) and then use Bitmap.GetPixel to retrieve the information when they click it. If you don't store the zone information in the image then you'll have no choice but to define zone boundaries, perhaps using a list of line segments, and then test against that. – RogerN Jan 10 '17 at 19:10
  • I think you need to be looking into Convex hull – johnny 5 Jan 10 '17 at 19:11
  • FYI, you don't need your regions to be convex. Take a look at the `Region` and `GraphicsPath` objects. You can create a `GraphicsPath` from a list of points, and then convert it into a `Region`. The `Region` object has a built-in method for checking if a point is contained within it. – RogerN Jan 10 '17 at 19:14
  • @RogerN Its fixed. When you said color channels I immediately thought yea I could give them the option to just draw the lines in different colors and create key so they know to select red for zone 4, blue for zone 1 and so on. But I don't think that's what you meant. So when you say write the zone number to a color channel can you explain that some more or provide a link I can read? – Lee Jan 10 '17 at 19:15
  • @Lee Separating the channels might be overkill now that I think about it. Let's just say that you could use a copy of your image to act as a zone map. Fill each zone with a different color, and then use GetPixel to extract the color from your zone map when the user clicks. Based on the color, you'll know which zone was clicked. The zone map is invisible to the user; it's just a behind-the-scenes image that you use to get zone information. – RogerN Jan 10 '17 at 19:19
  • @RogerN I see. I like that idea. I'm just not familiar with making that image invisible once colored in yet using it to see where they clicked. And would the color being used to draw the line replace the colored in area though or is the drawing only being done on the original image and the map separate? – Lee Jan 10 '17 at 19:29
  • @RogerN Is this what you were referring to? http://stackoverflow.com/questions/28792723/adding-or-subtracting-color-from-an-image-in-a-picturebox-using-c-sharp – Lee Jan 10 '17 at 19:41

1 Answers1

2

My proposal is that you use a background image as a zone map. This image will not be displayed to the user at all. You load it once and keep it in memory, but continue to display your original image and draw lines on it as usual. Then, when the user clicks on your image, you check the color of the zone map to determine the zone.

For example, let's say I'm using these two images as my display and and my zone map:

Zone display Zone map

Both image are loaded in your code, but only the display map is shown to the user:

class MyForm
{
    Bitmap zoneDisplay;
    Bitmap zoneMap;

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        zoneDisplay = (Bitmap)Image.FromFile(@"c:\temp\zonedisp.png"); // replace with actual path to file
        zoneMap = (Bitmap)Image.FromFile(@"c:\temp\zonemap.png");

        // put the display image into the picturebox (or whatever control displays it)
        pictureBox.Image = zoneDisplay;
    }

Then, when the user clicks your image, just check the color on the zone map:

private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
    var color = _zoneMap.GetPixel(e.X, e.Y);
    if (color == Color.FromArgb(0, 0, 255))
        MessageBox.Show("Zone 1");
    else if (color == Color.FromArgb(255, 0, 0))
        MessageBox.Show("Zone 2");
    else if (color == Color.FromArgb(0, 255, 0))
        MessageBox.Show("Zone 3");
    // etc...
}

If your colors are slightly off then you may need to perform a less exact comparison. Example:

static int ColorDelta(Color c1, Color c2)
{
    return Math.Abs(c1.R - c2.R) + Math.Abs(c1.G - c2.G) - Math.Abs(c1.B - c2.B);
}

private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
    var color = _zoneMap.GetPixel(e.X, e.Y);
    if (90 > ColorDelta(color, Color.FromArgb(0, 0, 255)))
        MessageBox.Show("Zone 1");
    else if (90 > ColorDelta(color, Color.FromArgb(255, 0, 0)))
        MessageBox.Show("Zone 2");
    else if (90 > ColorDelta(color, Color.FromArgb(0, 255, 0)))
        MessageBox.Show("Zone 3");
    // etc...
}
RogerN
  • 3,761
  • 11
  • 18
  • Sounds great. Let me work on that a bit with what time I have left here at the office and I'll post back. – Lee Jan 10 '17 at 19:52
  • Oh, and in case it wasn't obvious, you should use a lossless image format such as BMP or PNG. Do not use JPG for your zone map because it will distort the colors. – RogerN Jan 10 '17 at 19:56
  • I'm using PNG. I opened up 2 copies of my image. I manually colored the areas for the zones using simple RGB color's in paint, (red, green, blue..etc) used the code above to load them in and for mouse down, and it doesn't appear to work. Is just coloring in the areas of my image map not the right way to do it? I'm new to this method but I don't see where it compares the 2 images maybe? – Lee Jan 10 '17 at 20:14
  • It sounds like you're doing it right. Perhaps your RGB values are off by a little bit? Did you double-check the exact values using the Edit Colors window in Paint? What colors are you getting when you call GetPixel? – RogerN Jan 10 '17 at 20:29
  • Looks like Red for example is showing 0,0,255 correctly in Paint. Ok here's where I'm a bit confused. I put a stop on `GetPixel` and clicked on the displayed image where I know Red exist on the hidden map and this is what comes back `"{Name=ffed1c24, ARGB=(255, 237, 28, 36)}"` – Lee Jan 10 '17 at 20:34
  • Red should be 255,0,0 actually. The order is R, G, B. It looks like you're getting a slightly different shade of red from GetPixel, though. Are you sure the image wasn't saved as a JPG at some point? – RogerN Jan 10 '17 at 20:38
  • Well originally the image was a JPG. I however used Windows Snipping Tool and grabbed the part of the image I needed out of a larger JPG image and saved my snippit as a PNG. That PNG was then opened in Paint and the colors applied and saved still as a PNG. So I would think taking, what is basically a screen shot of a JPG and saving the screen shot as a PNG should still be ok? – Lee Jan 10 '17 at 20:43
  • The colors are off somehow. You can either re-color it very carefully (perhaps with GIMP or something else besides Paint), or use a broader comparison. For example you could subtract the R, G, B values from your target color and consider the total difference. – RogerN Jan 10 '17 at 20:47
  • I've edited my example to include less exact color comparisons at the bottom. – RogerN Jan 10 '17 at 20:52
  • Yea when I punch those numbers in I get a shade of Yellow. Should I be getting 4 numbers back on the GetPixel RGB value though? Is that normal? – Lee Jan 10 '17 at 20:53
  • It was MS Paint. I used a different program and it worked fine. Thanks a ton man. – Lee Jan 10 '17 at 20:57
  • Good idea. Reminds me of [this one](http://stackoverflow.com/questions/33478564/interactiveclickable-map/33480232?s=1|5.6980#33480232) – TaW Jan 11 '17 at 16:25