我正在开发一个可运行的java applet,它具有填充功能,就像绘制程序(如Microsoft Paint)中的fill方法一样。
这就是我的填充方法的工作原理:
小程序使用.getRGB
applet为窗口中的所有像素创建一个2D布尔数组,如果该像素的颜色与单击的颜色相同,则值为“true”,否则为“false”。这一步的重点是保持.getRGB
方法不受递归方法的影响,以防止出现此错误。
applet以递归方式搜索用户点击的两个布尔数组,记录ArrayList中“true”的每个相邻点。然后,该方法将其记录的每个点更改为false并继续。
小程序将ArrayList
中存储的每个点绘制为用户选择的颜色。
如果用户在一个只有几千个像素左右的小区域内点击,所有上述步骤都可以完美地工作。如果用户选择了一个大区域(例如大约360,000 / applet窗口的大小),applet将进入递归阶段,然后输出此错误:
Exception in thread "AWT-EventQueue-1" java.lang.StackOverflowError
at java.util.ArrayList.add(ArrayList.java:351)
at paint.recursiveSearch(paint.java:185)
at paint.recursiveSearch(paint.java:190)
at paint.recursiveSearch(paint.java:190)
at paint.recursiveSearch(paint.java:190)
at paint.recursiveSearch(paint.java:190)
at paint.recursiveSearch(paint.java:190)
at paint.recursiveSearch(paint.java:190)
(continues for a few pages)
这是我的递归代码:
public void recursiveSearch(boolean [][] list, Point p){
if(isValid(p)){
if(list[(int)p.y][(int)p.x]){
fillPoints.add(p);
list[(int)p.y][(int)p.x] = false;
recursiveSearch(list, new Point(p.x-1,p.y));//Checks to the left
recursiveSearch(list, new Point(p.x,p.y-1));//Checks above
recursiveSearch(list, new Point(p.x+1,p.y));//Checks to the right
recursiveSearch(list, new Point(p.x,p.y+1));//Checks below
}
}
}
有什么方法可以解决这样的错误吗?我知道循环永远不会永远存在,它可能需要很长时间。
答案 0 :(得分:1)
您需要的是广度优先搜索。您将拥有一个“未处理”像素的队列。首先,队列由用户单击的一个像素组成。现在,当队列不为空时,重复以下步骤:从队列中取出下一个像素,处理它(绘制到您需要的颜色或其他任何颜色),并为每个相邻的未访问像素标记相同颜色标记它添加到队列中。如果该区域包含360,000像素,则应该在非时间内运行。
答案 1 :(得分:0)
您可以增加Java进程的堆栈空间,例如:
java -Xss10m MyProgram
会给线程堆栈10兆。但是,我建议不要这样做,并考虑编写算法的迭代版本,特别是如果递归调用的数量取决于某种用户驱动的行为。
编辑:对于applet,我认为你不能指定-X
标志值。
答案 2 :(得分:0)
我不明白为什么你要复说。如果您将list
中的任何积分更改回true
,那将是一回事,但显然您没有。那么为什么不迭代?
实际上,你可能会多次检查每一个点,当只检查一个点时就足够了。
另一个想法:如果我没弄错,除非fillPoints
,否则您的算法不会将任何点添加到list[0, 0] == true
向量。这是你想要的行为吗?
编辑:好的,现在我对你要做的事情有了更好的了解。我建议您查看this part算法上维基百科页面的Flood Fill。
答案 3 :(得分:0)
基本问题是你对每个像素检查过多。我建议您在方法中添加一个“level”参数,该参数最初为0,但在递归调用时递增。然后添加一个初始print语句,显示当前递归调用的级别。
我想你会对代码的深度感到惊讶!
答案 4 :(得分:0)
如果要处理任意图像大小,则必须以非递归方式重写它。没有其他方法可以保证您的堆栈足够大。
答案 5 :(得分:0)
你的算法在纸上是有意义的,但是你发现它不能很好地扩展。
我怀疑任何图形程序都会使用这种方法。
答案 6 :(得分:0)
如果你想避免检查窗口上的所有像素,那么递归会是一个好主意。我建议改变所选像素的颜色并递归检查是否有任何需要改变的相邻像素,如果没有,则返回,等等。这样就避免了必须检查窗口中的每个像素。 (想象一下800 x 800px窗口的情况,你必须填充一个4x4px的区域。检查每一个像素都会过度杀伤。)
答案 7 :(得分:0)
从我的快速看。我想你经常去很多次。您应该更新方法以包括呼叫不应该检查的方向。像这样:
public void recursiveSearch(boolean [][] list, Point p, String directionFrom){
if(isValid(p)){
if(list[(int)p.y][(int)p.x]){
fillPoints.add(p);
list[(int)p.y][(int)p.x] = false;
//Add a check for which direction it came from and dont go that way.
if (string.equals(right)){
recursiveSearch(list, new Point(p.x,p.y-1),down);//Checks above
recursiveSearch(list, new Point(p.x+1,p.y),left);//Checks to the right
recursiveSearch(list, new Point(p.x,p.y+1),right);//Checks below
}else if(string.equals(left){
//... and so on
}
}
}
}
答案 8 :(得分:0)
应该发生大图像。你不应该使用递归。也许是这样的:
LinkedList<Point> frontier = new ...
frontier.add(starting_point);
while(frontier is not empty)
point = frontier.removeLast();
point.state = (point.color == the_color)
if(point.state==true)
// expand frontier
for(neighbor : neighbor_points)
if(neighbor.visited==false)
frontier.add(neighbor)
neighbor.visited=true;