/* * render.c - escape-time fractal renderer (Mandelbrot / Julia), C89. * * Colours come from a 256-entry palette: indices 0..254 are the escape * gradient and index 255 is reserved for points inside the set (black). * * Author: DeepSeek (deepseek-v4.1-flash), October 2026. */ #include #include #include "render.h" #include "gifenc.h" #define INTERIOR_INDEX 255 typedef struct { double t; int r, g, b; } Stop; static void ramp_from_stops(unsigned char *pal, const Stop *s, int n) { int i, j; for (i = 0; i < 255; i++) { double t = (double)i / 254.0; int r, g, b; for (j = 0; j < n - 1; j++) if (t >= s[j].t && t <= s[j + 1].t) break; if (j > n - 2) j = n - 2; { double u = (t - s[j].t) / (s[j + 1].t - s[j].t); if (u < 0.0) u = 0.0; if (u > 1.0) u = 1.0; r = (int)(s[j].r + u * (s[j + 1].r - s[j].r)); g = (int)(s[j].g + u * (s[j + 1].g - s[j].g)); b = (int)(s[j].b + u * (s[j + 1].b - s[j].b)); } pal[i * 3 + 0] = (unsigned char)r; pal[i * 3 + 1] = (unsigned char)g; pal[i * 3 + 2] = (unsigned char)b; } pal[255 * 3 + 0] = 0; pal[255 * 3 + 1] = 0; pal[255 * 3 + 2] = 0; } static void hsv_to_rgb(double h, double s, double v, int *r, int *g, int *b) { int i; double f, p, q, t; h = h - floor(h); h *= 6.0; i = (int)h; f = h - i; p = v * (1.0 - s); q = v * (1.0 - s * f); t = v * (1.0 - s * (1.0 - f)); switch (i % 6) { case 0: *r = (int)(v * 255); *g = (int)(t * 255); *b = (int)(p * 255); break; case 1: *r = (int)(q * 255); *g = (int)(v * 255); *b = (int)(p * 255); break; case 2: *r = (int)(p * 255); *g = (int)(v * 255); *b = (int)(t * 255); break; case 3: *r = (int)(p * 255); *g = (int)(q * 255); *b = (int)(v * 255); break; case 4: *r = (int)(t * 255); *g = (int)(p * 255); *b = (int)(v * 255); break; default:*r = (int)(v * 255); *g = (int)(p * 255); *b = (int)(q * 255); break; } } void fractal_palette(int kind, unsigned char *pal) { static const Stop gray[] = { { 0.0, 0, 0, 0 }, { 1.0, 255, 255, 255 } }; static const Stop fire[] = { { 0.00, 0, 0, 0 }, { 0.20, 80, 0, 0 }, { 0.45, 220, 40, 0 }, { 0.70, 255, 190, 0 }, { 1.00, 255, 255, 220 } }; static const Stop ocean[] = { { 0.00, 0, 0, 32 }, { 0.35, 0, 90, 160 }, { 0.70, 0, 200, 210 }, { 1.00, 224, 255, 255 } }; int i; switch (kind) { case PAL_GRAY: ramp_from_stops(pal, gray, (int)(sizeof(gray) / sizeof(gray[0]))); break; case PAL_OCEAN: ramp_from_stops(pal, ocean, (int)(sizeof(ocean) / sizeof(ocean[0]))); break; case PAL_RAINBOW: for (i = 0; i < 255; i++) { int r, g, b; hsv_to_rgb((double)i / 255.0, 1.0, 1.0, &r, &g, &b); pal[i * 3 + 0] = (unsigned char)r; pal[i * 3 + 1] = (unsigned char)g; pal[i * 3 + 2] = (unsigned char)b; } pal[255 * 3 + 0] = 0; pal[255 * 3 + 1] = 0; pal[255 * 3 + 2] = 0; break; case PAL_FIRE: default: ramp_from_stops(pal, fire, (int)(sizeof(fire) / sizeof(fire[0]))); break; } } void fractal_defaults(FractalParams *p) { p->width = 320; p->height = 240; p->center_re = -0.5; p->center_im = 0.0; p->span = 3.0; p->max_iter = 256; p->julia = 0; p->julia_re = -0.4; p->julia_im = 0.6; p->palette = PAL_FIRE; p->map = MAP_CYCLE; p->cycle = 6.0; } static int escape(const FractalParams *p, double cr, double ci, double *mu) { double zr, zi, zr2, zi2, mag2 = 0.0; double cr2, ci2; int n; if (p->julia) { zr = cr; zi = ci; cr2 = p->julia_re; ci2 = p->julia_im; } else { zr = 0.0; zi = 0.0; cr2 = cr; ci2 = ci; } n = 0; while (n < p->max_iter) { zr2 = zr * zr; zi2 = zi * zi; mag2 = zr2 + zi2; if (mag2 > 4.0) break; zi = 2.0 * zr * zi + ci2; zr = zr2 - zi2 + cr2; n++; } if (n >= p->max_iter) { *mu = (double)p->max_iter; return p->max_iter; } { double l = 0.5 * log(mag2); double l2 = log(2.0); if (l > l2) *mu = (double)n + 1.0 - log(l / l2) / l2; else *mu = (double)n; } return n; } int fractal_render(FILE *out, const FractalParams *p) { GifWriter g; unsigned char pal[256 * 3]; unsigned char *row; double wspan, hspan; int px, py; if (p->width <= 0 || p->height <= 0) return -1; fractal_palette(p->palette, pal); if (!gif_begin(&g, out, p->width, p->height, pal)) return -1; row = (unsigned char *)malloc((size_t)p->width); if (row == (unsigned char *)0) return -1; wspan = p->span; hspan = p->span * (double)p->height / (double)p->width; for (py = 0; py < p->height; py++) { double im = p->center_im + ((double)py + 0.5) / (double)p->height * hspan - 0.5 * hspan; for (px = 0; px < p->width; px++) { double re = p->center_re + ((double)px + 0.5) / (double)p->width * wspan - 0.5 * wspan; double mu = 0.0; int n = escape(p, re, im, &mu); int idx; if (n >= p->max_iter) { idx = INTERIOR_INDEX; } else if (p->map == MAP_LINEAR) { idx = (int)(254.0 * (double)n / (double)p->max_iter); } else { double t = mu / (double)p->max_iter; double f; if (!(t >= 0.0) || !(t <= 1.0)) t = 0.0; f = t * p->cycle; f = f - floor(f); idx = (int)(254.0 * f + 0.5); } if (idx < 0) idx = 0; if (idx > 255) idx = 255; row[px] = (unsigned char)idx; } if (!gif_write_row(&g, row)) { free(row); return -1; } } free(row); return gif_end(&g) ? 0 : -1; }