c# winforms gdi+ - 根据其内容裁剪图像

c# winforms gdi+ - Crop an Image to it's contents

我正在 GDI+ WinForms 中制作一个绘图应用程序,我有一个功能我想添加并且已经尝试添加但我没有在网上找到任何关于它的信息。

我想拍摄一张图片(或位图,并不重要)并将其裁剪到其中所有内容所在的位置。

举个例子:

我这里有一张图片 它周围有很多白色(将图像保存到您的计算机以查看它周围有白色。)我想将图像裁剪到火柴人所在的区域,我希望它看起来像像这样:

(保存到你的电脑,你可以比较两者)

如果你看第二个,这就是我想要制作的,它已经把图像缩减到只有火柴人了!

当然,我自己也做过。

我在网上找了很多解决方案都找不到,所以我决定自己尝试,但没有成功。

这是我尝试过的:

我有一个带有图片框和按钮的简单表单 - 单击按钮后,它应该会裁剪图像。我将图片框的 BackColor 设为黑色,并将其置于图像中心,这样图像中不再出现的区域就是黑色。

图像存储在名为 ImageToChange 的位图中。

点击按钮后它应该裁剪图像 - 所以我做了一个函数来做我将从按钮调用的功能

这个功能依赖于我在网上找到的另一个裁剪图像的功能:

public Bitmap CropImage(Image source, int x, int y, int width, int height)
    {
        Rectangle crop = new Rectangle(x, y, width, height);

        var bmp = new Bitmap(crop.Width, crop.Height);
        using (var gr = Graphics.FromImage(bmp))
        {
            gr.DrawImage(source, new Rectangle(0, 0, bmp.Width, bmp.Height), crop, GraphicsUnit.Pixel);
        }
        return bmp;
    }

上面的函数应该只是根据给定的 x、y、宽度和高度进行裁剪和成像 - 我没有编写代码,但我可以看到它的作用。

我的CropToContent函数依赖于最后那个

所以,这是我创建的用于裁剪图像的函数:

public Bitmap CropToContent(Bitmap oldBmp)
    {
        Rectangle currentRect = new Rectangle();

        // Get a base color

        for (int y = 0; y < oldBmp.Height; y++)
        {
            for (int x = 0; x < oldBmp.Width; x++)
            {
                if (oldBmp.GetPixel(x, y) != Color.White)
                {
                    // We need to interpret this!

                    if (!currentRect.Contains(new Point(x, y)))
                    {
                        // This will run if this is out of the current rectangle

                        if (x > (currentRect.X + currentRect.Width)) currentRect.Width += ((currentRect.X + currentRect.Width) + x);
                        if (x < (currentRect.X))
                        {
                            // Move the rectangle over there and extend it's width to make the right the same!
                            int oldRectLeft = currentRect.Left;

                            currentRect.X = x;
                            currentRect.Width += oldRectLeft - x;
                        }

                        if (y > (currentRect.Y + currentRect.Height)) currentRect.Height += ((currentRect.Y + currentRect.Height) + y);

                        if (y < (currentRect.Y + currentRect.Height))
                        {
                            int oldRectTop = currentRect.Top;

                            currentRect.Y = y;
                            currentRect.Height += oldRectTop - y;
                        }
                    }
                }
            }
        }
        return CropImage(oldBmp, currentRect.X, currentRect.Y, currentRect.Width, currentRect.Height);
    }

如你所见,它使用了我之前提到的CropImage功能!

此函数有一个矩形,表示图像将被裁剪到的位置 - 这会随着函数的运行而修改。

该函数循环遍历位图中的所有像素,如果它不是白色,它会忽略它 - 如果它是其他任何东西,它会根据它在矩形周围的位置来执行某些操作,

如果它位于矩形的左侧,它将移动矩形的 X 并更改宽度,因此矩形的右侧仍然相同

如果它位于矩形的顶部,它将向上移动矩形的 Y 轴并更改高度...

