Skip to content

PedroZappa/42_fractol

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fract'ol

This project is about creating graphically beautiful fractals.

Fractol Demo Overview

Table o'Contents

About 📌

Fract'ol is the first computer graphics project of the Common Core curriculum.

It is a simple graphics program using minilibx, an opportunity to learn how to use the mathematical notion of complex numbers, have a first contact with the concept of optimization in computer graphics, and event handling.


Mandatory Features

  • General

    • The program must take the type of the fractal to be displayed as a parameter and any other relevant option.
    • The program must display the fractal in the window powered by minilibx.
    • The project must contain a Makefile that compiles all sources. It must not relink.
    • Global variables are forbidden.
  • Rendering

    • The program must offer the Julia and Mandelbrot sets.
    • The mouse wheel zooms in and out almost infinitely, within the limits of the computer.
    • A different Julia set must be rendered if the program is passed the appropriate parameters.
    • A parameter passed on startup must be the type of the fractal to be rendered.
      • Adding more parameters is optional. If no parameter is provided, or the parameters are invalid, it displays the help page and exits cleanly.
    • A few different color schemes must be implemented.
  • Graphic Management

    • The program has to display the image in a window.
    • The management of the window must remain smooth.
    • Pressing ESC must close the window and exit the program in a clean way.
    • Clicking on the cross on the top window frame must have the same effect.
    • It is mandatory to use images from minilibx.

Bonus Features

  • One extra fractal.
  • The zoom follows the mouse position.
  • Moving the view by pressing the arrow keys.
  • Make the color range shift.

Implementation 📜

Before anything else, the main() function declares a t_display variable named display that stores all the necessary data, conveniently packed to be passed around the program.


t_display Structure

typedef struct s_display
{
	// mlx Data
	void        *mlx_conn;   // Stores pointer to mlx connection
	void        *mlx_win;    // Stores pointer to mlx window
	t_img       img;         // Stores the image data
	int         width;       // Stores the width of the window
	int         height;      // Stores the height of the window
	t_range     win_size;    // Stores the size of the window
	double      x_offset;    // Stores how much to shift when moving the view 
	double      y_offset;    // Stores how much to shift when moving the view
	double      zoom;        // Stores the zoom factor
	// Fractal Data
	char        *name;       // Stores the name of the fractal
	int         set;         // Stores the type of fractal
	long        iter;        // Stores the number of iterations
	t_complex   z;           // Stores z for Mandelbrot/Julia/Tricorn/Burning Ship
	t_complex   c;           // Stores c for Mandelbrot/Tricorn/Burning Ship
	t_complex   c_julia;     // Stores c for Julia
	t_complex   z_newton;    // Stores z for Newton
	t_range     frac_range;  // Stores the range of the complex plane
	double      escape;      // Stores the complex plane escape value
	double      newton_esc;  // Stores escape value for Newton
	t_range     color_iter;  // Stores a range of 0 to n iterations 
	t_range     color_range; // Stores a range from black to white
	int         color;       // Stores a color for the Newton fractal
}               t_display;

ft_args.c : Argument Parsing Functions

The main logic for argument parsing can be found inside the ft_args.c file.

ft_no_args() and ft_args() are used to parse the input arguments and ensure that if there is something wrong the program exits correctly (without memory leaks).

if (argc < 2)
	return (ft_no_args());
else if (!ft_args(&display, argc, argv))
	exit(EXIT_FAILURE);

ft_no_args()

If the program is passed no arguments:

  • It prints an error to stderr;
  • Displays the help page and exits cleanly.

ft_args()

Checks if the arguments passed are valid.

int	ft_args(t_display *d, int argc, char **argv)
{
	if (!ft_select_fractal(d, argc, argv))
		return (ft_invalid_args(argv[1]));
	if (!ft_set_args(d, argc, argv))
		return (0);
	return (1);
}
  • First checks if the fractal type selected is valid.
  • Then attempts to set the input arguments:

ft_select_fractal()

