aboutsummaryrefslogtreecommitdiffstats
path: root/mandle.c
diff options
context:
space:
mode:
Diffstat (limited to 'mandle.c')
-rw-r--r--mandle.c407
1 files changed, 407 insertions, 0 deletions
diff --git a/mandle.c b/mandle.c
new file mode 100644
index 0000000..318675f
--- /dev/null
+++ b/mandle.c
@@ -0,0 +1,407 @@
+#include <complex.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <SDL2/SDL.h>
+
+// Some polyfills for complex numbers
+#if !defined(CMPLX)
+#define CMPLX(x, y) ((double complex)((double)(x) + I * (double)(y)))
+#define CMPLXF(x, y) ((float complex)((float)(x) + I * (float)(y)))
+#define CMPLXL(x, y) \
+ ((long double complex)((long double)(x) + I * (long double)(y)))
+#endif
+
+static uintmax_t mandle(long double complex const c)
+{
+ const uintmax_t max = UINTMAX_MAX;
+ if ((pow(creal(c) - 0.25, 2) + pow(cimag(c), 2)) *
+ (pow(creal(c), 2) + (creal(c) / 2) + pow(cimag(c), 2) -
+ 0.1875) <
+ pow(cimag(c), 2) / 4 ||
+ pow(creal(c) + 1, 2) + pow(cimag(c), 2) < 0.0625) {
+ return 0;
+ }
+ long double complex z = c;
+ long double complex dz = CMPLXL(1, 0);
+
+ for (uintmax_t iteration = 0; iteration <= max; iteration++) {
+ if (pow(creal(dz), 2) + pow(cimag(dz), 2) * pow(cimag(dz), 2) <
+ 1e-300) {
+ break;
+ }
+ if (pow(creal(z), 2) + pow(cimag(z), 2) > 4.0) {
+ return iteration;
+ }
+
+ z = z * z + c;
+ dz *= 2 * z;
+ }
+ return 0;
+}
+
+static inline void putpixel(SDL_Surface *surface, SDL_Point const point,
+ SDL_Color color)
+{
+ if (surface->w <= point.x || surface->h <= point.y) {
+ return;
+ }
+ uint32_t const pixel =
+ SDL_MapRGBA(surface->format, color.r, color.g, color.b, color.a);
+ int bpp = surface->format->BytesPerPixel;
+ if (SDL_MUSTLOCK(surface)) {
+ SDL_LockSurface(surface);
+ }
+
+ uint8_t *p = (uint8_t *)surface->pixels + point.y * surface->pitch +
+ point.x * bpp;
+
+ switch (bpp) {
+ case 1:
+ *p = pixel;
+ break;
+ case 2:
+ *(uint16_t *)p = pixel;
+ break;
+ case 3:
+ if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
+ p[0] = pixel >> 16 & UINT8_MAX;
+ p[1] = pixel >> 8 & UINT8_MAX;
+ p[2] = pixel & UINT8_MAX;
+ } else {
+ p[0] = pixel & UINT8_MAX;
+ p[1] = (pixel >> 8) & UINT8_MAX;
+ p[2] = (pixel >> 16) & UINT8_MAX;
+ }
+ break;
+ case 4:
+ *(uint32_t *)p = pixel;
+ break;
+ }
+
+ if (SDL_MUSTLOCK(surface)) {
+ SDL_UnlockSurface(surface);
+ }
+}
+
+static inline SDL_Color color_scheme(uintmax_t iteration)
+{
+ uint8_t color_scale = iteration & UINT8_MAX;
+ unsigned type = iteration >> 8;
+ unsigned color_mask = 255 * (type % 5) / 4;
+ SDL_Color color = {.r = 0, .b = 0, .g = 0, .a = 255};
+ switch ((type / 5) % 19) {
+ case 0:
+ color = (SDL_Color){
+ .r = 0, .b = color_mask, .g = color_scale, .a = 255};
+ break;
+ case 1:
+ color = (SDL_Color){
+ .r = 255, .b = color_mask, .g = color_scale, .a = 255};
+ break;
+ case 2:
+ color = (SDL_Color){
+ .r = color_mask, .b = 0, .g = color_scale, .a = 255};
+ break;
+ case 3:
+ color = (SDL_Color){
+ .r = color_mask, .b = 255, .g = color_scale, .a = 255};
+ break;
+ case 4:
+ color = (SDL_Color){.r = color_mask,
+ .b = color_mask,
+ .g = color_scale,
+ .a = 255};
+ break;
+ case 5:
+ color = (SDL_Color){
+ .r = 0, .b = color_scale, .g = color_mask, .a = 255};
+ break;
+ case 6:
+ color = (SDL_Color){
+ .r = 255, .b = color_scale, .g = color_mask, .a = 255};
+ break;
+ case 7:
+ color = (SDL_Color){
+ .r = color_mask, .b = color_scale, .g = 0, .a = 255};
+ break;
+ case 8:
+ color = (SDL_Color){
+ .r = color_mask, .b = color_scale, .g = 255, .a = 255};
+ break;
+ case 9:
+ color = (SDL_Color){.r = color_mask,
+ .b = color_scale,
+ .g = color_mask,
+ .a = 255};
+ break;
+ case 10:
+ color = (SDL_Color){
+ .r = color_scale, .b = 0, .g = color_mask, .a = 255};
+ break;
+ case 11:
+ color = (SDL_Color){
+ .r = color_scale, .b = 255, .g = color_mask, .a = 255};
+ break;
+ case 12:
+ color = (SDL_Color){
+ .r = color_scale, .b = color_mask, .g = 0, .a = 255};
+ break;
+ case 13:
+ color = (SDL_Color){
+ .r = color_scale, .b = color_mask, .g = 255, .a = 255};
+ break;
+ case 14:
+ color = (SDL_Color){.r = color_scale,
+ .b = color_mask,
+ .g = color_mask,
+ .a = 255};
+ break;
+ case 15:
+ color = (SDL_Color){.r = color_scale,
+ .b = color_scale,
+ .g = color_mask,
+ .a = 255};
+ break;
+ case 16:
+ color = (SDL_Color){.r = color_scale,
+ .b = color_mask,
+ .g = color_scale,
+ .a = 255};
+ break;
+ case 17:
+ color = (SDL_Color){.r = color_mask,
+ .b = color_scale,
+ .g = color_scale,
+ .a = 255};
+ break;
+ case 18:
+ color = (SDL_Color){.r = color_scale,
+ .b = color_scale,
+ .g = color_scale,
+ .a = 255};
+ break;
+ }
+ return (type / (5 * 19)) % 2 ? color
+ : (SDL_Color){.r = ~color.r,
+ .b = ~color.b,
+ .g = ~color.g,
+ .a = color.a};
+}
+
+struct mandle_thr {
+ SDL_Surface *surface;
+ SDL_Point point;
+ SDL_mutex *mutex;
+ SDL_cond *cond;
+ SDL_mutex *cond_mutex;
+ uint32_t job_id;
+ char sleep_no;
+ bool quit;
+ long double complex centre;
+ long double scale;
+};
+
+static int mandle_thr(void *ptr)
+{
+ struct mandle_thr *data = ptr;
+ uint32_t id = 0;
+ SDL_LockMutex(data->cond_mutex);
+ data->sleep_no++;
+ SDL_CondWait(data->cond, data->cond_mutex);
+ data->sleep_no--;
+ SDL_UnlockMutex(data->cond_mutex);
+ SDL_LockMutex(data->mutex);
+ while (true) {
+ if (data->quit) {
+ SDL_UnlockMutex(data->mutex);
+ return 0;
+ }
+ if (id != data->job_id) {
+ id = data->job_id;
+ }
+ SDL_Point point = data->point;
+ if (data->point.x < data->surface->w) {
+ data->point.x++;
+ } else if (data->point.y < data->surface->h) {
+ data->point.y++;
+ data->point.x = 0;
+ } else {
+ SDL_UnlockMutex(data->mutex);
+ SDL_LockMutex(data->cond_mutex);
+ data->sleep_no++;
+ SDL_CondWait(data->cond, data->cond_mutex);
+ data->sleep_no--;
+ SDL_UnlockMutex(data->cond_mutex);
+ SDL_LockMutex(data->mutex);
+ id = data->job_id;
+ }
+ long double complex z =
+ data->scale * 2.50 *
+ CMPLXL((long double)point.x + 1e-10 - (long double)data->surface->w/2,
+ (long double)point.y + 1e-10 - (long double)data->surface->h/2) /
+ (long double)data->surface->w +
+ data->centre;
+ SDL_UnlockMutex(data->mutex);
+
+ SDL_Color color = color_scheme(mandle(z));
+
+ SDL_LockMutex(data->mutex);
+ putpixel(data->surface, point, color);
+ }
+}
+
+static uint32_t update_screen(uint32_t interval, void *param)
+{
+ SDL_Event event;
+ event.type = SDL_USEREVENT;
+ event.user = (SDL_UserEvent){
+ .type = SDL_USEREVENT,
+ .code = *(uint32_t *)param,
+ .data1 = NULL,
+ .data2 = NULL,
+ };
+
+ SDL_PushEvent(&event);
+ return interval;
+}
+
+int main(int argc, char *argv[argc + 1])
+{
+ (void)argv;
+ SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
+ SDL_Window *window = SDL_CreateWindow("Mandle", SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED, 640, 480,
+ SDL_WINDOW_RESIZABLE);
+ if (!window) {
+ SDL_LogMessage(SDL_LOG_CATEGORY_ERROR, SDL_LOG_PRIORITY_ERROR,
+ "Unable to create window: %s", SDL_GetError());
+ return EXIT_FAILURE;
+ }
+
+ long double complex centre = CMPLXL(-0.765, 0);
+ long double scale = 1;
+ long double complex const org_centre = centre;
+
+ uint32_t TimeEvent = SDL_RegisterEvents(1);
+ SDL_TimerID timer = SDL_AddTimer(40, update_screen, &TimeEvent);
+ SDL_Event event;
+ bool render = true;
+
+ struct mandle_thr data = {
+ .mutex = SDL_CreateMutex(),
+ .cond_mutex = SDL_CreateMutex(),
+ .cond = SDL_CreateCond(),
+ .quit = false,
+ .job_id = 0,
+ };
+
+ SDL_Thread *thread[SDL_GetCPUCount()];
+ for (size_t i = 0; i < sizeof thread / sizeof thread[0]; ++i) {
+ thread[i] = SDL_CreateThread(mandle_thr, "Renderer", &data);
+ if (!thread[i]) {
+ SDL_LogMessage(
+ SDL_LOG_CATEGORY_RENDER, SDL_LOG_PRIORITY_ERROR,
+ "SDL_CreateThread failed: %s\n", SDL_GetError());
+ }
+ }
+
+ while (!data.quit) {
+ SDL_WaitEvent(&event);
+ switch (event.type) {
+ case SDL_QUIT:
+ data.quit = true;
+ break;
+ case SDL_WINDOWEVENT:
+ switch (event.window.event) {
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ render = true;
+ break;
+ }
+ break;
+ case SDL_KEYDOWN:
+ switch (event.key.keysym.sym) {
+ case SDLK_UP:
+ centre -= CMPLXL(0, 0.1 * scale);
+ render = true;
+ break;
+ case SDLK_DOWN:
+ centre += CMPLXL(0, 0.1 * scale);
+ render = true;
+ break;
+ case SDLK_RIGHT:
+ centre += CMPLXL(0.1 * scale, 0);
+ render = true;
+ break;
+ case SDLK_LEFT:
+ centre -= CMPLXL(0.1 * scale, 0);
+ render = true;
+ break;
+ case SDLK_EQUALS:
+ case SDLK_KP_PLUS:
+ scale /= 2;
+ render = true;
+ break;
+ case SDLK_MINUS:
+ scale *= 2;
+ render = true;
+ break;
+ case SDLK_0:
+ centre = org_centre;
+ scale = 1;
+ render = true;
+ break;
+ case SDLK_q: {
+ data.quit = true;
+ break;
+ }
+ case SDLK_F11: {
+ bool flag = SDL_GetWindowFlags(window) &
+ SDL_WINDOW_FULLSCREEN_DESKTOP;
+ SDL_SetWindowFullscreen(
+ window,
+ flag ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP);
+ break;
+ }
+ }
+ break;
+ case SDL_USEREVENT: {
+ SDL_UpdateWindowSurface(window);
+ break;
+ }
+ }
+ if (render) {
+ char title[50] = {0};
+ snprintf(title, sizeof title, "Mandle %LG%+LGi x%Lf",
+ creall(centre), cimagl(centre), 1 / scale);
+ SDL_SetWindowTitle(window, title);
+ SDL_LockMutex(data.mutex);
+ data.job_id++;
+ data.point = (SDL_Point){.x = 0, .y = 0};
+ data.centre = centre;
+ data.surface = SDL_GetWindowSurface(window);
+ data.scale = scale;
+ SDL_FillRect(data.surface, NULL,
+ SDL_MapRGB(data.surface->format, 0, 0, 0));
+ SDL_UnlockMutex(data.mutex);
+ SDL_CondBroadcast(data.cond);
+ render = false;
+ }
+ }
+ SDL_CondBroadcast(data.cond);
+ for (size_t i = 0; i < sizeof thread / sizeof thread[0]; ++i) {
+ SDL_WaitThread(thread[i], NULL);
+ }
+ SDL_RemoveTimer(timer);
+ SDL_DestroyMutex(data.mutex);
+ SDL_DestroyMutex(data.cond_mutex);
+ SDL_DestroyCond(data.cond);
+
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+ return EXIT_SUCCESS;
+}