Coverage Report

Created: 2026-11-12 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/imagemagick/MagickCore/compare.c
Line
Count
Source
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         %
21
%  dedicated to making software imaging solutions freely available.           %
22
%                                                                             %
23
%  You may not use this file except in compliance with the License.  You may  %
24
%  obtain a copy of the License at                                            %
25
%                                                                             %
26
%    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 "MagickCore/studio.h"
44
#include "MagickCore/artifact.h"
45
#include "MagickCore/attribute.h"
46
#include "MagickCore/cache-view.h"
47
#include "MagickCore/channel.h"
48
#include "MagickCore/client.h"
49
#include "MagickCore/color.h"
50
#include "MagickCore/color-private.h"
51
#include "MagickCore/colorspace.h"
52
#include "MagickCore/colorspace-private.h"
53
#include "MagickCore/compare.h"
54
#include "MagickCore/compare-private.h"
55
#include "MagickCore/composite-private.h"
56
#include "MagickCore/constitute.h"
57
#include "MagickCore/distort.h"
58
#include "MagickCore/exception-private.h"
59
#include "MagickCore/enhance.h"
60
#include "MagickCore/fourier.h"
61
#include "MagickCore/geometry.h"
62
#include "MagickCore/image-private.h"
63
#include "MagickCore/list.h"
64
#include "MagickCore/log.h"
65
#include "MagickCore/memory_.h"
66
#include "MagickCore/monitor.h"
67
#include "MagickCore/monitor-private.h"
68
#include "MagickCore/option.h"
69
#include "MagickCore/pixel-accessor.h"
70
#include "MagickCore/property.h"
71
#include "MagickCore/registry.h"
72
#include "MagickCore/resource_.h"
73
#include "MagickCore/string_.h"
74
#include "MagickCore/statistic.h"
75
#include "MagickCore/statistic-private.h"
76
#include "MagickCore/string-private.h"
77
#include "MagickCore/thread-private.h"
78
#include "MagickCore/threshold.h"
79
#include "MagickCore/transform.h"
80
#include "MagickCore/utility.h"
81
#include "MagickCore/version.h"
82

83
/*
84
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
85
%                                                                             %
86
%                                                                             %
87
%                                                                             %
88
%   C o m p a r e I m a g e s                                                 %
89
%                                                                             %
90
%                                                                             %
91
%                                                                             %
92
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
93
%
94
%  CompareImages() compares one or more pixel channels of an image to a
95
%  reconstructed image and returns the difference image.
96
%
97
%  The format of the CompareImages method is:
98
%
99
%      Image *CompareImages(const Image *image,const Image *reconstruct_image,
100
%        const MetricType metric,double *distortion,ExceptionInfo *exception)
101
%
102
%  A description of each parameter follows:
103
%
104
%    o image: the image.
105
%
106
%    o reconstruct_image: the reconstruction image.
107
%
108
%    o metric: the metric.
109
%
110
%    o distortion: the computed distortion between the images.
111
%
112
%    o exception: return any errors or warnings in this structure.
113
%
114
*/
115
MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
116
  const MetricType metric,double *distortion,ExceptionInfo *exception)
