如何在 C# 中创建可点击的不规则形状区域

How to create a clickable irregularly-shaped region in c#

我有一张不规则形状的图片,例如心形或任意形状。 我可以让它在视觉上透明,但我需要让它只在形状区域上可点击。我听说我应该为此使用 "Region",但我不知道如何使用。

我试图搜索所有不为空、不透明或不为空的像素,并用它们创建一个点数组,但我无法 create/reshape 当前控制区域。我正在尝试制作一个自定义控件,您可以 select 按钮或图片,它们形状不规则且彼此靠近。

这是我正在处理的事情:

如图所示,共有8个不同的部分(假设左右两边合在一起)。正如你所看到的,它们彼此靠近,其中一些甚至可以放入其他之间的空space。

我的目标是,例如,如果我点击 Pectorals(图中的红色区域),它会变成它的彩色版本,然后一堆其他代码会 运行。

问题是,默认情况下,当我们添加任何带有 PictureBox 的图片时,它会从该图片的边界开始围绕该图片创建一个 Rectangle。因此,如果我将两张图片(如图所示)放在一起,其中一张的空白区域会阻止我点击另一张。

由于这个问题,它还引发了错误的对象 ClickEvent

我正在尝试设置 "Raise Event Region",我假设我们将其命名为 Graphic Region,就在图片所在的位置。我可以使用一个循环来收集像素的位置,该循环确定该图片的哪个坐标具有 "Color" (意味着它是图片的一部分,我想要可点击的区域)但我不能用该数据限制该区域。

我正在努力实现的一个例子:https://www.youtube.com/watch?v=K_JzL4kzCoE

最好的方法是什么?

你试过图像地图吗?

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

这应该为您提供了开始制作要覆盖在图像上的地图所需的信息。

这是一个处理图像遮罩的 Winforms 示例。当用户单击蒙版图像时,它会弹出一个消息框。这种基本技术显然可以修改以适应。

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 有一个 Image 从我徒手的心形资源加载。

解决这个问题有两种方法:

  • Regions 一起工作。

  • 使用透明 Images.

第一种方法涉及创建控件,例如 PictureBoxesPanels ,它们具有图像的 形状 并且只能在该形状内单击。

这很好,前提是您可以访问构成形状的矢量轮廓

下面是一个示例,它将 Panel 的可见和可点击 Region 限制为从跟踪点列表创建的不规则形状的 blob:

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

不幸的是,根据其中包含的点制作 Region 是行不通的;将 Region 想象成一个矢量形状列表,它们由点组成,但只是为了创建包含矢量,而不是像素..

您可以在形状周围描画,但这是一项繁重的工作,我觉得不值得。

因此,如果您没有矢量形状:请使用第二种方法:

这将假定您有图像(可能是 PNG),这些图像在不应接受任何点击的所有位置都是透明的。

最简单和最有效的方法是将它们与它们所在的点一起放在一个列表中;然后,每当它们发生变化时,将它们全部绘制到一个图像中,您可以将其分配给 PictureBox.Image.

这是一个 Mouseclick 事件,它将搜索图像列表中最顶端的 Image 以找到被单击的图像。为了将它们与它们的位置结合起来,我使用了一个元组列表:

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

我们在每个列表中搜索 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];
    }

}

这是我如何将图像合二为一的。请注意,为了测试,我已将它们添加到 ImageList 对象中。这有严重的缺点,因为所有图像都被缩放到一个共同的大小。您可能想要创建一个适合自己的列表!

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

我在启动时调用了它:

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

旁白:这种将所有图片合二为一的方式,也是唯一一种可以自由叠加图片的方式。 Winforms 不支持具有重叠控件的真实透明度。并且为每个形状测试一个 Pixel(最多)也非常快。