15
\$\begingroup\$

I've created a program which recreates the Queen Ann's Lace fractal. However, when the amount of points that are being plotted increases, the points are plotted way slower. Is there any way to make it so that I only add a point, instead of adding a point and repainting all of the others? I've looked everywhere for an answer, and nothing has made sense to me. An explanation would be very helpful.

import javax.swing.*; //JFrame and JPanel
import java.awt.*; //Color and Container
import javax.swing.JOptionPane;
import java.awt.event.*;
import java.util.Scanner;
import java.io.*;
import java.util.Random;
import java.util.ArrayList;
import java.util.List;

/**
 * Write a description of class ChaosGame here.
 * 
 * @author (your name) 
 * @version (a version number or a date)
 */
public class QueensLaceFractal
{
    public static void main(String[] args) { //main method
        Dimension d = new Dimension(700, 700);
        JFrame theGUI = new JFrame("Queens Lace Fractal");
        ColorPanel panel = new ColorPanel(Color.white);
        theGUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        panel.setPreferredSize(d);
        Container pane = theGUI.getContentPane();
        pane.add(panel);
        theGUI.pack();
        theGUI.setVisible(true);
    }
}
class ColorPanel extends JPanel implements MouseMotionListener, MouseListener{ //colorpanel class
    private boolean followMouse = true;
    private int x, y;
    private int seedx, seedy;
    private int pointx, pointy;
    private int i;
    private int dotcounter;
    private int newx, newy;

    private Random random = new Random();
    private javax.swing.Timer timer;
    private javax.swing.Timer arraytimer;
    private EndPoint[] EP = new EndPoint[8];
    private Seed seed;
    private Point[] p = new Point[50000];
    private Color yellow = new Color(255, 255, 1);
    private Color yellowOrange = new Color(255, 195, 0);
    private Color orangeRed = new Color(255, 83, 0);
    private Color violetRed = new Color(215, 0, 46);
    private Color violet = new Color(129, 0, 127);
    private Color violetBlue = new Color(44, 48, 156);
    private Color blueGreen = new Color(0, 101, 87);
    private Color yellowGreen = new Color(85, 171, 0);

    public ColorPanel(Color backcolor){
        //constructor
        setBackground(backcolor);
        addMouseListener(this);
        addMouseMotionListener(this);
        this.setFocusable(true);
        seed = new Seed(350, 350, Color.BLACK);
        EP[0] = new EndPoint(350, 50, yellow);
        EP[1] = new EndPoint(562, 138, yellowOrange);
        EP[2] = new EndPoint(650, 350, orangeRed);
        EP[3] = new EndPoint(562, 562, violetRed);
        EP[4] = new EndPoint(350, 650, violet);
        EP[5] = new EndPoint(138, 562, violetBlue);
        EP[6] = new EndPoint(50, 350, blueGreen);
        EP[7] = new EndPoint(138, 138, yellowGreen);
        dotcounter = 0;
        i = 0;
        timer = new javax.swing.Timer(1, new MoveListener());
        arraytimer = new javax.swing.Timer(1, new ArrayTimer());
    }

