From 0b7731ebb7c06fd73e945cec777960d450e8192c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 8 Aug 2014 15:13:32 -0400 Subject: Allow image size and initial position to be specified on the command line. --- main.c | 77 ++++++++++++++++++++++--------------------------- options.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- options.h | 5 +++- 3 files changed, 126 insertions(+), 55 deletions(-) diff --git a/main.c b/main.c index d255515..7c0ab56 100644 --- a/main.c +++ b/main.c @@ -36,9 +36,6 @@ typedef struct { // All-encompasing state struct typedef struct { const options_t *options; - unsigned int width; - unsigned int height; - size_t size; uint32_t *colors; pixel_t *pixels; png_byte **bitmap; @@ -55,12 +52,12 @@ main(int argc, char *argv[]) options_t options; if (!parse_options(&options, argc, argv)) { fprintf(stderr, "\n"); - print_usage(stderr, argv[0]); + print_usage(stderr, argv[0], options.help); return EXIT_FAILURE; } if (options.help) { - print_usage(stdout, argv[0]); + print_usage(stdout, argv[0], true); return EXIT_SUCCESS; } @@ -72,16 +69,16 @@ main(int argc, char *argv[]) } static uint32_t * -create_colors(const state_t *state) +create_colors(const options_t *options) { - const unsigned int bit_depth = state->options->bit_depth; + const unsigned int bit_depth = options->bit_depth; // From least to most perceptually important const unsigned int bskip = 1U << (24 - bit_depth)/3; const unsigned int rskip = 1U << (24 - bit_depth + 1)/3; const unsigned int gskip = 1U << (24 - bit_depth + 2)/3; - uint32_t *colors = xmalloc(state->size*sizeof(uint32_t)); + uint32_t *colors = xmalloc(options->ncolors*sizeof(uint32_t)); for (unsigned int b = 0, i = 0; b < 0x100; b += bskip) { for (unsigned int g = 0; g < 0x100; g += gskip) { for (unsigned int r = 0; r < 0x100; r += rskip, ++i) { @@ -90,14 +87,14 @@ create_colors(const state_t *state) } } - switch (state->options->mode) { + switch (options->mode) { case MODE_HUE_SORT: - qsort(colors, state->size, sizeof(uint32_t), color_comparator); + qsort(colors, options->ncolors, sizeof(uint32_t), color_comparator); break; case MODE_RANDOM: // Fisher-Yates shuffle - for (unsigned int i = state->size; i-- > 0;) { + for (unsigned int i = options->ncolors; i-- > 0;) { unsigned int j = xrand(i + 1); uint32_t temp = colors[i]; colors[i] = colors[j]; @@ -110,11 +107,11 @@ create_colors(const state_t *state) } static pixel_t * -create_pixels(const state_t *state) +create_pixels(const options_t *options) { - pixel_t *pixels = xmalloc(state->size*sizeof(pixel_t)); - for (unsigned int y = 0, i = 0; y < state->height; ++y) { - for (unsigned int x = 0; x < state->width; ++x, ++i) { + pixel_t *pixels = xmalloc(options->npixels*sizeof(pixel_t)); + for (unsigned int y = 0, i = 0; y < options->height; ++y) { + for (unsigned int x = 0; x < options->width; ++x, ++i) { pixel_t *pixel = pixels + i; pixel->node = NULL; pixel->x = x; @@ -126,11 +123,11 @@ create_pixels(const state_t *state) } static png_byte ** -create_bitmap(const state_t *state) +create_bitmap(const options_t *options) { - png_byte **rows = xmalloc(state->height*sizeof(png_byte *)); - const size_t row_size = 3*state->width*sizeof(png_byte); - for (unsigned int i = 0; i < state->height; ++i) { + png_byte **rows = xmalloc(options->height*sizeof(png_byte *)); + const size_t row_size = 4*options->width*sizeof(png_byte); + for (unsigned int i = 0; i < options->height; ++i) { rows[i] = xmalloc(row_size); memset(rows[i], 0, row_size); } @@ -140,19 +137,15 @@ create_bitmap(const state_t *state) static void init_state(state_t *state, const options_t *options) { + printf("Generating a %u-bit, %ux%u image (%zu pixels)\n", + options->bit_depth, options->width, options->height, options->npixels); + xsrand(options->seed); state->options = options; - state->width = 1U << (options->bit_depth + 1)/2; // Round up - state->height = 1U << options->bit_depth/2; // Round down - state->size = (size_t)state->width*state->height; - - printf("Generating a %u-bit, %ux%u image (%zu pixels)\n", - options->bit_depth, state->width, state->height, state->size); - - state->colors = create_colors(state); - state->pixels = create_pixels(state); - state->bitmap = create_bitmap(state); + state->colors = create_colors(options); + state->pixels = create_pixels(options); + state->bitmap = create_bitmap(options); } static void generate_bitmap(state_t *state); @@ -171,7 +164,7 @@ generate_image(state_t *state) static void destroy_state(state_t *state) { - for (unsigned int i = 0; i < state->height; ++i) { + for (unsigned int i = 0; i < state->options->height; ++i) { free(state->bitmap[i]); } free(state->bitmap); @@ -182,7 +175,7 @@ destroy_state(state_t *state) static pixel_t * get_pixel(const state_t *state, unsigned int x, unsigned int y) { - return state->pixels + state->width*y + x; + return state->pixels + state->options->width*y + x; } static pixel_t * @@ -190,15 +183,15 @@ try_neighbor(const state_t *state, pixel_t *pixel, int dx, int dy) { if (dx < 0 && pixel->x < -dx) { return NULL; - } else if (dx > 0 && pixel->x + dx >= state->width) { + } else if (dx > 0 && pixel->x + dx >= state->options->width) { return NULL; } else if (dy < 0 && pixel->y < -dy) { return NULL; - } else if (dy > 0 && pixel->y + dy >= state->height) { + } else if (dy > 0 && pixel->y + dy >= state->options->height) { return NULL; } - return pixel + (int)state->width*dy + dx; + return pixel + (int)state->options->width*dy + dx; } static unsigned int @@ -403,8 +396,8 @@ generate_bitmap(state_t *state) for (unsigned int i = 1, progress = 0; i <= bit_depth + 1; ++i) { unsigned int stripe = 1 << i; - for (unsigned int j = stripe/2 - 1; j < state->size; j += stripe, ++progress) { - if (progress%state->width == 0) { + for (unsigned int j = stripe/2 - 1; j < state->options->ncolors; j += stripe, ++progress) { + if (progress % state->options->width == 0) { if (animate) { sprintf(filename, "%s/%04u.png", state->options->filename, frame); write_png(state, filename); @@ -412,7 +405,7 @@ generate_bitmap(state_t *state) } print_progress("%.2f%%\t| boundary size: %zu\t| max boundary size: %zu", - 100.0*progress/state->size, kdf.size, max_size); + 100.0*progress/state->options->ncolors, kdf.size, max_size); } uint32_t color = state->colors[j]; @@ -432,8 +425,7 @@ generate_bitmap(state_t *state) pixel_t *pixel; if (j == 0) { - // First node goes in the center - pixel = get_pixel(state, state->width/2, state->height/2); + pixel = get_pixel(state, state->options->x, state->options->y); } else { pixel = find_next_pixel(state, &kdf, target); } @@ -444,8 +436,9 @@ generate_bitmap(state_t *state) max_size = kdf.size; } - png_byte *png_pixel = state->bitmap[pixel->y] + 3*pixel->x; + png_byte *png_pixel = state->bitmap[pixel->y] + 4*pixel->x; color_unpack(png_pixel, color); + png_pixel[3] = 0xFF; } } @@ -499,8 +492,8 @@ write_png(const state_t *state, const char *filename) } png_init_io(png_ptr, file); - png_set_IHDR(png_ptr, info_ptr, state->width, state->height, 8, - PNG_COLOR_TYPE_RGB, PNG_INTERLACE_ADAM7, + png_set_IHDR(png_ptr, info_ptr, state->options->width, state->options->height, 8, + PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_ADAM7, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_ABSOLUTE); png_set_rows(png_ptr, info_ptr, state->bitmap); diff --git a/options.c b/options.c index 06deddb..5a5b446 100644 --- a/options.c +++ b/options.c @@ -24,19 +24,19 @@ parse_arg(int argc, char *argv[], const char *short_form, const char *long_form, const char **value, int *i, bool *error) { - size_t short_len = strlen(short_form); - size_t long_len = strlen(long_form); + size_t short_len = short_form ? strlen(short_form) : 0; + size_t long_len = long_form ? strlen(long_form) : 0; const char *actual_form; const char *arg = argv[*i]; const char *candidate = NULL; - if (strncmp(arg, short_form, short_len) == 0) { + if (short_form && strncmp(arg, short_form, short_len) == 0) { actual_form = short_form; if (strlen(arg) > short_len) { candidate = arg + short_len; } - } else if (strncmp(arg, long_form, long_len) == 0) { + } else if (long_form && strncmp(arg, long_form, long_len) == 0) { actual_form = long_form; if (strlen(arg) > long_len) { if (arg[long_len] == '=') { @@ -209,7 +209,7 @@ print_colorized(FILE *file, bool tty, const char *format, ...) } void -print_usage(FILE *file, const char *command) +print_usage(FILE *file, const char *command, bool verbose) { #if __unix__ bool tty = isatty(fileno(file)); @@ -228,10 +228,17 @@ print_usage(FILE *file, const char *command) usage(" %s [-s|--hue-sort] [-r|--random]\n", whitespace); usage(" %s [-l|--selection @min@|@mean@]\n", whitespace); usage(" %s [-c|--color-space @RGB@|@Lab@|@Luv@]\n", whitespace); + usage(" %s [-w|--width @WIDTH@] [-h|--height @HEIGHT@]\n", whitespace); + usage(" %s [-x @X@] [-y @Y@]\n", whitespace); usage(" %s [-a|--animate]\n", whitespace); usage(" %s [-o|--output @PATH@]\n", whitespace); usage(" %s [-e|--seed @SEED@]\n", whitespace); - usage(" %s [-h|--help]\n", whitespace); + usage(" %s [-?|--help]\n", whitespace); + + if (!verbose) { + return; + } + usage("\n"); usage(" -b, --bit-depth @DEPTH@:\n"); usage(" Use all @DEPTH@\\-bit colors (!default!: @24@)\n\n"); @@ -245,6 +252,12 @@ print_usage(FILE *file, const char *command) usage(" @mean@: Pick the pixel with the closest average of all its neighbors\n\n"); usage(" -c, --color-space @RGB@|@Lab@|@Luv@:\n"); usage(" Use the given color space (!default!: @Lab@)\n\n"); + usage(" -w, --width @WIDTH@\n"); + usage(" -h, --height @HEIGHT@:\n"); + usage(" Generate a @WIDTH@x@HEIGHT@ image (!default!: @as small as possible@)\n\n"); + usage(" -x @X@\n"); + usage(" -y @Y@:\n"); + usage(" Place the first pixel at (@X@, @Y@) (!default!: @center@)\n\n"); usage(" -a, --animate:\n"); usage(" Generate frames of an animation\n\n"); usage(" -o, --output @PATH@:\n"); @@ -253,7 +266,7 @@ print_usage(FILE *file, const char *command) usage(" will hold many frames\n\n"); usage(" -e, --seed @SEED@:\n"); usage(" Seed the random number generator (!default!: @0@)\n\n"); - usage(" -h, --help:\n"); + usage(" -?, --help:\n"); usage(" Show this message\n"); #undef usage } @@ -271,6 +284,8 @@ parse_options(options_t *options, int argc, char *argv[]) options->seed = 0; options->help = false; + bool width_set = false, height_set = false; + bool x_set = false, y_set = false; bool result = true; for (int i = 1; i < argc; ++i) { @@ -288,10 +303,6 @@ parse_options(options_t *options, int argc, char *argv[]) options->mode = MODE_HUE_SORT; } else if (parse_arg(argc, argv, "-r", "--random", NULL, &i, &error)) { options->mode = MODE_RANDOM; - } else if (parse_arg(argc, argv, "-a", "--animate", NULL, &i, &error)) { - options->animate = true; - } else if (parse_arg(argc, argv, "-o", "--output", &value, &i, &error)) { - options->filename = value; } else if (parse_arg(argc, argv, "-l", "--selection", &value, &i, &error)) { if (strcmp(value, "min") == 0) { options->selection = SELECTION_MIN; @@ -312,12 +323,44 @@ parse_options(options_t *options, int argc, char *argv[]) fprintf(stderr, "Invalid color space: `%s'\n", value); error = true; } + } else if (parse_arg(argc, argv, "-w", "--width", &value, &i, &error)) { + if (str_to_uint(value, &options->width)) { + width_set = true; + } else { + fprintf(stderr, "Invalid width: `%s'\n", value); + error = true; + } + } else if (parse_arg(argc, argv, "-h", "--height", &value, &i, &error)) { + if (str_to_uint(value, &options->height)) { + height_set = true; + } else { + fprintf(stderr, "Invalid height: `%s'\n", value); + error = true; + } + } else if (parse_arg(argc, argv, "-x", NULL, &value, &i, &error)) { + if (str_to_uint(value, &options->x)) { + x_set = true; + } else { + fprintf(stderr, "Invalid x coordinate: `%s'\n", value); + error = true; + } + } else if (parse_arg(argc, argv, "-y", NULL, &value, &i, &error)) { + if (str_to_uint(value, &options->y)) { + y_set = true; + } else { + fprintf(stderr, "Invalid y coordinate: `%s'\n", value); + error = true; + } + } else if (parse_arg(argc, argv, "-a", "--animate", NULL, &i, &error)) { + options->animate = true; + } else if (parse_arg(argc, argv, "-o", "--output", &value, &i, &error)) { + options->filename = value; } else if (parse_arg(argc, argv, "-e", "--seed", &value, &i, &error)) { if (!str_to_uint(value, &options->seed)) { fprintf(stderr, "Invalid random seed: `%s'\n", value); error = true; } - } else if (parse_arg(argc, argv, "-h", "--help", NULL, &i, &error)) { + } else if (parse_arg(argc, argv, "-?", "--help", NULL, &i, &error)) { options->help = true; } else if (!error) { fprintf(stderr, "Unexpected argument `%s'\n", argv[i]); @@ -329,6 +372,38 @@ parse_options(options_t *options, int argc, char *argv[]) } } + options->ncolors = (size_t)1 << options->bit_depth; + + if (!width_set && !height_set) { + // Round width up to make widescreen the default + options->width = 1U << (options->bit_depth + 1)/2; + options->height = 1U << options->bit_depth/2; + } else if (width_set && !height_set) { + options->height = (options->ncolors + options->width - 1)/options->width; + } else if (!width_set && height_set) { + options->width = (options->ncolors + options->height - 1)/options->height; + } + + options->npixels = (size_t)options->width*options->height; + if (options->npixels < options->ncolors) { + fprintf(stderr, "Image too small (at least %zu pixels needed)\n", options->ncolors); + result = false; + } + + if (!x_set) { + options->x = options->width/2; + } else if (options->x >= options->width) { + fprintf(stderr, "-x coordinate too big, should be less than %u\n", options->width); + result = false; + } + + if (!y_set) { + options->y = options->height/2; + } else if (options->y >= options->height) { + fprintf(stderr, "-y coordinate too big, should be less than %u\n", options->height); + result = false; + } + // Default filename depends on -a flag if (!options->filename) { options->filename = options->animate ? "frames" : "kd-forest.png"; diff --git a/options.h b/options.h index fe996f7..382f5fc 100644 --- a/options.h +++ b/options.h @@ -40,6 +40,9 @@ typedef struct { mode_t mode; selection_t selection; color_space_t color_space; + unsigned int width, height; + size_t ncolors, npixels; + unsigned int x, y; bool animate; const char *filename; unsigned int seed; @@ -47,6 +50,6 @@ typedef struct { } options_t; bool parse_options(options_t *options, int argc, char *argv[]); -void print_usage(FILE *file, const char *command); +void print_usage(FILE *file, const char *command, bool verbose); #endif // OPTIONS_H -- cgit v1.2.3