2

i am trying to implement a simple application with the boundary fill algorithm in java and each time i am getting a stackoverflow error and i don't know why.

from the post i have seen, i believe it is because of the Robot.

here is the code

import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;

@SuppressWarnings("serial")
public class drawfill extends JPanel implements MouseListener,MouseMotionListener  {

    public static JFrame shell;
    public static Dimension shellSize = new Dimension(500, 500);
    public Graphics2D G;
    public Color boundaryColor = Color.black;
    public Color fillColor = Color.yellow;
    public int xInit;
    public int yInit;
    public int xFinal;
    public int yFinal;
    public boolean fill  = false;
    public Robot rb;
    BufferedImage img;
    Graphics2D gimg;

    public static void main(String[] args) throws AWTException {
        shell = new JFrame("Draw");
        shell.addWindowListener(new WindowAdapter(){
            public void windowClosing(WindowEvent we){
                System.exit(0);
            }
        });
        shell.setLayout(new BorderLayout());
        shell.setMinimumSize(shellSize);
        shell.setResizable(false);
        drawfill dpanel = new drawfill();
        RadioPanelClass radio = dpanel.new RadioPanelClass();

        shell.add(radio,BorderLayout.NORTH);
        shell.add(dpanel,BorderLayout.CENTER);
        shell.setVisible(true);
        shell.setLocationRelativeTo(null);
    }

    public drawfill() throws AWTException{
        rb = new Robot();
        super.setBackground(Color.white);
        changeCursor(true);
        super.addMouseMotionListener(this);
        super.addMouseListener(this);           
    }

    public void paint(Graphics g){
        G = (Graphics2D)g;
        super.paint(G);
        G.setColor(boundaryColor);
        G.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        G.drawRect(xInit, yInit, xFinal - xInit, yFinal - yInit);
    }

    public void changeCursor(boolean b){
        //true for draw 
        //flase for fill
        if (b)
            super.setCursor(Cursor.getPredefinedCursor (Cursor.CROSSHAIR_CURSOR));
        else
            super.setCursor(Cursor.getPredefinedCursor (Cursor.HAND_CURSOR));
    }

    public Color getPixel(int x,int y){
        return rb.getPixelColor(x,y);
    }

    public void setPixel(int x,int y,Color color){
        G.setColor(color);
        G.fillOval(x, y, 1, 1);

    }

    public void boundaryFill(int x, int y, Color fill,Color boundary) {
        Color interior = getPixel(x,y); 
        //System.out.println(interior.toString());
        if (interior != boundary && interior != fill){
            setPixel(x,y,fill);
            boundaryFill(x+1,y,fill,boundary);
            boundaryFill(x-1,y,fill,boundary);
            boundaryFill(x,y+1,fill,boundary);
            boundaryFill(x,y-1,fill,boundary);
        }
    }


    @Override
    public void mouseClicked(MouseEvent e) {
        if (fill){
            int x = e.getX();
            int y = e.getY();
            boundaryFill(x,y,fillColor,boundaryColor);
        }
    }

    @Override
    public void mouseEntered(MouseEvent e) {}

    @Override
    public void mouseExited(MouseEvent e) {}

