/* 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 #include #include #include #include #include #include "random.h" #include "img.h" #include "rect.h" #include "math.h" #include "genome.h" #include "palettes_builtin.h" #define streq(a,b) (strcmp (a, b) == 0) const char *argp_program_version = PACKAGE "-" VERSION; typedef struct { unsigned int bpc, width, height; float scale, time; char *cache; } render_arguments; static error_t parse_render_opt (int key, char *arg, struct argp_state * const state) { render_arguments * const arguments = state->input; switch (key) { case 'b': { int i = atoi (arg); if (i == 8 || i == 16) { arguments->bpc = i; } else { argp_error (state, "Bits per channel must be 8 or 16"); } break; } case 't': { float i = atof (arg); if (i <= 0) { argp_error (state, "Time must be > 0"); } else { arguments->time = i; } break; } case 's': if (arguments->width != 0 || arguments->height != 0) { argp_error (state, "scale, width and height are mutually " "exclusive"); } arguments->scale = atof (arg); if (arguments->scale <= 0.0) { argp_error (state, "Scale must be > 0"); } break; case 'w': { if (arguments->scale != 1.0 || arguments->height != 0) { argp_error (state, "scale, width and height are mutually " "exclusive"); } int i = atoi (arg); if (i <= 0) { argp_error (state, "width must be > 0"); } arguments->width = i; break; } case 'h': { if (arguments->scale != 1.0 || arguments->width != 0) { argp_error (state, "scale, width and height are mutually " "exclusive"); } int i = atoi (arg); if (i <= 0) { argp_error (state, "height must be > 0"); } arguments->height = i; break; } case 'c': arguments->cache = strdup (arg); break; case ARGP_KEY_ARG: if (state->arg_num > 0) { return ARGP_ERR_UNKNOWN; } break; case ARGP_KEY_END: break; default: return ARGP_ERR_UNKNOWN; break; } return 0; } static void do_render (const render_arguments * const arguments) { randctx rc; rand_seed(&rc); int ncps; flam3_genome * const cps = flam3_parse_xml2 (STDIN_FILENO, flam3_defaults_on, &ncps, &rc); if (cps == NULL) { fprintf(stderr,"error reading genomes from file\n"); exit(1); } assert (ncps == 1); flam3_genome * const genome = &cps[0]; float scale = arguments->scale; if (arguments->width != 0) { assert (scale == 1); assert (arguments->height == 0); scale = (float) arguments->width / (float) genome->width; } if (arguments->height != 0) { assert (scale == 1); assert (arguments->width == 0); scale = (float) arguments->height / (float) genome->height; } genome->height *= scale; genome->width *= scale; genome->pixels_per_unit *= scale; const unsigned int bytes_per_channel = arguments->bpc/8; const unsigned int channels = 4; const size_t this_size = channels * genome->width * genome->height * bytes_per_channel; void *image = (void *) calloc(this_size, sizeof(char)); bucket bucket; bucket_init (&bucket, (uint2) { genome->width, genome->height }); if (arguments->cache != NULL) { bucket_deserialize (&bucket, arguments->cache); } render_bucket (genome, &bucket, arguments->time); if (arguments->cache != NULL) { bucket_serialize (&bucket, arguments->cache); } fprintf (stderr, "%lu samples, %lu bad\n", bucket.samples, bucket.badvals); render_image (genome, &bucket, image, bytes_per_channel); write_png (stdout, image, genome->width, genome->height, bytes_per_channel); } static void print_genome (flam3_genome * const genome) { printf("\n"); flam3_print (stdout, genome, NULL); printf("\n"); } typedef struct { const char *palette; unsigned int width, height, max_xforms, max_var; signed int max_symmetry, min_symmetry; double post_likelihood, final_likelihood, symmetry_likelihood; } random_arguments; static error_t parse_random_opt (int key, char *arg, struct argp_state * const state) { random_arguments * const arguments = state->input; switch (key) { case 'h': { int i = atoi (arg); if (i <= 0) { argp_error (state, "Height must be > 0"); } else { arguments->height = i; } break; } case 'w': { int i = atoi (arg); if (i <= 0) { argp_error (state, "Width must be > 0"); } else { arguments->width = i; } break; } case 'x': { int i = atoi (arg); if (i <= 0) { argp_error (state, "Max xforms must be > 0"); } else { arguments->max_xforms = i; } break; } case 'v': { int i = atoi (arg); if (i <= 0) { argp_error (state, "Max variations must be > 0"); } else { arguments->max_var = i; } break; } case ARGP_KEY_ARG: if (state->arg_num > 0) { return ARGP_ERR_UNKNOWN; } break; case ARGP_KEY_END: break; default: return ARGP_ERR_UNKNOWN; break; } return 0; } #define GOLDEN_RATIO (1.618033988749894848204586834) #define GOLDEN_RATIO_M1 (GOLDEN_RATIO-1.0) #define GOLDEN_RATIO_DIV (GOLDEN_RATIO_M1/GOLDEN_RATIO) static double golden_bit (randctx * const rc) { return rand_bool (rc) ? GOLDEN_RATIO_DIV : GOLDEN_RATIO_M1; } static void adjust_bounding_box (flam3_genome * const genome, randctx * const rc) { double bmin[2], bmax[2]; flam3_estimate_bounding_box(genome, 0.01, 100000, bmin, bmax, rc); if (rand_d01(rc) < 0.3) { genome->center[0] = (bmin[0] + bmax[0]) / 2.0; genome->center[1] = (bmin[1] + bmax[1]) / 2.0; } else { double mix0, mix1; if (rand_bool(rc)) { mix0 = golden_bit(rc) + rand_d11(rc)/5; mix1 = golden_bit(rc); } else if (rand_bool(rc)) { mix0 = golden_bit(rc); mix1 = golden_bit(rc) + rand_d11(rc)/5; } else { mix0 = golden_bit(rc) + rand_d11(rc)/5; mix1 = golden_bit(rc) + rand_d11(rc)/5; } genome->center[0] = mix0 * bmin[0] + (1-mix0)*bmax[0]; genome->center[1] = mix1 * bmin[1] + (1-mix1)*bmax[1]; } const double zoomin = rand_d01 (rc) * 5; genome->pixels_per_unit = genome->width / (bmax[0] - bmin[0]) * zoomin; } static void do_random (const random_arguments * const arguments) { randctx rc; rand_seed(&rc); flam3_genome genome; clear_cp (&genome,flam3_defaults_on); genome.hue_rotation = rand_mod(&rc, 8) ? 0.0 : rand_d01(&rc); const palette * const p = palette_random (&builtin_palettes, &rc); assert (p != NULL); palette_copy (p, &genome.palette); palette_rotate_hue (&genome.palette, genome.hue_rotation); genome.interpolation = flam3_interpolation_linear; genome.rotate = rand_d01 (&rc) * 360.0; unsigned int nxforms = rand_mod (&rc, arguments->max_xforms) + 1; flam3_add_xforms(&genome,nxforms,0,0); /* Add a final xform 15% of the time */ const bool add_final = rand_d01(&rc) < arguments->final_likelihood; if (add_final) { flam3_add_xforms(&genome,1,0,1); ++nxforms; } /* Loop over xforms */ assert (nxforms > 0); for (unsigned int i = 0; i < nxforms; i++) { flam3_xform * const xform = &genome.xform[i]; const bool add_post = rand_d01 (&rc) < arguments->post_likelihood; xform_rand (xform, add_post, arguments->max_var, &rc); xform->density = 1.0 / nxforms; xform->animate = 1.0; } /* Randomly add symmetry (but not if we've already added a final xform) */ if (rand_d01(&rc) < arguments->symmetry_likelihood && !add_final) { assert (arguments->max_symmetry >= arguments->min_symmetry); unsigned int symrange = arguments->max_symmetry - arguments->min_symmetry + 1; flam3_add_symmetry(&genome, rand_mod (&rc, symrange) + arguments->min_symmetry); } /* random resets genome, adjust before finding appropriate bbox */ genome.width = arguments->width; genome.height = arguments->height; adjust_bounding_box (&genome, &rc); print_genome (&genome); } typedef struct { int method; unsigned int symmetry; } mutate_arguments; static error_t parse_mutate_opt (int key, char *arg, struct argp_state * const state) { mutate_arguments * const arguments = state->input; switch (key) { case 'm': if (arg == NULL) { arguments->method = MUTATE_NOT_SPECIFIED; } else if (streq (arg, "all-vars")) { arguments->method = MUTATE_ALL_VARIATIONS; } else if (streq(arg,"one-xform")) { arguments->method = MUTATE_ONE_XFORM_COEFS; } else if (streq(arg,"add-symmetry")) { arguments->method = MUTATE_ADD_SYMMETRY; } else if (streq(arg,"post-xforms")) { arguments->method = MUTATE_POST_XFORMS; } else if (streq(arg,"color-palette")) { arguments->method = MUTATE_COLOR_PALETTE; } else if (streq(arg,"delete-xform")) { arguments->method = MUTATE_DELETE_XFORM; } else if (streq(arg,"all-coefs")) { arguments->method = MUTATE_ALL_COEFS; } else { argp_error (state, "Unknown method %s", arg); } break; case ARGP_KEY_ARG: if (state->arg_num > 0) { return ARGP_ERR_UNKNOWN; } break; case ARGP_KEY_END: break; default: return ARGP_ERR_UNKNOWN; break; } return 0; } static void do_mutate (const mutate_arguments * const arguments) { randctx rc; rand_seed(&rc); int ncps; flam3_genome * const cps = flam3_parse_xml2 (STDIN_FILENO, flam3_defaults_on, &ncps, &rc); if (cps == NULL) { fprintf(stderr,"error reading genomes from file\n"); exit(1); } assert (ncps == 1); flam3_genome * const genome = &cps[0]; int ivars = 0; const double speed = 1.0; flam3_mutate (genome, arguments->method, &ivars, 1, arguments->symmetry, speed, &builtin_palettes, &rc); print_genome (genome); } typedef struct { int method; } cross_arguments; static error_t parse_cross_opt (int key, char *arg, struct argp_state * const state) { mutate_arguments * const arguments = state->input; switch (key) { case 'm': if (arg == NULL) { arguments->method = CROSS_NOT_SPECIFIED; } else if (streq(arg,"union")) { arguments->method = CROSS_UNION; } else if (streq(arg,"interpolate")) { arguments->method = CROSS_INTERPOLATE; } else if (streq(arg,"alternate")) { arguments->method = CROSS_ALTERNATE; } else { argp_error (state, "Unknown method %s", arg); } break; case ARGP_KEY_ARG: if (state->arg_num > 0) { return ARGP_ERR_UNKNOWN; } break; case ARGP_KEY_END: break; default: return ARGP_ERR_UNKNOWN; break; } return 0; } static void do_cross (const cross_arguments * const arguments) { randctx rc; rand_seed(&rc); int ncps; flam3_genome * const cps = flam3_parse_xml2 (STDIN_FILENO, flam3_defaults_on, &ncps, &rc); if (cps == NULL) { fprintf(stderr,"error reading genomes from file\n"); exit(1); } assert (ncps == 2); flam3_genome * const genome_a = &cps[0], * const genome_b = &cps[1]; flam3_genome genome_out; flam3_cross (genome_a, genome_b, &genome_out, arguments->method, &rc); print_genome (&genome_out); } typedef struct { unsigned int tries, resolution; bool change_palette; double time; } improvecolors_arguments; static error_t parse_improvecolors_opt (int key, char *arg, struct argp_state * const state) { improvecolors_arguments * const arguments = state->input; return 0; } static void do_improvecolors (const improvecolors_arguments * const arguments) { randctx rc; rand_seed(&rc); int ncps; flam3_genome * const cps = flam3_parse_xml2 (STDIN_FILENO, flam3_defaults_on, &ncps, &rc); if (cps == NULL) { fprintf(stderr,"error reading genomes from file\n"); exit(1); } assert (ncps == 1); flam3_improve_colors (&cps[0], arguments->tries, arguments->change_palette, arguments->resolution, arguments->time, &builtin_palettes, &rc); print_genome (&cps[0]); } typedef struct { float time; } interpolate_arguments; static error_t parse_interpolate_opt (int key, char *arg, struct argp_state * const state) { interpolate_arguments * const arguments = state->input; switch (key) { case 't': { assert (arg != NULL); float i = atof (arg); if (i >= 0.0 && i <= 1.0) { arguments->time = i; } else { argp_error (state, "Time must be between 0 and 1"); } break; } case ARGP_KEY_ARG: if (state->arg_num > 0) { return ARGP_ERR_UNKNOWN; } break; case ARGP_KEY_END: break; default: return ARGP_ERR_UNKNOWN; break; } return 0; } static void do_interpolate (const interpolate_arguments * const arguments) { randctx rc; rand_seed(&rc); int ncps; flam3_genome * const cps = flam3_parse_xml2 (STDIN_FILENO, flam3_defaults_on, &ncps, &rc); if (cps == NULL) { fprintf(stderr,"error reading genomes from file\n"); exit(1); } assert (ncps == 2); cps[0].time = 0.0; cps[1].time = 1.0; flam3_genome genome_out; flam3_interpolate (cps, 2, arguments->time, 0, &genome_out); print_genome (&genome_out); } static void show_help (const char * const argv0) { const char *progname = strrchr (argv0, (int) '/'); if (progname == NULL) { progname = argv0; } else { ++progname; } fprintf (stderr, "Usage: %s cross [OPTION...]\n" " Or: %s improvecolors [OPTION...]\n" " Or: %s interpolate [OPTION...]\n" " Or: %s mutate [OPTION...]\n" " Or: %s random [OPTION...]\n" " Or: %s render [OPTION...]\n", progname, progname, progname, progname, progname); } int main (int argc, char **argv) { if (argc < 2) { show_help (argv[0]); return EXIT_FAILURE; } const char * const command = argv[1]; if (streq (command, "cross")) { const struct argp_option options[] = { {"method", 'm', "XXX", OPTION_ARG_OPTIONAL, "Cross method" }, { 0 }, }; const char doc[] = PACKAGE "-cross -- a fractal flame renderer"; const struct argp argp = { .options = options, .parser = parse_cross_opt, .args_doc = NULL, .doc = doc, .children = NULL }; cross_arguments arguments = { .method = CROSS_NOT_SPECIFIED, }; argp_parse (&argp, argc, argv, 0, NULL, &arguments); do_cross (&arguments); } else if (streq (command, "improvecolors")) { const struct argp_option options[] = { { 0 }, }; const char doc[] = PACKAGE "-improvecolors -- improve flame colors"; const struct argp argp = { .options = options, .parser = parse_improvecolors_opt, .args_doc = NULL, .doc = doc, .children = NULL }; improvecolors_arguments arguments = { .tries = 100, .resolution = 10, .change_palette = true, .time = 1.0, }; argp_parse (&argp, argc, argv, 0, NULL, &arguments); do_improvecolors (&arguments); } else if (streq (command, "interpolate")) { const struct argp_option options[] = { {"time", 't', "float", OPTION_ARG_OPTIONAL, "Time step (0.5)" }, { 0 }, }; const char doc[] = PACKAGE "-interpolate -- fractal flame interpolation"; const struct argp argp = { .options = options, .parser = parse_interpolate_opt, .args_doc = NULL, .doc = doc, .children = NULL }; interpolate_arguments arguments = { .time = 0.5, }; argp_parse (&argp, argc, argv, 0, NULL, &arguments); do_interpolate (&arguments); #if 0 } else if (streq (command, "mutate")) { const struct argp_option options[] = { {"method", 'm', "XXX", OPTION_ARG_OPTIONAL, "Mutation method" }, { 0 }, }; const char doc[] = PACKAGE "-mutate -- a fractal flame renderer"; const struct argp argp = { .options = options, .parser = parse_mutate_opt, .args_doc = NULL, .doc = doc, .children = NULL }; mutate_arguments arguments = { .method = MUTATE_NOT_SPECIFIED, .symmetry = 0, }; argp_parse (&argp, argc, argv, 0, NULL, &arguments); do_mutate (&arguments); #endif } else if (streq (command, "random")) { /* generate random genome */ const struct argp_option options[] = { {"height", 'h', "pixels", 0, "Output flame height (1000)" }, {"width", 'w', "pixels", 0, "Output flame width (1000)" }, {"max-xforms", 'x', "number", 0, "Max number of xforms (6)" }, {"max-var", 'v', "number", 0, "Max number of variations per xform (unlimited)" }, { 0 }, }; const char doc[] = PACKAGE "-random -- a fractal flame generator"; const struct argp argp = { .options = options, .parser = parse_random_opt, .args_doc = NULL, .doc = doc, .children = NULL }; random_arguments arguments = { .palette = "flam3-palettes.xml", .width = 1000, .height = 1000, .max_xforms = 6, .max_var = flam3_nvariations, .post_likelihood = 0.4, .final_likelihood = 0.15, .symmetry_likelihood = 0.25, .min_symmetry = -6, .max_symmetry = 6, }; argp_parse (&argp, argc, argv, 0, NULL, &arguments); do_random (&arguments); } else if (streq (command, "render")) { /* render flame to image file */ const struct argp_option options[] = { {"scale", 's', "factor", 0, "Scale image dimensions by factor (1.0)" }, {"width", 'w', "pixels", 0, "Scale image to width" }, {"height", 'h', "pixels", 0, "Scale image to height" }, {"bpc", 'b', "8|16", 0, "Bits per channel of output image (8)" }, {"time", 't', "seconds", 0, "Rendering time" }, {"cache", 'c', "path", 0, "Cache file" }, { 0 }, }; const char doc[] = PACKAGE "-render -- a fractal flame renderer"; const struct argp argp = { .options = options, .parser = parse_render_opt, .args_doc = NULL, .doc = doc, .children = NULL, }; render_arguments arguments = { .bpc = 8, .scale = 1.0, .width = 0, .height = 0, .time = 1.0, .cache = NULL, }; argp_parse (&argp, argc, argv, 0, NULL, &arguments); do_render (&arguments); } else { show_help (argv[0]); return EXIT_FAILURE; } return EXIT_SUCCESS; }