mandelbrot

mandelbrot.git
git clone git://git.lenczewski.org/mandelbrot.git
Log | Files | Refs

commit c53860987a368f8e2f80c722c7b0bd48631e69f9
parent c55134c703ae3a7384f96c0290c15ebf444bac09
Author: MikoĊ‚aj Lenczewski <mblenczewski@gmail.com>
Date:   Sat, 18 Jan 2025 22:51:50 +0000

Implement basic cpu mandelbrot renderer

Diffstat:
Mbuild.sh | 2+-
Msrc/main.c | 273++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/mandelbrot.h | 32++++++++++++++++++++++++++++++++
3 files changed, 304 insertions(+), 3 deletions(-)

diff --git a/build.sh b/build.sh @@ -6,7 +6,7 @@ WARNINGS="-Wall -Wextra -Wpedantic ${WERROR:+-Werror}" CFLAGS="-std=c11 -g -O0" CPPFLAGS="" -LDFLAGS="" +LDFLAGS="-lm" set -ex diff --git a/src/main.c b/src/main.c @@ -1,9 +1,278 @@ +#define _XOPEN_SOURCE 700 + #include "mandelbrot.h" +#include <getopt.h> + +struct opts opts = { + .verbose = 0, + .xres = 800, .yres = 600, + .max_iters = 100, + .palette = PALETTE_SMOOTH, + .outfile = "/tmp/mandelbrot.bmp", +}; + +static void +usage(char *prog) +{ + fprintf(stderr, "Usage: %s [-hv] [-r <xres>x<yres> ] [-i <maxiters>] " + "[-p mono|gray|hist|smooth ] [-o <out.bmp>]\n", + prog); + + fprintf(stderr, "\t-h : display help information\n"); + fprintf(stderr, "\t-v : enable verbose logging\n"); + fprintf(stderr, "\t-r : set output resolution (default: 800x600)\n"); + fprintf(stderr, "\t-i : set maximum iterations per pixel (default: 100)\n"); + fprintf(stderr, "\t-p : set colour palette (default: smooth)\n"); + fprintf(stderr, "\t-o : set output filename (default: /tmp/mandelbrot.bmp)\n"); +} + +static int +parse_opts(int argc, char **argv, struct opts *opts) +{ + int opt; + while ((opt = getopt(argc, argv, OPTSTR)) > 0) { + switch (opt) { + case 'v': + opts->verbose = 1; + break; + + case 'r': { + char *saveptr; + char *xres = strtok_r(optarg, "x", &saveptr); + char *yres = strtok_r(NULL, "x", &saveptr); + + if (!xres || !yres) { + fprintf(stderr, "Must provide resolution in format: <xres>x<yres>\n"); + return -1; + } + + if ((opts->xres = strtoull(xres, NULL, 10)) == 0) { + fprintf(stderr, "Failed to parse x-resolution: %s\n", xres); + return -1; + } + + if ((opts->yres = strtoull(yres, NULL, 10)) == 0) { + fprintf(stderr, "Failed to parse y-resolution: %s\n", yres); + return -1; + } + } break; + + case 'i': + if ((opts->max_iters = strtoull(optarg, NULL, 10)) == 0) { + fprintf(stderr, "Failed to parse max iters: %s\n", optarg); + return -1; + } + break; + + case 'p': + if (strcmp(optarg, "mono") == 0) { + opts->palette = PALETTE_MONOCHROME; + } else if (strcmp(optarg, "gray") == 0) { + opts->palette = PALETTE_GRAYSCALE; + } else if (strcmp(optarg, "hist") == 0) { + opts->palette = PALETTE_HISTOGRAM; + } else if (strcmp(optarg, "smooth") == 0) { + opts->palette = PALETTE_SMOOTH; + } else { + fprintf(stderr, "Unknown palette: %s\n", optarg); + return -1; + } + break; + + case 'o': + opts->outfile = optarg; + break; + + default: + return -1; + } + } + + return 0; +} + +static inline uint32_t +pixel(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return (uint32_t) a << 24 | + (uint32_t) r << 16 | + (uint32_t) g << 8 | + (uint32_t) b; +} + +#define LERP(v0, v1, t) (((v0) * (1 - (t))) + ((v1) * (t))) + +static inline uint32_t +point(float x, float y, uint32_t max_iters, float *out) +{ + // z = 0+0i + // c = x + i*y + // z_i = z_j ^ 2 + c + // = (re_j + i * im_j) ^ 2 + (x + i * y) + // = ((re_j + i * im_j) * (re_j + i * im_j)) + (x + i * y) + // = (re_j^2 + 2 * (re_j * i * im_j) + (i^2 + im_j^2)) + (x + i * y) + // = (re_j^2 - im_j^2) + i * (2 * re_j * im_j) + (x + i * y) + // = (re_j^2 - im_j^2 + x) + i * ((2 * re_j * im_j) + y) + + float re = 0.0, im = 0.0, abs2 = 0.0; + + uint32_t i = 0; + while (abs2 < 4.0 && i < max_iters) { + float new_re = (re * re) - (im * im) + x; + float new_im = (2 * re * im) + y; + + abs2 = (new_re * new_re) + (new_im * new_im); + re = new_re; + im = new_im; + + i++; + } + + *out = abs2; + + return i; +} + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define CLAMP(v, v0, v1) MIN(MAX((v), (v0)), (v1)) + +static inline uint32_t +palette(float abs2, uint32_t iters, uint32_t max_iters, enum palette_type type) +{ + float f = (float) iters / max_iters; + + switch (type) { + case PALETTE_MONOCHROME: + return (iters == max_iters) ? pixel(0, 0, 0, 255) + : pixel(255, 255, 255, 255); + + case PALETTE_GRAYSCALE: + return (iters == max_iters) ? pixel(0, 0, 0, 255) + : pixel(f * 255, f * 255, f * 255, 255); + + case PALETTE_HISTOGRAM: { + // TODO: actually implement histogram colouring + + return (iters == max_iters) ? pixel(0, 0, 0, 255) + : pixel(f * 255, f * 255, f * 255, 255); + } break; + + case PALETTE_SMOOTH: { + float smooth_iters = (iters + 1) - (logf(logf(sqrtf(abs2))) / M_LN2); + float sf = CLAMP(smooth_iters / max_iters, 0.0f, 1.0f); + float v = 255 * (1 - sf); + + return (iters == max_iters) ? pixel(0, 0, 0, 255) : pixel(v, v, v, 255); + } break; + } +} + +static void +render_mandelbrot(uint32_t *buf, size_t width, size_t height, + size_t max_iters, enum palette_type palette_type) +{ + float x0 = -2.0, y0 = -1.5, x1 = 0.5, y1 = 1.5; + + for (uint32_t py = 0; py < height; py++) { + float yfrac = ((float) py / height); + float y = LERP(y0, y1, yfrac); + + for (uint32_t px = 0; px < width; px++) { + float xfrac = ((float) px / width); + float x = LERP(x0, x1, xfrac); + + float abs2; + uint32_t iters = point(x, y, max_iters, &abs2); + uint32_t pixel = palette(abs2, iters, max_iters, palette_type); + +#if 0 + printf("(%03u, %03u): %03u iters, 0x%02x%02x%02x%02x\n", + px, py, iters, + ((uint8_t *) &pixel)[0], + ((uint8_t *) &pixel)[1], + ((uint8_t *) &pixel)[2], + ((uint8_t *) &pixel)[3]); +#endif + + buf[py * width + px] = pixel; + } + } +} + +static void +write_bitmap(int fd, uint32_t const *buf, size_t width, size_t height) +{ + uint32_t pixels = width * height; + uint16_t pixel_size = 8 * sizeof *buf; + uint32_t pixel_bytes = pixels * sizeof *buf; + + uint32_t dib_header_size = 40; + uint32_t bitmap_header_size = 14; + uint32_t header_size = bitmap_header_size + dib_header_size; + + uint32_t file_size = header_size + pixel_bytes; + uint32_t reserved = 0; + + int32_t xres = width, yres = -height; + uint16_t planes = 0; + uint32_t compression = 0; + uint32_t xdpi = 0, ydpi = 0; + uint32_t palette = 0, important = 0; + + // bitmap header + write(fd, "BM", 2); + write(fd, &file_size, sizeof file_size); + write(fd, &reserved, sizeof reserved); + write(fd, &header_size, sizeof header_size); + + // dib header: BITMAPINFOHEADER + write(fd, &dib_header_size, sizeof dib_header_size); + write(fd, &xres, sizeof xres); + write(fd, &yres, sizeof yres); + write(fd, &planes, sizeof planes); + write(fd, &pixel_size, sizeof pixel_size); + write(fd, &compression, sizeof compression); + write(fd, &pixel_bytes, sizeof pixel_bytes); + write(fd, &xdpi, sizeof xdpi); + write(fd, &ydpi, sizeof ydpi); + write(fd, &palette, sizeof palette); + write(fd, &important, sizeof important); + + // pixel data + write(fd, buf, pixel_bytes); +} + int main(int argc, char **argv) { - printf("Hello, World!\n"); + if (parse_opts(argc, argv, &opts) < 0) { + usage(argv[0]); + exit(EXIT_FAILURE); + } - return 0; + if (opts.verbose) { + fprintf(stderr, "info: verbose: %d\n", opts.verbose); + fprintf(stderr, "info: resolution: %" PRIu64 "x%" PRIu64 "\n", + opts.xres, opts.yres); + fprintf(stderr, "info: max iters: %" PRIu64 "\n", opts.max_iters); + fprintf(stderr, "info: palette: %d\n", opts.palette); + fprintf(stderr, "info: output: %s\n", opts.outfile); + } + + size_t pixels = opts.xres * opts.yres; + uint32_t *buffer = malloc(pixels * sizeof *buffer); + assert(buffer); + + render_mandelbrot(buffer, opts.xres, opts.yres, opts.max_iters, opts.palette); + + int fd = creat(opts.outfile, 0644); + assert(fd > 0); + + write_bitmap(fd, buffer, opts.xres, opts.yres); + + close(fd); + + exit(EXIT_SUCCESS); } diff --git a/src/mandelbrot.h b/src/mandelbrot.h @@ -2,6 +2,38 @@ #define MANDELBROT_H #include <assert.h> +#include <inttypes.h> +#include <limits.h> +#include <stdint.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <fcntl.h> + +#include <math.h> + +/* =========================================================================== + */ + +enum palette_type { + PALETTE_MONOCHROME, + PALETTE_GRAYSCALE, + PALETTE_HISTOGRAM, + PALETTE_SMOOTH, +}; + +struct opts { + int verbose; + uint64_t xres, yres; + uint64_t max_iters; + enum palette_type palette; + char *outfile; +}; + +#define OPTSTR "hvr:i:p:o:" + +extern struct opts opts; #endif /* MANDELBROT_H */