1

My assignment is to implement an algorithm to color a closed shape starting from a given (x,y) coordinate and "spread" via recursive calls untill it reaches the borders of the shape. So far this is what I've come up with:

private void color(int x, int y) {
    g2d.draw(new Line2D.Double(x, y, x, y));
    if (!robot.getPixelColor(x - 1, y).equals(Color.BLACK) &&
            !robot.getPixelColor(x - 1, y).equals(Color.RED)) {
        color(x - 1, y);
    } else if (!robot.getPixelColor(x + 1, y).equals(Color.BLACK) &&
            !robot.getPixelColor(x - 1, y).equals(Color.RED)) {
        color(x + 1, y);
    } else if (!robot.getPixelColor(x, y - 1).equals(Color.BLACK) &&
            !robot.getPixelColor(x - 1, y).equals(Color.RED)) {
        color(x, y - 1);
    } else if (!robot.getPixelColor(x, y + 1).equals(Color.BLACK) &&
            !robot.getPixelColor(x - 1, y).equals(Color.RED)) {
        color(x, y + 1);
    }
}

The Robot class' getPixelColor is the only way I found to get the color of a given pixel (as far as I know another would be getRGB, but that only works on Image objects). To my understanding this should work, as the outer lines of the shape are definitely black, and the initial x and y values come from a MouseListener, so they are inside the shape, however I get the following error:

Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError
    at sun.java2d.pipe.BufferedContext.validateContext(BufferedContext.java:110)
    at sun.java2d.d3d.D3DRenderer.validateContextAA(D3DRenderer.java:42)
    at sun.java2d.pipe.BufferedRenderPipe$AAParallelogramPipe.fillParallelogram(BufferedRenderPipe.java:445)
    at sun.java2d.pipe.PixelToParallelogramConverter.drawGeneralLine(PixelToParallelogramConverter.java:264)
    at sun.java2d.pipe.PixelToParallelogramConverter.draw(PixelToParallelogramConverter.java:121)
    at sun.java2d.SunGraphics2D.draw(SunGraphics2D.java:2336)
    at dline.DrawingSpace.color(DrawingSpace.java:87)
    at dline.DrawingSpace.color(DrawingSpace.java:93)
    at dline.DrawingSpace.color(DrawingSpace.java:90)
    at dline.DrawingSpace.color(DrawingSpace.java:93)
    at dline.DrawingSpace.color(DrawingSpace.java:90)

(drawingSpace is a sub-class of JPanel)

The teacher did tell us that this is memory consuming, however it's supposed to be a working algorithm, so I'm doing something wrong, obviously. Any help would be much appriciated, thank you.

Jason Orendorff
  • 42,793
  • 6
  • 62
  • 96
István Kohn
  • 135
  • 8

3 Answers3

0

I'm guessing that you're backtracking onto previously visited pixels. The pixel you just drew probably won't be visible to robot until after you return from color, so it will not appear red from the previous painting.

Do you have a reference to the java.awt.Shape? A much simpler way than using the robot would be to use Shape.contains(Point) to see whether it's in the shape you're supposed to draw.

The basic algorithm either way is depth-first traveral. To do a DFS when there are possible cycles, you can record the pixels you've already drawn.

//java.awt.Point
Set<Point> paintedPixels = new HashSet<Point>();
private void color(int x, int y) {
    if ( paintedPixels.contains(new Point(x, y)) ) {
       //already painted
       return;
    }
    paintedPixels.add(new Point(x, y));
    //...
}

Now, this could still result in a very deep search. You might consider instead using a non-recursive breadth-first traveral. See the Wikipedia article on Flood Fill.

Mark Peters
  • 80,126
  • 17
  • 159
  • 190
  • 1
    This is a good point. Additionally, the robot works in screen coordinates, while drawLine works in component coordinates (if you are not using an additional AffineTransform). – Paŭlo Ebermann Mar 01 '11 at 19:50
0

You can try to increase the Stack size: How to increase the Java stack size?

Probably you have a bug in your algorithm, or the shape is too big. What helps if you 'draw' your algorithm on a piece of graph paper. That way you can check your algorithm.

Community
  • 1
  • 1
Daan
  • 9,984
  • 2
  • 30
  • 36
0

The problem with implementing this as a recursive algorithm is that it has (for bigger images) a very high recursion depth.

In Java (and most other imperative programming languages, too) the maximal recursion depth is limited by the amount of stack space for each thread, since it must keep a stack frame for each method invocation there.

You may try smaller images first, and try to increase the stack size with the -xss parameter.


Edit: As pointed out by Mark, the Robot will not get any pixels until your drawing is complete, since often your drawing is double-buffered (i.e. the Swing engine lets you paint first on an image, and draws then the complete image to the screen).

Also, you are not converting between device (screen) and user (component) coordinates for the lookup.

You wrote:

The Robot class' getPixelColor is the only way I found to get the color of a given pixel (as far as I know another would be getRGB, but that only works on Image objects).

So, why don't you use an Image object? Fill your shape while drawing on the Image, and then draw the whole image at once to the screen.


And your method can be made much more readable if you transfer the "is already painted" test inside the recursive call:

private void color(int x, int y) {
     // getPixel invokes something in the image - or replace it here.
    Color org = getPixel(x,y);
    if (org.equals(Color.BLACK)) {
        // reached the border
        return;
    }
    if (org.equals(Color.RED)) {
        // already painted before
        return;
    }
    g2d.draw(new Line2D.Double(x, y, x, y));
    color(x-1, y);
    color(x+1, y);
    color(x, y-1);
    color(x, y-1);
}
Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210