Stefan Cuevas Stefan Cuevas - 4 months ago 21
Java Question

Sierpinski Gasket implementation in Java Swing only shows up sometimes

So I have to create an implementation of a Sierpinski Gasket with Swing.
I can't use recursion or triangles. I have to use the following
algorithm:


Pick 3 points to define a triangle.

Select one of the vertices as current Loop 50,000 times:
Randomly choose a vertex as the target.
Draw a pixel at the mid-point between the target and current.
Make current the mid-point.


In the image below is what I sometimes get upon compilation, but other times it will pop up and disappear or it will not show up at all. If it does show up, and then I resize the window it disappears (I don't care about this, but if it helps.) I can only produce the below image sometimes when I compile (about 1/3rd of the time.) Below the image is my code, separated in two classes.

Image of when it works

import java.awt.*;
import javax.swing.JFrame;


public class SierpinskiGasket {

public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setTitle("SierpinskiGasket");
frame.setSize(630,580);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


drawSierpinski Sierpinski = new drawSierpinski();

frame.add(Sierpinski);

frame.setVisible(true);

}
}




import javax.swing.*;
import java.awt.*;


public class drawSierpinski extends JPanel{

Point point1 = new Point(10,550),
point2 = new Point(300,30),
point3 = new Point(600,555),
current = point1, target;
private int count = 0;


public void paintComponent(Graphics g){
super.paintComponent(g);

while(count<= 50000){
int choice = (int)(Math.random()*3);
switch(choice){
case 0: target = point1; break;
case 1: target = point2; break;
case 2: target = point3; break;
default: System.exit(0);

}
current = midpoint(current,target);
g.drawLine(current.x,current.y,current.x,current.y);

count++;
}
}

public Point midpoint(Point a, Point b){
return new Point((Math.round(a.x+b.x)/2),
(Math.round(a.y+b.y)/2));
}


}


I am assuming that it has something to do with how Swing does multithreading, but unfortunately I don't have too much knowledge of how to fix it. Thank you very much for any help!

Answer

This loop:

while(count<= 50000) { 

   // ....

}

may take a while to complete, and meanwhile it will be completely blocking the Swing event thread at its most key point -- while drawing. What's more, any trivial re-draw will trigger the loop to re-run, again freezing your GUI completely.

The solution: do your drawing outside of paintComponent. Instead create a BufferedImage the size of your JPanel, get the image's Graphics object, draw your random dots for your triangle in the BufferedImage, and then display that image within your JPanel's paintComponent method. You could draw the image at program start up, and then start up the GUI after its complete, or you can start the GUI and draw to the BufferedImage in a background thread, and display it when done, either would be fine (if this is the only thing your GUI should be doing).

For example:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;

import javax.swing.*;

public class SierpTest {
    public static final int BI_WIDTH = 630;
    public static final int BI_HEIGHT = 580;

    public static void main(String[] args) {

        // do this stuff off the swing event thread
        final BufferedImage sierpImg = new BufferedImage(BI_WIDTH, BI_HEIGHT, BufferedImage.TYPE_INT_ARGB);
        Graphics g = sierpImg.getGraphics();

        // draw triangle with g here

        g.dispose(); // always dispose of any Graphics you create yourself

        // do this on the Swing event thread
        SwingUtilities.invokeLater(() -> {
            SierpPanel sierpPanel = new SierpPanel(sierpImg); // pass in image
            JFrame frame = new JFrame("Siep Frame");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(sierpPanel);
            frame.pack(); // size it to the size of the JPanel
            frame.setLocationRelativeTo(null); // center it
            frame.setVisible(true);
        });
    }
}

class SierpPanel extends JPanel {
    private BufferedImage img = null;

    public SierpPanel(BufferedImage img) {
        this.img = img;
    }

    // so that JPanel sizes itself with the image
    @Override
    public Dimension getPreferredSize() {
        if (isPreferredSizeSet() || img == null) {
            return super.getPreferredSize();
        }
        return new Dimension(img.getWidth(), img.getHeight());
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (img != null) {
            g.drawImage(img, 0, 0, this);
        }
    }
}

For example:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.image.BufferedImage;

import javax.swing.*;

public class SierpTest {
    public static final int BI_WIDTH = 630;
    public static final int BI_HEIGHT = 580;
    private static final int MAX_COUNT = 100000;

    public static void main(String[] args) {

        // do this stuff off the swing event thread
        Point point1 = new Point(10, 550);
        Point point2 = new Point(300, 30);
        Point point3 = new Point(600, 555);
        Point current = point1;
        Point target = current; 
        int count = 0;

        final BufferedImage sierpImg = new BufferedImage(BI_WIDTH, BI_HEIGHT, BufferedImage.TYPE_INT_ARGB);
        Graphics g = sierpImg.getGraphics();
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, BI_WIDTH, BI_HEIGHT);
        g.setColor(Color.BLACK);

        while (count <= MAX_COUNT) {
            int choice = (int) (Math.random() * 3);
            switch (choice) {
            case 0:
                target = point1;
                break;
            case 1:
                target = point2;
                break;
            case 2:
                target = point3;
                break;
            default:
                System.exit(0);

            }
            current = midpoint(current, target);
            g.drawLine(current.x, current.y, current.x, current.y);

            count++;
        }

        // draw triangle with g here

        g.dispose(); // always dispose of any Graphics you create yourself

        // do this on the Swing event thread
        SwingUtilities.invokeLater(() -> {
            SierpPanel sierpPanel = new SierpPanel(sierpImg); // pass in image
            JFrame frame = new JFrame("Siep Frame");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(sierpPanel);
            frame.pack(); // size it to the size of the JPanel
            frame.setLocationRelativeTo(null); // center it
            frame.setVisible(true);
        });
    }

    public static Point midpoint(Point a, Point b) {
        return new Point((Math.round(a.x + b.x) / 2), (Math.round(a.y + b.y) / 2));
    }
}

class SierpPanel extends JPanel {
    private BufferedImage img = null;

    public SierpPanel(BufferedImage img) {
        this.img = img;
    }

    // so that JPanel sizes itself with the image
    @Override
    public Dimension getPreferredSize() {
        if (isPreferredSizeSet() || img == null) {
            return super.getPreferredSize();
        }
        return new Dimension(img.getWidth(), img.getHeight());
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (img != null) {
            g.drawImage(img, 0, 0, this);
        }
    }
}

Note that if you want to get fancy and draw the triangle as it's being created, and with a delay, then consider using either a Swing Timer or a SwingWorker.