    @Override
    public void mousePressed(MouseEvent e) {
        if (!fill){
            xInit = e.getX();
            yInit = e.getY();
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {}

    @Override
    public void mouseDragged(MouseEvent e) {
        if (!fill){
            xFinal = e.getX();
            yFinal = e.getY();
            repaint();
        }
    }

    @Override
    public void mouseMoved(MouseEvent e) {}

    class RadioPanelClass extends JPanel implements ActionListener {

        RadioPanelClass(){

            JRadioButton draw = new JRadioButton("draw");
                draw.setActionCommand("draw");
                draw.setSelected(true); 
            JRadioButton fill = new JRadioButton("fill");
                fill.setActionCommand("fill");
            super.add(draw);
            super.add(fill);

            ButtonGroup TypeRadio = new ButtonGroup();
            TypeRadio.add(draw);
            TypeRadio.add(fill);

            // Register a listener for the radio buttons.
            draw.addActionListener(this);
            fill.addActionListener(this);

        }

        @Override
        public void actionPerformed(ActionEvent e) {
            String actionCommand = e.getActionCommand();
            if (actionCommand == "draw") {
                changeCursor(true);
            }
            else if (actionCommand == "fill"){
                changeCursor(false);
                fill = true;
            }
        }
    }
}

the Error:

Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError
    at sun.nio.cs.SingleByte.withResult(Unknown Source)
    at sun.nio.cs.SingleByte.access$000(Unknown Source)
    at sun.nio.cs.SingleByte$Encoder.encodeArrayLoop(Unknown Source)
    at sun.nio.cs.SingleByte$Encoder.encodeLoop(Unknown Source)
    at java.nio.charset.CharsetEncoder.encode(Unknown Source)
    at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)
    at sun.nio.cs.StreamEncoder.write(Unknown Source)
    at java.io.OutputStreamWriter.write(Unknown Source)
    at java.io.BufferedWriter.flushBuffer(Unknown Source)
    at java.io.PrintStream.write(Unknown Source)
    at java.io.PrintStream.print(Unknown Source)
    at java.io.PrintStream.println(Unknown Source)
    at test.drawfill.boundaryFill(drawfill.java:99)
    at test.drawfill.boundaryFill(drawfill.java:102)
    at test.drawfill.boundaryFill(drawfill.java:102)

UPDATE:

i tried to change the code and use BufferedImage instead but i and still getting the same stackOverFlow error here is the updated code:

public void paintComponent(Graphics g){
        G = (Graphics2D)g;
        super.paintComponent(G);
        super.setBackground(Color.white);
        bi = new BufferedImage(super.getWidth(),super.getHeight(),BufferedImage.TYPE_INT_RGB);
        gbi = bi.createGraphics();

        gbi.setBackground(Color.WHITE);
        gbi.clearRect(0,0,super.getWidth(),super.getHeight());
        gbi.setColor(boundaryColor);
        gbi.drawRect(xInit, yInit, xFinal - xInit, yFinal - yInit);
        G.drawImage(bi, 0,0,null);
        gbi.dispose();
    }

public Color getPixel(int x,int y){
        return new Color(bi.getRGB(x, y));
    }

    public void setPixel(int x,int y,Color color){
        bi.setRGB(x, y, color.getRGB());
        repaint();
    }

