如何用SkiaSharp实现指定颜色区域的泛洪填充(Flood Fill)?
SkiaSharp实现指定颜色区域的颜色修改方案
一、全图替换指定标记色(高效版)
你的现有代码会修改全图颜色,是因为CreateTable滤镜会替换所有像素的RGB通道。要只修改标记色区域,推荐用SkiaSharp的颜色滤镜组合实现,利用GPU加速,性能远高于逐像素遍历。
代码实现
SKBitmap sourceBitmap = Image; // 你的原始图片 SKColor targetMarkerColor = SKColors.Magenta; // 预设的可修改区域标记色 SKColor newColor = color; // 用户选择的目标颜色 var paint = new SKPaint(); // 1. 创建蒙版滤镜:仅保留标记色区域,其余转为透明 var maskFilter = SKColorFilter.CreateColorThreshold( SKColorSpace.CreateSrgb(), targetMarkerColor.ToVector4(), SKColorSpace.CreateSrgb(), new SKVector4(20, 20, 20, 0), // RGB容差,根据需求调整 SKColorFilterColorThresholdStyle.Replace, SKColors.Transparent.ToVector4(), SKColors.White.ToVector4() ); // 2. 创建颜色替换滤镜:将所有像素转为目标颜色 var colorReplaceFilter = SKColorFilter.CreateTable( null, Enumerable.Repeat((byte)newColor.Red, 256).ToArray(), Enumerable.Repeat((byte)newColor.Green, 256).ToArray(), Enumerable.Repeat((byte)newColor.Blue, 256).ToArray() ); // 3. 组合滤镜:先提取标记色蒙版,再替换颜色 paint.ColorFilter = SKColorFilter.CreateCompose(colorReplaceFilter, maskFilter); // 先绘制原图,再叠加滤镜后的图层,仅标记色区域会被替换 canvas.DrawBitmap(sourceBitmap, new SKPoint(drawX, drawY)); canvas.DrawBitmap(sourceBitmap, new SKPoint(drawX, drawY), paint);
二、逐像素遍历替换标记色(简单直接版)
如果不需要高性能,或者需要更灵活的颜色判断逻辑,逐像素遍历是最直观的方案。
代码实现
SKBitmap sourceBitmap = Image; SKColor targetMarkerColor = SKColors.Magenta; SKColor newColor = color; sourceBitmap.LockPixels(); // 锁定像素缓冲区,提升读写性能 try { for (int y = 0; y < sourceBitmap.Height; y++) { for (int x = 0; x < sourceBitmap.Width; x++) { SKColor currentColor = sourceBitmap.GetPixel(x, y); // 精准匹配标记色,若需要容差可计算颜色差异 if (currentColor == targetMarkerColor) { sourceBitmap.SetPixel(x, y, newColor); } } } } finally { sourceBitmap.UnlockPixels(); // 必须解锁,否则位图可能损坏 } canvas.DrawBitmap(sourceBitmap, new SKPoint(drawX, drawY), paint);
带容差的颜色判断
如果标记色存在轻微色差,可通过计算颜色距离调整判断逻辑:
// 计算RGB通道差异的平方和(避免开根号,提升性能) int colorDiff = (currentColor.Red - targetMarkerColor.Red) * (currentColor.Red - targetMarkerColor.Red) + (currentColor.Green - targetMarkerColor.Green) * (currentColor.Green - targetMarkerColor.Green) + (currentColor.Blue - targetMarkerColor.Blue) * (currentColor.Blue - targetMarkerColor.Blue); // 设定容差阈值(示例为2500,对应RGB差值各50左右) if (colorDiff < 2500) { sourceBitmap.SetPixel(x, y, newColor); }
三、泛洪填充(仅替换连通的标记色区域)
如果需要实现点击某点后,填充连通的标记色区域(类似画图工具的油漆桶),可以用队列式泛洪算法:
代码实现
void FloodFill(SKBitmap bitmap, int startX, int startY, SKColor targetColor, SKColor newColor) { // 边界判断 if (startX < 0 || startX >= bitmap.Width || startY < 0 || startY >= bitmap.Height) return; SKColor originalColor = bitmap.GetPixel(startX, startY); // 若当前颜色不是目标标记色,或已替换为新颜色,直接返回 if (originalColor != targetColor || originalColor == newColor) return; Queue<SKPoint> pixelQueue = new Queue<SKPoint>(); pixelQueue.Enqueue(new SKPoint(startX, startY)); bitmap.SetPixel(startX, startY, newColor); while (pixelQueue.Count > 0) { SKPoint point = pixelQueue.Dequeue(); int x = (int)point.X; int y = (int)point.Y; // 检查并填充上方像素 if (y > 0 && bitmap.GetPixel(x, y - 1) == targetColor) { bitmap.SetPixel(x, y - 1, newColor); pixelQueue.Enqueue(new SKPoint(x, y - 1)); } // 下方 if (y < bitmap.Height - 1 && bitmap.GetPixel(x, y + 1) == targetColor) { bitmap.SetPixel(x, y + 1, newColor); pixelQueue.Enqueue(new SKPoint(x, y + 1)); } // 左方 if (x > 0 && bitmap.GetPixel(x - 1, y) == targetColor) { bitmap.SetPixel(x - 1, y, newColor); pixelQueue.Enqueue(new SKPoint(x - 1, y)); } // 右方 if (x < bitmap.Width - 1 && bitmap.GetPixel(x + 1, y) == targetColor) { bitmap.SetPixel(x + 1, y, newColor); pixelQueue.Enqueue(new SKPoint(x + 1, y)); } } } // 使用方式 SKBitmap sourceBitmap = Image; int clickX = 100; // 用户点击的X坐标 int clickY = 100; // 用户点击的Y坐标 sourceBitmap.LockPixels(); try { FloodFill(sourceBitmap, clickX, clickY, targetMarkerColor, newColor); } finally { sourceBitmap.UnlockPixels(); } canvas.DrawBitmap(sourceBitmap, new SKPoint(drawX, drawY), paint);
方案选择建议
- 全图替换标记色:优先用颜色滤镜组合方案,GPU加速性能优异,适合大图片;
- 简单场景或需要自定义颜色判断:用逐像素遍历方案;
- 连通区域填充:用泛洪填充算法,若追求更高性能可优化为扫描线泛洪算法。
内容的提问来源于stack exchange,提问作者Justin Greywolf
相关产品推荐
相关产品推荐