117
0
{
118
0
  CacheView
119
0
    *highlight_view,
120
0
    *image_view,
121
0
    *reconstruct_view;
122
123
0
  const char
124
0
    *artifact;
125
126
0
  Image
127
0
    *clone_image,
128
0
    *difference_image,
129
0
    *highlight_image;
130
131
0
  MagickBooleanType
132
0
    status = MagickTrue;
133
134
0
  PixelInfo
135
0
    highlight,
136
0
    lowlight,
137
0
    masklight;
138
139
0
  RectangleInfo
140
0
    geometry;
141
142
0
  size_t
143
0
    columns,
144
0
    rows;
145
146
0
  ssize_t
147
0
    y;
148
149
0
  assert(image != (Image *) NULL);
150
0
  assert(image->signature == MagickCoreSignature);
151
0
  assert(reconstruct_image != (const Image *) NULL);
152
0
  assert(reconstruct_image->signature == MagickCoreSignature);
153
0
  assert(distortion != (double *) NULL);
154
0
  if (IsEventLogging() != MagickFalse)
155
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
156
0
  *distortion=0.0;
157
0
  status=GetImageDistortion(image,reconstruct_image,metric,distortion,
158
0
    exception);
159
0
  if (status == MagickFalse)
160
0
    return((Image *) NULL);
161
0
  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
162
0
  SetGeometry(image,&geometry);
163
0
  geometry.width=columns;
164
0
  geometry.height=rows;
165
0
  clone_image=CloneImage(image,0,0,MagickTrue,exception);
166
0
  if (clone_image == (Image *) NULL)
167
0
    return((Image *) NULL);
168
0
  (void) SetImageMask(clone_image,ReadPixelMask,(Image *) NULL,exception);
169
0
  difference_image=ExtentImage(clone_image,&geometry,exception);
170
0
  clone_image=DestroyImage(clone_image);
171
0
  if (difference_image == (Image *) NULL)
172
0
    return((Image *) NULL);
173
0
  (void) ResetImagePage(difference_image,"0x0+0+0");
174
0
  (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
175
0
  highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
176
0
  if (highlight_image == (Image *) NULL)
177
0
    {
178
0
      difference_image=DestroyImage(difference_image);
179
0
      return((Image *) NULL);
180
0
    }
181
0
  status=SetImageStorageClass(highlight_image,DirectClass,exception);
182
0
  if (status == MagickFalse)
183
0
    {
184
0
      difference_image=DestroyImage(difference_image);
185
0
      highlight_image=DestroyImage(highlight_image);
186
0
      return((Image *) NULL);
187
0
    }
188
0
  (void) SetImageMask(highlight_image,ReadPixelMask,(Image *) NULL,exception);
189
0
  (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
190
0
  (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
191
0
  artifact=GetImageArtifact(image,"compare:highlight-color");
192
0
  if (artifact != (const char *) NULL)
193
0
    (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
194
0
  (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
195
0
  artifact=GetImageArtifact(image,"compare:lowlight-color");
196
0
  if (artifact != (const char *) NULL)
197
0
    (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
198
0
  (void) QueryColorCompliance("#888888cc",AllCompliance,&masklight,exception);
199
0
  artifact=GetImageArtifact(image,"compare:masklight-color");
200
0
  if (artifact != (const char *) NULL)
201
0
    (void) QueryColorCompliance(artifact,AllCompliance,&masklight,exception);
202
  /*
203
    Generate difference image.
204
  */
205
0
  image_view=AcquireVirtualCacheView(image,exception);
206
0
  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
207
0
  highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
208
#if defined(MAGICKCORE_OPENMP_SUPPORT)
209
  #pragma omp parallel for schedule(static) shared(status) \
210
    magick_number_threads(image,highlight_image,rows,1)
211
#endif
212
0
  for (y=0; y < (ssize_t) rows; y++)
213
0
  {
214
0
    const Quantum
215
0
      *magick_restrict p,
216
0
      *magick_restrict q;
217
218
0
    MagickBooleanType
219
0
      sync;
220
221
0
    Quantum
222
0
      *magick_restrict r;
223
224
0
    ssize_t
225
0
      x;
226
227
0
    if (status == MagickFalse)
228
0
      continue;
229
0
    p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
230
0
    q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
231
0
    r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
232
0
    if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
233
0
        (r == (Quantum *) NULL))
234
0
      {
235
0
        status=MagickFalse;
236
0
        continue;
237
0
      }
238
0
    for (x=0; x < (ssize_t) columns; x++)
239
0
    {
240
0
      if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
241
0
          (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
242
0
        {
243
0
          SetPixelViaPixelInfo(highlight_image,&masklight,r);
244
0
          p+=(ptrdiff_t) GetPixelChannels(image);
245
0
          q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
246
0
          r+=(ptrdiff_t) GetPixelChannels(highlight_image);
247
0
          continue;
248
0
        }
249
0
      if (IsFuzzyEquivalencePixel(image,p,reconstruct_image,q) == MagickFalse)
250
0
        SetPixelViaPixelInfo(highlight_image,&highlight,r);
251
0
      else
252
0
        SetPixelViaPixelInfo(highlight_image,&lowlight,r);
253
0
      p+=(ptrdiff_t) GetPixelChannels(image);
254
0
      q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
255
0
      r+=(ptrdiff_t) GetPixelChannels(highlight_image);
256
0
    }
257
0
    sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
258
0
    if (sync == MagickFalse)
259
0
      status=MagickFalse;
260
0
  }
261
0
  highlight_view=DestroyCacheView(highlight_view);
262
0
  reconstruct_view=DestroyCacheView(reconstruct_view);
263
0
  image_view=DestroyCacheView(image_view);
264
0
  if ((status != MagickFalse) && (difference_image != (Image *) NULL))
265
0
    status=CompositeImage(difference_image,highlight_image,image->compose,
266
0
      MagickTrue,0,0,exception);
267
0
  highlight_image=DestroyImage(highlight_image);
268
0
  if (status == MagickFalse)
269
0
    difference_image=DestroyImage(difference_image);
270
0
  return(difference_image);
271
0
}
272

273
/*
274
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
275
%                                                                             %
276
%                                                                             %
277
%                                                                             %
278
%   G e t I m a g e D i s t o r t i o n                                       %
279
%                                                                             %
280
%                                                                             %
281
%                                                                             %
282
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
283
%
284
%  GetImageDistortion() compares one or more pixel channels of an image to a
285
%  reconstructed image and returns the specified distortion metric.
286
%
287
%  The format of the GetImageDistortion method is:
288
%
289
%      MagickBooleanType GetImageDistortion(const Image *image,
290
%        const Image *reconstruct_image,const MetricType metric,
291
%        double *distortion,ExceptionInfo *exception)
292
%
293
%  A description of each parameter follows:
294
%
295
%    o image: the image.
296
%
297
%    o reconstruct_image: the reconstruction image.
298
%
299
%    o metric: the metric.
300
%
301
%    o distortion: the computed distortion between the images.
302
%
303
%    o exception: return any errors or warnings in this structure.
304
%
305
*/
306
307
static MagickBooleanType GetAESimilarity(const Image *image,
308
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
309
0
{
310
0
  CacheView
311
0
    *image_view,
312
0
    *reconstruct_view;
313
314
0
  double
315
0
    area,
316
0
    fuzz;
317
318
0
  MagickBooleanType
319
0
    status = MagickTrue;
320
321
0
  size_t
322
0
    columns,
323
0
    rows;
324
325
0
  ssize_t
326
0
    channels = 0,
327
0
    k,
328
0
    y;
329
330
  /*
331
    Compute the absolute error similarity.
332
  */
333
0
  fuzz=GetFuzzyColorDistance(image,reconstruct_image);
334
0
  (void) memset(similarity,0,(MaxPixelChannels+1)*sizeof(*similarity));
335
0
  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
336
0
  image_view=AcquireVirtualCacheView(image,exception);
337
0
  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
338
#if defined(MAGICKCORE_OPENMP_SUPPORT)
339
  #pragma omp parallel for schedule(static) shared(similarity,status) \
340
    magick_number_threads(image,image,rows,1)
341
#endif
342
0
  for (y=0; y < (ssize_t) rows; y++)
343
0
  {
344
0
    const Quantum
345
0
      *magick_restrict p,
346
0
      *magick_restrict q;
347
348
0
    double
349
0
      channel_similarity[MaxPixelChannels+1] = { 0.0 };
350
351
0
    ssize_t
352
0
      x;
353
354
0
    if (status == MagickFalse)
355
0
      continue;
356
0
    p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
357
0
    q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
358
0
    if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
359
0
      {
360
0
        status=MagickFalse;
361
0
        continue;
362
0
      }
363
0
    for (x=0; x < (ssize_t) columns; x++)
364
0
    {
365
0
      double
366
0
        Da,
367
0
        Sa;
368
369
0
      ssize_t
370
0
        i;
371
372
0
      if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
373
0
          (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
374
0
        {
375
0
          p+=(ptrdiff_t) GetPixelChannels(image);
376
0
          q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
377
0
          continue;
378
0
        }
379
0
      Sa=QuantumScale*(double) GetPixelAlpha(image,p);
380
0
      Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
381
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
382
0
      {
383
0
        double
384
0
          error;
385
386
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
387
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
388
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
389
0
          channel);
390
0
        if (((traits & UpdatePixelTrait) == 0) ||
391
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
392
0
          continue;
393
0
        if (channel == AlphaPixelChannel)
394
0
          error=(double) p[i]-(double) GetPixelChannel(reconstruct_image,
395
0
            channel,q);
396
0
        else
397
0
          error=Sa*(double) p[i]-Da*(double) GetPixelChannel(reconstruct_image,channel,
398
0
            q);
399
0
        if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
400
0
          {
401
0
            double ae = fabs(QuantumScale*error);
402
0
            channel_similarity[i]+=ae;
403
0
            channel_similarity[CompositePixelChannel]+=ae;
404
0
          }
405
0
      }
406
0
      p+=(ptrdiff_t) GetPixelChannels(image);
407
0
      q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
408
0
    }
409
#if defined(MAGICKCORE_OPENMP_SUPPORT)
410
    #pragma omp critical (MagickCore_GetAESimilarity)
411
#endif
412
0
    {
413
0
      ssize_t
414
0
        j;
415
416
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
417
0
      {
418
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
419
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
420
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
421
0
          channel);
422
0
        if (((traits & UpdatePixelTrait) == 0) ||
423
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
424
0
          continue;
425
0
        similarity[j]+=channel_similarity[j];
426
0
      }
427
0
      similarity[CompositePixelChannel]+=
428
0
        channel_similarity[CompositePixelChannel];
429
0
    }
430
0
  }
431
0
  reconstruct_view=DestroyCacheView(reconstruct_view);
432
0
  image_view=DestroyCacheView(image_view);
433
0
  area=MagickSafeReciprocal((double) columns*(double) rows);
434
0
  for (k=0; k < (ssize_t) GetPixelChannels(image); k++)
435
0
  {
436
0
    PixelChannel channel = GetPixelChannelChannel(image,k);
437
0
    PixelTrait traits = GetPixelChannelTraits(image,channel);
438
0
    PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
439
0
      channel);
440
0
    if (((traits & UpdatePixelTrait) == 0) ||
441
0
        ((reconstruct_traits & UpdatePixelTrait) == 0))
442
0
      continue;
443
0
    similarity[k]*=area;
444
0
    channels++;
445
0
  }
446
0
  similarity[CompositePixelChannel]*=area;
447
0
  if (channels != 0)
448
0
    similarity[CompositePixelChannel]/=(double) channels;
449
0
  return(status);
450
0
}
451
452
static MagickBooleanType GetDPCSimilarity(const Image *image,
453
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
454
0
{
455
0
#define SimilarityImageTag  "Similarity/Image"
456
457
0
  CacheView
458
0
    *image_view,
459
0
    *reconstruct_view;
460
461
0
  ChannelStatistics
462
0
    *image_statistics,
463
0
    *reconstruct_statistics;
464
465
0
  double
466
0
    norm[MaxPixelChannels+1] = { 0.0 },
467
0
    reconstruct_norm[MaxPixelChannels+1] = { 0.0 };
468
469
0
  MagickBooleanType
470
0
    status = MagickTrue;
471
472
0
  MagickOffsetType
473
0
    progress = 0;
474
475
0
  size_t
476
0
    columns,
477
0
    rows;
478
479
0
  ssize_t
480
0
    k,
481
0
    y;
482
483
  /*
484
    Compute the dot product correlation similarity.
485
  */
486
0
  image_statistics=GetImageStatistics(image,exception);
487
0
  reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
488
0
  if ((image_statistics == (ChannelStatistics *) NULL) ||
489
0
      (reconstruct_statistics == (ChannelStatistics *) NULL))
490
0
    {
491
0
      if (image_statistics != (ChannelStatistics *) NULL)
492
0
        image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
493
0
          image_statistics);
494
0
      if (reconstruct_statistics != (ChannelStatistics *) NULL)
495
0
        reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
496
0
          reconstruct_statistics);
497
0
      return(MagickFalse);
498
0
    }
499
0
  (void) memset(similarity,0,(MaxPixelChannels+1)*sizeof(*similarity));
500
0
  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
501
0
  image_view=AcquireVirtualCacheView(image,exception);
502
0
  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
503
#if defined(MAGICKCORE_OPENMP_SUPPORT)
504
  #pragma omp parallel for schedule(static) shared(norm,reconstruct_norm,similarity,status) \
505
    magick_number_threads(image,image,rows,1)
506
#endif
507
0
  for (y=0; y < (ssize_t) rows; y++)
508
0
  {
509
0
    const Quantum
510
0
      *magick_restrict p,
511
0
      *magick_restrict q;
512
513
0
    double
514
0
      channel_norm[MaxPixelChannels+1] = { 0.0 },
515
0
      channel_reconstruct_norm[MaxPixelChannels+1] = { 0.0 },
516
0
      channel_similarity[MaxPixelChannels+1] = { 0.0 };
517
518
0
    ssize_t
519
0
      x;
520
521
0
    p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
522
0
    q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
523
0
    if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
524
0
      {
525
0
        status=MagickFalse;
526
0
        continue;
527
0
      }
528
0
    for (x=0; x < (ssize_t) columns; x++)
529
0
    {
530
0
      double
531
0
        Da,
532
0
        Sa;
533
534
0
      ssize_t
535
0
        i;
536
537
0
      if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
538
0
          (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
539
0
        {
540
0
          p+=(ptrdiff_t) GetPixelChannels(image);
541
0
          q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
542
0
          continue;
543
0
        }
544
0
      Sa=QuantumScale*(double) GetPixelAlpha(image,p);
545
0
      Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
546
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
547
0
      {
548
0
        double
549
0
          alpha,
550
0
          beta;
551
552
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
553
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
554
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
555
0
          channel);
556
0
        if (((traits & UpdatePixelTrait) == 0) ||
557
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
558
0
          continue;
559
0
        if (channel == AlphaPixelChannel)
560
0
          {
561
0
            alpha=QuantumScale*((double) p[i]-image_statistics[channel].mean);
562
0
            beta=QuantumScale*((double) GetPixelChannel(reconstruct_image,
563
0
              channel,q)-reconstruct_statistics[channel].mean);
564
0
          }
565
0
        else
566
0
          {
567
0
            alpha=QuantumScale*(Sa*(double) p[i]-
568
0
              image_statistics[channel].mean);
569
0
            beta=QuantumScale*(Da*(double) GetPixelChannel(reconstruct_image,channel,
570
0
              q)-reconstruct_statistics[channel].mean);
571
0
          }
572
0
        channel_similarity[i]+=alpha*beta;
573
0
        channel_norm[i]+=alpha*alpha;
574
0
        channel_reconstruct_norm[i]+=beta*beta;
575
0
      }
576
0
      p+=(ptrdiff_t) GetPixelChannels(image);
577
0
      q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
578
0
    }
579
#if defined(MAGICKCORE_OPENMP_SUPPORT)
580
    #pragma omp critical (MagickCore_GetDPCSimilarity)
581
#endif
582
0
    {
583
0
      ssize_t
584
0
        j;
585
586
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
587
0
      {
588
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
589
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
590
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
591
0
          channel);
592
0
        if (((traits & UpdatePixelTrait) == 0) ||
593
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
594
0
          continue;
595
0
        similarity[j]+=channel_similarity[j];
596
0
        similarity[CompositePixelChannel]+=channel_similarity[j];
597
0
        norm[j]+=channel_norm[j];
598
0
        norm[CompositePixelChannel]+=channel_norm[j];
599
0
        reconstruct_norm[j]+=channel_reconstruct_norm[j];
600
0
        reconstruct_norm[CompositePixelChannel]+=channel_reconstruct_norm[j];
601
0
      }
602
0
    }
603
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
604
0
      {
605
0
        MagickBooleanType
606
0
          proceed;
607
608
#if defined(MAGICKCORE_OPENMP_SUPPORT)
609
        #pragma omp atomic
610
#endif
611
0
        progress++;
612
0
        proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
613
0
        if (proceed == MagickFalse)
614
0
          {
615
0
            status=MagickFalse;
616
0
            continue;
617
0
          }
618
0
      }
619
0
  }
620
0
  reconstruct_view=DestroyCacheView(reconstruct_view);
621
0
  image_view=DestroyCacheView(image_view);
622
  /*
623
    Compute dot product correlation: divide by mean.
624
  */
625
0
  for (k=0; k < (ssize_t) GetPixelChannels(image); k++)
626
0
  {
627
0
    PixelChannel channel = GetPixelChannelChannel(image,k);
628
0
    PixelTrait traits = GetPixelChannelTraits(image,channel);
629
0
    PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
630
0
      channel);
631
0
    if (((traits & UpdatePixelTrait) == 0) ||
632
0
        ((reconstruct_traits & UpdatePixelTrait) == 0))
633
0
      continue;
634
0
    similarity[k]*=MagickSafeReciprocal(sqrt(norm[k]*reconstruct_norm[k]));
635
0
  }
636
0
  similarity[CompositePixelChannel]*=MagickSafeReciprocal(sqrt(
637
0
    norm[CompositePixelChannel]*reconstruct_norm[CompositePixelChannel]));
638
  /*
639
    Free resources.
640
  */
641
0
  reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
642
0
    reconstruct_statistics);
643
0
  image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
644
0
    image_statistics);
645
0
  return(status);
646
0
}
647
648
static MagickBooleanType GetFUZZSimilarity(const Image *image,
649
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
650
0
{
651
0
  CacheView
652
0
    *image_view,
653
0
    *reconstruct_view;
654
655
0
  double
656
0
    area = 0.0,
657
0
    fuzz = 0.0;
658
659
0
  MagickBooleanType
660
0
    status = MagickTrue;
661
662
0
  size_t
663
0
    columns,
664
0
    rows;
665
666
0
  ssize_t
667
0
    k,
668
0
    y;
669
670
  /*
671
    Compute the MSE similarity within tolerance (fuzz).
672
  */
673
0
  fuzz=GetFuzzyColorDistance(image,reconstruct_image);
674
0
  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
675
0
  image_view=AcquireVirtualCacheView(image,exception);
676
0
  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
677
#if defined(MAGICKCORE_OPENMP_SUPPORT)
678
  #pragma omp parallel for schedule(static) shared(area,similarity,status) \
679
    magick_number_threads(image,image,rows,1)
680
#endif
681
0
  for (y=0; y < (ssize_t) rows; y++)
682
0
  {
683
0
    const Quantum
684
0
      *magick_restrict p,
685
0
      *magick_restrict q;
686
687
0
    double
688
0
      channel_area = 0.0,
689
0
      channel_similarity[MaxPixelChannels+1] = { 0.0 };
690
691
0
    ssize_t
692
0
      x;
693
694
0
    if (status == MagickFalse)
695
0
      continue;
696
0
    p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
697
0
    q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
698
0
    if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
699
0
      {
700
0
        status=MagickFalse;
701
0
        continue;
702
0
      }
703
0
    for (x=0; x < (ssize_t) columns; x++)
704
0
    {
705
0
      double
706
0
        Da,
707
0
        Sa;
708
709
0
      ssize_t
710
0
        i;
711
712
0
      if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
713
0
          (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
714
0
        {
715
0
          p+=(ptrdiff_t) GetPixelChannels(image);
716
0
          q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
717
0
          continue;
718
0
        }
719
0
      Sa=QuantumScale*(double) GetPixelAlpha(image,p);
720
0
      Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
721
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
722
0
      {
723
0
        double
724
0
          error;
725
726
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
727
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
728
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
729
0
          channel);
730
0
        if (((traits & UpdatePixelTrait) == 0) ||
731
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
732
0
          continue;
733
0
        if (channel == AlphaPixelChannel)
734
0
          error=(double) p[i]-(double) GetPixelChannel(reconstruct_image,
735
0
            channel,q);
736
0
        else
737
0
          error=Sa*(double) p[i]-Da*(double) GetPixelChannel(reconstruct_image,
738
0
            channel,q);
739
0
        if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
740
0
          {
741
0
            channel_similarity[i]+=QuantumScale*error*QuantumScale*error;
742
0
            channel_similarity[CompositePixelChannel]+=QuantumScale*error*
743
0
              QuantumScale*error;
744
0
            channel_area++;
745
0
          }
746
0
      }
747
0
      p+=(ptrdiff_t) GetPixelChannels(image);
748
0
      q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
749
0
    }
750
#if defined(MAGICKCORE_OPENMP_SUPPORT)
751
    #pragma omp critical (MagickCore_GetFUZZSimilarity)
752
#endif
753
0
    {
754
0
      ssize_t
755
0
        j;
756
757
0
      area+=channel_area;
758
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
759
0
      {
760
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
761
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
762
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
763
0
          channel);
764
0
        if (((traits & UpdatePixelTrait) == 0) ||
765
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
766
0
          continue;
767
0
        similarity[j]+=channel_similarity[j];
768
0
      }
769
0
      similarity[CompositePixelChannel]+=
770
0
        channel_similarity[CompositePixelChannel];
771
0
    }
772
0
  }
773
0
  reconstruct_view=DestroyCacheView(reconstruct_view);
774
0
  image_view=DestroyCacheView(image_view);
775
0
  area=MagickSafeReciprocal(area);
776
0
  for (k=0; k < (ssize_t) GetPixelChannels(image); k++)
777
0
  {
778
0
    PixelChannel channel = GetPixelChannelChannel(image,k);
779
0
    PixelTrait traits = GetPixelChannelTraits(image,channel);
780
0
    PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
781
0
      channel);
782
0
    if (((traits & UpdatePixelTrait) == 0) ||
783
0
        ((reconstruct_traits & UpdatePixelTrait) == 0))
784
0
      continue;
785
0
    similarity[k]*=area;
786
0
  }
787
0
  similarity[CompositePixelChannel]*=area;
788
0
  return(status);
789
0
}
790
791
static MagickBooleanType GetMAESimilarity(const Image *image,
792
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
793
0
{
794
0
  CacheView
795
0
    *image_view,
796
0
    *reconstruct_view;
797
798
0
  double
799
0
    area = 0.0;
800
801
0
  MagickBooleanType
802
0
    status = MagickTrue;
803
804
0
  size_t
805
0
    columns,
806
0
    rows;
807
808
0
  ssize_t
809
0
    channels = 0,
810
0
    k,
811
0
    y;
812
813
  /*
814
    Compute the mean absolute error similarity.
815
  */
816
0
  (void) memset(similarity,0,(MaxPixelChannels+1)*sizeof(*similarity));
817
0
  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
818
0
  image_view=AcquireVirtualCacheView(image,exception);
819
0
  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
820
#if defined(MAGICKCORE_OPENMP_SUPPORT)
821
  #pragma omp parallel for schedule(static) shared(area,similarity,status) \
822
    magick_number_threads(image,image,rows,1)
823
#endif
824
0
  for (y=0; y < (ssize_t) rows; y++)
825
0
  {
826
0
    const Quantum
827
0
      *magick_restrict p,
828
0
      *magick_restrict q;
829
830
0
    double
831
0
      channel_area = 0.0,
832
0
      channel_similarity[MaxPixelChannels+1] = { 0.0 };
833
834
0
    ssize_t
835
0
      x;
836
837
0
    if (status == MagickFalse)
838
0
      continue;
839
0
    p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
840
0
    q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
841
0
    if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
842
0
      {
843
0
        status=MagickFalse;
844
0
        continue;
845
0
      }
846
0
    for (x=0; x < (ssize_t) columns; x++)
847
0
    {
848
0
      double
849
0
        Da,
850
0
        Sa;
851
852
0
      ssize_t
853
0
        i;
854
855
0
      if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
856
0
          (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
857
0
        {
858
0
          p+=(ptrdiff_t) GetPixelChannels(image);
859
0
          q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
860
0
          continue;
861
0
        }
862
0
      Sa=QuantumScale*(double) GetPixelAlpha(image,p);
863
0
      Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
864
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
865
0
      {
866
0
        double
867
0
          error;
868
869
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
870
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
871
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
872
0
          channel);
873
0
        if (((traits & UpdatePixelTrait) == 0) ||
874
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
875
0
          continue;
876
0
        if (channel == AlphaPixelChannel)
877
0
          error=QuantumScale*fabs((double) p[i]-(double) GetPixelChannel(
878
0
            reconstruct_image,channel,q));
879
0
        else
880
0
          error=QuantumScale*fabs(Sa*(double) p[i]-Da*(double)
881
0
            GetPixelChannel(reconstruct_image,channel,q));
882
0
        channel_similarity[i]+=error;
883
0
        channel_similarity[CompositePixelChannel]+=error;
884
0
      }
885
0
      channel_area++;
886
0
      p+=(ptrdiff_t) GetPixelChannels(image);
887
0
      q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
888
0
    }
889
#if defined(MAGICKCORE_OPENMP_SUPPORT)
890
    #pragma omp critical (MagickCore_GetMAESimilarity)
891
#endif
892
0
    {
893
0
      ssize_t
894
0
        j;
895
896
0
      area+=channel_area;
897
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
898
0
      {
899
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
900
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
901
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
902
0
          channel);
903
0
        if (((traits & UpdatePixelTrait) == 0) ||
904
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
905
0
          continue;
906
0
        similarity[j]+=channel_similarity[j];
907
0
      }
908
0
      similarity[CompositePixelChannel]+=
909
0
        channel_similarity[CompositePixelChannel];
910
0
    }
911
0
  }
912
0
  reconstruct_view=DestroyCacheView(reconstruct_view);
913
0
  image_view=DestroyCacheView(image_view);
914
0
  area=MagickSafeReciprocal(area);
915
0
  for (k=0; k < (ssize_t) GetPixelChannels(image); k++)
916
0
  {
917
0
    PixelChannel channel = GetPixelChannelChannel(image,k);
918
0
    PixelTrait traits = GetPixelChannelTraits(image,channel);
919
0
    PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
920
0
      channel);
921
0
    if (((traits & UpdatePixelTrait) == 0) ||
922
0
        ((reconstruct_traits & UpdatePixelTrait) == 0))
923
0
      continue;
924
0
    similarity[k]*=area;
925
0
    channels++;
926
0
  }
927
0
  similarity[CompositePixelChannel]*=area;
928
0
  if (channels != 0)
929
0
    similarity[CompositePixelChannel]/=(double) channels;
930
0
  return(status);
931
0
}
932
933
static MagickBooleanType GetMEPPSimilarity(Image *image,
934
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
935
0
{
936
0
  CacheView
937
0
    *image_view,
938
0
    *reconstruct_view;
939
940
0
  double
941
0
    area = 0.0,
942
0
    maximum_error = -MagickMaximumValue,
943
0
    mean_error = 0.0;
944
945
0
  MagickBooleanType
946
0
    status = MagickTrue;
947
948
0
  size_t
949
0
    columns,
950
0
    rows;
951
952
0
  ssize_t
953
0
    channels = 0,
954
0
    k,
955
0
    y;
956
957
  /*
958
    Compute the mean error per pixel similarity.
959
  */
960
0
  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
961
0
  image_view=AcquireVirtualCacheView(image,exception);
962
0
  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
963
#if defined(MAGICKCORE_OPENMP_SUPPORT)
964
  #pragma omp parallel for schedule(static) shared(area,similarity,maximum_error,mean_error,status) \
965
    magick_number_threads(image,image,rows,1)
966
#endif
967
0
  for (y=0; y < (ssize_t) rows; y++)
968
0
  {
969
0
    const Quantum
970
0
      *magick_restrict p,
971
0
      *magick_restrict q;
972
973
0
    double
974
0
      channel_area = 0.0,
975
0
      channel_similarity[MaxPixelChannels+1] = { 0.0 },
976
0
      channel_maximum_error = maximum_error,
977
0
      channel_mean_error = 0.0;
978
979
0
    ssize_t
980
0
      x;
981
982
0
    if (status == MagickFalse)
983
0
      continue;
984
0
    p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
985
0
    q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
986
0
    if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
987
0
      {
988
0
        status=MagickFalse;
989
0
        continue;
990
0
      }
991
0
    for (x=0; x < (ssize_t) columns; x++)
992
0
    {
993
0
      double
994
0
        Da,
995
0
        Sa;
996
997
0
      ssize_t
998
0
        i;
999
1000
0
      if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1001
0
          (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1002
0
        {
1003
0
          p+=(ptrdiff_t) GetPixelChannels(image);
1004
0
          q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1005
0
          continue;
1006
0
        }
1007
0
      Sa=QuantumScale*(double) GetPixelAlpha(image,p);
1008
0
      Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
1009
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1010
0
      {
1011
0
        double
1012
0
          error;
1013
1014
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
1015
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
1016
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1017
0
          channel);
1018
0
        if (((traits & UpdatePixelTrait) == 0) ||
1019
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
1020
0
          continue;
1021
0
        if (channel == AlphaPixelChannel)
1022
0
          error=QuantumScale*fabs((double) p[i]-(double) GetPixelChannel(
1023
0
            reconstruct_image,channel,q));
1024
0
        else
1025
0
          error=QuantumScale*fabs(Sa*(double) p[i]-Da*(double)
1026
0
            GetPixelChannel(reconstruct_image,channel,q));
1027
0
        channel_similarity[i]+=error;
1028
0
        channel_similarity[CompositePixelChannel]+=error;
1029
0
        channel_mean_error+=error*error;
1030
0
        if (error > channel_maximum_error)
1031
0
          channel_maximum_error=error;
1032
0
      }
1033
0
      channel_area++;
1034
0
      p+=(ptrdiff_t) GetPixelChannels(image);
1035
0
      q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1036
0
    }
1037
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1038
    #pragma omp critical (MagickCore_GetMEPPSimilarity)
1039
#endif
1040
0
    {
1041
0
      ssize_t
1042
0
        j;
1043
1044
0
      area+=channel_area;
1045
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1046
0
      {
1047
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
1048
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
1049
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1050
0
          channel);
1051
0
        if (((traits & UpdatePixelTrait) == 0) ||
1052
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
1053
0
          continue;
1054
0
        similarity[j]+=channel_similarity[j];
1055
0
      }
1056
0
      similarity[CompositePixelChannel]+=
1057
0
        channel_similarity[CompositePixelChannel];
1058
0
      mean_error+=channel_mean_error;
1059
0
      if (channel_maximum_error > maximum_error)
1060
0
        maximum_error=channel_maximum_error;
1061
0
    }
1062
0
  }
1063
0
  reconstruct_view=DestroyCacheView(reconstruct_view);
1064
0
  image_view=DestroyCacheView(image_view);
1065
0
  area=MagickSafeReciprocal(area);
1066
0
  for (k=0; k < (ssize_t) GetPixelChannels(image); k++)
1067
0
  {
1068
0
    PixelChannel channel = GetPixelChannelChannel(image,k);
1069
0
    PixelTrait traits = GetPixelChannelTraits(image,channel);
1070
0
    PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1071
0
      channel);
1072
0
    if (((traits & UpdatePixelTrait) == 0) ||
1073
0
        ((reconstruct_traits & UpdatePixelTrait) == 0))
1074
0
      continue;
1075
0
    similarity[k]*=area;
1076
0
    channels++;
1077
0
  }
1078
0
  similarity[CompositePixelChannel]*=area;
1079
0
  if (channels != 0)
1080
0
    similarity[CompositePixelChannel]/=(double) channels;
1081
0
  image->error.mean_error_per_pixel=(double) QuantumRange*
1082
0
    similarity[CompositePixelChannel];
1083
0
  image->error.normalized_mean_error=mean_error*area;
1084
0
  image->error.normalized_maximum_error=maximum_error;
1085
0
  return(status);
1086
0
}
1087
1088
static MagickBooleanType GetMSESimilarity(const Image *image,
1089
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
1090
0
{
1091
0
  CacheView
1092
0
    *image_view,
1093
0
    *reconstruct_view;
1094
1095
0
  double
1096
0
    area = 0.0;
1097
1098
0
  MagickBooleanType
1099
0
    status = MagickTrue;
1100
1101
0
  size_t
1102
0
    columns,
1103
0
    rows;
1104
1105
0
  ssize_t
1106
0
    channels = 0,
1107
0
    k,
1108
0
    y;
1109
1110
  /*
1111
    Compute the mean sequared error similarity.
1112
  */
1113
0
  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1114
0
  image_view=AcquireVirtualCacheView(image,exception);
1115
0
  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1116
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1117
  #pragma omp parallel for schedule(static) shared(area,similarity,status) \
1118
    magick_number_threads(image,image,rows,1)
1119
#endif
1120
0
  for (y=0; y < (ssize_t) rows; y++)
1121
0
  {
1122
0
    const Quantum
1123
0
      *magick_restrict p,
1124
0
      *magick_restrict q;
1125
1126
0
    double
1127
0
      channel_area = 0.0,
1128
0
      channel_similarity[MaxPixelChannels+1] = { 0.0 };
1129
1130
0
    ssize_t
1131
0
      x;
1132
1133
0
    if (status == MagickFalse)
1134
0
      continue;
1135
0
    p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1136
0
    q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1137
0
    if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1138
0
      {
1139
0
        status=MagickFalse;
1140
0
        continue;
1141
0
      }
1142
0
    for (x=0; x < (ssize_t) columns; x++)
1143
0
    {
1144
0
      double
1145
0
        Da,
1146
0
        Sa;
1147
1148
0
      ssize_t
1149
0
        i;
1150
1151
0
      if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1152
0
          (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1153
0
        {
1154
0
          p+=(ptrdiff_t) GetPixelChannels(image);
1155
0
          q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1156
0
          continue;
1157
0
        }
1158
0
      Sa=QuantumScale*(double) GetPixelAlpha(image,p);
1159
0
      Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
1160
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1161
0
      {
1162
0
        double
1163
0
          error;
1164
1165
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
1166
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
1167
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1168
0
          channel);
1169
0
        if (((traits & UpdatePixelTrait) == 0) ||
1170
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
1171
0
          continue;
1172
0
        if (channel == AlphaPixelChannel)
1173
0
          error=QuantumScale*((double) p[i]-(double) GetPixelChannel(
1174
0
            reconstruct_image,channel,q));
1175
0
        else
1176
0
          error=QuantumScale*(Sa*(double) p[i]-Da*(double) GetPixelChannel(
1177
0
            reconstruct_image,channel,q));
1178
0
        channel_similarity[i]+=error*error;
1179
0
        channel_similarity[CompositePixelChannel]+=error*error;
1180
0
      }
1181
0
      channel_area++;
1182
0
      p+=(ptrdiff_t) GetPixelChannels(image);
1183
0
      q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1184
0
    }
1185
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1186
    #pragma omp critical (MagickCore_GetMSESimilarity)
1187
#endif
1188
0
    {
1189
0
      ssize_t
1190
0
        j;
1191
1192
0
      area+=channel_area;
1193
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1194
0
      {
1195
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
1196
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
1197
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1198
0
          channel);
1199
0
        if (((traits & UpdatePixelTrait) == 0) ||
1200
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
1201
0
          continue;
1202
0
        similarity[j]+=channel_similarity[j];
1203
0
      }
1204
0
      similarity[CompositePixelChannel]+=
1205
0
        channel_similarity[CompositePixelChannel];
1206
0
    }
1207
0
  }
1208
0
  reconstruct_view=DestroyCacheView(reconstruct_view);
1209
0
  image_view=DestroyCacheView(image_view);
1210
0
  area=MagickSafeReciprocal(area);
1211
0
  for (k=0; k < (ssize_t) GetPixelChannels(image); k++)
1212
0
  {
1213
0
    PixelChannel channel = GetPixelChannelChannel(image,k);
1214
0
    PixelTrait traits = GetPixelChannelTraits(image,channel);
1215
0
    PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1216
0
      channel);
1217
0
    if (((traits & UpdatePixelTrait) == 0) ||
1218
0
        ((reconstruct_traits & UpdatePixelTrait) == 0))
1219
0
      continue;
1220
0
    similarity[k]*=area;
1221
0
    channels++;
1222
0
  }
1223
0
  similarity[CompositePixelChannel]*=area;
1224
0
  if (channels != 0)
1225
0
   similarity[CompositePixelChannel]/=(double) channels;
1226
0
  return(status);
1227
0
}
1228
1229
static MagickBooleanType GetNCCSimilarity(const Image *image,
1230
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
1231
0
{
1232
0
  CacheView
1233
0
    *image_view,
1234
0
    *reconstruct_view;
1235
1236
0
  ChannelStatistics
1237
0
    *image_statistics,
1238
0
    *reconstruct_statistics;
1239
1240
0
  double
1241
0
    reconstruct_variance[MaxPixelChannels+1] = { 0.0 },
1242
0
    variance[MaxPixelChannels+1] = { 0.0 };
1243
1244
0
  MagickBooleanType
1245
0
    status = MagickTrue;
1246
1247
0
  MagickOffsetType
1248
0
    progress = 0;
1249
1250
0
  size_t
1251
0
    columns,
1252
0
    rows;
1253
1254
0
  ssize_t
1255
0
    k,
1256
0
    y;
1257
1258
  /*
1259
    Compute the normalized criss-correlation similarity.
1260
  */
1261
0
  image_statistics=GetImageStatistics(image,exception);
1262
0
  reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
1263
0
  if ((image_statistics == (ChannelStatistics *) NULL) ||
1264
0
      (reconstruct_statistics == (ChannelStatistics *) NULL))
1265
0
    {
1266
0
      if (image_statistics != (ChannelStatistics *) NULL)
1267
0
        image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1268
0
          image_statistics);
1269
0
      if (reconstruct_statistics != (ChannelStatistics *) NULL)
1270
0
        reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1271
0
          reconstruct_statistics);
1272
0
      return(MagickFalse);
1273
0
    }
1274
0
  (void) memset(similarity,0,(MaxPixelChannels+1)*sizeof(*similarity));
1275
0
  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1276
0
  image_view=AcquireVirtualCacheView(image,exception);
1277
0
  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1278
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1279
  #pragma omp parallel for schedule(static) shared(variance,reconstruct_variance,similarity,status) \
1280
    magick_number_threads(image,image,rows,1)
1281
#endif
1282
0
  for (y=0; y < (ssize_t) rows; y++)
1283
0
  {
1284
0
    const Quantum
1285
0
      *magick_restrict p,
1286
0
      *magick_restrict q;
1287
1288
0
    double
1289
0
      channel_reconstruct_variance[MaxPixelChannels+1] = { 0.0 },
1290
0
      channel_similarity[MaxPixelChannels+1] = { 0.0 },
1291
0
      channel_variance[MaxPixelChannels+1] = { 0.0 };
1292
1293
0
    ssize_t
1294
0
      x;
1295
1296
0
    p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1297
0
    q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1298
0
    if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1299
0
      {
1300
0
        status=MagickFalse;
1301
0
        continue;
1302
0
      }
1303
0
    for (x=0; x < (ssize_t) columns; x++)
1304
0
    {
1305
0
      double
1306
0
        Da,
1307
0
        Sa;
1308
1309
0
      ssize_t
1310
0
        i;
1311
1312
0
      if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1313
0
          (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1314
0
        {
1315
0
          p+=(ptrdiff_t) GetPixelChannels(image);
1316
0
          q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1317
0
          continue;
1318
0
        }
1319
0
      Sa=QuantumScale*(double) GetPixelAlpha(image,p);
1320
0
      Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
1321
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1322
0
      {
1323
0
        double
1324
0
          alpha,
1325
0
          beta;
1326
1327
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
1328
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
1329
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1330
0
          channel);
1331
0
        if (((traits & UpdatePixelTrait) == 0) ||
1332
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
1333
0
          continue;
1334
0
        if (channel == AlphaPixelChannel)
1335
0
          {
1336
0
            alpha=QuantumScale*((double) p[i]-image_statistics[channel].mean);
1337
0
            beta=QuantumScale*((double) GetPixelChannel(reconstruct_image,
1338
0
              channel,q)-reconstruct_statistics[channel].mean);
1339
0
          }
1340
0
        else
1341
0
          {
1342
0
            alpha=QuantumScale*(Sa*(double) p[i]-
1343
0
              image_statistics[channel].mean);
1344
0
            beta=QuantumScale*(Da*(double) GetPixelChannel(reconstruct_image,channel,
1345
0
              q)-reconstruct_statistics[channel].mean);
1346
0
          }
1347
0
        channel_similarity[i]+=alpha*beta;
1348
0
        channel_variance[i]+=alpha*alpha;
1349
0
        channel_reconstruct_variance[i]+=beta*beta;
1350
0
      }
1351
0
      p+=(ptrdiff_t) GetPixelChannels(image);
1352
0
      q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1353
0
    }
1354
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1355
    #pragma omp critical (MagickCore_GetNCCSimilarity)
1356
#endif
1357
0
    {
1358
0
      ssize_t
1359
0
        j;
1360
1361
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1362
0
      {
1363
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
1364
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
1365
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1366
0
          channel);
1367
0
        if (((traits & UpdatePixelTrait) == 0) ||
1368
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
1369
0
          continue;
1370
0
        similarity[j]+=channel_similarity[j];
1371
0
        similarity[CompositePixelChannel]+=channel_similarity[j];
1372
0
        variance[j]+=channel_variance[j];
1373
0
        variance[CompositePixelChannel]+=channel_variance[j];
1374
0
        reconstruct_variance[j]+=channel_reconstruct_variance[j];
1375
0
        reconstruct_variance[CompositePixelChannel]+=
1376
0
          channel_reconstruct_variance[j];
1377
0
      }
1378
0
    }
1379
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1380
0
      {
1381
0
        MagickBooleanType
1382
0
          proceed;
1383
1384
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1385
        #pragma omp atomic
1386
#endif
1387
0
        progress++;
1388
0
        proceed=SetImageProgress(image,SimilarityImageTag,progress,rows);
1389
0
        if (proceed == MagickFalse)
1390
0
          {
1391
0
            status=MagickFalse;
1392
0
            continue;
1393
0
          }
1394
0
      }
1395
0
  }
1396
0
  reconstruct_view=DestroyCacheView(reconstruct_view);
1397
0
  image_view=DestroyCacheView(image_view);
1398
  /*
1399
    Compute normalized cross correlation: divide by standard deviation.
1400
  */
1401
0
  for (k=0; k < (ssize_t) GetPixelChannels(image); k++)
1402
0
  {
1403
0
    PixelChannel channel = GetPixelChannelChannel(image,k);
1404
0
    PixelTrait traits = GetPixelChannelTraits(image,channel);
1405
0
    PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1406
0
      channel);
1407
0
    if (((traits & UpdatePixelTrait) == 0) ||
1408
0
        ((reconstruct_traits & UpdatePixelTrait) == 0))
1409
0
      continue;
1410
0
    similarity[k]*=MagickSafeReciprocal(sqrt(variance[k])*
1411
0
      sqrt(reconstruct_variance[k]));
1412
0
  }
1413
0
  similarity[CompositePixelChannel]*=MagickSafeReciprocal(sqrt(
1414
0
    variance[CompositePixelChannel])*sqrt(
1415
0
    reconstruct_variance[CompositePixelChannel]));
1416
  /*
1417
    Free resources.
1418
  */
1419
0
  reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1420
0
    reconstruct_statistics);
1421
0
  image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1422
0
    image_statistics);
1423
0
  return(status);
1424
0
}
1425
1426
static MagickBooleanType GetPASimilarity(const Image *image,
1427
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
1428
0
{
1429
0
  CacheView
1430
0
    *image_view,
1431
0
    *reconstruct_view;
1432
1433
0
  MagickBooleanType
1434
0
    status = MagickTrue;
1435
1436
0
  size_t
1437
0
    columns,
1438
0
    rows;
1439
1440
0
  ssize_t
1441
0
    y;
1442
1443
  /*
1444
    Compute the peak absolute similarity.
1445
  */
1446
0
  (void) memset(similarity,0,(MaxPixelChannels+1)*sizeof(*similarity));
1447
0
  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1448
0
  image_view=AcquireVirtualCacheView(image,exception);
1449
0
  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1450
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1451
  #pragma omp parallel for schedule(static) shared(similarity,status) \
1452
    magick_number_threads(image,image,rows,1)
1453
#endif
1454
0
  for (y=0; y < (ssize_t) rows; y++)
1455
0
  {
1456
0
    const Quantum
1457
0
      *magick_restrict p,
1458
0
      *magick_restrict q;
1459
1460
0
    double
1461
0
      channel_similarity[MaxPixelChannels+1] = { 0.0 };
1462
1463
0
    ssize_t
1464
0
      x;
1465
1466
0
    if (status == MagickFalse)
1467
0
      continue;
1468
0
    p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1469
0
    q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1470
0
    if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1471
0
      {
1472
0
        status=MagickFalse;
1473
0
        continue;
1474
0
      }
1475
0
    for (x=0; x < (ssize_t) columns; x++)
1476
0
    {
1477
0
      double
1478
0
        Da,
1479
0
        Sa;
1480
1481
0
      ssize_t
1482
0
        i;
1483
1484
0
      if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1485
0
          (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1486
0
        {
1487
0
          p+=(ptrdiff_t) GetPixelChannels(image);
1488
0
          q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1489
0
          continue;
1490
0
        }
1491
0
      Sa=QuantumScale*(double) GetPixelAlpha(image,p);
1492
0
      Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
1493
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1494
0
      {
1495
0
        double
1496
0
          distance;
1497
1498
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
1499
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
1500
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1501
0
          channel);
1502
0
        if (((traits & UpdatePixelTrait) == 0) ||
1503
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
1504
0
          continue;
1505
0
        if (channel == AlphaPixelChannel)
1506
0
          distance=QuantumScale*fabs((double) p[i]-(double)
1507
0
            GetPixelChannel(reconstruct_image,channel,q));
1508
0
        else
1509
0
          distance=QuantumScale*fabs(Sa*(double) p[i]-Da*(double) GetPixelChannel(
1510
0
            reconstruct_image,channel,q));
1511
0
        if (distance > channel_similarity[i])
1512
0
          channel_similarity[i]=distance;
1513
0
        if (distance > channel_similarity[CompositePixelChannel])
1514
0
          channel_similarity[CompositePixelChannel]=distance;
1515
0
      }
1516
0
      p+=(ptrdiff_t) GetPixelChannels(image);
1517
0
      q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1518
0
    }
1519
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1520
    #pragma omp critical (MagickCore_GetPASimilarity)
1521
#endif
1522
0
    {
1523
0
      ssize_t
1524
0
        j;
1525
1526
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1527
0
      {
1528
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
1529
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
1530
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1531
0
          channel);
1532
0
        if (((traits & UpdatePixelTrait) == 0) ||
1533
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
1534
0
          continue;
1535
0
        if (channel_similarity[j] > similarity[j])
1536
0
          similarity[j]=channel_similarity[j];
1537
0
      }
1538
0
      if (channel_similarity[CompositePixelChannel] > similarity[CompositePixelChannel])
1539
0
        similarity[CompositePixelChannel]=
1540
0
          channel_similarity[CompositePixelChannel];
1541
0
    }
1542
0
  }
1543
0
  reconstruct_view=DestroyCacheView(reconstruct_view);
1544
0
  image_view=DestroyCacheView(image_view);
1545
0
  return(status);
1546
0
}
1547
1548
static MagickBooleanType GetPDCSimilarity(const Image *image,
1549
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
1550
0
{
1551
0
  CacheView
1552
0
    *image_view,
1553
0
    *reconstruct_view;
1554
1555
0
  double
1556
0
    fuzz;
1557
1558
0
  MagickBooleanType
1559
0
    status = MagickTrue;
1560
1561
0
  size_t
1562
0
    columns,
1563
0
    rows;
1564
1565
0
  ssize_t
1566
0
    y;
1567
1568
  /*
1569
    Compute the pixel difference count similarity.
1570
  */
1571
0
  fuzz=GetFuzzyColorDistance(image,reconstruct_image);
1572
0
  (void) memset(similarity,0,(MaxPixelChannels+1)*sizeof(*similarity));
1573
0
  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1574
0
  image_view=AcquireVirtualCacheView(image,exception);
1575
0
  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1576
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1577
  #pragma omp parallel for schedule(static) shared(similarity,status) \
1578
    magick_number_threads(image,image,rows,1)
1579
#endif
1580
0
  for (y=0; y < (ssize_t) rows; y++)
1581
0
  {
1582
0
    const Quantum
1583
0
      *magick_restrict p,
1584
0
      *magick_restrict q;
1585
1586
0
    double
1587
0
      channel_similarity[MaxPixelChannels+1] = { 0.0 };
1588
1589
0
    ssize_t
1590
0
      x;
1591
1592
0
    if (status == MagickFalse)
1593
0
      continue;
1594
0
    p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1595
0
    q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1596
0
    if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
1597
0
      {
1598
0
        status=MagickFalse;
1599
0
        continue;
1600
0
      }
1601
0
    for (x=0; x < (ssize_t) columns; x++)
1602
0
    {
1603
0
      double
1604
0
        Da,
1605
0
        Sa;
1606
1607
0
      size_t
1608
0
        count = 0;
1609
1610
0
      ssize_t
1611
0
        i;
1612
1613
0
      if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
1614
0
          (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
1615
0
        {
1616
0
          p+=(ptrdiff_t) GetPixelChannels(image);
1617
0
          q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1618
0
          continue;
1619
0
        }
1620
0
      Sa=QuantumScale*(double) GetPixelAlpha(image,p);
1621
0
      Da=QuantumScale*(double) GetPixelAlpha(reconstruct_image,q);
1622
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1623
0
      {
1624
0
        double
1625
0
          error;
1626
1627
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
1628
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
1629
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1630
0
          channel);
1631
0
        if (((traits & UpdatePixelTrait) == 0) ||
1632
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
1633
0
          continue;
1634
0
        if (channel == AlphaPixelChannel)
1635
0
          error=(double) p[i]-(double) GetPixelChannel(reconstruct_image,
1636
0
            channel,q);
1637
0
        else
1638
0
          error=Sa*(double) p[i]-Da*(double) GetPixelChannel(reconstruct_image,
1639
0
            channel,q);
1640
0
        if (MagickSafeSignificantError(error*error,fuzz) != MagickFalse)
1641
0
          {
1642
0
            channel_similarity[i]++;
1643
0
            count++;
1644
0
          }
1645
0
      }
1646
0
      if (count != 0)
1647
0
        channel_similarity[CompositePixelChannel]++;
1648
0
      p+=(ptrdiff_t) GetPixelChannels(image);
1649
0
      q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1650
0
    }
1651
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1652
    #pragma omp critical (MagickCore_GetPDCSimilarity)
1653
#endif
1654
0
    {
1655
0
      ssize_t
1656
0
        j;
1657
1658
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1659
0
      {
1660
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
1661
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
1662
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1663
0
          channel);
1664
0
        if (((traits & UpdatePixelTrait) == 0) ||
1665
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
1666
0
          continue;
1667
0
        similarity[j]+=channel_similarity[j];
1668
0
      }
1669
0
      similarity[CompositePixelChannel]+=
1670
0
        channel_similarity[CompositePixelChannel];
1671
0
    }
1672
0
  }
1673
0
  reconstruct_view=DestroyCacheView(reconstruct_view);
1674
0
  image_view=DestroyCacheView(image_view);
1675
0
  return(status);
1676
0
}
1677
1678
static Image *GetEdgeCorrelationSurface(const Image *image,
1679
  ExceptionInfo *exception)
1680
0
{
1681
0
  Image
1682
0
    *surface;
1683
1684
0
  KernelInfo
1685
0
    *kernel;
1686
1687
  /*
1688
    Build a spatial-domain edge/correlation surface using a LoG convolution.
1689
  */
1690
0
  kernel=AcquireKernelInfo("LoG:0,1",exception);
1691
0
  if (kernel == (KernelInfo *) NULL)
1692
0
    return((Image *) NULL);
1693
0
  surface=MorphologyImage(image,ConvolveMorphology,1,kernel,exception);
1694
0
  kernel=DestroyKernelInfo(kernel);
1695
0
  return(surface);
1696
0
}
1697
1698
static MagickBooleanType GetPHASESimilarity(const Image *image,
1699
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
1700
0
{
1701
0
  CacheView
1702
0
    *edge_reconstruct_view,
1703
0
    *edge_view,
1704
0
    *image_view,
1705
0
    *reconstruct_view;
1706
1707
0
  double
1708
0
    area = 0,
1709
0
    correlation[MaxPixelChannels+1] = { 0.0 },
1710
0
    image_sum[MaxPixelChannels+1] = { 0.0 },
1711
0
    image_sum_squared[MaxPixelChannels+1] = { 0.0 },
1712
0
    reconstruct_sum[MaxPixelChannels+1] = { 0.0 },
1713
0
    reconstruct_sum_squared[MaxPixelChannels+1] = { 0.0 };
1714
1715
0
  Image
1716
0
    *edge_image,
1717
0
    *edge_reconstruct;
1718
1719
0
  MagickBooleanType
1720
0
    status = MagickTrue;
1721
1722
0
  size_t
1723
0
    columns = 0,
1724
0
    rows = 0;
1725
1726
0
  ssize_t
1727
0
    channels = 0,
1728
0
    j,
1729
0
    y;
1730
1731
  /*
1732
    Compute a Pearson-correlation similarity between two Laplacian-of-
1733
    Gaussian edge surfaces.
1734
  */
1735
0
  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
1736
0
  edge_image=GetEdgeCorrelationSurface(image,exception);
1737
0
  edge_reconstruct=GetEdgeCorrelationSurface(reconstruct_image,exception);
1738
0
  if ((edge_image == (Image *) NULL) || (edge_reconstruct == (Image *) NULL))
1739
0
    {
1740
0
      if (edge_image != (Image *) NULL)
1741
0
        edge_image=DestroyImage(edge_image);
1742
0
      if (edge_reconstruct != (Image *) NULL)
1743
0
        edge_reconstruct=DestroyImage(edge_reconstruct);
1744
0
      return(MagickFalse);
1745
0
    }
1746
0
  image_view=AcquireVirtualCacheView(image,exception);
1747
0
  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1748
0
  edge_view=AcquireVirtualCacheView(edge_image,exception);
1749
0
  edge_reconstruct_view=AcquireVirtualCacheView(edge_reconstruct,exception);
1750
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1751
  #pragma omp parallel for schedule(static) shared(status) \
1752
    magick_number_threads(edge_image,edge_reconstruct,rows,1)
1753
#endif
1754
0
  for (y=0; y < (ssize_t) rows; y++)
1755
0
  {
1756
0
    const Quantum
1757
0
      *magick_restrict p,
1758
0
      *magick_restrict pm,
1759
0
      *magick_restrict q,
1760
0
      *magick_restrict qm;
1761
1762
0
    double
1763
0
      channel_area = 0,
1764
0
      channel_correlation[MaxPixelChannels+1] = { 0.0 },
1765
0
      channel_image_sum[MaxPixelChannels+1] = { 0.0 },
1766
0
      channel_image_sum_squared[MaxPixelChannels+1] = { 0.0 },
1767
0
      channel_reconstruct_sum[MaxPixelChannels+1] = { 0.0 },
1768
0
      channel_reconstruct_sum_squared[MaxPixelChannels+1] = { 0.0 };
1769
1770
0
    ssize_t
1771
0
      i,
1772
0
      x;
1773
1774
0
    if (status == MagickFalse)
1775
0
      continue;
1776
0
    pm=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1777
0
    qm=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1778
0
    p=GetCacheViewVirtualPixels(edge_view,0,y,columns,1,exception);
1779
0
    q=GetCacheViewVirtualPixels(edge_reconstruct_view,0,y,columns,1,exception);
1780
0
    if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
1781
0
        (pm == (const Quantum *) NULL) || (qm == (const Quantum *) NULL))
1782
0
      {
1783
0
        status=MagickFalse;
1784
0
        continue;
1785
0
      }
1786
0
    for (x=0; x < (ssize_t) columns; x++)
1787
0
    {
1788
0
      if ((GetPixelReadMask(image,pm) <= (QuantumRange/2)) ||
1789
0
          (GetPixelReadMask(reconstruct_image,qm) <= (QuantumRange/2)))
1790
0
        {
1791
0
          p+=(ptrdiff_t) GetPixelChannels(edge_image);
1792
0
          q+=(ptrdiff_t) GetPixelChannels(edge_reconstruct);
1793
0
          pm+=(ptrdiff_t) GetPixelChannels(image);
1794
0
          qm+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1795
0
          continue;
1796
0
        }
1797
0
      for (i=0; i < (ssize_t) GetPixelChannels(edge_image); i++)
1798
0
      {
1799
0
        double
1800
0
          alpha,
1801
0
          beta;
1802
1803
0
        PixelChannel
1804
0
          channel;
1805
1806
0
        PixelTrait
1807
0
          reconstruct_traits,
1808
0
          traits;
1809
1810
0
        ssize_t
1811
0
          offset;
1812
1813
0
        channel=GetPixelChannelChannel(edge_image,i);
1814
0
        traits=GetPixelChannelTraits(edge_image,channel);
1815
0
        reconstruct_traits=GetPixelChannelTraits(edge_reconstruct,channel);
1816
0
        if (((traits & UpdatePixelTrait) == 0) ||
1817
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
1818
0
          continue;
1819
0
        offset=GetPixelChannelOffset(edge_reconstruct,channel);
1820
0
        if (offset < 0)
1821
0
          continue;
1822
0
        alpha=QuantumScale*(double) p[i];
1823
0
        beta=QuantumScale*(double) q[offset];
1824
0
        channel_image_sum[i]+=alpha;
1825
0
        channel_image_sum_squared[i]+=alpha*alpha;
1826
0
        channel_reconstruct_sum[i]+=beta;
1827
0
        channel_reconstruct_sum_squared[i]+=beta*beta;
1828
0
        channel_correlation[i]+=alpha*beta;
1829
0
      }
1830
0
      channel_area++;
1831
0
      p+=(ptrdiff_t) GetPixelChannels(edge_image);
1832
0
      q+=(ptrdiff_t) GetPixelChannels(edge_reconstruct);
1833
0
      pm+=(ptrdiff_t) GetPixelChannels(image);
1834
0
      qm+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
1835
0
    }
1836
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1837
    #pragma omp critical (MagickCore_GetPHASESimilarity)
1838
#endif
1839
0
    {
1840
0
      area+=channel_area;
1841
0
      for (i=0; i <= (ssize_t) MaxPixelChannels; i++)
1842
0
      {
1843
0
        correlation[i]+=channel_correlation[i];
1844
0
        image_sum[i]+=channel_image_sum[i];
1845
0
        image_sum_squared[i]+=channel_image_sum_squared[i];
1846
0
        reconstruct_sum[i]+=channel_reconstruct_sum[i];
1847
0
        reconstruct_sum_squared[i]+=channel_reconstruct_sum_squared[i];
1848
0
      }
1849
0
    }
1850
0
  }
1851
0
  edge_reconstruct_view=DestroyCacheView(edge_reconstruct_view);
1852
0
  edge_view=DestroyCacheView(edge_view);
1853
0
  reconstruct_view=DestroyCacheView(reconstruct_view);
1854
0
  image_view=DestroyCacheView(image_view);
1855
0
  edge_image=DestroyImage(edge_image);
1856
0
  edge_reconstruct=DestroyImage(edge_reconstruct);
1857
0
  if (status == MagickFalse)
1858
0
    return(MagickFalse);
1859
0
  if (area < 1.0)
1860
0
    {
1861
0
      (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1862
0
        "InsufficientImageDataInRaster","`%s'",image->filename);
1863
0
      return(MagickFalse);
1864
0
    }
1865
  /*
1866
    Reduce to a per-channel Pearson coefficient and average.
1867
  */
1868
0
  similarity[CompositePixelChannel]=0.0;
1869
0
  for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1870
0
  {
1871
0
    double
1872
0
      denominator,
1873
0
      image_variance,
1874
0
      numerator,
1875
0
      pearson,
1876
0
      reconstruct_variance;
1877
1878
0
    PixelChannel
1879
0
      channel;
1880
1881
0
    PixelTrait
1882
0
      reconstruct_traits,
1883
0
      traits;
1884
1885
0
    channel=GetPixelChannelChannel(image,j);
1886
0
    traits=GetPixelChannelTraits(image,channel);
1887
0
    reconstruct_traits=GetPixelChannelTraits(reconstruct_image,channel);
1888
0
    if (((traits & UpdatePixelTrait) == 0) ||
1889
0
        ((reconstruct_traits & UpdatePixelTrait) == 0))
1890
0
      continue;
1891
0
    numerator=area*correlation[j]-image_sum[j]*reconstruct_sum[j];
1892
0
    image_variance=area*image_sum_squared[j]-image_sum[j]*image_sum[j];
1893
0
    reconstruct_variance=area*reconstruct_sum_squared[j]-reconstruct_sum[j]*
1894
0
      reconstruct_sum[j];
1895
0
    if ((image_variance < MagickEpsilon) &&
1896
0
        (reconstruct_variance < MagickEpsilon))
1897
0
      pearson=(fabs(image_sum[j]-reconstruct_sum[j]) < MagickEpsilon) ?
1898
0
        1.0 : 0.0;
1899
0
    else
1900
0
      {
1901
0
        denominator=sqrt(image_variance)*sqrt(reconstruct_variance);
1902
0
        pearson=denominator < MagickEpsilon ? 0.0 : numerator/denominator;
1903
0
      }
1904
0
    if (pearson < -1.0)
1905
0
      pearson=(-1.0);
1906
0
    if (pearson > 1.0)
1907
0
      pearson=1.0;
1908
0
    similarity[j]=pearson;
1909
0
    similarity[CompositePixelChannel]+=pearson;
1910
0
    channels++;
1911
0
  }
1912
0
  if (channels != 0)
1913
0
    similarity[CompositePixelChannel]/=(double) channels;
1914
0
  return(MagickTrue);
1915
0
}
1916
1917
static MagickBooleanType GetPHASHSimilarity(const Image *image,
1918
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
1919
0
{
1920
0
  ChannelPerceptualHash
1921
0
    *channel_phash,
1922
0
    *reconstruct_phash;
1923
1924
0
  const char
1925
0
    *artifact;
1926
1927
0
  ssize_t
1928
0
    channels = 0,
1929
0
    i;
1930
1931
  /*
1932
    Compute the perceptual hash similarity.
1933
  */
1934
0
  channel_phash=GetImagePerceptualHash(image,exception);
1935
0
  if (channel_phash == (ChannelPerceptualHash *) NULL)
1936
0
    return(MagickFalse);
1937
0
  reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
1938
0
  if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
1939
0
    {
1940
0
      channel_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1941
0
        channel_phash);
1942
0
      return(MagickFalse);
1943
0
    }
1944
0
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1945
0
  {
1946
0
    double
1947
0
      difference = 0.0;
1948
1949
0
    ssize_t
1950
0
      j;
1951
1952
0
    PixelChannel channel = GetPixelChannelChannel(image,i);
1953
0
    PixelTrait traits = GetPixelChannelTraits(image,channel);
1954
0
    PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1955
0
      channel);
1956
0
    if (((traits & UpdatePixelTrait) == 0) ||
1957
0
        ((reconstruct_traits & UpdatePixelTrait) == 0))
1958
0
      continue;
1959
0
    for (j=0; j < (ssize_t) channel_phash[0].number_colorspaces; j++)
1960
0
    {
1961
0
      double
1962
0
        alpha,
1963
0
        beta;
1964
1965
0
      ssize_t
1966
0
        k;
1967
1968
0
      for (k=0; k < MaximumNumberOfPerceptualHashes; k++)
1969
0
      {
1970
0
        double
1971
0
          error;
1972
1973
0
        alpha=channel_phash[i].phash[j][k];
1974
0
        beta=reconstruct_phash[i].phash[j][k];
1975
0
        error=beta-alpha;
1976
0
        if (IsNaN(error) != 0)
1977
0
          error=0.0;
1978
0
        difference+=error*error;
1979
0
      }
1980
0
    }
1981
0
    similarity[i]+=difference;
1982
0
    similarity[CompositePixelChannel]+=difference;
1983
0
    channels++;
1984
0
  }
1985
0
  if (channels != 0)
1986
0
    similarity[CompositePixelChannel]/=(double) channels;
1987
0
  artifact=GetImageArtifact(image,"phash:normalize");
1988
0
  if (IsStringTrue(artifact) != MagickFalse)
1989
0
    {
1990
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1991
0
      {
1992
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
1993
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
1994
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
1995
0
          channel);
1996
0
        if (((traits & UpdatePixelTrait) == 0) ||
1997
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
1998
0
          continue;
1999
0
        similarity[i]=sqrt(similarity[i]/(double)
2000
0
          channel_phash[0].number_colorspaces);
2001
0
      }
2002
0
      similarity[CompositePixelChannel]=sqrt(similarity[CompositePixelChannel]/
2003
0
        (double) channel_phash[0].number_colorspaces);
2004
0
    }
2005
  /*
2006
    Free resources.
2007
  */
2008
0
  reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
2009
0
    reconstruct_phash);
2010
0
  channel_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(channel_phash);
2011
0
  return(MagickTrue);
2012
0
}
2013
2014
static MagickBooleanType GetPSNRSimilarity(const Image *image,
2015
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
2016
0
{
2017
0
  MagickBooleanType
2018
0
    status = MagickTrue;
2019
2020
0
  ssize_t
2021
0
    i;
2022
2023
  /*
2024
    Compute the peak signal-to-noise ratio similarity.
2025
  */
2026
0
  status=GetMSESimilarity(image,reconstruct_image,similarity,exception);
2027
0
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2028
0
  {
2029
0
    PixelChannel channel = GetPixelChannelChannel(image,i);
2030
0
    PixelTrait traits = GetPixelChannelTraits(image,channel);
2031
0
    PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
2032
0
      channel);
2033
0
    if (((traits & UpdatePixelTrait) == 0) ||
2034
0
        ((reconstruct_traits & UpdatePixelTrait) == 0))
2035
0
      continue;
2036
0
    similarity[i]=10.0*MagickSafeLog10(MagickSafeReciprocal(
2037
0
      similarity[i]))/MagickSafePSNRRecipicol(10.0);
2038
0
  }
2039
0
  similarity[CompositePixelChannel]=10.0*MagickSafeLog10(
2040
0
    MagickSafeReciprocal(similarity[CompositePixelChannel]))/
2041
0
    MagickSafePSNRRecipicol(10.0);
2042
0
  return(status);
2043
0
}
2044
2045
static MagickBooleanType GetRMSESimilarity(const Image *image,
2046
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
2047
0
{
2048
0
#define RMSESquareRoot(x)  sqrt((x) < 0.0 ? 0.0 : (x))
2049
2050
0
  MagickBooleanType
2051
0
    status = MagickTrue;
2052
2053
0
  ssize_t
2054
0
    i;
2055
2056
  /*
2057
    Compute the root mean-squared error similarity.
2058
  */
2059
0
  status=GetMSESimilarity(image,reconstruct_image,similarity,exception);
2060
0
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2061
0
  {
2062
0
    PixelChannel channel = GetPixelChannelChannel(image,i);
2063
0
    PixelTrait traits = GetPixelChannelTraits(image,channel);
2064
0
    PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
2065
0
      channel);
2066
0
    if (((traits & UpdatePixelTrait) == 0) ||
2067
0
        ((reconstruct_traits & UpdatePixelTrait) == 0))
2068
0
      continue;
2069
0
    similarity[i]=RMSESquareRoot(similarity[i]);
2070
0
  }
2071
0
  similarity[CompositePixelChannel]=RMSESquareRoot(
2072
0
    similarity[CompositePixelChannel]);
2073
0
  return(status);
2074
0
}
2075
2076
static MagickBooleanType GetSSIMSimularity(const Image *image,
2077
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
2078
0
{
2079
0
#define SSIMRadius  5.0
2080
0
#define SSIMSigma  1.5
2081
0
#define SSIMK1  0.01
2082
0
#define SSIMK2  0.03
2083
0
#define SSIML  1.0
2084
2085
0
  CacheView
2086
0
    *image_view,
2087
0
    *reconstruct_view;
2088
2089
0
  char
2090
0
    geometry[MagickPathExtent];
2091
2092
0
  const char
2093
0
    *artifact;
2094
2095
0
  double
2096
0
    area = 0.0,
2097
0
    c1,
2098
0
    c2,
2099
0
    radius,
2100
0
    sigma;
2101
2102
0
  KernelInfo
2103
0
    *kernel_info;
2104
2105
0
  MagickBooleanType
2106
0
    status = MagickTrue;
2107
2108
0
  size_t
2109
0
    columns,
2110
0
    rows;
2111
2112
0
  ssize_t
2113
0
    channels = 0,
2114
0
    l,
2115
0
    y;
2116
2117
  /*
2118
    Compute the structual similarity index similarity.
2119
  */
2120
0
  radius=SSIMRadius;
2121
0
  artifact=GetImageArtifact(image,"compare:ssim-radius");
2122
0
  if (artifact != (const char *) NULL)
2123
0
    radius=StringToDouble(artifact,(char **) NULL);
2124
0
  sigma=SSIMSigma;
2125
0
  artifact=GetImageArtifact(image,"compare:ssim-sigma");
2126
0
  if (artifact != (const char *) NULL)
2127
0
    sigma=StringToDouble(artifact,(char **) NULL);
2128
0
  (void) FormatLocaleString(geometry,MagickPathExtent,"gaussian:%.17gx%.17g",
2129
0
    radius,sigma);
2130
0
  kernel_info=AcquireKernelInfo(geometry,exception);
2131
0
  if (kernel_info == (KernelInfo *) NULL)
2132
0
    ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2133
0
      image->filename);
2134
0
  c1=pow(SSIMK1*SSIML,2.0);
2135
0
  artifact=GetImageArtifact(image,"compare:ssim-k1");
2136
0
  if (artifact != (const char *) NULL)
2137
0
    c1=pow(StringToDouble(artifact,(char **) NULL)*SSIML,2.0);
2138
0
  c2=pow(SSIMK2*SSIML,2.0);
2139
0
  artifact=GetImageArtifact(image,"compare:ssim-k2");
2140
0
  if (artifact != (const char *) NULL)
2141
0
    c2=pow(StringToDouble(artifact,(char **) NULL)*SSIML,2.0);
2142
0
  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
2143
0
  image_view=AcquireVirtualCacheView(image,exception);
2144
0
  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
2145
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2146
  #pragma omp parallel for schedule(static) shared(area,similarity,status) \
2147
    magick_number_threads(image,reconstruct_image,rows,1)
2148
#endif
2149
0
  for (y=0; y < (ssize_t) rows; y++)
2150
0
  {
2151
0
    const Quantum
2152
0
      *magick_restrict p,
2153
0
      *magick_restrict q;
2154
2155
0
    double
2156
0
      channel_area = 0.0,
2157
0
      channel_similarity[MaxPixelChannels+1] = { 0.0 };
2158
2159
0
    ssize_t
2160
0
      i,
2161
0
      x;
2162
2163
0
    if (status == MagickFalse)
2164
0
      continue;
2165
0
    p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel_info->width/2L),y-
2166
0
      ((ssize_t) kernel_info->height/2L),columns+kernel_info->width,
2167
0
      kernel_info->height,exception);
2168
0
    q=GetCacheViewVirtualPixels(reconstruct_view,-((ssize_t) kernel_info->width/
2169
0
      2L),y-((ssize_t) kernel_info->height/2L),columns+kernel_info->width,
2170
0
      kernel_info->height,exception);
2171
0
    if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
2172
0
      {
2173
0
        status=MagickFalse;
2174
0
        continue;
2175
0
      }
2176
0
    for (x=0; x < (ssize_t) columns; x++)
2177
0
    {
2178
0
      const Quantum
2179
0
        *magick_restrict reconstruct,
2180
0
        *magick_restrict test;
2181
2182
0
      double
2183
0
        x_pixel_mu[MaxPixelChannels+1] = { 0.0 },
2184
0
        x_pixel_sigma_squared[MaxPixelChannels+1] = { 0.0 },
2185
0
        xy_sigma[MaxPixelChannels+1] = { 0.0 },
2186
0
        y_pixel_mu[MaxPixelChannels+1] = { 0.0 },
2187
0
        y_pixel_sigma_squared[MaxPixelChannels+1] = { 0.0 };
2188
2189
0
      MagickRealType
2190
0
        *k;
2191
2192
0
      ssize_t
2193
0
        v;
2194
2195
0
      if ((GetPixelReadMask(image,p) <= (QuantumRange/2)) ||
2196
0
          (GetPixelReadMask(reconstruct_image,q) <= (QuantumRange/2)))
2197
0
        {
2198
0
          p+=(ptrdiff_t) GetPixelChannels(image);
2199
0
          q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
2200
0
          continue;
2201
0
        }
2202
0
      k=kernel_info->values;
2203
0
      test=p;
2204
0
      reconstruct=q;
2205
0
      for (v=0; v < (ssize_t) kernel_info->height; v++)
2206
0
      {
2207
0
        ssize_t
2208
0
          u;
2209
2210
0
        for (u=0; u < (ssize_t) kernel_info->width; u++)
2211
0
        {
2212
0
          for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2213
0
          {
2214
0
            double
2215
0
              x_pixel,
2216
0
              y_pixel;
2217
2218
0
            PixelChannel channel = GetPixelChannelChannel(image,i);
2219
0
            PixelTrait traits = GetPixelChannelTraits(image,channel);
2220
0
            PixelTrait reconstruct_traits = GetPixelChannelTraits(
2221
0
              reconstruct_image,channel);
2222
0
            if (((traits & UpdatePixelTrait) == 0) ||
2223
0
                ((reconstruct_traits & UpdatePixelTrait) == 0))
2224
0
              continue;
2225
0
            x_pixel=QuantumScale*(double) test[i];
2226
0
            x_pixel_mu[i]+=(*k)*x_pixel;
2227
0
            x_pixel_sigma_squared[i]+=(*k)*x_pixel*x_pixel;
2228
0
            y_pixel=QuantumScale*(double)
2229
0
              GetPixelChannel(reconstruct_image,channel,reconstruct);
2230
0
            y_pixel_mu[i]+=(*k)*y_pixel;
2231
0
            y_pixel_sigma_squared[i]+=(*k)*y_pixel*y_pixel;
2232
0
            xy_sigma[i]+=(*k)*x_pixel*y_pixel;
2233
0
          }
2234
0
          k++;
2235
0
          test+=(ptrdiff_t) GetPixelChannels(image);
2236
0
          reconstruct+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
2237
0
        }
2238
0
        test+=(ptrdiff_t) ((double) GetPixelChannels(image)*(double) columns);
2239
0
        reconstruct+=(ptrdiff_t) ((double) GetPixelChannels(reconstruct_image)*
2240
0
          (double) columns);
2241
0
      }
2242
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2243
0
      {
2244
0
        double
2245
0
          ssim,
2246
0
          x_pixel_mu_squared,
2247
0
          x_pixel_sigmas_squared,
2248
0
          xy_mu,
2249
0
          xy_sigmas,
2250
0
          y_pixel_mu_squared,
2251
0
          y_pixel_sigmas_squared;
2252
2253
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
2254
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
2255
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(
2256
0
          reconstruct_image,channel);
2257
0
        if (((traits & UpdatePixelTrait) == 0) ||
2258
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
2259
0
          continue;
2260
0
        x_pixel_mu_squared=x_pixel_mu[i]*x_pixel_mu[i];
2261
0
        y_pixel_mu_squared=y_pixel_mu[i]*y_pixel_mu[i];
2262
0
        xy_mu=x_pixel_mu[i]*y_pixel_mu[i];
2263
0
        xy_sigmas=xy_sigma[i]-xy_mu;
2264
0
        x_pixel_sigmas_squared=x_pixel_sigma_squared[i]-x_pixel_mu_squared;
2265
0
        y_pixel_sigmas_squared=y_pixel_sigma_squared[i]-y_pixel_mu_squared;
2266
0
        ssim=((2.0*xy_mu+c1)*(2.0*xy_sigmas+c2))*
2267
0
          MagickSafeReciprocal((x_pixel_mu_squared+y_pixel_mu_squared+c1)*
2268
0
           (x_pixel_sigmas_squared+y_pixel_sigmas_squared+c2));
2269
0
        channel_similarity[i]+=ssim;
2270
0
        channel_similarity[CompositePixelChannel]+=ssim;
2271
0
      }
2272
0
      p+=(ptrdiff_t) GetPixelChannels(image);
2273
0
      q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
2274
0
      channel_area++;
2275
0
    }
2276
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2277
    #pragma omp critical (MagickCore_GetSSIMSimularity)
2278
#endif
2279
0
    {
2280
0
      ssize_t
2281
0
        j;
2282
2283
0
      area+=channel_area;
2284
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2285
0
      {
2286
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
2287
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
2288
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
2289
0
          channel);
2290
0
        if (((traits & UpdatePixelTrait) == 0) ||
2291
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
2292
0
          continue;
2293
0
        similarity[j]+=channel_similarity[j];
2294
0
      }
2295
0
      similarity[CompositePixelChannel]+=
2296
0
        channel_similarity[CompositePixelChannel];
2297
0
    }
2298
0
  }
2299
0
  image_view=DestroyCacheView(image_view);
2300
0
  reconstruct_view=DestroyCacheView(reconstruct_view);
2301
0
  kernel_info=DestroyKernelInfo(kernel_info);
2302
0
  area=MagickSafeReciprocal(area);
2303
0
  for (l=0; l < (ssize_t) GetPixelChannels(image); l++)
2304
0
  {
2305
0
    PixelChannel channel = GetPixelChannelChannel(image,l);
2306
0
    PixelTrait traits = GetPixelChannelTraits(image,channel);
2307
0
    PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
2308
0
      channel);
2309
0
    if (((traits & UpdatePixelTrait) == 0) ||
2310
0
        ((reconstruct_traits & UpdatePixelTrait) == 0))
2311
0
      continue;
2312
0
    similarity[l]*=area;
2313
0
    channels++;
2314
0
  }
2315
0
  similarity[CompositePixelChannel]*=area;
2316
0
  if (channels != 0)
2317
0
   similarity[CompositePixelChannel]/=(double) channels;
2318
0
  return(status);
2319
0
}
2320
2321
static MagickBooleanType GetDSSIMSimilarity(const Image *image,
2322
  const Image *reconstruct_image,double *similarity,ExceptionInfo *exception)
2323
0
{
2324
0
  MagickBooleanType
2325
0
    status = MagickTrue;
2326
2327
0
  ssize_t
2328
0
    i;
2329
2330
  /*
2331
    Compute the structual dissimilarity index similarity.
2332
  */
2333
0
  status=GetSSIMSimularity(image,reconstruct_image,similarity,exception);
2334
0
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2335
0
  {
2336
0
    PixelChannel channel = GetPixelChannelChannel(image,i);
2337
0
    PixelTrait traits = GetPixelChannelTraits(image,channel);
2338
0
    PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
2339
0
      channel);
2340
0
    if (((traits & UpdatePixelTrait) == 0) ||
2341
0
        ((reconstruct_traits & UpdatePixelTrait) == 0))
2342
0
      continue;
2343
0
    similarity[i]=(1.0-similarity[i])/2.0;
2344
0
  }
2345
0
  similarity[CompositePixelChannel]=(1.0-similarity[CompositePixelChannel])/2.0;
2346
0
  return(status);
2347
0
}
2348
2349
MagickExport MagickBooleanType GetImageDistortion(Image *image,
2350
  const Image *reconstruct_image,const MetricType metric,double *distortion,
2351
  ExceptionInfo *exception)
2352
0
{
2353
0
#define CompareMetricNotSupportedException  "metric not supported"
2354
2355
0
  double
2356
0
    *channel_similarity;
2357
2358
0
  MagickBooleanType
2359
0
    status = MagickTrue;
2360
2361
0
  size_t
2362
0
    length;
2363
2364
0
  assert(image != (Image *) NULL);
2365
0
  assert(image->signature == MagickCoreSignature);
2366
0
  assert(reconstruct_image != (const Image *) NULL);
2367
0
  assert(reconstruct_image->signature == MagickCoreSignature);
2368
0
  assert(distortion != (double *) NULL);
2369
0
  if (IsEventLogging() != MagickFalse)
2370
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2371
  /*
2372
    Get image distortion.
2373
  */
2374
0
  *distortion=0.0;
2375
0
  length=MaxPixelChannels+1UL;
2376
0
  channel_similarity=(double *) AcquireQuantumMemory(length,
2377
0
    sizeof(*channel_similarity));
2378
0
  if (channel_similarity == (double *) NULL)
2379
0
    ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
2380
0
  (void) memset(channel_similarity,0,length*sizeof(*channel_similarity));
2381
0
  switch (metric)
2382
0
  {
2383
0
    case AbsoluteErrorMetric:
2384
0
    {
2385
0
      status=GetAESimilarity(image,reconstruct_image,channel_similarity,
2386
0
        exception);
2387
0
      break;
2388
0
    }
2389
0
    case DotProductCorrelationErrorMetric:
2390
0
    {
2391
0
      status=GetDPCSimilarity(image,reconstruct_image,channel_similarity,
2392
0
        exception);
2393
0
      break;
2394
0
    }
2395
0
    case FuzzErrorMetric:
2396
0
    {
2397
0
      status=GetFUZZSimilarity(image,reconstruct_image,channel_similarity,
2398
0
        exception);
2399
0
      break;
2400
0
    }
2401
0
    case MeanAbsoluteErrorMetric:
2402
0
    {
2403
0
      status=GetMAESimilarity(image,reconstruct_image,channel_similarity,
2404
0
        exception);
2405
0
      break;
2406
0
    }
2407
0
    case MeanErrorPerPixelErrorMetric:
2408
0
    {
2409
0
      status=GetMEPPSimilarity(image,reconstruct_image,channel_similarity,
2410
0
        exception);
2411
0
      break;
2412
0
    }
2413
0
    case MeanSquaredErrorMetric:
2414
0
    {
2415
0
      status=GetMSESimilarity(image,reconstruct_image,channel_similarity,
2416
0
        exception);
2417
0
      break;
2418
0
    }
2419
0
    case NormalizedCrossCorrelationErrorMetric:
2420
0
    {
2421
0
      status=GetNCCSimilarity(image,reconstruct_image,channel_similarity,
2422
0
        exception);
2423
0
      break;
2424
0
    }
2425
0
    case PeakAbsoluteErrorMetric:
2426
0
    {
2427
0
      status=GetPASimilarity(image,reconstruct_image,channel_similarity,
2428
0
        exception);
2429
0
      break;
2430
0
    }
2431
0
    case PeakSignalToNoiseRatioErrorMetric:
2432
0
    {
2433
0
      status=GetPSNRSimilarity(image,reconstruct_image,channel_similarity,
2434
0
        exception);
2435
0
      break;
2436
0
    }
2437
0
    case PerceptualHashErrorMetric:
2438
0
    {
2439
0
      status=GetPHASHSimilarity(image,reconstruct_image,channel_similarity,
2440
0
        exception);
2441
0
      break;
2442
0
    }
2443
0
    case PhaseCorrelationErrorMetric:
2444
0
    {
2445
0
      status=GetPHASESimilarity(image,reconstruct_image,channel_similarity,
2446
0
        exception);
2447
0
      break;
2448
0
    }
2449
0
    case PixelDifferenceCountErrorMetric:
2450
0
    {
2451
0
      status=GetPDCSimilarity(image,reconstruct_image,channel_similarity,
2452
0
        exception);
2453
0
      break;
2454
0
    }
2455
0
    case RootMeanSquaredErrorMetric:
2456
0
    case UndefinedErrorMetric:
2457
0
    default:
2458
0
    {
2459
0
      status=GetRMSESimilarity(image,reconstruct_image,channel_similarity,
2460
0
        exception);
2461
0
      break;
2462
0
    }
2463
0
    case StructuralDissimilarityErrorMetric:
2464
0
    {
2465
0
      status=GetDSSIMSimilarity(image,reconstruct_image,channel_similarity,
2466
0
        exception);
2467
0
      break;
2468
0
    }
2469
0
    case StructuralSimilarityErrorMetric:
2470
0
    {
2471
0
      status=GetSSIMSimularity(image,reconstruct_image,channel_similarity,
2472
0
        exception);
2473
0
      break;
2474
0
    }
2475
0
  }
2476
0
  *distortion=channel_similarity[CompositePixelChannel];
2477
0
  switch (metric)
2478
0
  {
2479
0
    case DotProductCorrelationErrorMetric:
2480
0
    case NormalizedCrossCorrelationErrorMetric:
2481
0
    case PhaseCorrelationErrorMetric:
2482
0
    case StructuralSimilarityErrorMetric:
2483
0
    {
2484
0
      *distortion=(1.0-(*distortion))/2.0;
2485
0
      break;
2486
0
    }
2487
0
    default: break;
2488
0
  }
2489
0
  channel_similarity=(double *) RelinquishMagickMemory(channel_similarity);
2490
0
  if ((IsNaN(*distortion) != 0) || (fabs(*distortion) < MagickEpsilon))
2491
0
    *distortion=0.0;
2492
0
  (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
2493
0
    *distortion);
2494
0
  return(status);
2495
0
}
2496

2497
/*
2498
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2499
%                                                                             %
2500
%                                                                             %
2501
%                                                                             %
2502
%   G e t I m a g e D i s t o r t i o n s                                     %
2503
%                                                                             %
2504
%                                                                             %
2505
%                                                                             %
2506
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2507
%
2508
%  GetImageDistortions() compares the pixel channels of an image to a
2509
%  reconstructed image and returns the specified metric for each channel.
2510
%
2511
%  The format of the GetImageDistortions method is:
2512
%
2513
%      double *GetImageDistortions(const Image *image,
2514
%        const Image *reconstruct_image,const MetricType metric,
2515
%        ExceptionInfo *exception)
2516
%
2517
%  A description of each parameter follows:
2518
%
2519
%    o image: the image.
2520
%
2521
%    o reconstruct_image: the reconstruction image.
2522
%
2523
%    o metric: the metric.
2524
%
2525
%    o exception: return any errors or warnings in this structure.
2526
%
2527
*/
2528
MagickExport double *GetImageDistortions(Image *image,
2529
  const Image *reconstruct_image,const MetricType metric,
2530
  ExceptionInfo *exception)
2531
0
{
2532
0
  double
2533
0
    *distortion,
2534
0
    *channel_similarity;
2535
2536
0
  MagickBooleanType
2537
0
    status = MagickTrue;
2538
2539
0
  size_t
2540
0
    length;
2541
2542
0
  ssize_t
2543
0
    i;
2544
2545
0
  assert(image != (Image *) NULL);
2546
0
  assert(image->signature == MagickCoreSignature);
2547
0
  assert(reconstruct_image != (const Image *) NULL);
2548
0
  assert(reconstruct_image->signature == MagickCoreSignature);
2549
0
  if (IsEventLogging() != MagickFalse)
2550
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2551
  /*
2552
    Get image distortion.
2553
  */
2554
0
  length=MaxPixelChannels+1UL;
2555
0
  channel_similarity=(double *) AcquireQuantumMemory(length,
2556
0
    sizeof(*channel_similarity));
2557
0
  if (channel_similarity == (double *) NULL)
2558
0
    ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
2559
0
  (void) memset(channel_similarity,0,length*sizeof(*channel_similarity));
2560
0
  switch (metric)
2561
0
  {
2562
0
    case AbsoluteErrorMetric:
2563
0
    {
2564
0
      status=GetAESimilarity(image,reconstruct_image,channel_similarity,
2565
0
        exception);
2566
0
      break;
2567
0
    }
2568
0
    case DotProductCorrelationErrorMetric:
2569
0
    {
2570
0
      status=GetDPCSimilarity(image,reconstruct_image,channel_similarity,
2571
0
        exception);
2572
0
      break;
2573
0
    }
2574
0
    case FuzzErrorMetric:
2575
0
    {
2576
0
      status=GetFUZZSimilarity(image,reconstruct_image,channel_similarity,
2577
0
        exception);
2578
0
      break;
2579
0
    }
2580
0
    case MeanAbsoluteErrorMetric:
2581
0
    {
2582
0
      status=GetMAESimilarity(image,reconstruct_image,channel_similarity,
2583
0
        exception);
2584
0
      break;
2585
0
    }
2586
0
    case MeanErrorPerPixelErrorMetric:
2587
0
    {
2588
0
      status=GetMEPPSimilarity(image,reconstruct_image,channel_similarity,
2589
0
        exception);
2590
0
      break;
2591
0
    }
2592
0
    case MeanSquaredErrorMetric:
2593
0
    {
2594
0
      status=GetMSESimilarity(image,reconstruct_image,channel_similarity,
2595
0
        exception);
2596
0
      break;
2597
0
    }
2598
0
    case NormalizedCrossCorrelationErrorMetric:
2599
0
    {
2600
0
      status=GetNCCSimilarity(image,reconstruct_image,channel_similarity,
2601
0
        exception);
2602
0
      break;
2603
0
    }
2604
0
    case PeakAbsoluteErrorMetric:
2605
0
    {
2606
0
      status=GetPASimilarity(image,reconstruct_image,channel_similarity,
2607
0
        exception);
2608
0
      break;
2609
0
    }
2610
0
    case PeakSignalToNoiseRatioErrorMetric:
2611
0
    {
2612
0
      status=GetPSNRSimilarity(image,reconstruct_image,channel_similarity,
2613
0
        exception);
2614
0
      break;
2615
0
    }
2616
0
    case PerceptualHashErrorMetric:
2617
0
    {
2618
0
      status=GetPHASHSimilarity(image,reconstruct_image,channel_similarity,
2619
0
        exception);
2620
0
      break;
2621
0
    }
2622
0
    case PhaseCorrelationErrorMetric:
2623
0
    {
2624
0
      status=GetPHASESimilarity(image,reconstruct_image,channel_similarity,
2625
0
        exception);
2626
0
      break;
2627
0
    }
2628
0
    case PixelDifferenceCountErrorMetric:
2629
0
    {
2630
0
      status=GetPDCSimilarity(image,reconstruct_image,channel_similarity,
2631
0
        exception);
2632
0
      break;
2633
0
    }
2634
0
    case RootMeanSquaredErrorMetric:
2635
0
    case UndefinedErrorMetric:
2636
0
    default:
2637
0
    {
2638
0
      status=GetRMSESimilarity(image,reconstruct_image,channel_similarity,
2639
0
        exception);
2640
0
      break;
2641
0
    }
2642
0
    case StructuralDissimilarityErrorMetric:
2643
0
    {
2644
0
      status=GetDSSIMSimilarity(image,reconstruct_image,channel_similarity,
2645
0
        exception);
2646
0
      break;
2647
0
    }
2648
0
    case StructuralSimilarityErrorMetric:
2649
0
    {
2650
0
      status=GetSSIMSimularity(image,reconstruct_image,channel_similarity,
2651
0
        exception);
2652
0
      break;
2653
0
    }
2654
0
  }
2655
0
  if (status == MagickFalse)
2656
0
    {
2657
0
      channel_similarity=(double *) RelinquishMagickMemory(channel_similarity);
2658
0
      return((double *) NULL);
2659
0
    }
2660
0
  distortion=channel_similarity;
2661
0
  switch (metric)
2662
0
  {
2663
0
    case DotProductCorrelationErrorMetric:
2664
0
    case NormalizedCrossCorrelationErrorMetric:
2665
0
    case PhaseCorrelationErrorMetric:
2666
0
    case StructuralSimilarityErrorMetric:
2667
0
    {
2668
0
      for (i=0; i <= MaxPixelChannels; i++)
2669
0
        distortion[i]=(1.0-distortion[i])/2.0;
2670
0
      break;
2671
0
    }
2672
0
    default: break;
2673
0
  }
2674
0
  for (i=0; i <= MaxPixelChannels; i++)
2675
0
    if (fabs(distortion[i]) < MagickEpsilon)
2676
0
      distortion[i]=0.0;
2677
0
  (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
2678
0
    distortion[CompositePixelChannel]);
2679
0
  return(distortion);
2680
0
}
2681

2682
/*
2683
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2684
%                                                                             %
2685
%                                                                             %
2686
%                                                                             %
2687
%  I s I m a g e s E q u a l                                                  %
2688
%                                                                             %
2689
%                                                                             %
2690
%                                                                             %
2691
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2692
%
2693
%  IsImagesEqual() compare the pixels of two images and returns immediately
2694
%  if any pixel is not identical.
2695
%
2696
%  The format of the IsImagesEqual method is:
2697
%
2698
%      MagickBooleanType IsImagesEqual(const Image *image,
2699
%        const Image *reconstruct_image,ExceptionInfo *exception)
2700
%
2701
%  A description of each parameter follows.
2702
%
2703
%    o image: the image.
2704
%
2705
%    o reconstruct_image: the reconstruction image.
2706
%
2707
%    o exception: return any errors or warnings in this structure.
2708
%
2709
*/
2710
MagickExport MagickBooleanType IsImagesEqual(const Image *image,
2711
  const Image *reconstruct_image,ExceptionInfo *exception)
2712
0
{
2713
0
  CacheView
2714
0
    *image_view,
2715
0
    *reconstruct_view;
2716
2717
0
  size_t
2718
0
    columns,
2719
0
    rows;
2720
2721
0
  ssize_t
2722
0
    y;
2723
2724
0
  assert(image != (Image *) NULL);
2725
0
  assert(image->signature == MagickCoreSignature);
2726
0
  assert(reconstruct_image != (const Image *) NULL);
2727
0
  assert(reconstruct_image->signature == MagickCoreSignature);
2728
0
  SetImageCompareBounds(image,reconstruct_image,&columns,&rows);
2729
0
  image_view=AcquireVirtualCacheView(image,exception);
2730
0
  reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
2731
0
  for (y=0; y < (ssize_t) rows; y++)
2732
0
  {
2733
0
    const Quantum
2734
0
      *magick_restrict p,
2735
0
      *magick_restrict q;
2736
2737
0
    ssize_t
2738
0
      x;
2739
2740
0
    p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
2741
0
    q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
2742
0
    if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
2743
0
      break;
2744
0
    for (x=0; x < (ssize_t) columns; x++)
2745
0
    {
2746
0
      ssize_t
2747
0
        i;
2748
2749
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2750
0
      {
2751
0
        double
2752
0
          distance;
2753
2754
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
2755
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
2756
0
        PixelTrait reconstruct_traits = GetPixelChannelTraits(reconstruct_image,
2757
0
          channel);
2758
0
        if (((traits & UpdatePixelTrait) == 0) ||
2759
0
            ((reconstruct_traits & UpdatePixelTrait) == 0))
2760
0
          continue;
2761
0
        distance=fabs((double) p[i]-(double) GetPixelChannel(reconstruct_image,
2762
0
          channel,q));
2763
0
        if (distance >= MagickEpsilon)
2764
0
          break;
2765
0
      }
2766
0
      if (i < (ssize_t) GetPixelChannels(image))
2767
0
        break;
2768
0
      p+=(ptrdiff_t) GetPixelChannels(image);
2769
0
      q+=(ptrdiff_t) GetPixelChannels(reconstruct_image);
2770
0
    }
2771
0
    if (x < (ssize_t) columns)
2772
0
      break;
2773
0
  }
2774
0
  reconstruct_view=DestroyCacheView(reconstruct_view);
2775
0
  image_view=DestroyCacheView(image_view);
2776
0
  return(y < (ssize_t) rows ? MagickFalse : MagickTrue);
2777
0
}
2778

2779
/*
2780
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2781
%                                                                             %
2782
%                                                                             %
2783
%                                                                             %
2784
%  S e t I m a g e C o l o r M e t r i c                                      %
2785
%                                                                             %
2786
%                                                                             %
2787
%                                                                             %
2788
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2789
%
2790
%  SetImageColorMetric() measures the difference between colors at each pixel
2791
%  location of two images.  A value other than 0 means the colors match
2792
%  exactly.  Otherwise an error measure is computed by summing over all
2793
%  pixels in an image the distance squared in RGB space between each image
2794
%  pixel and its corresponding pixel in the reconstruction image.  The error
2795
%  measure is assigned to these image members:
2796
%
2797
%    o mean_error_per_pixel:  The mean error for any single pixel in
2798
%      the image.
2799
%
2800
%    o normalized_mean_error:  The normalized mean quantization error for
2801
%      any single pixel in the image.  This distance measure is normalized to
2802
%      a range between 0 and 1.  It is independent of the range of red, green,
2803
%      and blue values in the image.
2804
%
2805
%    o normalized_maximum_error:  The normalized maximum quantization
2806
%      error for any single pixel in the image.  This distance measure is
2807
%      normalized to a range between 0 and 1.  It is independent of the range
2808
%      of red, green, and blue values in your image.
2809
%
2810
%  A small normalized mean square error, accessed as
2811
%  image->normalized_mean_error, suggests the images are very similar in
2812
%  spatial layout and color.
2813
%
2814
%  The format of the SetImageColorMetric method is:
2815
%
2816
%      MagickBooleanType SetImageColorMetric(Image *image,
2817
%        const Image *reconstruct_image,ExceptionInfo *exception)
2818
%
2819
%  A description of each parameter follows.
2820
%
2821
%    o image: the image.
2822
%
2823
%    o reconstruct_image: the reconstruction image.
2824
%
2825
%    o exception: return any errors or warnings in this structure.
2826
%
2827
*/
2828
MagickExport MagickBooleanType SetImageColorMetric(Image *image,
2829
  const Image *reconstruct_image,ExceptionInfo *exception)
2830
0
{
2831
0
  double
2832
0
    channel_similarity[MaxPixelChannels+1] = { 0.0 };
2833
2834
0
  MagickBooleanType
2835
0
    status;
2836
2837
0
  status=GetMEPPSimilarity(image,reconstruct_image,channel_similarity,
2838
0
    exception);
2839
0
  if (status == MagickFalse)
2840
0
    return(MagickFalse);
2841
0
  status=fabs(image->error.mean_error_per_pixel) < MagickEpsilon ?
2842
0
    MagickTrue : MagickFalse;
2843
0
  return(status);
2844
0
}
2845

2846
/*
2847
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2848
%                                                                             %
2849
%                                                                             %
2850
%                                                                             %
2851
%   S i m i l a r i t y I m a g e                                             %
2852
%                                                                             %
2853
%                                                                             %
2854
%                                                                             %
2855
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2856
%
2857
%  SimilarityImage() compares the reconstruction of the image and returns the
2858
%  best match offset.  In addition, it returns a similarity image such that an
2859
%  exact match location is completely white and if none of the pixels match,
2860
%  black, otherwise some gray level in-between.
2861
%
2862
%  Contributed by Fred Weinhaus.
2863
%
2864
%  The format of the SimilarityImageImage method is:
2865
%
2866
%      Image *SimilarityImage(const Image *image,const Image *reconstruct,
2867
%        const MetricType metric,const double similarity_threshold,
2868
%        RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
2869
%
2870
%  A description of each parameter follows:
2871
%
2872
%    o image: the image.
2873
%
2874
%    o reconstruct: find an area of the image that closely resembles this image.
2875
%
2876
%    o metric: the metric.
2877
%
2878
%    o similarity_threshold: minimum similarity for (sub)image match.
2879
%
2880
%    o offset: the best match offset of the reconstruction image within the
2881
%      image.
2882
%
2883
%    o similarity: the computed similarity between the images.
2884
%
2885
%    o exception: return any errors or warnings in this structure.
2886
%
2887
*/
2888
2889
#if defined(MAGICKCORE_HDRI_SUPPORT) && defined(MAGICKCORE_FFTW_DELEGATE)
2890
static Image *SIMCrossCorrelationImage(const Image *alpha_image,
2891
  const Image *beta_image,ExceptionInfo *exception)
2892
{
2893
  Image
2894
    *alpha_fft = (Image *) NULL,
2895
    *beta_fft = (Image *) NULL,
2896
    *complex_conjugate = (Image *) NULL,
2897
    *complex_multiplication = (Image *) NULL,
2898
    *cross_correlation = (Image *) NULL,
2899
    *temp_image = (Image *) NULL;
2900
2901
  /*
2902
    Take the FFT of beta (reconstruction) image.
2903
  */
2904
  temp_image=CloneImage(beta_image,0,0,MagickTrue,exception);
2905
  if (temp_image == (Image *) NULL)
2906
    return((Image *) NULL);
2907
  (void) SetImageArtifact(temp_image,"fourier:normalize","inverse");
2908
  beta_fft=ForwardFourierTransformImage(temp_image,MagickFalse,exception);
2909
  temp_image=DestroyImageList(temp_image);
2910
  if (beta_fft == (Image *) NULL)
2911
    return((Image *) NULL);
2912
  /*
2913
    Take the complex conjugate of beta_fft.
2914
  */
2915
  complex_conjugate=ComplexImages(beta_fft,ConjugateComplexOperator,exception);
2916
  beta_fft=DestroyImageList(beta_fft);
2917
  if (complex_conjugate == (Image *) NULL)
2918
    return((Image *) NULL);
2919
  /*
2920
    Take the FFT of the alpha (test) image.
2921
  */
2922
  temp_image=CloneImage(alpha_image,0,0,MagickTrue,exception);
2923
  if (temp_image == (Image *) NULL)
2924
    {
2925
      complex_conjugate=DestroyImageList(complex_conjugate);
2926
      return((Image *) NULL);
2927
    }
2928
  (void) SetImageArtifact(temp_image,"fourier:normalize","inverse");
2929
  alpha_fft=ForwardFourierTransformImage(temp_image,MagickFalse,exception);
2930
  temp_image=DestroyImageList(temp_image);
2931
  if (alpha_fft == (Image *) NULL)
2932
    {
2933
      complex_conjugate=DestroyImageList(complex_conjugate);
2934
      return((Image *) NULL);
2935
    }
2936
  /*
2937
    Do complex multiplication.
2938
  */
2939
  DisableCompositeClampUnlessSpecified(complex_conjugate);
2940
  DisableCompositeClampUnlessSpecified(complex_conjugate->next);
2941
  AppendImageToList(&complex_conjugate,alpha_fft);
2942
  complex_multiplication=ComplexImages(complex_conjugate,
2943
    MultiplyComplexOperator,exception);
2944
  complex_conjugate=DestroyImageList(complex_conjugate);
2945
  if (complex_multiplication == (Image *) NULL)
2946
    return((Image *) NULL);
2947
  /*
2948
    Do the IFT and return the cross-correlation result.
2949
  */
2950
  cross_correlation=InverseFourierTransformImage(complex_multiplication,
2951
    complex_multiplication->next,MagickFalse,exception);
2952
  complex_multiplication=DestroyImageList(complex_multiplication);
2953
  return(cross_correlation);
2954
}
2955
2956
static Image *SIMDerivativeImage(const Image *image,const char *kernel,
2957
  ExceptionInfo *exception)
2958
{
2959
  Image
2960
    *derivative_image;
2961
2962
  KernelInfo
2963
    *kernel_info;
2964
2965
  kernel_info=AcquireKernelInfo(kernel,exception);
2966
  if (kernel_info == (KernelInfo *) NULL)
2967
    return((Image *) NULL);
2968
  derivative_image=MorphologyImage(image,ConvolveMorphology,1,kernel_info,
2969
    exception);
2970
  kernel_info=DestroyKernelInfo(kernel_info);
2971
  return(derivative_image);
2972
}
2973
2974
static Image *SIMDivideImage(const Image *numerator_image,
2975
  const Image *denominator_image,ExceptionInfo *exception)
2976
{
2977
  CacheView
2978
    *denominator_view,
2979
    *numerator_view;
2980
2981
  Image
2982
    *divide_image;
2983
2984
  MagickBooleanType
2985
    status = MagickTrue;
2986
2987
  ssize_t
2988
    y;
2989
2990
  /*
2991
    Divide one image into another.
2992
  */
2993
  divide_image=CloneImage(numerator_image,0,0,MagickTrue,exception);
2994
  if (divide_image == (Image *) NULL)
2995
    return(divide_image);
2996
  numerator_view=AcquireAuthenticCacheView(divide_image,exception);
2997
  denominator_view=AcquireVirtualCacheView(denominator_image,exception);
2998
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2999
  #pragma omp parallel for schedule(static) shared(status) \
3000
    magick_number_threads(denominator_image,divide_image,divide_image->rows,1)
3001
#endif
3002
  for (y=0; y < (ssize_t) divide_image->rows; y++)
3003
  {
3004
    const Quantum
3005
      *magick_restrict p;
3006
3007
    Quantum
3008
      *magick_restrict q;
3009
3010
    ssize_t
3011
      x;
3012
3013
    if (status == MagickFalse)
3014
      continue;
3015
    p=GetCacheViewVirtualPixels(denominator_view,0,y,
3016
      denominator_image->columns,1,exception);
3017
    q=GetCacheViewAuthenticPixels(numerator_view,0,y,divide_image->columns,1,
3018
      exception);
3019
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3020
      {
3021
        status=MagickFalse;
3022
        continue;
3023
      }
3024
    for (x=0; x < (ssize_t) divide_image->columns; x++)
3025
    {
3026
      ssize_t
3027
        i;
3028
3029
      for (i=0; i < (ssize_t) GetPixelChannels(divide_image); i++)
3030
      {
3031
        PixelChannel channel = GetPixelChannelChannel(divide_image,i);
3032
        PixelTrait traits = GetPixelChannelTraits(divide_image,channel);
3033
        PixelTrait denominator_traits = GetPixelChannelTraits(denominator_image,
3034
          channel);
3035
        if (((traits & UpdatePixelTrait) == 0) ||
3036
            ((denominator_traits & UpdatePixelTrait) == 0))
3037
          continue;
3038
        q[i]=(Quantum) ((double) q[i]*MagickSafeReciprocal(QuantumScale*
3039
          (double) GetPixelChannel(denominator_image,channel,p)));
3040
      }
3041
      p+=(ptrdiff_t) GetPixelChannels(denominator_image);
3042
      q+=(ptrdiff_t) GetPixelChannels(divide_image);
3043
    }
3044
    if (SyncCacheViewAuthenticPixels(numerator_view,exception) == MagickFalse)
3045
      status=MagickFalse;
3046
  }
3047
  denominator_view=DestroyCacheView(denominator_view);
3048
  numerator_view=DestroyCacheView(numerator_view);
3049
  if (status == MagickFalse)
3050
    divide_image=DestroyImage(divide_image);
3051
  return(divide_image);
3052
}
3053
3054
static Image *SIMDivideByMagnitude(Image *image,Image *magnitude_image,
3055
  const Image *source_image,ExceptionInfo *exception)
3056
{
3057
  Image
3058
    *divide_image,
3059
    *result_image;
3060
3061
  RectangleInfo
3062
    geometry;
3063
3064
  divide_image=SIMDivideImage(image,magnitude_image,exception);
3065
  if (divide_image == (Image *) NULL)
3066
    return((Image *) NULL);
3067
  GetPixelInfoRGBA((Quantum) 0,(Quantum) 0,(Quantum) 0,(Quantum) 0,
3068
    &divide_image->background_color);
3069
  SetGeometry(source_image,&geometry);
3070
  geometry.width=MagickMax(source_image->columns,divide_image->columns);
3071
  geometry.height=MagickMax(source_image->rows,divide_image->rows);
3072
  result_image=ExtentImage(divide_image,&geometry,exception);
3073
  divide_image=DestroyImage(divide_image);
3074
  return(result_image);
3075
}
3076
3077
static MagickBooleanType SIMFilterImageNaNs(Image *image,
3078
  ExceptionInfo *exception)
3079
{
3080
  CacheView
3081
    *image_view;
3082
3083
  MagickBooleanType
3084
    status = MagickTrue;
3085
3086
  ssize_t
3087
    y;
3088
3089
  /*
3090
    Square each pixel in the image.
3091
  */
3092
  image_view=AcquireAuthenticCacheView(image,exception);
3093
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3094
  #pragma omp parallel for schedule(static) shared(status) \
3095
    magick_number_threads(image,image,image->rows,1)
3096
#endif
3097
  for (y=0; y < (ssize_t) image->rows; y++)
3098
  {
3099
    Quantum
3100
      *magick_restrict q;
3101
3102
    ssize_t
3103
      x;
3104
3105
    if (status == MagickFalse)
3106
      continue;
3107
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3108
    if (q == (Quantum *) NULL)
3109
      {
3110
        status=MagickFalse;
3111
        continue;
3112
      }
3113
    for (x=0; x < (ssize_t) image->columns; x++)
3114
    {
3115
      ssize_t
3116
        i;
3117
3118
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3119
      {
3120
        PixelChannel channel = GetPixelChannelChannel(image,i);
3121
        PixelTrait traits = GetPixelChannelTraits(image,channel);
3122
        if ((traits & UpdatePixelTrait) == 0)
3123
          continue;
3124
        if (IsNaN((double) q[i]) != 0)
3125
          q[i]=(Quantum) 0;
3126
      }
3127
      q+=(ptrdiff_t) GetPixelChannels(image);
3128
    }
3129
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3130
      status=MagickFalse;
3131
  }
3132
  image_view=DestroyCacheView(image_view);
3133
  return(status);
3134
}
3135
3136
static Image *SIMSquareImage(const Image *image,ExceptionInfo *exception)
3137
{
3138
  CacheView
3139
    *image_view;
3140
3141
  Image
3142
    *square_image;
3143
3144
  MagickBooleanType
3145
    status = MagickTrue;
3146
3147
  ssize_t
3148
    y;
3149
3150
  /*
3151
    Square each pixel in the image.
3152
  */
3153
  square_image=CloneImage(image,0,0,MagickTrue,exception);
3154
  if (square_image == (Image *) NULL)
3155
    return(square_image);
3156
  image_view=AcquireAuthenticCacheView(square_image,exception);
3157
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3158
  #pragma omp parallel for schedule(static) shared(status) \
3159
    magick_number_threads(square_image,square_image,square_image->rows,1)
3160
#endif
3161
  for (y=0; y < (ssize_t) square_image->rows; y++)
3162
  {
3163
    Quantum
3164
      *magick_restrict q;
3165
3166
    ssize_t
3167
      x;
3168
3169
    if (status == MagickFalse)
3170
      continue;
3171
    q=GetCacheViewAuthenticPixels(image_view,0,y,square_image->columns,1,
3172
      exception);
3173
    if (q == (Quantum *) NULL)
3174
      {
3175
        status=MagickFalse;
3176
        continue;
3177
      }
3178
    for (x=0; x < (ssize_t) square_image->columns; x++)
3179
    {
3180
      ssize_t
3181
        i;
3182
3183
      for (i=0; i < (ssize_t) GetPixelChannels(square_image); i++)
3184
      {
3185
        PixelChannel channel = GetPixelChannelChannel(square_image,i);
3186
        PixelTrait traits = GetPixelChannelTraits(square_image,channel);
3187
        if ((traits & UpdatePixelTrait) == 0)
3188
          continue;
3189
        q[i]=(Quantum) (QuantumScale*(double) q[i]*(double) q[i]);
3190
      }
3191
      q+=(ptrdiff_t) GetPixelChannels(square_image);
3192
    }
3193
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3194
      status=MagickFalse;
3195
  }
3196
  image_view=DestroyCacheView(image_view);
3197
  if (status == MagickFalse)
3198
    square_image=DestroyImage(square_image);
3199
  return(square_image);
3200
}
3201
3202
static Image *SIMMagnitudeImage(Image *alpha_image,Image *beta_image,
3203
  ExceptionInfo *exception)
3204
{
3205
  Image
3206
    *magnitude_image,
3207
    *xsq_image,
3208
    *ysq_image;
3209
3210
  MagickBooleanType
3211
    status = MagickTrue;
3212
3213
  (void) SetImageArtifact(alpha_image,"compose:clamp","False");
3214
  xsq_image=SIMSquareImage(alpha_image,exception);
3215
  if (xsq_image == (Image *) NULL)
3216
    return((Image *) NULL);
3217
  (void) SetImageArtifact(beta_image,"compose:clamp","False");
3218
  ysq_image=SIMSquareImage(beta_image,exception);
3219
  if (ysq_image == (Image *) NULL)
3220
    {
3221
      xsq_image=DestroyImage(xsq_image);
3222
      return((Image *) NULL);
3223
    }
3224
  status=CompositeImage(xsq_image,ysq_image,PlusCompositeOp,MagickTrue,0,0,
3225
    exception);
3226
  magnitude_image=xsq_image;
3227
  ysq_image=DestroyImage(ysq_image);
3228
  if (status == MagickFalse)
3229
    {
3230
      magnitude_image=DestroyImage(magnitude_image);
3231
      return((Image *) NULL);
3232
    }
3233
  status=EvaluateImage(magnitude_image,PowEvaluateOperator,0.5,exception);
3234
  if (status == MagickFalse)
3235
    {
3236
      magnitude_image=DestroyImage(magnitude_image);
3237
      return (Image *) NULL;
3238
    }
3239
  return(magnitude_image);
3240
}
3241
3242
static MagickBooleanType SIMMaximaImage(const Image *image,double *maxima,
3243
  RectangleInfo *offset,ExceptionInfo *exception)
3244
{
3245
  typedef struct
3246
  {
3247
    double
3248
      maxima;
3249
3250
    ssize_t
3251
      x,
3252
      y;
3253
  } MaximaInfo;
3254
3255
  CacheView
3256
    *image_view;
3257
3258
  const Quantum
3259
    *magick_restrict q;
3260
3261
  MagickBooleanType
3262
    status = MagickTrue;
3263
3264
  MaximaInfo
3265
    maxima_info = { -MagickMaximumValue, 0, 0 };
3266
3267
  ssize_t
3268
    y;
3269
3270
  /*
3271
    Identify the maxima value in the image and its location.
3272
  */
3273
  image_view=AcquireVirtualCacheView(image,exception);
3274
  q=GetCacheViewVirtualPixels(image_view,maxima_info.x,maxima_info.y,1,1,
3275
    exception);
3276
  if (q != (const Quantum *) NULL)
3277
    maxima_info.maxima=IsNaN((double) q[0]) != 0 ? 0.0 : (double) q[0];
3278
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3279
  #pragma omp parallel for schedule(static) shared(maxima_info,status) \
3280
    magick_number_threads(image,image,image->rows,1)
3281
#endif
3282
  for (y=0; y < (ssize_t) image->rows; y++)
3283
  {
3284
    const Quantum
3285
      *magick_restrict p;
3286
3287
    MaximaInfo
3288
      channel_maxima = { -MagickMaximumValue, 0, 0 };
3289
3290
    ssize_t
3291
      x;
3292
3293
    if (status == MagickFalse)
3294
      continue;
3295
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
3296
    if (p == (const Quantum *) NULL)
3297
      {
3298
        status=MagickFalse;
3299
        continue;
3300
      }
3301
    channel_maxima=maxima_info;
3302
    for (x=0; x < (ssize_t) image->columns; x++)
3303
    {
3304
      ssize_t
3305
        i;
3306
3307
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3308
      {
3309
        double
3310
          pixel;
3311
3312
        PixelChannel channel = GetPixelChannelChannel(image,i);
3313
        PixelTrait traits = GetPixelChannelTraits(image,channel);
3314
        if ((traits & UpdatePixelTrait) == 0)
3315
          continue;
3316
        pixel=(double) p[i];
3317
        if (IsNaN(pixel) != 0)
3318
          pixel=0.0;
3319
        if (pixel > channel_maxima.maxima)
3320
          {
3321
            channel_maxima.maxima=(double) p[i];
3322
            channel_maxima.x=x;
3323
            channel_maxima.y=y;
3324
          }
3325
      }
3326
      p+=(ptrdiff_t) GetPixelChannels(image);
3327
    }
3328
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3329
    #pragma omp critical (MagickCore_SIMMaximaImage)
3330
#endif
3331
    if (channel_maxima.maxima > maxima_info.maxima)
3332
      maxima_info=channel_maxima;
3333
  }
3334
  image_view=DestroyCacheView(image_view);
3335
  *maxima=maxima_info.maxima;
3336
  offset->x=maxima_info.x;
3337
  offset->y=maxima_info.y;
3338
  return(status);
3339
}
3340
3341
static MagickBooleanType SIMMinimaImage(const Image *image,double *minima,
3342
  RectangleInfo *offset,ExceptionInfo *exception)
3343
{
3344
  typedef struct
3345
  {
3346
    double
3347
      minima;
3348
3349
    ssize_t
3350
      x,
3351
      y;
3352
  } MinimaInfo;
3353
3354
  CacheView
3355
    *image_view;
3356
3357
  const Quantum
3358
    *magick_restrict q;
3359
3360
  MagickBooleanType
3361
    status = MagickTrue;
3362
3363
  MinimaInfo
3364
    minima_info = { MagickMaximumValue, 0, 0 };
3365
3366
  ssize_t
3367
    y;
3368
3369
  /*
3370
    Identify the minima value in the image and its location.
3371
  */
3372
  image_view=AcquireVirtualCacheView(image,exception);
3373
  q=GetCacheViewVirtualPixels(image_view,minima_info.x,minima_info.y,1,1,
3374
    exception);
3375
  if (q != (const Quantum *) NULL)
3376
    minima_info.minima=IsNaN((double) q[0]) != 0 ? 0.0 : (double) q[0];
3377
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3378
  #pragma omp parallel for schedule(static) shared(minima_info,status) \
3379
    magick_number_threads(image,image,image->rows,1)
3380
#endif
3381
  for (y=0; y < (ssize_t) image->rows; y++)
3382
  {
3383
    const Quantum
3384
      *magick_restrict p;
3385
3386
    MinimaInfo
3387
      channel_minima = { MagickMaximumValue, 0, 0 };
3388
3389
    ssize_t
3390
      x;
3391
3392
    if (status == MagickFalse)
3393
      continue;
3394
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
3395
    if (p == (const Quantum *) NULL)
3396
      {
3397
        status=MagickFalse;
3398
        continue;
3399
      }
3400
    channel_minima=minima_info;
3401
    for (x=0; x < (ssize_t) image->columns; x++)
3402
    {
3403
      ssize_t
3404
        i;
3405
3406
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3407
      {
3408
        double
3409
          pixel;
3410
3411
        PixelChannel channel = GetPixelChannelChannel(image,i);
3412
        PixelTrait traits = GetPixelChannelTraits(image,channel);
3413
        if ((traits & UpdatePixelTrait) == 0)
3414
          continue;
3415
        pixel=(double) p[i];
3416
        if (IsNaN(pixel) != 0)
3417
          pixel=0.0;
3418
        if (pixel < channel_minima.minima)
3419
          {
3420
            channel_minima.minima=pixel;
3421
            channel_minima.x=x;
3422
            channel_minima.y=y;
3423
          }
3424
      }
3425
      p+=(ptrdiff_t) GetPixelChannels(image);
3426
    }
3427
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3428
    #pragma omp critical (MagickCore_SIMMinimaImage)
3429
#endif
3430
    if (channel_minima.minima < minima_info.minima)
3431
      minima_info=channel_minima;
3432
  }
3433
  image_view=DestroyCacheView(image_view);
3434
  *minima=minima_info.minima;
3435
  offset->x=minima_info.x;
3436
  offset->y=minima_info.y;
3437
  return(status);
3438
}
3439
3440
static MagickBooleanType SIMMultiplyImage(Image *image,const double factor,
3441
  const ChannelStatistics *channel_statistics,ExceptionInfo *exception)
3442
{
3443
  CacheView
3444
    *image_view;
3445
3446
  MagickBooleanType
3447
    status = MagickTrue;
3448
3449
  ssize_t
3450
    y;
3451
3452
  /*
3453
    Multiply each pixel by a factor.
3454
  */
3455
  image_view=AcquireAuthenticCacheView(image,exception);
3456
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3457
  #pragma omp parallel for schedule(static) shared(status) \
3458
    magick_number_threads(image,image,image->rows,1)
3459
#endif
3460
  for (y=0; y < (ssize_t) image->rows; y++)
3461
  {
3462
    Quantum
3463
      *magick_restrict q;
3464
3465
    ssize_t
3466
      x;
3467
3468
    if (status == MagickFalse)
3469
      continue;
3470
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3471
    if (q == (Quantum *) NULL)
3472
      {
3473
        status=MagickFalse;
3474
        continue;
3475
      }
3476
    for (x=0; x < (ssize_t) image->columns; x++)
3477
    {
3478
      ssize_t
3479
        i;
3480
3481
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3482
      {
3483
        PixelChannel channel = GetPixelChannelChannel(image,i);
3484
        PixelTrait traits = GetPixelChannelTraits(image,channel);
3485
        if ((traits & UpdatePixelTrait) == 0)
3486
          continue;
3487
        if (channel_statistics != (const ChannelStatistics *) NULL)
3488
          q[i]=(Quantum) (factor*(double) q[i]*QuantumScale*
3489
            channel_statistics[channel].standard_deviation);
3490
        else
3491
          q[i]=(Quantum) (factor*(double) q[i]);
3492
      }
3493
      q+=(ptrdiff_t) GetPixelChannels(image);
3494
    }
3495
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3496
      status=MagickFalse;
3497
  }
3498
  image_view=DestroyCacheView(image_view);
3499
  return(status);
3500
}
3501
3502
static Image *SIMPhaseCorrelationImage(const Image *target_image,
3503
  const Image *reconstruct_image,const Image *magnitude_image,
3504
  ExceptionInfo *exception)
3505
{
3506
  Image
3507
    *target_fft = (Image *) NULL,
3508
    *reconstruct_fft = (Image *) NULL,
3509
    *complex_multiplication = (Image *) NULL,
3510
    *cross_correlation = (Image *) NULL;
3511
3512
  /*
3513
    Take the FFT of the reconstruction image.
3514
  */
3515
  reconstruct_fft=CloneImage(reconstruct_image,0,0,MagickTrue,exception);
3516
  if (reconstruct_fft == NULL)
3517
    return((Image *) NULL);
3518
  (void) SetImageArtifact(reconstruct_fft,"fourier:normalize","inverse");
3519
  reconstruct_fft=ForwardFourierTransformImage(reconstruct_fft,MagickFalse,
3520
    exception);
3521
  if (reconstruct_fft == NULL)
3522
    return((Image *) NULL);
3523
  /*
3524
    Take the FFT of the target image.
3525
  */
3526
  target_fft=CloneImage(target_image,0,0,MagickTrue,exception);
3527
  if (target_fft == (Image *) NULL)
3528
    {
3529
      reconstruct_fft=DestroyImageList(reconstruct_fft);
3530
      return((Image *) NULL);
3531
    }
3532
  (void) SetImageArtifact(target_fft,"fourier:normalize","inverse");
3533
  target_fft=ForwardFourierTransformImage(target_fft,MagickFalse,exception);
3534
  if (target_fft == (Image *) NULL)
3535
    {
3536
      reconstruct_fft=DestroyImageList(reconstruct_fft);
3537
      return((Image *) NULL);
3538
    }
3539
  /*
3540
    Take the complex conjugate of the reconstruction FFT.
3541
  */
3542
  reconstruct_fft=ComplexImages(reconstruct_fft,ConjugateComplexOperator,
3543
    exception);
3544
  if (reconstruct_fft == (Image *) NULL)
3545
    {
3546
      target_fft=DestroyImageList(target_fft);
3547
      return((Image *) NULL);
3548
    }
3549
  /*
3550
    Do complex multiplication.
3551
  */
3552
  AppendImageToList(&reconstruct_fft,target_fft);
3553
  DisableCompositeClampUnlessSpecified(reconstruct_fft);
3554
  DisableCompositeClampUnlessSpecified(reconstruct_fft->next);
3555
  complex_multiplication=ComplexImages(reconstruct_fft,MultiplyComplexOperator,
3556
    exception);
3557
  reconstruct_fft=DestroyImageList(reconstruct_fft);
3558
  if (complex_multiplication == (Image *) NULL)
3559
    return((Image *) NULL);
3560
  if (complex_multiplication->next != (Image *) NULL)
3561
    {
3562
      /*
3563
        Normalize the cross-power spectrum by the product magnitude.
3564
      */
3565
      DisableCompositeClampUnlessSpecified(complex_multiplication);
3566
      DisableCompositeClampUnlessSpecified(complex_multiplication->next);
3567
      (void) CompositeImage(complex_multiplication,magnitude_image,
3568
        DivideSrcCompositeOp,MagickTrue,0,0,exception);
3569
      (void) CompositeImage(complex_multiplication->next,magnitude_image,
3570
        DivideSrcCompositeOp,MagickTrue,0,0,exception);
3571
    }
3572
  /*
3573
    Do the IFT and return the phase-correlation result.
3574
  */
3575
  (void) SetImageArtifact(complex_multiplication,"fourier:normalize","inverse");
3576
  cross_correlation=InverseFourierTransformImage(complex_multiplication,
3577
    complex_multiplication->next,MagickFalse,exception);
3578
  complex_multiplication=DestroyImageList(complex_multiplication);
3579
  return(cross_correlation);
3580
}
3581
3582
static MagickBooleanType SIMSetImageMean(Image *image,
3583
  const ChannelStatistics *channel_statistics,ExceptionInfo *exception)
3584
{
3585
  CacheView
3586
    *image_view;
3587
3588
  MagickBooleanType
3589
    status = MagickTrue;
3590
3591
  ssize_t
3592
    y;
3593
3594
  /*
3595
    Set image mean.
3596
  */
3597
  image_view=AcquireAuthenticCacheView(image,exception);
3598
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3599
  #pragma omp parallel for schedule(static) shared(status) \
3600
    magick_number_threads(image,image,image->rows,1)
3601
#endif
3602
  for (y=0; y < (ssize_t) image->rows; y++)
3603
  {
3604
    Quantum
3605
      *magick_restrict q;
3606
3607
    ssize_t
3608
      x;
3609
3610
    if (status == MagickFalse)
3611
      continue;
3612
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3613
    if (q == (Quantum *) NULL)
3614
      {
3615
        status=MagickFalse;
3616
        continue;
3617
      }
3618
    for (x=0; x < (ssize_t) image->columns; x++)
3619
    {
3620
      ssize_t
3621
        i;
3622
3623
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3624
      {
3625
        PixelChannel channel = GetPixelChannelChannel(image,i);
3626
        PixelTrait traits = GetPixelChannelTraits(image,channel);
3627
        if ((traits & UpdatePixelTrait) == 0)
3628
          continue;
3629
        q[i]=(Quantum) channel_statistics[channel].mean;
3630
      }
3631
      q+=(ptrdiff_t) GetPixelChannels(image);
3632
    }
3633
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3634
      status=MagickFalse;
3635
  }
3636
  image_view=DestroyCacheView(image_view);
3637
  return(status);
3638
}
3639
3640
static Image *SIMSubtractImageMean(const Image *alpha_image,
3641
  const Image *beta_image,const ChannelStatistics *channel_statistics,
3642
  ExceptionInfo *exception)
3643
{
3644
  CacheView
3645
    *beta_view,
3646
    *image_view;
3647
3648
  Image
3649
    *subtract_image;
3650
3651
  MagickBooleanType
3652
    status = MagickTrue;
3653
3654
  ssize_t
3655
    y;
3656
3657
  /*
3658
    Subtract the image mean and pad.
3659
  */
3660
  subtract_image=CloneImage(beta_image,alpha_image->columns,alpha_image->rows,
3661
    MagickTrue,exception);
3662
  if (subtract_image == (Image *) NULL)
3663
    return(subtract_image);
3664
  image_view=AcquireAuthenticCacheView(subtract_image,exception);
3665
  beta_view=AcquireVirtualCacheView(beta_image,exception);
3666
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3667
  #pragma omp parallel for schedule(static) shared(status) \
3668
    magick_number_threads(beta_image,subtract_image,subtract_image->rows,1)
3669
#endif
3670
  for (y=0; y < (ssize_t) subtract_image->rows; y++)
3671
  {
3672
    const Quantum
3673
      *magick_restrict p;
3674
3675
    Quantum
3676
      *magick_restrict q;
3677
3678
    ssize_t
3679
      x;
3680
3681
    if (status == MagickFalse)
3682
      continue;
3683
    p=GetCacheViewVirtualPixels(beta_view,0,y,beta_image->columns,1,exception);
3684
    q=GetCacheViewAuthenticPixels(image_view,0,y,subtract_image->columns,1,
3685
      exception);
3686
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3687
      {
3688
        status=MagickFalse;
3689
        continue;
3690
      }
3691
    for (x=0; x < (ssize_t) subtract_image->columns; x++)
3692
    {
3693
      ssize_t
3694
        i;
3695
3696
      for (i=0; i < (ssize_t) GetPixelChannels(subtract_image); i++)
3697
      {
3698
        PixelChannel channel = GetPixelChannelChannel(subtract_image,i);
3699
        PixelTrait traits = GetPixelChannelTraits(subtract_image,channel);
3700
        PixelTrait beta_traits = GetPixelChannelTraits(beta_image,channel);
3701
        if (((traits & UpdatePixelTrait) == 0) ||
3702
            ((beta_traits & UpdatePixelTrait) == 0))
3703
          continue;
3704
        if ((x >= (ssize_t) beta_image->columns) ||
3705
            (y >= (ssize_t) beta_image->rows))
3706
          q[i]=(Quantum) 0;
3707
        else
3708
          q[i]=(Quantum) ((double) GetPixelChannel(beta_image,channel,p)-
3709
            channel_statistics[channel].mean);
3710
      }
3711
      p+=(ptrdiff_t) GetPixelChannels(beta_image);
3712
      q+=(ptrdiff_t) GetPixelChannels(subtract_image);
3713
    }
3714
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3715
      status=MagickFalse;
3716
  }
3717
  beta_view=DestroyCacheView(beta_view);
3718
  image_view=DestroyCacheView(image_view);
3719
  if (status == MagickFalse)
3720
    subtract_image=DestroyImage(subtract_image);
3721
  return(subtract_image);
3722
}
3723
3724
static Image *SIMUnityImage(const Image *alpha_image,const Image *beta_image,
3725
  ExceptionInfo *exception)
3726
{
3727
  CacheView
3728
    *image_view;
3729
3730
  Image
3731
    *unity_image;
3732
3733
  MagickBooleanType
3734
    status = MagickTrue;
3735
3736
  ssize_t
3737
    y;
3738
3739
  /*
3740
    Create a padded unity image.
3741
  */
3742
  unity_image=CloneImage(alpha_image,alpha_image->columns,alpha_image->rows,
3743
    MagickTrue,exception);
3744
  if (unity_image == (Image *) NULL)
3745
    return(unity_image);
3746
  if (SetImageStorageClass(unity_image,DirectClass,exception) == MagickFalse)
3747
    return(DestroyImage(unity_image));
3748
  image_view=AcquireAuthenticCacheView(unity_image,exception);
3749
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3750
  #pragma omp parallel for schedule(static) shared(status) \
3751
    magick_number_threads(unity_image,unity_image,unity_image->rows,1)
3752
#endif
3753
  for (y=0; y < (ssize_t) unity_image->rows; y++)
3754
  {
3755
    Quantum
3756
      *magick_restrict q;
3757
3758
    ssize_t
3759
      x;
3760
3761
    if (status == MagickFalse)
3762
      continue;
3763
    q=GetCacheViewAuthenticPixels(image_view,0,y,unity_image->columns,1,
3764
      exception);
3765
    if (q == (Quantum *) NULL)
3766
      {
3767
        status=MagickFalse;
3768
        continue;
3769
      }
3770
    for (x=0; x < (ssize_t) unity_image->columns; x++)
3771
    {
3772
      ssize_t
3773
        i;
3774
3775
      for (i=0; i < (ssize_t) GetPixelChannels(unity_image); i++)
3776
      {
3777
        PixelChannel channel = GetPixelChannelChannel(unity_image,i);
3778
        PixelTrait traits = GetPixelChannelTraits(unity_image,channel);
3779
        if ((traits & UpdatePixelTrait) == 0)
3780
          continue;
3781
        if ((x >= (ssize_t) beta_image->columns) ||
3782
            (y >= (ssize_t) beta_image->rows))
3783
          q[i]=(Quantum) 0;
3784
        else
3785
          q[i]=QuantumRange;
3786
      }
3787
      q+=(ptrdiff_t) GetPixelChannels(unity_image);
3788
    }
3789
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3790
      status=MagickFalse;
3791
  }
3792
  image_view=DestroyCacheView(image_view);
3793
  if (status == MagickFalse)
3794
    unity_image=DestroyImage(unity_image);
3795
  return(unity_image);
3796
}
3797
3798
static Image *SIMVarianceImage(Image *alpha_image,const Image *beta_image,
3799
  ExceptionInfo *exception)
3800
{
3801
  CacheView
3802
    *beta_view,
3803
    *image_view;
3804
3805
  Image
3806
    *variance_image;
3807
3808
  MagickBooleanType
3809
    status = MagickTrue;
3810
3811
  ssize_t
3812
    y;
3813
3814
  /*
3815
    Compute the variance of the two images.
3816
  */
3817
  variance_image=CloneImage(alpha_image,0,0,MagickTrue,exception);
3818
  if (variance_image == (Image *) NULL)
3819
    return(variance_image);
3820
  image_view=AcquireAuthenticCacheView(variance_image,exception);
3821
  beta_view=AcquireVirtualCacheView(beta_image,exception);
3822
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3823
  #pragma omp parallel for schedule(static) shared(status) \
3824
    magick_number_threads(beta_image,variance_image,variance_image->rows,1)
3825
#endif
3826
  for (y=0; y < (ssize_t) variance_image->rows; y++)
3827
  {
3828
    const Quantum
3829
      *magick_restrict p;
3830
3831
    Quantum
3832
      *magick_restrict q;
3833
3834
    ssize_t
3835
      x;
3836
3837
    if (status == MagickFalse)
3838
      continue;
3839
    p=GetCacheViewVirtualPixels(beta_view,0,y,beta_image->columns,1,
3840
      exception);
3841
    q=GetCacheViewAuthenticPixels(image_view,0,y,variance_image->columns,1,
3842
      exception);
3843
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3844
      {
3845
        status=MagickFalse;
3846
        continue;
3847
      }
3848
    for (x=0; x < (ssize_t) variance_image->columns; x++)
3849
    {
3850
      ssize_t
3851
        i;
3852
3853
      for (i=0; i < (ssize_t) GetPixelChannels(variance_image); i++)
3854
      {
3855
        double
3856
          error;
3857
3858
        PixelChannel channel = GetPixelChannelChannel(variance_image,i);
3859
        PixelTrait traits = GetPixelChannelTraits(variance_image,channel);
3860
        PixelTrait beta_traits = GetPixelChannelTraits(beta_image,channel);
3861
        if (((traits & UpdatePixelTrait) == 0) ||
3862
            ((beta_traits & UpdatePixelTrait) == 0))
3863
          continue;
3864
        error=(double) q[i]-(double) GetPixelChannel(beta_image,channel,p);
3865
        q[i]=(Quantum) ((double) ClampToQuantum((double) QuantumRange*
3866
          (sqrt(fabs(QuantumScale*error))/sqrt((double) QuantumRange))));
3867
      }
3868
      p+=(ptrdiff_t) GetPixelChannels(beta_image);
3869
      q+=(ptrdiff_t) GetPixelChannels(variance_image);
3870
    }
3871
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3872
      status=MagickFalse;
3873
  }
3874
  beta_view=DestroyCacheView(beta_view);
3875
  image_view=DestroyCacheView(image_view);
3876
  if (status == MagickFalse)
3877
    variance_image=DestroyImage(variance_image);
3878
  return(variance_image);
3879
}
3880
3881
static Image *DPCSimilarityImage(const Image *image,const Image *reconstruct,
3882
  RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
3883
{
3884
#define ThrowDPCSimilarityException() \
3885
{ \
3886
  if (dot_product_image != (Image *) NULL) \
3887
    dot_product_image=DestroyImage(dot_product_image); \
3888
  if (magnitude_image != (Image *) NULL) \
3889
    magnitude_image=DestroyImage(magnitude_image); \
3890
  if (reconstruct_image != (Image *) NULL) \
3891
    reconstruct_image=DestroyImage(reconstruct_image); \
3892
  if (rx_image != (Image *) NULL) \
3893
    rx_image=DestroyImage(rx_image); \
3894
  if (ry_image != (Image *) NULL) \
3895
    ry_image=DestroyImage(ry_image); \
3896
  if (target_image != (Image *) NULL) \
3897
    target_image=DestroyImage(target_image); \
3898
  if (threshold_image != (Image *) NULL) \
3899
    threshold_image=DestroyImage(threshold_image); \
3900
  if (trx_image != (Image *) NULL) \
3901
    trx_image=DestroyImage(trx_image); \
3902
  if (try_image != (Image *) NULL) \
3903
    try_image=DestroyImage(try_image); \
3904
  if (tx_image != (Image *) NULL) \
3905
    tx_image=DestroyImage(tx_image); \
3906
  if (ty_image != (Image *) NULL) \
3907
    ty_image=DestroyImage(ty_image); \
3908
  return((Image *) NULL); \
3909
}
3910
3911
  double
3912
    edge_factor = 0.0,
3913
    maxima = 0.0,
3914
    mean = 0.0,
3915
    standard_deviation = 0.0;
3916
3917
  Image
3918
    *dot_product_image = (Image *) NULL,
3919
    *magnitude_image = (Image *) NULL,
3920
    *reconstruct_image = (Image *) NULL,
3921
    *rx_image = (Image *) NULL,
3922
    *ry_image = (Image *) NULL,
3923
    *trx_image = (Image *) NULL,
3924
    *target_image = (Image *) NULL,
3925
    *threshold_image = (Image *) NULL,
3926
    *try_image = (Image *) NULL,
3927
    *tx_image = (Image *) NULL,
3928
    *ty_image = (Image *) NULL;
3929
3930
  MagickBooleanType
3931
    status = MagickTrue;
3932
3933
  RectangleInfo
3934
    geometry;
3935
3936
  /*
3937
    Dot product correlation-based image similarity using FFT local statistics.
3938
  */
3939
  target_image=CloneImage(image,0,0,MagickTrue,exception);
3940
  if (target_image == (Image *) NULL)
3941
    return((Image *) NULL);
3942
  /*
3943
    Compute the cross correlation of the test and reconstruct magnitudes.
3944
  */
3945
  reconstruct_image=CloneImage(reconstruct,0,0,MagickTrue,exception);
3946
  if (reconstruct_image == (Image *) NULL)
3947
    ThrowDPCSimilarityException();
3948
  /*
3949
    Compute X and Y derivatives of reference image.
3950
  */
3951
  (void) SetImageVirtualPixelMethod(reconstruct_image,EdgeVirtualPixelMethod,
3952
    exception);
3953
  rx_image=SIMDerivativeImage(reconstruct_image,"Sobel",exception);
3954
  if (rx_image == (Image *) NULL)
3955
    ThrowDPCSimilarityException();
3956
  ry_image=SIMDerivativeImage(reconstruct_image,"Sobel:90",exception);
3957
  reconstruct_image=DestroyImage(reconstruct_image);
3958
  if (ry_image == (Image *) NULL)
3959
    ThrowDPCSimilarityException();
3960
  /*
3961
    Compute magnitude of derivatives.
3962
  */
3963
  magnitude_image=SIMMagnitudeImage(rx_image,ry_image,exception);
3964
  if (magnitude_image == (Image *) NULL)
3965
    ThrowDPCSimilarityException();
3966
  /*
3967
    Compute an edge normalization correction.
3968
  */
3969
  threshold_image=CloneImage(magnitude_image,0,0,MagickTrue,exception);
3970
  if (threshold_image == (Image *) NULL)
3971
    ThrowDPCSimilarityException();
3972
  status=BilevelImage(threshold_image,0.0,exception);
3973
  if (status == MagickFalse)
3974
    ThrowDPCSimilarityException();
3975
  status=GetImageMean(threshold_image,&mean,&standard_deviation,exception);
3976
  threshold_image=DestroyImage(threshold_image);
3977
  if (status == MagickFalse)
3978
    ThrowDPCSimilarityException();
3979
  edge_factor=MagickSafeReciprocal(QuantumScale*mean*(double)
3980
    reconstruct->columns*(double) reconstruct->rows)+QuantumScale;
3981
  /*
3982
    Divide X and Y derivitives of reference image by magnitude.
3983
  */
3984
  trx_image=SIMDivideByMagnitude(rx_image,magnitude_image,image,exception);
3985
  rx_image=DestroyImage(rx_image);
3986
  if (trx_image == (Image *) NULL)
3987
    ThrowDPCSimilarityException();
3988
  rx_image=trx_image;
3989
  trx_image=(Image *) NULL;
3990
  try_image=SIMDivideByMagnitude(ry_image,magnitude_image,image,exception);
3991
  magnitude_image=DestroyImage(magnitude_image);
3992
  ry_image=DestroyImage(ry_image);
3993
  if (try_image == (Image *) NULL)
3994
    ThrowDPCSimilarityException();
3995
  ry_image=try_image;
3996
  try_image=(Image *) NULL;
3997
  /*
3998
    Compute X and Y derivatives of image.
3999
  */
4000
  (void) SetImageVirtualPixelMethod(target_image,EdgeVirtualPixelMethod,
4001
    exception);
4002
  tx_image=SIMDerivativeImage(target_image,"Sobel",exception);
4003
  if (tx_image == (Image *) NULL)
4004
    ThrowDPCSimilarityException();
4005
  ty_image=SIMDerivativeImage(target_image,"Sobel:90",exception);
4006
  target_image=DestroyImage(target_image);
4007
  if (ty_image == (Image *) NULL)
4008
    ThrowDPCSimilarityException();
4009
  /*
4010
    Compute magnitude of derivatives.
4011
  */
4012
  magnitude_image=SIMMagnitudeImage(tx_image,ty_image,exception);
4013
  if (magnitude_image == (Image *) NULL)
4014
    ThrowDPCSimilarityException();
4015
  /*
4016
    Divide Lx and Ly by magnitude.
4017
  */
4018
  trx_image=SIMDivideByMagnitude(tx_image,magnitude_image,image,exception);
4019
  tx_image=DestroyImage(tx_image);
4020
  if (trx_image == (Image *) NULL)
4021
    ThrowDPCSimilarityException();
4022
  tx_image=trx_image;
4023
  trx_image=(Image *) NULL;
4024
  try_image=SIMDivideByMagnitude(ty_image,magnitude_image,image,exception);
4025
  ty_image=DestroyImage(ty_image);
4026
  magnitude_image=DestroyImage(magnitude_image);
4027
  if (try_image == (Image *) NULL)
4028
    ThrowDPCSimilarityException();
4029
  ty_image=try_image;
4030
  try_image=(Image *) NULL;
4031
  /*
4032
    Compute the cross correlation of the test and reference images.
4033
  */
4034
  trx_image=SIMCrossCorrelationImage(tx_image,rx_image,exception);
4035
  rx_image=DestroyImage(rx_image);
4036
  tx_image=DestroyImage(tx_image);
4037
  if (trx_image == (Image *) NULL)
4038
    ThrowDPCSimilarityException();
4039
  try_image=SIMCrossCorrelationImage(ty_image,ry_image,exception);
4040
  ry_image=DestroyImage(ry_image);
4041
  ty_image=DestroyImage(ty_image);
4042
  if (try_image == (Image *) NULL)
4043
    ThrowDPCSimilarityException();
4044
  /*
4045
    Evaluate dot product correlation image.
4046
  */
4047
  (void) SetImageArtifact(try_image,"compose:clamp","false");
4048
  status=CompositeImage(trx_image,try_image,PlusCompositeOp,MagickTrue,0,0,
4049
    exception);
4050
  try_image=DestroyImage(try_image);
4051
  if (status == MagickFalse)
4052
    ThrowDPCSimilarityException();
4053
  status=SIMMultiplyImage(trx_image,edge_factor,
4054
    (const ChannelStatistics *) NULL,exception);
4055
  if (status == MagickFalse)
4056
    ThrowDPCSimilarityException();
4057
  /*
4058
    Crop results.
4059
  */
4060
  SetGeometry(image,&geometry);
4061
  geometry.width=image->columns;
4062
  geometry.height=image->rows;
4063
  (void) ResetImagePage(trx_image,"0x0+0+0");
4064
  dot_product_image=CropImage(trx_image,&geometry,exception);
4065
  trx_image=DestroyImage(trx_image);
4066
  if (dot_product_image == (Image *) NULL)
4067
    ThrowDPCSimilarityException();
4068
  (void) ResetImagePage(dot_product_image,"0x0+0+0");
4069
  /*
4070
    Identify the maxima value in the image and its location.
4071
  */
4072
  status=GrayscaleImage(dot_product_image,AveragePixelIntensityMethod,
4073
    exception);
4074
  if (status == MagickFalse)
4075
    ThrowDPCSimilarityException();
4076
  dot_product_image->depth=32;
4077
  dot_product_image->colorspace=GRAYColorspace;
4078
  dot_product_image->alpha_trait=UndefinedPixelTrait;
4079
  status=SIMFilterImageNaNs(dot_product_image,exception);
4080
  if (status == MagickFalse)
4081
    ThrowDPCSimilarityException();
4082
  status=SIMMaximaImage(dot_product_image,&maxima,offset,exception);
4083
  if (status == MagickFalse)
4084
    ThrowDPCSimilarityException();
4085
  if ((QuantumScale*maxima) > 1.0)
4086
    {
4087
      status=SIMMultiplyImage(dot_product_image,1.0/(QuantumScale*maxima),
4088
        (const ChannelStatistics *) NULL,exception);
4089
      maxima=(double) QuantumRange;
4090
    }
4091
  *similarity_metric=QuantumScale*maxima;
4092
  return(dot_product_image);
4093
}
4094
4095
static Image *MSESimilarityImage(const Image *image,const Image *reconstruct,
4096
  RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
4097
{
4098
#define ThrowMSESimilarityException() \
4099
{ \
4100
  if (alpha_image != (Image *) NULL) \
4101
    alpha_image=DestroyImage(alpha_image); \
4102
  if (beta_image != (Image *) NULL) \
4103
    beta_image=DestroyImage(beta_image); \
4104
  if (channel_statistics != (ChannelStatistics *) NULL) \
4105
    channel_statistics=(ChannelStatistics *) \
4106
      RelinquishMagickMemory(channel_statistics); \
4107
  if (mean_image != (Image *) NULL) \
4108
    mean_image=DestroyImage(mean_image); \
4109
  if (mse_image != (Image *) NULL) \
4110
    mse_image=DestroyImage(mse_image); \
4111
  if (reconstruct_image != (Image *) NULL) \
4112
    reconstruct_image=DestroyImage(reconstruct_image); \
4113
  if (sum_image != (Image *) NULL) \
4114
    sum_image=DestroyImage(sum_image); \
4115
  if (alpha_image != (Image *) NULL) \
4116
    alpha_image=DestroyImage(alpha_image); \
4117
  return((Image *) NULL); \
4118
}
4119
4120
  ChannelStatistics
4121
    *channel_statistics = (ChannelStatistics *) NULL;
4122
4123
  double
4124
    minima = 0.0;
4125
4126
  Image
4127
    *alpha_image = (Image *) NULL,
4128
    *beta_image = (Image *) NULL,
4129
    *mean_image = (Image *) NULL,
4130
    *mse_image = (Image *) NULL,
4131
    *reconstruct_image = (Image *) NULL,
4132
    *sum_image = (Image *) NULL,
4133
    *target_image = (Image *) NULL;
4134
4135
  MagickBooleanType
4136
    status = MagickTrue;
4137
4138
  RectangleInfo
4139
    geometry;
4140
4141
  /*
4142
    MSE correlation-based image similarity using FFT local statistics.
4143
  */
4144
  target_image=SIMSquareImage(image,exception);
4145
  if (target_image == (Image *) NULL)
4146
    ThrowMSESimilarityException();
4147
  reconstruct_image=SIMUnityImage(image,reconstruct,exception);
4148
  if (reconstruct_image == (Image *) NULL)
4149
    ThrowMSESimilarityException();
4150
  /*
4151
    Create (U * test)/# pixels.
4152
  */
4153
  alpha_image=SIMCrossCorrelationImage(target_image,reconstruct_image,
4154
    exception);
4155
  target_image=DestroyImage(target_image);
4156
  if (alpha_image == (Image *) NULL)
4157
    ThrowMSESimilarityException();
4158
  status=SIMMultiplyImage(alpha_image,1.0/(double) reconstruct->columns/(double)
4159
    reconstruct->rows,(const ChannelStatistics *) NULL,exception);
4160
  if (status == MagickFalse)
4161
    ThrowMSESimilarityException();
4162
  /*
4163
    Create 2*(test * reconstruction)# pixels.
4164
  */
4165
  (void) CompositeImage(reconstruct_image,reconstruct,CopyCompositeOp,
4166
    MagickTrue,0,0,exception);
4167
  beta_image=SIMCrossCorrelationImage(image,reconstruct_image,exception);
4168
  if (beta_image == (Image *) NULL)
4169
    {
4170
      reconstruct_image=DestroyImage(reconstruct_image);
4171
      ThrowMSESimilarityException();
4172
    }
4173
  status=SIMMultiplyImage(beta_image,-2.0/(double) reconstruct->columns/(double)
4174
    reconstruct->rows,(const ChannelStatistics *) NULL,exception);
4175
  reconstruct_image=DestroyImage(reconstruct_image);
4176
  if (status == MagickFalse)
4177
    ThrowMSESimilarityException();
4178
  /*
4179
    Mean of reconstruction squared.
4180
  */
4181
  sum_image=SIMSquareImage(reconstruct,exception);
4182
  if (sum_image == (Image *) NULL)
4183
    ThrowMSESimilarityException();
4184
  channel_statistics=GetImageStatistics(sum_image,exception);
4185
  if (channel_statistics == (ChannelStatistics *) NULL)
4186
    ThrowMSESimilarityException();
4187
  status=SetImageStorageClass(sum_image,DirectClass,exception);
4188
  if (status == MagickFalse)
4189
    ThrowMSESimilarityException();
4190
  status=SIMSetImageMean(sum_image,channel_statistics,exception);
4191
  channel_statistics=(ChannelStatistics *)
4192
    RelinquishMagickMemory(channel_statistics);
4193
  if (status == MagickFalse)
4194
    ThrowMSESimilarityException();
4195
  /*
4196
    Create mean image.
4197
  */
4198
  AppendImageToList(&sum_image,alpha_image);
4199
  AppendImageToList(&sum_image,beta_image);
4200
  mean_image=EvaluateImages(sum_image,SumEvaluateOperator,exception);
4201
  if (mean_image == (Image *) NULL)
4202
    ThrowMSESimilarityException();
4203
  sum_image=DestroyImage(sum_image);
4204
  status=GrayscaleImage(mean_image,AveragePixelIntensityMethod,exception);
4205
  if (status == MagickFalse)
4206
    ThrowMSESimilarityException();
4207
  /*
4208
    Crop to difference of reconstruction and test images.
4209
  */
4210
  SetGeometry(image,&geometry);
4211
  geometry.width=image->columns;
4212
  geometry.height=image->rows;
4213
  (void) ResetImagePage(mean_image,"0x0+0+0");
4214
  mse_image=CropImage(mean_image,&geometry,exception);
4215
  mean_image=DestroyImage(mean_image);
4216
  if (mse_image == (Image *) NULL)
4217
    ThrowMSESimilarityException();
4218
  /*
4219
    Identify the minima value in the correlation image and its location.
4220
  */
4221
  (void) ResetImagePage(mse_image,"0x0+0+0");
4222
  (void) ClampImage(mse_image,exception);
4223
  mse_image->depth=32;
4224
  mse_image->colorspace=GRAYColorspace;
4225
  mse_image->alpha_trait=UndefinedPixelTrait;
4226
  status=SIMMinimaImage(mse_image,&minima,offset,exception);
4227
  if (status == MagickFalse)
4228
    ThrowMSESimilarityException();
4229
  status=NegateImage(mse_image,MagickFalse,exception);
4230
  if (status == MagickFalse)
4231
    ThrowMSESimilarityException();
4232
  alpha_image=DestroyImage(alpha_image);
4233
  beta_image=DestroyImage(beta_image);
4234
  if ((QuantumScale*minima) < (double) FLT_EPSILON)
4235
    minima=0.0;
4236
  *similarity_metric=QuantumScale*minima;
4237
  return(mse_image);
4238
}
4239
4240
static Image *NCCSimilarityImage(const Image *image,const Image *reconstruct,
4241
  RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
4242
{
4243
#define ThrowNCCSimilarityException() \
4244
{ \
4245
  if (alpha_image != (Image *) NULL) \
4246
    alpha_image=DestroyImage(alpha_image); \
4247
  if (beta_image != (Image *) NULL) \
4248
    beta_image=DestroyImage(beta_image); \
4249
  if (channel_statistics != (ChannelStatistics *) NULL) \
4250
    channel_statistics=(ChannelStatistics *) \
4251
      RelinquishMagickMemory(channel_statistics); \
4252
  if (correlation_image != (Image *) NULL) \
4253
    correlation_image=DestroyImage(correlation_image); \
4254
  if (divide_image != (Image *) NULL) \
4255
    divide_image=DestroyImage(divide_image); \
4256
  if (ncc_image != (Image *) NULL) \
4257
    ncc_image=DestroyImage(ncc_image); \
4258
  if (normalize_image != (Image *) NULL) \
4259
    normalize_image=DestroyImage(normalize_image); \
4260
  if (reconstruct_image != (Image *) NULL) \
4261
    reconstruct_image=DestroyImage(reconstruct_image); \
4262
  if (target_image != (Image *) NULL) \
4263
    target_image=DestroyImage(target_image); \
4264
  if (variance_image != (Image *) NULL) \
4265
    variance_image=DestroyImage(variance_image); \
4266
  return((Image *) NULL); \
4267
}
4268
4269
  ChannelStatistics
4270
    *channel_statistics = (ChannelStatistics *) NULL;
4271
4272
  double
4273
    maxima = 0.0;
4274
4275
  Image
4276
    *alpha_image = (Image *) NULL,
4277
    *beta_image = (Image *) NULL,
4278
    *correlation_image = (Image *) NULL,
4279
    *divide_image = (Image *) NULL,
4280
    *ncc_image = (Image *) NULL,
4281
    *normalize_image = (Image *) NULL,
4282
    *reconstruct_image = (Image *) NULL,
4283
    *target_image = (Image *) NULL,
4284
    *variance_image = (Image *) NULL;
4285
4286
  MagickBooleanType
4287
    status = MagickTrue;
4288
4289
  RectangleInfo
4290
    geometry;
4291
4292
  /*
4293
    NCC correlation-based image similarity with FFT local statistics.
4294
  */
4295
  target_image=SIMSquareImage(image,exception);
4296
  if (target_image == (Image *) NULL)
4297
    ThrowNCCSimilarityException();
4298
  reconstruct_image=SIMUnityImage(image,reconstruct,exception);
4299
  if (reconstruct_image == (Image *) NULL)
4300
    ThrowNCCSimilarityException();
4301
  /*
4302
    Compute the cross correlation of the test and reconstruction images.
4303
  */
4304
  alpha_image=SIMCrossCorrelationImage(target_image,reconstruct_image,
4305
    exception);
4306
  target_image=DestroyImage(target_image);
4307
  if (alpha_image == (Image *) NULL)
4308
    ThrowNCCSimilarityException();
4309
  status=SIMMultiplyImage(alpha_image,(double) QuantumRange*
4310
    (double) reconstruct->columns*(double) reconstruct->rows,
4311
    (const ChannelStatistics *) NULL,exception);
4312
  if (status == MagickFalse)
4313
    ThrowNCCSimilarityException();
4314
  /*
4315
    Compute the cross correlation of the source and reconstruction images.
4316
  */
4317
  beta_image=SIMCrossCorrelationImage(image,reconstruct_image,exception);
4318
  reconstruct_image=DestroyImage(reconstruct_image);
4319
  if (beta_image == (Image *) NULL)
4320
    ThrowNCCSimilarityException();
4321
  target_image=SIMSquareImage(beta_image,exception);
4322
  beta_image=DestroyImage(beta_image);
4323
  if (target_image == (Image *) NULL)
4324
    ThrowNCCSimilarityException();
4325
  status=SIMMultiplyImage(target_image,(double) QuantumRange,
4326
    (const ChannelStatistics *) NULL,exception);
4327
  if (status == MagickFalse)
4328
    ThrowNCCSimilarityException();
4329
  /*
4330
    Compute the variance of the two images.
4331
  */
4332
  variance_image=SIMVarianceImage(alpha_image,target_image,exception);
4333
  target_image=DestroyImage(target_image);
4334
  alpha_image=DestroyImage(alpha_image);
4335
  if (variance_image == (Image *) NULL)
4336
    ThrowNCCSimilarityException();
4337
  /*
4338
    Subtract the image mean.
4339
  */
4340
  channel_statistics=GetImageStatistics(reconstruct,exception);
4341
  if (channel_statistics == (ChannelStatistics *) NULL)
4342
    ThrowNCCSimilarityException();
4343
  status=SIMMultiplyImage(variance_image,1.0,channel_statistics,exception);
4344
  if (status == MagickFalse)
4345
    ThrowNCCSimilarityException();
4346
  normalize_image=SIMSubtractImageMean(image,reconstruct,channel_statistics,
4347
    exception);
4348
  channel_statistics=(ChannelStatistics *)
4349
    RelinquishMagickMemory(channel_statistics);
4350
  if (normalize_image == (Image *) NULL)
4351
    ThrowNCCSimilarityException();
4352
  correlation_image=SIMCrossCorrelationImage(image,normalize_image,exception);
4353
  normalize_image=DestroyImage(normalize_image);
4354
  if (correlation_image == (Image *) NULL)
4355
    ThrowNCCSimilarityException();
4356
  /*
4357
    Divide the two images.
4358
  */
4359
  divide_image=SIMDivideImage(correlation_image,variance_image,exception);
4360
  correlation_image=DestroyImage(correlation_image);
4361
  variance_image=DestroyImage(variance_image);
4362
  if (divide_image == (Image *) NULL)
4363
    ThrowNCCSimilarityException();
4364
  /*
4365
    Crop padding.
4366
  */
4367
  SetGeometry(image,&geometry);
4368
  geometry.width=image->columns;
4369
  geometry.height=image->rows;
4370
  (void) ResetImagePage(divide_image,"0x0+0+0");
4371
  ncc_image=CropImage(divide_image,&geometry,exception);
4372
  divide_image=DestroyImage(divide_image);
4373
  if (ncc_image == (Image *) NULL)
4374
    ThrowNCCSimilarityException();
4375
  /*
4376
    Identify the maxima value in the image and its location.
4377
  */
4378
  (void) ResetImagePage(ncc_image,"0x0+0+0");
4379
  status=GrayscaleImage(ncc_image,AveragePixelIntensityMethod,exception);
4380
  if (status == MagickFalse)
4381
    ThrowNCCSimilarityException();
4382
  ncc_image->depth=32;
4383
  ncc_image->colorspace=GRAYColorspace;
4384
  ncc_image->alpha_trait=UndefinedPixelTrait;
4385
  status=SIMMaximaImage(ncc_image,&maxima,offset,exception);
4386
  if (status == MagickFalse)
4387
    ThrowNCCSimilarityException();
4388
  if ((QuantumScale*maxima) > 1.0)
4389
    {
4390
      status=SIMMultiplyImage(ncc_image,1.0/(QuantumScale*maxima),
4391
        (const ChannelStatistics *) NULL,exception);
4392
      maxima=(double) QuantumRange;
4393
    }
4394
  *similarity_metric=QuantumScale*maxima;
4395
  return(ncc_image);
4396
}
4397
4398
static Image *PhaseSimilarityImage(const Image *image,const Image *reconstruct,
4399
  RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
4400
{
4401
#define ThrowPhaseSimilarityException() \
4402
{ \
4403
  if (phase_image != (Image *) NULL) \
4404
    phase_image=DestroyImage(phase_image); \
4405
  if (gamma_image != (Image *) NULL) \
4406
    gamma_image=DestroyImage(gamma_image); \
4407
  if (test_magnitude != (Image *) NULL) \
4408
    test_magnitude=DestroyImage(test_magnitude); \
4409
  if (magnitude_image != (Image *) NULL) \
4410
    magnitude_image=DestroyImage(magnitude_image); \
4411
  if (reconstruct_magnitude != (Image *) NULL) \
4412
    reconstruct_magnitude=DestroyImage(reconstruct_magnitude); \
4413
  if (correlation_image != (Image *) NULL) \
4414
    correlation_image=DestroyImage(correlation_image); \
4415
  if (fft_images != (Image *) NULL) \
4416
    fft_images=DestroyImageList(fft_images); \
4417
  if (reconstruct_image != (Image *) NULL) \
4418
    reconstruct_image=DestroyImage(reconstruct_image); \
4419
  if (target_image != (Image *) NULL) \
4420
    target_image=DestroyImage(target_image); \
4421
  return((Image *) NULL); \
4422
}
4423
4424
  double
4425
    maxima = 0.0;
4426
4427
  Image
4428
    *correlation_image = (Image *) NULL,
4429
    *fft_images = (Image *) NULL,
4430
    *gamma_image = (Image *) NULL,
4431
    *magnitude_image = (Image *) NULL,
4432
    *phase_image = (Image *) NULL,
4433
    *reconstruct_image = (Image *) NULL,
4434
    *reconstruct_magnitude = (Image *) NULL,
4435
    *target_image = (Image *) NULL,
4436
    *test_magnitude = (Image *) NULL;
4437
4438
  MagickBooleanType
4439
    status = MagickTrue;
4440
4441
  RectangleInfo
4442
    geometry;
4443
4444
  /*
4445
    Phase correlation-based image similarity using FFT local statistics.
4446
  */
4447
  target_image=CloneImage(image,0,0,MagickTrue,exception);
4448
  if (target_image == (Image *) NULL)
4449
    ThrowPhaseSimilarityException();
4450
  (void) ResetImagePage(target_image,"0x0+0+0");
4451
  GetPixelInfoRGBA((Quantum) 0,(Quantum) 0,(Quantum) 0,(Quantum) 0,
4452
    &target_image->background_color);
4453
  status=SetImageExtent(target_image,2*CastDoubleToSizeT(ceil((double)
4454
    image->columns/2.0)),2*CastDoubleToSizeT(ceil((double) image->rows/2.0)),
4455
    exception);
4456
  if (status == MagickFalse)
4457
    ThrowPhaseSimilarityException();
4458
  /*
4459
    Compute the cross correlation of the target and reconstruct magnitudes.
4460
  */
4461
  reconstruct_image=CloneImage(reconstruct,0,0,MagickTrue,exception);
4462
  if (reconstruct_image == (Image *) NULL)
4463
    ThrowPhaseSimilarityException();
4464
  (void) ResetImagePage(reconstruct_image,"0x0+0+0");
4465
  GetPixelInfoRGBA((Quantum) 0,(Quantum) 0,(Quantum) 0,(Quantum) 0,
4466
    &reconstruct_image->background_color);
4467
  status=SetImageExtent(reconstruct_image,2*CastDoubleToSizeT(ceil((double)
4468
    image->columns/2.0)),2*CastDoubleToSizeT(ceil((double) image->rows/2.0)),
4469
    exception);
4470
  if (status == MagickFalse)
4471
    ThrowPhaseSimilarityException();
4472
  /*
4473
    Evaluate phase coorelation image and divide by the product magnitude.
4474
  */
4475
  (void) SetImageArtifact(target_image,"fourier:normalize","inverse");
4476
  fft_images=ForwardFourierTransformImage(target_image,MagickTrue,exception);
4477
  if (fft_images == (Image *) NULL)
4478
    ThrowPhaseSimilarityException();
4479
  test_magnitude=CloneImage(fft_images,0,0,MagickTrue,exception);
4480
  fft_images=DestroyImageList(fft_images);
4481
  if (test_magnitude == (Image *) NULL)
4482
    ThrowPhaseSimilarityException();
4483
  (void) SetImageArtifact(reconstruct_image,"fourier:normalize","inverse");
4484
  fft_images=ForwardFourierTransformImage(reconstruct_image,MagickTrue,
4485
    exception);
4486
  if (fft_images == (Image *) NULL)
4487
    ThrowPhaseSimilarityException();
4488
  reconstruct_magnitude=CloneImage(fft_images,0,0,MagickTrue,exception);
4489
  fft_images=DestroyImageList(fft_images);
4490
  if (reconstruct_magnitude == (Image *) NULL)
4491
    ThrowPhaseSimilarityException();
4492
  magnitude_image=CloneImage(reconstruct_magnitude,0,0,MagickTrue,exception);
4493
  if (magnitude_image == (Image *) NULL)
4494
    ThrowPhaseSimilarityException();
4495
  DisableCompositeClampUnlessSpecified(magnitude_image);
4496
  (void) CompositeImage(magnitude_image,test_magnitude,MultiplyCompositeOp,
4497
    MagickTrue,0,0,exception);
4498
  /*
4499
    Compute the cross correlation of the target and reconstruction images.
4500
  */
4501
  correlation_image=SIMPhaseCorrelationImage(target_image,reconstruct_image,
4502
    magnitude_image,exception);
4503
  target_image=DestroyImage(target_image);
4504
  reconstruct_image=DestroyImage(reconstruct_image);
4505
  test_magnitude=DestroyImage(test_magnitude);
4506
  reconstruct_magnitude=DestroyImage(reconstruct_magnitude);
4507
  if (correlation_image == (Image *) NULL)
4508
    ThrowPhaseSimilarityException();
4509
  gamma_image=CloneImage(correlation_image,0,0,MagickTrue,exception);
4510
  correlation_image=DestroyImage(correlation_image);
4511
  if (gamma_image == (Image *) NULL)
4512
    ThrowPhaseSimilarityException();
4513
  /*
4514
    Crop padding.
4515
  */
4516
  SetGeometry(image,&geometry);
4517
  geometry.width=image->columns;
4518
  geometry.height=image->rows;
4519
  (void) ResetImagePage(gamma_image,"0x0+0+0");
4520
  phase_image=CropImage(gamma_image,&geometry,exception);
4521
  gamma_image=DestroyImage(gamma_image);
4522
  if (phase_image == (Image *) NULL)
4523
    ThrowPhaseSimilarityException();
4524
  (void) ResetImagePage(phase_image,"0x0+0+0");
4525
  /*
4526
    Identify the maxima value in the correlation image and its location.
4527
  */
4528
  status=GrayscaleImage(phase_image,AveragePixelIntensityMethod,exception);
4529
  if (status == MagickFalse)
4530
    ThrowPhaseSimilarityException();
4531
  phase_image->depth=32;
4532
  phase_image->colorspace=GRAYColorspace;
4533
  phase_image->alpha_trait=UndefinedPixelTrait;
4534
  status=SIMFilterImageNaNs(phase_image,exception);
4535
  if (status == MagickFalse)
4536
    ThrowPhaseSimilarityException();
4537
  status=SIMMaximaImage(phase_image,&maxima,offset,exception);
4538
  if (status == MagickFalse)
4539
    ThrowPhaseSimilarityException();
4540
  magnitude_image=DestroyImage(magnitude_image);
4541
  *similarity_metric=QuantumScale*maxima;
4542
  return(phase_image);
4543
}
4544
4545
static Image *PSNRSimilarityImage(const Image *image,const Image *reconstruct,
4546
  RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
4547
{
4548
  Image
4549
    *psnr_image = (Image *) NULL;
4550
4551
  psnr_image=MSESimilarityImage(image,reconstruct,offset,similarity_metric,
4552
    exception);
4553
  if (psnr_image == (Image *) NULL)
4554
    return(psnr_image);
4555
  *similarity_metric=10.0*MagickSafeLog10(MagickSafeReciprocal(
4556
    *similarity_metric))/MagickSafePSNRRecipicol(10.0);
4557
  return(psnr_image);
4558
}
4559
4560
static Image *RMSESimilarityImage(const Image *image,const Image *reconstruct,
4561
  RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
4562
{
4563
  Image
4564
    *rmse_image = (Image *) NULL;
4565
4566
  rmse_image=MSESimilarityImage(image,reconstruct,offset,similarity_metric,
4567
    exception);
4568
  if (rmse_image == (Image *) NULL)
4569
    return(rmse_image);
4570
  *similarity_metric=sqrt(*similarity_metric);
4571
  return(rmse_image);
4572
}
4573
#endif
4574
4575
static double GetSimilarityMetric(const Image *image,
4576
  const Image *reconstruct_image,const MetricType metric,
4577
  const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
4578
0
{
4579
0
  double
4580
0
    *channel_similarity,
4581
0
    similarity = 0.0;
4582
4583
0
  ExceptionInfo
4584
0
    *sans_exception = AcquireExceptionInfo();
4585
4586
0
  Image
4587
0
    *similarity_image;
4588
4589
0
  MagickBooleanType
4590
0
    status = MagickTrue;
4591
4592
0
  RectangleInfo
4593
0
    geometry;
4594
4595
0
  size_t
4596
0
    length = MaxPixelChannels+1UL;
4597
4598
0
  SetGeometry(reconstruct_image,&geometry);
4599
0
  geometry.x=x_offset;
4600
0
  geometry.y=y_offset;
4601
0
  similarity_image=CropImage(image,&geometry,sans_exception);
4602
0
  sans_exception=DestroyExceptionInfo(sans_exception);
4603
0
  if (similarity_image == (Image *) NULL)
4604
0
    return(NAN);
4605
  /*
4606
    Get image distortion.
4607
  */
4608
0
  channel_similarity=(double *) AcquireQuantumMemory(length,
4609
0
    sizeof(*channel_similarity));
4610
0
  if (channel_similarity == (double *) NULL)
4611
0
    return(NAN);
4612
0
  (void) memset(channel_similarity,0,length*sizeof(*channel_similarity));
4613
0
  switch (metric)
4614
0
  {
4615
0
    case AbsoluteErrorMetric:
4616
0
    {
4617
0
      status=GetAESimilarity(similarity_image,reconstruct_image,
4618
0
        channel_similarity,exception);
4619
0
      break;
4620
0
    }
4621
0
    case DotProductCorrelationErrorMetric:
4622
0
    {
4623
0
      status=GetDPCSimilarity(similarity_image,reconstruct_image,
4624
0
        channel_similarity,exception);
4625
0
      break;
4626
0
    }
4627
0
    case FuzzErrorMetric:
4628
0
    {
4629
0
      status=GetFUZZSimilarity(similarity_image,reconstruct_image,
4630
0
        channel_similarity,exception);
4631
0
      break;
4632
0
    }
4633
0
    case MeanAbsoluteErrorMetric:
4634
0
    {
4635
0
      status=GetMAESimilarity(similarity_image,reconstruct_image,
4636
0
        channel_similarity,exception);
4637
0
      break;
4638
0
    }
4639
0
    case MeanErrorPerPixelErrorMetric:
4640
0
    {
4641
0
      status=GetMEPPSimilarity(similarity_image,reconstruct_image,
4642
0
        channel_similarity,exception);
4643
0
      break;
4644
0
    }
4645
0
    case MeanSquaredErrorMetric:
4646
0
    {
4647
0
      status=GetMSESimilarity(similarity_image,reconstruct_image,
4648
0
        channel_similarity,exception);
4649
0
      break;
4650
0
    }
4651
0
    case NormalizedCrossCorrelationErrorMetric:
4652
0
    {
4653
0
      status=GetNCCSimilarity(similarity_image,reconstruct_image,
4654
0
        channel_similarity,exception);
4655
0
      break;
4656
0
    }
4657
0
    case PeakAbsoluteErrorMetric:
4658
0
    {
4659
0
      status=GetPASimilarity(similarity_image,reconstruct_image,
4660
0
        channel_similarity,exception);
4661
0
      break;
4662
0
    }
4663
0
    case PeakSignalToNoiseRatioErrorMetric:
4664
0
    {
4665
0
      status=GetPSNRSimilarity(similarity_image,reconstruct_image,
4666
0
        channel_similarity,exception);
4667
0
      break;
4668
0
    }
4669
0
    case PerceptualHashErrorMetric:
4670
0
    {
4671
0
      status=GetPHASHSimilarity(similarity_image,reconstruct_image,
4672
0
        channel_similarity,exception);
4673
0
      break;
4674
0
    }
4675
0
    case PhaseCorrelationErrorMetric:
4676
0
    {
4677
0
      status=GetPHASESimilarity(similarity_image,reconstruct_image,
4678
0
        channel_similarity,exception);
4679
0
      break;
4680
0
    }
4681
0
    case PixelDifferenceCountErrorMetric:
4682
0
    {
4683
0
      status=GetPDCSimilarity(similarity_image,reconstruct_image,
4684
0
        channel_similarity,exception);
4685
0
      break;
4686
0
    }
4687
0
    case RootMeanSquaredErrorMetric:
4688
0
    case UndefinedErrorMetric:
4689
0
    default:
4690
0
    {
4691
0
      status=GetRMSESimilarity(similarity_image,reconstruct_image,
4692
0
        channel_similarity,exception);
4693
0
      break;
4694
0
    }
4695
0
    case StructuralDissimilarityErrorMetric:
4696
0
    {
4697
0
      status=GetDSSIMSimilarity(similarity_image,reconstruct_image,
4698
0
        channel_similarity,exception);
4699
0
      break;
4700
0
    }
4701
0
    case StructuralSimilarityErrorMetric:
4702
0
    {
4703
0
      status=GetSSIMSimularity(similarity_image,reconstruct_image,
4704
0
        channel_similarity,exception);
4705
0
      break;
4706
0
    }
4707
0
  }
4708
0
  similarity_image=DestroyImage(similarity_image);
4709
0
  similarity=channel_similarity[CompositePixelChannel];
4710
0
  channel_similarity=(double *) RelinquishMagickMemory(channel_similarity);
4711
0
  if (status == MagickFalse)
4712
0
    return(NAN);
4713
0
  return(similarity);
4714
0
}
4715
4716
MagickExport Image *SimilarityImage(const Image *image,const Image *reconstruct,
4717
  const MetricType metric,const double similarity_threshold,
4718
  RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
4719
0
{
4720
0
#define SimilarityImageTag  "Similarity/Image"
4721
4722
0
  typedef struct
4723
0
  {
4724
0
    double
4725
0
      similarity;
4726
4727
0
    ssize_t
4728
0
      x,
4729
0
      y;
4730
0
  } SimilarityInfo;
4731
4732
0
  CacheView
4733
0
    *similarity_view;
4734
4735
0
  Image
4736
0
    *similarity_image = (Image *) NULL;
4737
4738
0
  MagickBooleanType
4739
0
    status = MagickTrue,
4740
0
    threshold_trigger = MagickFalse;
4741
4742
0
  MagickOffsetType
4743
0
    progress = 0;
4744
4745
0
  SimilarityInfo
4746
0
    similarity_info = { 0.0, 0, 0 };
4747
4748
0
  size_t
4749
0
    columns,
4750
0
    rows;
4751
4752
0
  ssize_t
4753
0
    y;
4754
4755
0
  assert(image != (const Image *) NULL);
4756
0
  assert(image->signature == MagickCoreSignature);
4757
0
  assert(exception != (ExceptionInfo *) NULL);
4758
0
  assert(exception->signature == MagickCoreSignature);
4759
0
  assert(offset != (RectangleInfo *) NULL);
4760
0
  if (IsEventLogging() != MagickFalse)
4761
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4762
0
  SetGeometry(reconstruct,offset);
4763
0
  *similarity_metric=0.0;
4764
0
  offset->x=0;
4765
0
  offset->y=0;
4766
#if defined(MAGICKCORE_HDRI_SUPPORT) && defined(MAGICKCORE_FFTW_DELEGATE)
4767
{
4768
  const char *artifact = GetImageArtifact(image,"compare:frequency-domain");
4769
  if (artifact == (const char *) NULL)
4770
    artifact=GetImageArtifact(image,"compare:accelerate-ncc");
4771
  if (((artifact == (const char *) NULL) ||
4772
       (IsStringTrue(artifact) != MagickFalse)) &&
4773
      ((image->channels & ReadMaskChannel) == 0))
4774
    switch (metric)
4775
    {
4776
      case DotProductCorrelationErrorMetric:
4777
      {
4778
        similarity_image=DPCSimilarityImage(image,reconstruct,offset,
4779
          similarity_metric,exception);
4780
        return(similarity_image);
4781
      }
4782
      case MeanSquaredErrorMetric:
4783
      {
4784
        similarity_image=MSESimilarityImage(image,reconstruct,offset,
4785
          similarity_metric,exception);
4786
        return(similarity_image);
4787
      }
4788
      case NormalizedCrossCorrelationErrorMetric:
4789
      {
4790
        similarity_image=NCCSimilarityImage(image,reconstruct,offset,
4791
          similarity_metric,exception);
4792
        return(similarity_image);
4793
      }
4794
      case PeakSignalToNoiseRatioErrorMetric:
4795
      {
4796
        similarity_image=PSNRSimilarityImage(image,reconstruct,offset,
4797
          similarity_metric,exception);
4798
        return(similarity_image);
4799
      }
4800
      case PhaseCorrelationErrorMetric:
4801
      {
4802
        similarity_image=PhaseSimilarityImage(image,reconstruct,offset,
4803
          similarity_metric,exception);
4804
        return(similarity_image);
4805
      }
4806
      case RootMeanSquaredErrorMetric:
4807
      case UndefinedErrorMetric:
4808
      {
4809
        similarity_image=RMSESimilarityImage(image,reconstruct,offset,
4810
          similarity_metric,exception);
4811
        return(similarity_image);
4812
      }
4813
      default:
4814
        break;
4815
    }
4816
}
4817
#endif
4818
0
  if ((image->columns < reconstruct->columns) ||
4819
0
      (image->rows < reconstruct->rows))
4820
0
    {
4821
0
      (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
4822
0
        "GeometryDoesNotContainImage","`%s'",image->filename);
4823
0
      return((Image *) NULL);
4824
0
    }
4825
0
  SetImageCompareBounds(image,reconstruct,&columns,&rows);
4826
0
  similarity_image=CloneImage(image,columns,rows,MagickTrue,exception);
4827
0
  if (similarity_image == (Image *) NULL)
4828
0
    return((Image *) NULL);
4829
0
  similarity_image->depth=32;
4830
0
  similarity_image->colorspace=GRAYColorspace;
4831
0
  similarity_image->alpha_trait=UndefinedPixelTrait;
4832
0
  status=SetImageStorageClass(similarity_image,DirectClass,exception);
4833
0
  if (status == MagickFalse)
4834
0
    return(DestroyImage(similarity_image));
4835
  /*
4836
    Measure similarity of reconstruction image against image.
4837
  */
4838
0
  similarity_info.similarity=GetSimilarityMetric(image,reconstruct,metric,
4839
0
    similarity_info.x,similarity_info.y,exception);
4840
0
  similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
4841
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4842
  #pragma omp parallel for schedule(static) shared(similarity_info,status,threshold_trigger) \
4843
    magick_number_threads(image,reconstruct,similarity_image->rows,1)
4844
#endif
4845
0
  for (y=0; y < (ssize_t) similarity_image->rows; y++)
4846
0
  {
4847
0
    double
4848
0
      similarity;
4849
4850
0
    Quantum
4851
0
      *magick_restrict q;
4852
4853
0
    SimilarityInfo
4854
0
      channel_info = similarity_info;
4855
4856
0
    ssize_t
4857
0
      x;
4858
4859
0
    if (status == MagickFalse)
4860
0
      continue;
4861
0
    if (threshold_trigger != MagickFalse)
4862
0
      continue;
4863
0
    q=QueueCacheViewAuthenticPixels(similarity_view,0,y,
4864
0
      similarity_image->columns,1,exception);
4865
0
    if (q == (Quantum *) NULL)
4866
0
      {
4867
0
        status=MagickFalse;
4868
0
        continue;
4869
0
      }
4870
0
    for (x=0; x < (ssize_t) similarity_image->columns; x++)
4871
0
    {
4872
0
      ssize_t
4873
0
        i;
4874
4875
0
      similarity=GetSimilarityMetric((Image *) image,reconstruct,metric,x,y,
4876
0
        exception);
4877
0
      switch (metric)
4878
0
      {
4879
0
        case DotProductCorrelationErrorMetric:
4880
0
        case NormalizedCrossCorrelationErrorMetric:
4881
0
        case PeakSignalToNoiseRatioErrorMetric:
4882
0
        case PhaseCorrelationErrorMetric:
4883
0
        case StructuralSimilarityErrorMetric:
4884
0
        {
4885
0
          if (similarity <= channel_info.similarity)
4886
0
            break;
4887
0
          channel_info.similarity=similarity;
4888
0
          channel_info.x=x;
4889
0
          channel_info.y=y;
4890
0
          break;
4891
0
        }
4892
0
        default:
4893
0
        {
4894
0
          if (similarity >= channel_info.similarity)
4895
0
            break;
4896
0
          channel_info.similarity=similarity;
4897
0
          channel_info.x=x;
4898
0
          channel_info.y=y;
4899
0
          break;
4900
0
        }
4901
0
      }
4902
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4903
0
      {
4904
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
4905
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
4906
0
        PixelTrait similarity_traits = GetPixelChannelTraits(similarity_image,
4907
0
          channel);
4908
0
        if (((traits & UpdatePixelTrait) == 0) ||
4909
0
            ((similarity_traits & UpdatePixelTrait) == 0))
4910
0
          continue;
4911
0
        switch (metric)
4912
0
        {
4913
0
          case DotProductCorrelationErrorMetric:
4914
0
          case NormalizedCrossCorrelationErrorMetric:
4915
0
          case PeakSignalToNoiseRatioErrorMetric:
4916
0
          case PhaseCorrelationErrorMetric:
4917
0
          case StructuralSimilarityErrorMetric:
4918
0
          {
4919
0
            SetPixelChannel(similarity_image,channel,ClampToQuantum((double)
4920
0
              QuantumRange*similarity),q);
4921
0
            break;
4922
0
          }
4923
0
          default:
4924
0
          {
4925
0
            SetPixelChannel(similarity_image,channel,ClampToQuantum((double)
4926
0
              QuantumRange*(1.0-similarity)),q);
4927
0
            break;
4928
0
          }
4929
0
        }
4930
0
      }
4931
0
      q+=(ptrdiff_t) GetPixelChannels(similarity_image);
4932
0
    }
4933
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4934
    #pragma omp critical (MagickCore_GetSimilarityMetric)
4935
#endif
4936
0
    switch (metric)
4937
0
    {
4938
0
      case DotProductCorrelationErrorMetric:
4939
0
      case NormalizedCrossCorrelationErrorMetric:
4940
0
      case PeakSignalToNoiseRatioErrorMetric:
4941
0
      case PhaseCorrelationErrorMetric:
4942
0
      case StructuralSimilarityErrorMetric:
4943
0
      {
4944
0
        if (similarity_threshold != DefaultSimilarityThreshold)
4945
0
          if (channel_info.similarity >= similarity_threshold)
4946
0
            threshold_trigger=MagickTrue;
4947
0
        if (channel_info.similarity >= similarity_info.similarity)
4948
0
          similarity_info=channel_info;
4949
0
        break;
4950
0
      }
4951
0
      default:
4952
0
      {
4953
0
        if (similarity_threshold != DefaultSimilarityThreshold)
4954
0
          if (channel_info.similarity < similarity_threshold)
4955
0
            threshold_trigger=MagickTrue;
4956
0
        if (channel_info.similarity < similarity_info.similarity)
4957
0
          similarity_info=channel_info;
4958
0
        break;
4959
0
      }
4960
0
    }
4961
0
    if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
4962
0
      status=MagickFalse;
4963
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
4964
0
      {
4965
0
        MagickBooleanType
4966
0
          proceed;
4967
4968
0
        progress++;
4969
0
        proceed=SetImageProgress(image,SimilarityImageTag,progress,image->rows);
4970
0
        if (proceed == MagickFalse)
4971
0
          status=MagickFalse;
4972
0
      }
4973
0
  }
4974
0
  similarity_view=DestroyCacheView(similarity_view);
4975
0
  if (status == MagickFalse)
4976
0
    similarity_image=DestroyImage(similarity_image);
4977
0
  *similarity_metric=similarity_info.similarity;
4978
0
  if (fabs(*similarity_metric) < MagickEpsilon)
4979
0
    *similarity_metric=0.0;
4980
0
  offset->x=similarity_info.x;
4981
0
  offset->y=similarity_info.y;
4982
0
  (void) FormatImageProperty((Image *) image,"similarity","%.*g",
4983
0
    GetMagickPrecision(),*similarity_metric);
4984
0
  (void) FormatImageProperty((Image *) image,"similarity.offset.x","%.*g",
4985
0
    GetMagickPrecision(),(double) offset->x);
4986
0
  (void) FormatImageProperty((Image *) image,"similarity.offset.y","%.*g",
4987
0
    GetMagickPrecision(),(double) offset->y);
4988
0
  return(similarity_image);
4989
0
}