This function checks if the fractal type is valid.

  • It first converts the fractal type (first argument) to lowercase.
  • If it is valid, ft_set_fractal() is called and the function outputs 1.
  • If it is NOT valid it outputs 0.

ft_set_args()

Here we make sure we got the right number of arguments and check if they are the right type before the program initializes anything.

  • First checks the iterations argument:

    • If the 2nd argument is a valid input for the number of iterations, we set it to d->iter. In case it is a negative value a default value is set instead.
    • Otherwise the program prints an error to stderr and exits.
  • Then we check for the Julia case in which we get a complex number as the third and fourth arguments.

    • If the input arguments are a valid doubles we set them to d->c_julia.r and d->c_julia.i.
    • Otherwise the program prints an error to stderr and exits.

ft_init.c : Initialization Functions

After all validation tests are passed, the program calls ft_init_display().


ft_init_display()

ft_init_display(&display, argv);

It initializes:

  • the mlx connection into d->mlx_conn by calling mlx_init();
  • the mlx window into d->mlx_win by calling mlx_new_window();
  • the image pointer into d->img.img by calling mlx_new_image();
  • the image pixels into d->img.pix by calling mlx_get_data_addr();

All these calls are properly protected by calls to cleanup functions in case a initialization error arises.

After everything is properly allocated we proceed to initialize the event handling functionality.


ft_init_events()

This function initializes three event handlers to be triggered when certain events are received:


ft_kill_handle();
  • Listens for DestroyNotify event;
    • Destroys the image data;
    • Destroys the mlx window;
    • Destroys the mlx connection;
    • Frees the t_display pointer to the mlx_conn;

ft_handle_keys();
  • Listens for KeyPress events;
    • If Escape is received, it exits by calling ft_kill_handle();
    • If the arrow keys are pressed, ft_handle_offsets() is called;
    • If PageUp or PageDown are pressed, the d->iter is increased or decreased by 1 respectively;
    • If Space, 1, 2, 3, 4, 5 are pressed, ft_swith_set() is called.
    • If Left-Shift, Right-Shift, r, g or b are pressed, ft_switch_color() is invoked.
    • Else if the key press received is not being handled, a message with the keysym value is printed to stdout.
    • If an event was successfully caught ft_render() is called causing a re-render of the window.

ft_handle_mouse();
  • Listens for ButtonPress events;
    • If the left mouse button is pressed inside the window when on the Mandelbrot set the fractal settings are changed and a re-render is triggered with a Julia set with its c set to the current mouse position;
    • Else if the right button is pressed the window re-renders the Mandelbrot set.
    • Else if the mouse wheel is scrolled up or down ft_handle_zoom() is called.

Note

Understanding ft_handle_zoom() :

Centering & Scaling

The keys to zooming in computer graphics are :

  • Adjusting the view's center, by changing the d->x_offset and d->y_offset;
  • Adjusting the view's scale, by changing the d->zoom factor;

Mouse Position & Zoom Center

The x and y coordinates of the mouse are used to determine the zoom center;

  • This is done by mapping the mouse position to the range of the complex plane;

Zoom Factor & Scaling

  • The zoom factor (SCALE_FACTOR) determines how much the view is scaled with each zoom operation.
  • Increasing the zoom level, divides d->zoom value by the SCALE_FACTOR, enlarging the view;
  • Decreasing the zoom level, multiplies d->zoom value by the SCALE_FACTOR, shrinking the view.
  • fabs() is used to ensure that the scale factor is always positive, regardless of the current zoom level.

Offset Adjustment

  • The offset adjustment (0.13 * fabs(d->zoom)) is a scaling factor that controls how much the view is moved in response to zooming.
  • This factor is multiplied by the mapped mouse position to ensure that the zoom center is adjusted proportionally to the zoom level, providing a smoother and more controlled zooming.

Now that we got the X connection, the window and event handling up and running all there is left to do it the data initialization.


ft_init_data()

In this function we initialize the data inside the t_display structure to be passed and used by the program.

ft_init_display(&display, argv);

Check out ft_init.c and fractol.h for a closer look at what is being initialized and to what values.


ft_usage()

