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(&reg);
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