Zooming in on Mandelbrot set fractal in Java
Asked Answered
T

2

5

I am beginning to make a mandelbrot set fractal viewer. I am having a lot of issues when it comes to zooming in on the fractal. If you try to zoom, the viewer will just close in on the center. I have done as much as I can to understand this dilemma. How can I zoom in on my fractal in such a way that when I zoom, it will zoom in on the center of the screen, and not the center of the fractal?

import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import java.awt.event.*;



public class Mandelbrot extends JFrame implements ActionListener {

    private JPanel ctrlPanel;
    private JPanel btnPanel;
    private int numIter = 50;
    private double zoom = 130;
    private double zoomIncrease = 100;
    private int colorIter = 20;
    private BufferedImage I;
    private double zx, zy, cx, cy, temp;
    private int xMove, yMove = 0;
    private JButton[] ctrlBtns = new JButton[9];
    private Color themeColor = new Color(150,180,200);

    public Mandelbrot() {
        super("Mandelbrot Set");
        setBounds(100, 100, 800, 600);
        setResizable(false);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        plotPoints();

        Container contentPane = getContentPane();

        contentPane.setLayout(null);




        ctrlPanel = new JPanel();
        ctrlPanel.setBounds(600,0,200,600);
        ctrlPanel.setBackground(themeColor);
        ctrlPanel.setLayout(null);

        btnPanel = new JPanel();
        btnPanel.setBounds(0,200,200,200);
        btnPanel.setLayout(new GridLayout(3,3));
        btnPanel.setBackground(themeColor);

        ctrlBtns[1] = new JButton("up");
        ctrlBtns[7] = new JButton("down");
        ctrlBtns[3] = new JButton ("left");
        ctrlBtns[5] = new JButton("right");
        ctrlBtns[2] = new JButton("+");
        ctrlBtns[0] = new JButton("-");
        ctrlBtns[8] = new JButton(">");
        ctrlBtns[6] = new JButton("<");
        ctrlBtns[4] = new JButton();

        contentPane.add(ctrlPanel);
        contentPane.add(new imgPanel());
        ctrlPanel.add(btnPanel);

        for (int x = 0; x<ctrlBtns.length;x++){
            btnPanel.add(ctrlBtns[x]);
            ctrlBtns[x].addActionListener(this);
        }

        validate();

    }

    public class imgPanel extends JPanel{
        public imgPanel(){
            setBounds(0,0,600,600);

        }

        @Override
        public void paint (Graphics g){
            super.paint(g);
            g.drawImage(I, 0, 0, this);
        }
    }

    public void plotPoints(){
        I = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
        for (int y = 0; y < getHeight(); y++) {
            for (int x = 0; x < getWidth(); x++) {
                zx = zy = 0;
                cx = (x - 320+xMove) / zoom;
                cy = (y - 290+yMove) / zoom;
                int iter = numIter;
                while (zx * zx + zy * zy < 4 && iter > 0) {
                    temp = zx * zx - zy * zy + cx;
                    zy = 2 * zx * zy + cy;
                    zx = temp;
                    iter--;
                }
                I.setRGB(x, y, iter | (iter << colorIter));
            }
        }
    }

    public void actionPerformed(ActionEvent ae){
        String event = ae.getActionCommand();

        switch (event){
        case "up":
            yMove-=100;
            break;
        case "down":
            yMove+=100;
            break;
        case "left":
            xMove-=100;
            break;
        case "right":
            xMove+=100;
            break;
        case "+":
            zoom+=zoomIncrease;
            zoomIncrease+=100;
            break;
        case "-":
            zoom-=zoomIncrease;
            zoomIncrease-=100;
            break;
        case ">":
            colorIter++;
            break;
        case "<":
            colorIter--;
            break;
        }



        plotPoints();
        validate();
        repaint();
    }




    public static void main(String[] args) {
        new Mandelbrot().setVisible(true);
    }
}
Turkic answered 31/12, 2012 at 4:50 Comment(0)
T
2

I was able to fix this problem by having an additional horizontal or vertical shift every time I zoomed in.

Therefore, instead of having:

case "+": //zooming in
zoom+=zoomIncrease;
zoomIncrease+=100;
break;

I will now have

case "+":
initialZoom = zoom;
zoom+=zoomIncrease;
zoomIncrease*=2;
xMove*=2;
yMove*=2;
break;

this basically relocates the image that has been drawn by multiplying its movement on the x and y axes by the factor that the zoom has increased (which is two).

Turkic answered 2/1, 2013 at 2:47 Comment(1)
Multiplying the xMove by zoom, so that you later can divide by zoom, will essentially work, but it seems like an overly complicated way to do it. Actually, the result is not mathematically correct: zoom starts at 130, and zoomIncrease starts at 100, and when you add those together, it does not amount to an exact doubling, so doubling the xMove will not be exactly correct. Remember, in all coding, simple is always better. Simplify, and make this mathematically correct, by eliminating zoomIncrease and simply use zoom *= 2;Potation
P
5

The Mandelbrot set exists in a mathematical plane with natural coordinates. You "view" this with a BufferedImage I using "view port" coordinates. It is all in the mapping between these. You have labeled the viewport coordinates as x and y, and the "real" coordinates into the Mandelbrot space as cx and cy. These are the formulae:

        cx = (x - 320+xMove) / zoom;
        cy = (y - 290+yMove) / zoom;

In order to zoom in and out of a particular "real" spot, you want the amount of your displacement to be constant as you zoom. The problem is that the amount of displacement is being scaled by the zoom amount. Remember cx and cy are the real coordinates in the Mandelbrot plane and x & y are the viewport coordinates. Thus, when looking at the middle of the viewport, as you change zoom, you want cx & cy to remain constant.

My guess is that you want something like:

    cx = ((x - 320) / zoom) + xMove;
    cy = ((y - 290) / zoom) + yMove;

This will make the "movement" in the Mandelbrot plane remain independent of zoom amount. I am assuming that the 320 and 290 is related to viewport size and gives you a zero in the middle of the viewport.

You are going to want the amount that xMove & yMove change on a keystroke to not be a fixed amount (100) but rather an amount that depends on zoom level. As you zoom in a lot you want the amount of movement in the real Mandelbrot plane to be smaller for each keystroke.

Potation answered 31/12, 2012 at 15:57 Comment(0)
T
2

I was able to fix this problem by having an additional horizontal or vertical shift every time I zoomed in.

Therefore, instead of having:

case "+": //zooming in
zoom+=zoomIncrease;
zoomIncrease+=100;
break;

I will now have

case "+":
initialZoom = zoom;
zoom+=zoomIncrease;
zoomIncrease*=2;
xMove*=2;
yMove*=2;
break;

this basically relocates the image that has been drawn by multiplying its movement on the x and y axes by the factor that the zoom has increased (which is two).

Turkic answered 2/1, 2013 at 2:47 Comment(1)
Multiplying the xMove by zoom, so that you later can divide by zoom, will essentially work, but it seems like an overly complicated way to do it. Actually, the result is not mathematically correct: zoom starts at 130, and zoomIncrease starts at 100, and when you add those together, it does not amount to an exact doubling, so doubling the xMove will not be exactly correct. Remember, in all coding, simple is always better. Simplify, and make this mathematically correct, by eliminating zoomIncrease and simply use zoom *= 2;Potation

© 2022 - 2024 — McMap. All rights reserved.