I am trying to make a Mandelbrot Set renderer with a GUI where you can click and drag to zoom into a specific area. When run, it will do the initial calculations and rendering fine, but when you try to click and drag to zoom in, the console says it is doing the calculations, but the content of the JFrame is not updated.
However, I'm not even positive that it is recalculating, because the initial calculation takes about 8 seconds but when you click/drag to zoom it takes about 6 ms.
I have posted my code below.
Complex Numbers Class
public class Complex {
private double real, imag;
// Constructors
public Complex(){
real=0.0;
imag=0.0;
}
public Complex(double real, double imag) {
this.real=real;
this.imag=imag;
}
// add given complex number to this one, returning the Complex result
public Complex add(Complex other) {
return new Complex(this.real+other.real, this.imag+other.imag);
}
// multiply given complex number by this one, returning the Complex result
public Complex multiply(Complex other) {
return new Complex((this.real*other.real)-(this.imag*other.imag), (this.imag*other.real)+(this.real*other.imag));
}
// get the magnitude of this complex number
public double getMagnitude() {
return Math.sqrt((real*real)+(imag*imag));
}
}
Runnable MandelbrotTask Class
public class MandelbrotTask implements Runnable {
private double x1, y1, x2, y2;
private int startCol, endCol, startRow, endRow, maxIters;
private int[][] iterCounts;
public MandelbrotTask(int maxIters, double x1, double y1, double x2, double y2, int startCol, int endCol, int startRow, int endRow, int[][] iterCounts) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.startCol = startCol;
this.endCol = endCol;
this.startRow = startRow;
this.endRow = endRow;
this.iterCounts = iterCounts;
this.maxIters=maxIters;
}
@Override
public void run() {
for (int i = startRow; i < endRow; i++) {
for (int j = startCol; j < endCol; j++) {
Complex c = getComplex(i, j);
int iterCount = countIters(c);
iterCounts[i][j] = iterCount;
}
}
}
public Complex getComplex(int i, int j){
//output image is 600 X 600 pixels
double incrementX;
double incrementY;
if(x2!=x1){
incrementX=(Math.abs(x2-x1)/600);
}
else{
throw new ArithmeticException("Error: area=0");
}
if(y2!=y1){
incrementY=(Math.abs(y2-y1)/600);
}
else{
throw new ArithmeticException("Error: area=0");
}
return new Complex(x1+((double)i*incrementX), y1+((double)j*incrementY));
}
public int countIters(Complex c){
Complex z=new Complex(0, 0);
int iters=0;
while(z.getMagnitude()<2 && iters<=maxIters){
z=z.multiply(z).add(c);
iters++;
}
return iters;
}
}
Main Mandelbrot Class
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Scanner;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class Mandelbrot {
private static final int HEIGHT = 600;
private static final int WIDTH = 600;
private static final int maxIters=50000;
private static Rectangle zoomBox;
private static Point initialClick;
private static JLabel content; //bufferedImage will be put into this JLabel
private static int[][] iterCounts;
private static BufferedImage bufferedImage; //rendering will be written to this bufferedImage
private static JFrame frame;
public static void main(String[] args) throws IOException {
zoomBox=null;
Scanner keyboard = new Scanner(System.in);
double x1 = -2;
double y1 = -2;
double x2 = 2;
double y2 = 2;
/*System.out.print("Max iterations (16,581,375 supported): ");
int maxIters=50000;
if(maxIters>16581375){
throw new UnsupportedOperationException("Error: Max Iterations: Overflow.");
}
System.out.print("Output filename: ");
String fileName = keyboard.next();
if(!fileName.endsWith(".png") && !fileName.endsWith(".PNG")){
fileName=fileName + ".png";
}*/
// TODO: create the rendering, save it to a file
iterCounts=new int[WIDTH][HEIGHT];
recalculate(x1, y1, x2, y2, iterCounts);
bufferedImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
MouseAdapter listener = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
handleMousePressed(e);
}
@Override
public void mouseDragged(MouseEvent e) {
handleMouseDragged(e);
}
@Override
public void mouseReleased(MouseEvent e) {
handleMouseReleased(e);
}
};
content=new JLabel(new ImageIcon(render(iterCounts, bufferedImage, zoomBox, true)));
content.addMouseListener(listener);
content.addMouseMotionListener(listener);
/*OutputStream os = new BufferedOutputStream(new FileOutputStream(fileName));
try {
ImageIO.write(bufferedImage, "PNG", os);
} finally {
os.close();
}*/
frame = new JFrame("Mandelbrot Viewer");
frame.getContentPane().add(content);
frame.pack();
frame.setSize(new Dimension(WIDTH, HEIGHT));
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static BufferedImage render(int[][] iterCounts, BufferedImage bufferedImage, Rectangle zoomBox, boolean updated){
bufferedImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
Graphics g = bufferedImage.getGraphics();
Graphics2D g2=(Graphics2D) g;
if(updated){
for(int i=0; i<WIDTH; i++){
for(int j=0; j<HEIGHT; j++){
if(iterCounts[i][j]<maxIters){
String hexCode= String.format("#%06x", (0xFFFFFF & (32*iterCounts[i][j])));
g.setColor(Color.decode(hexCode));
}
else{
g.setColor(Color.CYAN);
}
g.drawLine(i, j, i, j);
}
}
}
else{
if(zoomBox!=null){
g2.setStroke(new BasicStroke(7));
g2.draw(zoomBox);
}
}
return bufferedImage;
}
public static int[][] recalculate(double x1, double y1, double x2, double y2, int[][] iterCounts){
MandelbrotTask[] tasks=new MandelbrotTask[4];
tasks[0]=new MandelbrotTask(maxIters, x1, y1, x2, y2, 0, WIDTH, 0, HEIGHT/4, iterCounts);
tasks[1]=new MandelbrotTask(maxIters, x1, y1, x2, y2, 0, WIDTH, HEIGHT/4, 2*(HEIGHT/4), iterCounts);
tasks[2]=new MandelbrotTask(maxIters, x1, y1, x2, y2, 0, WIDTH, 2*(HEIGHT/4), 3*(HEIGHT/4), iterCounts);
tasks[3]=new MandelbrotTask(maxIters, x1, y1, x2, y2, 0, WIDTH, 3*(HEIGHT/4), 4*(HEIGHT/4), iterCounts);
//parallelize computation
Thread[] threads=new Thread[4];
for(int i=0; i<4; i++){
threads[i]=new Thread(tasks[i]);
}
System.out.println("Working...");
//start timer, start computation
long start=System.currentTimeMillis();
for(int i=0; i<4; i++){
threads[i].start();
}
for(int i=0; i<4; i++){
try {
threads[i].join();
} catch (InterruptedException e) {
System.err.println("A thread was interrupted.");
}
}
//end timer
long end=System.currentTimeMillis();
long elapsed=end-start;
System.out.println("Done.");
System.out.println("Took " + elapsed + " ms.");
return iterCounts;
}
protected static void handleMousePressed(MouseEvent e) {
initialClick=e.getPoint();
}
protected static void handleMouseDragged(MouseEvent e) {
if(e.getX()>e.getY()){
zoomBox=new Rectangle((int)initialClick.getX(), (int)initialClick.getY(), (int)(e.getX()-initialClick.getX()), (int)(e.getY()-initialClick.getX()));
}
else if(e.getY()>e.getX()){
zoomBox=new Rectangle((int)initialClick.getX(), (int)initialClick.getY(), (int)(e.getX()-initialClick.getY()), (int)(e.getY()-initialClick.getY()));
}
else{
zoomBox=new Rectangle((int)initialClick.getX(), (int)initialClick.getY(), (int)(e.getX()-initialClick.getX()), (int)(e.getY()-initialClick.getY()));
}
content=new JLabel(new ImageIcon(render(iterCounts, bufferedImage, zoomBox, false)));
content.repaint();
}
protected static void handleMouseReleased(MouseEvent e) {
recalculate(initialClick.getX(), initialClick.getY(), e.getX(), e.getY(), iterCounts);
SwingUtilities.invokeLater(new Runnable(){
@Override
public void run() {
zoomBox=null;
content=new JLabel(new ImageIcon(render(iterCounts, bufferedImage, zoomBox, false)));
content.repaint();
}
});
}
}
See Question&Answers more detail:
os