#include #include #include #include #include #include #include #include #include "candidates.h" #include "colour.h" #include "image.h" typedef struct { const char *input; RGB background; size_t limit; float minimum_score; uint32_t block_size; bool have_background; bool tweak; } Options; static void print_usage(FILE *stream) { fprintf(stream, "Usage: accent-extract IMAGE --background '#RRGGBB' [OPTIONS]\n" "\n" "Options:\n" " --background COLOR Required background colour (#RRGGBB).\n" " --limit N Pre-score candidate limit (default: 20).\n" " --min-score FLOAT Minimum score to print (default: 0.60).\n" " --block-size N Square averaging block size (default: 16).\n" " --tweak Refine output lightness and saturation.\n" " --help Show this help.\n"); } static bool parse_positive_size(const char *text, size_t *out) { char *end; uintmax_t value; errno = 0; value = strtoumax(text, &end, 10); if (errno != 0 || end == text || *end != '\0' || value == 0 || value > SIZE_MAX) { return false; } *out = (size_t)value; return true; } static bool parse_block_size(const char *text, uint32_t *out) { size_t value; if (!parse_positive_size(text, &value) || value > UINT32_MAX) { return false; } *out = (uint32_t)value; return true; } static bool parse_score(const char *text, float *out) { char *end; float value; errno = 0; value = strtof(text, &end); if (errno != 0 || end == text || *end != '\0' || !isfinite(value) || value < 0.0f || value > 1.0f) { return false; } *out = value; return true; } static int parse_options(int argc, char **argv, Options *options) { int index; options->input = NULL; options->limit = 20; options->minimum_score = 0.60f; options->block_size = 16; options->have_background = false; options->tweak = false; for (index = 1; index < argc; ++index) { const char *argument = argv[index]; if (strcmp(argument, "--help") == 0) { print_usage(stdout); return 1; } if (strcmp(argument, "--tweak") == 0) { options->tweak = true; continue; } if (strcmp(argument, "--background") == 0 || strcmp(argument, "--limit") == 0 || strcmp(argument, "--min-score") == 0 || strcmp(argument, "--block-size") == 0) { const char *value; if (++index >= argc) { fprintf(stderr, "accent-extract: %s requires a value\n", argument); return -1; } value = argv[index]; if (strcmp(argument, "--background") == 0) { if (!colour_parse_hex(value, &options->background)) { fprintf(stderr, "accent-extract: invalid background colour: %s\n", value); return -1; } options->have_background = true; } else if (strcmp(argument, "--limit") == 0) { if (!parse_positive_size(value, &options->limit)) { fprintf(stderr, "accent-extract: invalid limit: %s\n", value); return -1; } } else if (strcmp(argument, "--min-score") == 0) { if (!parse_score(value, &options->minimum_score)) { fprintf(stderr, "accent-extract: invalid minimum score: %s\n", value); return -1; } } else if (!parse_block_size(value, &options->block_size)) { fprintf(stderr, "accent-extract: invalid block size: %s\n", value); return -1; } } else if (argument[0] == '-') { fprintf(stderr, "accent-extract: unknown option: %s\n", argument); return -1; } else if (options->input != NULL) { fprintf(stderr, "accent-extract: only one input image is accepted\n"); return -1; } else { options->input = argument; } } if (options->input == NULL) { fprintf(stderr, "accent-extract: no input image supplied\n"); return -1; } if (!options->have_background) { fprintf(stderr, "accent-extract: --background is required\n"); return -1; } return 0; } int main(int argc, char **argv) { Options options; CandidateSet candidates; ScoredCandidate *results = NULL; size_t result_count = 0; size_t index; int parsed = parse_options(argc, argv, &options); if (parsed != 0) { if (parsed < 0) { print_usage(stderr); } return parsed < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } if (!SDL_Init(0)) { fprintf(stderr, "accent-extract: SDL initialisation failed: %s\n", SDL_GetError()); return EXIT_FAILURE; } candidates_init(&candidates); if (!image_process_file(options.input, options.block_size, &candidates)) { fprintf(stderr, "accent-extract: could not process '%s': %s\n", options.input, SDL_GetError()); candidates_destroy(&candidates); SDL_Quit(); return EXIT_FAILURE; } if (!candidates_rank(&candidates, options.limit, options.background, options.minimum_score, &results, &result_count)) { fprintf(stderr, "accent-extract: out of memory while ranking colours\n"); candidates_destroy(&candidates); SDL_Quit(); return EXIT_FAILURE; } if (options.tweak) { candidates_tweak(results, result_count, options.background); } for (index = 0; index < result_count; ++index) { printf("#%02x%02x%02x %.3f\n", results[index].colour.r, results[index].colour.g, results[index].colour.b, (double)results[index].score); } candidates_destroy(&candidates); SDL_Quit(); return EXIT_SUCCESS; }