/*
FLAM3 - cosmic recursive fractal flames
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 "private.h"
#include "palettes.h"
lib_palette *the_palettes = NULL;
int npalettes;
static void parse_palettes(xmlNode *node) {
xmlAttrPtr attr;
char *val;
lib_palette *pal;
while (node) {
if (node->type == XML_ELEMENT_NODE && !xmlStrcmp(node->name, (const xmlChar *)"palette")) {
attr = node->properties;
pal = &the_palettes[npalettes];
memset(pal, 0, sizeof(lib_palette));
while (attr) {
val = (char *) xmlGetProp(node, attr->name);
if (!xmlStrcmp(attr->name, (const xmlChar *)"data")) {
int count = 256;
int c_idx = 0;
int r,g,b;
int col_count = 0;
int sscanf_ret;
c_idx=0;
col_count = 0;
do {
sscanf_ret = sscanf((char *)&(val[c_idx]),"00%2x%2x%2x",&r,&g,&b);
if (sscanf_ret != 3) {
printf("Error: Problem reading hexadecimal color data.\n");
exit(1);
}
c_idx += 8;
while (isspace( (int)val[c_idx]))
c_idx++;
pal->colors[col_count][0] = r;
pal->colors[col_count][1] = g;
pal->colors[col_count][2] = b;
col_count++;
} while (col_countname, (const xmlChar *)"number")) {
pal->number = atoi(val);
} else if (!xmlStrcmp(attr->name, (const xmlChar *)"name")) {
strncpy(pal->name, val, flam3_name_len);
pal->name[flam3_name_len-1] = 0;
}
xmlFree(val);
attr = attr->next;
}
npalettes++;
the_palettes = realloc(the_palettes, (1 + npalettes) * sizeof(lib_palette));
} else
parse_palettes(node->children);
node = node->next;
}
}
static int init_palettes(char *filename) {
FILE *fp;
xmlDocPtr doc;
xmlNode *rootnode;
int i, c, slen = 5000;
char *s;
fp = fopen(filename, "rb");
if (NULL == fp) {
fprintf(stderr, "flam3: could not open palette file ");
perror(filename);
exit(1);
}
/* Incrementally read XML file into a string */
s = malloc(slen);
i = 0;
do {
c = getc(fp);
if (EOF == c) {
if (ferror(fp)) {
perror(filename);
exit(1);
}
break;
}
s[i++] = c;
if (i == slen-1) {
slen *= 2;
s = realloc(s, slen);
}
} while (1);
fclose(fp);
s[i] = 0;
doc = xmlReadMemory(s, (int)strlen(s), filename, NULL, XML_PARSE_NONET);
if (NULL == doc) {
fprintf(stderr, "error parsing %s (%s).\n", filename, s);
exit(1);
}
rootnode = xmlDocGetRootElement(doc);
the_palettes = malloc(sizeof(lib_palette));
npalettes = 0;
parse_palettes(rootnode);
xmlFreeDoc(doc);
free(s);
xmlCleanupParser();
return(1);
}
int flam3_get_palette(int n, flam3_palette c, double hue_rotation) {
int cmap_len = 256;
int idx, i, j;
if (NULL == the_palettes) {
char *d = getenv("flam3_palettes");
init_palettes(d ? d : (PACKAGE_DATA_DIR "/flam3-palettes.xml"));
}
if (flam3_palette_random == n)
n = the_palettes[random()%npalettes].number;
for (idx = 0; idx < npalettes; idx++) {
if (n == the_palettes[idx].number) {
/* Loop over elements of cmap */
for (i = 0; i < cmap_len; i++) {
int ii = (i * 256) / cmap_len;
double rgb[3], hsv[3];
/* Colors are in 0-1 space */
for (j = 0; j < 3; j++)
rgb[j] = the_palettes[idx].colors[ii][j] / 255.0;
rgb2hsv(rgb, hsv);
hsv[0] += hue_rotation * 6.0;
hsv2rgb(hsv, rgb);
c[i].index = i;
for (j = 0; j < 3; j++)
c[i].color[j] = rgb[j];
c[i].color[3] = 1.0;
}
return n;
}
}
fprintf(stderr, "warning: palette number %d not found, using white.\n", n);
for (i = 0; i < cmap_len; i++) {
c[i].index = i;
for (j = 0; j < 4; j++)
c[i].color[j] = 1.0;
}
return flam3_palette_random;
}
/* rgb 0 - 1,
h 0 - 6, s 0 - 1, v 0 - 1 */
void rgb2hsv(rgb, hsv)
double *rgb; double *hsv;
{
double rd, gd, bd, h, s, v, max, min, del, rc, gc, bc;
rd = rgb[0];
gd = rgb[1];
bd = rgb[2];
/* compute maximum of rd,gd,bd */
if (rd>=gd) { if (rd>=bd) max = rd; else max = bd; }
else { if (gd>=bd) max = gd; else max = bd; }
/* compute minimum of rd,gd,bd */
if (rd<=gd) { if (rd<=bd) min = rd; else min = bd; }
else { if (gd<=bd) min = gd; else min = bd; }
del = max - min;
v = max;
if (max != 0.0) s = (del) / max;
else s = 0.0;
h = 0;
if (s != 0.0) {
rc = (max - rd) / del;
gc = (max - gd) / del;
bc = (max - bd) / del;
if (rd==max) h = bc - gc;
else if (gd==max) h = 2 + rc - bc;
else if (bd==max) h = 4 + gc - rc;
if (h<0) h += 6;
}
hsv[0] = h;
hsv[1] = s;
hsv[2] = v;
}
/* h 0 - 6, s 0 - 1, v 0 - 1
rgb 0 - 1 */
void hsv2rgb(hsv, rgb)
double *hsv;
double *rgb;
{
double h = hsv[0], s = hsv[1], v = hsv[2];
int j;
double rd, gd, bd;
double f, p, q, t;
while (h >= 6.0) h = h - 6.0;
while (h < 0.0) h = h + 6.0;
j = (int) floor(h);
f = h - j;
p = v * (1-s);
q = v * (1 - (s*f));
t = v * (1 - (s*(1 - f)));
switch (j) {
case 0: rd = v; gd = t; bd = p; break;
case 1: rd = q; gd = v; bd = p; break;
case 2: rd = p; gd = v; bd = t; break;
case 3: rd = p; gd = q; bd = v; break;
case 4: rd = t; gd = p; bd = v; break;
case 5: rd = v; gd = p; bd = q; break;
default: rd = v; gd = t; bd = p; break;
}
rgb[0] = rd;
rgb[1] = gd;
rgb[2] = bd;
}
double flam3_calc_alpha(double density, double gamma, double linrange) {
double dnorm = density;
double funcval = pow(linrange, gamma);
double frac,alpha;
if (dnorm>0) {
if (dnorm < linrange) {
frac = dnorm/linrange;
alpha = (1.0-frac) * dnorm * (funcval / linrange) + frac * pow(dnorm,gamma);
} else
alpha = pow(dnorm,gamma);
} else
alpha = 0;
return(alpha);
}
void flam3_calc_newrgb(double *cbuf, double ls, double highpow, double *newrgb) {
int rgbi;
double newls,lsratio;
double newhsv[3];
double a, maxa=-1.0, maxc=0;
double adjhlp;
if (ls==0.0 || (cbuf[0]==0.0 && cbuf[1]==0.0 && cbuf[2]==0.0)) {
newrgb[0] = 0.0;
newrgb[1] = 0.0;
newrgb[2] = 0.0;
return;
}
/* Identify the most saturated channel */
for (rgbi=0;rgbi<3;rgbi++) {
a = ls * (cbuf[rgbi]/PREFILTER_WHITE);
if (a>maxa) {
maxa = a;
maxc = cbuf[rgbi]/PREFILTER_WHITE;
}
}
/* If a channel is saturated and we have a non-negative highlight power */
/* modify the color to prevent hue shift */
if (maxa>255 && highpow>=0.0) {
newls = 255.0/maxc;
lsratio = pow(newls/ls,highpow);
/* Calculate the max-value color (ranged 0 - 1) */
for (rgbi=0;rgbi<3;rgbi++)
newrgb[rgbi] = newls*(cbuf[rgbi]/PREFILTER_WHITE)/255.0;
/* Reduce saturation by the lsratio */
rgb2hsv(newrgb,newhsv);
newhsv[1] *= lsratio;
hsv2rgb(newhsv,newrgb);
for (rgbi=0;rgbi<3;rgbi++)
newrgb[rgbi] *= 255.0;
} else {
newls = 255.0/maxc;
adjhlp = -highpow;
if (adjhlp>1)
adjhlp=1;
if (maxa<=255)
adjhlp=1.0;
/* Calculate the max-value color (ranged 0 - 1) interpolated with the old behaviour */
for (rgbi=0;rgbi<3;rgbi++)
newrgb[rgbi] = ((1.0-adjhlp)*newls + adjhlp*ls)*(cbuf[rgbi]/PREFILTER_WHITE);
// for (rgbi=0;rgbi<3;rgbi++)
// newrgb[rgbi] = ls*(cbuf[rgbi]/PREFILTER_WHITE);
}
}
static int random_xform(flam3_genome *g, int excluded) {
int ntries = 0;
while (ntries++ < 100) {
int i = random() % g->num_xforms;
if (g->xform[i].density > 0.0 && i != excluded)
return i;
}
return -1;
}
static double try_colors(flam3_genome *g, int color_resolution) {
int *hist;
int i, hits, res = color_resolution;
int res3 = res * res * res;
flam3_frame f;
unsigned char *image, *p;
flam3_genome saved;
double scalar;
int pixtotal;
stat_struct stats;
memset(&saved, 0, sizeof(flam3_genome));
flam3_copy(&saved, g);
g->sample_density = 1;
g->spatial_oversample = 1;
g->estimator = 0.0;
/* Scale the image so that the total number of pixels is ~10000 */
pixtotal = g->width * g->height;
scalar = sqrt( 10000.0 / (double)pixtotal);
g->width *= scalar;
g->height *= scalar;
g->pixels_per_unit *= scalar;
// g->width = 100; // XXX keep aspect ratio
// g->height = 100;
// g->pixels_per_unit = 50;
g->nbatches = 1;
g->ntemporal_samples = 1;
// f.temporal_filter_radius = 0.0;
flam3_init_frame(&f);
f.bits = 33;
f.bytes_per_channel=1;
f.verbose = 0;
f.genomes = g;
f.ngenomes = 1;
f.earlyclip = 1;
f.pixel_aspect_ratio = 1.0;
f.progress = 0;
f.nthreads = 1;
f.sub_batch_size = 10000;
image = (unsigned char *) calloc(g->width * g->height, 3);
if (flam3_render(&f, image, flam3_field_both, 3, 0, &stats)) {
fprintf(stderr,"Error rendering test image for trycolors. Aborting.\n");
exit(1);
}
hist = calloc(sizeof(int), res3);
p = image;
for (i = 0; i < g->height * g->width; i++) {
hist[(p[0] * res / 256) +
(p[1] * res / 256) * res +
(p[2] * res / 256) * res * res]++;
p += 3;
}
if (0) {
int j, k;
for (i = 0; i < res; i++) {
fprintf(stderr, "\ni=%d: \n", i);
for (j = 0; j < res; j++) {
for (k = 0; k < res; k++) {
fprintf(stderr, " %5d", hist[i * res * res + j * res + k]);
}
fprintf(stderr, "\n");
}
}
}
hits = 0;
for (i = 0; i < res3; i++) {
if (hist[i]) hits++;
}
free(hist);
free(image);
g->sample_density = saved.sample_density;
g->width = saved.width;
g->height = saved.height;
g->spatial_oversample = saved.spatial_oversample;
g->pixels_per_unit = saved.pixels_per_unit;
g->nbatches = saved.nbatches;
g->ntemporal_samples = saved.ntemporal_samples;
g->estimator = saved.estimator;
/* Free xform storage */
clear_cp(&saved,flam3_defaults_on);
return (double) hits / res3;
}
static void change_colors(flam3_genome *g, int change_palette) {
int i;
int x0, x1;
if (change_palette) {
g->hue_rotation = 0.0;
g->palette_index = flam3_get_palette(flam3_palette_random, g->palette, 0.0);
}
for (i = 0; i < g->num_xforms; i++) {
g->xform[i].color = flam3_random01();
}
x0 = random_xform(g, -1);
x1 = random_xform(g, x0);
if (x0 >= 0 && (random()&1)) g->xform[x0].color = 0.0;
if (x1 >= 0 && (random()&1)) g->xform[x1].color = 1.0;
}
void flam3_improve_colors(flam3_genome *g, int ntries, int change_palette, int color_resolution) {
int i;
double best, b;
flam3_genome best_genome;
memset(&best_genome, 0, sizeof(flam3_genome));
best = try_colors(g, color_resolution);
flam3_copy(&best_genome,g);
for (i = 0; i < ntries; i++) {
change_colors(g, change_palette);
b = try_colors(g, color_resolution);
if (b > best) {
best = b;
flam3_copy(&best_genome,g);
}
}
flam3_copy(g,&best_genome);
clear_cp(&best_genome,flam3_defaults_on);
}