6
\$\begingroup\$

As an exercise, I decided to try implementing a Mandelbrot Set viewer that produces ASCII images.

Small Example:

                                                           ..                                       
                                                          .......                                   
                                                         ...........                                
                                                        .............                               
                                                       ..............                               
                                                   ..........###.......                             
                                               ............######!........                          
                                            ...............%#####...............                    
                                          ...........!!.!.%#######..!..%.........                   
                                        .........##.%###################!..#.....                   
                                      ............###########################.....                  
                        ......   ...........!...############################!.....                  
                      ........................###############################......                 
                     .......................####################################!..                 
                    ........#....#.........####################################!...                 
                   .........!#########....!#####################################!..                 
              ............##############%.#####################################...                  
         ................!###############.####################################....                  
   ...................#######################################################.....                  
   ...................#######################################################.....                  
         ................!###############.####################################....                  
              ............##############%.#####################################...                  
                   .........!#########....!#####################################!..                 
                    ........#....#.........####################################!...                 
                     .......................####################################!..                 
                      ........................###############################......                 
                        ......   ...........!...############################!.....                  
                                      ............###########################.....                  
                                        .........##.%###################!..#.....                   
                                          ...........!!.!.%#######..!..%.........                   
                                            ...............%#####...............                    
                                               ............######!........                          
                                                   ..........###.......                             
                                                       ..............                               
                                                        .............                               
                                                         ...........                                
                                                          .......                                   
                                                           ..  

Huge Example (You may want to zoom out.)

I ended up playing around with a few new things here:

  • I decided to try limiting my use of pointers to only the cases where it's absolutely necessary (like set_complex). I decided cleanliness was better than performance, and it seems to perform well anyways; even though it's using copies everywhere.

  • I opted to using snake_case, as that seems to be semi-idiomatic for C, and is far more readable than camelCase or lowercase.

  • I'm doing (very simple) file operations for the first time.

  • I decided to wrap malloc and calloc in terminating_ wrapper functions that handle checking the returned pointer; terminating with a message if it's NULL.

I'd like thoughts on anything here, though I'm especially interested in:

  • Is there a better way of writing char_for_iters? The bulky branching seems less than ideal.

  • I've gotten a few suggestions that I should be using 1 instead of sizeof(char). Is this really necessary/preferred though? I find I like the explicitness of having the type specified; even if it's not necessary.

  • Are my terminating_ malloc and calloc functions at all a common idea? Or is it far more typical to have the handling inline in the code?



helpers.h

#ifndef HELPERS_H
#define HELPERS_H

#include <stdlib.h>

// Prints an error message to stderr if ptr is NULL
// Message is in the form "Could not allocate space for %s.".
void ensure_allocation(const void* ptr, const char* allocation_reason);

// Attempts to allocate the requested amount of memory and asserts the validity of the
//  returned pointer using ensure_allocation before returning
void* terminating_malloc(size_t bytes, const char* allocation_reason);
void* terminating_calloc(size_t count, size_t bytes_per, const char* allocation_reason);

#endif

helpers.c

#include <stdio.h>
#include <stdlib.h>

#include "helpers.h"

void ensure_allocation(const void* ptr, const char* allocation_reason) {
    if (!ptr) {
        fprintf(stderr, "Could not allocate space for %s.", allocation_reason);
        exit(EXIT_FAILURE);
    }
}

void* terminating_malloc(size_t bytes, const char* allocation_reason) {
    void* ptr = malloc(bytes);

    ensure_allocation(ptr, allocation_reason);

    return ptr;
}

void* terminating_calloc(size_t count, size_t bytes_per, const char* allocation_reason) {
    void* ptr = calloc(count, bytes_per);

    ensure_allocation(ptr, allocation_reason);

    return ptr;
}

complex.h

#ifndef COMPLEX_H
#define COMPLEX_H

typedef struct Complex {
    double real;
    double imaginary;

} Complex;

void set_complex(Complex* c, double real, double imaginary);
Complex new_complex(double real, double imaginary);
Complex copy_complex(Complex src);

// Overwrites out with the result of squaring c.
Complex square_complex(Complex c);

#endif

complex.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "complex.h"
#include "helpers.h"

// TODO: This should probably all be moved to the header for performance reasons

void set_complex(Complex* c, double real, double imaginary) {
    c->real = real;
    c->imaginary = imaginary;
}

Complex new_complex(double real, double imaginary) {
    Complex c;
    set_complex(&c, real, imaginary);

    return c;
}

Complex copy_complex(Complex src) {
    Complex copy;
    copy.real = src.real;
    copy.imaginary = src.imaginary;

    return copy;
}

Complex square_complex(Complex c) {
    double real = (c.real * c.real) - (c.imaginary * c.imaginary);
    double imaginary = 2 * c.real * c.imaginary;

    return new_complex(real, imaginary);
}

iteraton.h

#ifndef ITERATION_H
#define ITERATION_H

#include <stdlib.h>

#include "complex.h"

// Can be lowered to sacrifice accuracy for speed
#define STD_MAX_ITERATIONS 200

// Once this is exceeded, a number is bound to head off into infinity
#define STD_INFINITY_LIMIT 2 

