James Barker
James Barker

Reputation: 11

Zoom On Mandelbrot

I am trying to figure out a way to zoom in on my Mandelbrot set on click. I have it so when I click it slightly zooms in but it doesn't move the Mandelbrot accordingly.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Numerics;

namespace Project_V2
{
    public partial class FractalGen : Form
    {
        public double zoom = 2.4;
        public FractalGen()
        {
            InitializeComponent();
        }

        private void pictureBox1_Click(object sender, EventArgs e)
        {
            zoom -= 0.3;
            Mandelbrot();
        }

        private void button1_Click(object sender, EventArgs e)
        {
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Mandelbrot();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {

        }

        private void Mandelbrot()
        {
            Bitmap bm = new Bitmap(pictureBox1.Width, pictureBox1.Height);
            DateTime StartT = DateTime.Now;
            for (int x = 0; x < pictureBox1.Width; x++)
            {
                for (int y = 0; y < pictureBox1.Height; y++)
                {
                    double a = (double)(x - (pictureBox1.Width / 1.25)) / (double)(pictureBox1.Width / zoom);
                    double b = (double)(y - (pictureBox1.Height / 2)) / (double)(pictureBox1.Height / zoom);
                    Complex C = new Complex(a, b);
                    Complex Z = new Complex(0, 0);
                    int u = 0;
                    do
                    {
                        u++;
                        Z = Z * Z;
                        Z = Z + C;
                        double Mag = Complex.Abs(Z);
                        if (Mag > 2.0) break;
                    } while (u < 255);
                    Color rgbInside = Color.FromArgb(0, 0, 0);
                    Color rgbOutside = Color.FromArgb(u >= 127 ? 255 : 2 * u, u >= 127 ? (u - 127) : 0, 0);
                    bm.SetPixel(x, y, u < 255 ? rgbOutside : rgbInside);
                }
            }
            pictureBox1.Image = bm;
            DateTime EndT = DateTime.Now;
            string Time = Convert.ToString((EndT - StartT).TotalSeconds);
            textBox1.Text = "Time Taken: " + Time + " Seconds";
        }

        private void button1_Click_1(object sender, EventArgs e)
        {
            zoom = 2.4;
            Mandelbrot();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            saveFileDialog1.ShowDialog();
        }

        private void saveFileDialog1_FileOk(object sender, CancelEventArgs e)
        {
            string name = saveFileDialog1.FileName;
            pictureBox1.Image.Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Png);
        }
    }
}

The current code divides the picturebox width and height by a value, but I want to have it so it zooms in on where I click. How could I scale the picturebox in relation to where I click?

Upvotes: 0

Views: 2721

Answers (1)

Peter Duniho
Peter Duniho

Reputation: 70671

It would be helpful to understand what guidance has already been offered to you. I infer from this comment that there is probably an existing question you asked and then deleted, in which this problem was discussed. Without knowing what help has already been offered and what specifically it was you were unable to understand, it's hard to know how best to present a new answer.

That said, there are two fundamental problems you have to solve to achieve the behavior you're looking for:

  1. Identify where the mouse is clicked. Currently, you are using the Click event, which only reports to your code that the control was clicked, not where it was clicked.
  2. Fix your rendering code to accommodate changes in the boundaries of what should be drawn. Currently you always map the range of pixels in your bitmap to the range in the complex plane centered on (-0.72, 0), adjusting only how far from that center is rendered (i.e. the zoom variable.

To address #1, you need to subscribe to the MouseClick event instead. This will pass a MouseEventArgs object to your handler, which has a Location property that indicates the point within the control's client area that was actually clicked (i.e. the coordinates reported are relative to an origin at the top-left of the control).

To address #2, you need to add some variables to your class to keep track of the relative position where you want to draw. There are lots of ways to do this, but staying with your current convention of representing the current "zoom" factor as the width and height of the rendered area, it seems to make sense to track the center of the space being rendered, i.e. the location the mouse was clicked, in terms of the location on the complex plane that was drawn.

I have a strong objection to programs that lock up the UI while they do long-running tasks. So in the process of revising your code example, I modified it so that it would render the image in a worker thread, and would report the rendering progress by providing a status message on a Label that I added to the form. (This involved making your Mandelbrot() method an async method…I went ahead and used await when calling it to suppress compiler warnings, but of course in this context the awaits are not strictly needed.)

One thing I did not do was to add further optimizations to the code. These tend to obfuscate the changes that are directly related to your question. There are a number of things you could do to your code to help improve performance, by removing redundant calculations or by using mathematical relationships to perform computations less expensively than the literal translation of the Mandelbrot algorithm.

There is lots on the Internet about how to render Mandelbrot images, so you can look around to find that sort of advice, once you have gotten the basic idea working. Besides, for now, with only 255 iterations per pixel and the relatively small bitmap, performance isn't a serious consideration.

Here is the version of your code I came up with:

public partial class Form1 : Form
{
    double zoom = 2.4;
    double centerX = -0.72, centerY = 0;

    public Form1()
    {
        InitializeComponent();
    }

    private async void pictureBox1_MouseClick(object sender, MouseEventArgs e)
    {
        double minX = centerX - zoom / 2, minY = centerY - zoom / 2;
        centerX = minX + (double)e.Location.X / pictureBox1.Width * zoom;
        centerY = minY + (double)e.Location.Y / pictureBox1.Height * zoom;
        zoom -= 0.3;
        await Mandelbrot();
    }

    private async void Form1_Load(object sender, EventArgs e)
    {
        await Mandelbrot();
    }

    private async Task Mandelbrot()
    {
        IProgress<double> progress = new Progress<double>(x => label1.Text = $"{x * 100:0}% done");

        DateTime StartT = DateTime.UtcNow;
        pictureBox1.Image = await Task.Run(() => _GenerateBitmap(progress));
        DateTime EndT = DateTime.UtcNow;
        string Time = Convert.ToString((EndT - StartT).TotalSeconds);
        textBox1.Text = "Time Taken: " + Time + " Seconds";
    }

    private Bitmap _GenerateBitmap(IProgress<double> progress)
    {
        Bitmap bm = new Bitmap(pictureBox1.Width, pictureBox1.Height);
        double minX = centerX - zoom / 2, minY = centerY - zoom / 2;

        for (int x = 0; x < pictureBox1.Width; x++)
        {
            for (int y = 0; y < pictureBox1.Height; y++)
            {
                double a = minX + (double)x / pictureBox1.Width * zoom;
                double b = minY + (double)y / pictureBox1.Height * zoom;
                Complex C = new Complex(a, b);
                Complex Z = new Complex(0, 0);
                int u = 0;
                do
                {
                    u++;
                    Z = Z * Z;
                    Z = Z + C;
                    double Mag = Complex.Abs(Z);
                    if (Mag > 2.0) break;
                } while (u < 255);
                Color rgbInside = Color.FromArgb(0, 0, 0);
                Color rgbOutside = Color.FromArgb(u >= 127 ? 255 : 2 * u, u >= 127 ? (u - 127) : 0, 0);
                bm.SetPixel(x, y, u < 255 ? rgbOutside : rgbInside);
            }

            progress.Report((double)x / pictureBox1.Width);
        }

        return bm;
    }

    private async void button1_Click_1(object sender, EventArgs e)
    {
        zoom = 2.4;
        await Mandelbrot();
    }
}

Upvotes: 3

Related Questions