边界填充java导致***

Posted

技术标签:

【中文标题】边界填充java导致***【英文标题】:Boundary fill java causing *** 【发布时间】:2015-04-04 12:40:12 【问题描述】:

我正在尝试使用 java 中的边界填充算法实现一个简单的应用程序,每次我遇到 *** 错误,我不知道为什么。

从我看到的帖子来看,我相信这是因为机器人。

这里是代码

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;
            
        
    

错误:

Exception in thread "AWT-EventQueue-0" java.lang.***Error
    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)

更新:

我尝试更改代码并改用BufferedImage,但我仍然收到相同的*** 错误,这是更新后的代码:

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;
    

【问题讨论】:

我没有时间深入挖掘,但我的猜测是这是由无限递归循环引起的。我怀疑boundaryFill 一直在无休止地调用自己,而没有达到允许它退出的终止条件。最终这将导致堆栈空间不足,您将看到您看到的 ***Error。 为什么getPixel访问rb,而setPixel操纵G @ScottHunter 还有其他方法可以执行此操作吗? boundaryFill 似乎在做多余的工作(和递归)。例如,操作像素 (25, 44) 的调用将尝试处理 (26, 44) 和 (24, 44),然后在处理 (26, 44) 时,它会尝试处理 (27, 44) 和(25, 44) 我们回到之前的像素。此外,正如@ScottHunter 指出的那样,如果您操作不同的对象,那么中断条件将永远不会启动,导致它以递归方式无休止地重复工作,最终因 SO 错误而失败。 【参考方案1】:

我已经回答了这个问题: https://***.com/a/67253198/8196026

您提供的图形尺寸比它可以处理的尺寸更大,这就是为什么重复调用和递归堆栈内存不足的原因,就像这里的 500 一样。我建议尝试减少这个维度,它应该可以正常工作。

【讨论】:

【参考方案2】:

这里有几个问题正在结合:

正如 Scott Hunter 的回答所暗示的,java.awt.Robot 作用于屏幕上像素的实际颜色,而不是Graphics2D 中的颜色。这意味着 Robot.getPixelColor(screenX, screenY) 返回的颜色在 Graphics2D 实际绘制到屏幕上之前不会更新 - 这不会在您的 boundaryFill() 调用中间发生。

此外,Robot 在屏幕坐标中运行,而Graphics2D 在(在这种情况下)您的JPanel 的坐标空间中运行 - 这意味着即使您重新绘制,Robot.getPixelColor 的参数需要不同于G.fillOval 的参数。

接下来,您不会对传递给 boundaryFill() 的坐标进行边界检查 - 这意味着如果您的递归到达该区域的边缘,您将继续递归直到 x 为 Integer.MAX_VALUE 或您的堆栈溢出。

作为额外的奖励,我必须仔细检查,但我很确定坚持传递给 Component.paint()Graphics2D 不太可能表现得很好。做你想做的事情的传统方法是在屏幕外创建一个BufferedImage,渲染到它,然后在你的paintComponent()覆盖中调用Graphics2D.drawImage()。 (因为您正在扩展 JPanel 并因此使用 Swing,所以无论如何您都应该覆盖 paintComponent 而不是 paint。这样做也可以让您避免使用 Robot,因为您可以使用 BufferedImage.getRGB 来确定像素颜色。

【讨论】:

我相信意图是通过检查像素的颜色来识别边界:如果是边界使用的颜色,它就在边界上,并且该像素既没有着色也没有递归之上(?)。所以代码尝试做边界检查,但即使它工作正常,非边界像素上的无限递归仍然会发生。 我尝试更新代码并使用BufferedImage,但得到了同样的错误! 您正在使用 != 来比较两种新的不同颜色。用 (!interior.equals(boundary)) 替换您的内部 != 边界,对填充测试执行相同操作,然后重试。如果您还没有 - 尝试将您的代码放入调试器并逐步解决问题。 另外,您仍然在每次调用paintComponent 时创建一个新的BufferedImage。这将导致您的边界填充()的工作被覆盖。您需要在paintComponent 之外创建BufferedImage,在那里绘制您的初始边界,而paintComponent 方法可能只是对drawImage 的一次调用。 我已经重新更新了代码并将BufferedImage 放在PaintComponent 方法之外。现在该算法部分工作。我有一个块绘制的图表并再次得到*** 错误!另外,如果我继续单击尚未填充的区域,则图表的一部分再次被填充并出现***。输出截图:screenshot【参考方案3】:

您正在使用rb 来确定给定像素的颜色,这应该控制boundaryFill 的递归。但是,当您设置一个像素时,您会操纵G,并且不清楚(更不用说如何)rb 是否被告知这些像素更改;如果rb 永远不会改变,那么没有什么可以阻止boundaryFill 的递归。

【讨论】:

以上是关于边界填充java导致***的主要内容,如果未能解决你的问题,请参考以下文章

Pytorch 四种边界填充方式(Padding)

达到边界颜色时,填充无法填充整个形状?

为啥在某些 Chrome 条件下 SVG 路径元素的填充属性会填充整个边界框?

从外部表格边界线填充

OpenCV高手勿入! 半小时学会基本操作 边界填充

为啥我们选择“边界框”的方式来填充三角形呢?