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