/* main.c: main driver for autotrace -- convert bitmaps to splines. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* Def: HAVE_CONFIG_H */ #include "autotrace.h" #include "message.h" #include "cmdline.h" #include "logreport.h" #include "getopt.h" #include "filename.h" #include "xstd.h" #include "atou.h" #include "strgicmp.h" #include "input.h" #include #include #include /* Pointers to functions based on input format. (-input-format) */ static at_input_read_func input_reader = NULL; /* Return NAME with any leading path stripped off. This returns a pointer into NAME. For example, `basename ("/foo/bar.baz")' returns "bar.baz". */ static char * get_basename (char * name); /* The name of the file we're going to write. (-output-file) */ static char * output_name = (char *)""; /* The output function. (-output-format) */ static at_output_write_func output_writer = NULL; /* Whether to print version information */ static at_bool printed_version; /* Whether to write a log file */ static at_bool logging = false; /* Whether to dump a bitmap file */ static at_bool dumping_bitmap = false; /* Report tracing status in real time (--report-progress) */ static at_bool report_progress = false; #define dot_printer_max_column 50 #define dot_printer_char '|' static void dot_printer(at_real percentage, at_address client_data); static char * read_command_line (int, char * [], at_fitting_opts_type *, at_output_opts_type *); static unsigned int hctoi (char c); static void dump (at_bitmap_type * bitmap, FILE * fp); static void input_list_formats(FILE * file); static void output_list_formats(FILE* file); static void exception_handler(at_string msg, at_msg_type type, at_address data); #define DEFAULT_FORMAT "eps" int main (int argc, char * argv[]) { at_fitting_opts_type * fitting_opts; at_input_opts_type * input_opts; at_output_opts_type * output_opts; char * input_name, * input_rootname; char * logfile_name = NULL, * dumpfile_name = NULL; at_splines_type * splines; at_bitmap_type * bitmap; FILE *output_file; FILE *dump_file; at_progress_func progress_reporter = NULL; int progress_stat = 0; fitting_opts = at_fitting_opts_new (); output_opts = at_output_opts_new (); input_name = read_command_line (argc, argv, fitting_opts, output_opts); if (strgicmp (output_name, input_name)) FATAL("Input and output file may not be the same\n"); if ((input_rootname = remove_suffix (get_basename (input_name))) == NULL) FATAL1 ("Not a valid inputname %s", input_name); if (logging) log_file = xfopen (logfile_name = extend_filename (input_rootname, "log"), "w"); /* BUG: Sometimes input_rootname points to the heap, sometimes to the stack, so it can't safely be freed. */ /* if (input_rootname != input_name) free (input_rootname); */ if (logging) free (logfile_name); /* Set input_reader if it is not set in command line args */ if (!input_reader) input_reader = at_input_get_handler (input_name); /* Set output_writer if it is not set in command line args Step1. Guess from a file name. Step2. Use default. */ if (!output_writer) output_writer = at_output_get_handler (output_name); if (!output_writer) { output_writer = at_output_get_handler_by_suffix(DEFAULT_FORMAT); if (output_writer == NULL) FATAL1("Default format %s not supported", DEFAULT_FORMAT); } /* Open output file */ if (!strcmp (output_name, "")) output_file = stdout; else output_file = xfopen(output_name, "wb"); /* Open the main input file. */ if (input_reader != NULL) { input_opts = at_input_opts_new (); if (fitting_opts->background_color) input_opts->background_color = at_color_copy(fitting_opts->background_color); bitmap = at_bitmap_read(input_reader, input_name, input_opts, exception_handler, NULL); at_input_opts_free(input_opts); } else FATAL ("Unsupported input format"); if (report_progress) { progress_reporter = dot_printer; fprintf(stderr, "%-15s", input_name); }; splines = at_splines_new_full(bitmap, fitting_opts, exception_handler, NULL, progress_reporter, &progress_stat, NULL, NULL); /* Dump loaded bitmap if needed */ if (dumping_bitmap) { dumpfile_name = extend_filename (input_rootname, "bitmap"); dump_file = xfopen (dumpfile_name, "wb"); dump(bitmap, dump_file); fclose(dump_file); } at_splines_write (output_writer, output_file, output_name, output_opts, splines, exception_handler, NULL); at_output_opts_free(output_opts); if (output_file != stdout) fclose (output_file); at_splines_free (splines); at_bitmap_free (bitmap); at_fitting_opts_free(fitting_opts); if (report_progress) fputs("\n", stderr); return 0; } /* Reading the options. */ #define USAGE1 "Options:\ should be a supported image.\n"\ GETOPT_USAGE \ "background-color : the color of the background that\n\ should be ignored, for example FFFFFF;\n\ default is no background color.\n\ centerline: trace a character's centerline, rather than its outline.\n\ color-count : number of colors a color bitmap is reduced to,\n\ it does not work on grayscale, allowed are 1..256;\n\ default is 0, that means not color reduction is done.\n\ corner-always-threshold : if the angle at a pixel is\n\ less than this, it is considered a corner, even if it is within\n\ `corner-surround' pixels of another corner; default is 60.\n\ corner-surround : number of pixels on either side of a\n\ point to consider when determining if that point is a corner;\n\ default is 4.\n\ corner-threshold : if a pixel, its predecessor(s),\n\ and its successor(s) meet at an angle smaller than this, it's a\n\ corner; default is 100.\n\ despeckle-level : 0..20; default is no despeckling.\n\ despeckle-tightness : 0.0..8.0; default is 2.0.\n\ dpi : The dots per inch value in the input image, affects scaling\n\ of mif output image\n" #define USAGE2 "error-threshold : subdivide fitted curves that are off by\n\ more pixels than this; default is 2.0.\n\ filter-iterations : smooth the curve this many times\n\ before fitting; default is 4.\n\ input-format: %s. \n\ help: print this message.\n\ line-reversion-threshold : if a spline is closer to a straight\n\ line than this, weighted by the square of the curve length, keep it a\n\ straight line even if it is a list with curves; default is .01.\n\ line-threshold : if the spline is not more than this far away\n\ from the straight line defined by its endpoints,\n\ then output a straight line; default is 1.\n\ list-output-formats: print a list of support output formats to stderr.\n\ list-input-formats: print a list of support input formats to stderr.\n\ log: write detailed progress reports to .log.\n\ output-file : write to \n\ output-format : use format for the output file\n\ %s can be used.\n\ preserve-width: whether to preserve line width prior to thinning.\n\ remove-adjacent-corners: remove corners that are adjacent.\n\ tangent-surround : number of points on either side of a\n\ point to consider when computing the tangent at that point; default is 3.\n\ report-progress: report tracing status in real time.\n\ debug-arch: print the type of cpu.\n\ debug-bitmap: dump loaded bitmap to .bitmap.\n\ version: print the version number of this program.\n\ width-weight-factor : weight factor for fitting the linewidth.\n\ " /* We return the name of the image to process. */ static char * read_command_line (int argc, char * argv[], at_fitting_opts_type * fitting_opts, at_output_opts_type * output_opts) { int g; /* `getopt' return code. */ int option_index; struct option long_options[] = { { "align-threshold", 1, 0, 0 }, { "background-color", 1, 0, 0 }, { "debug-arch", 0, 0, 0 }, { "debug-bitmap", 0, (int *)&dumping_bitmap, 1 }, { "centerline", 0, 0, 0 }, { "color-count", 1, 0, 0 }, { "corner-always-threshold", 1, 0, 0 }, { "corner-surround", 1, 0, 0 }, { "corner-threshold", 1, 0, 0 }, { "despeckle-level", 1, 0, 0 }, { "despeckle-tightness", 1, 0, 0 }, { "dpi", 1, 0, 0 }, { "error-threshold", 1, 0, 0 }, { "filter-iterations", 1, 0, 0 }, { "help", 0, 0, 0 }, { "input-format", 1, 0, 0 }, { "line-reversion-threshold", 1, 0, 0 }, { "line-threshold", 1, 0, 0 }, { "list-output-formats", 0, 0, 0 }, { "list-input-formats", 0, 0, 0 }, { "log", 0, (int *) &logging, 1 }, { "output-file", 1, 0, 0 }, { "output-format", 1, 0, 0 }, { "preserve-width", 0, 0, 0 }, { "range", 1, 0, 0 }, { "remove-adjacent-corners", 0, 0, 0 }, { "tangent-surround", 1, 0, 0 }, { "report-progress", 0, (int *) &report_progress, 1}, { "version", 0, (int *) &printed_version, 1 }, { "width-weight-factor", 1, 0, 0 }, { 0, 0, 0, 0 } }; while (true) { g = getopt_long_only (argc, argv, "", long_options, &option_index); if (g == EOF) break; if (g == '?') exit (1); /* Unknown option. */ assert (g == 0); /* We have no short option names. */ if (ARGUMENT_IS ("background-color")) { if (strlen (optarg) != 6) FATAL ("background-color be six chars long"); fitting_opts->background_color = at_color_new((unsigned char)(hctoi (optarg[0]) * 16 + hctoi (optarg[1])), (unsigned char)(hctoi (optarg[2]) * 16 + hctoi (optarg[3])), (unsigned char)(hctoi (optarg[4]) * 16 + hctoi (optarg[5]))); } else if (ARGUMENT_IS ("centerline")) fitting_opts->centerline = true; else if (ARGUMENT_IS ("color-count")) fitting_opts->color_count = atou (optarg); else if (ARGUMENT_IS ("corner-always-threshold")) fitting_opts->corner_always_threshold = (at_real) atof (optarg); else if (ARGUMENT_IS ("corner-surround")) fitting_opts->corner_surround = atou (optarg); else if (ARGUMENT_IS ("corner-threshold")) fitting_opts->corner_threshold = (at_real) atof (optarg); else if (ARGUMENT_IS ("debug-arch")) { int endian = 1; char * str; if (*(char *)&endian) str = "little"; else str = "big"; printf("%d bit, %s endian\n", sizeof(void *) * 8, str); exit(0); } else if (ARGUMENT_IS ("despeckle-level")) fitting_opts->despeckle_level = atou (optarg); else if (ARGUMENT_IS ("despeckle-tightness")) fitting_opts->despeckle_tightness = (at_real) atof (optarg); else if (ARGUMENT_IS ("dpi")) output_opts->dpi = atou (optarg); else if (ARGUMENT_IS ("error-threshold")) fitting_opts->error_threshold = (at_real) atof (optarg); else if (ARGUMENT_IS ("filter-iterations")) fitting_opts->filter_iterations = atou (optarg); else if (ARGUMENT_IS ("help")) { char *ishortlist, *oshortlist; fprintf (stderr, "Usage: %s [options] .\n", argv[0]); fprintf (stderr, USAGE1); fprintf (stderr, USAGE2, ishortlist = at_input_shortlist(), oshortlist = at_output_shortlist()); free (ishortlist); free (oshortlist); fprintf (stderr, "\nYou can get the source code of autotrace from \n%s\n", at_home_site()); exit (0); } else if (ARGUMENT_IS ("input-format")) { input_reader = at_input_get_handler_by_suffix (optarg); if (!input_reader) FATAL1 ("Input format %s not supported\n", optarg); } else if (ARGUMENT_IS ("line-threshold")) fitting_opts->line_threshold = (at_real) atof (optarg); else if (ARGUMENT_IS ("line-reversion-threshold")) fitting_opts->line_reversion_threshold = (at_real) atof (optarg); else if (ARGUMENT_IS ("list-output-formats")) { fprintf (stderr, "Supported output formats:\n"); output_list_formats (stderr); exit (0); } else if (ARGUMENT_IS ("list-input-formats")) { fprintf (stderr, "Supported input formats:\n"); input_list_formats (stderr); exit (0); } else if (ARGUMENT_IS ("output-file")) output_name = optarg; else if (ARGUMENT_IS ("output-format")) { output_writer = at_output_get_handler_by_suffix (optarg); if (output_writer == NULL) FATAL1 ("Output format %s not supported", optarg); } else if (ARGUMENT_IS ("preserve_width")) fitting_opts->preserve_width = true; else if (ARGUMENT_IS ("remove-adjacent-corners")) fitting_opts->remove_adjacent_corners = true; else if (ARGUMENT_IS ("tangent-surround")) fitting_opts->tangent_surround = atou (optarg); else if (ARGUMENT_IS ("version")) printf ("AutoTrace version %s.\n", at_version(false)); else if (ARGUMENT_IS ("width-weight-factor")) fitting_opts->width_weight_factor = (at_real) atof (optarg); /* Else it was just a flag; getopt has already done the assignment. */ } FINISH_COMMAND_LINE (); } /* Return NAME with any leading path stripped off. This returns a pointer into NAME. For example, `basename ("/foo/bar.baz")' returns "bar.baz". */ static char * get_basename (char * name) { #ifdef WIN32 char * base = strrchr (name, '\\'); #else char * base = strrchr (name, '/'); #endif return base ? base + 1 : name; } /* Convert hex char to integer */ static unsigned int hctoi (char c) { if (c == '0') return (0); else if (c == '1') return (1); else if (c == '2') return (2); else if (c == '3') return (3); else if (c == '4') return (4); else if (c == '5') return (5); else if (c == '6') return (6); else if (c == '7') return (7); else if (c == '8') return (8); else if (c == '9') return (9); else if (c == 'a') return (10); else if (c == 'A') return (10); else if (c == 'b') return (11); else if (c == 'B') return (11); else if (c == 'c') return (12); else if (c == 'C') return (12); else if (c == 'd') return (13); else if (c == 'D') return (13); else if (c == 'e') return (14); else if (c == 'E') return (14); else if (c == 'f') return (15); else if (c == 'F') return (15); else FATAL ("No hex values"); } static void input_list_formats(FILE * file) { char ** list = at_input_list_new (); char ** tmp; char * suffix; char * descr; tmp = list; while (*list) { suffix = *list++; descr = *list++; fprintf(file, "%5s %s\n", suffix, descr); } at_input_list_free(tmp); } static void output_list_formats(FILE* file) { char ** list = at_output_list_new (); char ** tmp; char * suffix; char * descr; tmp = list; while (*list) { suffix = *list++; descr = *list++; fprintf(file, "%10s %s\n", suffix, descr); } at_output_list_free(tmp); } static void dot_printer(at_real percentage, at_address client_data) { int * current = (int *)client_data; float unit = (float)1.0 / (float)(dot_printer_max_column) ; int maximum = (int)(percentage / unit); while (*current < maximum) { fputc(dot_printer_char, stderr); (*current)++; } } static void dump (at_bitmap_type * bitmap, FILE * fp) { unsigned short width, height; unsigned int np; width = at_bitmap_get_width (bitmap); height = at_bitmap_get_height (bitmap); np = at_bitmap_get_planes (bitmap); fprintf(fp, "w=%u, h=%u, np=%u\n", width, height, np); fwrite(AT_BITMAP_BITS(*bitmap), sizeof(unsigned char), width * height * np, fp); } static void exception_handler(at_string msg, at_msg_type type, at_address data) { if (type == AT_MSG_FATAL) { fprintf (stderr, "%s\n", msg); exit (1); } else if (type == AT_MSG_WARNING) fprintf (stderr, "%s\n", msg); else exception_handler("Wrong type of msg", AT_MSG_FATAL, NULL); }