    public void paintComponent(Graphics g){
        //paints everything on the panel
        super.paintComponent(g);
        seed.fill(g);
        for (int i = 0; i < 8; i++){
            EP[i].fill(g);
        }
        if (followMouse == true){
            //seed follows mouse
            timer.start();
            seed.moveto(x, y);
            g.drawString("Seed" ,x + 10, y - 10);
        }
        else if (followMouse == false){
            //begins drawing points
            timer.stop();
            arraytimer.start();
            int n = random.nextInt(9);
            newx = pointx;
            newy = pointy;
            //1 = yellow
            //2 = yellowOrange
            //3 = orangeRed
            //4 = violetRed
            //5 = violet
            //6 = violetBlue
            //7 = blueGreen
            //8 = yellowGreen
            //9 = black

            if(dotcounter <= 14){

                if (n == 8){
                    pointx = newx - 350;
                    double doublex = (double) pointx;
                    doublex = doublex * .5;
                    pointx = (int) Math.round(doublex);
                    pointx = newx - pointx;
                    pointy = newy - 350;
                    double doubley = (double) pointy;
                    doubley = doubley * .5;
                    pointy = (int) Math.round(doubley);
                    pointy = newy - pointy;
                    p[dotcounter] = new Point(pointx, pointy, seed.getColor());
                }
                else{
                    pointx = newx - EP[n].getX();
                    double doublex = (double) pointx;
                    doublex = doublex * .75;
                    pointx = (int) Math.round(doublex);
                    pointx = newx - pointx;
                    pointy = newy - EP[n].getY();
                    double doubley = (double) pointy;
                    doubley = doubley * .75;
                    pointy = (int) Math.round(doubley);
                    pointy = newy - pointy;
                    p[dotcounter] = new Point(pointx, pointy, EP[n].getColor());
                }

            }
            else if (dotcounter >= 15 && dotcounter < 50000){
                if (n == 8){
                    pointx = newx - 350;
                    double doublex = (double) pointx;
                    doublex = doublex * .5;
                    pointx = (int) Math.round(doublex);
                    pointx = newx - pointx;
                    pointy = newy - 350;
                    double doubley = (double) pointy;
                    doubley = doubley * .5;
                    pointy = (int) Math.round(doubley);
                    pointy = newy - pointy;
                    p[dotcounter] = new Point(pointx, pointy, seed.getColor());
                }
                else{
                    pointx = newx - EP[n].getX();
                    double doublex = (double) pointx;
                    doublex = doublex * .75;
                    pointx = (int) Math.round(doublex);
                    pointx = newx - pointx;
                    pointy = newy - EP[n].getY();
                    double doubley = (double) pointy;
                    doubley = doubley * .75;
                    pointy = (int) Math.round(doubley);
                    pointy = newy - pointy;
                    p[dotcounter] = new Point(pointx, pointy, EP[n].getColor());
                }

            }
            else{
                //stops timer
                arraytimer.stop();
            }
            for (i = 0; i < dotcounter; i++)
            { 
                //draws points 1 by 1
                p[i].fill(g);
            }
        }
        g.drawString(""+dotcounter, 600, 60);
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        // TODO Auto-generated method stub
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        //gets x and y of the mouse at all times
        x = e.getX();
        y = e.getY();
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        // TODO Auto-generated method stub
    }

    @Override
    public void mouseEntered(MouseEvent e) {
        // TODO Auto-generated method stub
    }

    @Override
    public void mouseExited(MouseEvent e) {
        // TODO Auto-generated method stub
    }

    @Override
    public void mousePressed(MouseEvent e){
        //
        if (followMouse == true){
            seedx = x;
            seedy = y;
            pointx = 350;
            pointy = 350;
            followMouse = false;
        }
        else{}
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        // TODO Auto-generated method stub
    }

    private class MoveListener implements ActionListener{
        public void actionPerformed(ActionEvent e){
            //repaints
            repaint();
        }
    }
    private class ArrayTimer implements ActionListener{
        public void actionPerformed(ActionEvent e){
            //calls paintcomponent method and adds 1 to dotcounter
            dotcounter++;
            repaint();
        }
    }
}
class EndPoint{
    // variables
    private int centerX, centerY, radius;
    private Color color;
    public EndPoint(int x, int y, Color thecolor){
        //constructor
        centerX = x;
        centerY = y;
        radius = 4;
        color = thecolor;
    }

    public void fill(Graphics g){
        //fills circle
        Color oldColor = g.getColor();
        g.setColor(color);
        g.fillOval(centerX-radius, centerY-radius, radius*2, radius*2);
        g.setColor(oldColor);
    }

    public void moveto(int xAmount, int yAmount){
        //Moves seed
        centerX = xAmount;
        centerY = yAmount;
    }

    public int getX(){
        return centerX;
    }

    public int getY(){
        return centerY;
    }