The ft_usage() function prints the usage of the program and all available commands to stdout.

ft_usage();

ft_render()

This is where the pixel-by-pixel drawing of the window takes place.

ft_render(&display);
  • It iterates over each pixel in the window;
  • Selects the rendering function based on the chosen fractal type;
  • For each pixel it evaluates the function describing the selected set;
while (++y <= HEIGHT)
{
	x = -1;
	while (++x < WIDTH)
		ft_select_set(d, x, y);
	ft_printf("\r%sRendering:%s [%d%%]", YEL, NC, ((y * 100) / d->height));
}
ft_printf("\t%sComplete!%s\n", MAG, NC);
  • Once the calculations are done mlx_put_image_to_window() is called to render the image to the window.
mlx_put_image_to_window(d->mlx_conn, d->mlx_win, d->img.img, 0, 0);
  • Then ft_render_ui() is called to print a simple UI to the window.
ft_render_ui(d);

Note

This is a function that can produce memory leaks if the usage of ft_itoa() and ft_strjoin() are not handled correctly. Take a look for yourself at ft_ui.c for details.


mlx_loop()

Finally, the program enters an infinite loop, keeping the window open listening for user events.

mlx_loop(d->mlx_conn);

Usage 🏁

First, clone the contents of this repository over SSH:

git clone git@github.com:PedroZappa/42_fractol.git

Then, make sure that the program is compiled with all its dependencies using make:

make

One way to find out all available startup options and keybindings, is to run the program without arguments:

./fractol

Testing 🧪

If you want to test the program with valgrind, you can use the following make rule:

make valgrind

There is also a convenient make rule to run a Norminette check:

make norm

Appendixes


MinilibX 🪟

MinilibX is a small library, a simplified version of XLib (X11R6) written in C , designed to introduce students to the X-Window System. 1


X-Window System

The X-Window System is an architecture independent windowing system for bitmap displays that provides a basic framework for creating graphical user interfaces. 2 It enables users to draw and move windows on a display using the mouse and keyboard.

Note

In computing, a bitmap (also known as bit array or bitmap index) is a mapping from a given domain (for instance, a range of integers) to bits. 3


X client-server Architecture

X is based on a client-server model:

  • one X server connects to multiple X client programs.
flowchart TB
	Display[Display]
	Keys[Keyboard]
	Mouse[Mouse]

	Keys[Keyboard] --->|input| Xserv[X Server]
	Mouse[Mouse] --->|input| Xserv
	Display[Display] <---|output| Xserv
	subgraph W[User Workstation]
		Xserv[X Server]
		Xserv --> X-client[X client1]
		Xserv --> X-client2[X client2]
	end
	subgraph Remote Machine
		Xserv -->|Network Conn| X-client3[X client3]
	end

The X Server receives requests to output graphics on the display (through windows) and sends back user input (from a keyboard, mouse, etc).

Note

There are many implementations of the X Window System (Xlib), minilibx being just one among many following the X Consortium standard; 4


Complex Numbers

Complex numbers are numbers in the form (a + bi) where:

  • a is the real part:
  • b is the imaginary part;
  • i is the imaginary unit, defined by the equation $i^2 = -1$.

Note

$i = \sqrt-1$


Complex Arithmetic

Like with real numbers, we can perform arithmetic on complex numbers.


Addition

$(a + bi) + (c + di) = (a + c) + (b + d)i$

Example of how to add two complex numbers:

$((3 - 4i) + (2 + 5i)) =$

$((3 + 2) + (-4 + 5)i) =$

$(5 + i)$


Subtraction

$(a + bi) - (c + di) = (a - c) + (b - d)i$


Multiplication

Multiplication is similar to multiplying binomials but with complex numbers we work with the real and imaginary parts separately.

Complex * Real

$c(a + bi) = (c * a) + (c * b)i$

Example:

$3(6 + 2i) =$

$(3 * 6) + (3 * 2i) =$ # Distribute

$(18 * 6i)$ # Simplify


Complex * Complex

