/*
Copyright (C) 1992-2009 Spotworks LLC
Copyright (C) 2015 pucket contributors
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
#include
#include "math.h"
#include "palettes.h"
#include "rect.h"
/* Add color to the end of palette
*/
void palette_add (palette * const p, const double4 c) {
++p->count;
if (p->count > p->allocated || p->color == NULL) {
double4 *newcolors;
if (p->allocated == 0) {
/* flam3’s default palette size */
p->allocated = 256;
} else {
p->allocated *= 2;
}
int ret = posix_memalign ((void **) &newcolors, sizeof (*newcolors),
sizeof (*newcolors) * p->allocated);
assert (ret == 0 && newcolors != NULL);
memcpy (newcolors, p->color,
(p->count-1)*sizeof (*p->color));
free (p->color);
p->color = newcolors;
}
p->color[p->count-1] = c;
}
static void parse_palettes(xmlNode *node, palette_collection * const pc) {
/* node can be NULL */
assert (pc != NULL);
while (node) {
if (node->type == XML_ELEMENT_NODE &&
!xmlStrcmp(node->name, (const xmlChar *)"palette")) {
xmlAttrPtr attr = node->properties;
++pc->count;
pc->p = realloc (pc->p, pc->count * sizeof (*pc->p));
assert (pc->p != NULL);
palette * const p = &pc->p[pc->count-1];
memset (p, 0, sizeof (*p));
while (attr) {
char *val = (char *) xmlGetProp(node, attr->name);
if (!xmlStrcmp(attr->name, (const xmlChar *)"data")) {
int c_idx = 0;
while (val[c_idx] != '\0') {
if (isspace( (int)val[c_idx])) {
c_idx++;
} else {
unsigned int r,g,b;
const int sscanf_ret = sscanf((char *)&(val[c_idx]),
"00%2x%2x%2x",&r,&g,&b);
assert (sscanf_ret == 3);
palette_add (p, (double4) { (double) r/255.0,
(double) g/255.0, (double) b/255.0, 1.0 });
c_idx += 8;
}
}
}
xmlFree(val);
attr = attr->next;
}
} else
parse_palettes(node->children, pc);
node = node->next;
}
}
/* Read palette collection from file
*/
bool palette_read_collection (const char * const filename,
palette_collection * const pc) {
xmlDocPtr doc;
xmlNode *rootnode;
if ((doc = xmlReadFile (filename, NULL, XML_PARSE_NONET)) == NULL) {
return false;
}
memset (pc, 0, sizeof (*pc));
rootnode = xmlDocGetRootElement(doc);
parse_palettes (rootnode, pc);
xmlFreeDoc(doc);
return true;
}
const palette *palette_random (const palette_collection * const pc,
randctx * const rc) {
size_t i = rand_mod (rc, pc->count);
return &pc->p[i];
}
void palette_copy (const palette * restrict const src, palette * restrict const dest) {
assert (src != NULL);
assert (dest != NULL);
*dest = *src;
if (src->name != NULL) {
dest->name = strdup (src->name);
}
if (src->color != NULL) {
int ret = posix_memalign ((void **) &dest->color, sizeof (*dest->color),
sizeof (*dest->color) * dest->count);
assert (ret == 0 && dest->color != NULL);
memcpy (dest->color, src->color, dest->count * sizeof (*dest->color));
}
}
void palette_rotate_hue (palette * const p, double rot) {
for (unsigned int i = 0; i < p->count; i++) {
double4 rgb = p->color[i];
double4 hsv = rgb2hsv(rgb);
hsv[0] += rot * 6.0;
rgb = hsv2rgb(hsv);
p->color[i] = rgb;
}
}
/* rgb 0 - 1,
h 0 - 6, s 0 - 1, v 0 - 1 */
double4 rgb2hsv(double4 rgb)
{
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;
}
return (double4) { h, s, v, rgb[3] };
}
/* h 0 - 6, s 0 - 1, v 0 - 1
rgb 0 - 1 */
double4 hsv2rgb(double4 hsv)
{
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;
}
return (double4) { rd, gd, bd, hsv[3] };
}
static int random_xform(flam3_genome *g, int excluded, randctx * const rc) {
int ntries = 0;
while (ntries++ < 100) {
int i = rand_mod(rc, g->num_xforms);
if (g->xform[i].density > 0.0 && i != excluded)
return i;
}
return -1;
}
static double try_colors(flam3_genome *g, unsigned int color_resolution,
double timelimit) {
assert (g != NULL);
assert (color_resolution > 0);
int *hist;
unsigned int res = color_resolution, res3 = res * res * res;
unsigned char *image, *p;
flam3_genome saved;
int pixtotal;
memset(&saved, 0, sizeof(flam3_genome));
flam3_copy(&saved, g);
/* Scale the image so that the total number of pixels is ~10000 */
pixtotal = g->width * g->height;
const double scalar = sqrt( 10000.0 / (double)pixtotal);
g->width *= scalar;
g->height *= scalar;
g->pixels_per_unit *= scalar;
const unsigned int bytes_per_channel=1;
const unsigned int channels = 4;
image = (unsigned char *) calloc(g->width * g->height, bytes_per_channel * channels);
bucket bucket;
bucket_init (&bucket, (uint2) { g->width, g->height });
render_bucket (g, &bucket, timelimit);
render_image (g, &bucket, image, bytes_per_channel);
hist = calloc(sizeof(int), res3);
p = image;
for (unsigned int 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 += channels;
}
unsigned int hits = 0;
for (unsigned int i = 0; i < res3; i++) {
if (hist[i]) hits++;
}
free(hist);
free(image);
g->width = saved.width;
g->height = saved.height;
g->pixels_per_unit = saved.pixels_per_unit;
/* Free xform storage */
clear_cp(&saved,flam3_defaults_on);
return (double) hits / (double) res3;
}
static void change_colors(flam3_genome *g, bool change_palette,
const palette_collection * const pc, randctx * const rc) {
int i;
int x0, x1;
if (change_palette) {
g->hue_rotation = 0.0;
const palette * const p = palette_random (pc, rc);
assert (p != NULL);
palette_copy (p, &g->palette);
}
for (i = 0; i < g->num_xforms; i++) {
g->xform[i].color = rand_d01(rc);
}
x0 = random_xform(g, -1, rc);
x1 = random_xform(g, x0, rc);
if (x0 >= 0 && rand_bool(rc)) g->xform[x0].color = 0.0;
if (x1 >= 0 && rand_bool(rc)) g->xform[x1].color = 1.0;
}
void flam3_improve_colors(flam3_genome *g, unsigned int ntries,
bool change_palette,
unsigned int color_resolution, double timelimit,
const palette_collection * const pc,
randctx * const rc) {
const double trytime = timelimit/(double) ntries;
flam3_genome best_genome;
memset(&best_genome, 0, sizeof(flam3_genome));
double best = try_colors(g, color_resolution, trytime);
assert (best >= 0.0);
flam3_copy(&best_genome,g);
for (unsigned int i = 0; i < ntries; i++) {
change_colors(g, change_palette, pc, rc);
const double b = try_colors(g, color_resolution, trytime);
assert (b >= 0.0);
if (b < 0) {
fprintf(stderr,"error in try_colors, aborting tries\n");
break;
}
if (b > best) {
best = b;
flam3_copy(&best_genome,g);
}
}
flam3_copy(g,&best_genome);
clear_cp(&best_genome,flam3_defaults_on);
}