1/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3%                                                                             %
4%                                                                             %
5%                                                                             %
6%               CCCC   OOO   M   M  PPPP    AAA   RRRR    EEEEE               %
7%              C      O   O  MM MM  P   P  A   A  R   R   E                   %
8%              C      O   O  M M M  PPPP   AAAAA  RRRR    EEE                 %
9%              C      O   O  M   M  P      A   A  R R     E                   %
10%               CCCC   OOO   M   M  P      A   A  R  R    EEEEE               %
11%                                                                             %
12%                                                                             %
13%                         Image Comparison Methods                            %
14%                                                                             %
15%                              Software Design                                %
16%                                   Cristy                                    %
17%                               December 2003                                 %
18%                                                                             %
19%                                                                             %
20%  Copyright 1999-2016 ImageMagick Studio LLC, a non-profit organization      %
21%  dedicated to making software imaging solutions freely available.           %
22%                                                                             %
23%  You may not use this file except in compliance with the License.  You may  %
24%  obtain a copy of the License at                                            %
25%                                                                             %
26%    http://www.imagemagick.org/script/license.php                            %
27%                                                                             %
28%  Unless required by applicable law or agreed to in writing, software        %
29%  distributed under the License is distributed on an "AS IS" BASIS,          %
30%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
31%  See the License for the specific language governing permissions and        %
32%  limitations under the License.                                             %
33%                                                                             %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%  Use the compare program to mathematically and visually annotate the
37%  difference between an image and its reconstruction.
38%
39*/
40
41/*
42  Include declarations.
43*/
44#include "MagickWand/studio.h"
45#include "MagickWand/MagickWand.h"
46#include "MagickWand/mogrify-private.h"
47#include "MagickCore/string-private.h"
48
49/*
50%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
51%                                                                             %
52%                                                                             %
53%                                                                             %
54%   C o m p a r e I m a g e C o m m a n d                                     %
55%                                                                             %
56%                                                                             %
57%                                                                             %
58%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
59%
60%  CompareImagesCommand() compares two images and returns the difference between
61%  them as a distortion metric and as a new image visually annotating their
62%  differences.
63%
64%  The format of the CompareImagesCommand method is:
65%
66%      MagickBooleanType CompareImagesCommand(ImageInfo *image_info,int argc,
67%        char **argv,char **metadata,ExceptionInfo *exception)
68%
69%  A description of each parameter follows:
70%
71%    o image_info: the image info.
72%
73%    o argc: the number of elements in the argument vector.
74%
75%    o argv: A text array containing the command line arguments.
76%
77%    o metadata: any metadata is returned here.
78%
79%    o exception: return any errors or warnings in this structure.
80%
81*/
82
83static MagickBooleanType CompareUsage(void)
84{
85  const char
86    **p;
87
88  static const char
89    *miscellaneous[]=
90    {
91      "-channel mask        set the image channel mask",
92      "-debug events        display copious debugging information",
93      "-help                print program options",
94      "-list type           print a list of supported option arguments",
95      "-log format          format of debugging information",
96      (char *) NULL
97    },
98    *settings[]=
99    {
100      "-alpha option        on, activate, off, deactivate, set, opaque, copy",
101      "                     transparent, extract, background, or shape",
102      "-authenticate password",
103      "                     decipher image with this password",
104      "-colorspace type     alternate image colorspace",
105      "-compose operator    set image composite operator",
106      "-compress type       type of pixel compression when writing the image",
107      "-decipher filename   convert cipher pixels to plain pixels",
108      "-define format:option",
109      "                     define one or more image format options",
110      "-density geometry    horizontal and vertical density of the image",
111      "-depth value         image depth",
112      "-dissimilarity-threshold value",
113      "                     maximum distortion for (sub)image match",
114      "-encipher filename   convert plain pixels to cipher pixels",
115      "-extract geometry    extract area from image",
116      "-format \"string\"     output formatted image characteristics",
117      "-fuzz distance       colors within this distance are considered equal",
118      "-highlight-color color",
119      "                     empasize pixel differences with this color",
120      "-identify            identify the format and characteristics of the image",
121      "-interlace type      type of image interlacing scheme",
122      "-limit type value    pixel cache resource limit",
123      "-lowlight-color color",
124      "                     de-emphasize pixel differences with this color",
125      "-metric type         measure differences between images with this metric",
126      "-monitor             monitor progress",
127      "-profile filename    add, delete, or apply an image profile",
128      "-quality value       JPEG/MIFF/PNG compression level",
129      "-quiet               suppress all warning messages",
130      "-quantize colorspace reduce colors in this colorspace",
131      "-regard-warnings     pay attention to warning messages",
132      "-respect-parentheses settings remain in effect until parenthesis boundary",
133      "-sampling-factor geometry",
134      "                     horizontal and vertical sampling factor",
135      "-seed value          seed a new sequence of pseudo-random numbers",
136      "-set attribute value set an image attribute",
137      "-quality value       JPEG/MIFF/PNG compression level",
138      "-similarity-threshold value",
139      "                     minimum distortion for (sub)image match",
140      "-size geometry       width and height of image",
141      "-subimage-search     search for subimage",
142      "-synchronize         synchronize image to storage device",
143      "-taint               declare the image as modified",
144      "-transparent-color color",
145      "                     transparent color",
146      "-type type           image type",
147      "-verbose             print detailed information about the image",
148      "-version             print version information",
149      "-virtual-pixel method",
150      "                     virtual pixel access method",
151      (char *) NULL
152    };
153
154  ListMagickVersion(stdout);
155  (void) printf("Usage: %s [options ...] image reconstruct difference\n",
156    GetClientName());
157  (void) printf("\nImage Settings:\n");
158  for (p=settings; *p != (char *) NULL; p++)
159    (void) printf("  %s\n",*p);
160  (void) printf("\nMiscellaneous Options:\n");
161  for (p=miscellaneous; *p != (char *) NULL; p++)
162    (void) printf("  %s\n",*p);
163  (void) printf(
164    "\nBy default, the image format of 'file' is determined by its magic\n");
165  (void) printf(
166    "number.  To specify a particular image format, precede the filename\n");
167  (void) printf(
168    "with an image format name and a colon (i.e. ps:image) or specify the\n");
169  (void) printf(
170    "image type as the filename suffix (i.e. image.ps).  Specify 'file' as\n");
171  (void) printf("'-' for standard input or output.\n");
172  return(MagickFalse);
173}
174
175WandExport MagickBooleanType CompareImagesCommand(ImageInfo *image_info,
176  int argc,char **argv,char **metadata,ExceptionInfo *exception)
177{
178#define CompareEpsilon  (1.0e-06)
179#define DefaultDissimilarityThreshold  0.31830988618379067154
180#define DefaultSimilarityThreshold  (-1.0)
181#define DestroyCompare() \
182{ \
183  if (similarity_image != (Image *) NULL) \
184    similarity_image=DestroyImageList(similarity_image); \
185  if (difference_image != (Image *) NULL) \
186    difference_image=DestroyImageList(difference_image); \
187  DestroyImageStack(); \
188  for (i=0; i < (ssize_t) argc; i++) \
189    argv[i]=DestroyString(argv[i]); \
190  argv=(char **) RelinquishMagickMemory(argv); \
191}
192#define ThrowCompareException(asperity,tag,option) \
193{ \
194  if (exception->severity < (asperity)) \
195    (void) ThrowMagickException(exception,GetMagickModule(),asperity,tag, \
196      "`%s'",option); \
197  DestroyCompare(); \
198  return(MagickFalse); \
199}
200#define ThrowCompareInvalidArgumentException(option,argument) \
201{ \
202  (void) ThrowMagickException(exception,GetMagickModule(),OptionError, \
203    "InvalidArgument","'%s': %s",option,argument); \
204  DestroyCompare(); \
205  return(MagickFalse); \
206}
207
208  char
209    *filename,
210    *option;
211
212  const char
213    *format;
214
215  double
216    dissimilarity_threshold,
217    distortion,
218    similarity_metric,
219    similarity_threshold;
220
221  Image
222    *difference_image,
223    *image,
224    *reconstruct_image,
225    *similarity_image;
226
227  ImageStack
228    image_stack[MaxImageStackDepth+1];
229
230  MagickBooleanType
231    fire,
232    pend,
233    respect_parenthesis,
234    subimage_search;
235
236  MagickStatusType
237    status;
238
239  MetricType
240    metric;
241
242  RectangleInfo
243    offset;
244
245  register ssize_t
246    i;
247
248  ssize_t
249    j,
250    k;
251
252  /*
253    Set defaults.
254  */
255  assert(image_info != (ImageInfo *) NULL);
256  assert(image_info->signature == MagickCoreSignature);
257  if (image_info->debug != MagickFalse)
258    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
259  assert(exception != (ExceptionInfo *) NULL);
260  if (argc == 2)
261    {
262      option=argv[1];
263      if ((LocaleCompare("version",option+1) == 0) ||
264          (LocaleCompare("-version",option+1) == 0))
265        {
266          ListMagickVersion(stdout);
267          return(MagickTrue);
268        }
269    }
270  if (argc < 3)
271    return(CompareUsage());
272  difference_image=NewImageList();
273  similarity_image=NewImageList();
274  dissimilarity_threshold=DefaultDissimilarityThreshold;
275  similarity_threshold=DefaultSimilarityThreshold;
276  distortion=0.0;
277  format=(char *) NULL;
278  j=1;
279  k=0;
280  metric=UndefinedErrorMetric;
281  NewImageStack();
282  option=(char *) NULL;
283  pend=MagickFalse;
284  reconstruct_image=NewImageList();
285  respect_parenthesis=MagickFalse;
286  status=MagickTrue;
287  subimage_search=MagickFalse;
288  /*
289    Compare an image.
290  */
291  ReadCommandlLine(argc,&argv);
292  status=ExpandFilenames(&argc,&argv);
293  if (status == MagickFalse)
294    ThrowCompareException(ResourceLimitError,"MemoryAllocationFailed",
295      GetExceptionMessage(errno));
296  for (i=1; i < (ssize_t) (argc-1); i++)
297  {
298    option=argv[i];
299    if (LocaleCompare(option,"(") == 0)
300      {
301        FireImageStack(MagickTrue,MagickTrue,pend);
302        if (k == MaxImageStackDepth)
303          ThrowCompareException(OptionError,"ParenthesisNestedTooDeeply",
304            option);
305        PushImageStack();
306        continue;
307      }
308    if (LocaleCompare(option,")") == 0)
309      {
310        FireImageStack(MagickTrue,MagickTrue,MagickTrue);
311        if (k == 0)
312          ThrowCompareException(OptionError,"UnableToParseExpression",option);
313        PopImageStack();
314        continue;
315      }
316    if (IsCommandOption(option) == MagickFalse)
317      {
318        Image
319          *images;
320
321        /*
322          Read input image.
323        */
324        FireImageStack(MagickFalse,MagickFalse,pend);
325        filename=argv[i];
326        if ((LocaleCompare(filename,"--") == 0) && (i < (ssize_t) (argc-1)))
327          filename=argv[++i];
328        images=ReadImages(image_info,filename,exception);
329        status&=(images != (Image *) NULL) &&
330          (exception->severity < ErrorException);
331        if (images == (Image *) NULL)
332          continue;
333        AppendImageStack(images);
334        continue;
335      }
336    pend=image != (Image *) NULL ? MagickTrue : MagickFalse;
337    switch (*(option+1))
338    {
339      case 'a':
340      {
341        if (LocaleCompare("alpha",option+1) == 0)
342          {
343            ssize_t
344              type;
345
346            if (*option == '+')
347              break;
348            i++;
349            if (i == (ssize_t) argc)
350              ThrowCompareException(OptionError,"MissingArgument",option);
351            type=ParseCommandOption(MagickAlphaChannelOptions,MagickFalse,argv[i]);
352            if (type < 0)
353              ThrowCompareException(OptionError,"UnrecognizedAlphaChannelOption",
354                argv[i]);
355            break;
356          }
357        if (LocaleCompare("authenticate",option+1) == 0)
358          {
359            if (*option == '+')
360              break;
361            i++;
362            if (i == (ssize_t) argc)
363              ThrowCompareException(OptionError,"MissingArgument",option);
364            break;
365          }
366        ThrowCompareException(OptionError,"UnrecognizedOption",option);
367      }
368      case 'c':
369      {
370        if (LocaleCompare("cache",option+1) == 0)
371          {
372            if (*option == '+')
373              break;
374            i++;
375            if (i == (ssize_t) argc)
376              ThrowCompareException(OptionError,"MissingArgument",option);
377            if (IsGeometry(argv[i]) == MagickFalse)
378              ThrowCompareInvalidArgumentException(option,argv[i]);
379            break;
380          }
381        if (LocaleCompare("channel",option+1) == 0)
382          {
383            ssize_t
384              channel;
385
386            if (*option == '+')
387              break;
388            i++;
389            if (i == (ssize_t) argc)
390              ThrowCompareException(OptionError,"MissingArgument",option);
391            channel=ParseChannelOption(argv[i]);
392            if (channel < 0)
393              ThrowCompareException(OptionError,"UnrecognizedChannelType",
394                argv[i]);
395            (void) SetPixelChannelMask(image,(ChannelType) channel);
396            break;
397          }
398        if (LocaleCompare("colorspace",option+1) == 0)
399          {
400            ssize_t
401              colorspace;
402
403            if (*option == '+')
404              break;
405            i++;
406            if (i == (ssize_t) argc)
407              ThrowCompareException(OptionError,"MissingArgument",option);
408            colorspace=ParseCommandOption(MagickColorspaceOptions,MagickFalse,
409              argv[i]);
410            if (colorspace < 0)
411              ThrowCompareException(OptionError,"UnrecognizedColorspace",
412                argv[i]);
413            break;
414          }
415        if (LocaleCompare("compose",option+1) == 0)
416          {
417            ssize_t
418              compose;
419
420            if (*option == '+')
421              break;
422            i++;
423            if (i == (ssize_t) argc)
424              ThrowCompareException(OptionError,"MissingArgument",option);
425            compose=ParseCommandOption(MagickComposeOptions,MagickFalse,
426              argv[i]);
427            if (compose < 0)
428              ThrowCompareException(OptionError,"UnrecognizedComposeOperator",
429                argv[i]);
430            break;
431          }
432        if (LocaleCompare("compress",option+1) == 0)
433          {
434            ssize_t
435              compress;
436
437            if (*option == '+')
438              break;
439            i++;
440            if (i == (ssize_t) argc)
441              ThrowCompareException(OptionError,"MissingArgument",option);
442            compress=ParseCommandOption(MagickCompressOptions,MagickFalse,
443              argv[i]);
444            if (compress < 0)
445              ThrowCompareException(OptionError,"UnrecognizedImageCompression",
446                argv[i]);
447            break;
448          }
449        if (LocaleCompare("concurrent",option+1) == 0)
450          break;
451        ThrowCompareException(OptionError,"UnrecognizedOption",option)
452      }
453      case 'd':
454      {
455        if (LocaleCompare("debug",option+1) == 0)
456          {
457            LogEventType
458              event_mask;
459
460            if (*option == '+')
461              break;
462            i++;
463            if (i == (ssize_t) argc)
464              ThrowCompareException(OptionError,"MissingArgument",option);
465            event_mask=SetLogEventMask(argv[i]);
466            if (event_mask == UndefinedEvents)
467              ThrowCompareException(OptionError,"UnrecognizedEventType",
468                argv[i]);
469            break;
470          }
471        if (LocaleCompare("decipher",option+1) == 0)
472          {
473            if (*option == '+')
474              break;
475            i++;
476            if (i == (ssize_t) argc)
477              ThrowCompareException(OptionError,"MissingArgument",option);
478            break;
479          }
480        if (LocaleCompare("define",option+1) == 0)
481          {
482            i++;
483            if (i == (ssize_t) argc)
484              ThrowCompareException(OptionError,"MissingArgument",option);
485            if (*option == '+')
486              {
487                const char
488                  *define;
489
490                define=GetImageOption(image_info,argv[i]);
491                if (define == (const char *) NULL)
492                  ThrowCompareException(OptionError,"NoSuchOption",argv[i]);
493                break;
494              }
495            break;
496          }
497        if (LocaleCompare("density",option+1) == 0)
498          {
499            if (*option == '+')
500              break;
501            i++;
502            if (i == (ssize_t) argc)
503              ThrowCompareException(OptionError,"MissingArgument",option);
504            if (IsGeometry(argv[i]) == MagickFalse)
505              ThrowCompareInvalidArgumentException(option,argv[i]);
506            break;
507          }
508        if (LocaleCompare("depth",option+1) == 0)
509          {
510            if (*option == '+')
511              break;
512            i++;
513            if (i == (ssize_t) argc)
514              ThrowCompareException(OptionError,"MissingArgument",option);
515            if (IsGeometry(argv[i]) == MagickFalse)
516              ThrowCompareInvalidArgumentException(option,argv[i]);
517            break;
518          }
519        if (LocaleCompare("dissimilarity-threshold",option+1) == 0)
520          {
521            if (*option == '+')
522              break;
523            i++;
524            if (i == (ssize_t) argc)
525              ThrowCompareException(OptionError,"MissingArgument",option);
526            if (IsGeometry(argv[i]) == MagickFalse)
527              ThrowCompareInvalidArgumentException(option,argv[i]);
528            if (*option == '+')
529              dissimilarity_threshold=DefaultDissimilarityThreshold;
530            else
531              dissimilarity_threshold=StringToDouble(argv[i],(char **) NULL);
532            break;
533          }
534        if (LocaleCompare("duration",option+1) == 0)
535          {
536            if (*option == '+')
537              break;
538            i++;
539            if (i == (ssize_t) argc)
540              ThrowCompareException(OptionError,"MissingArgument",option);
541            if (IsGeometry(argv[i]) == MagickFalse)
542              ThrowCompareInvalidArgumentException(option,argv[i]);
543            break;
544          }
545        ThrowCompareException(OptionError,"UnrecognizedOption",option)
546      }
547      case 'e':
548      {
549        if (LocaleCompare("encipher",option+1) == 0)
550          {
551            if (*option == '+')
552              break;
553            i++;
554            if (i == (ssize_t) argc)
555              ThrowCompareException(OptionError,"MissingArgument",option);
556            break;
557          }
558        if (LocaleCompare("extract",option+1) == 0)
559          {
560            if (*option == '+')
561              break;
562            i++;
563            if (i == (ssize_t) argc)
564              ThrowCompareException(OptionError,"MissingArgument",option);
565            if (IsGeometry(argv[i]) == MagickFalse)
566              ThrowCompareInvalidArgumentException(option,argv[i]);
567            break;
568          }
569        ThrowCompareException(OptionError,"UnrecognizedOption",option)
570      }
571      case 'f':
572      {
573        if (LocaleCompare("format",option+1) == 0)
574          {
575            if (*option == '+')
576              break;
577            i++;
578            if (i == (ssize_t) argc)
579              ThrowCompareException(OptionError,"MissingArgument",option);
580            format=argv[i];
581            break;
582          }
583        if (LocaleCompare("fuzz",option+1) == 0)
584          {
585            if (*option == '+')
586              break;
587            i++;
588            if (i == (ssize_t) argc)
589              ThrowCompareException(OptionError,"MissingArgument",option);
590            if (IsGeometry(argv[i]) == MagickFalse)
591              ThrowCompareInvalidArgumentException(option,argv[i]);
592            break;
593          }
594        ThrowCompareException(OptionError,"UnrecognizedOption",option)
595      }
596      case 'h':
597      {
598        if ((LocaleCompare("help",option+1) == 0) ||
599            (LocaleCompare("-help",option+1) == 0))
600          return(CompareUsage());
601        if (LocaleCompare("highlight-color",option+1) == 0)
602          {
603            if (*option == '+')
604              break;
605            i++;
606            if (i == (ssize_t) argc)
607              ThrowCompareException(OptionError,"MissingArgument",option);
608            break;
609          }
610        ThrowCompareException(OptionError,"UnrecognizedOption",option)
611      }
612      case 'i':
613      {
614        if (LocaleCompare("identify",option+1) == 0)
615          break;
616        if (LocaleCompare("interlace",option+1) == 0)
617          {
618            ssize_t
619              interlace;
620
621            if (*option == '+')
622              break;
623            i++;
624            if (i == (ssize_t) argc)
625              ThrowCompareException(OptionError,"MissingArgument",option);
626            interlace=ParseCommandOption(MagickInterlaceOptions,MagickFalse,
627              argv[i]);
628            if (interlace < 0)
629              ThrowCompareException(OptionError,"UnrecognizedInterlaceType",
630                argv[i]);
631            break;
632          }
633        ThrowCompareException(OptionError,"UnrecognizedOption",option)
634      }
635      case 'l':
636      {
637        if (LocaleCompare("limit",option+1) == 0)
638          {
639            char
640              *p;
641
642            double
643              value;
644
645            ssize_t
646              resource;
647
648            if (*option == '+')
649              break;
650            i++;
651            if (i == (ssize_t) argc)
652              ThrowCompareException(OptionError,"MissingArgument",option);
653            resource=ParseCommandOption(MagickResourceOptions,MagickFalse,
654              argv[i]);
655            if (resource < 0)
656              ThrowCompareException(OptionError,"UnrecognizedResourceType",
657                argv[i]);
658            i++;
659            if (i == (ssize_t) argc)
660              ThrowCompareException(OptionError,"MissingArgument",option);
661            value=StringToDouble(argv[i],&p);
662            (void) value;
663            if ((p == argv[i]) && (LocaleCompare("unlimited",argv[i]) != 0))
664              ThrowCompareInvalidArgumentException(option,argv[i]);
665            break;
666          }
667        if (LocaleCompare("list",option+1) == 0)
668          {
669            ssize_t
670              list;
671
672            if (*option == '+')
673              break;
674            i++;
675            if (i == (ssize_t) argc)
676              ThrowCompareException(OptionError,"MissingArgument",option);
677            list=ParseCommandOption(MagickListOptions,MagickFalse,argv[i]);
678            if (list < 0)
679              ThrowCompareException(OptionError,"UnrecognizedListType",argv[i]);
680            status=MogrifyImageInfo(image_info,(int) (i-j+1),(const char **)
681              argv+j,exception);
682            DestroyCompare();
683            return(status == 0 ? MagickTrue : MagickFalse);
684          }
685        if (LocaleCompare("log",option+1) == 0)
686          {
687            if (*option == '+')
688              break;
689            i++;
690            if ((i == (ssize_t) argc) || (strchr(argv[i],'%') == (char *) NULL))
691              ThrowCompareException(OptionError,"MissingArgument",option);
692            break;
693          }
694        if (LocaleCompare("lowlight-color",option+1) == 0)
695          {
696            if (*option == '+')
697              break;
698            i++;
699            if (i == (ssize_t) argc)
700              ThrowCompareException(OptionError,"MissingArgument",option);
701            break;
702          }
703        ThrowCompareException(OptionError,"UnrecognizedOption",option)
704      }
705      case 'm':
706      {
707        if (LocaleCompare("matte",option+1) == 0)
708          break;
709        if (LocaleCompare("metric",option+1) == 0)
710          {
711            ssize_t
712              type;
713
714            if (*option == '+')
715              break;
716            i++;
717            if (i == (ssize_t) argc)
718              ThrowCompareException(OptionError,"MissingArgument",option);
719            type=ParseCommandOption(MagickMetricOptions,MagickTrue,argv[i]);
720            if (type < 0)
721              ThrowCompareException(OptionError,"UnrecognizedMetricType",
722                argv[i]);
723            metric=(MetricType) type;
724            break;
725          }
726        if (LocaleCompare("monitor",option+1) == 0)
727          break;
728        ThrowCompareException(OptionError,"UnrecognizedOption",option)
729      }
730      case 'p':
731      {
732        if (LocaleCompare("profile",option+1) == 0)
733          {
734            i++;
735            if (i == (ssize_t) argc)
736              ThrowCompareException(OptionError,"MissingArgument",option);
737            break;
738          }
739        ThrowCompareException(OptionError,"UnrecognizedOption",option)
740      }
741      case 'q':
742      {
743        if (LocaleCompare("quality",option+1) == 0)
744          {
745            if (*option == '+')
746              break;
747            i++;
748            if (i == (ssize_t) argc)
749              ThrowCompareException(OptionError,"MissingArgument",option);
750            if (IsGeometry(argv[i]) == MagickFalse)
751              ThrowCompareInvalidArgumentException(option,argv[i]);
752            break;
753          }
754        if (LocaleCompare("quantize",option+1) == 0)
755          {
756            ssize_t
757              colorspace;
758
759            if (*option == '+')
760              break;
761            i++;
762            if (i == (ssize_t) argc)
763              ThrowCompareException(OptionError,"MissingArgument",option);
764            colorspace=ParseCommandOption(MagickColorspaceOptions,
765              MagickFalse,argv[i]);
766            if (colorspace < 0)
767              ThrowCompareException(OptionError,"UnrecognizedColorspace",
768                argv[i]);
769            break;
770          }
771        if (LocaleCompare("quiet",option+1) == 0)
772          break;
773        ThrowCompareException(OptionError,"UnrecognizedOption",option)
774      }
775      case 'r':
776      {
777        if (LocaleCompare("regard-warnings",option+1) == 0)
778          break;
779        if (LocaleNCompare("respect-parentheses",option+1,17) == 0)
780          {
781            respect_parenthesis=(*option == '-') ? MagickTrue : MagickFalse;
782            break;
783          }
784        ThrowCompareException(OptionError,"UnrecognizedOption",option)
785      }
786      case 's':
787      {
788        if (LocaleCompare("sampling-factor",option+1) == 0)
789          {
790            if (*option == '+')
791              break;
792            i++;
793            if (i == (ssize_t) argc)
794              ThrowCompareException(OptionError,"MissingArgument",option);
795            if (IsGeometry(argv[i]) == MagickFalse)
796              ThrowCompareInvalidArgumentException(option,argv[i]);
797            break;
798          }
799        if (LocaleCompare("seed",option+1) == 0)
800          {
801            if (*option == '+')
802              break;
803            i++;
804            if (i == (ssize_t) argc)
805              ThrowCompareException(OptionError,"MissingArgument",option);
806            if (IsGeometry(argv[i]) == MagickFalse)
807              ThrowCompareInvalidArgumentException(option,argv[i]);
808            break;
809          }
810        if (LocaleCompare("set",option+1) == 0)
811          {
812            i++;
813            if (i == (ssize_t) argc)
814              ThrowCompareException(OptionError,"MissingArgument",option);
815            if (*option == '+')
816              break;
817            i++;
818            if (i == (ssize_t) argc)
819              ThrowCompareException(OptionError,"MissingArgument",option);
820            break;
821          }
822        if (LocaleCompare("similarity-threshold",option+1) == 0)
823          {
824            if (*option == '+')
825              break;
826            i++;
827            if (i == (ssize_t) argc)
828              ThrowCompareException(OptionError,"MissingArgument",option);
829            if (IsGeometry(argv[i]) == MagickFalse)
830              ThrowCompareInvalidArgumentException(option,argv[i]);
831            if (*option == '+')
832              similarity_threshold=DefaultSimilarityThreshold;
833            else
834              similarity_threshold=StringToDouble(argv[i],(char **) NULL);
835            break;
836          }
837        if (LocaleCompare("size",option+1) == 0)
838          {
839            if (*option == '+')
840              break;
841            i++;
842            if (i == (ssize_t) argc)
843              ThrowCompareException(OptionError,"MissingArgument",option);
844            if (IsGeometry(argv[i]) == MagickFalse)
845              ThrowCompareInvalidArgumentException(option,argv[i]);
846            break;
847          }
848        if (LocaleCompare("subimage-search",option+1) == 0)
849          {
850            if (*option == '+')
851              {
852                subimage_search=MagickFalse;
853                break;
854              }
855            subimage_search=MagickTrue;
856            break;
857          }
858        if (LocaleCompare("synchronize",option+1) == 0)
859          break;
860        ThrowCompareException(OptionError,"UnrecognizedOption",option)
861      }
862      case 't':
863      {
864        if (LocaleCompare("taint",option+1) == 0)
865          break;
866        if (LocaleCompare("transparent-color",option+1) == 0)
867          {
868            if (*option == '+')
869              break;
870            i++;
871            if (i == (ssize_t) argc)
872              ThrowCompareException(OptionError,"MissingArgument",option);
873            break;
874          }
875        if (LocaleCompare("type",option+1) == 0)
876          {
877            ssize_t
878              type;
879
880            if (*option == '+')
881              break;
882            i++;
883            if (i == (ssize_t) argc)
884              ThrowCompareException(OptionError,"MissingArgument",option);
885            type=ParseCommandOption(MagickTypeOptions,MagickFalse,argv[i]);
886            if (type < 0)
887              ThrowCompareException(OptionError,"UnrecognizedImageType",
888                argv[i]);
889            break;
890          }
891        ThrowCompareException(OptionError,"UnrecognizedOption",option)
892      }
893      case 'v':
894      {
895        if (LocaleCompare("verbose",option+1) == 0)
896          break;
897        if ((LocaleCompare("version",option+1) == 0) ||
898            (LocaleCompare("-version",option+1) == 0))
899          {
900            ListMagickVersion(stdout);
901            break;
902          }
903        if (LocaleCompare("virtual-pixel",option+1) == 0)
904          {
905            ssize_t
906              method;
907
908            if (*option == '+')
909              break;
910            i++;
911            if (i == (ssize_t) argc)
912              ThrowCompareException(OptionError,"MissingArgument",option);
913            method=ParseCommandOption(MagickVirtualPixelOptions,MagickFalse,
914              argv[i]);
915            if (method < 0)
916              ThrowCompareException(OptionError,
917                "UnrecognizedVirtualPixelMethod",argv[i]);
918            break;
919          }
920        ThrowCompareException(OptionError,"UnrecognizedOption",option)
921      }
922      case '?':
923        break;
924      default:
925        ThrowCompareException(OptionError,"UnrecognizedOption",option)
926    }
927    fire=(GetCommandOptionFlags(MagickCommandOptions,MagickFalse,option) &
928      FireOptionFlag) == 0 ?  MagickFalse : MagickTrue;
929    if (fire != MagickFalse)
930      FireImageStack(MagickTrue,MagickTrue,MagickTrue);
931  }
932  if (k != 0)
933    ThrowCompareException(OptionError,"UnbalancedParenthesis",argv[i]);
934  if (i-- != (ssize_t) (argc-1))
935    ThrowCompareException(OptionError,"MissingAnImageFilename",argv[i]);
936  if ((image == (Image *) NULL) || (GetImageListLength(image) < 2))
937    ThrowCompareException(OptionError,"MissingAnImageFilename",argv[i]);
938  FinalizeImageSettings(image_info,image,MagickTrue);
939  if ((image == (Image *) NULL) || (GetImageListLength(image) < 2))
940    ThrowCompareException(OptionError,"MissingAnImageFilename",argv[i]);
941  image=GetImageFromList(image,0);
942  reconstruct_image=GetImageFromList(image,1);
943  offset.x=0;
944  offset.y=0;
945  if (subimage_search != MagickFalse)
946    {
947      similarity_image=SimilarityImage(image,reconstruct_image,metric,
948        similarity_threshold,&offset,&similarity_metric,exception);
949      if (similarity_metric > dissimilarity_threshold)
950        ThrowCompareException(ImageError,"ImagesTooDissimilar",image->filename);
951    }
952  if ((reconstruct_image->columns == image->columns) &&
953       (reconstruct_image->rows == image->rows))
954    difference_image=CompareImages(image,reconstruct_image,metric,&distortion,
955      exception);
956  else
957    if (similarity_image == (Image *) NULL)
958      {
959        if (metric == PerceptualHashErrorMetric)
960          difference_image=CompareImages(image,reconstruct_image,metric,
961            &distortion,exception);
962        else
963          ThrowCompareException(OptionError,"ImageWidthsOrHeightsDiffer",
964            image->filename);
965      }
966    else
967      {
968        Image
969          *composite_image;
970
971        /*
972          Determine if reconstructed image is a subimage of the image.
973        */
974        composite_image=CloneImage(image,0,0,MagickTrue,exception);
975        if (composite_image == (Image *) NULL)
976          difference_image=CompareImages(image,reconstruct_image,metric,
977            &distortion,exception);
978        else
979          {
980            Image
981              *distort_image;
982
983            RectangleInfo
984              page;
985
986            (void) CompositeImage(composite_image,reconstruct_image,
987              CopyCompositeOp,MagickTrue,offset.x,offset.y,exception);
988            difference_image=CompareImages(image,composite_image,metric,
989              &distortion,exception);
990            if (difference_image != (Image *) NULL)
991              {
992                difference_image->page.x=offset.x;
993                difference_image->page.y=offset.y;
994              }
995            composite_image=DestroyImage(composite_image);
996            page.width=reconstruct_image->columns;
997            page.height=reconstruct_image->rows;
998            page.x=offset.x;
999            page.y=offset.y;
1000            distort_image=CropImage(image,&page,exception);
1001            if (distort_image != (Image *) NULL)
1002              {
1003                Image
1004                  *sans_image;
1005
1006                sans_image=CompareImages(distort_image,reconstruct_image,metric,
1007                  &distortion,exception);
1008                distort_image=DestroyImage(distort_image);
1009                if (sans_image != (Image *) NULL)
1010                  sans_image=DestroyImage(sans_image);
1011              }
1012          }
1013        if (difference_image != (Image *) NULL)
1014          {
1015            AppendImageToList(&difference_image,similarity_image);
1016            similarity_image=(Image *) NULL;
1017          }
1018      }
1019  if (difference_image == (Image *) NULL)
1020    status=0;
1021  else
1022    {
1023      if (image_info->verbose != MagickFalse)
1024        (void) SetImageColorMetric(image,reconstruct_image,exception);
1025      if (*difference_image->magick == '\0')
1026        (void) CopyMagickString(difference_image->magick,image->magick,
1027          MagickPathExtent);
1028      if (image_info->verbose == MagickFalse)
1029        {
1030          switch (metric)
1031          {
1032            case FuzzErrorMetric:
1033            case MeanAbsoluteErrorMetric:
1034            case MeanSquaredErrorMetric:
1035            case PeakAbsoluteErrorMetric:
1036            case RootMeanSquaredErrorMetric:
1037            {
1038              (void) FormatLocaleFile(stderr,"%g (%g)",QuantumRange*distortion,
1039                (double) distortion);
1040              break;
1041            }
1042            case AbsoluteErrorMetric:
1043            case NormalizedCrossCorrelationErrorMetric:
1044            case PeakSignalToNoiseRatioErrorMetric:
1045            case PerceptualHashErrorMetric:
1046            {
1047              (void) FormatLocaleFile(stderr,"%g",distortion);
1048              break;
1049            }
1050            case MeanErrorPerPixelErrorMetric:
1051            {
1052              (void) FormatLocaleFile(stderr,"%g (%g, %g)",distortion,
1053                image->error.normalized_mean_error,
1054                image->error.normalized_maximum_error);
1055              break;
1056            }
1057            case UndefinedErrorMetric:
1058              break;
1059          }
1060          if (subimage_search != MagickFalse)
1061            (void) FormatLocaleFile(stderr," @ %.20g,%.20g",(double)
1062              difference_image->page.x,(double) difference_image->page.y);
1063        }
1064      else
1065        {
1066          double
1067            *channel_distortion;
1068
1069          channel_distortion=GetImageDistortions(image,reconstruct_image,
1070            metric,exception);
1071          (void) FormatLocaleFile(stderr,"Image: %s\n",image->filename);
1072          if ((reconstruct_image->columns != image->columns) ||
1073              (reconstruct_image->rows != image->rows))
1074            (void) FormatLocaleFile(stderr,"Offset: %.20g,%.20g\n",(double)
1075              difference_image->page.x,(double) difference_image->page.y);
1076          (void) FormatLocaleFile(stderr,"  Channel distortion: %s\n",
1077            CommandOptionToMnemonic(MagickMetricOptions,(ssize_t) metric));
1078          switch (metric)
1079          {
1080            case FuzzErrorMetric:
1081            case MeanAbsoluteErrorMetric:
1082            case MeanSquaredErrorMetric:
1083            case PeakAbsoluteErrorMetric:
1084            case RootMeanSquaredErrorMetric:
1085            {
1086              switch (image->colorspace)
1087              {
1088                case RGBColorspace:
1089                default:
1090                {
1091                  (void) FormatLocaleFile(stderr,"    red: %g (%g)\n",
1092                    QuantumRange*channel_distortion[RedPixelChannel],
1093                    channel_distortion[RedPixelChannel]);
1094                  (void) FormatLocaleFile(stderr,"    green: %g (%g)\n",
1095                    QuantumRange*channel_distortion[GreenPixelChannel],
1096                    channel_distortion[GreenPixelChannel]);
1097                  (void) FormatLocaleFile(stderr,"    blue: %g (%g)\n",
1098                    QuantumRange*channel_distortion[BluePixelChannel],
1099                    channel_distortion[BluePixelChannel]);
1100                  if (image->alpha_trait != UndefinedPixelTrait)
1101                    (void) FormatLocaleFile(stderr,"    alpha: %g (%g)\n",
1102                      QuantumRange*channel_distortion[AlphaPixelChannel],
1103                      channel_distortion[AlphaPixelChannel]);
1104                  break;
1105                }
1106                case CMYKColorspace:
1107                {
1108                  (void) FormatLocaleFile(stderr,"    cyan: %g (%g)\n",
1109                    QuantumRange*channel_distortion[CyanPixelChannel],
1110                    channel_distortion[CyanPixelChannel]);
1111                  (void) FormatLocaleFile(stderr,"    magenta: %g (%g)\n",
1112                    QuantumRange*channel_distortion[MagentaPixelChannel],
1113                    channel_distortion[MagentaPixelChannel]);
1114                  (void) FormatLocaleFile(stderr,"    yellow: %g (%g)\n",
1115                    QuantumRange*channel_distortion[YellowPixelChannel],
1116                    channel_distortion[YellowPixelChannel]);
1117                  (void) FormatLocaleFile(stderr,"    black: %g (%g)\n",
1118                    QuantumRange*channel_distortion[BlackPixelChannel],
1119                    channel_distortion[BlackPixelChannel]);
1120                  if (image->alpha_trait != UndefinedPixelTrait)
1121                    (void) FormatLocaleFile(stderr,"    alpha: %g (%g)\n",
1122                      QuantumRange*channel_distortion[AlphaPixelChannel],
1123                      channel_distortion[AlphaPixelChannel]);
1124                  break;
1125                }
1126                case GRAYColorspace:
1127                {
1128                  (void) FormatLocaleFile(stderr,"    gray: %g (%g)\n",
1129                    QuantumRange*channel_distortion[GrayPixelChannel],
1130                    channel_distortion[GrayPixelChannel]);
1131                  if (image->alpha_trait != UndefinedPixelTrait)
1132                    (void) FormatLocaleFile(stderr,"    alpha: %g (%g)\n",
1133                      QuantumRange*channel_distortion[AlphaPixelChannel],
1134                      channel_distortion[AlphaPixelChannel]);
1135                  break;
1136                }
1137              }
1138              (void) FormatLocaleFile(stderr,"    all: %g (%g)\n",
1139                QuantumRange*channel_distortion[MaxPixelChannels],
1140                channel_distortion[MaxPixelChannels]);
1141              break;
1142            }
1143            case AbsoluteErrorMetric:
1144            case NormalizedCrossCorrelationErrorMetric:
1145            case PeakSignalToNoiseRatioErrorMetric:
1146            case PerceptualHashErrorMetric:
1147            {
1148              switch (image->colorspace)
1149              {
1150                case RGBColorspace:
1151                default:
1152                {
1153                  (void) FormatLocaleFile(stderr,"    red: %g\n",
1154                    channel_distortion[RedPixelChannel]);
1155                  (void) FormatLocaleFile(stderr,"    green: %g\n",
1156                    channel_distortion[GreenPixelChannel]);
1157                  (void) FormatLocaleFile(stderr,"    blue: %g\n",
1158                    channel_distortion[BluePixelChannel]);
1159                  if (image->alpha_trait != UndefinedPixelTrait)
1160                    (void) FormatLocaleFile(stderr,"    alpha: %g\n",
1161                      channel_distortion[AlphaPixelChannel]);
1162                  break;
1163                }
1164                case CMYKColorspace:
1165                {
1166                  (void) FormatLocaleFile(stderr,"    cyan: %g\n",
1167                    channel_distortion[CyanPixelChannel]);
1168                  (void) FormatLocaleFile(stderr,"    magenta: %g\n",
1169                    channel_distortion[MagentaPixelChannel]);
1170                  (void) FormatLocaleFile(stderr,"    yellow: %g\n",
1171                    channel_distortion[YellowPixelChannel]);
1172                  (void) FormatLocaleFile(stderr,"    black: %g\n",
1173                    channel_distortion[BlackPixelChannel]);
1174                  if (image->alpha_trait != UndefinedPixelTrait)
1175                    (void) FormatLocaleFile(stderr,"    alpha: %g\n",
1176                      channel_distortion[AlphaPixelChannel]);
1177                  break;
1178                }
1179                case GRAYColorspace:
1180                {
1181                  (void) FormatLocaleFile(stderr,"    gray: %g\n",
1182                    channel_distortion[GrayPixelChannel]);
1183                  if (image->alpha_trait != UndefinedPixelTrait)
1184                    (void) FormatLocaleFile(stderr,"    alpha: %g\n",
1185                      channel_distortion[AlphaPixelChannel]);
1186                  break;
1187                }
1188              }
1189              (void) FormatLocaleFile(stderr,"    all: %g\n",
1190                channel_distortion[MaxPixelChannels]);
1191              break;
1192            }
1193            case MeanErrorPerPixelErrorMetric:
1194            {
1195              (void) FormatLocaleFile(stderr,"    %g (%g, %g)\n",
1196                channel_distortion[MaxPixelChannels],
1197                image->error.normalized_mean_error,
1198                image->error.normalized_maximum_error);
1199              break;
1200            }
1201            case UndefinedErrorMetric:
1202              break;
1203          }
1204          channel_distortion=(double *) RelinquishMagickMemory(
1205            channel_distortion);
1206          if (subimage_search != MagickFalse)
1207            (void) FormatLocaleFile(stderr,"   Offset: %.20g,%.20g\n",(double)
1208              difference_image->page.x,(double) difference_image->page.y);
1209        }
1210      status&=WriteImages(image_info,difference_image,argv[argc-1],exception);
1211      if ((metadata != (char **) NULL) && (format != (char *) NULL))
1212        {
1213          char
1214            *text;
1215
1216          text=InterpretImageProperties(image_info,difference_image,format,
1217            exception);
1218          if (text == (char *) NULL)
1219            ThrowCompareException(ResourceLimitError,"MemoryAllocationFailed",
1220              GetExceptionMessage(errno));
1221          (void) ConcatenateString(&(*metadata),text);
1222          text=DestroyString(text);
1223        }
1224      difference_image=DestroyImageList(difference_image);
1225    }
1226  DestroyCompare();
1227  if ((metric == NormalizedCrossCorrelationErrorMetric) ||
1228      (metric == UndefinedErrorMetric))
1229    {
1230      if (fabs(distortion-1.0) > CompareEpsilon)
1231        (void) SetImageOption(image_info,"compare:dissimilar","true");
1232    }
1233  else
1234    if (fabs(distortion) > CompareEpsilon)
1235      (void) SetImageOption(image_info,"compare:dissimilar","true");
1236  return(status != 0 ? MagickTrue : MagickFalse);
1237}
1238