summaryrefslogtreecommitdiffstats
path: root/plug-ins/flame/libifs.c
diff options
context:
space:
mode:
Diffstat (limited to 'plug-ins/flame/libifs.c')
-rw-r--r--plug-ins/flame/libifs.c1487
1 files changed, 1487 insertions, 0 deletions
diff --git a/plug-ins/flame/libifs.c b/plug-ins/flame/libifs.c
new file mode 100644
index 0000000..461ac5c
--- /dev/null
+++ b/plug-ins/flame/libifs.c
@@ -0,0 +1,1487 @@
+/*
+ flame - cosmic recursive fractal flames
+ Copyright (C) 1992 Scott Draves <spot@cs.cmu.edu>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h> /* strcmp */
+
+#include "libgimp/gimp.h"
+
+#include "libifs.h"
+
+#define CHOOSE_XFORM_GRAIN 100
+
+static int flam3_random_bit (void);
+static double flam3_random01 (void);
+
+/*
+ * run the function system described by CP forward N generations.
+ * store the n resulting 3 vectors in POINTS. the initial point is passed
+ * in POINTS[0]. ignore the first FUSE iterations.
+ */
+
+void
+iterate (control_point *cp,
+ int n,
+ int fuse,
+ point *points)
+{
+ int i, j, count_large = 0, count_nan = 0;
+ int xform_distrib[CHOOSE_XFORM_GRAIN];
+ double p[3], t, r, dr;
+ p[0] = points[0][0];
+ p[1] = points[0][1];
+ p[2] = points[0][2];
+
+ /*
+ * first, set up xform, which is an array that converts a uniform random
+ * variable into one with the distribution dictated by the density
+ * fields
+ */
+ dr = 0.0;
+ for (i = 0; i < NXFORMS; i++)
+ dr += cp->xform[i].density;
+ dr = dr / CHOOSE_XFORM_GRAIN;
+
+ j = 0;
+ t = cp->xform[0].density;
+ r = 0.0;
+ for (i = 0; i < CHOOSE_XFORM_GRAIN; i++)
+ {
+ while (r >= t)
+ {
+ j++;
+ t += cp->xform[j].density;
+ }
+ xform_distrib[i] = j;
+ r += dr;
+ }
+
+ for (i = -fuse; i < n; i++)
+ {
+ /* FIXME: the following is supported only by gcc and c99 */
+ int fn = xform_distrib[g_random_int_range (0, CHOOSE_XFORM_GRAIN)];
+ double tx, ty, v;
+
+ if (p[0] > 100.0 || p[0] < -100.0 ||
+ p[1] > 100.0 || p[1] < -100.0)
+ count_large++;
+ if (p[0] != p[0])
+ count_nan++;
+
+#define coef cp->xform[fn].c
+#define vari cp->xform[fn].var
+
+ /* first compute the color coord */
+ p[2] = (p[2] + cp->xform[fn].color) / 2.0;
+
+ /* then apply the affine part of the function */
+ tx = coef[0][0] * p[0] + coef[1][0] * p[1] + coef[2][0];
+ ty = coef[0][1] * p[0] + coef[1][1] * p[1] + coef[2][1];
+
+ p[0] = p[1] = 0.0;
+ /* then add in proportional amounts of each of the variations */
+ v = vari[0];
+ if (v > 0.0)
+ {
+ /* linear */
+ double nx, ny;
+ nx = tx;
+ ny = ty;
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[1];
+ if (v > 0.0)
+ {
+ /* sinusoidal */
+ double nx, ny;
+ nx = sin (tx);
+ ny = sin (ty);
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[2];
+ if (v > 0.0)
+ {
+ /* spherical */
+ double nx, ny;
+ double r2 = tx * tx + ty * ty + 1e-6;
+ nx = tx / r2;
+ ny = ty / r2;
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[3];
+ if (v > 0.0)
+ {
+ /* swirl */
+ double r2 = tx * tx + ty * ty; /* /k here is fun */
+ double c1 = sin (r2);
+ double c2 = cos (r2);
+ double nx = c1 * tx - c2 * ty;
+ double ny = c2 * tx + c1 * ty;
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[4];
+ if (v > 0.0)
+ {
+ /* horseshoe */
+ double a, c1, c2, nx, ny;
+ if (tx < -EPS || tx > EPS ||
+ ty < -EPS || ty > EPS)
+ a = atan2(tx, ty); /* times k here is fun */
+ else
+ a = 0.0;
+ c1 = sin (a);
+ c2 = cos (a);
+ nx = c1 * tx - c2 * ty;
+ ny = c2 * tx + c1 * ty;
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[5];
+ if (v > 0.0)
+ {
+ /* polar */
+ double nx, ny;
+ if (tx < -EPS || tx > EPS ||
+ ty < -EPS || ty > EPS)
+ nx = atan2 (tx, ty) / G_PI;
+ else
+ nx = 0.0;
+
+ ny = sqrt (tx * tx + ty * ty) - 1.0;
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[6];
+ if (v > 0.0)
+ {
+ /* bent */
+ double nx, ny;
+ nx = tx;
+ ny = ty;
+ if (nx < 0.0) nx = nx * 2.0;
+ if (ny < 0.0) ny = ny / 2.0;
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[7];
+ if (v > 0.0)
+ {
+ /* folded handkerchief */
+ double theta, r2, nx, ny;
+ if (tx < -EPS || tx > EPS ||
+ ty < -EPS || ty > EPS)
+ theta = atan2( tx, ty );
+ else
+ theta = 0.0;
+ r2 = sqrt (tx * tx + ty * ty);
+ nx = sin (theta + r2) * r2;
+ ny = cos (theta - r2) * r2;
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[8];
+ if (v > 0.0)
+ {
+ /* heart */
+ double theta, r2, nx, ny;
+ if (tx < -EPS || tx > EPS ||
+ ty < -EPS || ty > EPS)
+ theta = atan2( tx, ty );
+ else
+ theta = 0.0;
+ r2 = sqrt (tx * tx + ty * ty);
+ theta *= r2;
+ nx = sin (theta) * r2;
+ ny = cos (theta) * -r2;
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[9];
+ if (v > 0.0)
+ {
+ /* disc */
+ double theta, r2, nx, ny;
+ if ( tx < -EPS || tx > EPS ||
+ ty < - EPS || ty > EPS)
+ theta = atan2 (tx, ty);
+ else
+ theta = 0.0;
+ nx = tx * G_PI;
+ ny = ty * G_PI;
+ r2 = sqrt (nx * nx * ny * ny);
+ p[0] += v * sin(r2) * theta / G_PI;
+ p[1] += v * cos(r2) * theta / G_PI;
+ }
+
+ v = vari[10];
+ if (v > 0.0)
+ {
+ /* spiral */
+ double theta, r2;
+ if (tx < -EPS || tx > EPS ||
+ ty < -EPS || ty > EPS)
+ theta = atan2( tx, ty );
+ else
+ theta = 0.0;
+ r2 = sqrt (tx * tx + ty * ty) + 1e-6;
+ p[0] += v * (cos (theta) + sin (r2)) / r2;
+ p[1] += v * (cos (theta) + cos (r2)) / r2;
+ }
+
+ v = vari[11];
+ if (v > 0.0)
+ {
+ /* hyperbolic */
+ double theta, r2;
+ if (tx < -EPS || tx > EPS ||
+ ty < -EPS || ty > EPS)
+ theta = atan2 (tx, ty);
+ else
+ theta = 0.0;
+ r2 = sqrt (tx * tx + ty * ty) + 1e-6;
+ p[0] += v * sin (theta) / r2;
+ p[1] += v * cos (theta) * r2;
+ }
+
+ v = vari[12];
+ if (v > 0.0 )
+ {
+ double theta, r2;
+ /* diamond */
+ if ( tx < -EPS || tx > EPS ||
+ ty < -EPS || ty > EPS)
+ theta = atan2 (tx, ty);
+ else
+ theta = 0.0;
+ r2 = sqrt( tx * tx + ty * ty );
+ p[0] += v * sin (theta) * cos (r2);
+ p[1] += v * cos (theta) * sin (r2);
+ }
+
+ v = vari[13];
+ if (v > 0.0)
+ {
+ /* ex */
+ double theta, r2, n0, n1, m0, m1;
+ if ( tx < -EPS || tx > EPS ||
+ ty < -EPS || ty > EPS)
+ theta = atan2 (tx, ty);
+ else
+ theta = 0.0;
+ r2 = sqrt( tx * tx + ty * ty );
+ n0 = sin(theta + r2);
+ n1 = cos(theta - r2);
+ m0 = n0 * n0 * n0 * r2;
+ m1 = n1 * n1 * n1 * r2;
+ p[0] += v * (m0 + m1);
+ p[1] += v * (m0 - m1);
+ }
+
+ v = vari[14];
+ if ( v > 0.0)
+ {
+ double theta, r2, nx, ny;
+ /* julia */
+ if (tx < -EPS || tx > EPS ||
+ ty < -EPS || ty > EPS)
+ theta = atan2 (tx, ty);
+ else
+ theta = 0.0;
+ if (flam3_random_bit ())
+ theta += G_PI;
+ r2 = pow (tx * tx + ty * ty, 0.25);
+ nx = r2 * cos (theta);
+ ny = r2 * sin (theta);
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[15];
+ if (v > 0.0)
+ {
+ /* waves */
+ double dx, dy, nx, ny;
+ dx = coef[2][0];
+ dy = coef[2][1];
+ nx = tx + coef[1][0] * sin (ty / ((dx * dx) + EPS));
+ ny = ty + coef[1][1] * sin (tx / ((dy * dy) + EPS));
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[16];
+ if (v > 0.0)
+ {
+ /* fisheye */
+ double theta, r2, nx, ny;
+ if (tx < -EPS || tx > EPS ||
+ ty < -EPS || ty > EPS)
+ theta = atan2 (tx, ty);
+ else
+ theta = 0.0;
+ r2 = sqrt (tx * tx + ty * ty);
+ r2 = 2 * r2 / (r2 + 1);
+ nx = r2 * cos (theta);
+ ny = r2 * sin (theta);
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[17];
+ if (v > 0.0)
+ {
+ /* popcorn */
+ double dx, dy, nx, ny;
+ dx = tan (3 * ty);
+ dy = tan (3 * tx);
+ nx = tx + coef[2][0] * sin (dx);
+ ny = ty + coef[2][1] * sin (dy);
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[18];
+ if (v > 0.0)
+ {
+ /* exponential */
+ double dx, dy, nx, ny;
+ dx = exp (tx - 1.0);
+ dy = G_PI * ty;
+ nx = cos (dy) * dx;
+ ny = sin (dy) * dx;
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[19];
+ if (v > 0.0)
+ {
+ /* power */
+ double theta, r2, tsin, tcos, nx, ny;
+ if (tx < -EPS || tx > EPS ||
+ ty < -EPS || ty > EPS)
+ theta = atan2 (tx, ty);
+ else
+ theta = 0.0;
+ tsin = sin (theta);
+ tcos = cos (theta);
+ r2 = sqrt (tx * tx + ty * ty);
+ r2 = pow (r2, tsin);
+ nx = r2 * tcos;
+ ny = r2 * tsin;
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[20];
+ if (v > 0.0)
+ {
+ /* cosine */
+ double nx, ny;
+ nx = cos (tx * G_PI) * cosh (ty);
+ ny = -sin (tx * G_PI) * sinh (ty);
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[21];
+ if (v > 0.0)
+ {
+ /* rings */
+ double theta, r2, dx, nx, ny;
+ if (tx < -EPS || tx > EPS ||
+ ty < -EPS || ty > EPS)
+ theta = atan2 (tx, ty);
+ else
+ theta = 0;
+ dx = coef[2][0];
+ dx = dx * dx + EPS;
+ r2 = sqrt (tx * tx + ty * ty);
+ r2 = fmod (r2 + dx, 2 * dx) - dx + r2 * (1 - dx);
+ nx = cos (theta) * r2;
+ ny = sin (theta) * r2;
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[22];
+ if (v > 0.0)
+ {
+ /* fan */
+ double theta, r2, dx, dy, dx2, nx, ny;
+ if (tx < -EPS || tx > EPS ||
+ ty < -EPS || ty > EPS)
+ theta = atan2 (tx, ty);
+ else
+ theta = 0.0;
+ dx = coef[2][0];
+ dy = coef[2][1];
+ dx = G_PI * (dx * dx + EPS);
+ dx2 = dx / 2;
+ r2 = sqrt (tx * tx + ty * ty );
+ theta += (fmod (theta + dy, dx) > dx2) ? -dx2: dx2;
+ nx = cos (theta) * r2;
+ ny = sin (theta) * r2;
+ p[0] += v * nx;
+ p[1] += v * ny;
+ }
+
+ v = vari[23];
+ if (v > 0.0)
+ {
+ /* eyefish */
+ double r2;
+ r2 = 2.0 * v / (sqrt(tx * tx + ty * ty) + 1.0);
+ p[0] += r2 * tx;
+ p[1] += r2 * ty;
+ }
+
+ v = vari[24];
+ if (v > 0.0)
+ {
+ /* bubble */
+ double r2;
+ r2 = v / ((tx * tx + ty * ty) / 4 + 1);
+ p[0] += r2 * tx;
+ p[1] += r2 * ty;
+ }
+
+ v = vari[25];
+ if (v > 0.0)
+ {
+ /* cylinder */
+ double nx;
+ nx = sin (tx);
+ p[0] += v * nx;
+ p[1] += v * ty;
+ }
+
+ v = vari[26];
+ if (v > 0.0)
+ {
+ /* noise */
+ double rx, sinr, cosr, nois;
+ rx = flam3_random01 () * 2 * G_PI;
+ sinr = sin (rx);
+ cosr = cos (rx);
+ nois = flam3_random01 ();
+ p[0] += v * nois * tx * cosr;
+ p[1] += v * nois * ty * sinr;
+ }
+
+ v = vari[27];
+ if (v > 0.0)
+ {
+ /* blur */
+ double rx, sinr, cosr, nois;
+ rx = flam3_random01 () * 2 * G_PI;
+ sinr = sin (rx);
+ cosr = cos (rx);
+ nois = flam3_random01 ();
+ p[0] += v * nois * cosr;
+ p[1] += v * nois * sinr;
+ }
+
+ v = vari[28];
+ if (v > 0.0)
+ {
+ /* gaussian */
+ double ang, sina, cosa, r2;
+ ang = flam3_random01 () * 2 * G_PI;
+ sina = sin (ang);
+ cosa = cos (ang);
+ r2 = v * (flam3_random01 () + flam3_random01 () + flam3_random01 () +
+ flam3_random01 () - 2.0);
+ p[0] += r2 * cosa;
+ p[1] += r2 * sina;
+ }
+
+ /* if fuse over, store it */
+ if (i >= 0)
+ {
+ points[i][0] = p[0];
+ points[i][1] = p[1];
+ points[i][2] = p[2];
+ }
+ }
+}
+
+/* args must be non-overlapping */
+void
+mult_matrix (double s1[2][2],
+ double s2[2][2],
+ double d[2][2])
+{
+ d[0][0] = s1[0][0] * s2[0][0] + s1[1][0] * s2[0][1];
+ d[1][0] = s1[0][0] * s2[1][0] + s1[1][0] * s2[1][1];
+ d[0][1] = s1[0][1] * s2[0][0] + s1[1][1] * s2[0][1];
+ d[1][1] = s1[0][1] * s2[1][0] + s1[1][1] * s2[1][1];
+}
+
+static
+double det_matrix (double s[2][2])
+{
+ return s[0][0] * s[1][1] - s[0][1] * s[1][0];
+}
+
+static void
+interpolate_angle (double t,
+ double s,
+ double *v1,
+ double *v2,
+ double *v3,
+ int tie,
+ int cross)
+{
+ double x = *v1;
+ double y = *v2;
+ double d;
+ static double lastx, lasty;
+
+ /* take the shorter way around the circle... */
+ if (x > y)
+ {
+ d = x - y;
+ if (d > G_PI + EPS ||
+ (d > G_PI - EPS && tie))
+ y += 2 * G_PI;
+ }
+ else
+ {
+ d = y - x;
+ if (d > G_PI + EPS ||
+ (d > G_PI - EPS && tie))
+ x += 2 * G_PI;
+ }
+ /* unless we are supposed to avoid crossing */
+ if (cross)
+ {
+ if (lastx > x)
+ {
+ if (lasty < y)
+ y -= 2 * G_PI;
+ }
+ else
+ {
+ if (lasty > y)
+ y += 2 * G_PI;
+ }
+ }
+ else
+ {
+ lastx = x;
+ lasty = y;
+ }
+
+ *v3 = s * x + t * y;
+}
+
+static void
+interpolate_complex (double t,
+ double s,
+ double *r1,
+ double *r2,
+ double *r3,
+ int flip,
+ int tie,
+ int cross)
+{
+ double c1[2], c2[2], c3[2];
+ double a1, a2, a3, d1, d2, d3;
+
+ c1[0] = r1[0];
+ c1[1] = r1[1];
+ c2[0] = r2[0];
+ c2[1] = r2[1];
+ if (flip)
+ {
+ double t = c1[0];
+ c1[0] = c1[1];
+ c1[1] = t;
+ t = c2[0];
+ c2[0] = c2[1];
+ c2[1] = t;
+ }
+
+ /* convert to log space */
+ a1 = atan2 (c1[1], c1[0]);
+ a2 = atan2 (c2[1], c2[0]);
+ d1 = 0.5 * log (c1[0] * c1[0] + c1[1] * c1[1]);
+ d2 = 0.5 * log (c2[0] * c2[0] + c2[1] * c2[1]);
+
+ /* interpolate linearly */
+ interpolate_angle (t, s, &a1, &a2, &a3, tie, cross);
+ d3 = s * d1 + t * d2;
+
+ /* convert back */
+ d3 = exp (d3);
+ c3[0] = cos (a3) * d3;
+ c3[1] = sin (a3) * d3;
+
+ if (flip)
+ {
+ r3[1] = c3[0];
+ r3[0] = c3[1];
+ }
+ else
+ {
+ r3[0] = c3[0];
+ r3[1] = c3[1];
+ }
+}
+
+static void
+interpolate_matrix (double t,
+ double m1[3][2],
+ double m2[3][2],
+ double m3[3][2])
+{
+ double s = 1.0 - t;
+
+ interpolate_complex (t, s, &m1[0][0], &m2[0][0], &m3[0][0], 0, 0, 0);
+ interpolate_complex (t, s, &m1[1][0], &m2[1][0], &m3[1][0], 1, 1, 0);
+
+ /* handle the translation part of the xform linearly */
+ m3[2][0] = s * m1[2][0] + t * m2[2][0];
+ m3[2][1] = s * m1[2][1] + t * m2[2][1];
+}
+
+#define INTERP(x) result->x = c0 * cps[i1].x + c1 * cps[i2].x
+
+/*
+ * create a control point that interpolates between the control points
+ * passed in CPS. for now just do linear. in the future, add control
+ * point types and other things to the cps. CPS must be sorted by time.
+ */
+void
+interpolate (control_point cps[],
+ int ncps,
+ double time,
+ control_point *result)
+{
+ int i, j, i1, i2;
+ double c0, c1, t;
+
+ g_return_if_fail (ncps > 0);
+
+ if (ncps == 1)
+ {
+ *result = cps[0];
+ return;
+ }
+ if (cps[0].time >= time)
+ {
+ i1 = 0;
+ i2 = 1;
+ }
+ else if (cps[ncps - 1].time <= time)
+ {
+ i1 = ncps - 2;
+ i2 = ncps - 1;
+ }
+ else
+ {
+ i1 = 0;
+ while (i1 < ncps && cps[i1].time < time)
+ i1++;
+ i1--;
+ i2 = i1 + 1;
+
+ if (i2 == ncps ||
+ (time - cps[i1].time > -1e-7 &&
+ time - cps[i1].time < 1e-7))
+ {
+ *result = cps[i1];
+ return;
+ }
+ }
+
+ c0 = (cps[i2].time - time) / (cps[i2].time - cps[i1].time);
+ c1 = 1.0 - c0;
+
+ result->time = time;
+
+ if (cps[i1].cmap_inter)
+ {
+ for (i = 0; i < 256; i++)
+ {
+ double spread = 0.15;
+ double d0, d1, e0, e1, c = 2 * G_PI * i / 256.0;
+ c = cos(c * cps[i1].cmap_inter) + 4.0 * c1 - 2.0;
+ if (c > spread) c = spread;
+ if (c < -spread) c = -spread;
+ d1 = (c + spread) * 0.5 / spread;
+ d0 = 1.0 - d1;
+ e0 = (d0 < 0.5) ? (d0 * 2) : (d1 * 2);
+ e1 = 1.0 - e0;
+ for (j = 0; j < 3; j++)
+ {
+ result->cmap[i][j] = (d0 * cps[i1].cmap[i][j] +
+ d1 * cps[i2].cmap[i][j]);
+#define bright_peak 2.0
+ result->cmap[i][j] = (e1 * result->cmap[i][j] +
+ e0 * 1.0);
+ }
+ }
+ }
+ else
+ {
+ for (i = 0; i < 256; i++)
+ {
+ double t[3], s[3];
+ rgb2hsv (cps[i1].cmap[i], s);
+ rgb2hsv (cps[i2].cmap[i], t);
+ for (j = 0; j < 3; j++)
+ t[j] = c0 * s[j] + c1 * t[j];
+ hsv2rgb (t, result->cmap[i]);
+ }
+ }
+
+ result->cmap_index = -1;
+ INTERP(brightness);
+ INTERP(contrast);
+ INTERP(gamma);
+ INTERP(width);
+ INTERP(height);
+ INTERP(spatial_oversample);
+ INTERP(center[0]);
+ INTERP(center[1]);
+ INTERP(pixels_per_unit);
+ INTERP(spatial_filter_radius);
+ INTERP(sample_density);
+ INTERP(zoom);
+ INTERP(nbatches);
+ INTERP(white_level);
+ for (i = 0; i < 2; i++)
+ for (j = 0; j < 2; j++)
+ {
+ INTERP(pulse[i][j]);
+ INTERP(wiggle[i][j]);
+ }
+
+ for (i = 0; i < NXFORMS; i++)
+ {
+ double r;
+ double rh_time;
+
+ INTERP(xform[i].density);
+ if (result->xform[i].density > 0)
+ result->xform[i].density = 1.0;
+ INTERP(xform[i].color);
+ for (j = 0; j < NVARS; j++)
+ INTERP(xform[i].var[j]);
+ t = 0.0;
+ for (j = 0; j < NVARS; j++)
+ t += result->xform[i].var[j];
+ t = 1.0 / t;
+ for (j = 0; j < NVARS; j++)
+ result->xform[i].var[j] *= t;
+
+ interpolate_matrix(c1, cps[i1].xform[i].c, cps[i2].xform[i].c,
+ result->xform[i].c);
+
+ rh_time = time * 2 * G_PI / (60.0 * 30.0);
+
+ /* apply pulse factor. */
+ r = 1.0;
+ for (j = 0; j < 2; j++)
+ r += result->pulse[j][0] * sin(result->pulse[j][1] * rh_time);
+ for (j = 0; j < 3; j++)
+ {
+ result->xform[i].c[j][0] *= r;
+ result->xform[i].c[j][1] *= r;
+ }
+
+ /* apply wiggle factor */
+ for (j = 0; j < 2; j++)
+ {
+ double tt = result->wiggle[j][1] * rh_time;
+ double m = result->wiggle[j][0];
+ result->xform[i].c[0][0] += m * cos(tt);
+ result->xform[i].c[1][0] += m * -sin(tt);
+ result->xform[i].c[0][1] += m * sin(tt);
+ result->xform[i].c[1][1] += m * cos(tt);
+ }
+ } /* for i */
+}
+
+
+
+/*
+ * split a string passed in ss into tokens on whitespace.
+ * # comments to end of line. ; terminates the record
+ */
+void
+tokenize (char **ss,
+ char *argv[],
+ int *argc)
+{
+ char *s = *ss;
+ int i = 0, state = 0;
+ gint len = 0;
+
+ len = strlen (s);
+ while (*s != ';' && len > 0)
+ {
+ char c = *s;
+ switch (state)
+ {
+ case 0:
+ if (c == '#')
+ state = 2;
+ else if (!g_ascii_isspace (c))
+ {
+ argv[i] = s;
+ i++;
+ state = 1;
+ }
+ break;
+ case 1:
+ if (g_ascii_isspace (c))
+ {
+ *s = 0;
+ state = 0;
+ }
+ break;
+ case 2:
+ if (c == '\n')
+ state = 0;
+ break;
+ }
+ s++;
+ len--;
+ }
+ *s = 0;
+ *ss = s + 1;
+ *argc = i;
+}
+
+static int
+compare_xforms (const void *va,
+ const void *vb)
+{
+ double aa[2][2];
+ double bb[2][2];
+ double ad, bd;
+ const xform *a = va;
+ const xform *b = vb;
+
+ aa[0][0] = a->c[0][0];
+ aa[0][1] = a->c[0][1];
+ aa[1][0] = a->c[1][0];
+ aa[1][1] = a->c[1][1];
+ bb[0][0] = b->c[0][0];
+ bb[0][1] = b->c[0][1];
+ bb[1][0] = b->c[1][0];
+ bb[1][1] = b->c[1][1];
+ ad = det_matrix (aa);
+ bd = det_matrix (bb);
+ if (ad < bd)
+ return -1;
+ if (ad > bd)
+ return 1;
+ return 0;
+}
+
+#define MAXARGS 1000
+#define streql(x,y) (!strcmp(x,y))
+
+/*
+ * given a pointer to a string SS, fill fields of a control point CP.
+ */
+
+void
+parse_control_point (char **ss,
+ control_point *cp)
+{
+ char *argv[MAXARGS];
+ int argc, i, j;
+ gint64 xf_index = 0;
+ gint parse_errors = 0;
+ double *slot = NULL, t;
+
+ for (i = 0; i < NXFORMS; i++)
+ {
+ cp->xform[i].density = 0.0;
+ cp->xform[i].color = (i == 0);
+ cp->xform[i].var[0] = 1.0;
+ for (j = 1; j < NVARS; j++)
+ cp->xform[i].var[j] = 0.0;
+ cp->xform[i].c[0][0] = 1.0;
+ cp->xform[i].c[0][1] = 0.0;
+ cp->xform[i].c[1][0] = 0.0;
+ cp->xform[i].c[1][1] = 1.0;
+ cp->xform[i].c[2][0] = 0.0;
+ cp->xform[i].c[2][1] = 0.0;
+ }
+ for (j = 0; j < 2; j++)
+ {
+ cp->pulse[j][0] = 0.0;
+ cp->pulse[j][1] = 60.0;
+ cp->wiggle[j][0] = 0.0;
+ cp->wiggle[j][1] = 60.0;
+ }
+
+ tokenize (ss, argv, &argc);
+
+ i = 0;
+ while (i < argc)
+ {
+ gint itoken;
+
+ itoken = i;
+ if (i < argc)
+ {
+ /* First value belonging to token. */
+ i++;
+ }
+ else
+ {
+ g_printerr ("Not enough parameters. File may be corrupt!\n");
+ parse_errors++;
+ break;
+ }
+
+ if (streql ("xform", argv[itoken]))
+ {
+ if (! g_ascii_string_to_signed (argv[i++], 10, 0, NXFORMS-1, &xf_index, NULL))
+ {
+ g_printerr ("Invalid xform index '%s'\n", argv[i-1]);
+ parse_errors++;
+ xf_index = 0;
+ }
+ }
+ else if (streql ("density", argv[itoken]))
+ {
+ cp->xform[xf_index].density = g_strtod (argv[i++], NULL);
+ }
+ else if (streql ("color", argv[itoken]))
+ {
+ cp->xform[xf_index].color = g_strtod (argv[i++], NULL);
+ }
+ else if (streql ("coefs", argv[itoken]))
+ {
+ /* We need 6 coef values and we know are at the first */
+ if (i + 5 >= argc)
+ {
+ g_printerr ("Not enough parameters. File may be corrupt!\n");
+ parse_errors++;
+ break;
+ }
+ slot = cp->xform[xf_index].c[0];
+ for (j = 0; j < 6; j++)
+ {
+ *slot++ = g_strtod (argv[i++], NULL);
+ }
+ cp->xform[xf_index].density = 1.0;
+ }
+ else if (streql ("var", argv[itoken]))
+ {
+ /* We need NVARS var values and we know are at the first */
+ if (i + NVARS > argc)
+ {
+ g_printerr ("Not enough parameters. File may be corrupt!\n");
+ parse_errors++;
+ break;
+ }
+ slot = cp->xform[xf_index].var;
+ for (j = 0; j < NVARS; j++)
+ {
+ *slot++ = g_strtod (argv[i++], NULL);
+ }
+ }
+ else if (streql ("time", argv[itoken]))
+ {
+ cp->time = g_strtod (argv[i++], NULL);
+ }
+ else if (streql ("brightness", argv[itoken]))
+ {
+ cp->brightness = g_strtod (argv[i++], NULL);
+ }
+ else if (streql ("contrast", argv[itoken]))
+ {
+ cp->contrast = g_strtod (argv[i++], NULL);
+ }
+ else if (streql ("gamma", argv[itoken]))
+ {
+ cp->gamma = g_strtod (argv[i++], NULL);
+ }
+ else if (streql ("zoom", argv[itoken]))
+ {
+ cp->zoom = g_strtod (argv[i++], NULL);
+ }
+ else if (streql ("image_size", argv[itoken]))
+ {
+ gint64 w, h;
+
+ /* We need 2 values and we know are at the first */
+ if (i + 1 >= argc)
+ {
+ g_printerr ("Not enough parameters. File may be corrupt!\n");
+ parse_errors++;
+ break;
+ }
+ if (! g_ascii_string_to_signed (argv[i++], 10, 1, GIMP_MAX_IMAGE_SIZE, &w, NULL))
+ {
+ g_printerr ("Ignoring invalid image width '%s'\n", argv[i-1]);
+ parse_errors++;
+ }
+ else if (! g_ascii_string_to_signed (argv[i++], 10, 1, GIMP_MAX_IMAGE_SIZE, &h, NULL))
+ {
+ g_printerr ("Ignoring invalid image_size heigth '%s'\n", argv[i-1]);
+ parse_errors++;
+ }
+ else
+ {
+ cp->width = w;
+ cp->height = h;
+ }
+ }
+ else if (streql ("center", argv[itoken]))
+ {
+ /* We need 2 values and we know are at the first */
+ if (i + 1 >= argc)
+ {
+ g_printerr ("Not enough parameters. File may be corrupt!\n");
+ parse_errors++;
+ break;
+ }
+ cp->center[0] = g_strtod (argv[i++], NULL);
+ cp->center[1] = g_strtod (argv[i++], NULL);
+ }
+ else if (streql ("pixels_per_unit", argv[itoken]))
+ {
+ cp->pixels_per_unit = g_strtod (argv[i++], NULL);
+ }
+ else if (streql ("pulse", argv[itoken]))
+ {
+ /* We need 4 values and we know are at the first */
+ if (i + 3 >= argc)
+ {
+ g_printerr ("Not enough parameters. File may be corrupt!\n");
+ parse_errors++;
+ break;
+ }
+ slot = &cp->pulse[0][0];
+ for (j = 0; j < 4; j++)
+ {
+ *slot++ = g_strtod (argv[i++], NULL);
+ }
+ }
+ else if (streql ("wiggle", argv[itoken]))
+ {
+ /* We need 4 values and we know are at the first */
+ if (i + 3 >= argc)
+ {
+ g_printerr ("Not enough parameters. File may be corrupt!\n");
+ parse_errors++;
+ break;
+ }
+ slot = &cp->wiggle[0][0];
+ for (j = 0; j < 4; j++)
+ {
+ *slot++ = g_strtod (argv[i++], NULL);
+ }
+ }
+ else if (streql ("spatial_oversample", argv[itoken]))
+ {
+ gint64 oversample;
+
+ /* Values in the gui seem to be between 1 and 4 */
+ if (! g_ascii_string_to_signed (argv[i++], 10, 1, 4, &oversample, NULL))
+ {
+ g_printerr ("Ignoring invalid spatial oversample value '%s'\n", argv[i-1]);
+ parse_errors++;
+ }
+ else
+ {
+ cp->spatial_oversample = oversample;
+ }
+ }
+ else if (streql ("spatial_filter_radius", argv[itoken]))
+ {
+ cp->spatial_filter_radius = g_strtod (argv[i++], NULL);
+ }
+ else if (streql ("sample_density", argv[itoken]))
+ {
+ cp->sample_density = g_strtod (argv[i++], NULL);
+ }
+ else if (streql ("nbatches", argv[itoken]))
+ {
+ gint64 nbatches;
+
+ /* Not sure what the maximum should be. It always seems to be set to 1. */
+ if (! g_ascii_string_to_signed (argv[i++], 10, 0, 2, &nbatches, NULL))
+ {
+ g_printerr ("Ignoring invalid nbatches value '%s'\n", argv[i-1]);
+ parse_errors++;
+ }
+ else
+ {
+ cp->nbatches = nbatches;
+ }
+ }
+ else if (streql ("white_level", argv[itoken]))
+ {
+ gint64 wl;
+
+ if (! g_ascii_string_to_signed (argv[i++], 10, 0, 255, &wl, NULL))
+ {
+ g_printerr ("Ignoring invalid white level value '%s'\n", argv[i-1]);
+ parse_errors++;
+ }
+ else
+ {
+ cp->white_level = wl;
+ }
+ }
+ else if (streql ("cmap", argv[itoken]))
+ {
+ gint64 cmi;
+
+ /* -1 = random */
+ if (! g_ascii_string_to_signed (argv[i++], 10, -1, 255, &cmi, NULL))
+ {
+ g_printerr ("Ignoring invalid color map value '%s'\n", argv[i-1]);
+ parse_errors++;
+ }
+ else
+ {
+ cp->cmap_index = cmi;
+ }
+ }
+ else if (streql ("cmap_inter", argv[itoken]))
+ {
+ gint64 cmi;
+
+ /* 0 or 1 */
+ if (! g_ascii_string_to_signed (argv[i++], 10, 0, 1, &cmi, NULL))
+ {
+ g_printerr ("Ignoring invalid color interpolate value '%s'\n", argv[i-1]);
+ parse_errors++;
+ }
+ else
+ {
+ cp->cmap_inter = cmi;
+ }
+ }
+ else
+ {
+ g_printerr ("Invalid token '%s'. File may be corrupt!\n", argv[itoken]);
+ parse_errors++;
+ }
+ }
+
+ if (parse_errors > 0)
+ g_warning ("Input file contains %d errors. File may be corrupt!", parse_errors);
+
+ for (i = 0; i < NXFORMS; i++)
+ {
+ t = 0.0;
+ for (j = 0; j < NVARS; j++)
+ t += cp->xform[i].var[j];
+ t = 1.0 / t;
+ for (j = 0; j < NVARS; j++)
+ cp->xform[i].var[j] *= t;
+ }
+ qsort ((char *) cp->xform, NXFORMS, sizeof(xform), compare_xforms);
+}
+
+void
+print_control_point (FILE *f,
+ control_point *cp,
+ int quote)
+{
+ int i, j;
+ char *q = quote ? "# " : "";
+ fprintf (f, "%stime %g\n", q, cp->time);
+ if (cp->cmap_index != -1)
+ fprintf (f, "%scmap %d\n", q, cp->cmap_index);
+ fprintf (f, "%simage_size %d %d center %g %g pixels_per_unit %g\n",
+ q, cp->width, cp->height, cp->center[0], cp->center[1],
+ cp->pixels_per_unit);
+ fprintf (f, "%sspatial_oversample %d spatial_filter_radius %g",
+ q, cp->spatial_oversample, cp->spatial_filter_radius);
+ fprintf (f, " sample_density %g\n", cp->sample_density);
+ fprintf (f, "%snbatches %d white_level %d\n",
+ q, cp->nbatches, cp->white_level);
+ fprintf (f, "%sbrightness %g gamma %g cmap_inter %d\n",
+ q, cp->brightness, cp->gamma, cp->cmap_inter);
+
+ for (i = 0; i < NXFORMS; i++)
+ if (cp->xform[i].density > 0.0)
+ {
+ fprintf (f, "%sxform %d density %g color %g\n",
+ q, i, cp->xform[i].density, cp->xform[i].color);
+ fprintf (f, "%svar", q);
+ for (j = 0; j < NVARS; j++)
+ fprintf (f, " %g", cp->xform[i].var[j]);
+ fprintf (f, "\n%scoefs", q);
+ for (j = 0; j < 3; j++)
+ fprintf (f, " %g %g", cp->xform[i].c[j][0], cp->xform[i].c[j][1]);
+ fprintf (f, "\n");
+ }
+ fprintf (f, "%s;\n", q);
+}
+
+/* returns a uniform variable from 0 to 1 */
+double
+random_uniform01 (void)
+{
+ return g_random_double ();
+}
+
+double random_uniform11 (void)
+{
+ return g_random_double_range (-1, 1);
+}
+
+/* returns a mean 0 variance 1 random variable
+ see numerical recipes p 217 */
+double random_gaussian(void)
+{
+ static int iset = 0;
+ static double gset;
+ double fac, r, v1, v2;
+
+ if (iset == 0)
+ {
+ do
+ {
+ v1 = random_uniform11 ();
+ v2 = random_uniform11 ();
+ r = v1 * v1 + v2 * v2;
+ }
+ while (r >= 1.0 || r == 0.0);
+ fac = sqrt (-2.0 * log (r) / r);
+ gset = v1 * fac;
+ iset = 1;
+ return v2 * fac;
+ }
+ iset = 0;
+ return gset;
+}
+
+void
+copy_variation (control_point *cp0,
+ control_point *cp1)
+{
+ int i, j;
+ for (i = 0; i < NXFORMS; i++)
+ {
+ for (j = 0; j < NVARS; j++)
+ cp0->xform[i].var[j] = cp1->xform[i].var[j];
+ }
+}
+
+#define random_distrib(v) ((v)[g_random_int_range (0, vlen(v))])
+
+void
+random_control_point (control_point *cp,
+ int ivar)
+{
+ int i, nxforms, var;
+ static int xform_distrib[] =
+ {
+ 2, 2, 2,
+ 3, 3, 3,
+ 4, 4,
+ 5
+ };
+ static int var_distrib[] =
+ {
+ -1, -1, -1,
+ 0, 0, 0, 0,
+ 1, 1, 1,
+ 2, 2, 2,
+ 3, 3,
+ 4, 4,
+ 5
+ };
+
+ static int mixed_var_distrib[] =
+ {
+ 0, 0, 0,
+ 1, 1, 1,
+ 2, 2, 2,
+ 3, 3,
+ 4, 4,
+ 5, 5
+ };
+
+ get_cmap (cmap_random, cp->cmap, 256);
+ cp->time = 0.0;
+ nxforms = random_distrib (xform_distrib);
+ var = (0 > ivar) ?
+ random_distrib(var_distrib) :
+ ivar;
+ for (i = 0; i < nxforms; i++)
+ {
+ int j, k;
+ cp->xform[i].density = 1.0 / nxforms;
+ cp->xform[i].color = i == 0;
+ for (j = 0; j < 3; j++)
+ for (k = 0; k < 2; k++)
+ cp->xform[i].c[j][k] = random_uniform11();
+ for (j = 0; j < NVARS; j++)
+ cp->xform[i].var[j] = 0.0;
+ if (var >= 0)
+ cp->xform[i].var[var] = 1.0;
+ else
+ cp->xform[i].var[random_distrib(mixed_var_distrib)] = 1.0;
+
+ }
+ for (; i < NXFORMS; i++)
+ cp->xform[i].density = 0.0;
+}
+
+/*
+ * find a 2d bounding box that does not enclose eps of the fractal density
+ * in each compass direction. works by binary search.
+ * this is stupid, it should just use the find nth smallest algorithm.
+ */
+void
+estimate_bounding_box (control_point *cp,
+ double eps,
+ double *bmin,
+ double *bmax)
+{
+ int i, j, batch = (eps == 0.0) ? 10000 : 10.0/eps;
+ int low_target = batch * eps;
+ int high_target = batch - low_target;
+ point min, max, delta;
+ point *points = g_malloc0 (sizeof (point) * batch);
+
+ iterate (cp, batch, 20, points);
+
+ min[0] = min[1] = 1e10;
+ max[0] = max[1] = -1e10;
+
+ for (i = 0; i < batch; i++)
+ {
+ if (points[i][0] < min[0]) min[0] = points[i][0];
+ if (points[i][1] < min[1]) min[1] = points[i][1];
+ if (points[i][0] > max[0]) max[0] = points[i][0];
+ if (points[i][1] > max[1]) max[1] = points[i][1];
+ }
+
+ if (low_target == 0)
+ {
+ bmin[0] = min[0];
+ bmin[1] = min[1];
+ bmax[0] = max[0];
+ bmax[1] = max[1];
+ return;
+ }
+
+ delta[0] = (max[0] - min[0]) * 0.25;
+ delta[1] = (max[1] - min[1]) * 0.25;
+
+ bmax[0] = bmin[0] = min[0] + 2.0 * delta[0];
+ bmax[1] = bmin[1] = min[1] + 2.0 * delta[1];
+
+ for (i = 0; i < 14; i++)
+ {
+ int n, s, e, w;
+ n = s = e = w = 0;
+ for (j = 0; j < batch; j++)
+ {
+ if (points[j][0] < bmin[0]) n++;
+ if (points[j][0] > bmax[0]) s++;
+ if (points[j][1] < bmin[1]) w++;
+ if (points[j][1] > bmax[1]) e++;
+ }
+ bmin[0] += (n < low_target) ? delta[0] : -delta[0];
+ bmax[0] += (s < high_target) ? delta[0] : -delta[0];
+ bmin[1] += (w < low_target) ? delta[1] : -delta[1];
+ bmax[1] += (e < high_target) ? delta[1] : -delta[1];
+ delta[0] = delta[0] / 2.0;
+ delta[1] = delta[1] / 2.0;
+ }
+ g_free (points);
+}
+
+/* this has serious flaws in it */
+
+double
+standard_metric (control_point *cp1,
+ control_point *cp2)
+{
+ int i, j, k;
+ double t;
+ double dist = 0.0;
+
+ for (i = 0; i < NXFORMS; i++)
+ {
+ double var_dist = 0.0;
+ double coef_dist = 0.0;
+ for (j = 0; j < NVARS; j++)
+ {
+ t = cp1->xform[i].var[j] - cp2->xform[i].var[j];
+ var_dist += t * t;
+ }
+ for (j = 0; j < 3; j++)
+ for (k = 0; k < 2; k++)
+ {
+ t = cp1->xform[i].c[j][k] - cp2->xform[i].c[j][k];
+ coef_dist += t *t;
+ }
+ /* weight them equally for now. */
+ dist += var_dist + coef_dist;
+ }
+ return dist;
+}
+
+static int
+flam3_random_bit (void)
+{
+ static int n = 0;
+ static int l;
+ if (n == 0)
+ {
+ l = g_random_int ();
+ n = 20;
+ }
+ else
+ {
+ l = l >> 1;
+ n--;
+ }
+ return l & 1;
+}
+
+static double
+flam3_random01 (void)
+{
+ return (g_random_int () & 0xfffffff) / (double) 0xfffffff;
+}