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