    public void boundaryFill(int x, int y, Color fill,Color boundary) {
        if ( (x>= xInit && x<= xFinal) && (y>= yInit && y<=yFinal) ){
            Color interior = getPixel(x,y); 
            //System.out.println(interior.toString());
            if (interior != boundary && interior != fill){
                setPixel(x,y,fill);
                boundaryFill(x+1,y,fill,boundary);
                boundaryFill(x-1,y,fill,boundary);
                boundaryFill(x,y+1,fill,boundary);
                boundaryFill(x,y-1,fill,boundary);
            }
            else
                return;
        }
        else
            return;
    }
Tarounen
  • 1,119
  • 3
  • 14
  • 25
  • I don't have time to dig deeper, but my guess is that this is caused by an infinite recursive loop. I suspect that `boundaryFill` is calling itself endlessly without ever reaching a terminating condition that allows it to exit. Eventually that will cause the stack to run out of space, and you'll get the StackOverflowError you're seeing. – Bobulous Feb 04 '15 at 20:28
  • Why does `getPixel` access `rb`, but `setPixel` manipulates `G`? – Scott Hunter Feb 04 '15 at 20:31
  • @ScottHunter is there any other way to perfom this operation? – Tarounen Feb 04 '15 at 20:35
  • `boundaryFill` seems to be doing redundant work (and recursion). For example, the call to manipulate pixel (25, 44) will attempt to process (26, 44) and (24, 44), and then when (26, 44) is processed, it'll attempt (27, 44) and (25, 44) and we're back to the earlier pixel. Also, as @ScottHunter pointed out, if you manipulate different objects, then the break condition will never kick in, causing it to repeat the work in a recursive manner endlessly, eventually failing with SO error. – srkavin Feb 04 '15 at 20:44

3 Answers3

0

You are using rb to determine the color of a given pixel, which is supposed to be controlling the recursion of boundaryFill. But when you set a pixel, you manipulate G, and it is not clear if (much less how) rb is being informed of these pixel changes; if rb never changes, then there is nothing to stop the recursion of boundaryFill.

Scott Hunter
  • 48,888
  • 12
  • 60
  • 101
0

There are several problems here that are combining:

As Scott Hunter's answer suggests, a java.awt.Robot operates on the actual color of pixels on the screen, not on the colors in the Graphics2D. This means that the color returned by Robot.getPixelColor(screenX, screenY) won't be updated until the Graphics2D is actually painted to the screen - which can't happen in the middle of your boundaryFill() call.

Additionally, a Robot operates in screen coordinates, while the Graphics2D is operating in the coordinate space of (in this case) your JPanel - meaning that even if you did repaint, the arguments to Robot.getPixelColor would need to be different than the arguments to G.fillOval.

Next, you're not doing bounds checking on the coordinates passed to boundaryFill() - meaning that if your recursion ever hits the edge of the area, you'll keep recursing until x is Integer.MAX_VALUE or your stack overflows.

As an additional bonus, I'd have to double check, but I'm pretty sure holding on to the Graphics2D that's passed in to Component.paint() is not likely to be well-behaved. The traditional way to do what you're trying would be to create a BufferedImage offscreen, render into it, and then call Graphics2D.drawImage() in your paintComponent() override. (Since you're extending JPanel and therefore using Swing, you should be overriding paintComponent instead of paint anyway. Doing this would also let you avoid using the Robot, since you can use BufferedImage.getRGB to determine pixel colors.

Sbodd
  • 11,279
  • 6
  • 41
  • 42
  • I believe the intent is to recognize the boundary by checking the color of the pixel: if it is the color used by the boundary, it is on the boundary, and that pixel is neither colored in nor recursed upon(?). So the code is *trying* to do bounds checking, but even if it is working, the unbounded recursion on non-boundary pixels would still occur. – Scott Hunter Feb 05 '15 at 14:14
  • i tried to updated the code and use `BufferedImage` but got the same error! – Tarounen Feb 05 '15 at 20:24
  • You're using != to compare two Colors that you new'd up differently. Replace your interior != boundary with (!interior.equals(boundary)), do the same for your fill test, and try again. And if you haven't yet - try putting your code into a debugger and stepping through it to figure out the problems. – Sbodd Feb 05 '15 at 20:39
  • Also, you're still creating a new BufferedImage in every call to paintComponent. That's going to result in your boundaryFill()'s work being overwritten. You'll want to create the BufferedImage outside of paintComponent, draw your initial boundary there, and the paintComponent method will probably just be a single call to drawImage. – Sbodd Feb 05 '15 at 20:43
  • i have re updated the codes and place `BufferedImage` outside the `PaintComponent` method. and now the algorithm works partially. i have a block a the diagram painted and get `stackoverflow` error again! also if i continue to click area which haven't been filled, again part of the diagram is filled and `stackoverflow` occurs. Screenshot of output: [screenshot](http://i.imgur.com/oiwsmjC.png) – Tarounen Feb 06 '15 at 06:43
0

I have already answered this: https://stackoverflow.com/a/67253198/8196026

You are giving larger dimensions of your figure than what it could handle that's why recurring calls and recursion stack gets out of memory, like here its 500 in your case. I suggest try reducing this dimension and it should work fine.

mukuldiwan
  • 11
  • 1