brot.c in git
BROT(1)                 FreeBSD General Commands Manual                BROT(1)

NAME
     brot – Mandelbrot renderer

SYNOPSIS
     brot [-i depth] [-s samples] [-t a+bi] [-f a+bi]

DESCRIPTION
     brot renders the Mandelbrot set in grayscale.

     The arguments are as follows:

     -f a+bi
             Set complex transform factor.

     -i depth
             Set number of iterations.

     -s samples
             Set number of samples per pixel.

     -t a+bi
             Set complex translation term.

     The interactive keys are as follows:

     q       Quit.

     ,.      Adjust depth.

     []      Adjust samples.

     hl      Adjust real component of translation.

     kj      Adjust imaginary componenent of translation.

     ui      Adjust rotation.

     -+      Adjust scale.

     0       Reset translation and transform.

FreeBSD 12.0-RELEASE-p4        September 8, 2018       FreeBSD 12.0-RELEASE-p4
/* Copyright (C) 2018  C. McEnroe <june@causal.agency>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <complex.h>
#include <err.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <time.h>
#include <unistd.h>

#include "gfx.h"

static uint32_t rgb(uint8_t r, uint8_t g, uint8_t b) {
	return (uint32_t)r << 16 | (uint32_t)g << 8 | (uint32_t)b;
}
static uint32_t gray(uint8_t n) {
	return rgb(n, n, n);
}

static double absSq(double complex z) {
	return creal(z) * creal(z) + cimag(z) * cimag(z);
}

static uint32_t depth = 50;

static uint32_t mandelbrot(double complex c) {
	double complex z = 0;
	for (uint32_t i = 0; i < depth; ++i) {
		if (absSq(z) > 4.0) return i;
		z = z * z + c;
	}
	return 0;
}

static double complex translate = -0.75;
static double complex transform = 2.5;

static uint32_t samples = 1;

static void sample(uint32_t *buf, size_t width, size_t height) {
	double yRatio = (height > width) ? (double)height / (double)width : 1.0;
	double xRatio = (width > height) ? (double)width / (double)height : 1.0;

	memset(buf, 0, 4 * width * height);
	size_t superWidth = width * samples;
	size_t superHeight = height * samples;
	for (size_t y = 0; y < superHeight; ++y) {
		for (size_t x = 0; x < superWidth; ++x) {
			double zx = (((double)x + 0.5) / (double)superWidth - 0.5) * xRatio;
			double zy = (((double)y + 0.5) / (double)superHeight - 0.5) * yRatio;
			uint32_t n = mandelbrot((zx + zy * I) * transform + translate);
			buf[(y / samples) * width + (x / samples)] += n;
		}
	}
}

static void color(uint32_t *buf, size_t width, size_t height) {
	for (size_t i = 0; i < width * height; ++i) {
		buf[i] = gray(255 * buf[i] / samples / samples / depth);
	}
}

static double frameTime;
void draw(uint32_t *buf, size_t width, size_t height) {
	clock_t t0 = clock();
	sample(buf, width, height);
	color(buf, width, height);
	frameTime = (double)(clock() - t0) / (double)CLOCKS_PER_SEC;
}

static double translateStep = 1.0 / 128.0;
static double rotateStep = 1.0 / 128.0;
static double scaleStep = 1.0 / 32.0;

bool input(char in) {
	const double PI = acos(-1.0);
	switch (in) {
		break; case 'q': return false;
		break; case '.': depth++;
		break; case ',': if (depth > 1) depth--;
		break; case 'l': translate += translateStep * transform;
		break; case 'h': translate -= translateStep * transform;
		break; case 'j': translate += translateStep * I * transform;
		break; case 'k': translate -= translateStep * I * transform;
		break; case 'u': transform *= cexp(rotateStep * PI * I);
		break; case 'i': transform /= cexp(rotateStep * PI * I);
		break; case '+': transform *= 1.0 - scaleStep;
		break; case '-': transform /= 1.0 - scaleStep;
		break; case '0': translate = -0.75; transform = 2.5;
		break; case ']': samples++;
		break; case '[': if (samples > 1) samples--;
	}
	return true;
}

const char *status(void) {
	static char buf[256];
	snprintf(
		buf, sizeof(buf),
		"brot -s %u -i %u -t %g%+gi -f %g%+gi # %.6f",
		samples, depth,
		creal(translate), cimag(translate),
		creal(transform), cimag(transform),
		frameTime
	);
	return buf;
}

static double complex parseComplex(const char *str) {
	double real = 0.0, imag = 0.0;
	real = strtod(str, (char **)&str);
	if (str[0] == 'i') {
		imag = real;
		real = 0.0;
	} else if (str[0]) {
		imag = strtod(str, NULL);
	}
	return real + imag * I;
}

int init(int argc, char *argv[]) {
	int opt;
	while (0 < (opt = getopt(argc, argv, "f:i:s:t:"))) {
		switch (opt) {
			break; case 'f': transform = parseComplex(optarg);
			break; case 'i': depth = strtoul(optarg, NULL, 0);
			break; case 's': samples = strtoul(optarg, NULL, 0);
			break; case 't': translate = parseComplex(optarg);
			break; default: return EX_USAGE;
		}
	}
	return EX_OK;
}