I have written the mandelbrotset in java,but if i want to zoom into it it gets blurry after around 14 clicks, no matter the Maxiterration number, if its 100 it gets blurry and if its 100000 it gets blurry after 14 zoom ins.Something i noticed is that after i zoom in twice, all of the next zoom ins are instant in contrast to the first two which usually take a few seconds, this may help finding the solution. The code:
import java.util.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import java.math.BigDecimal;
public class test extends JFrame {
static final int WIDTH = 400;
static final int HEIGHT = WIDTH;
Canvas canvas;
BufferedImage fractalImage;
static final int MAX_ITER = 10000;
static final BigDecimal DEFAULT_TOP_LEFT_X = new BigDecimal(-2.0);
static final BigDecimal DEFAULT_TOP_LEFT_Y = new BigDecimal(1.4);
static final double DEFAULT_ZOOM = Math.round((double) (WIDTH/3));
final int numThreads = 10;
double zoomFactor = DEFAULT_ZOOM;
BigDecimal topLeftX = DEFAULT_TOP_LEFT_X;
BigDecimal topLeftY = DEFAULT_TOP_LEFT_Y;
BigDecimal z_r = new BigDecimal(0.0);
BigDecimal z_i = new BigDecimal(0.0);
// -------------------------------------------------------------------
public test() {
setInitialGUIProperties();
addCanvas();
canvas.addKeyStrokeEvents();
updateFractal();
this.setVisible(true);
}
// -------------------------------------------------------------------
public static void main(String[] args) {
new test();
}
// -------------------------------------------------------------------
private void addCanvas() {
canvas = new Canvas();
fractalImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
canvas.setVisible(true);
this.add(canvas, BorderLayout.CENTER);
} // addCanvas
// -------------------------------------------------------------------
private void setInitialGUIProperties() {
this.setTitle("Fractal Explorer");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(WIDTH, HEIGHT);
this.setResizable(false);
this.setLocationRelativeTo(null);
} // setInitialGUIProperties
// -------------------------------------------------------------------
private BigDecimal getXPos(double x) {
return topLeftX.add(new BigDecimal(x/zoomFactor));
} // getXPos
// -------------------------------------------------------------------
private BigDecimal getYPos(double y) {
return topLeftY.subtract(new BigDecimal(y/zoomFactor));
} // getYPos
// -------------------------------------------------------------------
/**
* Aktualisiert das Fraktal, indem die Anzahl der Iterationen für jeden Punkt im Fraktal berechnet wird und die Farbe basierend darauf geändert wird.
**/
public void updateFractal() {
Thread[] threads = new Thread[numThreads];
int rowsPerThread = HEIGHT / numThreads;
// Construct each thread
for (int i=0; i<numThreads; i++) {
threads[i] = new Thread(new FractalThread(i * rowsPerThread, (i+1) * rowsPerThread));
}
// Starte jeden thread
for (int i=0; i<numThreads; i++) {
threads[i].start();
}
// Warten bis alle threads fertig sind
for (int i=0; i<numThreads; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
canvas.repaint();
} // updateFractal
// -------------------------------------------------------------------
//Gibt basierend auf der Iterationsanzahl eine trennungsfarbe zurück eines gegebenen Punktes im Fraktal
private class FractalThread implements Runnable {
int startY;
int endY;
public FractalThread(int startY, int endY) {
this.startY = startY;
this.endY = endY;
}
public void run() {
BigDecimal c_r;
BigDecimal c_i;
for (int x = 0; x < WIDTH; x++ ) {
for (int y = startY; y < endY; y++ ) {
c_r = getXPos(x);
c_i = getYPos(y);
int iterCount = computeIterations(c_r, c_i);
int pixelColor = makeColor(iterCount);
fractalImage.setRGB(x, y, pixelColor);
}
System.out.println(x);
}
} // run
} // FractalThread
private int makeColor( int iterCount ) {
int color = 0b011011100001100101101000;
int mask = 0b000000000000010101110111;
int shiftMag = iterCount / 13;
if (iterCount == MAX_ITER)
return Color.BLACK.getRGB();
return color | (mask << shiftMag);
} // makeColor
// -------------------------------------------------------------------
private int computeIterations(BigDecimal c_r, BigDecimal c_i) {
BigDecimal z_r = new BigDecimal(0.0);
BigDecimal z_i = new BigDecimal(0.0);
BigDecimal z_r_tmp = z_r;
BigDecimal dummy2 = new BigDecimal(2.0);
int iterCount = 0;
while ( z_r.doubleValue()*z_r.doubleValue() + z_i.doubleValue()*z_i.doubleValue() <= 4.0 ) {
z_r_tmp = z_r;
z_r = z_r.multiply(z_r).subtract(z_i.multiply(z_r)).add(c_r);
z_i = z_i.multiply(dummy2).multiply(z_i).multiply(z_r_tmp).add(c_i);
if (iterCount >= MAX_ITER) return MAX_ITER;
iterCount++;
}
return iterCount;
} // computeIterations
// -------------------------------------------------------------------
private void moveUp() {
double curHeight = HEIGHT / zoomFactor;
topLeftY = topLeftY.add(new BigDecimal(curHeight / 6));
updateFractal();
} // moveUp
// -------------------------------------------------------------------
private void moveDown() {
double curHeight = HEIGHT / zoomFactor;
topLeftY = topLeftY.subtract(new BigDecimal(curHeight / 6));
updateFractal();
} // moveDown
// -------------------------------------------------------------------
private void moveLeft() {
double curWidth = WIDTH / zoomFactor;
topLeftX = topLeftX.subtract(new BigDecimal(curWidth / 6));
updateFractal();
} // moveLeft
// -------------------------------------------------------------------
private void moveRight() {
double curWidth = WIDTH / zoomFactor;
topLeftX = topLeftX.add(new BigDecimal(curWidth / 6));;
updateFractal();
} // moveRight
// -------------------------------------------------------------------
private void adjustZoom( double newX, double newY, double newZoomFactor ) {
topLeftX = topLeftX.add(new BigDecimal(newX/zoomFactor));
topLeftY = topLeftY.subtract(new BigDecimal(newX/zoomFactor));
zoomFactor = newZoomFactor;
topLeftX = topLeftX.subtract(new BigDecimal(( WIDTH/2) / zoomFactor));
topLeftY = topLeftY.add(new BigDecimal( (HEIGHT/2) / zoomFactor));
updateFractal();
} // adjustZoom
// -------------------------------------------------------------------
private class Canvas extends JPanel implements MouseListener {
public Canvas() {
addMouseListener(this);
}
@Override public Dimension getPreferredSize() {
return new Dimension(WIDTH, HEIGHT);
} // getPreferredSize
@Override public void paintComponent(Graphics drawingObj) {
drawingObj.drawImage( fractalImage, 0, 0, null );
} // paintComponent
@Override public void mousePressed(MouseEvent mouse) {
double x = (double) mouse.getX();
double y = (double) mouse.getY();
switch( mouse.getButton() ) {
//Links
case MouseEvent.BUTTON1:
adjustZoom( x, y, zoomFactor*10 );
break;
// Rechts
case MouseEvent.BUTTON3:
adjustZoom( x, y, zoomFactor/2 );
break;
}
} // mousePressed
public void addKeyStrokeEvents() {
KeyStroke wKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, 0 );
KeyStroke aKey = KeyStroke.getKeyStroke(KeyEvent.VK_A, 0 );
KeyStroke sKey = KeyStroke.getKeyStroke(KeyEvent.VK_S, 0 );
KeyStroke dKey = KeyStroke.getKeyStroke(KeyEvent.VK_D, 0 );
Action wPressed = new AbstractAction() {
@Override public void actionPerformed(ActionEvent e) {
moveUp();
}
};
Action aPressed = new AbstractAction() {
@Override public void actionPerformed(ActionEvent e) {
moveLeft();
}
};
Action sPressed = new AbstractAction() {
@Override public void actionPerformed(ActionEvent e) {
moveDown();
}
};
Action dPressed = new AbstractAction() {
@Override public void actionPerformed(ActionEvent e) {
moveRight();
}
};
this.getInputMap().put( wKey, "w_key" );
this.getInputMap().put( aKey, "a_key" );
this.getInputMap().put( sKey, "s_key" );
this.getInputMap().put( dKey, "d_key" );
this.getActionMap().put( "w_key", wPressed );
this.getActionMap().put( "a_key", aPressed );
this.getActionMap().put( "s_key", sPressed );
this.getActionMap().put( "d_key", dPressed );
} // addKeyStrokeEvents
@Override public void mouseReleased(MouseEvent mouse){ }
@Override public void mouseClicked(MouseEvent mouse) { }
@Override public void mouseEntered(MouseEvent mouse) { }
@Override public void mouseExited (MouseEvent mouse) { }
} // Canvas
} // FractalExplorer
I updated the code to use BigDecimals, and tried using less heapspace, because i got a few errors because of it, but know the for loop with x which picks a color just stops when the value of x equals 256-258, and if i change the width/height, then the program stops at around half of the width+an eight of the width.
I did more testing, and it stops at computIterations(...);, i don't know why, but i hope this helps. It seems like it doesn't stop but rather slow down after a certain amount of times.
I finnaly solved it. The code:
import java.util.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import java.math.BigDecimal;
public class FractalExplorer2 extends JFrame {
static final int WIDTH = 400;
static final int HEIGHT = WIDTH;
Canvas canvas;
BufferedImage fractalImage;
static final int MAX_ITER = 1000;
static final BigDecimal DEFAULT_TOP_LEFT_X = new BigDecimal(-2.0);
static final BigDecimal DEFAULT_TOP_LEFT_Y = new BigDecimal(1.4);
static final double DEFAULT_ZOOM = Math.round((double) (WIDTH/3));
static final int SCALE = 20;
static final int ROUND = BigDecimal.ROUND_CEILING;
final int numThreads = 10;
double zoomFactor = DEFAULT_ZOOM;
BigDecimal topLeftX = DEFAULT_TOP_LEFT_X;
BigDecimal topLeftY = DEFAULT_TOP_LEFT_Y;
// -------------------------------------------------------------------
public FractalExplorer2() {
long a = System.nanoTime();
setup();
addCanvas();
canvas.addKeyStrokeEvents();
updateFractal();
this.setVisible(true);
long b = System.nanoTime();
System.out.println((b-a));
}
// -------------------------------------------------------------------
public static void main(String[] args) {
new FractalExplorer2();
}
// -------------------------------------------------------------------
private void addCanvas() {
canvas = new Canvas();
fractalImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
canvas.setVisible(true);
this.add(canvas, BorderLayout.CENTER);
} // addCanvas
// -------------------------------------------------------------------
private void setup() {
this.setTitle("Fractal Explorer");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(WIDTH, HEIGHT);
this.setResizable(false);
this.setLocationRelativeTo(null);
} // setInitialGUIProperties
// -------------------------------------------------------------------
private BigDecimal getXPos(double x) {
return topLeftX.add(new BigDecimal(x/zoomFactor));
} // getXPos
// -------------------------------------------------------------------
private BigDecimal getYPos(double y) {
return topLeftY.subtract(new BigDecimal(y/zoomFactor));
} // getYPos
// -------------------------------------------------------------------
/**
* Aktualisiert das Fraktal, indem die Anzahl der Iterationen für jeden Punkt im Fraktal berechnet wird und die Farbe basierend darauf geändert wird.
**/
public void updateFractal() {
Thread[] threads = new Thread[numThreads];
int rowsPerThread = HEIGHT / numThreads;
// Construct each thread
for (int i=0; i<numThreads; i++) {
threads[i] = new Thread(new FractalThread(i * rowsPerThread, (i+1) * rowsPerThread));
}
// Starte jeden thread
for (int i=0; i<numThreads; i++) {
threads[i].start();
}
// Warten bis alle threads fertig sind
for (int i=0; i<numThreads; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
canvas.repaint();
} // updateFractal
// -------------------------------------------------------------------
//Gibt basierend auf der Iterationsanzahl eine trennungsfarbe zurück eines gegebenen Punktes im Fraktal
private class FractalThread implements Runnable {
int startY;
int endY;
public FractalThread(int startY, int endY) {
this.startY = startY;
this.endY = endY;
}
public void run() {
BigDecimal c_r;
BigDecimal c_i;
for (int x = 0; x < WIDTH; x++ ) {
for (int y = startY; y < endY; y++ ) {
c_r = getXPos(x);
c_i = getYPos(y);
int iterCount = computeIterations(c_r, c_i);
int pixelColor = makeColor(iterCount);
fractalImage.setRGB(x, y, pixelColor);
}
}
} // run
} // FractalThread
private int makeColor( int iterCount ) {
int color = 0b011011100001100101101000;
int mask = 0b000000000000010101110111;
int shiftMag = iterCount / 13;
if (iterCount == MAX_ITER)
return Color.BLACK.getRGB();
return color | (mask << shiftMag);
} // makeColor
// -------------------------------------------------------------------
private int computeIterations(BigDecimal c_r, BigDecimal c_i) {
BigDecimal z_r = new BigDecimal(0.0).setScale(SCALE,ROUND);
BigDecimal z_i = new BigDecimal(0.0).setScale(SCALE,ROUND);
BigDecimal z_r_tmp;
BigDecimal dummy2 = new BigDecimal(2.0).setScale(SCALE,ROUND);
BigDecimal dummy4 = new BigDecimal(4.0).setScale(SCALE,ROUND);
int iterCount = 0;
while (z_r.multiply(z_r).add((z_i.multiply(z_i))).compareTo(dummy4) != 1) {
z_r_tmp = z_r.setScale(SCALE,ROUND);
z_r = z_r.multiply(z_r).subtract(z_i.multiply(z_i)).add(c_r).setScale(SCALE,ROUND);
z_i = dummy2.multiply(z_i).multiply(z_r_tmp).add(c_i).setScale(SCALE,ROUND);
if (iterCount >= MAX_ITER) return MAX_ITER;
iterCount++;
}
return iterCount;
} // computeIterations
// -------------------------------------------------------------------
private void moveUp() {
double curHeight = HEIGHT / zoomFactor;
topLeftY = topLeftY.add(new BigDecimal(curHeight / 6));
updateFractal();
} // moveUp
// -------------------------------------------------------------------
private void moveDown() {
double curHeight = HEIGHT / zoomFactor;
topLeftY = topLeftY.subtract(new BigDecimal(curHeight / 6));
updateFractal();
} // moveDown
// -------------------------------------------------------------------
private void moveLeft() {
double curWidth = WIDTH / zoomFactor;
topLeftX = topLeftX.subtract(new BigDecimal(curWidth / 6));
updateFractal();
} // moveLeft
// -------------------------------------------------------------------
private void moveRight() {
double curWidth = WIDTH / zoomFactor;
topLeftX = topLeftX.add(new BigDecimal(curWidth / 6));
updateFractal();
} // moveRight
// -------------------------------------------------------------------
private void adjustZoom( double newX, double newY, double newZoomFactor ) {
topLeftX = topLeftX.add(new BigDecimal(newX/zoomFactor)).setScale(SCALE,ROUND);
topLeftY = topLeftY.subtract(new BigDecimal(newY/zoomFactor)).setScale(SCALE,ROUND);
zoomFactor = newZoomFactor;
topLeftX = topLeftX.subtract(new BigDecimal(( WIDTH/2) / zoomFactor)).setScale(SCALE,ROUND);
topLeftY = topLeftY.add(new BigDecimal( (HEIGHT/2) / zoomFactor)).setScale(SCALE,ROUND);
updateFractal();
} // adjustZoom
// -------------------------------------------------------------------
private class Canvas extends JPanel implements MouseListener {
public Canvas() {
addMouseListener(this);
}
@Override public Dimension getPreferredSize() {
return new Dimension(WIDTH, HEIGHT);
} // getPreferredSize
@Override public void paintComponent(Graphics drawingObj) {
drawingObj.drawImage( fractalImage, 0, 0, null );
} // paintComponent
@Override public void mousePressed(MouseEvent mouse) {
double x = (double) mouse.getX();
double y = (double) mouse.getY();
switch( mouse.getButton() ) {
//Links
case MouseEvent.BUTTON1:
adjustZoom( x, y, zoomFactor*5 );
break;
// Rechts
case MouseEvent.BUTTON3:
adjustZoom( x, y, zoomFactor/2 );
break;
}
} // mousePressed
public void addKeyStrokeEvents() {
KeyStroke wKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, 0 );
KeyStroke aKey = KeyStroke.getKeyStroke(KeyEvent.VK_A, 0 );
KeyStroke sKey = KeyStroke.getKeyStroke(KeyEvent.VK_S, 0 );
KeyStroke dKey = KeyStroke.getKeyStroke(KeyEvent.VK_D, 0 );
Action wPressed = new AbstractAction() {
@Override public void actionPerformed(ActionEvent e) {
moveUp();
}
};
Action aPressed = new AbstractAction() {
@Override public void actionPerformed(ActionEvent e) {
moveLeft();
}
};
Action sPressed = new AbstractAction() {
@Override public void actionPerformed(ActionEvent e) {
moveDown();
}
};
Action dPressed = new AbstractAction() {
@Override public void actionPerformed(ActionEvent e) {
moveRight();
}
};
this.getInputMap().put( wKey, "w_key" );
this.getInputMap().put( aKey, "a_key" );
this.getInputMap().put( sKey, "s_key" );
this.getInputMap().put( dKey, "d_key" );
this.getActionMap().put( "w_key", wPressed );
this.getActionMap().put( "a_key", aPressed );
this.getActionMap().put( "s_key", sPressed );
this.getActionMap().put( "d_key", dPressed );
} // addKeyStrokeEvents
@Override public void mouseReleased(MouseEvent mouse){ }
@Override public void mouseClicked(MouseEvent mouse) { }
@Override public void mouseEntered(MouseEvent mouse) { }
@Override public void mouseExited (MouseEvent mouse) { }
} // Canvas
} // FractalExplorer
I just replaced the double variables with BigDecimal and set a Scale, so that the calculation doesn't take too long. I think the code can still be improved, but this is my code right now.