So in a step to learn C++ i decided to try to implement the mandelbrot set using CImg. Here is my program:
#include <cstdlib>
#include <iostream>
#include <complex>
#define cimg_use_png
#include "CImg.h"
using namespace cimg_library;
const int MAX_ITER = 80;
const int WIDTH = 600;
const int HEIGHT = 400;
const int DEPTH = 1;
const int COLOR_SPECTRUM = 3;
const int RE_START = -2;
const int RE_END = 1;
const int IM_START = -1;
const int IM_END = 1;
double mandelbrot(const std::complex<double> &value) {
std::complex<double> z(0, 0);
int n = 0;
while (abs(z) <= 2 && n < MAX_ITER){
z = (z * z) + value;
n += 1;
}
if(n == MAX_ITER) {
return n;
}
return n + 1 - log(log2(norm(z)));
}
int main() {
CImg<int> img(WIDTH, HEIGHT, DEPTH, COLOR_SPECTRUM, 0);
img.RGBtoHSV();
cimg_forXY(img, x, y) {
std::complex<double> complex(RE_START + ((double)x / WIDTH) * (RE_END - RE_START),
IM_START + ((double)y / HEIGHT) * (IM_END - IM_START));
double m = mandelbrot(complex);
int hue = (255 * m / MAX_ITER);
int saturation = 255;
int value = 0;
if (m < MAX_ITER) {
value = 255;
}
int color[] = { hue, saturation, value };
img.draw_point(x, y, color);
}
// img.HSVtoRGB().display("Mandelbrot");
img.HSVtoRGB().save_png("output/test.png");
return 0;
}
I manage to draw out the classic mandelbrot shape, but then i tried implementing smoothing to get rid of the ugly banding. But i cant get it to really work.
And seriously tbh i have no idea what i'm doing because math is hard.
I also get this warning when its saving the image
[CImg] *** Warning ***[instance(600,400,1,3,0x104800000,non-shared)]
CImg<int>::save_png(): Instance has pixel values in
[-1.65164e+07,65025], probable type overflow in file 'output/test.png'.
so apparently i have an overflow somewhere but i can't really tell where.
If anyone could help me and explain what I'm doing, and point out all the wrong things i have done but in a simple way, i would be forever grateful.
UPDATE
so by adding a normalization step i managed to get a better image:
img.HSVtoRGB()
.normalize(0, 255)
.save_png("output/test.png");
And the overflow error is gone.
But there seems to be something wrong in the conversion between HSV and RGB.
Everything that is white should be black.
The HSV values i'm writing are 255, 255, 0
but it gets converted to 255, 255, 255 in RGB which is wrong. It should get converted to black.
UPDATE 2
As Bob informed me Hue and Saturation is not a number between 0..100 it's represented by a number between 0..1.
That was my main misstake and why i got an overflow and the blacks became white. By correcting that and doing the small fixes Bob suggested i now get:
In the HSV color model, the hue is usually expressed as an angle in degrees [0°, 360°), while the saturation and value are in the range [0, 1].
A way to fix the posted code is to adjust the values and their type stored in img
.
CImg<double> img(WIDTH, HEIGHT, DEPTH, COLOR_SPECTRUM, 0);
// ^^^^^^ I guess a float could be enough
// img.RGBtoHSV(); Here, is useless. CImg has no notion of the color space
cimg_forXY(img, x, y)
{
std::complex<double> c(/* Get real and imaginary part from x and y */);
double m = mandelbrot(c);
double color[] = {
// ^^^^^^
360.0 * m / MAX_ITER, // hue
1.0, // saturation
(m < MAX_ITER) ? 1.0 : 0.0 // value
};
img.draw_point(x, y, color);
}
img.HSVtoRGB().normalize(0, 255).save_png("output/test.png");
Also note that in mandelbrot()
you could use
while (std::norm(z) <= 4 && n < MAX_ITER) //
{ // ^^^^^^^^^^^^^^^^^ To avoid a sqrt at every iteration
// See e.g. https://linas.org/art-gallery/escape/smooth.html for this:
return n + 1 - std::log2( std::log( std::norm(z) ) * 0.5 );
}