1 /* main.c: main driver for autotrace -- convert bitmaps to splines. */
2 
3 #include <string.h>
4 #include <assert.h>
5 #include <math.h>
6 
7 #include "pm_c_util.h"
8 #include "mallocvar.h"
9 #include "nstring.h"
10 #include "shhopt.h"
11 #include "pam.h"
12 
13 #include "autotrace.h"
14 #include "message.h"
15 #include "logreport.h"
16 #include "output-svg.h"
17 #include "bitmap.h"
18 
19 #define dot_printer_max_column 50
20 #define dot_printer_char '|'
21 
22 
23 
24 static void
readImageToBitmap(FILE * const ifP,at_bitmap_type ** const bitmapPP)25 readImageToBitmap(FILE *            const ifP,
26                   at_bitmap_type ** const bitmapPP) {
27 
28     at_bitmap_type * bitmapP;
29     struct pam pam;
30     tuple ** tuples;
31     unsigned int row;
32     tuple * row255;
33 
34     MALLOCVAR_NOFAIL(bitmapP);
35 
36     tuples = pnm_readpam(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
37 
38     bitmapP->width  = pam.width;
39     bitmapP->height = pam.height;
40     bitmapP->np     = pam.depth;
41 
42     MALLOCARRAY(bitmapP->bitmap, pam.width * pam.height * pam.depth);
43 
44     row255 = pnm_allocpamrow(&pam);
45 
46     for (row = 0; row < pam.height; ++row) {
47         unsigned int col;
48 
49         pnm_scaletuplerow(&pam, row255, tuples[row], 255);
50 
51         for (col = 0; col < pam.width; ++col) {
52             unsigned int plane;
53 
54             for (plane = 0; plane < pam.depth; ++plane) {
55                 unsigned int const bitmapIndex =
56                     (row * pam.width + col) * pam.depth + plane;
57                 bitmapP->bitmap[bitmapIndex] = row255[col][plane];
58             }
59         }
60     }
61     pnm_freepamrow(row255);
62     pnm_freepamarray(tuples, &pam);
63 
64     *bitmapPP = bitmapP;
65 }
66 
67 
68 
69 static void
dotPrinter(float const percentage,void * const clientData)70 dotPrinter(float  const percentage,
71            void * const clientData) {
72 
73     int * const currentP = (int *)clientData;
74     float const unit     = (float)1.0 / (float)(dot_printer_max_column) ;
75     int   const maximum  = (int)(percentage / unit);
76 
77     while (*currentP < maximum) {
78         fputc(dot_printer_char, stderr);
79         (*currentP)++;
80     }
81 }
82 
83 
84 
85 static void
exceptionHandler(const char * const msg,at_msg_type const type,void * const data)86 exceptionHandler(const char * const msg,
87                  at_msg_type  const type,
88                  void *       const data) {
89 
90     if (type == AT_MSG_FATAL)
91         pm_error("%s", msg);
92     else if (type == AT_MSG_WARNING)
93         pm_message("%s", msg);
94     else
95         exceptionHandler("Wrong type of msg", AT_MSG_FATAL, NULL);
96 }
97 
98 
99 
100 struct cmdlineInfo {
101     const char * inputFileName;
102     float        align_threshold;
103     unsigned int backgroundSpec;
104     pixel        background_color;
105     unsigned int centerline;
106     float        corner_always_threshold;
107     unsigned int corner_surround;
108     float        corner_threshold;
109     unsigned int dpi;
110     float        error_threshold;
111     unsigned int filter_iterations;
112     float        line_reversion_threshold;
113     float        line_threshold;
114     unsigned int log;
115     unsigned int preserve_width;
116     unsigned int remove_adjacent_corners;
117     unsigned int tangent_surround;
118     unsigned int report_progress;
119     float        width_weight_factor;
120 };
121 
122 
123 static void
parseCommandLine(int argc,char ** argv,struct cmdlineInfo * const cmdlineP)124 parseCommandLine(int argc,
125                  char ** argv,
126                  struct cmdlineInfo  * const cmdlineP) {
127 /* --------------------------------------------------------------------------
128    Parse program command line described in Unix standard form by argc
129    and argv.  Return the information in the options as *cmdlineP.
130 
131    If command line is internally inconsistent (invalid options, etc.),
132    issue error message to stderr and abort program.
133 
134    Note that the strings we return are stored in the storage that
135    was passed to us as the argv array.  We also trash *argv.
136 --------------------------------------------------------------------------*/
137     optEntry * option_def;
138     /* Instructions to pm_optParseOptions3 on how to parse our options. */
139     optStruct3 opt;
140 
141     const char * background_colorOpt;
142 
143     unsigned int option_def_index;
144 
145     MALLOCARRAY_NOFAIL(option_def, 100);
146 
147     option_def_index = 0;   /* incremented by OPTENT3 */
148     OPTENT3(0, "align-threshold",     OPT_FLOAT,
149             &cmdlineP->align_threshold,  NULL,                              0);
150     OPTENT3(0, "background-color",    OPT_STRING,
151             &background_colorOpt,        &cmdlineP->backgroundSpec,         0);
152     OPTENT3(0, "centerline",          OPT_FLAG,
153             NULL,                        &cmdlineP->centerline,             0);
154     OPTENT3(0, "corner-always-threshold", OPT_FLOAT,
155             &cmdlineP->corner_always_threshold, NULL,                       0);
156     OPTENT3(0, "corner-surround",     OPT_UINT,
157             &cmdlineP->corner_surround,  NULL,                              0);
158     OPTENT3(0, "corner-threshold",    OPT_FLOAT,
159             &cmdlineP->corner_threshold, NULL,                              0);
160     OPTENT3(0, "dpi",                 OPT_UINT,
161             &cmdlineP->dpi,              NULL,                              0);
162     OPTENT3(0, "error-threshold",     OPT_FLOAT,
163             &cmdlineP->error_threshold,  NULL,                              0);
164     OPTENT3(0, "filter-iterations",   OPT_UINT,
165             &cmdlineP->filter_iterations, NULL,                             0);
166     OPTENT3(0, "line-reversion-threshold", OPT_FLOAT,
167             &cmdlineP->line_reversion_threshold, NULL,                    0);
168     OPTENT3(0, "line-threshold",      OPT_FLOAT,
169             &cmdlineP->line_threshold, NULL,                                0);
170     OPTENT3(0, "log",                 OPT_FLAG,
171             NULL,                         &cmdlineP->log,                   0);
172     OPTENT3(0, "preserve-width",      OPT_FLAG,
173             NULL,                         &cmdlineP->preserve_width,        0);
174     OPTENT3(0, "remove-adjacent-corners", OPT_UINT,
175             NULL,                       &cmdlineP->remove_adjacent_corners, 0);
176     OPTENT3(0, "tangent-surround",    OPT_UINT,
177             &cmdlineP->tangent_surround, NULL,                              0);
178     OPTENT3(0, "report-progress",     OPT_FLAG,
179             NULL,                       &cmdlineP->report_progress,         0);
180     OPTENT3(0, "width-weight-factor", OPT_FLOAT,
181             &cmdlineP->width_weight_factor, NULL,                           0);
182 
183 
184     opt.opt_table = option_def;
185     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
186     opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
187 
188     /* Set some defaults the lazy way (using multiple setting of variables) */
189 
190     cmdlineP->corner_always_threshold  = 60.0;
191     cmdlineP->corner_surround          = 4;
192     cmdlineP->corner_threshold         = 100.0;
193     cmdlineP->error_threshold          = 2.0;
194     cmdlineP->filter_iterations        = 4;
195     cmdlineP->line_reversion_threshold = 0.01;
196     cmdlineP->line_threshold           = 1.0;
197     cmdlineP->tangent_surround         = 3;
198     cmdlineP->width_weight_factor      = 6.0;
199 
200     pm_optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
201     /* Uses and sets argc, argv, and some of *cmdlineP and others. */
202 
203     if (cmdlineP->backgroundSpec)
204         cmdlineP->background_color = ppm_parsecolor(background_colorOpt, 255);
205 
206     if (argc-1 < 1)
207         cmdlineP->inputFileName = "-";
208     else {
209         cmdlineP->inputFileName = argv[1];
210 
211         if (argc-1 > 1)
212             pm_error("Too many arguments (%u).  The only non-option argument "
213                      "is the input file name.", argc-1);
214     }
215     free(option_def);
216 }
217 
218 
219 
220 static void
fitSplines(at_bitmap_type * const bitmapP,struct cmdlineInfo const cmdline,at_msg_func exceptionHandler,at_progress_func progressFunc,at_spline_list_array_type ** const splinesPP)221 fitSplines(at_bitmap_type *             const bitmapP,
222            struct cmdlineInfo           const cmdline,
223            at_msg_func                        exceptionHandler,
224            at_progress_func                   progressFunc,
225            at_spline_list_array_type ** const splinesPP) {
226 
227     unsigned int progressStat;
228     at_fitting_opts_type * fittingOptsP;
229 
230     progressStat = 0;
231 
232     fittingOptsP = at_fitting_opts_new();
233 
234     fittingOptsP->backgroundSpec           = cmdline.backgroundSpec;
235     fittingOptsP->background_color         = cmdline.background_color;
236     fittingOptsP->corner_always_threshold  = cmdline.corner_always_threshold;
237     fittingOptsP->corner_surround          = cmdline.corner_surround;
238     fittingOptsP->corner_threshold         = cmdline.corner_threshold;
239     fittingOptsP->error_threshold          = cmdline.error_threshold;
240     fittingOptsP->filter_iterations        = cmdline.filter_iterations;
241     fittingOptsP->line_reversion_threshold = cmdline.line_reversion_threshold;
242     fittingOptsP->line_threshold           = cmdline.line_threshold;
243     fittingOptsP->remove_adjacent_corners  = cmdline.remove_adjacent_corners;
244     fittingOptsP->tangent_surround         = cmdline.tangent_surround;
245     fittingOptsP->centerline               = cmdline.centerline;
246     fittingOptsP->preserve_width           = cmdline.preserve_width;
247     fittingOptsP->width_weight_factor      = cmdline.width_weight_factor;
248 
249     *splinesPP = at_splines_new_full(bitmapP, fittingOptsP,
250                                      exceptionHandler, NULL,
251                                      progressFunc, &progressStat,
252                                      NULL, NULL);
253 
254     at_fitting_opts_free(fittingOptsP);
255 }
256 
257 
258 
259 static void
writeSplines(at_spline_list_array_type * const splinesP,struct cmdlineInfo const cmdline,at_output_write_func outputWriter,FILE * const ofP,at_msg_func exceptionHandler)260 writeSplines(at_spline_list_array_type * const splinesP,
261              struct cmdlineInfo          const cmdline,
262              at_output_write_func              outputWriter,
263              FILE *                      const ofP,
264              at_msg_func                       exceptionHandler) {
265 
266     at_output_opts_type * outputOptsP;
267 
268     outputOptsP = at_output_opts_new();
269     outputOptsP->dpi = cmdline.dpi;
270 
271     at_splines_write(outputWriter, ofP, outputOptsP,
272                      splinesP, exceptionHandler, NULL);
273 
274     at_output_opts_free(outputOptsP);
275 }
276 
277 
278 
279 static const char *
filenameRoot(const char * const filename)280 filenameRoot(const char * const filename) {
281 /*----------------------------------------------------------------------------
282    Return the root of the filename.  E.g. for /home/bryanh/foo.ppm,
283    return 'foo'.
284 -----------------------------------------------------------------------------*/
285     char * buffer;
286     bool foundSlash;
287     unsigned int slashPos;
288     bool foundDot;
289     unsigned int dotPos;
290     unsigned int rootStart, rootEnd;
291     unsigned int i, j;
292 
293     for (i = 0, foundSlash = FALSE; i < strlen(filename); ++i) {
294         if (filename[i] == '/') {
295             foundSlash = TRUE;
296             slashPos = i;
297         }
298     }
299 
300     if (foundSlash)
301         rootStart = slashPos + 1;
302     else
303         rootStart = 0;
304 
305     for (i = rootStart, foundDot = FALSE; i < strlen(filename); ++i) {
306         if (filename[i] == '.') {
307             foundDot = TRUE;
308             dotPos = i;
309         }
310     }
311 
312     if (foundDot)
313         rootEnd = dotPos;
314     else
315         rootEnd = strlen(filename);
316 
317     MALLOCARRAY(buffer, rootEnd - rootStart + 1);
318 
319     j = 0;
320     for (i = rootStart; i < rootEnd; ++i)
321         buffer[j++] = filename[i];
322 
323     buffer[j] = '\0';
324 
325     return buffer;
326 }
327 
328 
329 
330 static void
openLogFile(FILE ** const logFileP,const char * const inputFileArg)331 openLogFile(FILE **      const logFileP,
332             const char * const inputFileArg) {
333 
334     const char * logfileName;
335 
336     if (streq(inputFileArg, "-"))
337         pm_asprintf(&logfileName, "pamtosvg.log");
338     else {
339         const char * inputRootName;
340 
341         inputRootName = filenameRoot(inputFileArg);
342         if (inputRootName == NULL)
343             pm_error("Can't find the root portion of file name '%s'",
344                      inputFileArg);
345 
346         pm_asprintf(&logfileName, "%s.log", inputRootName);
347 
348         pm_strfree(inputRootName);
349     }
350 
351     *logFileP = pm_openw(logfileName);
352 
353     pm_strfree(logfileName);
354 }
355 
356 
357 
358 int
main(int argc,char * argv[])359 main(int argc, char * argv[]) {
360 
361     struct cmdlineInfo cmdline;
362     FILE * ifP;
363     at_bitmap_type * bitmapP;
364     at_spline_list_array_type * splinesP;
365     at_progress_func progressReporter;
366 
367     pnm_init(&argc, argv);
368 
369     parseCommandLine(argc, argv, &cmdline);
370 
371     ifP = pm_openr(cmdline.inputFileName);
372 
373     if (cmdline.log)
374         openLogFile(&log_file, cmdline.inputFileName);
375 
376     readImageToBitmap(ifP, &bitmapP);
377 
378     if (cmdline.report_progress) {
379         progressReporter = dotPrinter;
380         fprintf(stderr, "%-15s", cmdline.inputFileName);
381     } else
382         progressReporter = NULL;
383 
384     fitSplines(bitmapP, cmdline, exceptionHandler,
385                progressReporter, &splinesP);
386 
387     writeSplines(splinesP, cmdline, output_svg_writer, stdout,
388                  exceptionHandler);
389 
390     pm_close(stdout);
391     pm_close(ifP);
392     if (cmdline.log)
393         pm_close(log_file);
394 
395     at_splines_free(splinesP);
396     at_bitmap_free(bitmapP);
397 
398     if (cmdline.report_progress)
399         fputs("\n", stderr);
400 
401     return 0;
402 }
403