【问题标题】:c# winforms gdi+ - Crop an Image to it's contentsc# winforms gdi+ - 将图像裁剪到其内容
【发布时间】:2018-02-11 20:19:52
【问题描述】:

我正在 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;

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

【问题讨论】:

  • 调试代码时总是从小处着手,这张图片的像素太多了,你很容易失去耐心。从 1x1 位图开始,可以很好地检查边界条件。接下来是 3x3,中心有一个像素。然后一个在边缘。然后两个。到那时,您将消除 99% 的错误。
  • 好的,那我试试小一点的图片。

标签: c# winforms bitmap gdi+ crop


【解决方案1】:

我对你所说的“不会工作”的意思有点理解,但我想我找到了问题。

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

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;

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

【讨论】:

  • 好的,我将更改其余的逻辑,还值得一提的是 if (bmp.GetPixel(x, y) == Color.White) 似乎不起作用,如果这是我的两个问题,它应该是 if (bmp.GetPixel(x, y) == Color.FromArgb(255, 255, 255, 255))将此答案标记为答案。
  • 原来存在三个问题,所以我给出了实际答案。
  • 颜色比较不起作用的事实是您的位图可能具有像素格式 Format32bppArgb(或其他 32 位),您还需要比较 alpha。一个解决方案是从位图本身获取像素的颜色。 Greenshot 有一个实现,它尝试获取每个角的颜色的裁剪区域,并查看哪个生成最大的矩形:github.com/greenshot/greenshot/blob/master/GreenshotPlugin/Core/…
【解决方案2】:

所以,感谢 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);
    }

我发现在使用较小的图像进行调试时,我必须在第一个像素上设置 Rectangle 的起点 - 它默认为 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),但由于某种原因它不起作用。

【讨论】:

    猜你喜欢
    • 2018-04-22
    • 1970-01-01
    • 2012-08-24
    • 2012-04-10
    • 2013-12-19
    • 1970-01-01
    • 2011-04-23
    • 2017-08-04
    • 1970-01-01
    相关资源
    最近更新 更多