1 /*
2 libdmtx - Data Matrix Encoding/Decoding Library
3
4 Copyright (C) 2008, 2009 Mike Laughton
5 Copyright (C) 2008 Ryan Raasch
6 Copyright (C) 2008 Olivier Guilyardi
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
22 Contact: mike@dragonflylogic.com
23 */
24
25 #include "dmtxread.h"
26
27 char *programName;
28
29 /**
30 * @brief Main function for the dmtxread Data Matrix scanning utility.
31 * @param argc count of arguments passed from command line
32 * @param argv list of argument passed strings from command line
33 * @return Numeric exit code
34 */
35 int
main(int argc,char * argv[])36 main(int argc, char *argv[])
37 {
38 char *filePath;
39 int i;
40 int err;
41 int fileIndex, imgPageIndex;
42 int fileCount;
43 int imgScanCount, pageScanCount;
44 int width, height;
45 unsigned char *pxl;
46 UserOptions opt;
47 DmtxTime timeout;
48 DmtxImage *img;
49 DmtxDecode *dec;
50 DmtxRegion *reg;
51 DmtxMessage *msg;
52 MagickBooleanType success;
53 MagickWand *wand;
54
55 opt = GetDefaultOptions();
56
57 err = HandleArgs(&opt, &fileIndex, &argc, &argv);
58 if(err != DmtxPass)
59 ShowUsage(EX_USAGE);
60
61 fileCount = (argc == fileIndex) ? 1 : argc - fileIndex;
62
63 MagickWandGenesis();
64
65 /* Loop once for each image named on command line */
66 imgScanCount = 0;
67 for(i = 0; i < fileCount; i++) {
68
69 /* Open image from file or stream (might contain multiple pages) */
70 filePath = (argc == fileIndex) ? "-" : argv[fileIndex++];
71
72 wand = NewMagickWand();
73 if(wand == NULL) {
74 FatalError(EX_OSERR, "Magick error");
75 }
76
77 /* XXX note this is not the same as MagickSetImageResolution() ...
78 * need to research what this is setting. Could be dots per inch, dots
79 * per centimeter, or even dots per "image width" */
80 if(opt.dpi != DmtxUndefined) {
81 success = MagickSetResolution(wand, (double)opt.dpi, (double)opt.dpi);
82 if(success == MagickFalse) {
83 CleanupMagick(&wand, DmtxTrue);
84 FatalError(EX_OSERR, "Unable to set image resolution");
85 }
86 }
87
88 success = MagickReadImage(wand, filePath);
89 if(success == MagickFalse) {
90 CleanupMagick(&wand, DmtxTrue);
91 FatalError(EX_OSERR, "Unable to open file \"%s\" for reading", filePath);
92 }
93
94 width = MagickGetImageWidth(wand);
95 height = MagickGetImageHeight(wand);
96
97 /* Loop once for each page within image */
98 MagickResetIterator(wand);
99 for(imgPageIndex = 0; MagickNextImage(wand) != MagickFalse; imgPageIndex++) {
100
101 /* If requested, only scan specific page */
102 if(opt.page != DmtxUndefined && opt.page - 1 != imgPageIndex)
103 continue;
104
105 /* Reset timeout for each new page */
106 if(opt.timeoutMS != DmtxUndefined)
107 timeout = dmtxTimeAdd(dmtxTimeNow(), opt.timeoutMS);
108
109 /* Allocate memory for pixel data */
110 pxl = (unsigned char *)malloc(3 * width * height * sizeof(unsigned char));
111 if(pxl == NULL) {
112 CleanupMagick(&wand, DmtxFalse);
113 FatalError(EX_OSERR, "malloc() error");
114 }
115
116 /* Copy pixels to known format */
117 success = MagickGetImagePixels(wand, 0, 0, width, height, "RGB", CharPixel, pxl);
118 if(success == MagickFalse || pxl == NULL) {
119 CleanupMagick(&wand, DmtxTrue);
120 FatalError(EX_OSERR, "malloc() error");
121 }
122
123 /* Initialize libdmtx image */
124 img = dmtxImageCreate(pxl, width, height, DmtxPack24bppRGB);
125 if(img == NULL) {
126 CleanupMagick(&wand, DmtxFalse);
127 FatalError(EX_SOFTWARE, "dmtxImageCreate() error");
128 }
129
130 dmtxImageSetProp(img, DmtxPropImageFlip, DmtxFlipNone);
131
132 /* Initialize scan */
133 dec = dmtxDecodeCreate(img, opt.shrinkMin);
134 if(dec == NULL) {
135 CleanupMagick(&wand, DmtxFalse);
136 FatalError(EX_SOFTWARE, "decode create error");
137 }
138
139 err = SetDecodeOptions(dec, img, &opt);
140 if(err != DmtxPass) {
141 CleanupMagick(&wand, DmtxFalse);
142 FatalError(EX_SOFTWARE, "decode option error");
143 }
144
145 /* Find and decode every barcode on page */
146 pageScanCount = 0;
147 for(;;) {
148 /* Find next barcode region within image, but do not decode yet */
149 if(opt.timeoutMS == DmtxUndefined)
150 reg = dmtxRegionFindNext(dec, NULL);
151 else
152 reg = dmtxRegionFindNext(dec, &timeout);
153
154 /* Finished file or ran out of time before finding another region */
155 if(reg == NULL)
156 break;
157
158 /* Decode region based on requested barcode mode */
159 if(opt.mosaic == DmtxTrue)
160 msg = dmtxDecodeMosaicRegion(dec, reg, opt.correctionsMax);
161 else
162 msg = dmtxDecodeMatrixRegion(dec, reg, opt.correctionsMax);
163
164 if(msg != NULL) {
165 PrintStats(dec, reg, msg, imgPageIndex, &opt);
166 PrintMessage(reg, msg, &opt);
167
168 pageScanCount++;
169 imgScanCount++;
170
171 dmtxMessageDestroy(&msg);
172 }
173
174 dmtxRegionDestroy(®);
175
176 if(opt.stopAfter != DmtxUndefined && imgScanCount >= opt.stopAfter)
177 break;
178 }
179
180 if(opt.diagnose == DmtxTrue)
181 WriteDiagnosticImage(dec, "debug.pnm");
182
183 dmtxDecodeDestroy(&dec);
184 dmtxImageDestroy(&img);
185 free(pxl);
186 }
187
188 CleanupMagick(&wand, DmtxFalse);
189 }
190
191 MagickWandTerminus();
192
193 exit((imgScanCount > 0) ? EX_OK : 1);
194 }
195
196 /**
197 *
198 *
199 */
200 static UserOptions
GetDefaultOptions(void)201 GetDefaultOptions(void)
202 {
203 UserOptions opt;
204
205 memset(&opt, 0x00, sizeof(UserOptions));
206
207 /* Default options */
208 opt.codewords = DmtxFalse;
209 opt.edgeMin = DmtxUndefined;
210 opt.edgeMax = DmtxUndefined;
211 opt.scanGap = 2;
212 opt.timeoutMS = DmtxUndefined;
213 opt.newline = DmtxFalse;
214 opt.page = DmtxUndefined;
215 opt.squareDevn = DmtxUndefined;
216 opt.dpi = DmtxUndefined;
217 opt.sizeIdxExpected = DmtxSymbolShapeAuto;
218 opt.edgeThresh = 5;
219 opt.xMin = NULL;
220 opt.xMax = NULL;
221 opt.yMin = NULL;
222 opt.yMax = NULL;
223 opt.correctionsMax = DmtxUndefined;
224 opt.diagnose = DmtxFalse;
225 opt.mosaic = DmtxFalse;
226 opt.stopAfter = DmtxUndefined;
227 opt.pageNumbers = DmtxFalse;
228 opt.corners = DmtxFalse;
229 opt.shrinkMin = 1;
230 opt.shrinkMax = 1;
231 opt.unicode = DmtxFalse;
232 opt.verbose = DmtxFalse;
233 opt.gs1 = DmtxUndefined;
234
235 return opt;
236 }
237
238 /**
239 * @brief Set and validate user-requested options from command line arguments.
240 * @param opt runtime options from defaults or command line
241 * @param argcp pointer to argument count
242 * @param argvp pointer to argument list
243 * @param fileIndex pointer to index of first non-option arg (if successful)
244 * @return DmtxPass | DmtxFail
245 */
246 static DmtxPassFail
HandleArgs(UserOptions * opt,int * fileIndex,int * argcp,char ** argvp[])247 HandleArgs(UserOptions *opt, int *fileIndex, int *argcp, char **argvp[])
248 {
249 int i;
250 int err;
251 int optchr;
252 int longIndex;
253 char *ptr;
254
255 struct option longOptions[] = {
256 {"codewords", no_argument, NULL, 'c'},
257 {"minimum-edge", required_argument, NULL, 'e'},
258 {"maximum-edge", required_argument, NULL, 'E'},
259 {"gap", required_argument, NULL, 'g'},
260 {"list-formats", no_argument, NULL, 'l'},
261 {"milliseconds", required_argument, NULL, 'm'},
262 {"newline", no_argument, NULL, 'n'},
263 {"page", required_argument, NULL, 'p'},
264 {"square-deviation", required_argument, NULL, 'q'},
265 {"resolution", required_argument, NULL, 'r'},
266 {"symbol-size", required_argument, NULL, 's'},
267 {"threshold", required_argument, NULL, 't'},
268 {"x-range-min", required_argument, NULL, 'x'},
269 {"x-range-max", required_argument, NULL, 'X'},
270 {"y-range-min", required_argument, NULL, 'y'},
271 {"y-range-max", required_argument, NULL, 'Y'},
272 {"max-corrections", required_argument, NULL, 'C'},
273 {"diagnose", no_argument, NULL, 'D'},
274 {"mosaic", no_argument, NULL, 'M'},
275 {"stop-after", required_argument, NULL, 'N'},
276 {"page-numbers", no_argument, NULL, 'P'},
277 {"corners", no_argument, NULL, 'R'},
278 {"shrink", required_argument, NULL, 'S'},
279 {"unicode", no_argument, NULL, 'U'},
280 {"gs1", required_argument, NULL, 'G'},
281 {"verbose", no_argument, NULL, 'v'},
282 {"version", no_argument, NULL, 'V'},
283 {"help", no_argument, NULL, 0 },
284 {0, 0, 0, 0}
285 };
286
287 programName = Basename((*argvp)[0]);
288
289 *fileIndex = 0;
290
291 for(;;) {
292 optchr = getopt_long(*argcp, *argvp,
293 "ce:E:g:lm:np:q:r:s:t:x:X:y:Y:vC:DMN:PRS:G:UV", longOptions, &longIndex);
294 if(optchr == -1)
295 break;
296
297 switch(optchr) {
298 case 0: /* --help */
299 ShowUsage(EX_OK);
300 break;
301 case 'l':
302 ListImageFormats();
303 exit(EX_OK);
304 break;
305 case 'c':
306 opt->codewords = DmtxTrue;
307 break;
308 case 'e':
309 err = StringToInt(&(opt->edgeMin), optarg, &ptr);
310 if(err != DmtxPass || opt->edgeMin <= 0 || *ptr != '\0')
311 FatalError(EX_USAGE, _("Invalid edge length specified \"%s\""), optarg);
312 break;
313 case 'E':
314 err = StringToInt(&(opt->edgeMax), optarg, &ptr);
315 if(err != DmtxPass || opt->edgeMax <= 0 || *ptr != '\0')
316 FatalError(EX_USAGE, _("Invalid edge length specified \"%s\""), optarg);
317 break;
318 case 'g':
319 err = StringToInt(&(opt->scanGap), optarg, &ptr);
320 if(err != DmtxPass || opt->scanGap <= 0 || *ptr != '\0')
321 FatalError(EX_USAGE, _("Invalid gap specified \"%s\""), optarg);
322 break;
323 case 'm':
324 err = StringToInt(&(opt->timeoutMS), optarg, &ptr);
325 if(err != DmtxPass || opt->timeoutMS < 0 || *ptr != '\0')
326 FatalError(EX_USAGE, _("Invalid timeout (in milliseconds) specified \"%s\""), optarg);
327 break;
328 case 'n':
329 opt->newline = DmtxTrue;
330 break;
331 case 'p':
332 err = StringToInt(&(opt->page), optarg, &ptr);
333 if(err != DmtxPass || opt->page < 1 || *ptr != '\0')
334 FatalError(EX_USAGE, _("Invalid page specified \"%s\""), optarg);
335 break;
336 case 'q':
337 err = StringToInt(&(opt->squareDevn), optarg, &ptr);
338 if(err != DmtxPass || *ptr != '\0' ||
339 opt->squareDevn < 0 || opt->squareDevn > 90)
340 FatalError(EX_USAGE, _("Invalid squareness deviation specified \"%s\""), optarg);
341 break;
342 case 'r':
343 err = StringToInt(&(opt->dpi), optarg, &ptr);
344 if(err != DmtxPass || *ptr != '\0' || opt->dpi < 1)
345 FatalError(EX_USAGE, _("Invalid resolution specified \"%s\""), optarg);
346 break;
347 case 's':
348 /* Determine correct barcode size and/or shape */
349 if(*optarg == 'a') {
350 opt->sizeIdxExpected = DmtxSymbolShapeAuto;
351 }
352 else if(*optarg == 's') {
353 opt->sizeIdxExpected = DmtxSymbolSquareAuto;
354 }
355 else if(*optarg == 'r') {
356 opt->sizeIdxExpected = DmtxSymbolRectAuto;
357 }
358 else {
359 for(i = 0; i < DmtxSymbolSquareCount + DmtxSymbolRectCount; i++) {
360 if(strncmp(optarg, symbolSizes[i], 8) == 0) {
361 opt->sizeIdxExpected = i;
362 break;
363 }
364 }
365 if(i == DmtxSymbolSquareCount + DmtxSymbolRectCount)
366 return DmtxFail;
367 }
368 break;
369 case 't':
370 err = StringToInt(&(opt->edgeThresh), optarg, &ptr);
371 if(err != DmtxPass || *ptr != '\0' ||
372 opt->edgeThresh < 1 || opt->edgeThresh > 100)
373 FatalError(EX_USAGE, _("Invalid edge threshold specified \"%s\""), optarg);
374 break;
375 case 'x':
376 opt->xMin = optarg;
377 break;
378 case 'X':
379 opt->xMax = optarg;
380 break;
381 case 'y':
382 opt->yMin = optarg;
383 break;
384 case 'Y':
385 opt->yMax = optarg;
386 break;
387 case 'v':
388 opt->verbose = DmtxTrue;
389 break;
390 case 'C':
391 err = StringToInt(&(opt->correctionsMax), optarg, &ptr);
392 if(err != DmtxPass || opt->correctionsMax < 0 || *ptr != '\0')
393 FatalError(EX_USAGE, _("Invalid max corrections specified \"%s\""), optarg);
394 break;
395 case 'D':
396 opt->diagnose = DmtxTrue;
397 break;
398 case 'M':
399 opt->mosaic = DmtxTrue;
400 break;
401 case 'N':
402 err = StringToInt(&(opt->stopAfter), optarg, &ptr);
403 if(err != DmtxPass || opt->stopAfter < 1 || *ptr != '\0')
404 FatalError(EX_USAGE, _("Invalid count specified \"%s\""), optarg);
405 break;
406 case 'P':
407 opt->pageNumbers = DmtxTrue;
408 break;
409 case 'R':
410 opt->corners = DmtxTrue;
411 break;
412 case 'S':
413 err = StringToInt(&(opt->shrinkMin), optarg, &ptr);
414 if(err != DmtxPass || opt->shrinkMin < 1 || *ptr != '\0')
415 FatalError(EX_USAGE, _("Invalid shrink factor specified \"%s\""), optarg);
416
417 /* XXX later populate shrinkMax based on specified N-N range */
418 opt->shrinkMax = opt->shrinkMin;
419 break;
420 case 'U':
421 opt->unicode = DmtxTrue;
422 break;
423 case 'G':
424 err = StringToInt(&(opt->gs1), optarg, &ptr);
425 if(err != DmtxPass || opt->gs1 <= 0 || opt->gs1 > 255 || *ptr != '\0')
426 FatalError(EX_USAGE, _("Invalid gs1 character specified \"%s\""), optarg);
427 break;
428 case 'V':
429 fprintf(stderr, "%s version %s\n", programName, DmtxVersion);
430 fprintf(stderr, "libdmtx version %s\n", dmtxVersion());
431 exit(EX_OK);
432 break;
433 default:
434 return DmtxFail;
435 break;
436 }
437 }
438 *fileIndex = optind;
439
440 return DmtxPass;
441 }
442
443 /**
444 * @brief Display program usage and exit with received status.
445 * @param status error code returned to OS
446 * @return void
447 */
448 static void
ShowUsage(int status)449 ShowUsage(int status)
450 {
451 if(status != 0) {
452 fprintf(stderr, _("Usage: %s [OPTION]... [FILE]...\n"), programName);
453 fprintf(stderr, _("Try `%s --help' for more information.\n"), programName);
454 }
455 else {
456 fprintf(stderr, _("Usage: %s [OPTION]... [FILE]...\n"), programName);
457 fprintf(stderr, _("\
458 Scan image FILE for Data Matrix barcodes and print decoded results to\n\
459 standard output. Note that %s may find multiple barcodes in one image.\n\
460 \n\
461 Example: Scan top third of IMAGE001.png and stop after first barcode is found:\n\
462 \n\
463 %s -n -Y33%% -N1 IMAGE001.png\n\
464 \n\
465 OPTIONS:\n"), programName, programName);
466 fprintf(stderr, _("\
467 -c, --codewords print codewords extracted from barcode pattern\n\
468 -e, --minimum-edge=N pixel length of smallest expected edge in image\n\
469 -E, --maximum-edge=N pixel length of largest expected edge in image\n\
470 -g, --gap=N use scan grid with gap of N pixels between lines\n\
471 -l, --list-formats list supported image formats\n"));
472 fprintf(stderr, _("\
473 -m, --milliseconds=N stop scan after N milliseconds (per image)\n\
474 -n, --newline print newline character at the end of decoded data\n\
475 -p, --page=N only scan Nth page of images\n\
476 -q, --square-deviation=N allow non-squareness of corners in degrees (0-90)\n\
477 -r, --resolution=N resolution for vector images (PDF, SVG, etc...)\n"));
478 fprintf(stderr, _("\
479 -s, --symbol-size=[asr|RxC] only consider barcodes of specific size or shape\n\
480 a = All sizes [default]\n\
481 s = Only squares\n\
482 r = Only rectangles\n\
483 RxC = Exactly this many rows and columns (10x10, 8x18, etc...)\n"));
484 fprintf(stderr, _("\
485 -t, --threshold=N ignore weak edges below threshold N (1-100)\n\
486 -x, --x-range-min=N[%%] do not scan pixels to the left of N (or N%%)\n\
487 -X, --x-range-max=N[%%] do not scan pixels to the right of N (or N%%)\n\
488 -y, --y-range-min=N[%%] do not scan pixels below N (or N%%)\n\
489 -Y, --y-range-max=N[%%] do not scan pixels above N (or N%%)\n"));
490 fprintf(stderr, _("\
491 -C, --corrections-max=N correct at most N errors (0 = correction disabled)\n\
492 -D, --diagnose make copy of image with additional diagnostic data\n\
493 -M, --mosaic interpret detected regions as Data Mosaic barcodes\n\
494 -N, --stop-after=N stop scanning after Nth barcode is returned\n\
495 -P, --page-numbers prefix decoded message with fax/tiff page number\n"));
496 fprintf(stderr, _("\
497 -R, --corners prefix decoded message with corner locations\n\
498 -S, --shrink=N internally shrink image by a factor of N\n\
499 -U, --unicode print Extended ASCII in Unicode (UTF-8)\n\
500 -G, --gs1=N enable GS1 mode and define character to represent FNC1\n\
501 -v, --verbose use verbose messages\n\
502 -V, --version print program version information\n\
503 --help display this help and exit\n"));
504 fprintf(stderr, _("\nReport bugs to <mike@dragonflylogic.com>.\n"));
505 }
506
507 exit(status);
508 }
509
510 /**
511 *
512 *
513 */
514 static DmtxPassFail
SetDecodeOptions(DmtxDecode * dec,DmtxImage * img,UserOptions * opt)515 SetDecodeOptions(DmtxDecode *dec, DmtxImage *img, UserOptions *opt)
516 {
517 int err;
518
519 #define RETURN_IF_FAILED(e) if(e != DmtxPass) { return DmtxFail; }
520
521 err = dmtxDecodeSetProp(dec, DmtxPropScanGap, opt->scanGap);
522 RETURN_IF_FAILED(err)
523
524 if(opt->gs1 != DmtxUndefined) {
525 err = dmtxDecodeSetProp(dec, DmtxPropFnc1, opt->gs1);
526 RETURN_IF_FAILED(err)
527 }
528
529 if(opt->edgeMin != DmtxUndefined) {
530 err = dmtxDecodeSetProp(dec, DmtxPropEdgeMin, opt->edgeMin);
531 RETURN_IF_FAILED(err)
532 }
533
534 if(opt->edgeMax != DmtxUndefined) {
535 err = dmtxDecodeSetProp(dec, DmtxPropEdgeMax, opt->edgeMax);
536 RETURN_IF_FAILED(err)
537 }
538
539 if(opt->squareDevn != DmtxUndefined) {
540 err = dmtxDecodeSetProp(dec, DmtxPropSquareDevn, opt->squareDevn);
541 RETURN_IF_FAILED(err)
542 }
543
544 err = dmtxDecodeSetProp(dec, DmtxPropSymbolSize, opt->sizeIdxExpected);
545 RETURN_IF_FAILED(err)
546
547 err = dmtxDecodeSetProp(dec, DmtxPropEdgeThresh, opt->edgeThresh);
548 RETURN_IF_FAILED(err)
549
550 if(opt->xMin) {
551 err = dmtxDecodeSetProp(dec, DmtxPropXmin, ScaleNumberString(opt->xMin, img->width));
552 RETURN_IF_FAILED(err)
553 }
554
555 if(opt->xMax) {
556 err = dmtxDecodeSetProp(dec, DmtxPropXmax, ScaleNumberString(opt->xMax, img->width));
557 RETURN_IF_FAILED(err)
558 }
559
560 if(opt->yMin) {
561 err = dmtxDecodeSetProp(dec, DmtxPropYmin, ScaleNumberString(opt->yMin, img->height));
562 RETURN_IF_FAILED(err)
563 }
564
565 if(opt->yMax) {
566 err = dmtxDecodeSetProp(dec, DmtxPropYmax, ScaleNumberString(opt->yMax, img->height));
567 RETURN_IF_FAILED(err)
568 }
569
570 #undef RETURN_IF_FAILED
571
572 return DmtxPass;
573 }
574
575 /**
576 * @brief Print decoded message to standard output
577 * @param opt runtime options from defaults or command line
578 * @param dec pointer to DmtxDecode struct
579 * @return DmtxPass | DmtxFail
580 */
581 static DmtxPassFail
PrintStats(DmtxDecode * dec,DmtxRegion * reg,DmtxMessage * msg,int imgPageIndex,UserOptions * opt)582 PrintStats(DmtxDecode *dec, DmtxRegion *reg, DmtxMessage *msg,
583 int imgPageIndex, UserOptions *opt)
584 {
585 int height;
586 int dataWordLength;
587 int rotateInt;
588 double rotate;
589 DmtxVector2 p00, p10, p11, p01;
590
591 height = dmtxDecodeGetProp(dec, DmtxPropHeight);
592
593 p00.X = p00.Y = p10.Y = p01.X = 0.0;
594 p10.X = p01.Y = p11.X = p11.Y = 1.0;
595 dmtxMatrix3VMultiplyBy(&p00, reg->fit2raw);
596 dmtxMatrix3VMultiplyBy(&p10, reg->fit2raw);
597 dmtxMatrix3VMultiplyBy(&p11, reg->fit2raw);
598 dmtxMatrix3VMultiplyBy(&p01, reg->fit2raw);
599
600 dataWordLength = dmtxGetSymbolAttribute(DmtxSymAttribSymbolDataWords, reg->sizeIdx);
601 if(opt->verbose == DmtxTrue) {
602
603 /* rotate = (2 * M_PI) + (atan2(reg->fit2raw[0][1], reg->fit2raw[1][1]) -
604 atan2(reg->fit2raw[1][0], reg->fit2raw[0][0])) / 2.0; */
605 rotate = (2 * M_PI) + atan2(p10.Y - p00.Y, p10.X - p00.X);
606
607 rotateInt = (int)(rotate * 180/M_PI + 0.5);
608 if(rotateInt >= 360)
609 rotateInt -= 360;
610
611 fprintf(stderr, "--------------------------------------------------\n");
612 fprintf(stderr, " Matrix Size: %d x %d\n",
613 dmtxGetSymbolAttribute(DmtxSymAttribSymbolRows, reg->sizeIdx),
614 dmtxGetSymbolAttribute(DmtxSymAttribSymbolCols, reg->sizeIdx));
615 fprintf(stderr, " Data Codewords: %d (capacity %d)\n",
616 dataWordLength - msg->padCount, dataWordLength);
617 fprintf(stderr, " Error Codewords: %d\n",
618 dmtxGetSymbolAttribute(DmtxSymAttribSymbolErrorWords, reg->sizeIdx));
619 fprintf(stderr, " Data Regions: %d x %d\n",
620 dmtxGetSymbolAttribute(DmtxSymAttribHorizDataRegions, reg->sizeIdx),
621 dmtxGetSymbolAttribute(DmtxSymAttribVertDataRegions, reg->sizeIdx));
622 fprintf(stderr, "Interleaved Blocks: %d\n",
623 dmtxGetSymbolAttribute(DmtxSymAttribInterleavedBlocks, reg->sizeIdx));
624 fprintf(stderr, " Rotation Angle: %d\n", rotateInt);
625 fprintf(stderr, " Corner 0: (%0.1f, %0.1f)\n", p00.X, height - 1 - p00.Y);
626 fprintf(stderr, " Corner 1: (%0.1f, %0.1f)\n", p10.X, height - 1 - p10.Y);
627 fprintf(stderr, " Corner 2: (%0.1f, %0.1f)\n", p11.X, height - 1 - p11.Y);
628 fprintf(stderr, " Corner 3: (%0.1f, %0.1f)\n", p01.X, height - 1 - p01.Y);
629 fprintf(stderr, "--------------------------------------------------\n");
630 }
631
632 if(opt->pageNumbers == DmtxTrue)
633 fprintf(stderr, "%d:", imgPageIndex + 1);
634
635 if(opt->corners == DmtxTrue) {
636 fprintf(stderr, "%d,%d:", (int)(p00.X + 0.5), height - 1 - (int)(p00.Y + 0.5));
637 fprintf(stderr, "%d,%d:", (int)(p10.X + 0.5), height - 1 - (int)(p10.Y + 0.5));
638 fprintf(stderr, "%d,%d:", (int)(p11.X + 0.5), height - 1 - (int)(p11.Y + 0.5));
639 fprintf(stderr, "%d,%d:", (int)(p01.X + 0.5), height - 1 - (int)(p01.Y + 0.5));
640 }
641
642 return DmtxPass;
643 }
644
645 /**
646 *
647 *
648 */
649 static DmtxPassFail
PrintMessage(DmtxRegion * reg,DmtxMessage * msg,UserOptions * opt)650 PrintMessage(DmtxRegion *reg, DmtxMessage *msg, UserOptions *opt)
651 {
652 int i;
653 int remainingDataWords;
654 int dataWordLength;
655
656 if(opt->codewords == DmtxTrue) {
657 dataWordLength = dmtxGetSymbolAttribute(DmtxSymAttribSymbolDataWords, reg->sizeIdx);
658 for(i = 0; i < msg->codeSize; i++) {
659 remainingDataWords = dataWordLength - i;
660 if(remainingDataWords > msg->padCount)
661 fprintf(stdout, "%c:%03d\n", 'd', msg->code[i]);
662 else if(remainingDataWords > 0)
663 fprintf(stdout, "%c:%03d\n", 'p', msg->code[i]);
664 else
665 fprintf(stdout, "%c:%03d\n", 'e', msg->code[i]);
666 }
667 }
668 else {
669 if(opt->unicode == DmtxTrue) {
670 for(i = 0; i < msg->outputIdx; i++) {
671 if(msg->output[i] < 128) {
672 fputc(msg->output[i], stdout);
673 }
674 else if(msg->output[i] < 192) {
675 fputc(0xc2, stdout);
676 fputc(msg->output[i], stdout);
677 }
678 else {
679 fputc(0xc3, stdout);
680 fputc(msg->output[i] - 64, stdout);
681 }
682 }
683 }
684 else {
685 fwrite(msg->output, sizeof(char), msg->outputIdx, stdout);
686 }
687
688 if(opt->newline)
689 fputc('\n', stdout);
690 }
691
692 return DmtxPass;
693 }
694
695 /**
696 *
697 *
698 */
699 static void
CleanupMagick(MagickWand ** wand,int magickError)700 CleanupMagick(MagickWand **wand, int magickError)
701 {
702 char *excMessage;
703 ExceptionType excSeverity;
704
705 if(magickError == DmtxTrue) {
706 excMessage = MagickGetException(*wand, &excSeverity);
707 /* fprintf(stderr, "%s %s %lu %s\n", GetMagickModule(), excMessage); */
708 MagickRelinquishMemory(excMessage);
709 }
710
711 if(*wand != NULL) {
712 DestroyMagickWand(*wand);
713 *wand = NULL;
714 }
715 }
716
717 /**
718 * @brief List supported input image formats on stdout
719 * @return void
720 */
721 static void
ListImageFormats(void)722 ListImageFormats(void)
723 {
724 int i, idx;
725 int row, rowCount;
726 int col, colCount;
727 unsigned long totalCount;
728 char **list;
729
730 list = MagickQueryFormats("*", &totalCount);
731
732 if(list == NULL)
733 return;
734
735 fprintf(stderr, "\n");
736
737 colCount = 7;
738 rowCount = totalCount / colCount;
739 if(totalCount % colCount)
740 rowCount++;
741
742 for(i = 0; i < colCount * rowCount; i++) {
743 col = i % colCount;
744 row = i / colCount;
745 idx = col * rowCount + row;
746 fprintf(stderr, "%10s", (idx < totalCount) ? list[col * rowCount + row] : " ");
747 fprintf(stderr, "%s", (col + 1 < colCount) ? " " : "\n");
748 }
749 fprintf(stderr, "\n");
750
751 MagickRelinquishMemory(list);
752 }
753
754 /**
755 *
756 *
757 */
758 static void
WriteDiagnosticImage(DmtxDecode * dec,char * imagePath)759 WriteDiagnosticImage(DmtxDecode *dec, char *imagePath)
760 {
761 int totalBytes, headerBytes;
762 int bytesWritten;
763 unsigned char *pnm;
764 FILE *fp;
765
766 fp = fopen(imagePath, "wb");
767 if(fp == NULL) {
768 perror(programName);
769 FatalError(EX_CANTCREAT, _("Unable to create image \"%s\""), imagePath);
770 }
771
772 pnm = dmtxDecodeCreateDiagnostic(dec, &totalBytes, &headerBytes, 0);
773 if(pnm == NULL)
774 FatalError(EX_OSERR, _("Unable to create diagnostic image"));
775
776 bytesWritten = fwrite(pnm, sizeof(unsigned char), totalBytes, fp);
777 if(bytesWritten != totalBytes)
778 FatalError(EX_IOERR, _("Unable to write diagnostic image"));
779
780 free(pnm);
781 fclose(fp);
782 }
783
784 /**
785 *
786 *
787 */
788 static int
ScaleNumberString(char * s,int extent)789 ScaleNumberString(char *s, int extent)
790 {
791 int err;
792 int numValue;
793 int scaledValue;
794 char *terminate;
795
796 assert(s != NULL);
797
798 err = StringToInt(&numValue, s, &terminate);
799 if(err != DmtxPass)
800 FatalError(EX_USAGE, _("Integer value required"));
801
802 scaledValue = (*terminate == '%') ? (int)(0.01 * numValue * extent + 0.5) : numValue;
803
804 if(scaledValue < 0)
805 scaledValue = 0;
806
807 if(scaledValue >= extent)
808 scaledValue = extent - 1;
809
810 return scaledValue;
811 }
812