    public Color getColor(){
        return color;
    }
}
class Seed{
    // variables
    private int centerX, centerY, radius;
    private Color color;
    public Seed(int x, int y, Color thecolor){
        //constructor
        centerX = x;
        centerY = y;
        radius = 4;
        color = thecolor;
    }

    public void fill(Graphics g){
        //fills circle
        Color oldColor = g.getColor();
        g.setColor(color);
        g.fillOval(centerX-radius, centerY-radius, radius*2, radius*2);
        g.setColor(oldColor);
    }

    public void moveto(int xAmount, int yAmount){
        //Moves seed
        centerX = xAmount;
        centerY = yAmount;
    }

    public int getX(){
        return centerX;
    }

    public int getY(){
        return centerY;
    }

    public Color getColor(){
        return color;
    }
}
class Point{
    // variables
    private int centerX, centerY, radius;
    private Color color;
    public Point(int x, int y, Color thecolor){
        //constructor
        centerX = x;
        centerY = y;
        radius = 1;
        color = thecolor;
    }

    public void fill(Graphics g){
        //fills circle
        Color oldColor = g.getColor();
        g.setColor(color);
        g.fillRect(centerX, centerY, radius, radius);
        g.setColor(oldColor);
    }

    public void moveto(int xAmount, int yAmount){
        //Moves seed
        centerX = xAmount;
        centerY = yAmount;
    }
}

Here is an example of the output, after more than 21k iterations:

example output

\$\endgroup\$
  • 1
    \$\begingroup\$ I know that it's runnable, but is there any way to improve the rate that the points are added? It seems to slow down greatly as more and more points are added. \$\endgroup\$ – Trevor Mayo Feb 24 '15 at 21:35
  • \$\begingroup\$ It would be nice to include a picture/screenshot of the result, for reference. \$\endgroup\$ – Mathieu Guindon Mar 23 '15 at 16:06
6
\$\begingroup\$

The reason your current code will eventually slow down is because you are drawing every previously calculated pixel, every millisecond. So towards the end of your drawing, you're trying to plot 500,000 pixels in every paintComponent invocation.

To fix this, create yourself a transparent buffered image which you will paint your pixels onto. Then, in your repaint method, you can:

  • wipe the panel
  • paint your seed image
  • paint the coloured circles
  • add one more dot to your buffered image
  • paint the buffered image on top

Here's a modified version of your code that does exactly that. I also made a few edits along the way, some of which I'm afraid I've forgotten as I chased after the main prize. However, here's a few things I changed:

  • Used adaptor classes rather than implementing the mouse listeners in the panel itself. The adaptor classes (MouseAdapter and MouseMotionAdapter) conveniently have empty implementations for all methods in the equivalent listener interfaces.
  • No need to do if(foo == true). It should just be if (foo). There is also no need for empty else{} clauses; they can be removed.

Here's the final code:

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension; //Color and Container
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;
import java.util.Random;

import javax.swing.JFrame; //JFrame and JPanel
import javax.swing.JPanel;
import javax.swing.Timer;

/**
 * Write a description of class ChaosGame here.
 * 
 * @author (your name)
 * @version (a version number or a date)
 */
public class QueensLaceFractal {
  public static void main(String[] args) { // main method
    Dimension d = new Dimension(700, 700);
    JFrame theGUI = new JFrame("Queens Lace Fractal");

    ColorPanel panel = new ColorPanel(Color.white);
    theGUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    panel.setPreferredSize(d);
    Container pane = theGUI.getContentPane();
    pane.add(panel);
    theGUI.pack();

    // Must be called after pack() so that size of panel is known
    panel.startRendering();

    theGUI.setVisible(true);

  }
}

class ColorPanel extends JPanel {

  private static final long serialVersionUID = 1L;

  private boolean followMouse = true;
  private int x, y;
  private int seedx, seedy;
  private int pointx, pointy;
  private int dotcounter = 1;
  private int newx, newy;

