/*
Copyright (C) 1992-2009 Spotworks LLC
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 .
*/
#include "interpolation.h"
#include "palettes.h"
#include
#define INTERP(x) do { result->x = 0.0; \
for (k = 0; k < ncp; k++) result->x += c[k] * cpi[k].x; } while(0)
#define INTERI(x) do { double tt = 0.0; \
for (k = 0; k < ncp; k++) tt += c[k] * cpi[k].x; \
result->x = (int)rint(tt); } while(0)
int id_matrix(double2 s[3]) {
return
(s[0][0] == 1.0) &&
(s[0][1] == 0.0) &&
(s[1][0] == 0.0) &&
(s[1][1] == 1.0) &&
(s[2][0] == 0.0) &&
(s[2][1] == 0.0);
}
static void clear_matrix(double2 m[3]) {
const double2 zero = (double2) { 0.0, 0.0 };
m[0] = zero;
m[1] = zero;
m[2] = zero;
}
static void sum_matrix(double s, const double2 m1[3], double2 m2[3]) {
m2[0] += s * m1[0];
m2[1] += s * m1[1];
m2[2] += s * m1[2];
}
#if 0
void interpolate_cmap(flam3_palette cmap, double blend,
int index0, double hue0, int index1, double hue1,
randctx * const rc) {
flam3_palette p0,p1;
int i, j, rcode;
rcode = flam3_get_palette(index0, p0, hue0, rc);
if (rcode<0)
fprintf(stderr,"unable to retrieve palette %d, setting to white\n", index0);
rcode = flam3_get_palette(index1, p1, hue1, rc);
if (rcode<0)
fprintf(stderr,"unable to retrieve palette %d, setting to white\n", index1);
for (i = 0; i < 256; i++) {
double4 t, s;
double t4, s4;
s = rgb2hsv(vector_d4 (p0[i].color));
t = rgb2hsv(vector_d4 (p1[i].color));
s[3] = p0[i].color[3];
t[3] = p1[i].color[3];
s4 = p0[i].index;
t4 = p1[i].index;
for (j = 0; j < 4; j++)
t[j] = ((1.0-blend) * s[j]) + (blend * t[j]);
t4 = ((1.0-blend) * s4) + (blend * t4);
const double4 c = hsv2rgb(t);
cmap[i].color[0] = c[0];
cmap[i].color[1] = c[1];
cmap[i].color[2] = c[2];
cmap[i].color[3] = t[3];
cmap[i].index = t4;
}
}
#endif
static void interp_and_convert_back(double *c, int ncps, int xfi, double cxang[4][2],
double cxmag[4][2], double cxtrn[4][2],double store_array[3][2]) {
int i,col;
double accang[2],accmag[2];
double expmag;
int accmode[2];
accang[0] = 0.0;
accang[1] = 0.0;
accmag[0] = 0.0;
accmag[1] = 0.0;
accmode[0]=accmode[1]=0;
/* accumulation mode defaults to logarithmic, but in special */
/* cases we want to switch to linear accumulation */
for (col=0; col<2; col++) {
for (i=0; i0 && cflag==0) {
/* Adjust the angles to make sure that it's within wind:wind+2pi */
refang = cp[k].xform[xfi].wind[col] - 2*M_PI;
/* Make sure both angles are within [refang refang+2*pi] */
while(cxang[k-1][col] < refang)
cxang[k-1][col] += 2*M_PI;
while(cxang[k-1][col] > refang + 2*M_PI)
cxang[k-1][col] -= 2*M_PI;
while(cxang[k][col] < refang)
cxang[k][col] += 2*M_PI;
while(cxang[k][col] > refang + 2*M_PI)
cxang[k][col] -= 2*M_PI;
} else {
/* Normal way of adjusting angles */
d = cxang[k][col]-cxang[k-1][col];
/* Adjust to avoid the -pi/pi discontinuity */
if (d > M_PI+EPS)
cxang[k][col] -= 2*M_PI;
else if (d < -(M_PI-EPS) ) /* Forces clockwise rotation at 180 */
cxang[k][col] += 2*M_PI;
}
}
}
}
void interpolate_catmull_rom(flam3_genome cps[], double t, flam3_genome *result) {
double t2 = t * t;
double t3 = t2 * t;
double cmc[4];
cmc[0] = (2*t2 - t - t3) / 2;
cmc[1] = (3*t3 - 5*t2 + 2) / 2;
cmc[2] = (4*t2 - 3*t3 + t) / 2;
cmc[3] = (t3 - t2) / 2;
flam3_interpolate_n(result, 4, cps, cmc, 0);
}
static double smoother(double t) {
return 3*t*t - 2*t*t*t;
}
static double get_stagger_coef(double t, double stagger_prc, int num_xforms, int this_xform) {
/* max_stag is the spacing between xform start times if stagger_prc = 1.0 */
double max_stag = (double)(num_xforms-1)/num_xforms;
/* scale the spacing by stagger_prc */
double stag_scaled = stagger_prc * max_stag;
/* t ranges from 1 to 0 (the contribution of cp[0] to the blend) */
/* the first line below makes the first xform interpolate first */
/* the second line makes the last xform interpolate first */
double st = stag_scaled * (num_xforms - 1 - this_xform) / (num_xforms-1);
// double st = stag_scaled * (this_xform) / (num_xforms-1);
double et = st + (1-stag_scaled);
// printf("t=%f xf:%d st=%f et=%f : : %f\n",t,this_xform,st,et,smoother((t-st)/(1-stag_scaled)));
if (t <= st)
return (0);
else if (t >= et)
return (1);
else
return ( smoother((t-st)/(1-stag_scaled)) );
}
/* all cpi and result must be aligned (have the same number of xforms,
and have final xform in the same slot) */
void flam3_interpolate_n(flam3_genome *result, int ncp,
flam3_genome *cpi, double *c, double stagger) {
int i, j, k, numstd;
/* HSV palette interpolation */
for (i = 0; i < cpi[0].palette.count; i++) {
double4 s, t;
int alpha1 = 1;
s[0] = s[1] = s[2] = s[3] = 0.0;
for (k = 0; k < ncp; k++) {
assert (cpi[k].palette.count == cpi[0].palette.count);
t = rgb2hsv(cpi[k].palette.color[i]);
for (j = 0; j < 3; j++)
s[j] += c[k] * t[j];
s[3] += c[k] * cpi[k].palette.color[i][3];
if (cpi[k].palette.color[i][3] != 1.0)
alpha1 = 0;
}
if (alpha1 == 1)
s[3] = 1.0;
const double4 ret_color = hsv2rgb(s);
palette_add (&result->palette, (double4) { ret_color[0], ret_color[1],
ret_color[2], s[3] });
}
result->symmetry = 0;
result->palette_mode = cpi[0].palette_mode;
result->interpolation_type = cpi[0].interpolation_type;
INTERP(brightness);
INTERP(contrast);
INTERP(highlight_power);
INTERP(gamma);
INTERP(vibrancy);
INTERP(hue_rotation);
INTERI(width);
INTERI(height);
INTERP(center[0]);
INTERP(center[1]);
INTERP(pixels_per_unit);
INTERP(zoom);
INTERP(rotate);
INTERP(gam_lin_thresh);
/* Interpolate the chaos array */
numstd = cpi[0].num_xforms - (cpi[0].final_xform_index >= 0);
for (i=0;ichaos[i][j]<0) result->chaos[i][j]=0;
//chaos can be > 1
//if (result->chaos[i][j]>1) result->chaos[i][j]=1.0;
}
}
/* Interpolate each xform */
for (i = 0; i < cpi[0].num_xforms; i++) {
double csave[2];
double td;
int all_id;
int nx = cpi[0].num_xforms-(cpi[0].final_xform_index>=0);
if (ncp==2 && stagger>0 && i!=cpi[0].final_xform_index) {
csave[0] = c[0];
csave[1] = c[1];
c[0] = get_stagger_coef(csave[0],stagger,nx,i);
c[1] = 1.0-c[0];
}
INTERP(xform[i].density);
td = result->xform[i].density;
result->xform[i].density = (td < 0.0) ? 0.0 : td;
INTERP(xform[i].color);
if (result->xform[i].color<0) result->xform[i].color=0;
if (result->xform[i].color>1) result->xform[i].color=1;
INTERP(xform[i].color_speed);
if (result->xform[i].color_speed<0) result->xform[i].color_speed=0;
if (result->xform[i].color_speed>1) result->xform[i].color_speed=1;
INTERP(xform[i].opacity);
INTERP(xform[i].animate);
INTERP(xform[i].blob_low);
INTERP(xform[i].blob_high);
INTERP(xform[i].blob_waves);
INTERP(xform[i].pdj_ac[0]);
INTERP(xform[i].pdj_bd[0]);
INTERP(xform[i].pdj_ac[1]);
INTERP(xform[i].pdj_bd[1]);
INTERP(xform[i].fan2_x);
INTERP(xform[i].fan2_y);
INTERP(xform[i].rings2_val);
INTERP(xform[i].perspective_angle);
INTERP(xform[i].perspective_dist);
INTERP(xform[i].julian_power);
INTERP(xform[i].julian_dist);
INTERP(xform[i].juliascope_power);
INTERP(xform[i].juliascope_dist);
INTERP(xform[i].radial_blur_angle);
INTERP(xform[i].pie_slices);
INTERP(xform[i].pie_rotation);
INTERP(xform[i].pie_thickness);
INTERP(xform[i].ngon_sides);
INTERP(xform[i].ngon_power);
INTERP(xform[i].ngon_circle);
INTERP(xform[i].ngon_corners);
INTERP(xform[i].curl_c1);
INTERP(xform[i].curl_c2);
INTERP(xform[i].rectangles_x);
INTERP(xform[i].rectangles_y);
INTERP(xform[i].disc2_rot);
INTERP(xform[i].disc2_twist);
INTERP(xform[i].super_shape_rnd);
INTERP(xform[i].super_shape_m);
INTERP(xform[i].super_shape_n1);
INTERP(xform[i].super_shape_n2);
INTERP(xform[i].super_shape_n3);
INTERP(xform[i].super_shape_holes);
INTERP(xform[i].flower_petals);
INTERP(xform[i].flower_holes);
INTERP(xform[i].conic_eccentricity);
INTERP(xform[i].conic_holes);
INTERP(xform[i].parabola_height);
INTERP(xform[i].parabola_width);
INTERP(xform[i].bent2_x);
INTERP(xform[i].bent2_y);
INTERP(xform[i].bipolar_shift);
INTERP(xform[i].cell_size);
INTERP(xform[i].cpow_r);
INTERP(xform[i].cpow_i);
INTERP(xform[i].cpow_power);
INTERP(xform[i].curve_xamp);
INTERP(xform[i].curve_yamp);
INTERP(xform[i].curve_xlength);
INTERP(xform[i].curve_ylength);
INTERP(xform[i].escher_beta);
INTERP(xform[i].lazysusan_x);
INTERP(xform[i].lazysusan_y);
INTERP(xform[i].lazysusan_twist);
INTERP(xform[i].lazysusan_space);
INTERP(xform[i].lazysusan_spin);
INTERP(xform[i].modulus_x);
INTERP(xform[i].modulus_y);
INTERP(xform[i].oscope_separation);
INTERP(xform[i].oscope_frequency);
INTERP(xform[i].oscope_amplitude);
INTERP(xform[i].oscope_damping);
INTERP(xform[i].popcorn2_x);
INTERP(xform[i].popcorn2_y);
INTERP(xform[i].popcorn2_c);
INTERP(xform[i].separation_x);
INTERP(xform[i].separation_xinside);
INTERP(xform[i].separation_y);
INTERP(xform[i].separation_yinside);
INTERP(xform[i].split_xsize);
INTERP(xform[i].split_ysize);
INTERP(xform[i].splits_x);
INTERP(xform[i].splits_y);
INTERP(xform[i].stripes_space);
INTERP(xform[i].stripes_warp);
INTERP(xform[i].wedge_angle);
INTERP(xform[i].wedge_hole);
INTERP(xform[i].wedge_count);
INTERP(xform[i].wedge_swirl);
INTERP(xform[i].wedge_julia_angle);
INTERP(xform[i].wedge_julia_count);
INTERP(xform[i].wedge_julia_power);
INTERP(xform[i].wedge_julia_dist);
INTERP(xform[i].wedge_sph_angle);
INTERP(xform[i].wedge_sph_hole);
INTERP(xform[i].wedge_sph_count);
INTERP(xform[i].wedge_sph_swirl);
INTERP(xform[i].whorl_inside);
INTERP(xform[i].whorl_outside);
INTERP(xform[i].waves2_scalex);
INTERP(xform[i].waves2_scaley);
INTERP(xform[i].waves2_freqx);
INTERP(xform[i].waves2_freqy);
INTERP(xform[i].auger_sym);
INTERP(xform[i].auger_weight);
INTERP(xform[i].auger_freq);
INTERP(xform[i].auger_scale);
INTERP(xform[i].flux_spread);
INTERP(xform[i].mobius_re_a);
INTERP(xform[i].mobius_im_a);
INTERP(xform[i].mobius_re_b);
INTERP(xform[i].mobius_im_b);
INTERP(xform[i].mobius_re_c);
INTERP(xform[i].mobius_im_c);
INTERP(xform[i].mobius_re_d);
INTERP(xform[i].mobius_im_d);
INTERP(xform[i].asteria_alpha);
INTERP(xform[i].bcollide_num);
INTERP(xform[i].bcollide_a);
for (j = 0; j < flam3_nvariations; j++)
INTERP(xform[i].var[j]);
if (flam3_inttype_log == cpi[0].interpolation_type) {
double cxmag[4][2]; // XXX why only 4? should be ncp
double cxang[4][2];
double cxtrn[4][2];
/* affine part */
clear_matrix(result->xform[i].c);
convert_linear_to_polar(cpi,ncp,i,0,cxang,cxmag,cxtrn);
interp_and_convert_back(c, ncp, i, cxang, cxmag, cxtrn,result->xform[i].c);
/* post part */
all_id = 1;
for (k=0; kxform[i].post);
if (all_id) {
result->xform[i].post[0][0] = 1.0;
result->xform[i].post[1][1] = 1.0;
} else {
convert_linear_to_polar(cpi,ncp,i,1,cxang,cxmag,cxtrn);
interp_and_convert_back(c, ncp, i, cxang, cxmag, cxtrn,result->xform[i].post);
}
} else {
/* Interpolate c matrix & post */
clear_matrix(result->xform[i].c);
clear_matrix(result->xform[i].post);
all_id = 1;
for (k = 0; k < ncp; k++) {
sum_matrix(c[k], cpi[k].xform[i].c, result->xform[i].c);
sum_matrix(c[k], cpi[k].xform[i].post, result->xform[i].post);
all_id &= id_matrix(cpi[k].xform[i].post);
}
if (all_id) {
clear_matrix(result->xform[i].post);
result->xform[i].post[0][0] = 1.0;
result->xform[i].post[1][1] = 1.0;
}
}
if (ncp==2 && stagger>0 && i!=cpi[0].final_xform_index) {
c[0] = csave[0];
c[1] = csave[1];
}
}
}
static void establish_asymmetric_refangles(flam3_genome *cp, int ncps) {
int k, xfi, col;
double cxang[4][2],d,c1[2];
for (xfi=0; xfi M_PI+EPS)
cxang[k][col] -= 2*M_PI;
else if (d < -(M_PI-EPS) )
cxang[k][col] += 2*M_PI;
/* If this is an asymmetric case, store the NON-symmetric angle */
/* Check them pairwise and store the reference angle in the second */
/* to avoid overwriting if asymmetric on both sides */
padsymflag = 0;
sym0 = (cp[k-1].xform[xfi].animate==0 || (cp[k-1].xform[xfi].padding==1 && padsymflag));
sym1 = (cp[k].xform[xfi].animate==0 || (cp[k].xform[xfi].padding==1 && padsymflag));
if ( sym1 && !sym0 )
cp[k].xform[xfi].wind[col] = cxang[k-1][col] + 2*M_PI;
else if ( sym0 && !sym1 )
cp[k].xform[xfi].wind[col] = cxang[k][col] + 2*M_PI;
}
}
}
}
void flam3_align(flam3_genome *dst, flam3_genome *src, int nsrc) {
int i, tfx, tnx, max_nx = 0, max_fx = 0;
int already_aligned=1;
int xf,j;
int ii,fnd;
double normed;
int usethisone;
usethisone = (nsrc/2) - 1;
max_nx = src[0].num_xforms - (src[0].final_xform_index >= 0);
max_fx = src[0].final_xform_enable;
for (i = 1; i < nsrc; i++) {
tnx = src[i].num_xforms - (src[i].final_xform_index >= 0);
if (max_nx != tnx) {
already_aligned = 0;
if (tnx > max_nx) max_nx = tnx;
}
tfx = src[i].final_xform_enable;
if (max_fx != tfx) {
already_aligned = 0;
max_fx |= tfx;
}
}
/* Pad the cps to equal xforms */
for (i = 0; i < nsrc; i++) {
flam3_copyx(&dst[i], &src[i], max_nx, max_fx);
}
/* Skip if this genome is compatibility mode */
if (dst[usethisone].interpolation_type == flam3_inttype_compat ||
dst[usethisone].interpolation_type == flam3_inttype_older)
return;
/* Check to see if there's a parametric variation present in one xform */
/* but not in an aligned xform. If this is the case, use the parameters */
/* from the xform with the variation as the defaults for the blank one. */
/* All genomes will have the same number of xforms at this point */
/* num = max_nx + max_fx */
for (i = 0; i0) {
/* Check to see if the prior genome's xform is populated */
if (dst[i-1].xform[xf].var[j] != 0) {
/* Copy the prior genome's parameters and continue */
flam3_copy_params(&(dst[i].xform[xf]), &(dst[i-1].xform[xf]), j);
continue;
}
} else if (i0 && dst[i-1].interpolation_type==flam3_inttype_log) ) {
for (ii=-1; ii<=1; ii+=2) {
/* Skip if out of bounds */
if (i+ii<0 || i+ii>=nsrc)
continue;
/* Skip if this is also padding */
if (dst[i+ii].xform[xf].padding==1)
continue;
/* Spherical / Ngon (trumps all others due to holes) */
/* Interpolate these against a 180 degree rotated identity */
/* with weight -1. */
/* Added JULIAN/JULIASCOPE to get rid of black wedges */
if (dst[i+ii].xform[xf].var[VAR_SPHERICAL]>0 ||
dst[i+ii].xform[xf].var[VAR_NGON]>0 ||
dst[i+ii].xform[xf].var[VAR_JULIAN]>0 ||
dst[i+ii].xform[xf].var[VAR_JULIASCOPE]>0 ||
dst[i+ii].xform[xf].var[VAR_POLAR]>0 ||
dst[i+ii].xform[xf].var[VAR_WEDGE_SPH]>0 ||
dst[i+ii].xform[xf].var[VAR_WEDGE_JULIA]>0) {
dst[i].xform[xf].var[VAR_LINEAR] = -1.0;
/* Set the coefs appropriately */
dst[i].xform[xf].c[0][0] = -1.0;
dst[i].xform[xf].c[0][1] = 0.0;
dst[i].xform[xf].c[1][0] = 0.0;
dst[i].xform[xf].c[1][1] = -1.0;
dst[i].xform[xf].c[2][0] = 0.0;
dst[i].xform[xf].c[2][1] = 0.0;
fnd=-1;
}
}
}
if (fnd==0) {
for (ii=-1; ii<=1; ii+=2) {
/* Skip if out of bounds */
if (i+ii<0 || i+ii>=nsrc)
continue;
/* Skip if also padding */
if (dst[i+ii].xform[xf].padding==1)
continue;
/* Rectangles */
if (dst[i+ii].xform[xf].var[VAR_RECTANGLES]>0) {
dst[i].xform[xf].var[VAR_RECTANGLES] = 1.0;
dst[i].xform[xf].rectangles_x = 0.0;
dst[i].xform[xf].rectangles_y = 0.0;
fnd++;
}
/* Rings 2 */
if (dst[i+ii].xform[xf].var[VAR_RINGS2]>0) {
dst[i].xform[xf].var[VAR_RINGS2] = 1.0;
dst[i].xform[xf].rings2_val = 0.0;
fnd++;
}
/* Fan 2 */
if (dst[i+ii].xform[xf].var[VAR_FAN2]>0) {
dst[i].xform[xf].var[VAR_FAN2] = 1.0;
dst[i].xform[xf].fan2_x = 0.0;
dst[i].xform[xf].fan2_y = 0.0;
fnd++;
}
/* Blob */
if (dst[i+ii].xform[xf].var[VAR_BLOB]>0) {
dst[i].xform[xf].var[VAR_BLOB] = 1.0;
dst[i].xform[xf].blob_low = 1.0;
dst[i].xform[xf].blob_high = 1.0;
dst[i].xform[xf].blob_waves = 1.0;
fnd++;
}
/* Perspective */
if (dst[i+ii].xform[xf].var[VAR_PERSPECTIVE]>0) {
dst[i].xform[xf].var[VAR_PERSPECTIVE] = 1.0;
dst[i].xform[xf].perspective_angle = 0.0;
/* Keep the perspective distance as-is */
fnd++;
}
/* Curl */
if (dst[i+ii].xform[xf].var[VAR_CURL]>0) {
dst[i].xform[xf].var[VAR_CURL] = 1.0;
dst[i].xform[xf].curl_c1 = 0.0;
dst[i].xform[xf].curl_c2 = 0.0;
fnd++;
}
/* Super-Shape */
if (dst[i+ii].xform[xf].var[VAR_SUPER_SHAPE]>0) {
dst[i].xform[xf].var[VAR_SUPER_SHAPE] = 1.0;
/* Keep supershape_m the same */
dst[i].xform[xf].super_shape_n1 = 2.0;
dst[i].xform[xf].super_shape_n2 = 2.0;
dst[i].xform[xf].super_shape_n3 = 2.0;
dst[i].xform[xf].super_shape_rnd = 0.0;
dst[i].xform[xf].super_shape_holes = 0.0;
fnd++;
}
}
}
/* If we didn't have any matches with those, */
/* try the affine ones, fan and rings */
if (fnd==0) {
for (ii=-1; ii<=1; ii+=2) {
/* Skip if out of bounds */
if (i+ii<0 || i+ii>=nsrc)
continue;
/* Skip if also a padding xform */
if (dst[i+ii].xform[xf].padding==1)
continue;
/* Fan */
if (dst[i+ii].xform[xf].var[VAR_FAN]>0) {
dst[i].xform[xf].var[VAR_FAN] = 1.0;
fnd++;
}
/* Rings */
if (dst[i+ii].xform[xf].var[VAR_RINGS]>0) {
dst[i].xform[xf].var[VAR_RINGS] = 1.0;
fnd++;
}
}
if (fnd>0) {
/* Set the coefs appropriately */
dst[i].xform[xf].c[0][0] = 0.0;
dst[i].xform[xf].c[0][1] = 1.0;
dst[i].xform[xf].c[1][0] = 1.0;
dst[i].xform[xf].c[1][1] = 0.0;
dst[i].xform[xf].c[2][0] = 0.0;
dst[i].xform[xf].c[2][1] = 0.0;
}
}
/* If we still have no matches, switch back to linear */
if (fnd==0)
dst[i].xform[xf].var[VAR_LINEAR] = 1.0;
else if (fnd>0) {
/* Otherwise, go through and normalize the weights. */
normed = 0.0;
for (j = 0; j < flam3_nvariations; j++)
normed += dst[i].xform[xf].var[j];
for (j = 0; j < flam3_nvariations; j++)
dst[i].xform[xf].var[j] /= normed;
}
}
} /* xforms */
} /* genomes */
}