1 /* main.c: main driver for autotrace -- convert bitmaps to splines. */
2 
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif /* Def: HAVE_CONFIG_H */
6 
7 #include "autotrace.h"
8 #include "message.h"
9 #include "cmdline.h"
10 #include "logreport.h"
11 #include "getopt.h"
12 #include "filename.h"
13 #include "xstd.h"
14 #include "atou.h"
15 #include "strgicmp.h"
16 #include "input.h"
17 
18 #include <string.h>
19 #include <assert.h>
20 #include <math.h>
21 
22 /* Pointers to functions based on input format.  (-input-format)  */
23 static at_input_read_func input_reader = NULL;
24 
25 /* Return NAME with any leading path stripped off.  This returns a
26    pointer into NAME.  For example, `basename ("/foo/bar.baz")'
27    returns "bar.baz".  */
28 static char * get_basename (char * name);
29 
30 /* The name of the file we're going to write.  (-output-file) */
31 static char * output_name = (char *)"";
32 
33 /* The output function. (-output-format) */
34 static at_output_write_func output_writer = NULL;
35 
36 /* Whether to print version information */
37 static at_bool printed_version;
38 
39 /* Whether to write a log file */
40 static at_bool logging = false;
41 
42 /* Whether to dump a bitmap file */
43 static at_bool dumping_bitmap = false;
44 
45 /* Report tracing status in real time (--report-progress) */
46 static at_bool report_progress = false;
47 #define dot_printer_max_column 50
48 #define dot_printer_char '|'
49 static void dot_printer(at_real percentage, at_address client_data);
50 
51 static char * read_command_line (int, char * [],
52 				 at_fitting_opts_type *,
53 				 at_output_opts_type *);
54 
55 static unsigned int hctoi (char c);
56 
57 static void dump (at_bitmap_type * bitmap, FILE * fp);
58 
59 static void input_list_formats(FILE * file);
60 static void output_list_formats(FILE* file);
61 
62 static void exception_handler(at_string msg, at_msg_type type, at_address data);
63 
64 #define DEFAULT_FORMAT "eps"
65 
66 int
main(int argc,char * argv[])67 main (int argc, char * argv[])
68 {
69   at_fitting_opts_type * fitting_opts;
70   at_input_opts_type * input_opts;
71   at_output_opts_type * output_opts;
72   char * input_name, * input_rootname;
73   char * logfile_name = NULL, * dumpfile_name = NULL;
74   at_splines_type * splines;
75   at_bitmap_type * bitmap;
76   FILE *output_file;
77   FILE *dump_file;
78 
79   at_progress_func progress_reporter = NULL;
80   int progress_stat = 0;
81 
82   fitting_opts 		    = at_fitting_opts_new ();
83   output_opts  		    = at_output_opts_new ();
84   input_name = read_command_line (argc, argv, fitting_opts, output_opts);
85 
86   if (strgicmp (output_name, input_name))
87     FATAL("Input and output file may not be the same\n");
88 
89   if ((input_rootname = remove_suffix (get_basename (input_name))) == NULL)
90 	FATAL1 ("Not a valid inputname %s", input_name);
91 
92   if (logging)
93     log_file = xfopen (logfile_name = extend_filename (input_rootname, "log"), "w");
94 
95   /* BUG: Sometimes input_rootname points to the heap, sometimes to
96      the stack, so it can't safely be freed. */
97 /*
98   if (input_rootname != input_name)
99     free (input_rootname);
100 */
101   if (logging)
102     free (logfile_name);
103 
104   /* Set input_reader if it is not set in command line args */
105   if (!input_reader)
106     input_reader = at_input_get_handler (input_name);
107 
108   /* Set output_writer if it is not set in command line args
109      Step1. Guess from a file name.
110      Step2. Use default. */
111   if (!output_writer)
112     output_writer = at_output_get_handler (output_name);
113   if (!output_writer)
114     {
115       output_writer = at_output_get_handler_by_suffix(DEFAULT_FORMAT);
116       if (output_writer == NULL)
117 	FATAL1("Default format %s not supported", DEFAULT_FORMAT);
118     }
119 
120   /* Open output file */
121   if (!strcmp (output_name, ""))
122     output_file = stdout;
123   else
124     output_file = xfopen(output_name, "wb");
125 
126   /* Open the main input file.  */
127   if (input_reader != NULL)
128     {
129       input_opts = at_input_opts_new ();
130       if (fitting_opts->background_color)
131 	input_opts->background_color = at_color_copy(fitting_opts->background_color);
132 
133       bitmap = at_bitmap_read(input_reader, input_name, input_opts,
134 			      exception_handler, NULL);
135 
136       at_input_opts_free(input_opts);
137     }
138   else
139     FATAL ("Unsupported input format");
140 
141   if (report_progress)
142     {
143       progress_reporter = dot_printer;
144       fprintf(stderr, "%-15s", input_name);
145     };
146 
147   splines = at_splines_new_full(bitmap, fitting_opts,
148 				exception_handler, NULL,
149 				progress_reporter, &progress_stat,
150 				NULL, NULL);
151 
152   /* Dump loaded bitmap if needed */
153   if (dumping_bitmap)
154     {
155       dumpfile_name = extend_filename (input_rootname, "bitmap");
156       dump_file   = xfopen (dumpfile_name, "wb");
157       dump(bitmap, dump_file);
158       fclose(dump_file);
159     }
160 
161   at_splines_write (output_writer,
162 		    output_file,
163 		    output_name,
164 		    output_opts,
165 		    splines,
166 		    exception_handler, NULL);
167   at_output_opts_free(output_opts);
168 
169   if (output_file != stdout)
170     fclose (output_file);
171 
172   at_splines_free (splines);
173   at_bitmap_free (bitmap);
174   at_fitting_opts_free(fitting_opts);
175 
176   if (report_progress)
177     fputs("\n", stderr);
178 
179   return 0;
180 }
181 
182 
183 /* Reading the options.  */
184 
185 #define USAGE1 "Options:\
186 <input_name> should be a supported image.\n"\
187   GETOPT_USAGE								\
188 "background-color <hexadezimal>: the color of the background that\n\
189   should be ignored, for example FFFFFF;\n\
190   default is no background color.\n\
191 centerline: trace a character's centerline, rather than its outline.\n\
192 color-count <unsigned>: number of colors a color bitmap is reduced to,\n\
193   it does not work on grayscale, allowed are 1..256;\n\
194   default is 0, that means not color reduction is done.\n\
195 corner-always-threshold <angle-in-degrees>: if the angle at a pixel is\n\
196   less than this, it is considered a corner, even if it is within\n\
197   `corner-surround' pixels of another corner; default is 60.\n\
198 corner-surround <unsigned>: number of pixels on either side of a\n\
199   point to consider when determining if that point is a corner;\n\
200   default is 4.\n\
201 corner-threshold <angle-in-degrees>: if a pixel, its predecessor(s),\n\
202   and its successor(s) meet at an angle smaller than this, it's a\n\
203   corner; default is 100.\n\
204 despeckle-level <unsigned>: 0..20; default is no despeckling.\n\
205 despeckle-tightness <real>: 0.0..8.0; default is 2.0.\n\
206 dpi <unsigned>: The dots per inch value in the input image, affects scaling\n\
207   of mif output image\n"
208 #define USAGE2 "error-threshold <real>: subdivide fitted curves that are off by\n\
209   more pixels than this; default is 2.0.\n\
210 filter-iterations <unsigned>: smooth the curve this many times\n\
211   before fitting; default is 4.\n\
212 input-format:  %s. \n\
213 help: print this message.\n\
214 line-reversion-threshold <real>: if a spline is closer to a straight\n\
215   line than this, weighted by the square of the curve length, keep it a\n\
216   straight line even if it is a list with curves; default is .01.\n\
217 line-threshold <real>: if the spline is not more than this far away\n\
218   from the straight line defined by its endpoints,\n\
219   then output a straight line; default is 1.\n\
220 list-output-formats: print a list of support output formats to stderr.\n\
221 list-input-formats:  print a list of support input formats to stderr.\n\
222 log: write detailed progress reports to <input_name>.log.\n\
223 output-file <filename>: write to <filename>\n\
224 output-format <format>: use format <format> for the output file\n\
225   %s can be used.\n\
226 preserve-width: whether to preserve line width prior to thinning.\n\
227 remove-adjacent-corners: remove corners that are adjacent.\n\
228 tangent-surround <unsigned>: number of points on either side of a\n\
229   point to consider when computing the tangent at that point; default is 3.\n\
230 report-progress: report tracing status in real time.\n\
231 debug-arch: print the type of cpu.\n\
232 debug-bitmap: dump loaded bitmap to <input_name>.bitmap.\n\
233 version: print the version number of this program.\n\
234 width-weight-factor <real>: weight factor for fitting the linewidth.\n\
235 "
236 
237 /* We return the name of the image to process.  */
238 
239 static char *
read_command_line(int argc,char * argv[],at_fitting_opts_type * fitting_opts,at_output_opts_type * output_opts)240 read_command_line (int argc, char * argv[],
241 		   at_fitting_opts_type * fitting_opts,
242 		   at_output_opts_type * output_opts)
243 {
244   int g;   /* `getopt' return code.  */
245   int option_index;
246   struct option long_options[]
247     = { { "align-threshold",		1, 0, 0 },
248 	{ "background-color",		1, 0, 0 },
249 	{ "debug-arch",                 0, 0, 0 },
250 	{ "debug-bitmap",               0, (int *)&dumping_bitmap, 1 },
251         { "centerline",			0, 0, 0 },
252         { "color-count",                1, 0, 0 },
253         { "corner-always-threshold",    1, 0, 0 },
254         { "corner-surround",            1, 0, 0 },
255         { "corner-threshold",           1, 0, 0 },
256         { "despeckle-level",            1, 0, 0 },
257         { "despeckle-tightness",        1, 0, 0 },
258         { "dpi",			1, 0, 0 },
259         { "error-threshold",            1, 0, 0 },
260         { "filter-iterations",          1, 0, 0 },
261         { "help",                       0, 0, 0 },
262         { "input-format",		1, 0, 0 },
263         { "line-reversion-threshold",	1, 0, 0 },
264         { "line-threshold",             1, 0, 0 },
265         { "list-output-formats",        0, 0, 0 },
266         { "list-input-formats",         0, 0, 0 },
267         { "log",                        0, (int *) &logging, 1 },
268         { "output-file",		1, 0, 0 },
269         { "output-format",		1, 0, 0 },
270         { "preserve-width",             0, 0, 0 },
271         { "range",                      1, 0, 0 },
272         { "remove-adjacent-corners",    0, 0, 0 },
273         { "tangent-surround",           1, 0, 0 },
274 	{ "report-progress",            0, (int *) &report_progress, 1},
275         { "version",                    0, (int *) &printed_version, 1 },
276 	{ "width-weight-factor",               1, 0, 0 },
277         { 0, 0, 0, 0 } };
278 
279   while (true)
280     {
281 
282       g = getopt_long_only (argc, argv, "", long_options, &option_index);
283 
284       if (g == EOF)
285         break;
286 
287       if (g == '?')
288         exit (1);  /* Unknown option.  */
289 
290       assert (g == 0); /* We have no short option names.  */
291 
292       if (ARGUMENT_IS ("background-color"))
293         {
294            if (strlen (optarg) != 6)
295                FATAL ("background-color be six chars long");
296 	       fitting_opts->background_color = at_color_new((unsigned char)(hctoi (optarg[0]) * 16 + hctoi (optarg[1])),
297 				       (unsigned char)(hctoi (optarg[2]) * 16 + hctoi (optarg[3])),
298 				       (unsigned char)(hctoi (optarg[4]) * 16 + hctoi (optarg[5])));
299 	    }
300       else if (ARGUMENT_IS ("centerline"))
301 	fitting_opts->centerline = true;
302 
303       else if (ARGUMENT_IS ("color-count"))
304         fitting_opts->color_count = atou (optarg);
305 
306       else if (ARGUMENT_IS ("corner-always-threshold"))
307         fitting_opts->corner_always_threshold = (at_real) atof (optarg);
308 
309       else if (ARGUMENT_IS ("corner-surround"))
310         fitting_opts->corner_surround = atou (optarg);
311 
312       else if (ARGUMENT_IS ("corner-threshold"))
313         fitting_opts->corner_threshold = (at_real) atof (optarg);
314 
315       else if (ARGUMENT_IS ("debug-arch"))
316 	{
317 	  int endian = 1;
318 	  char * str;
319 	  if (*(char *)&endian)
320 	    str = "little";
321 	  else
322 	    str = "big";
323 
324 	  printf("%d bit, %s endian\n",
325 		 sizeof(void *) * 8,
326 		 str);
327 	  exit(0);
328 	}
329 
330       else if (ARGUMENT_IS ("despeckle-level"))
331         fitting_opts->despeckle_level = atou (optarg);
332 
333       else if (ARGUMENT_IS ("despeckle-tightness"))
334         fitting_opts->despeckle_tightness = (at_real) atof (optarg);
335 
336       else if (ARGUMENT_IS ("dpi"))
337 	output_opts->dpi = atou (optarg);
338 
339       else if (ARGUMENT_IS ("error-threshold"))
340         fitting_opts->error_threshold = (at_real) atof (optarg);
341 
342       else if (ARGUMENT_IS ("filter-iterations"))
343         fitting_opts->filter_iterations = atou (optarg);
344 
345       else if (ARGUMENT_IS ("help"))
346         {
347 	  char *ishortlist, *oshortlist;
348           fprintf (stderr, "Usage: %s [options] <input_name>.\n", argv[0]);
349           fprintf (stderr, USAGE1);
350           fprintf (stderr, USAGE2, ishortlist = at_input_shortlist(),
351 		   oshortlist = at_output_shortlist());
352 	  free (ishortlist);
353 	  free (oshortlist);
354 	  fprintf (stderr,
355 		   "\nYou can get the source code of autotrace from \n%s\n",
356 		   at_home_site());
357           exit (0);
358         }
359 
360       else if (ARGUMENT_IS ("input-format"))
361         {
362 	  input_reader = at_input_get_handler_by_suffix (optarg);
363 	  if (!input_reader)
364 	    FATAL1 ("Input format %s not supported\n", optarg);
365         }
366 
367       else if (ARGUMENT_IS ("line-threshold"))
368         fitting_opts->line_threshold = (at_real) atof (optarg);
369 
370       else if (ARGUMENT_IS ("line-reversion-threshold"))
371         fitting_opts->line_reversion_threshold = (at_real) atof (optarg);
372 
373       else if (ARGUMENT_IS ("list-output-formats"))
374         {
375 	  fprintf (stderr, "Supported output formats:\n");
376 	  output_list_formats (stderr);
377 	  exit (0);
378         }
379       else if (ARGUMENT_IS ("list-input-formats"))
380         {
381 	  fprintf (stderr, "Supported input formats:\n");
382 	  input_list_formats (stderr);
383 	  exit (0);
384         }
385 
386       else if (ARGUMENT_IS ("output-file"))
387         output_name = optarg;
388 
389       else if (ARGUMENT_IS ("output-format"))
390         {
391 	    output_writer = at_output_get_handler_by_suffix (optarg);
392 	    if (output_writer == NULL)
393 	      FATAL1 ("Output format %s not supported", optarg);
394         }
395       else if (ARGUMENT_IS ("preserve_width"))
396 	fitting_opts->preserve_width = true;
397 
398       else if (ARGUMENT_IS ("remove-adjacent-corners"))
399 	fitting_opts->remove_adjacent_corners = true;
400 
401       else if (ARGUMENT_IS ("tangent-surround"))
402         fitting_opts->tangent_surround = atou (optarg);
403 
404       else if (ARGUMENT_IS ("version"))
405         printf ("AutoTrace version %s.\n", at_version(false));
406 
407       else if (ARGUMENT_IS ("width-weight-factor"))
408 	fitting_opts->width_weight_factor = (at_real) atof (optarg);
409 
410       /* Else it was just a flag; getopt has already done the assignment.  */
411     }
412   FINISH_COMMAND_LINE ();
413 }
414 
415 /* Return NAME with any leading path stripped off.  This returns a
416    pointer into NAME.  For example, `basename ("/foo/bar.baz")'
417    returns "bar.baz".  */
418 
419 static char *
get_basename(char * name)420 get_basename (char * name)
421 {
422 #ifdef WIN32
423   char * base = strrchr (name, '\\');
424 #else
425   char * base = strrchr (name, '/');
426 #endif
427   return base ? base + 1 : name;
428 }
429 
430 
431 /* Convert hex char to integer */
432 
hctoi(char c)433 static unsigned int hctoi (char c)
434 {
435   if (c == '0')
436     return (0);
437   else if (c == '1')
438     return (1);
439   else if (c == '2')
440     return (2);
441   else if (c == '3')
442     return (3);
443   else if (c == '4')
444     return (4);
445   else if (c == '5')
446     return (5);
447   else if (c == '6')
448     return (6);
449   else if (c == '7')
450     return (7);
451   else if (c == '8')
452     return (8);
453   else if (c == '9')
454     return (9);
455   else if (c == 'a')
456     return (10);
457   else if (c == 'A')
458     return (10);
459   else if (c == 'b')
460     return (11);
461   else if (c == 'B')
462     return (11);
463   else if (c == 'c')
464     return (12);
465   else if (c == 'C')
466     return (12);
467   else if (c == 'd')
468     return (13);
469   else if (c == 'D')
470     return (13);
471   else if (c == 'e')
472     return (14);
473   else if (c == 'E')
474     return (14);
475   else if (c == 'f')
476     return (15);
477   else if (c == 'F')
478     return (15);
479   else
480     FATAL ("No hex values");
481 }
482 
483 static void
input_list_formats(FILE * file)484 input_list_formats(FILE * file)
485 {
486   char ** list = at_input_list_new ();
487   char ** tmp;
488 
489   char * suffix;
490   char * descr;
491   tmp = list;
492   while (*list)
493     {
494       suffix = *list++;
495       descr = *list++;
496       fprintf(file, "%5s %s\n", suffix, descr);
497     }
498 
499   at_input_list_free(tmp);
500 }
501 
502 
503 static void
output_list_formats(FILE * file)504 output_list_formats(FILE* file)
505 {
506   char ** list = at_output_list_new ();
507   char ** tmp;
508 
509   char * suffix;
510   char * descr;
511   tmp = list;
512   while (*list)
513     {
514       suffix = *list++;
515       descr = *list++;
516       fprintf(file, "%10s %s\n", suffix, descr);
517     }
518 
519   at_output_list_free(tmp);
520 }
521 
522 static void
dot_printer(at_real percentage,at_address client_data)523 dot_printer(at_real percentage, at_address client_data)
524 {
525   int * current = (int *)client_data;
526   float unit 	= (float)1.0 / (float)(dot_printer_max_column) ;
527   int maximum = (int)(percentage / unit);
528 
529   while (*current < maximum)
530     {
531       fputc(dot_printer_char, stderr);
532       (*current)++;
533     }
534 }
535 
536 static void
dump(at_bitmap_type * bitmap,FILE * fp)537 dump (at_bitmap_type * bitmap, FILE * fp)
538 {
539   unsigned short width, height;
540   unsigned int np;
541 
542   width  = at_bitmap_get_width (bitmap);
543   height = at_bitmap_get_height (bitmap);
544   np 	 = at_bitmap_get_planes (bitmap);
545   fprintf(fp, "w=%u, h=%u, np=%u\n", width, height, np);
546 
547   fwrite(AT_BITMAP_BITS(*bitmap),
548 	 sizeof(unsigned char),
549 	 width * height * np,
550 	 fp);
551 }
552 
553 static void
exception_handler(at_string msg,at_msg_type type,at_address data)554 exception_handler(at_string msg, at_msg_type type, at_address data)
555 {
556   if (type == AT_MSG_FATAL)
557     {
558       fprintf (stderr, "%s\n", msg);
559       exit (1);
560     }
561   else if (type == AT_MSG_WARNING)
562     fprintf (stderr, "%s\n", msg);
563   else
564     exception_handler("Wrong type of msg", AT_MSG_FATAL, NULL);
565 }
566