$(a + bi)(c + di) = ac + adi + bci + bdi^2$

  • Because $i^2 = -1$, we can simplify the expression to:

$(a + bi)(c + di) = ac + adi + bci - bd$

  • Simplifying, we combine the real parts, and then the imaginary parts:

$(a + bi)(c + di) =$

$(ac - bd) + (ad + bc)i$

Example:

$(4 + 3i)(2 - 5i) =$

$(4 * 2) + (4 * (-5i)) + (3i * 2) + (3i * (-5i)) =$

$8 - 20i + 6i - 15i^2 =$

$8 + 15 - 20i + 6i =$

$(23 - 14i)$


Expanding a Complex Number

Here is an example on how to expand a squared complex number:

$(a + bi)^2 =$

$(a * a) + (a * bi) + (a * bi) - (bi * bi)$

$(a^2 - bi^2) + 2(a * bi))$

  • The real part is $(a^2 - b^2)$;
  • The imaginary part is $2(a * bi)$;

Complex Plane

We can take complex numbers and plot them in a plane known as the Complex Plane.

This plane is formed by the mapping of the real and imaginary parts of a complex number to a Cartesian coordinate system. The real part mapped to the x-axis and the imaginary part to the y-axis.


Fractals

Fractals are infinitely complex self-similar patterns across multiple scales.

Generated by:

  • Initializing a complex number $z = (x + yi)$ where: $i^2 = -1$
  • x and y are image pixel coordinates mapped to a range between -2 to 2.
  • A formula is iterated until the value of |z| becomes greater than 2.
    • If the point never escapes the range it IS considered to be part of the set.
    • If the point escapes the range it means it is NOT part of the set.
    • The color of each pixel is determined by the number of iterations it took to escape the set.

Julia Set

Formula : $f(z_{n+1}) = z_n^2 + c$

There are infinitely many Julia sets. To generate them, we use the same complex number c for all pixels.

  • For each pixel in the image:
    • z is initially set to 0.
    • z is updated repeatedly following the formula $z_{n+1} = z^2 + c$.
    • c is a complex number that seeds a specific Julia set.

Julia Fractol Demo


Mandelbrot Set

Formula : $f(z_{n+1}) = z_n^2 + c$

For the Mandelbrot set, we use different complex numbers for each pixel. It is the one map to all Julia sets.

  • For each pixel in the image:
    • z is initially set to 0.
    • z is updated repeatedly following the formula $z_{n+1} = z^2 + c$.
    • c is a complex constant defined as: $c = (x + yi)$ where: $i^2 = -1$

Mandelbrot Fractol Demo


Burning Ship Set

$f(z_{n+1}) = (|{Re}(z_n)| + |{Im}(z_n)|i)^2 + c$

The Burning Ship Set is generated by the equation above where:

  • $z_n$ is the current complex number;
  • c is a complex constant (just like in the Julia Set formula);
  • $z_{n+1}$ is the next complex number in the sequence;
  • The real and imaginary components are set to their absolute values before squaring at each iteration.

This modification results in the distinctive "burning ship" appearance of the fractal.

Burning Ship Fractol Demo


Tricorn Set

Formula : $f(z_{n+1}) = \overline{z_n}^2 + c$

The Tricorn fractal is a variant of the Mandelbrot set and is characterized by its triangular shape. It is generated by using a slightly different formula where:

  • The complex conjugate of z is squared instead of z itself.
  • The complex conjugate of z is represented by $\overline{z_n}$
  • c is a complex constant that varies for each pixel in the image.

Note

To get the complex conjugate of a complex number $z_n = (a + bi)$, we simply invert the sign of the imaginary part like so: $\overline{z_n} = (a - bi)$

For example: The conjugate of (4 + 7i) is (4 - 7i).

Tricorn Fractol Demo


Footnotes

Footnotes

  1. The Fractal Geometry of Nature - Benoit B. Mandelbrot - Google Livros

  2. Are Fractals or Fractal Curves Differentiable?

  3. How to Draw Fractals by Hand: A Beginner's Guide

  4. Complete List of Books by Benoit Mandelbrot