1

I have made a Mandelbrot fractal using PIL module in Python. Now, I want to make a GIF of zooming into one point. I have watched other code on-line, but needless to say, I didn't understand it, since the pattern I'm using is a bit different (I'm using classes).

I know that to zoom in I need to change scale... But I plainly don't know how to implement it.

from PIL import Image
import random


class Fractal:
    """Fractal class."""

    def __init__(self, size, scale, computation):
        """Constructor.

        Arguments:
        size -- the size of the image as a tuple (x, y)
        scale -- the scale of x and y as a list of 2-tuple
                 [(minimum_x, minimum_y), (maximum_x, maximum_y)]
        computation -- the function used for computing pixel values as a function
        """
        self.size = size
        self.scale = scale
        self.computation = computation
        self.img = Image.new("RGB", (size[0], size[1]))

    def compute(self):
        """
        Create the fractal by computing every pixel value.
        """
        for y in range(self.size[1]):
            for x in range(self.size[0]):
                i = self.pixel_value((x, y))
                r = i % 8 * 32
                g = i % 16 * 16
                b = i % 32 * 8
                self.img.putpixel((x, y), (r, g, b))

    def pixel_value(self, pixel):
        """
        Return the number of iterations it took for the pixel to go out of bounds.

        Arguments:
        pixel -- the pixel coordinate (x, y)

        Returns:
        the number of iterations of computation it took to go out of bounds as integer.
        """
        # x = pixel[0] * (self.scale[1][0] - self.scale[0][0]) / self.size[0] + self.scale[0][0]
        # y = pixel[1] * (self.scale[1][1] - self.scale[0][1]) / self.size[1] + self.scale[0][1]
        x = (pixel[0] / self.size[0]) * (self.scale[1][0] - self.scale[0][0]) + self.scale[0][0]
        y = (pixel[1] / self.size[1]) * (self.scale[1][1] - self.scale[0][1]) + self.scale[0][1]

        return self.computation((x, y))

    def save_image(self, filename):
        """
        Save the image to hard drive.

        Arguments:
        filename -- the file name to save the file to as a string.
        """
        self.img.save(filename, "PNG")

if __name__ == "__main__":
    def mandelbrot_computation(pixel):
        """Return integer -> how many iterations it takes for the pixel to escape the mandelbrot set."""
        c = complex(pixel[0], pixel[1])  # Complex number: A + Bi  (A is real number, B is imaginary number).
        z = 0  # We are assuming the starting z value for each square is 0.
        iterations = 0  # Will count how many iterations it takes for a pixel to escape the mandelbrot set.

        for i in range(255):  # The more iterations, the more detailed the mandelbrot set will be.
            if abs(z) >= 2.0:  # Checks, if pixel escapes the mandelbrot set. Same as square root of pix[0] and pix[1].
                break
            z = z**2 + c
            iterations += 1

        return iterations

    mandelbrot = Fractal((1000, 1000), [(-2, -2), (2, 2)], mandelbrot_computation())
    mandelbrot.compute()
    mandelbrot.save_image("mandelbrot.png")
Prune
  • 76,765
  • 14
  • 60
  • 81
  • Welcome to StackOverflow. Please read and follow the posting guidelines in the help documentation. [How to ask](http://stackoverflow.com/help/how-to-ask) and [Minimal, complete, verifiable example](http://stackoverflow.com/help/mcve) apply here. – Prune Nov 28 '16 at 21:47
  • Hello and thank you. Sorry, I rushed with this question since It has been bothering me for so long. Thank you for your detailed answer! – Jaroslav Gorshkov Nov 28 '16 at 22:09
  • We're here to help. You'll get better answers if you adhere to the posting guidelines, making it easier for speed-readers to ingest the problem. – Prune Nov 28 '16 at 22:16
  • When you get to a resolution, please remember to up-vote useful things and accept your favourite answer (even if you have to write it yourself), so Stack Overflow can properly archive the question. – Prune Nov 28 '16 at 22:17

1 Answers1

1

This is a "simple" linear transformation, both scale (zoom) and translate (shift), as you learned in linear algebra. Do you recall a formula something like

s(y-k) = r(x-h) + c

The translation is (h, k); the scale in each direction is (r, s).

To implement this, you need to alter your loop increments. To zoom in a factor of k in each direction, you need to reduce the range of your coordinates, likewise reducing the increment between pixel positions.

The most important thing here is to partially decouple your display coordinates from your mathematical values: you no longer display the value for 0.2 - 0.5i at the location labeled (0.2, -0.5); the new position computes from your new frame bounds.

Your old code isn't quite right for this:

    for y in range(self.size[1]):
        for x in range(self.size[0]):
            i = self.pixel_value((x, y))
            ...
            self.img.putpixel((x, y), (r, g, b))

Instead, you'll need something like:

    # Assume that the limits x_min, x_max, y_min, y_max
    #   are assigned by the zoom operation.
    x_inc = (x_max - x_min) / self.size[0]
    y_inc = (y_max - y_min) / self.size[1]
    for y in range(self.size[1]):
        for x in range(self.size[0]):
            a = x*x_inc + x_min
            b = y*y_inc + y_min
            i = self.pixel_value((a, b))
            ...
            self.img.putpixel((x, y), (r, g, b))
Prune
  • 76,765
  • 14
  • 60
  • 81