  private Random random = new Random();
  private javax.swing.Timer timer;
  private EndPoint[] EP = new EndPoint[8];
  private Seed seed;
  private Color yellow = new Color(255, 255, 1);
  private Color yellowOrange = new Color(255, 195, 0);
  private Color orangeRed = new Color(255, 83, 0);
  private Color violetRed = new Color(215, 0, 46);
  private Color violet = new Color(129, 0, 127);
  private Color violetBlue = new Color(44, 48, 156);
  private Color blueGreen = new Color(0, 101, 87);
  private Color yellowGreen = new Color(85, 171, 0);

  private BufferedImage fractalImage;

  public ColorPanel(Color backcolor) {
    setBackground(backcolor);
    setDoubleBuffered(true);
    // constructor
    addMouseListener(new SeedMouseListener());
    addMouseMotionListener(new SeedMouseMotionListener());
    this.setFocusable(true);
    seed = new Seed(350, 350, Color.BLACK);
    EP[0] = new EndPoint(350, 50, yellow);
    EP[1] = new EndPoint(562, 138, yellowOrange);
    EP[2] = new EndPoint(650, 350, orangeRed);
    EP[3] = new EndPoint(562, 562, violetRed);
    EP[4] = new EndPoint(350, 650, violet);
    EP[5] = new EndPoint(138, 562, violetBlue);
    EP[6] = new EndPoint(50, 350, blueGreen);
    EP[7] = new EndPoint(138, 138, yellowGreen);
  }

  public void startRendering() {
    // Create a buffered image to hold the fractal. Make the background
    // transparent

    fractalImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2 = fractalImage.createGraphics();
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
    g2.fillRect(0, 0, getWidth(), getHeight());
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

    timer = new Timer(1, new ActionListener() {      
      @Override
      public void actionPerformed(ActionEvent e) {
        repaint();        
      }
    });

    timer.start();
  }

  private class SeedMouseMotionListener extends MouseMotionAdapter {
    @Override
    public void mouseMoved(MouseEvent e) {
      // gets x and y of the mouse at all times
      x = e.getX();
      y = e.getY();
    }
  }

  private class SeedMouseListener extends MouseAdapter {
    @Override
    public void mouseMoved(MouseEvent e) {
      // gets x and y of the mouse at all times
      x = e.getX();
      y = e.getY();
    }

    @Override
    public void mousePressed(MouseEvent e) {
      if (followMouse) {
        seedx = x;
        seedy = y;
        pointx = 350;
        pointy = 350;
        followMouse = false;
      }
    }
  }

