I have rewritten my C Mandelbrot set image generator, including many suggestions given in my previous question (see: Mandelbrot image generator), as well as some recomendations by a friend.
The total list of additions is as follows:
- Parallelisation (omp.h)
- typedef-ing of structures
- Implementing a better file-type (P6 PPM instead of P3 PPM)
- Extensive use of command line options and arguments (getopt)
- Testing of user-defined regions of the complex plane
- Various bug fixes (possible data leak, weird image dimensions, etc.)
- Generally better coding and layout (more concise and easier to read/understand)
My question is the same as in my last post, what things have I done well here, what things have I done badly, and how can I improve in the future? I would appreciate all and any feedback on both my use of C, and my implementation of the algorithm itself.
Note: for anyone who compiles this code, I have had best results using gcc's -O3 and -fopenmp flags
// mandelbrot.c - generates a .PPM (Portable Pixmap format, P6) file of the mandelbrot set with shading
// Options:
// -f [output file name] (required)
// -h [image height in px] (required)
// -t [max imaginary component]
// -b [min imaginary component]
// -v [max real component]
// -n [min real component]
// Return/exit codes:
// 0 - successful execution
// -1 - argument error
// -2 - file access error
// -3 - image height error
#include <stdio.h>
#include <complex.h>
#include <math.h>
#include <stdlib.h>
#include <omp.h>
#include <ctype.h>
#include <unistd.h>
#define MAX_TESTS 2000
typedef struct
{
unsigned int height;
unsigned int width;
double ymax, ymin;
double xmax, xmin;
char *file_name;
} image_meta;
typedef struct
{
unsigned char red;
unsigned char green;
unsigned char blue;
} colour;
short mandelbrot_test(double complex c); //Calculates the number of iterations of the algorithm required for a given complex number to reach a magnitude >= 2
colour rgb_gen(short iterations); //Generates an RGB value based on the rate of divergence
image_meta image_meta_gen(int argc, char *argv[]); //Generates the meta data for the image by parsing the input arguments
int main(int argc, char *argv[])
{
image_meta image;
image = image_meta_gen(argc, argv);
printf("Image dimensions: %dx%d\n", image.width, image.height);
int xpx, ypx;
double a, b, xdiff, ydiff;
double complex num;
FILE *file;
xdiff = image.xmax - image.xmin;
ydiff = image.ymax - image.ymin;
if((file = fopen(image.file_name, "w")) != NULL)
{
fprintf(file, "P6 %d %d 255\n", image.width, image.height);
colour rgb[image.width];
#pragma omp parallel
for(ypx = 0; ypx < image.height; ypx++)
{
for(xpx = 0; xpx < image.width; xpx++)
{
a = image.xmin + xpx * xdiff / image.width;
b = image.ymax - ypx * ydiff / image.height;
num = a + b * I;
rgb[xpx] = rgb_gen(mandelbrot_test(num));
}
fwrite(rgb, sizeof(colour), image.width, file);
}
fclose(file);
}
else
{
fprintf(stderr, "Unable to access file!\n");
exit(-2);
}
exit(0);
}
short mandelbrot_test(double complex c)
{
double complex x = 0;
double abs = c * conj(c);
if(abs * (8.0 * abs - 3.0) < 3.0 / 32.0 - creal(c)) //Quick test to see if we can bail out early by checking if the number lies within the main cardioid
{
return MAX_TESTS;
}
for(int i = 1; i < MAX_TESTS; i++)
{
x *= x;
x += c;
if(cabs(x) >= 2)
{
return i;
}
}
return MAX_TESTS;
}
colour rgb_gen(short iterations)
{
colour rgb;
int brightness;
if(iterations == MAX_TESTS)
{
rgb.red = 255;
rgb.green = 255;
rgb.blue = 255;
}
else
{
brightness = 256.0 * log2(iterations) / log2(MAX_TESTS - 1);
rgb.red = brightness;
rgb.green = brightness;
rgb.blue = 255;
}
return rgb;
}
image_meta image_meta_gen(int argc, char *argv[])
{
int c;
image_meta image;
image.file_name = NULL;
opterr = 0;
image.xmax = image.ymax = image.xmin = image.ymin = -1;
image.height = 0;
while((c = getopt(argc, argv, "h:f:t:b:v:n:")) != -1)
{
switch(c)
{
case 'h':
image.height = atoi(optarg);
break;
case 't':
image.ymax = atof(optarg);
break;
case 'b':
image.ymin = atof(optarg);
break;
case 'v':
image.xmax = atof(optarg);
break;
case 'n':
image.xmin = atof(optarg);
break;
case 'f':
image.file_name = optarg;
break;
case '?':
if(optopt == 'f' || optopt == 'h')
{
fprintf(stderr, "Option -%c requires an argument.\n", optopt);
}
else if(isprint(optopt))
{
fprintf(stderr, "Unknown option `-%c'.\n", optopt);
}
else
{
fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt);
}
fprintf(stderr, "Usage: ./mandelbrot -f file_name -h height [options]\nOptional: -t y-max -b y-min -v x-min -n x-max\n");
exit(-1);
}
}
if(argc < 4)
{
fprintf(stderr, "Error: too few args\nUsage: ./mandelbrot -f file_name -h height [options]\nOptional: -t y-max -b y-min -v x-min -n x-max\n");
exit(-1);
}
if(image.height < 30)
{
fprintf(stderr, "Height can't be less than 30!\n");
exit(-3);
}
if(image.xmax == image.xmin)
{
image.xmax = 0.8;
image.xmin = -2.0;
printf("Using default x values...\n");
}
if(image.ymax == image.ymin)
{
image.ymax = 1.2;
image.ymin = -1.2;
printf("Using default y values...\n");
}
if(image.xmin > image.xmax)
{
double temp = image.xmin;
image.xmin = image.xmax;
image.xmax = temp;
}
if(image.ymin > image.ymax)
{
double temp = image.ymin;
image.ymin = image.ymax;
image.ymax = temp;
}
image.width = image.height * (image.xmax - image.xmin) / (image.ymax - image.ymin);
return image;
}