// Test how many iterations it takes for c to go to infinity when iterated
size_t test_point(Complex c, size_t max_iteration, size_t infinity_limit);

#endif

iteration.c

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#include "iteration.h"
#include "complex.h"

// Returns the result of iterating current_complex once using the standard Mandelbrot iteration method
static Complex mandelbrot_iteration(
    Complex initial_complex,
    Complex current_complex) {

    Complex sqrd = square_complex(current_complex);

    return new_complex(
                sqrd.real + initial_complex.real,
                sqrd.imaginary + initial_complex.imaginary);
}

static bool is_under_limit(Complex c, size_t infinity_limit) {
    // Numbers must be positive as they're being squared.
    return (size_t)((c.real * c.real) + (c.imaginary * c.imaginary))
                    <= (infinity_limit * infinity_limit);
}

size_t test_point(Complex c, size_t max_iteration, size_t infinity_limit) {
    Complex current_c = copy_complex(c);

    size_t i;
    for (i = 0; i < max_iteration; i++) {
        if (is_under_limit(current_c, infinity_limit)) {
            current_c = mandelbrot_iteration(c, current_c);

        } else {
            break;
        }
    }

    return i;
}

display is "text image" specific portion of the code. When/if I adapt the code to produce actual images, I'd write a separate display file to produce images instead of text.

display.h

#ifndef DISPLAY_H
#define DISPLAY_H

#include <stdlib.h>
#include <stdio.h>

// Produces a formatted string representing a view of the Mandelbrot Set
char* format_mandelbrot_view(double lower_real,
                             double upper_real,
                             double lower_imag,
                             double upper_imag,
                             size_t chars_wide,
                             size_t chars_high);

// Prints a view returned by format_mandelbrot_view to the given file stream
void print_mandelbrot_view(FILE* stream,
                           double lower_real,
                           double upper_real,
                           double lower_imag,
                           double upper_imag,
                           size_t chars_wide,
                           size_t chars_high);

#endif

display.c

#include <stdlib.h>
#include <stdio.h>

#include "helpers.h"
#include "iteration.h"

static char char_for_iters(size_t iters) {
    if (iters >= 200) {
        return '#';

    } else if (iters >= 150) {
        return '@';

    } else if (iters >= 100) {
        return '%';

    } else if (iters >= 50) {
        return '!';

    } else if (iters >= 5) {
        return '.';

    } else {
        return ' ';
    }
}

char* format_mandelbrot_view(double lower_real,
                             double upper_real,
                             double lower_imag,
                             double upper_imag,
                             size_t chars_wide,
                             size_t chars_high) {

    size_t buffer_size = (chars_wide * chars_high) + chars_high + 1;
    char* buffer = terminating_calloc(buffer_size, sizeof(char), "complex format buffer");

    double comp_width = upper_real - lower_real;
    double comp_height = upper_imag - lower_imag;

    double real_step = comp_width / (chars_wide - 1);
    double imag_step = comp_height / (chars_high - 1);

    size_t i = 0;
    for (double y = lower_imag; y <= upper_imag && i < buffer_size - 1; y += imag_step) {
        for (double x = lower_real; x <= upper_real && i < buffer_size - 1; x += real_step) {
            size_t iters = test_point(new_complex(x, y),
                                      STD_MAX_ITERATIONS, STD_INFINITY_LIMIT);

            buffer[i] = char_for_iters(iters);

            i++;
        }

        buffer[i] = '\n';

        i++;
    }

    return buffer;
}

void print_mandelbrot_view(FILE* stream,
                           double lower_real,
                           double upper_real,
                           double lower_imag,
                           double upper_imag,
                           size_t chars_wide,
                           size_t chars_high) {

    char* formatted = format_mandelbrot_view(lower_real, upper_real,
                                             lower_imag, upper_imag,
                                             chars_wide, chars_high);

    fprintf(stream, "%s\n", formatted);

    free(formatted);
}

main.c

#include <stdio.h>
#include <stdlib.h>

#include "display.h"

#define OUTPUT_PATH "./mandelbrot_output.txt"

void save_view_at(double lower_real,
                  double upper_real,
                  double lower_imag,
                  double upper_imag,
                  size_t image_width) {

    FILE* file = fopen(OUTPUT_PATH, "w+");

    if (file) {
        print_mandelbrot_view(file,
                              lower_real, upper_real,
                              lower_imag, upper_imag,
                              // Halving the height because it looks best when
                              //  width is 2 * height.
                              image_width, (size_t)(image_width / 2));

        fclose(file);

    } else {
        printf("Cannot open file at %s", OUTPUT_PATH);

    }

}

int main() {
    save_view_at(-2, 1, -1.5, 1.5, 500);

    printf("Saved...\n");

    return 0;
}
\$\endgroup\$
  • \$\begingroup\$ I just realized that copy_complex can simply return the parameter since I only need a shallow copy anyways. Is this at all typical? \$\endgroup\$ – Carcigenicate Apr 17 at 12:19
  • \$\begingroup\$ c has complex numbers which might be helpful: en.cppreference.com/w/c/numeric/complex \$\endgroup\$ – sudo rm -rf slash Apr 20 at 21:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.