MagickCore  6.9.13-52
Convert, Edit, Or Compose Bitmap Images
compare.c
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 % MagickCore Image Comparison Methods %
14 % %
15 % Software Design %
16 % Cristy %
17 % December 2003 %
18 % %
19 % %
20 % Copyright 1999 ImageMagick Studio LLC, a non-profit organization dedicated %
21 % 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 % https://imagemagick.org/license/ %
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 %
37 %
38 */
39 
40 /*
41  Include declarations.
42 */
43 #include "magick/studio.h"
44 #include "magick/artifact.h"
45 #include "magick/attribute.h"
46 #include "magick/cache-view.h"
47 #include "magick/channel.h"
48 #include "magick/client.h"
49 #include "magick/color.h"
50 #include "magick/color-private.h"
51 #include "magick/colorspace.h"
52 #include "magick/colorspace-private.h"
53 #include "magick/compare.h"
54 #include "magick/compare-private.h"
55 #include "magick/composite-private.h"
56 #include "magick/constitute.h"
57 #include "magick/exception-private.h"
58 #include "magick/geometry.h"
59 #include "magick/image-private.h"
60 #include "magick/list.h"
61 #include "magick/log.h"
62 #include "magick/memory_.h"
63 #include "magick/monitor.h"
64 #include "magick/monitor-private.h"
65 #include "magick/option.h"
66 #include "magick/pixel-private.h"
67 #include "magick/property.h"
68 #include "magick/resource_.h"
69 #include "magick/statistic-private.h"
70 #include "magick/string_.h"
71 #include "magick/string-private.h"
72 #include "magick/statistic.h"
73 #include "magick/thread-private.h"
74 #include "magick/transform.h"
75 #include "magick/utility.h"
76 #include "magick/version.h"
77 
78 /*
79 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
80 % %
81 % %
82 % %
83 % C o m p a r e I m a g e C h a n n e l s %
84 % %
85 % %
86 % %
87 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
88 %
89 % CompareImageChannels() compares one or more image channels of an image
90 % to a reconstructed image and returns the difference image.
91 %
92 % The format of the CompareImageChannels method is:
93 %
94 % Image *CompareImageChannels(const Image *image,
95 % const Image *reconstruct_image,const ChannelType channel,
96 % const MetricType metric,double *distortion,ExceptionInfo *exception)
97 %
98 % A description of each parameter follows:
99 %
100 % o image: the image.
101 %
102 % o reconstruct_image: the reconstruct image.
103 %
104 % o channel: the channel.
105 %
106 % o metric: the metric.
107 %
108 % o distortion: the computed distortion between the images.
109 %
110 % o exception: return any errors or warnings in this structure.
111 %
112 */
113 
114 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
115  const MetricType metric,double *distortion,ExceptionInfo *exception)
116 {
117  Image
118  *highlight_image;
119 
120  highlight_image=CompareImageChannels(image,reconstruct_image,
121  CompositeChannels,metric,distortion,exception);
122  return(highlight_image);
123 }
124 
125 static inline MagickBooleanType ValidateImageMorphology(
126  const Image *magick_restrict image,
127  const Image *magick_restrict reconstruct_image)
128 {
129  /*
130  Does the image match the reconstructed image morphology?
131  */
132  if (GetNumberChannels(image,DefaultChannels) !=
133  GetNumberChannels(reconstruct_image,DefaultChannels))
134  return(MagickFalse);
135  return(MagickTrue);
136 }
137 
138 MagickExport Image *CompareImageChannels(Image *image,
139  const Image *reconstruct_image,const ChannelType channel,
140  const MetricType metric,double *distortion,ExceptionInfo *exception)
141 {
142  CacheView
143  *highlight_view,
144  *image_view,
145  *reconstruct_view;
146 
147  const char
148  *artifact;
149 
150  Image
151  *clone_image,
152  *difference_image,
153  *highlight_image;
154 
155  MagickBooleanType
156  status = MagickTrue;
157 
159  highlight,
160  lowlight,
161  zero;
162 
163  size_t
164  columns,
165  rows;
166 
167  ssize_t
168  y;
169 
170  assert(image != (Image *) NULL);
171  assert(image->signature == MagickCoreSignature);
172  assert(reconstruct_image != (const Image *) NULL);
173  assert(reconstruct_image->signature == MagickCoreSignature);
174  assert(distortion != (double *) NULL);
175  if (IsEventLogging() != MagickFalse)
176  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
177  *distortion=0.0;
178  if (metric != PerceptualHashErrorMetric)
179  if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
180  ThrowImageException(ImageError,"ImageMorphologyDiffers");
181  status=GetImageChannelDistortion(image,reconstruct_image,channel,metric,
182  distortion,exception);
183  if (status == MagickFalse)
184  return((Image *) NULL);
185  clone_image=CloneImage(image,0,0,MagickTrue,exception);
186  if (clone_image == (Image *) NULL)
187  return((Image *) NULL);
188  (void) SetImageMask(clone_image,(Image *) NULL);
189  difference_image=CloneImage(clone_image,0,0,MagickTrue,exception);
190  clone_image=DestroyImage(clone_image);
191  if (difference_image == (Image *) NULL)
192  return((Image *) NULL);
193  (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel);
194  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
195  highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
196  if (highlight_image == (Image *) NULL)
197  {
198  difference_image=DestroyImage(difference_image);
199  return((Image *) NULL);
200  }
201  if (SetImageStorageClass(highlight_image,DirectClass) == MagickFalse)
202  {
203  InheritException(exception,&highlight_image->exception);
204  difference_image=DestroyImage(difference_image);
205  highlight_image=DestroyImage(highlight_image);
206  return((Image *) NULL);
207  }
208  (void) SetImageMask(highlight_image,(Image *) NULL);
209  (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel);
210  (void) QueryMagickColor("#f1001ecc",&highlight,exception);
211  artifact=GetImageArtifact(image,"compare:highlight-color");
212  if (artifact != (const char *) NULL)
213  (void) QueryMagickColor(artifact,&highlight,exception);
214  (void) QueryMagickColor("#ffffffcc",&lowlight,exception);
215  artifact=GetImageArtifact(image,"compare:lowlight-color");
216  if (artifact != (const char *) NULL)
217  (void) QueryMagickColor(artifact,&lowlight,exception);
218  if (highlight_image->colorspace == CMYKColorspace)
219  {
220  ConvertRGBToCMYK(&highlight);
221  ConvertRGBToCMYK(&lowlight);
222  }
223  /*
224  Generate difference image.
225  */
226  GetMagickPixelPacket(image,&zero);
227  image_view=AcquireVirtualCacheView(image,exception);
228  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
229  highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
230 #if defined(MAGICKCORE_OPENMP_SUPPORT)
231  #pragma omp parallel for schedule(static) shared(status) \
232  magick_number_threads(image,highlight_image,rows,1)
233 #endif
234  for (y=0; y < (ssize_t) rows; y++)
235  {
236  MagickBooleanType
237  sync;
238 
240  pixel,
241  reconstruct_pixel;
242 
243  const IndexPacket
244  *magick_restrict indexes,
245  *magick_restrict reconstruct_indexes;
246 
247  const PixelPacket
248  *magick_restrict p,
249  *magick_restrict q;
250 
251  IndexPacket
252  *magick_restrict highlight_indexes;
253 
255  *magick_restrict r;
256 
257  ssize_t
258  x;
259 
260  if (status == MagickFalse)
261  continue;
262  p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
263  q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
264  r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
265  if ((p == (const PixelPacket *) NULL) ||
266  (q == (const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
267  {
268  status=MagickFalse;
269  continue;
270  }
271  indexes=GetCacheViewVirtualIndexQueue(image_view);
272  reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
273  highlight_indexes=GetCacheViewAuthenticIndexQueue(highlight_view);
274  pixel=zero;
275  reconstruct_pixel=zero;
276  for (x=0; x < (ssize_t) columns; x++)
277  {
278  SetMagickPixelPacket(image,p,indexes == (IndexPacket *) NULL ? NULL :
279  indexes+x,&pixel);
280  SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes ==
281  (IndexPacket *) NULL ? NULL : reconstruct_indexes+x,&reconstruct_pixel);
282  if (IsMagickColorSimilar(&pixel,&reconstruct_pixel) == MagickFalse)
283  SetPixelPacket(highlight_image,&highlight,r,highlight_indexes ==
284  (IndexPacket *) NULL ? NULL : highlight_indexes+x);
285  else
286  SetPixelPacket(highlight_image,&lowlight,r,highlight_indexes ==
287  (IndexPacket *) NULL ? NULL : highlight_indexes+x);
288  p++;
289  q++;
290  r++;
291  }
292  sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
293  if (sync == MagickFalse)
294  status=MagickFalse;
295  }
296  highlight_view=DestroyCacheView(highlight_view);
297  reconstruct_view=DestroyCacheView(reconstruct_view);
298  image_view=DestroyCacheView(image_view);
299  (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
300  highlight_image=DestroyImage(highlight_image);
301  if (status == MagickFalse)
302  difference_image=DestroyImage(difference_image);
303  return(difference_image);
304 }
305 
306 /*
307 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
308 % %
309 % %
310 % %
311 % G e t I m a g e C h a n n e l D i s t o r t i o n %
312 % %
313 % %
314 % %
315 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
316 %
317 % GetImageChannelDistortion() compares one or more image channels of an image
318 % to a reconstructed image and returns the specified distortion metric.
319 %
320 % The format of the GetImageChannelDistortion method is:
321 %
322 % MagickBooleanType GetImageChannelDistortion(const Image *image,
323 % const Image *reconstruct_image,const ChannelType channel,
324 % const MetricType metric,double *distortion,ExceptionInfo *exception)
325 %
326 % A description of each parameter follows:
327 %
328 % o image: the image.
329 %
330 % o reconstruct_image: the reconstruct image.
331 %
332 % o channel: the channel.
333 %
334 % o metric: the metric.
335 %
336 % o distortion: the computed distortion between the images.
337 %
338 % o exception: return any errors or warnings in this structure.
339 %
340 */
341 
342 MagickExport MagickBooleanType GetImageDistortion(Image *image,
343  const Image *reconstruct_image,const MetricType metric,double *distortion,
344  ExceptionInfo *exception)
345 {
346  MagickBooleanType
347  status;
348 
349  status=GetImageChannelDistortion(image,reconstruct_image,CompositeChannels,
350  metric,distortion,exception);
351  return(status);
352 }
353 
354 static MagickBooleanType GetAESimilarity(const Image *image,
355  const Image *reconstruct_image,const ChannelType channel,double *similarity,
356  ExceptionInfo *exception)
357 {
358  CacheView
359  *image_view,
360  *reconstruct_view;
361 
362  double
363  fuzz;
364 
365  MagickBooleanType
366  status = MagickTrue;
367 
368  size_t
369  columns,
370  rows;
371 
372  ssize_t
373  y;
374 
375  /*
376  Compute the absolute difference in pixels between two images.
377  */
378  fuzz=GetFuzzyColorDistance(image,reconstruct_image);
379  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
380  image_view=AcquireVirtualCacheView(image,exception);
381  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
382 #if defined(MAGICKCORE_OPENMP_SUPPORT)
383  #pragma omp parallel for schedule(static) shared(similarity,status) \
384  magick_number_threads(image,image,rows,1)
385 #endif
386  for (y=0; y < (ssize_t) rows; y++)
387  {
388  const IndexPacket
389  *magick_restrict indexes,
390  *magick_restrict reconstruct_indexes;
391 
392  const PixelPacket
393  *magick_restrict p,
394  *magick_restrict q;
395 
396  double
397  channel_similarity[CompositeChannels+1] = { 0.0 };
398 
399  ssize_t
400  i,
401  x;
402 
403  if (status == MagickFalse)
404  continue;
405  p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
406  q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
407  if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
408  {
409  status=MagickFalse;
410  continue;
411  }
412  indexes=GetCacheViewVirtualIndexQueue(image_view);
413  reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
414  (void) memset(channel_similarity,0,sizeof(channel_similarity));
415  for (x=0; x < (ssize_t) columns; x++)
416  {
417  double
418  Da,
419  error,
420  Sa;
421 
422  Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
423  ((double) QuantumRange-(double) OpaqueOpacity));
424  Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
425  ((double) QuantumRange-(double) OpaqueOpacity));
426  if ((channel & RedChannel) != 0)
427  {
428  error=Sa*(double) GetPixelRed(p)-Da*(double)
429  GetPixelRed(q);
430  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
431  {
432  channel_similarity[RedChannel]+=fabs(error);
433  channel_similarity[CompositeChannels]+=fabs(error);
434  }
435  }
436  if ((channel & GreenChannel) != 0)
437  {
438  error=Sa*(double) GetPixelGreen(p)-Da*(double)
439  GetPixelGreen(q);
440  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
441  {
442  channel_similarity[GreenChannel]+=fabs(error);
443  channel_similarity[CompositeChannels]+=fabs(error);
444  }
445  }
446  if ((channel & BlueChannel) != 0)
447  {
448  error=Sa*(double) GetPixelBlue(p)-Da*(double)
449  GetPixelBlue(q);
450  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
451  {
452  channel_similarity[BlueChannel]+=fabs(error);
453  channel_similarity[CompositeChannels]+=fabs(error);
454  }
455  }
456  if (((channel & OpacityChannel) != 0) &&
457  (image->matte != MagickFalse))
458  {
459  error=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
460  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
461  {
462  channel_similarity[OpacityChannel]+=fabs(error);
463  channel_similarity[CompositeChannels]+=fabs(error);
464  }
465  }
466  if (((channel & IndexChannel) != 0) &&
467  (image->colorspace == CMYKColorspace))
468  {
469  error=Sa*(double) indexes[x]-Da*(double) reconstruct_indexes[x];
470  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
471  {
472  channel_similarity[IndexChannel]+=fabs(error);
473  channel_similarity[CompositeChannels]+=fabs(error);
474  }
475  }
476  p++;
477  q++;
478  }
479 #if defined(MAGICKCORE_OPENMP_SUPPORT)
480  #pragma omp critical (MagickCore_GetAESimilarity)
481 #endif
482  for (i=0; i <= (ssize_t) CompositeChannels; i++)
483  similarity[i]+=channel_similarity[i];
484  }
485  similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
486  reconstruct_view=DestroyCacheView(reconstruct_view);
487  image_view=DestroyCacheView(image_view);
488  return(status);
489 }
490 
491 static MagickBooleanType GetFUZZSimilarity(const Image *image,
492  const Image *reconstruct_image,const ChannelType channel,
493  double *similarity,ExceptionInfo *exception)
494 {
495  CacheView
496  *image_view,
497  *reconstruct_view;
498 
499  double
500  area = 0.0,
501  fuzz;
502 
503  MagickBooleanType
504  status = MagickTrue;
505 
506  size_t
507  columns,
508  rows;
509 
510  ssize_t
511  i,
512  y;
513 
514  fuzz=GetFuzzyColorDistance(image,reconstruct_image);
515  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
516  image_view=AcquireVirtualCacheView(image,exception);
517  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
518 #if defined(MAGICKCORE_OPENMP_SUPPORT)
519  #pragma omp parallel for schedule(static) shared(status) \
520  magick_number_threads(image,image,rows,1)
521 #endif
522  for (y=0; y < (ssize_t) rows; y++)
523  {
524  double
525  channel_area = 0.0,
526  channel_similarity[CompositeChannels+1] = { 0.0 };
527 
528  const IndexPacket
529  *magick_restrict indexes,
530  *magick_restrict reconstruct_indexes;
531 
532  const PixelPacket
533  *magick_restrict p,
534  *magick_restrict q;
535 
536  ssize_t
537  i,
538  x;
539 
540  if (status == MagickFalse)
541  continue;
542  p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
543  q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
544  if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
545  {
546  status=MagickFalse;
547  continue;
548  }
549  indexes=GetCacheViewVirtualIndexQueue(image_view);
550  reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
551  for (x=0; x < (ssize_t) columns; x++)
552  {
553  MagickRealType
554  Da,
555  error,
556  Sa;
557 
558  Sa=QuantumScale*(image->matte != MagickFalse ? (double)
559  GetPixelAlpha(p) : ((double) QuantumRange-(double) OpaqueOpacity));
560  Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
561  (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
562  OpaqueOpacity));
563  if ((channel & RedChannel) != 0)
564  {
565  error=QuantumScale*(Sa*GetPixelRed(p)-Da*GetPixelRed(q));
566  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
567  {
568  channel_similarity[RedChannel]+=error*error;
569  channel_similarity[CompositeChannels]+=error*error;
570  channel_area++;
571  }
572  }
573  if ((channel & GreenChannel) != 0)
574  {
575  error=QuantumScale*(Sa*GetPixelGreen(p)-Da*GetPixelGreen(q));
576  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
577  {
578  channel_similarity[GreenChannel]+=error*error;
579  channel_similarity[CompositeChannels]+=error*error;
580  channel_area++;
581  }
582  }
583  if ((channel & BlueChannel) != 0)
584  {
585  error=QuantumScale*(Sa*GetPixelBlue(p)-Da*GetPixelBlue(q));
586  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
587  {
588  channel_similarity[BlueChannel]+=error*error;
589  channel_similarity[CompositeChannels]+=error*error;
590  channel_area++;
591  }
592  }
593  if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
594  {
595  error=QuantumScale*((double) GetPixelOpacity(p)-GetPixelOpacity(q));
596  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
597  {
598  channel_similarity[OpacityChannel]+=error*error;
599  channel_similarity[CompositeChannels]+=error*error;
600  channel_area++;
601  }
602  }
603  if (((channel & IndexChannel) != 0) &&
604  (image->colorspace == CMYKColorspace))
605  {
606  error=QuantumScale*(Sa*GetPixelIndex(indexes+x)-Da*
607  GetPixelIndex(reconstruct_indexes+x));
608  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
609  {
610  channel_similarity[BlackChannel]+=error*error;
611  channel_similarity[CompositeChannels]+=error*error;
612  channel_area++;
613  }
614  }
615  p++;
616  q++;
617  }
618 #if defined(MAGICKCORE_OPENMP_SUPPORT)
619  #pragma omp critical (MagickCore_GetMeanAbsoluteError)
620 #endif
621  {
622  area+=channel_area;
623  for (i=0; i <= (ssize_t) CompositeChannels; i++)
624  similarity[i]+=channel_similarity[i];
625  }
626  }
627  reconstruct_view=DestroyCacheView(reconstruct_view);
628  image_view=DestroyCacheView(image_view);
629  area=MagickSafeReciprocal(area);
630  for (i=0; i <= (ssize_t) CompositeChannels; i++)
631  similarity[i]*=area;
632  return(status);
633 }
634 
635 static MagickBooleanType GetPDCSimilarity(const Image *image,
636  const Image *reconstruct_image,const ChannelType channel,double *similarity,
637  ExceptionInfo *exception)
638 {
639  CacheView
640  *image_view,
641  *reconstruct_view;
642 
643  double
644  area,
645  fuzz;
646 
647  MagickBooleanType
648  status = MagickTrue;
649 
650  size_t
651  columns,
652  rows;
653 
654  ssize_t
655  j,
656  y;
657 
658  /*
659  Compute the absolute difference in pixels between two images.
660  */
661  fuzz=GetFuzzyColorDistance(image,reconstruct_image);
662  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
663  image_view=AcquireVirtualCacheView(image,exception);
664  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
665 #if defined(MAGICKCORE_OPENMP_SUPPORT)
666  #pragma omp parallel for schedule(static) shared(similarity,status) \
667  magick_number_threads(image,image,rows,1)
668 #endif
669  for (y=0; y < (ssize_t) rows; y++)
670  {
671  const IndexPacket
672  *magick_restrict indexes,
673  *magick_restrict reconstruct_indexes;
674 
675  const PixelPacket
676  *magick_restrict p,
677  *magick_restrict q;
678 
679  double
680  channel_similarity[CompositeChannels+1] = { 0.0 };
681 
682  ssize_t
683  i,
684  x;
685 
686  if (status == MagickFalse)
687  continue;
688  p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
689  q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
690  if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
691  {
692  status=MagickFalse;
693  continue;
694  }
695  indexes=GetCacheViewVirtualIndexQueue(image_view);
696  reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
697  (void) memset(channel_similarity,0,sizeof(channel_similarity));
698  for (x=0; x < (ssize_t) columns; x++)
699  {
700  double
701  Da,
702  error,
703  Sa;
704 
705  size_t
706  count = 0;
707 
708  Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
709  ((double) QuantumRange-(double) OpaqueOpacity));
710  Da=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(q) :
711  ((double) QuantumRange-(double) OpaqueOpacity));
712  if ((channel & RedChannel) != 0)
713  {
714  error=Sa*(double) GetPixelRed(p)-Da*(double)
715  GetPixelRed(q);
716  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
717  {
718  channel_similarity[RedChannel]++;
719  count++;
720  }
721  }
722  if ((channel & GreenChannel) != 0)
723  {
724  error=Sa*(double) GetPixelGreen(p)-Da*(double)
725  GetPixelGreen(q);
726  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
727  {
728  channel_similarity[GreenChannel]++;
729  count++;
730  }
731  }
732  if ((channel & BlueChannel) != 0)
733  {
734  error=Sa*(double) GetPixelBlue(p)-Da*(double)
735  GetPixelBlue(q);
736  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
737  {
738  channel_similarity[BlueChannel]++;
739  count++;
740  }
741  }
742  if (((channel & OpacityChannel) != 0) &&
743  (image->matte != MagickFalse))
744  {
745  error=(double) GetPixelOpacity(p)-(double) GetPixelOpacity(q);
746  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
747  {
748  channel_similarity[OpacityChannel]++;
749  count++;
750  }
751  }
752  if (((channel & IndexChannel) != 0) &&
753  (image->colorspace == CMYKColorspace))
754  {
755  error=Sa*(double) indexes[x]-Da*(double) reconstruct_indexes[x];
756  if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
757  {
758  channel_similarity[IndexChannel]++;
759  count++;
760  }
761  }
762  if (count != 0)
763  channel_similarity[CompositeChannels]++;
764  p++;
765  q++;
766  }
767 #if defined(MAGICKCORE_OPENMP_SUPPORT)
768  #pragma omp critical (MagickCore_GetAESimilarity)
769 #endif
770  for (i=0; i <= (ssize_t) CompositeChannels; i++)
771  similarity[i]+=channel_similarity[i];
772  }
773  reconstruct_view=DestroyCacheView(reconstruct_view);
774  image_view=DestroyCacheView(image_view);
775  area=MagickSafeReciprocal((double) columns*rows);
776  for (j=0; j <= CompositeChannels; j++)
777  similarity[j]*=area;
778  return(status);
779 }
780 
781 static MagickBooleanType GetMAESimilarity(const Image *image,
782  const Image *reconstruct_image,const ChannelType channel,
783  double *similarity,ExceptionInfo *exception)
784 {
785  CacheView
786  *image_view,
787  *reconstruct_view;
788 
789  MagickBooleanType
790  status;
791 
792  size_t
793  columns,
794  rows;
795 
796  ssize_t
797  i,
798  y;
799 
800  status=MagickTrue;
801  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
802  image_view=AcquireVirtualCacheView(image,exception);
803  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
804 #if defined(MAGICKCORE_OPENMP_SUPPORT)
805  #pragma omp parallel for schedule(static) shared(status) \
806  magick_number_threads(image,image,rows,1)
807 #endif
808  for (y=0; y < (ssize_t) rows; y++)
809  {
810  double
811  channel_similarity[CompositeChannels+1];
812 
813  const IndexPacket
814  *magick_restrict indexes,
815  *magick_restrict reconstruct_indexes;
816 
817  const PixelPacket
818  *magick_restrict p,
819  *magick_restrict q;
820 
821  ssize_t
822  i,
823  x;
824 
825  if (status == MagickFalse)
826  continue;
827  p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
828  q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
829  if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
830  {
831  status=MagickFalse;
832  continue;
833  }
834  indexes=GetCacheViewVirtualIndexQueue(image_view);
835  reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
836  (void) memset(channel_similarity,0,sizeof(channel_similarity));
837  for (x=0; x < (ssize_t) columns; x++)
838  {
839  MagickRealType
840  distance,
841  Da,
842  Sa;
843 
844  Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
845  ((double) QuantumRange-(double) OpaqueOpacity));
846  Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
847  (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
848  OpaqueOpacity));
849  if ((channel & RedChannel) != 0)
850  {
851  distance=QuantumScale*fabs(Sa*(double) GetPixelRed(p)-Da*
852  (double) GetPixelRed(q));
853  channel_similarity[RedChannel]+=distance;
854  channel_similarity[CompositeChannels]+=distance;
855  }
856  if ((channel & GreenChannel) != 0)
857  {
858  distance=QuantumScale*fabs(Sa*(double) GetPixelGreen(p)-Da*
859  (double) GetPixelGreen(q));
860  channel_similarity[GreenChannel]+=distance;
861  channel_similarity[CompositeChannels]+=distance;
862  }
863  if ((channel & BlueChannel) != 0)
864  {
865  distance=QuantumScale*fabs(Sa*(double) GetPixelBlue(p)-Da*
866  (double) GetPixelBlue(q));
867  channel_similarity[BlueChannel]+=distance;
868  channel_similarity[CompositeChannels]+=distance;
869  }
870  if (((channel & OpacityChannel) != 0) &&
871  (image->matte != MagickFalse))
872  {
873  distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
874  GetPixelOpacity(q));
875  channel_similarity[OpacityChannel]+=distance;
876  channel_similarity[CompositeChannels]+=distance;
877  }
878  if (((channel & IndexChannel) != 0) &&
879  (image->colorspace == CMYKColorspace))
880  {
881  distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
882  (double) GetPixelIndex(reconstruct_indexes+x));
883  channel_similarity[BlackChannel]+=distance;
884  channel_similarity[CompositeChannels]+=distance;
885  }
886  p++;
887  q++;
888  }
889 #if defined(MAGICKCORE_OPENMP_SUPPORT)
890  #pragma omp critical (MagickCore_GetMeanAbsoluteError)
891 #endif
892  for (i=0; i <= (ssize_t) CompositeChannels; i++)
893  similarity[i]+=channel_similarity[i];
894  }
895  reconstruct_view=DestroyCacheView(reconstruct_view);
896  image_view=DestroyCacheView(image_view);
897  for (i=0; i <= (ssize_t) CompositeChannels; i++)
898  similarity[i]/=((double) columns*rows);
899  similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
900  return(status);
901 }
902 
903 static MagickBooleanType GetMEPPSimilarity(Image *image,
904  const Image *reconstruct_image,const ChannelType channel,double *similarity,
905  ExceptionInfo *exception)
906 {
907  CacheView
908  *image_view,
909  *reconstruct_view;
910 
911  double
912  maximum_error = -MagickMaximumValue,
913  mean_error = 0.0;
914 
915  MagickBooleanType
916  status;
917 
918  size_t
919  columns,
920  rows;
921 
922  ssize_t
923  i,
924  y;
925 
926  status=MagickTrue;
927  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
928  image_view=AcquireVirtualCacheView(image,exception);
929  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
930 #if defined(MAGICKCORE_OPENMP_SUPPORT)
931  #pragma omp parallel for schedule(static) shared(maximum_error,status) \
932  magick_number_threads(image,image,rows,1)
933 #endif
934  for (y=0; y < (ssize_t) rows; y++)
935  {
936  double
937  channel_similarity[CompositeChannels+1] = { 0.0 },
938  local_maximum = maximum_error,
939  local_mean_error = 0.0;
940 
941  const IndexPacket
942  *magick_restrict indexes,
943  *magick_restrict reconstruct_indexes;
944 
945  const PixelPacket
946  *magick_restrict p,
947  *magick_restrict q;
948 
949  ssize_t
950  i,
951  x;
952 
953  if (status == MagickFalse)
954  continue;
955  p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
956  q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
957  if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
958  {
959  status=MagickFalse;
960  continue;
961  }
962  indexes=GetCacheViewVirtualIndexQueue(image_view);
963  reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
964  (void) memset(channel_similarity,0,sizeof(channel_similarity));
965  for (x=0; x < (ssize_t) columns; x++)
966  {
967  MagickRealType
968  distance,
969  Da,
970  Sa;
971 
972  Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
973  ((double) QuantumRange-(double) OpaqueOpacity));
974  Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
975  (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
976  OpaqueOpacity));
977  if ((channel & RedChannel) != 0)
978  {
979  distance=QuantumScale*fabs(Sa*(double) GetPixelRed(p)-Da*
980  (double) GetPixelRed(q));
981  channel_similarity[RedChannel]+=distance;
982  channel_similarity[CompositeChannels]+=distance;
983  local_mean_error+=distance*distance;
984  if (distance > local_maximum)
985  local_maximum=distance;
986  }
987  if ((channel & GreenChannel) != 0)
988  {
989  distance=QuantumScale*fabs(Sa*(double) GetPixelGreen(p)-Da*
990  (double) GetPixelGreen(q));
991  channel_similarity[GreenChannel]+=distance;
992  channel_similarity[CompositeChannels]+=distance;
993  local_mean_error+=distance*distance;
994  if (distance > local_maximum)
995  local_maximum=distance;
996  }
997  if ((channel & BlueChannel) != 0)
998  {
999  distance=QuantumScale*fabs(Sa*(double) GetPixelBlue(p)-Da*
1000  (double) GetPixelBlue(q));
1001  channel_similarity[BlueChannel]+=distance;
1002  channel_similarity[CompositeChannels]+=distance;
1003  local_mean_error+=distance*distance;
1004  if (distance > local_maximum)
1005  local_maximum=distance;
1006  }
1007  if (((channel & OpacityChannel) != 0) &&
1008  (image->matte != MagickFalse))
1009  {
1010  distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
1011  GetPixelOpacity(q));
1012  channel_similarity[OpacityChannel]+=distance;
1013  channel_similarity[CompositeChannels]+=distance;
1014  local_mean_error+=distance*distance;
1015  if (distance > local_maximum)
1016  local_maximum=distance;
1017  }
1018  if (((channel & IndexChannel) != 0) &&
1019  (image->colorspace == CMYKColorspace))
1020  {
1021  distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
1022  (double) GetPixelIndex(reconstruct_indexes+x));
1023  channel_similarity[BlackChannel]+=distance;
1024  channel_similarity[CompositeChannels]+=distance;
1025  local_mean_error+=distance*distance;
1026  if (distance > local_maximum)
1027  local_maximum=distance;
1028  }
1029  p++;
1030  q++;
1031  }
1032 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1033  #pragma omp critical (MagickCore_GetMeanAbsoluteError)
1034 #endif
1035  {
1036  for (i=0; i <= (ssize_t) CompositeChannels; i++)
1037  similarity[i]+=channel_similarity[i];
1038  mean_error+=local_mean_error;
1039  if (local_maximum > maximum_error)
1040  maximum_error=local_maximum;
1041  }
1042  }
1043  reconstruct_view=DestroyCacheView(reconstruct_view);
1044  image_view=DestroyCacheView(image_view);
1045  for (i=0; i <= (ssize_t) CompositeChannels; i++)
1046  similarity[i]/=((double) columns*rows);
1047  similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1048  image->error.mean_error_per_pixel=QuantumRange*similarity[CompositeChannels];
1049  image->error.normalized_mean_error=mean_error/((double) columns*rows);
1050  image->error.normalized_maximum_error=maximum_error;
1051  return(status);
1052 }
1053 
1054 static MagickBooleanType GetMSESimilarity(const Image *image,
1055  const Image *reconstruct_image,const ChannelType channel,
1056  double *similarity,ExceptionInfo *exception)
1057 {
1058  CacheView
1059  *image_view,
1060  *reconstruct_view;
1061 
1062  double
1063  area = 0.0;
1064 
1065  MagickBooleanType
1066  status;
1067 
1068  size_t
1069  columns,
1070  rows;
1071 
1072  ssize_t
1073  i,
1074  y;
1075 
1076  status=MagickTrue;
1077  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1078  image_view=AcquireVirtualCacheView(image,exception);
1079  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1080 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1081  #pragma omp parallel for schedule(static) shared(similarity,status) \
1082  magick_number_threads(image,image,rows,1)
1083 #endif
1084  for (y=0; y < (ssize_t) rows; y++)
1085  {
1086  double
1087  channel_similarity[CompositeChannels+1] = { 0.0 };
1088 
1089  const IndexPacket
1090  *magick_restrict indexes,
1091  *magick_restrict reconstruct_indexes;
1092 
1093  const PixelPacket
1094  *magick_restrict p,
1095  *magick_restrict q;
1096 
1097  ssize_t
1098  i,
1099  x;
1100 
1101  if (status == MagickFalse)
1102  continue;
1103  p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1104  q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1105  if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
1106  {
1107  status=MagickFalse;
1108  continue;
1109  }
1110  indexes=GetCacheViewVirtualIndexQueue(image_view);
1111  reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1112  for (x=0; x < (ssize_t) columns; x++)
1113  {
1114  double
1115  distance,
1116  Da,
1117  Sa;
1118 
1119  Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1120  ((double) QuantumRange-(double) OpaqueOpacity));
1121  Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1122  (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1123  OpaqueOpacity));
1124  if ((channel & RedChannel) != 0)
1125  {
1126  distance=QuantumScale*(Sa*(double) GetPixelRed(p)-Da*(double)
1127  GetPixelRed(q));
1128  channel_similarity[RedChannel]+=distance*distance;
1129  channel_similarity[CompositeChannels]+=distance*distance;
1130  }
1131  if ((channel & GreenChannel) != 0)
1132  {
1133  distance=QuantumScale*(Sa*(double) GetPixelGreen(p)-Da*(double)
1134  GetPixelGreen(q));
1135  channel_similarity[GreenChannel]+=distance*distance;
1136  channel_similarity[CompositeChannels]+=distance*distance;
1137  }
1138  if ((channel & BlueChannel) != 0)
1139  {
1140  distance=QuantumScale*(Sa*(double) GetPixelBlue(p)-Da*(double)
1141  GetPixelBlue(q));
1142  channel_similarity[BlueChannel]+=distance*distance;
1143  channel_similarity[CompositeChannels]+=distance*distance;
1144  }
1145  if (((channel & OpacityChannel) != 0) &&
1146  (image->matte != MagickFalse))
1147  {
1148  distance=QuantumScale*((double) GetPixelOpacity(p)-(double)
1149  GetPixelOpacity(q));
1150  channel_similarity[OpacityChannel]+=distance*distance;
1151  channel_similarity[CompositeChannels]+=distance*distance;
1152  }
1153  if (((channel & IndexChannel) != 0) &&
1154  (image->colorspace == CMYKColorspace) &&
1155  (reconstruct_image->colorspace == CMYKColorspace))
1156  {
1157  distance=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-Da*
1158  (double) GetPixelIndex(reconstruct_indexes+x));
1159  channel_similarity[BlackChannel]+=distance*distance;
1160  channel_similarity[CompositeChannels]+=distance*distance;
1161  }
1162  p++;
1163  q++;
1164  }
1165 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1166  #pragma omp critical (MagickCore_GetMeanSquaredError)
1167 #endif
1168  for (i=0; i <= (ssize_t) CompositeChannels; i++)
1169  similarity[i]+=channel_similarity[i];
1170  }
1171  reconstruct_view=DestroyCacheView(reconstruct_view);
1172  image_view=DestroyCacheView(image_view);
1173  area=MagickSafeReciprocal((double) columns*rows);
1174  for (i=0; i <= (ssize_t) CompositeChannels; i++)
1175  similarity[i]*=area;
1176  similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1177  return(status);
1178 }
1179 
1180 static MagickBooleanType GetNCCSimilarity(const Image *image,
1181  const Image *reconstruct_image,const ChannelType channel,double *similarity,
1182  ExceptionInfo *exception)
1183 {
1184 #define SimilarityImageTag "Similarity/Image"
1185 
1186  CacheView
1187  *image_view,
1188  *reconstruct_view;
1189 
1191  *image_statistics,
1192  *reconstruct_statistics;
1193 
1194  double
1195  alpha_variance[CompositeChannels+1] = { 0.0 },
1196  beta_variance[CompositeChannels+1] = { 0.0 };
1197 
1198  MagickBooleanType
1199  status;
1200 
1201  MagickOffsetType
1202  progress;
1203 
1204  size_t
1205  columns,
1206  rows;
1207 
1208  ssize_t
1209  i,
1210  y;
1211 
1212  /*
1213  Normalize to account for variation due to lighting and exposure condition.
1214  */
1215  image_statistics=GetImageChannelStatistics(image,exception);
1216  reconstruct_statistics=GetImageChannelStatistics(reconstruct_image,exception);
1217  if ((image_statistics == (ChannelStatistics *) NULL) ||
1218  (reconstruct_statistics == (ChannelStatistics *) NULL))
1219  {
1220  if (image_statistics != (ChannelStatistics *) NULL)
1221  image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1222  image_statistics);
1223  if (reconstruct_statistics != (ChannelStatistics *) NULL)
1224  reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1225  reconstruct_statistics);
1226  return(MagickFalse);
1227  }
1228  (void) memset(similarity,0,(CompositeChannels+1)*sizeof(*similarity));
1229  status=MagickTrue;
1230  progress=0;
1231  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1232  image_view=AcquireVirtualCacheView(image,exception);
1233  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1234 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1235  #pragma omp parallel for schedule(static) shared(status) \
1236  magick_number_threads(image,image,rows,1)
1237 #endif
1238  for (y=0; y < (ssize_t) rows; y++)
1239  {
1240  const IndexPacket
1241  *magick_restrict indexes,
1242  *magick_restrict reconstruct_indexes;
1243 
1244  const PixelPacket
1245  *magick_restrict p,
1246  *magick_restrict q;
1247 
1248  double
1249  channel_alpha_variance[CompositeChannels+1] = { 0.0 },
1250  channel_beta_variance[CompositeChannels+1] = { 0.0 },
1251  channel_similarity[CompositeChannels+1] = { 0.0 };
1252 
1253  ssize_t
1254  x;
1255 
1256  if (status == MagickFalse)
1257  continue;
1258  p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1259  q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1260  if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
1261  {
1262  status=MagickFalse;
1263  continue;
1264  }
1265  indexes=GetCacheViewVirtualIndexQueue(image_view);
1266  reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1267  for (x=0; x < (ssize_t) columns; x++)
1268  {
1269  MagickRealType
1270  alpha,
1271  beta,
1272  Da,
1273  Sa;
1274 
1275  Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1276  (double) QuantumRange);
1277  Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1278  (double) GetPixelAlpha(q) : (double) QuantumRange);
1279  if ((channel & RedChannel) != 0)
1280  {
1281  alpha=QuantumScale*(Sa*(double) GetPixelRed(p)-
1282  image_statistics[RedChannel].mean);
1283  beta=QuantumScale*(Da*(double) GetPixelRed(q)-
1284  reconstruct_statistics[RedChannel].mean);
1285  channel_similarity[RedChannel]+=alpha*beta;
1286  channel_similarity[CompositeChannels]+=alpha*beta;
1287  channel_alpha_variance[RedChannel]+=alpha*alpha;
1288  channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1289  channel_beta_variance[RedChannel]+=beta*beta;
1290  channel_beta_variance[CompositeChannels]+=beta*beta;
1291  }
1292  if ((channel & GreenChannel) != 0)
1293  {
1294  alpha=QuantumScale*(Sa*(double) GetPixelGreen(p)-
1295  image_statistics[GreenChannel].mean);
1296  beta=QuantumScale*(Da*(double) GetPixelGreen(q)-
1297  reconstruct_statistics[GreenChannel].mean);
1298  channel_similarity[GreenChannel]+=alpha*beta;
1299  channel_similarity[CompositeChannels]+=alpha*beta;
1300  channel_alpha_variance[GreenChannel]+=alpha*alpha;
1301  channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1302  channel_beta_variance[GreenChannel]+=beta*beta;
1303  channel_beta_variance[CompositeChannels]+=beta*beta;
1304  }
1305  if ((channel & BlueChannel) != 0)
1306  {
1307  alpha=QuantumScale*(Sa*(double) GetPixelBlue(p)-
1308  image_statistics[BlueChannel].mean);
1309  beta=QuantumScale*(Da*(double) GetPixelBlue(q)-
1310  reconstruct_statistics[BlueChannel].mean);
1311  channel_similarity[BlueChannel]+=alpha*beta;
1312  channel_alpha_variance[BlueChannel]+=alpha*alpha;
1313  channel_beta_variance[BlueChannel]+=beta*beta;
1314  }
1315  if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1316  {
1317  alpha=QuantumScale*((double) GetPixelAlpha(p)-
1318  image_statistics[AlphaChannel].mean);
1319  beta=QuantumScale*((double) GetPixelAlpha(q)-
1320  reconstruct_statistics[AlphaChannel].mean);
1321  channel_similarity[OpacityChannel]+=alpha*beta;
1322  channel_similarity[CompositeChannels]+=alpha*beta;
1323  channel_alpha_variance[OpacityChannel]+=alpha*alpha;
1324  channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1325  channel_beta_variance[OpacityChannel]+=beta*beta;
1326  channel_beta_variance[CompositeChannels]+=beta*beta;
1327  }
1328  if (((channel & IndexChannel) != 0) &&
1329  (image->colorspace == CMYKColorspace) &&
1330  (reconstruct_image->colorspace == CMYKColorspace))
1331  {
1332  alpha=QuantumScale*(Sa*(double) GetPixelIndex(indexes+x)-
1333  image_statistics[BlackChannel].mean);
1334  beta=QuantumScale*(Da*(double) GetPixelIndex(reconstruct_indexes+
1335  x)-reconstruct_statistics[BlackChannel].mean);
1336  channel_similarity[BlackChannel]+=alpha*beta;
1337  channel_similarity[CompositeChannels]+=alpha*beta;
1338  channel_alpha_variance[BlackChannel]+=alpha*alpha;
1339  channel_alpha_variance[CompositeChannels]+=alpha*alpha;
1340  channel_beta_variance[BlackChannel]+=beta*beta;
1341  channel_beta_variance[CompositeChannels]+=beta*beta;
1342  }
1343  p++;
1344  q++;
1345  }
1346 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1347  #pragma omp critical (GetNCCSimilarity)
1348 #endif
1349  {
1350  ssize_t
1351  j;
1352 
1353  for (j=0; j <= (ssize_t) CompositeChannels; j++)
1354  {
1355  similarity[j]+=channel_similarity[j];
1356  alpha_variance[j]+=channel_alpha_variance[j];
1357  beta_variance[j]+=channel_beta_variance[j];
1358  }
1359  }
1360  if (image->progress_monitor != (MagickProgressMonitor) NULL)
1361  {
1362  MagickBooleanType
1363  proceed;
1364 
1365 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1366  #pragma omp atomic
1367 #endif
1368  progress++;
1369  proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1370  if (proceed == MagickFalse)
1371  status=MagickFalse;
1372  }
1373  }
1374  reconstruct_view=DestroyCacheView(reconstruct_view);
1375  image_view=DestroyCacheView(image_view);
1376  /*
1377  Divide by the standard deviation.
1378  */
1379  for (i=0; i <= (ssize_t) CompositeChannels; i++)
1380  similarity[i]*=MagickSafeReciprocal(sqrt(alpha_variance[i])*
1381  sqrt(beta_variance[i]));
1382  /*
1383  Free resources.
1384  */
1385  reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1386  reconstruct_statistics);
1387  image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1388  image_statistics);
1389  return(status);
1390 }
1391 
1392 static MagickBooleanType GetPASimilarity(const Image *image,
1393  const Image *reconstruct_image,const ChannelType channel,
1394  double *similarity,ExceptionInfo *exception)
1395 {
1396  CacheView
1397  *image_view,
1398  *reconstruct_view;
1399 
1400  MagickBooleanType
1401  status;
1402 
1403  size_t
1404  columns,
1405  rows;
1406 
1407  ssize_t
1408  y;
1409 
1410  status=MagickTrue;
1411  (void) memset(similarity,0,(CompositeChannels+1)*sizeof(*similarity));
1412  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1413  image_view=AcquireVirtualCacheView(image,exception);
1414  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1415 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1416  #pragma omp parallel for schedule(static) shared(status) \
1417  magick_number_threads(image,image,rows,1)
1418 #endif
1419  for (y=0; y < (ssize_t) rows; y++)
1420  {
1421  double
1422  channel_similarity[CompositeChannels+1];
1423 
1424  const IndexPacket
1425  *magick_restrict indexes,
1426  *magick_restrict reconstruct_indexes;
1427 
1428  const PixelPacket
1429  *magick_restrict p,
1430  *magick_restrict q;
1431 
1432  ssize_t
1433  i,
1434  x;
1435 
1436  if (status == MagickFalse)
1437  continue;
1438  p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1439  q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1440  if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
1441  {
1442  status=MagickFalse;
1443  continue;
1444  }
1445  indexes=GetCacheViewVirtualIndexQueue(image_view);
1446  reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
1447  (void) memset(channel_similarity,0,(CompositeChannels+1)*
1448  sizeof(*channel_similarity));
1449  for (x=0; x < (ssize_t) columns; x++)
1450  {
1451  MagickRealType
1452  distance,
1453  Da,
1454  Sa;
1455 
1456  Sa=QuantumScale*(image->matte != MagickFalse ? (double) GetPixelAlpha(p) :
1457  ((double) QuantumRange-(double) OpaqueOpacity));
1458  Da=QuantumScale*(reconstruct_image->matte != MagickFalse ?
1459  (double) GetPixelAlpha(q) : ((double) QuantumRange-(double)
1460  OpaqueOpacity));
1461  if ((channel & RedChannel) != 0)
1462  {
1463  distance=QuantumScale*fabs(Sa*(double) GetPixelRed(p)-Da*
1464  (double) GetPixelRed(q));
1465  if (distance > channel_similarity[RedChannel])
1466  channel_similarity[RedChannel]=distance;
1467  if (distance > channel_similarity[CompositeChannels])
1468  channel_similarity[CompositeChannels]=distance;
1469  }
1470  if ((channel & GreenChannel) != 0)
1471  {
1472  distance=QuantumScale*fabs(Sa*(double) GetPixelGreen(p)-Da*
1473  (double) GetPixelGreen(q));
1474  if (distance > channel_similarity[GreenChannel])
1475  channel_similarity[GreenChannel]=distance;
1476  if (distance > channel_similarity[CompositeChannels])
1477  channel_similarity[CompositeChannels]=distance;
1478  }
1479  if ((channel & BlueChannel) != 0)
1480  {
1481  distance=QuantumScale*fabs(Sa*(double) GetPixelBlue(p)-Da*
1482  (double) GetPixelBlue(q));
1483  if (distance > channel_similarity[BlueChannel])
1484  channel_similarity[BlueChannel]=distance;
1485  if (distance > channel_similarity[CompositeChannels])
1486  channel_similarity[CompositeChannels]=distance;
1487  }
1488  if (((channel & OpacityChannel) != 0) &&
1489  (image->matte != MagickFalse))
1490  {
1491  distance=QuantumScale*fabs((double) GetPixelOpacity(p)-(double)
1492  GetPixelOpacity(q));
1493  if (distance > channel_similarity[OpacityChannel])
1494  channel_similarity[OpacityChannel]=distance;
1495  if (distance > channel_similarity[CompositeChannels])
1496  channel_similarity[CompositeChannels]=distance;
1497  }
1498  if (((channel & IndexChannel) != 0) &&
1499  (image->colorspace == CMYKColorspace) &&
1500  (reconstruct_image->colorspace == CMYKColorspace))
1501  {
1502  distance=QuantumScale*fabs(Sa*(double) GetPixelIndex(indexes+x)-Da*
1503  (double) GetPixelIndex(reconstruct_indexes+x));
1504  if (distance > channel_similarity[BlackChannel])
1505  channel_similarity[BlackChannel]=distance;
1506  if (distance > channel_similarity[CompositeChannels])
1507  channel_similarity[CompositeChannels]=distance;
1508  }
1509  p++;
1510  q++;
1511  }
1512 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1513  #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1514 #endif
1515  for (i=0; i <= (ssize_t) CompositeChannels; i++)
1516  if (channel_similarity[i] > similarity[i])
1517  similarity[i]=channel_similarity[i];
1518  }
1519  reconstruct_view=DestroyCacheView(reconstruct_view);
1520  image_view=DestroyCacheView(image_view);
1521  return(status);
1522 }
1523 
1524 static MagickBooleanType GetPSNRSimilarity(const Image *image,
1525  const Image *reconstruct_image,const ChannelType channel,
1526  double *similarity,ExceptionInfo *exception)
1527 {
1528  MagickBooleanType
1529  status;
1530 
1531  status=GetMSESimilarity(image,reconstruct_image,channel,similarity,
1532  exception);
1533  if ((channel & RedChannel) != 0)
1534  similarity[RedChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1535  similarity[RedChannel]))/MagickSafePSNRRecipicol(10.0);
1536  if ((channel & GreenChannel) != 0)
1537  similarity[GreenChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1538  similarity[GreenChannel]))/MagickSafePSNRRecipicol(10.0);
1539  if ((channel & BlueChannel) != 0)
1540  similarity[BlueChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1541  similarity[BlueChannel]))/MagickSafePSNRRecipicol(10.0);
1542  if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
1543  similarity[OpacityChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1544  similarity[OpacityChannel]))/MagickSafePSNRRecipicol(10.0);
1545  if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1546  similarity[BlackChannel]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1547  similarity[BlackChannel]))/MagickSafePSNRRecipicol(10.0);
1548  similarity[CompositeChannels]=10.0*MagickSafeLog10(MagickSafeReciprocal(
1549  similarity[CompositeChannels]))/MagickSafePSNRRecipicol(10.0);
1550  return(status);
1551 }
1552 
1553 static MagickBooleanType GetPHASHSimilarity(const Image *image,
1554  const Image *reconstruct_image,const ChannelType channel,double *similarity,
1555  ExceptionInfo *exception)
1556 {
1558  *image_phash,
1559  *reconstruct_phash;
1560 
1561  double
1562  error,
1563  difference;
1564 
1565  ssize_t
1566  i;
1567 
1568  /*
1569  Compute perceptual hash in the sRGB colorspace.
1570  */
1571  image_phash=GetImageChannelPerceptualHash(image,exception);
1572  if (image_phash == (ChannelPerceptualHash *) NULL)
1573  return(MagickFalse);
1574  reconstruct_phash=GetImageChannelPerceptualHash(reconstruct_image,exception);
1575  if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1576  {
1577  image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1578  return(MagickFalse);
1579  }
1580  for (i=0; i < MaximumNumberOfImageMoments; i++)
1581  {
1582  /*
1583  Compute sum of moment differences squared.
1584  */
1585  if ((channel & RedChannel) != 0)
1586  {
1587  error=reconstruct_phash[RedChannel].P[i]-image_phash[RedChannel].P[i];
1588  if (IsNaN(error) != 0)
1589  error=0.0;
1590  difference=error*error;
1591  similarity[RedChannel]+=difference;
1592  similarity[CompositeChannels]+=difference;
1593  }
1594  if ((channel & GreenChannel) != 0)
1595  {
1596  error=reconstruct_phash[GreenChannel].P[i]-
1597  image_phash[GreenChannel].P[i];
1598  if (IsNaN(error) != 0)
1599  error=0.0;
1600  difference=error*error;
1601  similarity[GreenChannel]+=difference;
1602  similarity[CompositeChannels]+=difference;
1603  }
1604  if ((channel & BlueChannel) != 0)
1605  {
1606  error=reconstruct_phash[BlueChannel].P[i]-image_phash[BlueChannel].P[i];
1607  if (IsNaN(error) != 0)
1608  error=0.0;
1609  difference=error*error;
1610  similarity[BlueChannel]+=difference;
1611  similarity[CompositeChannels]+=difference;
1612  }
1613  if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1614  (reconstruct_image->matte != MagickFalse))
1615  {
1616  error=reconstruct_phash[OpacityChannel].P[i]-
1617  image_phash[OpacityChannel].P[i];
1618  if (IsNaN(error) != 0)
1619  error=0.0;
1620  difference=error*error;
1621  similarity[OpacityChannel]+=difference;
1622  similarity[CompositeChannels]+=difference;
1623  }
1624  if (((channel & IndexChannel) != 0) &&
1625  (image->colorspace == CMYKColorspace) &&
1626  (reconstruct_image->colorspace == CMYKColorspace))
1627  {
1628  error=reconstruct_phash[IndexChannel].P[i]-
1629  image_phash[IndexChannel].P[i];
1630  if (IsNaN(error) != 0)
1631  error=0.0;
1632  difference=error*error;
1633  similarity[IndexChannel]+=difference;
1634  similarity[CompositeChannels]+=difference;
1635  }
1636  }
1637  /*
1638  Compute perceptual hash in the HCLP colorspace.
1639  */
1640  for (i=0; i < MaximumNumberOfImageMoments; i++)
1641  {
1642  /*
1643  Compute sum of moment differences squared.
1644  */
1645  if ((channel & RedChannel) != 0)
1646  {
1647  error=reconstruct_phash[RedChannel].Q[i]-image_phash[RedChannel].Q[i];
1648  if (IsNaN(error) != 0)
1649  error=0.0;
1650  difference=error*error;
1651  similarity[RedChannel]+=difference;
1652  similarity[CompositeChannels]+=difference;
1653  }
1654  if ((channel & GreenChannel) != 0)
1655  {
1656  error=reconstruct_phash[GreenChannel].Q[i]-
1657  image_phash[GreenChannel].Q[i];
1658  if (IsNaN(error) != 0)
1659  error=0.0;
1660  difference=error*error;
1661  similarity[GreenChannel]+=difference;
1662  similarity[CompositeChannels]+=difference;
1663  }
1664  if ((channel & BlueChannel) != 0)
1665  {
1666  error=reconstruct_phash[BlueChannel].Q[i]-image_phash[BlueChannel].Q[i];
1667  if (IsNaN(error) != 0)
1668  error=0.0;
1669  difference=error*error;
1670  similarity[BlueChannel]+=difference;
1671  similarity[CompositeChannels]+=difference;
1672  }
1673  if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse) &&
1674  (reconstruct_image->matte != MagickFalse))
1675  {
1676  error=reconstruct_phash[OpacityChannel].Q[i]-
1677  image_phash[OpacityChannel].Q[i];
1678  if (IsNaN(error) != 0)
1679  error=0.0;
1680  difference=error*error;
1681  similarity[OpacityChannel]+=difference;
1682  similarity[CompositeChannels]+=difference;
1683  }
1684  if (((channel & IndexChannel) != 0) &&
1685  (image->colorspace == CMYKColorspace) &&
1686  (reconstruct_image->colorspace == CMYKColorspace))
1687  {
1688  error=reconstruct_phash[IndexChannel].Q[i]-
1689  image_phash[IndexChannel].Q[i];
1690  if (IsNaN(error) != 0)
1691  error=0.0;
1692  difference=error*error;
1693  similarity[IndexChannel]+=difference;
1694  similarity[CompositeChannels]+=difference;
1695  }
1696  }
1697  similarity[CompositeChannels]/=(double) GetNumberChannels(image,channel);
1698  /*
1699  Free resources.
1700  */
1701  reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1702  reconstruct_phash);
1703  image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
1704  return(MagickTrue);
1705 }
1706 
1707 static MagickBooleanType GetRMSESimilarity(const Image *image,
1708  const Image *reconstruct_image,const ChannelType channel,double *similarity,
1709  ExceptionInfo *exception)
1710 {
1711 #define RMSESquareRoot(x) sqrt((x) < 0.0 ? 0.0 : (x))
1712 
1713  MagickBooleanType
1714  status;
1715 
1716  status=GetMSESimilarity(image,reconstruct_image,channel,similarity,
1717  exception);
1718  if ((channel & RedChannel) != 0)
1719  similarity[RedChannel]=RMSESquareRoot(similarity[RedChannel]);
1720  if ((channel & GreenChannel) != 0)
1721  similarity[GreenChannel]=RMSESquareRoot(similarity[GreenChannel]);
1722  if ((channel & BlueChannel) != 0)
1723  similarity[BlueChannel]=RMSESquareRoot(similarity[BlueChannel]);
1724  if (((channel & OpacityChannel) != 0) &&
1725  (image->matte != MagickFalse))
1726  similarity[OpacityChannel]=RMSESquareRoot(similarity[OpacityChannel]);
1727  if (((channel & IndexChannel) != 0) &&
1728  (image->colorspace == CMYKColorspace))
1729  similarity[BlackChannel]=RMSESquareRoot(similarity[BlackChannel]);
1730  similarity[CompositeChannels]=RMSESquareRoot(similarity[CompositeChannels]);
1731  return(status);
1732 }
1733 
1734 MagickExport MagickBooleanType GetImageChannelDistortion(Image *image,
1735  const Image *reconstruct_image,const ChannelType channel,
1736  const MetricType metric,double *distortion,ExceptionInfo *exception)
1737 {
1738  double
1739  *channel_similarity;
1740 
1741  MagickBooleanType
1742  status;
1743 
1744  size_t
1745  length;
1746 
1747  assert(image != (Image *) NULL);
1748  assert(image->signature == MagickCoreSignature);
1749  assert(reconstruct_image != (const Image *) NULL);
1750  assert(reconstruct_image->signature == MagickCoreSignature);
1751  assert(distortion != (double *) NULL);
1752  if (IsEventLogging() != MagickFalse)
1753  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1754  *distortion=0.0;
1755  if (metric != PerceptualHashErrorMetric)
1756  if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1757  ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
1758  /*
1759  Get image distortion.
1760  */
1761  length=CompositeChannels+1UL;
1762  channel_similarity=(double *) AcquireQuantumMemory(length,
1763  sizeof(*channel_similarity));
1764  if (channel_similarity == (double *) NULL)
1765  ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1766  (void) memset(channel_similarity,0,length*sizeof(*channel_similarity));
1767  switch (metric)
1768  {
1769  case AbsoluteErrorMetric:
1770  {
1771  status=GetAESimilarity(image,reconstruct_image,channel,
1772  channel_similarity,exception);
1773  break;
1774  }
1775  case FuzzErrorMetric:
1776  {
1777  status=GetFUZZSimilarity(image,reconstruct_image,channel,
1778  channel_similarity,exception);
1779  break;
1780  }
1781  case MeanAbsoluteErrorMetric:
1782  {
1783  status=GetMAESimilarity(image,reconstruct_image,channel,
1784  channel_similarity,exception);
1785  break;
1786  }
1787  case MeanErrorPerPixelMetric:
1788  {
1789  status=GetMEPPSimilarity(image,reconstruct_image,channel,
1790  channel_similarity,exception);
1791  break;
1792  }
1793  case MeanSquaredErrorMetric:
1794  {
1795  status=GetMSESimilarity(image,reconstruct_image,channel,
1796  channel_similarity,exception);
1797  break;
1798  }
1799  case NormalizedCrossCorrelationErrorMetric:
1800  {
1801  status=GetNCCSimilarity(image,reconstruct_image,channel,
1802  channel_similarity,exception);
1803  break;
1804  }
1805  case PeakAbsoluteErrorMetric:
1806  {
1807  status=GetPASimilarity(image,reconstruct_image,channel,
1808  channel_similarity,exception);
1809  break;
1810  }
1811  case PeakSignalToNoiseRatioMetric:
1812  {
1813  status=GetPSNRSimilarity(image,reconstruct_image,channel,
1814  channel_similarity,exception);
1815  break;
1816  }
1817  case PerceptualHashErrorMetric:
1818  {
1819  status=GetPHASHSimilarity(image,reconstruct_image,channel,
1820  channel_similarity,exception);
1821  break;
1822  }
1823  case PixelDifferenceCountErrorMetric:
1824  {
1825  status=GetPDCSimilarity(image,reconstruct_image,channel,
1826  channel_similarity,exception);
1827  break;
1828  }
1829  case RootMeanSquaredErrorMetric:
1830  case UndefinedErrorMetric:
1831  default:
1832  {
1833  status=GetRMSESimilarity(image,reconstruct_image,channel,
1834  channel_similarity,exception);
1835  break;
1836  }
1837  }
1838  *distortion=channel_similarity[CompositeChannels];
1839  switch (metric)
1840  {
1841  case NormalizedCrossCorrelationErrorMetric:
1842  {
1843  *distortion=(1.0-(*distortion))/2.0;
1844  break;
1845  }
1846  default: break;
1847  }
1848  if (fabs(*distortion) < MagickEpsilon)
1849  *distortion=0.0;
1850  channel_similarity=(double *) RelinquishMagickMemory(channel_similarity);
1851  (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
1852  *distortion);
1853  return(status);
1854 }
1855 
1856 /*
1857 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1858 % %
1859 % %
1860 % %
1861 % G e t I m a g e C h a n n e l D i s t o r t i o n s %
1862 % %
1863 % %
1864 % %
1865 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1866 %
1867 % GetImageChannelDistortions() compares the image channels of an image to a
1868 % reconstructed image and returns the specified distortion metric for each
1869 % channel.
1870 %
1871 % The format of the GetImageChannelDistortions method is:
1872 %
1873 % double *GetImageChannelDistortions(const Image *image,
1874 % const Image *reconstruct_image,const MetricType metric,
1875 % ExceptionInfo *exception)
1876 %
1877 % A description of each parameter follows:
1878 %
1879 % o image: the image.
1880 %
1881 % o reconstruct_image: the reconstruct image.
1882 %
1883 % o metric: the metric.
1884 %
1885 % o exception: return any errors or warnings in this structure.
1886 %
1887 */
1888 MagickExport double *GetImageChannelDistortions(Image *image,
1889  const Image *reconstruct_image,const MetricType metric,
1890  ExceptionInfo *exception)
1891 {
1892  double
1893  *distortion,
1894  *similarity;
1895 
1896  MagickBooleanType
1897  status;
1898 
1899  size_t
1900  length;
1901 
1902  ssize_t
1903  i;
1904 
1905  assert(image != (Image *) NULL);
1906  assert(image->signature == MagickCoreSignature);
1907  assert(reconstruct_image != (const Image *) NULL);
1908  assert(reconstruct_image->signature == MagickCoreSignature);
1909  if (IsEventLogging() != MagickFalse)
1910  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1911  if (metric != PerceptualHashErrorMetric)
1912  if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1913  {
1914  (void) ThrowMagickException(&image->exception,GetMagickModule(),
1915  ImageError,"ImageMorphologyDiffers","`%s'",image->filename);
1916  return((double *) NULL);
1917  }
1918  /*
1919  Get image distortion.
1920  */
1921  length=CompositeChannels+1UL;
1922  similarity=(double *) AcquireQuantumMemory(length,
1923  sizeof(*similarity));
1924  if (similarity == (double *) NULL)
1925  ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1926  (void) memset(similarity,0,length*sizeof(*similarity));
1927  status=MagickTrue;
1928  switch (metric)
1929  {
1930  case AbsoluteErrorMetric:
1931  {
1932  status=GetAESimilarity(image,reconstruct_image,CompositeChannels,
1933  similarity,exception);
1934  break;
1935  }
1936  case FuzzErrorMetric:
1937  {
1938  status=GetFUZZSimilarity(image,reconstruct_image,CompositeChannels,
1939  similarity,exception);
1940  break;
1941  }
1942  case MeanAbsoluteErrorMetric:
1943  {
1944  status=GetMAESimilarity(image,reconstruct_image,CompositeChannels,
1945  similarity,exception);
1946  break;
1947  }
1948  case MeanErrorPerPixelMetric:
1949  {
1950  status=GetMEPPSimilarity(image,reconstruct_image,CompositeChannels,
1951  similarity,exception);
1952  break;
1953  }
1954  case MeanSquaredErrorMetric:
1955  {
1956  status=GetMSESimilarity(image,reconstruct_image,CompositeChannels,
1957  similarity,exception);
1958  break;
1959  }
1960  case NormalizedCrossCorrelationErrorMetric:
1961  {
1962  status=GetNCCSimilarity(image,reconstruct_image,CompositeChannels,
1963  similarity,exception);
1964  break;
1965  }
1966  case PeakAbsoluteErrorMetric:
1967  {
1968  status=GetPASimilarity(image,reconstruct_image,CompositeChannels,
1969  similarity,exception);
1970  break;
1971  }
1972  case PeakSignalToNoiseRatioMetric:
1973  {
1974  status=GetPSNRSimilarity(image,reconstruct_image,CompositeChannels,
1975  similarity,exception);
1976  break;
1977  }
1978  case PerceptualHashErrorMetric:
1979  {
1980  status=GetPHASHSimilarity(image,reconstruct_image,CompositeChannels,
1981  similarity,exception);
1982  break;
1983  }
1984  case PixelDifferenceCountErrorMetric:
1985  {
1986  status=GetPDCSimilarity(image,reconstruct_image,CompositeChannels,
1987  similarity,exception);
1988  break;
1989  }
1990  case RootMeanSquaredErrorMetric:
1991  case UndefinedErrorMetric:
1992  default:
1993  {
1994  status=GetRMSESimilarity(image,reconstruct_image,CompositeChannels,
1995  similarity,exception);
1996  break;
1997  }
1998  }
1999  if (status == MagickFalse)
2000  {
2001  similarity=(double *) RelinquishMagickMemory(similarity);
2002  return((double *) NULL);
2003  }
2004  distortion=similarity;
2005  switch (metric)
2006  {
2007  case NormalizedCrossCorrelationErrorMetric:
2008  {
2009  for (i=0; i <= (ssize_t) CompositeChannels; i++)
2010  distortion[i]=(1.0-distortion[i])/2.0;
2011  break;
2012  }
2013  default: break;
2014  }
2015  for (i=0; i <= (ssize_t) CompositeChannels; i++)
2016  if (fabs(distortion[i]) < MagickEpsilon)
2017  distortion[i]=0.0;
2018  return(distortion);
2019 }
2020 
2021 /*
2022 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2023 % %
2024 % %
2025 % %
2026 % I s I m a g e s E q u a l %
2027 % %
2028 % %
2029 % %
2030 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2031 %
2032 % IsImagesEqual() measures the difference between colors at each pixel
2033 % location of two images. A value other than 0 means the colors match
2034 % exactly. Otherwise an error measure is computed by summing over all
2035 % pixels in an image the distance squared in RGB space between each image
2036 % pixel and its corresponding pixel in the reconstruct image. The error
2037 % measure is assigned to these image members:
2038 %
2039 % o mean_error_per_pixel: The mean error for any single pixel in
2040 % the image.
2041 %
2042 % o normalized_mean_error: The normalized mean quantization error for
2043 % any single pixel in the image. This distance measure is normalized to
2044 % a range between 0 and 1. It is independent of the range of red, green,
2045 % and blue values in the image.
2046 %
2047 % o normalized_maximum_error: The normalized maximum quantization
2048 % error for any single pixel in the image. This distance measure is
2049 % normalized to a range between 0 and 1. It is independent of the range
2050 % of red, green, and blue values in your image.
2051 %
2052 % A small normalized mean square error, accessed as
2053 % image->normalized_mean_error, suggests the images are very similar in
2054 % spatial layout and color.
2055 %
2056 % The format of the IsImagesEqual method is:
2057 %
2058 % MagickBooleanType IsImagesEqual(Image *image,
2059 % const Image *reconstruct_image)
2060 %
2061 % A description of each parameter follows.
2062 %
2063 % o image: the image.
2064 %
2065 % o reconstruct_image: the reconstruct image.
2066 %
2067 */
2068 MagickExport MagickBooleanType IsImagesEqual(Image *image,
2069  const Image *reconstruct_image)
2070 {
2071  CacheView
2072  *image_view,
2073  *reconstruct_view;
2074 
2076  *exception;
2077 
2078  MagickBooleanType
2079  status;
2080 
2081  MagickRealType
2082  area,
2083  gamma,
2084  maximum_error,
2085  mean_error,
2086  mean_error_per_pixel;
2087 
2088  size_t
2089  columns,
2090  rows;
2091 
2092  ssize_t
2093  y;
2094 
2095  assert(image != (Image *) NULL);
2096  assert(image->signature == MagickCoreSignature);
2097  assert(reconstruct_image != (const Image *) NULL);
2098  assert(reconstruct_image->signature == MagickCoreSignature);
2099  exception=(&image->exception);
2100  if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
2101  ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
2102  area=0.0;
2103  maximum_error=0.0;
2104  mean_error_per_pixel=0.0;
2105  mean_error=0.0;
2106  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
2107  image_view=AcquireVirtualCacheView(image,exception);
2108  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
2109  for (y=0; y < (ssize_t) rows; y++)
2110  {
2111  const IndexPacket
2112  *magick_restrict indexes,
2113  *magick_restrict reconstruct_indexes;
2114 
2115  const PixelPacket
2116  *magick_restrict p,
2117  *magick_restrict q;
2118 
2119  ssize_t
2120  x;
2121 
2122  p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
2123  q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
2124  if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL))
2125  break;
2126  indexes=GetCacheViewVirtualIndexQueue(image_view);
2127  reconstruct_indexes=GetCacheViewVirtualIndexQueue(reconstruct_view);
2128  for (x=0; x < (ssize_t) columns; x++)
2129  {
2130  MagickRealType
2131  distance;
2132 
2133  distance=fabs((double) GetPixelRed(p)-(double) GetPixelRed(q));
2134  mean_error_per_pixel+=distance;
2135  mean_error+=distance*distance;
2136  if (distance > maximum_error)
2137  maximum_error=distance;
2138  area++;
2139  distance=fabs((double) GetPixelGreen(p)-(double) GetPixelGreen(q));
2140  mean_error_per_pixel+=distance;
2141  mean_error+=distance*distance;
2142  if (distance > maximum_error)
2143  maximum_error=distance;
2144  area++;
2145  distance=fabs((double) GetPixelBlue(p)-(double) GetPixelBlue(q));
2146  mean_error_per_pixel+=distance;
2147  mean_error+=distance*distance;
2148  if (distance > maximum_error)
2149  maximum_error=distance;
2150  area++;
2151  if (image->matte != MagickFalse)
2152  {
2153  distance=fabs((double) GetPixelOpacity(p)-(double)
2154  GetPixelOpacity(q));
2155  mean_error_per_pixel+=distance;
2156  mean_error+=distance*distance;
2157  if (distance > maximum_error)
2158  maximum_error=distance;
2159  area++;
2160  }
2161  if ((image->colorspace == CMYKColorspace) &&
2162  (reconstruct_image->colorspace == CMYKColorspace))
2163  {
2164  distance=fabs((double) GetPixelIndex(indexes+x)-(double)
2165  GetPixelIndex(reconstruct_indexes+x));
2166  mean_error_per_pixel+=distance;
2167  mean_error+=distance*distance;
2168  if (distance > maximum_error)
2169  maximum_error=distance;
2170  area++;
2171  }
2172  p++;
2173  q++;
2174  }
2175  }
2176  reconstruct_view=DestroyCacheView(reconstruct_view);
2177  image_view=DestroyCacheView(image_view);
2178  gamma=MagickSafeReciprocal(area);
2179  image->error.mean_error_per_pixel=gamma*mean_error_per_pixel;
2180  image->error.normalized_mean_error=gamma*QuantumScale*QuantumScale*mean_error;
2181  image->error.normalized_maximum_error=QuantumScale*maximum_error;
2182  status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
2183  return(status);
2184 }
2185 
2186 /*
2187 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2188 % %
2189 % %
2190 % %
2191 % S i m i l a r i t y I m a g e %
2192 % %
2193 % %
2194 % %
2195 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2196 %
2197 % SimilarityImage() compares the reference image of the image and returns the
2198 % best match offset. In addition, it returns a similarity image such that an
2199 % exact match location is completely white and if none of the pixels match,
2200 % black, otherwise some gray level in-between.
2201 %
2202 % The format of the SimilarityImageImage method is:
2203 %
2204 % Image *SimilarityImage(const Image *image,const Image *reference,
2205 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
2206 %
2207 % A description of each parameter follows:
2208 %
2209 % o image: the image.
2210 %
2211 % o reference: find an area of the image that closely resembles this image.
2212 %
2213 % o the best match offset of the reference image within the image.
2214 %
2215 % o similarity: the computed similarity between the images.
2216 %
2217 % o exception: return any errors or warnings in this structure.
2218 %
2219 */
2220 
2221 static double GetSimilarityMetric(const Image *image,
2222  const Image *reconstruct_image,const MetricType metric,
2223  const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
2224 {
2225  double
2226  *channel_similarity,
2227  similarity = 0.0;
2228 
2230  *sans_exception = AcquireExceptionInfo();
2231 
2232  Image
2233  *similarity_image;
2234 
2235  MagickBooleanType
2236  status = MagickTrue;
2237 
2239  geometry;
2240 
2241  size_t
2242  length = CompositeChannels+1UL;
2243 
2244  SetGeometry(reconstruct_image,&geometry);
2245  geometry.x=x_offset;
2246  geometry.y=y_offset;
2247  similarity_image=CropImage(image,&geometry,sans_exception);
2248  sans_exception=DestroyExceptionInfo(sans_exception);
2249  if (similarity_image == (Image *) NULL)
2250  return(NAN);
2251  /*
2252  Get image distortion.
2253  */
2254  channel_similarity=(double *) AcquireQuantumMemory(length,
2255  sizeof(*channel_similarity));
2256  if (channel_similarity == (double *) NULL)
2257  ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
2258  (void) memset(channel_similarity,0,length*sizeof(*channel_similarity));
2259  switch (metric)
2260  {
2261  case AbsoluteErrorMetric:
2262  {
2263  status=GetAESimilarity(similarity_image,reconstruct_image,
2264  CompositeChannels,channel_similarity,exception);
2265  break;
2266  }
2267  case FuzzErrorMetric:
2268  {
2269  status=GetFUZZSimilarity(similarity_image,reconstruct_image,
2270  CompositeChannels,channel_similarity,exception);
2271  break;
2272  }
2273  case MeanAbsoluteErrorMetric:
2274  {
2275  status=GetMAESimilarity(similarity_image,reconstruct_image,
2276  CompositeChannels,channel_similarity,exception);
2277  break;
2278  }
2279  case MeanErrorPerPixelMetric:
2280  {
2281  status=GetMEPPSimilarity(similarity_image,reconstruct_image,
2282  CompositeChannels,channel_similarity,exception);
2283  break;
2284  }
2285  case MeanSquaredErrorMetric:
2286  {
2287  status=GetMSESimilarity(similarity_image,reconstruct_image,
2288  CompositeChannels,channel_similarity,exception);
2289  break;
2290  }
2291  case NormalizedCrossCorrelationErrorMetric:
2292  {
2293  status=GetNCCSimilarity(similarity_image,reconstruct_image,
2294  CompositeChannels,channel_similarity,exception);
2295  break;
2296  }
2297  case PeakAbsoluteErrorMetric:
2298  {
2299  status=GetPASimilarity(similarity_image,reconstruct_image,
2300  CompositeChannels,channel_similarity,exception);
2301  break;
2302  }
2303  case PeakSignalToNoiseRatioMetric:
2304  {
2305  status=GetPSNRSimilarity(similarity_image,reconstruct_image,
2306  CompositeChannels,channel_similarity,exception);
2307  break;
2308  }
2309  case PerceptualHashErrorMetric:
2310  {
2311  status=GetPHASHSimilarity(similarity_image,reconstruct_image,
2312  CompositeChannels,channel_similarity,exception);
2313  break;
2314  }
2315  case PixelDifferenceCountErrorMetric:
2316  {
2317  status=GetPDCSimilarity(similarity_image,reconstruct_image,
2318  CompositeChannels,channel_similarity,exception);
2319  break;
2320  }
2321  case RootMeanSquaredErrorMetric:
2322  case UndefinedErrorMetric:
2323  default:
2324  {
2325  status=GetRMSESimilarity(similarity_image,reconstruct_image,
2326  CompositeChannels,channel_similarity,exception);
2327  break;
2328  }
2329  }
2330  similarity_image=DestroyImage(similarity_image);
2331  similarity=channel_similarity[CompositeChannels];
2332  channel_similarity=(double *) RelinquishMagickMemory(channel_similarity);
2333  if (status == MagickFalse)
2334  return(NAN);
2335  return(similarity);
2336 }
2337 
2338 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
2339  RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
2340 {
2341  Image
2342  *similarity_image;
2343 
2344  similarity_image=SimilarityMetricImage(image,reference,
2345  RootMeanSquaredErrorMetric,offset,similarity_metric,exception);
2346  return(similarity_image);
2347 }
2348 
2349 MagickExport Image *SimilarityMetricImage(Image *image,const Image *reconstruct,
2350  const MetricType metric,RectangleInfo *offset,double *similarity_metric,
2351  ExceptionInfo *exception)
2352 {
2353 #define SimilarityImageTag "Similarity/Image"
2354 
2355  typedef struct
2356  {
2357  double
2358  similarity;
2359 
2360  ssize_t
2361  x,
2362  y;
2363  } SimilarityInfo;
2364 
2365  CacheView
2366  *similarity_view;
2367 
2368  const char
2369  *artifact;
2370 
2371  double
2372  similarity_threshold;
2373 
2374  Image
2375  *similarity_image = (Image *) NULL;
2376 
2377  MagickBooleanType
2378  status;
2379 
2380  MagickOffsetType
2381  progress;
2382 
2383  SimilarityInfo
2384  similarity_info = { 0 };
2385 
2386  ssize_t
2387  y;
2388 
2389  assert(image != (const Image *) NULL);
2390  assert(image->signature == MagickCoreSignature);
2391  assert(exception != (ExceptionInfo *) NULL);
2392  assert(exception->signature == MagickCoreSignature);
2393  assert(offset != (RectangleInfo *) NULL);
2394  if (IsEventLogging() != MagickFalse)
2395  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2396  SetGeometry(reconstruct,offset);
2397  *similarity_metric=0.0;
2398  offset->x=0;
2399  offset->y=0;
2400  if (ValidateImageMorphology(image,reconstruct) == MagickFalse)
2401  ThrowImageException(ImageError,"ImageMorphologyDiffers");
2402  if ((image->columns < reconstruct->columns) ||
2403  (image->rows < reconstruct->rows))
2404  {
2405  (void) ThrowMagickException(&image->exception,GetMagickModule(),
2406  OptionWarning,"GeometryDoesNotContainImage","`%s'",image->filename);
2407  return((Image *) NULL);
2408  }
2409  similarity_image=CloneImage(image,image->columns-reconstruct->columns+1,
2410  image->rows-reconstruct->rows+1,MagickTrue,exception);
2411  if (similarity_image == (Image *) NULL)
2412  return((Image *) NULL);
2413  similarity_image->depth=32;
2414  similarity_image->colorspace=GRAYColorspace;
2415  similarity_image->matte=MagickFalse;
2416  status=SetImageStorageClass(similarity_image,DirectClass);
2417  if (status == MagickFalse)
2418  {
2419  InheritException(exception,&similarity_image->exception);
2420  return(DestroyImage(similarity_image));
2421  }
2422  /*
2423  Measure similarity of reconstruction image against image.
2424  */
2425  similarity_threshold=DefaultSimilarityThreshold;
2426  artifact=GetImageArtifact(image,"compare:similarity-threshold");
2427  if (artifact != (const char *) NULL)
2428  similarity_threshold=StringToDouble(artifact,(char **) NULL);
2429  status=MagickTrue;
2430  similarity_info.similarity=GetSimilarityMetric(image,reconstruct,metric,
2431  similarity_info.x,similarity_info.y,exception);
2432  progress=0;
2433  similarity_view=AcquireVirtualCacheView(similarity_image,exception);
2434 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2435  #pragma omp parallel for schedule(static) shared(status,similarity_info) \
2436  magick_number_threads(image,reconstruct,similarity_image->rows << 2,1)
2437 #endif
2438  for (y=0; y < (ssize_t) similarity_image->rows; y++)
2439  {
2440  double
2441  similarity;
2442 
2443  MagickBooleanType
2444  threshold_trigger = MagickFalse;
2445 
2446  PixelPacket
2447  *magick_restrict q;
2448 
2449  SimilarityInfo
2450  channel_info = similarity_info;
2451 
2452  ssize_t
2453  x;
2454 
2455  if (status == MagickFalse)
2456  continue;
2457  if (threshold_trigger != MagickFalse)
2458  continue;
2459  q=QueueCacheViewAuthenticPixels(similarity_view,0,y,
2460  similarity_image->columns,1,exception);
2461  if (q == (PixelPacket *) NULL)
2462  {
2463  status=MagickFalse;
2464  continue;
2465  }
2466  for (x=0; x < (ssize_t) similarity_image->columns; x++)
2467  {
2468  MagickBooleanType
2469  update = MagickFalse;
2470 
2471  similarity=GetSimilarityMetric(image,reconstruct,metric,x,y,exception);
2472  switch (metric)
2473  {
2474  case NormalizedCrossCorrelationErrorMetric:
2475  case PeakSignalToNoiseRatioMetric:
2476  {
2477  if (similarity > channel_info.similarity)
2478  update=MagickTrue;
2479  break;
2480  }
2481  default:
2482  {
2483  if (similarity < channel_info.similarity)
2484  update=MagickTrue;
2485  break;
2486  }
2487  }
2488  if (update != MagickFalse)
2489  {
2490  channel_info.similarity=similarity;
2491  channel_info.x=x;
2492  channel_info.y=y;
2493  }
2494  switch (metric)
2495  {
2496  case NormalizedCrossCorrelationErrorMetric:
2497  case PeakSignalToNoiseRatioMetric:
2498  {
2499  SetPixelRed(q,ClampToQuantum((double) QuantumRange*similarity));
2500  break;
2501  }
2502  default:
2503  {
2504  SetPixelRed(q,ClampToQuantum((double) QuantumRange*(1.0-similarity)));
2505  break;
2506  }
2507  }
2508  SetPixelGreen(q,GetPixelRed(q));
2509  SetPixelBlue(q,GetPixelRed(q));
2510  q++;
2511  }
2512 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2513  #pragma omp critical (MagickCore_SimilarityMetricImage)
2514 #endif
2515  switch (metric)
2516  {
2517  case NormalizedCrossCorrelationErrorMetric:
2518  case PeakSignalToNoiseRatioMetric:
2519  {
2520  if (similarity_threshold != DefaultSimilarityThreshold)
2521  if (channel_info.similarity >= similarity_threshold)
2522  threshold_trigger=MagickTrue;
2523  if (channel_info.similarity >= similarity_info.similarity)
2524  similarity_info=channel_info;
2525  break;
2526  }
2527  default:
2528  {
2529  if (similarity_threshold != DefaultSimilarityThreshold)
2530  if (channel_info.similarity < similarity_threshold)
2531  threshold_trigger=MagickTrue;
2532  if (channel_info.similarity < similarity_info.similarity)
2533  similarity_info=channel_info;
2534  break;
2535  }
2536  }
2537  if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
2538  status=MagickFalse;
2539  if (image->progress_monitor != (MagickProgressMonitor) NULL)
2540  {
2541  MagickBooleanType
2542  proceed;
2543 
2544  progress++;
2545  proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
2546  if (proceed == MagickFalse)
2547  status=MagickFalse;
2548  }
2549  }
2550  similarity_view=DestroyCacheView(similarity_view);
2551  if (status == MagickFalse)
2552  similarity_image=DestroyImage(similarity_image);
2553  *similarity_metric=similarity_info.similarity;
2554  if (fabs(*similarity_metric) < MagickEpsilon)
2555  *similarity_metric=0.0;
2556  offset->x=similarity_info.x;
2557  offset->y=similarity_info.y;
2558  (void) FormatImageProperty((Image *) image,"similarity","%.*g",
2559  GetMagickPrecision(),*similarity_metric);
2560  (void) FormatImageProperty((Image *) image,"similarity.offset.x","%.*g",
2561  GetMagickPrecision(),(double) offset->x);
2562  (void) FormatImageProperty((Image *) image,"similarity.offset.y","%.*g",
2563  GetMagickPrecision(),(double) offset->y);
2564  return(similarity_image);
2565 }
Definition: image.h:133