C# 洪水填充算法
又到周末了,这意味着我可以和朋友们一起玩了 我已经厌倦了手工创建测试级别,所以我想从引擎开发中休息一下,使用级别编辑器: 我想在编辑器中实现一个泛洪填充算法,它的工作原理与绘制程序类似。有没有人对什么技术对我有用有什么建议 该级别只是一个2d数组,因此可以认为它实际上与位图相同 谢谢C# 洪水填充算法,c#,algorithm,flood-fill,C#,Algorithm,Flood Fill,又到周末了,这意味着我可以和朋友们一起玩了 我已经厌倦了手工创建测试级别,所以我想从引擎开发中休息一下,使用级别编辑器: 我想在编辑器中实现一个泛洪填充算法,它的工作原理与绘制程序类似。有没有人对什么技术对我有用有什么建议 该级别只是一个2d数组,因此可以认为它实际上与位图相同 谢谢 WikMedia在其文章中提供了一些不同洪水填充技术的伪代码示例。选择哪种技术取决于应用程序 记住,洪水填充可以很容易地进行线程化(类似于快速排序的方式)。公平地说,它应该非常简单。由于您具有基本的平铺结构,因
WikMedia在其文章中提供了一些不同洪水填充技术的伪代码示例。选择哪种技术取决于应用程序
记住,洪水填充可以很容易地进行线程化(类似于快速排序的方式)。公平地说,它应该非常简单。由于您具有基本的平铺结构,因此算法相当简单:
Select Tile To Fill:
Fill Till
Check neighbouring Tiles - If Empty Then Fill
Repeat, for all filled tiles.
免责声明:以上是一个非常基本的描述。网络上有很多参考资料,比我能解释得更好。我们必须为学校编制:
1: stuff the start pixel into a queue, note its color. note it as added.
2: begin picking a pixel off the queue. If it's similar to the start pixel:
2: put all its neighbours into the queue
for each added pixel, note it's added. if already noted for a pixel, don't
add it anymore.
3: color it with the destination color.
3: nonempty => jump back to 2
4: empty => we are finished
根据我们是做8邻还是4邻,我们检查所有8邻像素,或者只检查某个像素的左/右或上/下像素。下面是代码(使用ImageJ。我删除了一些不相关的代码)。我希望它有意义,它是Java。只需提问:
public class Uebung1_2 implements PlugInFilter, MouseListener {
private ImageProcessor ip;
boolean[] state;
int[] pixels;
Queue<Integer> nextPixels;
int threshould;
/**
* adds one pixel to the next-pixel queue only if it's not
* already added.
*/
void addNextPixel(int p) {
if(!state[p]) {
nextPixels.add(p);
state[p] = true;
}
}
boolean pixelsSimilar(int color1, int color2) {
int dr = Math.abs(((color1 >> 16) & 0xff) -
((color2 >> 16) & 0xff));
int dg = Math.abs(((color1 >> 8) & 0xff) -
((color2 >> 8) & 0xff));
int db = Math.abs(((color1 >> 0) & 0xff) -
((color2 >> 0) & 0xff));
return ((double)(dr + dg + db) / 3.0) <= threshould;
}
/**
* actually does the hard work :)
* @param x the x position from which to start filling
* @param y the y position from which to start filling
*/
private void doFill(int x, int y, boolean connect8) {
// first, add the start pixel
int width = ip.getWidth(),
height = ip.getHeight();
/* for 8bit, we just gonna take the median of rgb */
Color colorC = ij.gui.Toolbar.getForegroundColor();
int color = colorC.getRGB();
int firstPixel = ip.get(x, y);
// go on with the mainloop
addNextPixel(y * width + x);
while(!nextPixels.isEmpty()) {
int nextPixel = nextPixels.remove();
int pixel = pixels[nextPixel];
if(pixelsSimilar(pixel, firstPixel)) {
// yay it matches. put the neighbours.
int xN = nextPixel % width,
yN = nextPixel / width;
/* the three pixels above */
if(yN - 1 >= 0) {
if(connect8) {
if(xN + 1 < width) {
addNextPixel(nextPixel - width + 1);
}
if(xN - 1 >= 0) {
addNextPixel(nextPixel - width - 1);
}
}
addNextPixel(nextPixel - width);
}
/* pixels left and right from the current one */
if(xN > 0) {
addNextPixel(nextPixel - 1);
}
if(xN + 1 < width) {
addNextPixel(nextPixel + 1);
}
/* three pixels below */
if(yN + 1 < height) {
if(connect8) {
if(xN + 1 < width) {
addNextPixel(nextPixel + width + 1);
}
if(xN - 1 >= 0) {
addNextPixel(nextPixel + width - 1);
}
}
addNextPixel(nextPixel + width);
}
/* color it finally */
pixels[nextPixel] = color;
}
}
}
@Override
public void run(ImageProcessor ip) {
ij.WindowManager.getCurrentImage().getCanvas().addMouseListener(this);
this.ip = ip;
this.pixels = (int[])ip.getPixels();
this.state = new boolean[ip.getPixelCount()];
this.nextPixels = new LinkedList<Integer>();
}
@Override
public int setup(String arg0, ImagePlus arg1) {
return DOES_RGB;
}
@Override
public void mouseClicked(MouseEvent e) {
ij.WindowManager.getCurrentWindow().getCanvas().removeMouseListener(this);
ij.gui.GenericDialog g = new GenericDialog("Please enter parameters");
g.addChoice("connection", new String[]{"4-connect", "8-connect"}, "8-connect");
g.addNumericField("Threshould (0..255)", 0.0, 3);
g.showDialog();
boolean connect8 = g.getNextChoice().equals("8-connect");
threshould = (int) g.getNextNumber();
doFill(e.getX(), e.getY(), connect8);
ij.WindowManager.getCurrentImage().draw();
}
}
public类Uebung1_2实现PlugInFilter、MouseListener{
专用图像处理器ip;
布尔[]状态;
int[]像素;
队列下一个像素;
内阈值;
/**
*将一个像素添加到下一个像素队列(如果不是)
*已经添加了。
*/
void addNextPixel(int p){
如果(!状态[p]){
添加(p);
状态[p]=真;
}
}
布尔像素相似(int-color1,int-color2){
int dr=Math.abs((color1>>16)和0xff)-
((color2>>16)和0xff));
int dg=Math.abs((color1>>8)和0xff)-
((color2>>8)和0xff));
int db=Math.abs((color1>>0)和0xff)-
((color2>>0)和0xff));
返回((双)(dr+dg+db)/3.0)=0){
如果(连接8){
如果(xN+1<宽度){
添加nextPixel(nextPixel-宽度+1);
}
如果(xN-1>=0){
addNextPixel(nextPixel-width-1);
}
}
addNextPixel(nextPixel-宽度);
}
/*从当前像素向左和向右的像素*/
如果(xN>0){
添加nextPixel(nextPixel-1);
}
如果(xN+1<宽度){
添加nextPixel(nextPixel+1);
}
/*下面三个像素*/
如果(yN+1<高度){
如果(连接8){
如果(xN+1<宽度){
添加下一个像素(下一个像素+宽度+1);
}
如果(xN-1>=0){
添加下一个像素(下一个像素+宽度-1);
}
}
添加下一个像素(下一个像素+宽度);
}
/*最后把它涂上颜色*/
像素[nextPixel]=颜色;
}
}
}
@凌驾
公共无效运行(图像处理器ip){
ij.WindowManager.getCurrentImage().getCanvas().addMouseListener(此);
this.ip=ip;
this.pixels=(int[])ip.getPixels();
this.state=new boolean[ip.getPixelCount()];
this.nextPixels=newlinkedlist();
}
@凌驾
公共整数设置(字符串arg0,ImagePlus arg1){
不返回RGB;
}
@凌驾
公共无效mouseClicked(MouseEvent e){
ij.WindowManager.getCurrentWindow().getCanvas().removeMouseListener(此);
ij.gui.GenericDialog g=新建GenericDialog(“请输入参数”);
g、 addChoice(“connection”,新字符串[]{“4-connect”,“8-connect”},“8-connect”);
g、 addNumericField(“阈值(0..255)”,0.0,3);
g、 showDialog();
布尔连接8=g.getNextChoice().equals(“8-connect”);
threshold=(int)g.getNextNumber();
doFill(e.getX(),e.getY(),connect8);
ij.WindowManager.getCurrentImage().draw();
}
}
维基百科的文章非常好。只要你的网格很小,任何东西都可以工作
今年秋天早些时候,我对1000万像素的扫描图像进行了洪水填充。(问题是从复印机扫描过的书页上去除黑色边缘。)在这种情况下,只有两种颜色,所以我基本上把这个问题当作在无向图中搜索,每个像素沿着四个指南针方向与其相邻像素相连。我维护了一个单独的位图来跟踪访问过的像素
主要调查结果如下:
- 不要尝试递归深度优先搜索。您确实需要一个显式的数据结构
- 辅助队列使用的空间比堆栈少得多。大约减少40倍的空间。换句话说,与深度优先搜索相比,更喜欢广度优先搜索
同样,这些发现仅适用于具有数百万像素的网格。在一个很好的小网格上,如您的问题中所示,任何简单的算法都应该有效。简单的函数,不检查颜色容差 使用:
var img = Image.FromFile("test.png");
img = img.FloodFill(new Point(0, 0), Color.Red);
img.Save("testcomplete.png", ImageFormat.Png);
主要功能:
public static Image FloodFill(this Image img, Point pt, Color color)
{
Stack<Point> pixels = new Stack<Point>();
var targetColor = ((Bitmap)img).GetPixel(pt.X, pt.Y);
pixels.Push(pt);
while (pixels.Count > 0)
{
Point a = pixels.Pop();
if (a.X < img.Width && a.X > -1 && a.Y < img.Height && a.Y > -1)
{
if (((Bitmap)img).GetPixel(a.X, a.Y) == targetColor)
{
((Bitmap)img).SetPixel(a.X, a.Y, color);
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));
}
}
}
return img;
}
公共静态图像泛光填充(此图像img、点pt、颜色)
{
堆栈像素=新堆栈();
var targetColor=((位图)img).GetPixel(pt.X,pt.Y);
像素推送(pt);
而(像素数>0)
{
点a=像素。弹出
using System.Runtime.InteropServices;
//insert by Zswang(wjhu111#21cn.com) at 2007-05-22
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateSolidBrush(int crColor);
[DllImport("gdi32.dll")]
public static extern bool ExtFloodFill(IntPtr hdc, int nXStart, int nYStart,
int crColor, uint fuFillType);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern int GetPixel(IntPtr hdc, int x, int y);
public static uint FLOODFILLBORDER = 0;
public static uint FLOODFILLSURFACE = 1;
private void button1_Click(object sender, EventArgs e)
{
Graphics vGraphics = Graphics.FromHwnd(Handle);
vGraphics.DrawRectangle(Pens.Blue, new Rectangle(0, 0, 300, 300));
vGraphics.DrawRectangle(Pens.Blue, new Rectangle(50, 70, 300, 300));
IntPtr vDC = vGraphics.GetHdc();
IntPtr vBrush = CreateSolidBrush(ColorTranslator.ToWin32(Color.Red));
IntPtr vPreviouseBrush = SelectObject(vDC, vBrush);
ExtFloodFill(vDC, 10, 10, GetPixel(vDC, 10, 10), FLOODFILLSURFACE);
SelectObject(vDC, vPreviouseBrush);
DeleteObject(vBrush);
vGraphics.ReleaseHdc(vDC);
}