c# 如何使洪水填充在颜色渐变上工作?

c# how to make flood fill work over color gradients?

我在 C# 方面还不是很有经验,必须编写一个在颜色发生轻微变化(如图片中的阴影)时也能正常工作的填充算法。我发现了一种基于堆栈的 4 向算法,我对其进行了修改,以更改 RGB 光谱中与之前的值(整个 "RGB-Test" 部分)略有不同的像素,而不是仅更改一个区域颜色:

private void FloodFill2(Bitmap bmp, Point pt, Color targetColor, Color replacementColor)
    {
        Stack<Point> pixels = new Stack<Point>();
        pixels.Push(pt);

        while (pixels.Count > 0)
        {
            Point a = pixels.Pop();
            if (a.X < bmp.Width && a.X > 0 &&
                    a.Y < bmp.Height && a.Y > 0)//make sure we stay within bounds
            {
                // RGB-Test Start
                green = false;
                red = false;
                blue = false;

                if (bmp.GetPixel(a.X, a.Y).G > targetColor.G)
                {
                    if (targetColor.G - bmp.GetPixel(a.X, a.Y).G > (-20))
                    {
                        green = true;
                    }
                }
                else
                {
                    if (bmp.GetPixel(a.X, a.Y).G - targetColor.G > (-20))
                    {
                        green = true;
                    }
                }

                if (bmp.GetPixel(a.X, a.Y).R > targetColor.R)
                {
                    if (targetColor.R - bmp.GetPixel(a.X, a.Y).R > (-20))
                    {
                        red = true;
                    }
                }
                else
                {
                    if (bmp.GetPixel(a.X, a.Y).R - targetColor.R > (-20))
                    {
                        red = true;
                    }
                }

                if (bmp.GetPixel(a.X, a.Y).B > targetColor.B)
                {
                    if (targetColor.B - bmp.GetPixel(a.X, a.Y).B > (-20))
                    {
                        blue = true;
                    }
                }
                else
                {
                    if (bmp.GetPixel(a.X, a.Y).B - targetColor.B > (-20))
                    {
                        blue = true;
                    }
                }
                // RGB-Test End

                if (red == true && blue == true && green == true)
                { 
                    bmp.SetPixel(a.X, a.Y, replacementColor);
                    pixels.Push(new Point(a.X - 1, a.Y));
                    pixels.Push(new Point(a.X + 1, a.Y));
                    pixels.Push(new Point(a.X, a.Y - 1));
                    pixels.Push(new Point(a.X, a.Y + 1));
                }
            }
        }
        //refresh our main picture box
        pictureBox1.Image = bmp;
        pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
        return;
    }    

现在的问题是,如果图像中的渐变太强,它将停止,然后看起来像这样:https://i.stack.imgur.com/15jhd.png

作为解决方案,我考虑将 "targetColor" 更改为当前正在更改的像素的新颜色,以便它可以 "travel" 在渐变上,并且只有在突然出现时才停止颜色差异太大。

但是我对堆栈和 c# 的一般知识知之甚少的问题来了,因为第一次尝试像这样修改这部分代码

if (red == true && blue == true && green == true)
                { 
                    newColor = bmp.GetPixel(a.X, a.Y); // added this
                    bmp.SetPixel(a.X, a.Y, replacementColor);
                    pixels.Push(new Point(a.X - 1, a.Y));
                    pixels.Push(new Point(a.X + 1, a.Y));
                    pixels.Push(new Point(a.X, a.Y - 1));
                    pixels.Push(new Point(a.X, a.Y + 1));
                    targetColor = newColor; // and this
                }

我得到的结果看起来像这样:https://i.stack.imgur.com/U52mF.png

这很奇怪,因为它确实做了它应该做的事情,但不是在所有它应该做的地方,而且只是在图片上以一些条纹的形式出现。

感谢您提供的每一个解决方案和其他有关如何使其正常工作的想法。

我解决这个问题的方法是将点添加到堆栈时检查的颜色信息也存储在堆栈中 所以例如我们检查了颜色为 (255,10,1) 的像素 (10,15),在此期间我们将像素 (10,16) 与之前颜色 (10,15) 的信息相加,在像素检查期间我们比较了它颜色为上一个。 我所做的更改:

1) 包含有关堆栈中上一个像素颜色的信息: 为此,我使用了名为 touple:

的 c# 构造
Stack<(Point point, Color target)> pixels = new Stack<(Point, Color)>();
pixels.Push((pt, target));

2) 在使用堆栈时我们得到对 pixel/targetColour

    var curr = pixels.Pop();
    var a = curr.point;
    Color targetColor = curr.target;

3) 在向堆栈添加点时,我们还包括旧像素颜色:

var old = bmp.GetPixel(a.X, a.Y);
bmp.SetPixel(a.X, a.Y, replacementColor);
pixels.Push((new Point(a.X - 1, a.Y), old));
pixels.Push((new Point(a.X + 1, a.Y), old));
pixels.Push((new Point(a.X, a.Y - 1), old));
pixels.Push((new Point(a.X, a.Y + 1), old));

重构后的代码:

void FloodFill2(Bitmap bmp, Point pt, Color target, Color replacementColor)
{
    Stack<(Point point, Color target)> pixels = new Stack<(Point, Color)>();
    pixels.Push((pt, target));

    while (pixels.Count > 0)
    {
        var curr = pixels.Pop();
        var a = curr.point;
        Color targetColor = curr.target;

        if (a.X < bmp.Width && a.X > 0 &&
                a.Y < bmp.Height && a.Y > 0)//make sure we stay within bounds
        {
            var tolerance = 10;
            var green = Math.Abs(targetColor.G - bmp.GetPixel(a.X, a.Y).G) < tolerance;
            var red = Math.Abs(targetColor.R - bmp.GetPixel(a.X, a.Y).R) < tolerance;
            var blue = Math.Abs(targetColor.B - bmp.GetPixel(a.X, a.Y).B) < tolerance;

            if (red == true && blue == true && green == true)
            {
                var old = bmp.GetPixel(a.X, a.Y);
                bmp.SetPixel(a.X, a.Y, replacementColor);
                pixels.Push((new Point(a.X - 1, a.Y), old));
                pixels.Push((new Point(a.X + 1, a.Y), old));
                pixels.Push((new Point(a.X, a.Y - 1), old));
                pixels.Push((new Point(a.X, a.Y + 1), old));
            }
        }
    }
    //refresh our main picture box
    pictureBox1.Image = bmp;
    pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
    return;
}

最终效果:

如果像我一样,接受的答案不适合你:试试下面这个修改后的代码:它与@Adam maleks 的答案非常相似,除了公差被发送到方法(与硬编码相反)和 Stack 已拆分为两个对象以防止类型错误。

    private Bitmap floodfill(Bitmap input, Point pt, Color target, Color replacementColor, int r_tol, int g_tol, int b_tol)
    {
        Stack<Point> pixels = new Stack<Point>();
        Stack<Color> colour = new Stack<Color>();

        pixels.Push(pt);
        colour.Push(target);

        while (pixels.Count > 0)
        {

            var current_pixels = pixels.Pop();
            var current_colour = colour.Pop();
            var a = new Point(current_pixels.X, current_pixels.Y);
            Color targetColor = current_colour;

            if (a.X < input.Width && a.X > 0 && a.Y < input.Height && a.Y > 0)
            {
                var green = Math.Abs(targetColor.G - input.GetPixel(a.X, a.Y).G) < r_tol;
                var red = Math.Abs(targetColor.R - input.GetPixel(a.X, a.Y).R) < g_tol;
                var blue = Math.Abs(targetColor.B - input.GetPixel(a.X, a.Y).B) < b_tol;

                if (red == true && blue == true && green == true)
                {
                    var old_pixels = input.GetPixel(a.X, a.Y);
                    input.SetPixel(a.X, a.Y, replacementColor);
                    pixels.Push(new Point(a.X - 1, a.Y));
                    colour.Push(old_pixels);
                    pixels.Push(new Point(a.X + 1, a.Y));
                    colour.Push(old_pixels);
                    pixels.Push(new Point(a.X, a.Y - 1));
                    colour.Push(old_pixels);
                    pixels.Push(new Point(a.X, a.Y + 1));
                    colour.Push(old_pixels);
                }
            }
        }

        return input;
    }