如果它位于矩形的右侧,它将更改宽度以匹配。

如果它位于矩形的底部,它将更改高度以匹配。

如果它在矩形内部,它根本不会在意。

我不明白为什么这个功能不起作用。

在表单的 Load 上运行此代码:

ImageToChange = Properties.Resources.stickman;

        pictureBox1.Image = ImageToChange;

然后我把原封不动的火柴人放进Properties.Resources.stickman

然后点击按钮运行:

ImageToChange = CropToContent(ImageToChange);

        pictureBox1.Image = ImageToChange;

我不明白为什么这行不通,如果您阅读了整篇文章,非常感谢。

我对 "won't work" 的意思有点不明白,但我想我找到了问题所在。

错误在你的逻辑中,例如修改矩形右侧的代码行:

if (x > (currentRect.X + currentRect.Width)) currentRect.Width += ((currentRect.X + currentRect.Width) + x);

这通过 添加 x、rectangle.X 和 rectangle.Width 来修改检测到的矩形的宽度,这是错误的。你想要的大概是这样的:

if (x > (currentRect.X + currentRect.Width)) currentRect.Width = x - currentRect.X;

您需要对其余逻辑进行类似的更改。

所以,由于 Hans Passant 指出我应该逐步检查它并密切关注它,所以终于有了答案,这是一个非常小的图像。

Robin Krom 说有些逻辑不对。

这是最终结果:

public Bitmap CropToContent(Bitmap oldBmp)
    {
        Rectangle currentRect = new Rectangle();
        bool IsFirstOne = true;

        // Get a base color

        for (int y = 0; y < oldBmp.Height; y++)
        {
            for (int x = 0; x < oldBmp.Width; x++)
            {
                Color debug = oldBmp.GetPixel(x, y);
                if (oldBmp.GetPixel(x, y) != Color.FromArgb(255, 255, 255, 255))
                {
                    // We need to interpret this!

                    // Check if it is the first one!

                    if (IsFirstOne)
                    {
                        currentRect.X = x;
                        currentRect.Y = y;
                        currentRect.Width = 1;
                        currentRect.Height = 1;
                        IsFirstOne = false;
                    }
                    else
                    {

                        if (!currentRect.Contains(new Point(x, y)))
                        {
                            // This will run if this is out of the current rectangle

                            if (x > (currentRect.X + currentRect.Width)) currentRect.Width = x - currentRect.X;
                            if (x < (currentRect.X))
                            {
                                // Move the rectangle over there and extend it's width to make the right the same!
                                int oldRectLeft = currentRect.Left;

                                currentRect.X = x;
                                currentRect.Width += oldRectLeft - x;
                            }

                            if (y > (currentRect.Y + currentRect.Height)) currentRect.Height = y - currentRect.Y;

                            if (y < (currentRect.Y + currentRect.Height))
                            {
                                int oldRectTop = currentRect.Top;

                                currentRect.Y = y;
                                currentRect.Height += oldRectTop - y;
                            }
                        }
                    }
                }
            }
        }
        return CropImage(oldBmp, currentRect.X, currentRect.Y, currentRect.Width, currentRect.Height);
    }

我在使用较小的图像进行调试时发现,我必须在第一个像素上设置矩形的起点 - 它默认为 0, 0,如果第一个像素位于 2, 2,它将打开当然是对的。

矩形扩展了一个,现在位于 0, 0 的位置,宽度为 1...是的...这是不正确的 - 它需要从第一个像素所在的位置开始,所以我添加了它。

当然,感谢 Robin Krom,我修正了逻辑,并且该功能在火柴人上完美运行!

所以它是这样开始的:

结果是这样的:

还值得注意的是,if (oldBmp.GetPixel(x, y) != Color.FromArgb(255, 255, 255, 255)) 行曾经是 if (oldBmp.GetPixel(x, y) != Color.White),由于某种原因,它不起作用。