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