如何在WPF窗口中实现泛洪填充(Flood Fill)填充图片局部区域?
在WPF中实现泛洪填充(Flood Fill)功能
先看需求对应的图:
待填充的原图:
目标填充效果:
以下是具体实现方案:
核心逻辑
泛洪填充的本质是从点击的起始像素点出发,将相邻的、颜色匹配的像素批量替换为目标颜色。在WPF中,需要将只读的BitmapSource转换为可修改的像素数组,执行填充算法后再转回可显示的位图对象。
分步实现
1. 处理像素数据的工具方法
WPF的BitmapSource默认只读,我们需要先提取像素数组,修改后再更新回位图:
// 从BitmapSource提取像素数组 private byte[] ExtractPixelArray(BitmapSource bitmap) { int stride = bitmap.PixelWidth * (bitmap.Format.BitsPerPixel / 8); byte[] pixels = new byte[bitmap.PixelHeight * stride]; bitmap.CopyPixels(pixels, stride, 0); return pixels; } // 将修改后的像素数组转换为WriteableBitmap用于显示 private WriteableBitmap RefreshBitmap(BitmapSource original, byte[] modifiedPixels) { WriteableBitmap wb = new WriteableBitmap(original); int stride = wb.PixelWidth * (wb.Format.BitsPerPixel / 8); wb.WritePixels(new Int32Rect(0, 0, wb.PixelWidth, wb.PixelHeight), modifiedPixels, stride, 0); return wb; }
2. 非递归版泛洪填充算法(避免栈溢出)
递归版在处理大填充区域时容易触发栈溢出,推荐使用队列实现的广度优先搜索(BFS):
private void FloodFill(byte[] pixels, int width, int height, int startX, int startY, Color targetColor, Color replaceColor, int bitsPerPixel) { int bytesPerPixel = bitsPerPixel / 8; int stride = width * bytesPerPixel; // 获取起始点的原始颜色 int startIndex = startY * stride + startX * bytesPerPixel; Color originalColor = Color.FromArgb( pixels[startIndex + 3], pixels[startIndex + 2], pixels[startIndex + 1], pixels[startIndex] ); // 目标颜色与原始颜色一致时直接返回,避免死循环 if (originalColor == replaceColor) return; Queue<Point> pixelQueue = new Queue<Point>(); pixelQueue.Enqueue(new Point(startX, startY)); // 标记已处理的像素,防止重复入队 bool[,] processed = new bool[height, width]; processed[startY, startX] = true; while (pixelQueue.Count > 0) { Point current = pixelQueue.Dequeue(); int x = (int)current.X; int y = (int)current.Y; // 替换当前像素颜色 int pixelIndex = y * stride + x * bytesPerPixel; pixels[pixelIndex] = replaceColor.B; pixels[pixelIndex + 1] = replaceColor.G; pixels[pixelIndex + 2] = replaceColor.R; pixels[pixelIndex + 3] = replaceColor.A; // 检查并处理上下左右四个方向的相邻像素 CheckNeighbor(x, y - 1, width, height, originalColor, pixels, bytesPerPixel, stride, processed, pixelQueue); CheckNeighbor(x, y + 1, width, height, originalColor, pixels, bytesPerPixel, stride, processed, pixelQueue); CheckNeighbor(x - 1, y, width, height, originalColor, pixels, bytesPerPixel, stride, processed, pixelQueue); CheckNeighbor(x + 1, y, width, height, originalColor, pixels, bytesPerPixel, stride, processed, pixelQueue); } } // 检查相邻像素是否符合填充条件,符合则入队 private void CheckNeighbor(int x, int y, int width, int height, Color originalColor, byte[] pixels, int bytesPerPixel, int stride, bool[,] processed, Queue<Point> queue) { if (x >= 0 && x < width && y >= 0 && y < height && !processed[y, x]) { int index = y * stride + x * bytesPerPixel; Color currentColor = Color.FromArgb( pixels[index + 3], pixels[index + 2], pixels[index + 1], pixels[index] ); if (currentColor == originalColor) { processed[y, x] = true; queue.Enqueue(new Point(x, y)); } } }
3. 在WPF控件中触发填充
在Image控件的鼠标点击事件中调用填充逻辑,将控件坐标转换为图片像素坐标:
private void Image_MouseDown(object sender, MouseButtonEventArgs e) { Image targetImage = sender as Image; if (targetImage.Source == null) return; Point clickPoint = e.GetPosition(targetImage); BitmapSource originalBitmap = (BitmapSource)targetImage.Source; // 将控件点击坐标转换为图片像素坐标 int pixelX = (int)(clickPoint.X * originalBitmap.PixelWidth / targetImage.ActualWidth); int pixelY = (int)(clickPoint.Y * originalBitmap.PixelHeight / targetImage.ActualHeight); byte[] pixelArray = ExtractPixelArray(originalBitmap); // 示例:将点击区域填充为红色,需根据原图实际像素格式调整原始颜色判断 FloodFill(pixelArray, originalBitmap.PixelWidth, originalBitmap.PixelHeight, pixelX, pixelY, Color.FromRgb(0, 0, 0), // 原图中需要填充的目标颜色(黑色线条内的白色区域需对应调整) Color.FromRgb(255, 0, 0), // 填充的目标颜色 originalBitmap.Format.BitsPerPixel); targetImage.Source = RefreshBitmap(originalBitmap, pixelArray); }
注意事项
- 确保图片像素格式为
Bgra32或Rgb24,其他格式需要调整颜色提取和替换的逻辑。 - 大尺寸图片建议在后台线程执行填充,避免UI卡顿。
- 如果需要支持模糊边缘的填充,可以添加颜色相似度判断(允许一定RGB差值),而非严格匹配颜色。
内容的提问来源于stack exchange,提问作者ramy_hackintosh
相关产品推荐
相关产品推荐