  @Override
  protected void paintComponent(Graphics g) {

    // Wipe entire screen
    super.paintComponent(g);

    // Paint the eight circles
    for (int i = 0; i < 8; i++) {
      EP[i].fill(g);
    }

    // Paint the seed
    seed.fill(g);

    // If we are still following the mouse, just paint the "Seed" string and
    // finish
    if (followMouse) {
      seed.moveto(x, y);
      g.drawString("Seed", x + 10, y - 10);
      return;
    }

    // The counter is drawn onto the main graphics, otherwise we'd
    // have to overwrite the numbers on the buffered image
    g.drawString("" + dotcounter, 600, 60);


    // No need to keep an array of points anymore...
    Point p = null;
    int n = random.nextInt(9);
    newx = pointx;
    newy = pointy;
    // 1 = yellow
    // 2 = yellowOrange
    // 3 = orangeRed
    // 4 = violetRed
    // 5 = violet
    // 6 = violetBlue
    // 7 = blueGreen
    // 8 = yellowGreen
    // 9 = black

    if (dotcounter <= 14) {
      if (n == 8) {
        pointx = newx - 350;
        double doublex = (double) pointx;
        doublex = doublex * .5;
        pointx = (int) Math.round(doublex);
        pointx = newx - pointx;
        pointy = newy - 350;
        double doubley = (double) pointy;
        doubley = doubley * .5;
        pointy = (int) Math.round(doubley);
        pointy = newy - pointy;
        p = new Point(pointx, pointy, seed.getColor());
      } else {
        pointx = newx - EP[n].getX();
        double doublex = (double) pointx;
        doublex = doublex * .75;
        pointx = (int) Math.round(doublex);
        pointx = newx - pointx;
        pointy = newy - EP[n].getY();
        double doubley = (double) pointy;
        doubley = doubley * .75;
        pointy = (int) Math.round(doubley);
        pointy = newy - pointy;
        p = new Point(pointx, pointy, EP[n].getColor());
      }

    } else if (dotcounter >= 15 && dotcounter < 50000) {
      if (n == 8) {
        pointx = newx - 350;
        double doublex = (double) pointx;
        doublex = doublex * .5;
        pointx = (int) Math.round(doublex);
        pointx = newx - pointx;
        pointy = newy - 350;
        double doubley = (double) pointy;
        doubley = doubley * .5;
        pointy = (int) Math.round(doubley);
        pointy = newy - pointy;
        p = new Point(pointx, pointy, seed.getColor());
      } else {
        pointx = newx - EP[n].getX();
        double doublex = (double) pointx;
        doublex = doublex * .75;
        pointx = (int) Math.round(doublex);
        pointx = newx - pointx;
        pointy = newy - EP[n].getY();
        double doubley = (double) pointy;
        doubley = doubley * .75;
        pointy = (int) Math.round(doubley);
        pointy = newy - pointy;
        p = new Point(pointx, pointy, EP[n].getColor());
      }

    } else {
      // stops timer
      timer.stop();
    }

    // Draws point onto buffered image and draws image onto graphics
    Graphics fractalGraphic = fractalImage.getGraphics();
    if (p != null) {
      dotcounter++;
      p.fill(fractalGraphic);
    }
    g.drawImage(fractalImage, 0, 0, null);
  }
}

class EndPoint {
  // variables
  private int centerX, centerY, radius;
  private Color color;

  public EndPoint(int x, int y, Color thecolor) {
    // constructor
    centerX = x;
    centerY = y;
    radius = 4;
    color = thecolor;
  }

  public void fill(Graphics g) {
    // fills circle
    Color oldColor = g.getColor();
    g.setColor(color);
    g.fillOval(centerX - radius, centerY - radius, radius * 2, radius * 2);
    g.setColor(oldColor);
  }

  public void moveto(int xAmount, int yAmount) {
    // Moves seed
    centerX = xAmount;
    centerY = yAmount;
  }

  public int getX() {
    return centerX;
  }

  public int getY() {
    return centerY;
  }

  public Color getColor() {
    return color;
  }
}

class Seed {
  // variables
  private int centerX, centerY, radius;
  private Color color;

  public Seed(int x, int y, Color thecolor) {
    // constructor
    centerX = x;
    centerY = y;
    radius = 4;
    color = thecolor;
  }

  public void fill(Graphics g) {
    // fills circle
    Color oldColor = g.getColor();
    g.setColor(color);
    g.fillOval(centerX - radius, centerY - radius, radius * 2, radius * 2);
    g.setColor(oldColor);
  }

  public void moveto(int xAmount, int yAmount) {
    // Moves seed
    centerX = xAmount;
    centerY = yAmount;
  }

  public int getX() {
    return centerX;
  }

  public int getY() {
    return centerY;
  }

  public Color getColor() {
    return color;
  }
}

class Point {
  // variables
  private int centerX, centerY, radius;
  private Color color;

  public Point(int x, int y, Color thecolor) {
    // constructor
    centerX = x;
    centerY = y;
    radius = 1;
    color = thecolor;
  }

  public void fill(Graphics g) {
    // fills circle
    Color oldColor = g.getColor();
    g.setColor(color);
    g.fillRect(centerX, centerY, radius, radius);
    g.setColor(oldColor);
  }

  public void moveto(int xAmount, int yAmount) {
    // Moves seed
    centerX = xAmount;
    centerY = yAmount;
  }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.