Hiee guys, me here.
Last time I was playing with SDL_Texture and using some math algorithms to change the alpha of each pixel, now I decided that I want to do some more cool-looking stuff with pixels, and what won't fit better here as complex numbers and fractals.
Introduction to complex numbers
If you don't know complex number is a specific type of a number that has a real and imaginary part. The real part is our usual number we use (e.g. 1000 or 7 or anything else) and imaginary number is a combination of a casual number and an i (f.e. 12*i), the i is a square root of -1, so i^2 = -1.
So lets say we have a complex number 9 + 10i. So what can we do with this? Imagine.
Okay lets say we want to draw imaginary numbers on a plane (not the flying complex metal structure) where the x-axis is a real part and y-axis is an imaginary part. So if we use out 9 + 10i it is the same as x = 9 and y = 10 coordinates on a usual x,y plane (I believe you can find where to put them).
Also the complex numbers have a set of operations, which is the same a normal set of operations we can use on an one-variable equation (e.h. 10 + 9a), so doing (10 + 9a) + (11 + 1a) is the same as (10 + 9i) + (11 + 1i), also consider that - works the same, so we will have 21 + 10i. The same works multiplication, (10 + 9i) * (11 + 1i) = 10*11 + 10*1i + 9i*11 + 9i*1i, and if i*i = -1, we know that 9i*1i = -9 (the rest I am too lazy to compute). Okay now what about division, it works the same, but needs a bit more work to be done, what we need is to c1/c2 = c3 (where c1,c2 and c3 are complex numbers), so c1/c2 somehow needs to become the r +/- ni.
So consider c1 = 2 + i and c2 = 4 + i, if we do c1/c2 we will have
2 + i (2+i)(4-i)
----- = ----------
4 + 1 (4+i)(4-i)
And knowing that (a+b)(a-b) = a^2 - b^2, and b has an imaginary part (the i), also that i*i = -1 we get a^2+b^2.
So we get
(2+i)(4-i) 8-2i+4i+1 9 2i
---------- = --------- = ---- + ----
(4+i)(4-i) 17 17 17
which basically is in form r + ni.
And the last important part we need is absolute value of the complex number which is achievable by this formula
float abs = square_root(real*real + imaginary*imaginary)
which is the same as getting the distance from the point (0,0) to (real, imaginary).
Introduction to fractals
About them
There is an existing paradox about measurement the coastline which says that the lower measuring units you use the bigger the overall measurement becomes. So if you pick a measurement units of 100 meters and 50 meters after the final estimation on both won't be the same, the one that uses 100 meters unit will be lower the the 50 meter units. The most popular example is a measuring the coastline of Britain where the 100 km unit gives 2800 km length, while the 50 km unit gives 3400 km length and if we keep lowering the unit we will get the bigger values.
And so why is it happening?
Normally I would say that ehh physics or ehh math or we are just stupid but not now ;J, we can say that this happens because when something infinitely grows smaller the overall length doesn't disappear. So when we want to measure the coastline in the best details we can, we will have to zoom to get smaller details, which most likely have more structure which will make us zoom even more. (these are my thoughts so don't even think that these are right) Possibly it is finite, we just need to zoom straight to atoms, or smaller if needed, until we get the smallest possible unit in our world.
And so the fractals work, we zoom more are more, we get more and more structure which makes us zoom more. They have infinite structures and this doesn't change on how far we zoom into the fractals.
How to get fractals
We can use the complex numbers (although it is possible to achieve fractals without them, they are just too handy) to achieve that, they create a pretty handy 2 dimensional points we can use.
So to achieve the zooming effect we can use an iteration algorithm, which basically produces that "more structure" with each iteration. In this article we will use one of the popular Mandelbrot set. We can achieve that by having this algorithm
// suppose the iter is an integer that shows the amount of iterations
// the curr is a complex number that has a current state
// the c0 is the complex number before any iteration
// so the formula we need to implement is c(n+1) = c(n)*c(n) + c(0)
for (int i = 0; i < iter; i++) {
// so basically curr (c(n+1)) = curr*curr (or the c(n)*c(n)) + c0
curr = complex_add(complex_mul(curr, curr), c0);
distance = complex_abs(curr); // the absolute value of the fractal
if (distance > 2) { // our stopping point that Mandelbrot defines
break;
}
}
Implementation in c
And here is the most interesting part for every programmer I believe, we will implement what we have been learning for the past 1-3 hours ;t.
Complex numbers
I decided to create my own struct Complex and corresponding calls
typedef struct {
double real;
double imag;
} Complex;
Complex complex_add(Complex a, Complex b) {
return (Complex){
a.real + b.real,
a.imag + b.imag
};
}
Complex complex_sub(Complex a, Complex b) {
return (Complex){
a.real - b.real,
a.imag - b.imag
};
}
Complex complex_mul(Complex a, Complex b) {
return (Complex){
a.real * b.real - a.imag * b.imag,
a.real * b.imag + a.imag * b.real
};
}
Complex complex_div(Complex a, Complex b) {
double denominator = b.real * b.real + b.imag * b.imag;
return (Complex){
(a.real * b.real + a.imag * b.imag) / denominator,
(a.imag * b.real - a.real * b.imag) / denominator
};
}
double complex_abs(Complex z) {
return sqrt(z.real * z.real + z.imag * z.imag);
}
These functions implement the complex number operations I talked before.
main.c
As always I use this setup in the top
// the window dimensions
#define WIDTH 1200
#define HEIGHT 1200
typedef struct {
SDL_Window* window;
SDL_Renderer* renderer;
} State;
// global variable that contains the window and corresponding to it renderer
State state;
And the
void init() {
if (!SDL_Init(SDL_INIT_VIDEO)) {
printf("couldn't init sdl\n");
exit(1);
}
if (!SDL_CreateWindowAndRenderer(
"image stuff",
WIDTH, HEIGHT,
SDL_WINDOW_RESIZABLE,
&state.window, &state.renderer))
exit(1);
}
void destroy() {
SDL_DestroyWindow(state.window);
SDL_DestroyRenderer(state.renderer);
SDL_Quit();
}
int main() {
init(); // creates SDL_Window and SDL_Rendere, puts them in the state
SDL_SetRenderLogicalPresentation( // doesn't need it now tbh
state.renderer,
WIDTH, HEIGHT,
SDL_LOGICAL_PRESENTATION_STRETCH);
SDL_SetRenderDrawColor(state.renderer, 0, 0, 0, 0);
SDL_RenderClear(state.renderer);
draw_fractal(50); // the function that draws the fractals
SDL_RenderPresent(state.renderer);
SDL_Event event;
do { // a casual stop
SDL_WaitEvent(&event);
} while (event.type != SDL_EVENT_QUIT);
destroy(); // SDL_Quit, SDL_DestroyWindow/Renderer
return 0;
}
Drawing the Mandelbrot fractals
So what we do is we going through the each pixel of the window, getting its real and imaginary part to convert the (x,y) to complex number and do the iterations on that complex number.
We use the escaped to check if this pixel is the fractal part we need or no. So it basically means if escaped is 1, it is not the fractal pixel, otherwise it is.
What you can find here is the multiplications by 2.5 on both real and imaginary part and the -2 and -1. The 2.5 is basically the zoom, but the bigger the number is the smaller the fractal will become. And the -2, -1 is the shift of the fractal on the axis'. You can try changing the values to experiment with it.
And now you may also have the question why do i do w/WIDTH and h/HEIGHT, we basically need the real and imaginary part to be pretty small to fit into the distance, which we use 2, you also can change that number to experiment on how the fractal will change.
void draw_fractal(int iter) {
for (int h = 0; h < HEIGHT; h++) {
for (int w = 0; w < WIDTH; w++) {
double real = (double)w / WIDTH * 2.5 - 2;
double imag = (double)h / HEIGHT * 2.5 - 1;
Complex c0 = {
.real = real,
.imag = imag
};
int escaped = 0;
double distance;
Complex curr = c0;
for (int i = 0; i < iter; i++) {
curr = complex_add(complex_mul(curr, curr), c0);
distance = complex_abs(curr);
if (distance > 2) {
escaped = 1;
break;
}
}
if (escaped)
SDL_SetRenderDrawColor(state.renderer, 0, 0, 0, 0);
else {
uint8_t color = (distance / 3) * 255;
SDL_SetRenderDrawColor(state.renderer, color, color, color, 0);
}
SDL_RenderPoint(state.renderer, w, h);
}
}
}
Okay and now if we run that code we will get a pretty fractal we worked so hard for.
Playing with pixel colors
As you may see it looks pretty cool. Now what I want to change is how we draw it, instead of using the escaped to draw the background I want to use the distance to directly affect the color of the pixels.
So remove the if else and put there this code
uint8_t color = (distance / 3) * 255;
uint8_t r = (1000 * distance * w) / 255;
uint8_t g = (1000 * distance * h) / 255;
uint8_t b = color;
Lets try removing the 1000 * part and see what happens...

Okay it looks a bit better but still meh.
Lets try changing to this
uint8_t r = (distance * w) / 255;
uint8_t g = (distance * h) / 255;
uint8_t b = (1000 * distance) / 255;
And run...

Okok it looks way cooler now, although a bit hard to see.
What if I multiply a costant to each of the colors, so add multiplier and basically multiply to each of the colors.
Lets try setting a value to 10...

Okay cool now it looks really pretty.
I will finish this now, but it was a fun topic to learn.


Top comments (0)