blob: 8b2ce8145dc728ef2f7d025e67c2edf4fe418b1d [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
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% John Cristy %
17% December 2003 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/artifact.h"
45#include "MagickCore/cache-view.h"
46#include "MagickCore/client.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/colorspace-private.h"
51#include "MagickCore/compare.h"
52#include "MagickCore/composite-private.h"
53#include "MagickCore/constitute.h"
54#include "MagickCore/exception-private.h"
55#include "MagickCore/geometry.h"
56#include "MagickCore/image-private.h"
57#include "MagickCore/list.h"
58#include "MagickCore/log.h"
59#include "MagickCore/memory_.h"
60#include "MagickCore/monitor.h"
61#include "MagickCore/monitor-private.h"
62#include "MagickCore/option.h"
63#include "MagickCore/pixel-accessor.h"
64#include "MagickCore/resource_.h"
65#include "MagickCore/string_.h"
66#include "MagickCore/statistic.h"
67#include "MagickCore/transform.h"
68#include "MagickCore/utility.h"
69#include "MagickCore/version.h"
cristy3ed852e2009-09-05 21:47:34 +000070
71/*
72%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
73% %
74% %
75% %
cristy8a9106f2011-07-05 14:39:26 +000076% C o m p a r e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +000077% %
78% %
79% %
80%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
81%
cristy8a9106f2011-07-05 14:39:26 +000082% CompareImages() compares one or more pixel channels of an image to a
83% reconstructed image and returns the difference image.
cristy3ed852e2009-09-05 21:47:34 +000084%
cristy8a9106f2011-07-05 14:39:26 +000085% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +000086%
cristy8a9106f2011-07-05 14:39:26 +000087% Image *CompareImages(const Image *image,const Image *reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +000088% const MetricType metric,double *distortion,ExceptionInfo *exception)
89%
90% A description of each parameter follows:
91%
92% o image: the image.
93%
94% o reconstruct_image: the reconstruct image.
95%
cristy3ed852e2009-09-05 21:47:34 +000096% o metric: the metric.
97%
98% o distortion: the computed distortion between the images.
99%
100% o exception: return any errors or warnings in this structure.
101%
102*/
cristy3ed852e2009-09-05 21:47:34 +0000103MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
104 const MetricType metric,double *distortion,ExceptionInfo *exception)
105{
cristyc4c8d132010-01-07 01:58:38 +0000106 CacheView
107 *highlight_view,
108 *image_view,
109 *reconstruct_view;
110
cristy3ed852e2009-09-05 21:47:34 +0000111 const char
112 *artifact;
113
114 Image
115 *difference_image,
116 *highlight_image;
117
cristy3ed852e2009-09-05 21:47:34 +0000118 MagickBooleanType
119 status;
120
cristy4c08aed2011-07-01 19:47:50 +0000121 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000122 highlight,
cristy3fc482f2011-09-23 00:43:35 +0000123 lowlight;
cristy3ed852e2009-09-05 21:47:34 +0000124
cristy49dd6a02011-09-24 23:08:01 +0000125 ssize_t
126 y;
127
cristy3ed852e2009-09-05 21:47:34 +0000128 assert(image != (Image *) NULL);
129 assert(image->signature == MagickSignature);
130 if (image->debug != MagickFalse)
131 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
132 assert(reconstruct_image != (const Image *) NULL);
133 assert(reconstruct_image->signature == MagickSignature);
134 assert(distortion != (double *) NULL);
135 *distortion=0.0;
136 if (image->debug != MagickFalse)
137 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
138 if ((reconstruct_image->columns != image->columns) ||
139 (reconstruct_image->rows != image->rows))
140 ThrowImageException(ImageError,"ImageSizeDiffers");
cristy8a9106f2011-07-05 14:39:26 +0000141 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
142 exception);
cristy3ed852e2009-09-05 21:47:34 +0000143 if (status == MagickFalse)
144 return((Image *) NULL);
145 difference_image=CloneImage(image,0,0,MagickTrue,exception);
146 if (difference_image == (Image *) NULL)
147 return((Image *) NULL);
cristy63240882011-08-05 19:05:27 +0000148 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +0000149 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
150 exception);
151 if (highlight_image == (Image *) NULL)
152 {
153 difference_image=DestroyImage(difference_image);
154 return((Image *) NULL);
155 }
cristy3fc482f2011-09-23 00:43:35 +0000156 status=SetImageStorageClass(highlight_image,DirectClass,exception);
157 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000158 {
cristy3ed852e2009-09-05 21:47:34 +0000159 difference_image=DestroyImage(difference_image);
160 highlight_image=DestroyImage(highlight_image);
161 return((Image *) NULL);
162 }
cristy63240882011-08-05 19:05:27 +0000163 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
cristy269c9412011-10-13 23:41:15 +0000164 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,
cristy9950d572011-10-01 18:22:35 +0000165 exception);
cristy3ed852e2009-09-05 21:47:34 +0000166 artifact=GetImageArtifact(image,"highlight-color");
167 if (artifact != (const char *) NULL)
cristy269c9412011-10-13 23:41:15 +0000168 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,
cristy9950d572011-10-01 18:22:35 +0000169 exception);
cristy269c9412011-10-13 23:41:15 +0000170 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,
cristy9950d572011-10-01 18:22:35 +0000171 exception);
cristy3ed852e2009-09-05 21:47:34 +0000172 artifact=GetImageArtifact(image,"lowlight-color");
173 if (artifact != (const char *) NULL)
cristy269c9412011-10-13 23:41:15 +0000174 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,
cristy9950d572011-10-01 18:22:35 +0000175 exception);
cristy3ed852e2009-09-05 21:47:34 +0000176 if (highlight_image->colorspace == CMYKColorspace)
177 {
178 ConvertRGBToCMYK(&highlight);
179 ConvertRGBToCMYK(&lowlight);
180 }
181 /*
182 Generate difference image.
183 */
184 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000185 image_view=AcquireCacheView(image);
186 reconstruct_view=AcquireCacheView(reconstruct_image);
187 highlight_view=AcquireCacheView(highlight_image);
cristyb5d5f722009-11-04 03:03:49 +0000188#if defined(MAGICKCORE_OPENMP_SUPPORT)
189 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000190#endif
cristybb503372010-05-27 20:51:26 +0000191 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000192 {
193 MagickBooleanType
194 sync;
195
cristy4c08aed2011-07-01 19:47:50 +0000196 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000197 *restrict p,
198 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000199
cristy4c08aed2011-07-01 19:47:50 +0000200 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000201 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000202
cristy49dd6a02011-09-24 23:08:01 +0000203 register ssize_t
204 x;
205
cristy3ed852e2009-09-05 21:47:34 +0000206 if (status == MagickFalse)
207 continue;
208 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
209 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
210 1,exception);
211 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
212 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000213 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
214 (r == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000215 {
216 status=MagickFalse;
217 continue;
218 }
cristybb503372010-05-27 20:51:26 +0000219 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000220 {
221 MagickStatusType
222 difference;
223
cristy3fc482f2011-09-23 00:43:35 +0000224 register ssize_t
225 i;
226
cristy3ed852e2009-09-05 21:47:34 +0000227 difference=MagickFalse;
cristy3fc482f2011-09-23 00:43:35 +0000228 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
229 {
cristy0beccfa2011-09-25 20:47:53 +0000230 MagickRealType
231 distance;
232
cristy3fc482f2011-09-23 00:43:35 +0000233 PixelChannel
234 channel;
235
236 PixelTrait
237 reconstruct_traits,
238 traits;
239
240 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
241 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
242 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
243 if ((traits == UndefinedPixelTrait) ||
244 (reconstruct_traits == UndefinedPixelTrait))
245 continue;
cristy49dd6a02011-09-24 23:08:01 +0000246 if ((reconstruct_traits & UpdatePixelTrait) == 0)
247 continue;
cristy0beccfa2011-09-25 20:47:53 +0000248 distance=p[i]-(MagickRealType)
249 GetPixelChannel(reconstruct_image,channel,q);
250 if (fabs((double) distance) >= MagickEpsilon)
cristy3fc482f2011-09-23 00:43:35 +0000251 difference=MagickTrue;
252 }
253 if (difference == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000254 SetPixelPixelInfo(highlight_image,&lowlight,r);
cristy3fc482f2011-09-23 00:43:35 +0000255 else
256 SetPixelPixelInfo(highlight_image,&highlight,r);
cristyed231572011-07-14 02:18:59 +0000257 p+=GetPixelChannels(image);
258 q+=GetPixelChannels(reconstruct_image);
259 r+=GetPixelChannels(highlight_image);
cristy3ed852e2009-09-05 21:47:34 +0000260 }
261 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
262 if (sync == MagickFalse)
263 status=MagickFalse;
264 }
265 highlight_view=DestroyCacheView(highlight_view);
266 reconstruct_view=DestroyCacheView(reconstruct_view);
267 image_view=DestroyCacheView(image_view);
cristye941a752011-10-15 01:52:48 +0000268 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0,
269 exception);
cristy3ed852e2009-09-05 21:47:34 +0000270 highlight_image=DestroyImage(highlight_image);
271 if (status == MagickFalse)
272 difference_image=DestroyImage(difference_image);
273 return(difference_image);
274}
275
276/*
277%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
278% %
279% %
280% %
cristy8a9106f2011-07-05 14:39:26 +0000281% G e t I m a g e D i s t o r t i o n %
cristy3ed852e2009-09-05 21:47:34 +0000282% %
283% %
284% %
285%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
286%
cristy8a9106f2011-07-05 14:39:26 +0000287% GetImageDistortion() compares one or more pixel channels of an image to a
288% reconstructed image and returns the specified distortion metric.
cristy3ed852e2009-09-05 21:47:34 +0000289%
cristy8a9106f2011-07-05 14:39:26 +0000290% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +0000291%
cristy8a9106f2011-07-05 14:39:26 +0000292% MagickBooleanType GetImageDistortion(const Image *image,
293% const Image *reconstruct_image,const MetricType metric,
294% double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000295%
296% A description of each parameter follows:
297%
298% o image: the image.
299%
300% o reconstruct_image: the reconstruct image.
301%
cristy3ed852e2009-09-05 21:47:34 +0000302% o metric: the metric.
303%
304% o distortion: the computed distortion between the images.
305%
306% o exception: return any errors or warnings in this structure.
307%
308*/
309
cristy3cc758f2010-11-27 01:33:49 +0000310static MagickBooleanType GetAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000311 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000312{
cristyc4c8d132010-01-07 01:58:38 +0000313 CacheView
314 *image_view,
315 *reconstruct_view;
316
cristy3ed852e2009-09-05 21:47:34 +0000317 MagickBooleanType
318 status;
319
cristy9d314ff2011-03-09 01:30:28 +0000320 ssize_t
321 y;
322
cristy3ed852e2009-09-05 21:47:34 +0000323 /*
324 Compute the absolute difference in pixels between two images.
325 */
326 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000327 image_view=AcquireCacheView(image);
328 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000329#if defined(MAGICKCORE_OPENMP_SUPPORT)
330 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000331#endif
cristybb503372010-05-27 20:51:26 +0000332 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000333 {
334 double
cristy3fc482f2011-09-23 00:43:35 +0000335 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000336
cristy4c08aed2011-07-01 19:47:50 +0000337 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000338 *restrict p,
339 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000340
cristybb503372010-05-27 20:51:26 +0000341 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000342 i,
343 x;
344
345 if (status == MagickFalse)
346 continue;
347 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
348 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
349 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000350 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000351 {
352 status=MagickFalse;
353 continue;
354 }
cristy3ed852e2009-09-05 21:47:34 +0000355 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000356 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000357 {
cristy3fc482f2011-09-23 00:43:35 +0000358 MagickBooleanType
359 difference;
360
361 register ssize_t
362 i;
363
364 difference=MagickFalse;
365 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
366 {
367 PixelChannel
368 channel;
369
370 PixelTrait
371 reconstruct_traits,
372 traits;
373
374 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
375 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
376 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
377 if ((traits == UndefinedPixelTrait) ||
378 (reconstruct_traits == UndefinedPixelTrait))
379 continue;
cristy49dd6a02011-09-24 23:08:01 +0000380 if ((reconstruct_traits & UpdatePixelTrait) == 0)
381 continue;
cristy0beccfa2011-09-25 20:47:53 +0000382 if (p[i] != GetPixelChannel(reconstruct_image,channel,q))
cristy3fc482f2011-09-23 00:43:35 +0000383 difference=MagickTrue;
384 }
385 if (difference != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000386 {
cristy3fc482f2011-09-23 00:43:35 +0000387 channel_distortion[i]++;
388 channel_distortion[MaxPixelChannels]++;
cristy3ed852e2009-09-05 21:47:34 +0000389 }
cristyed231572011-07-14 02:18:59 +0000390 p+=GetPixelChannels(image);
391 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000392 }
cristyb5d5f722009-11-04 03:03:49 +0000393#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000394 #pragma omp critical (MagickCore_GetAbsoluteError)
395#endif
cristy3fc482f2011-09-23 00:43:35 +0000396 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000397 distortion[i]+=channel_distortion[i];
398 }
399 reconstruct_view=DestroyCacheView(reconstruct_view);
400 image_view=DestroyCacheView(image_view);
401 return(status);
402}
403
cristy49dd6a02011-09-24 23:08:01 +0000404static size_t GetImageChannels(const Image *image)
cristy3ed852e2009-09-05 21:47:34 +0000405{
cristy3fc482f2011-09-23 00:43:35 +0000406 register ssize_t
407 i;
408
cristy49dd6a02011-09-24 23:08:01 +0000409 size_t
cristy3ed852e2009-09-05 21:47:34 +0000410 channels;
411
412 channels=0;
cristy3fc482f2011-09-23 00:43:35 +0000413 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
414 {
415 PixelTrait
416 traits;
417
418 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
cristy25a5f2f2011-09-24 14:09:43 +0000419 if ((traits & UpdatePixelTrait) != 0)
cristy3fc482f2011-09-23 00:43:35 +0000420 channels++;
421 }
cristy3ed852e2009-09-05 21:47:34 +0000422 return(channels);
423}
424
cristy343eee92010-12-11 02:17:57 +0000425static MagickBooleanType GetFuzzDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000426 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy343eee92010-12-11 02:17:57 +0000427{
428 CacheView
429 *image_view,
430 *reconstruct_view;
431
cristy343eee92010-12-11 02:17:57 +0000432 MagickBooleanType
433 status;
434
435 register ssize_t
436 i;
437
cristy9d314ff2011-03-09 01:30:28 +0000438 ssize_t
439 y;
440
cristy343eee92010-12-11 02:17:57 +0000441 status=MagickTrue;
442 image_view=AcquireCacheView(image);
443 reconstruct_view=AcquireCacheView(reconstruct_image);
444#if defined(MAGICKCORE_OPENMP_SUPPORT)
445 #pragma omp parallel for schedule(dynamic,4) shared(status)
446#endif
447 for (y=0; y < (ssize_t) image->rows; y++)
448 {
449 double
cristy3fc482f2011-09-23 00:43:35 +0000450 channel_distortion[MaxPixelChannels+1];
cristy343eee92010-12-11 02:17:57 +0000451
cristy4c08aed2011-07-01 19:47:50 +0000452 register const Quantum
cristy343eee92010-12-11 02:17:57 +0000453 *restrict p,
454 *restrict q;
455
456 register ssize_t
457 i,
458 x;
459
460 if (status == MagickFalse)
461 continue;
462 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristye6529ff2011-02-04 20:05:32 +0000463 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
464 1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000465 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy343eee92010-12-11 02:17:57 +0000466 {
467 status=MagickFalse;
468 continue;
469 }
cristy343eee92010-12-11 02:17:57 +0000470 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
471 for (x=0; x < (ssize_t) image->columns; x++)
472 {
cristy3fc482f2011-09-23 00:43:35 +0000473 register ssize_t
474 i;
cristy343eee92010-12-11 02:17:57 +0000475
cristy3fc482f2011-09-23 00:43:35 +0000476 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
477 {
478 MagickRealType
479 distance;
480
481 PixelChannel
482 channel;
483
484 PixelTrait
485 reconstruct_traits,
486 traits;
487
488 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
489 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
490 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
491 if ((traits == UndefinedPixelTrait) ||
492 (reconstruct_traits == UndefinedPixelTrait))
493 continue;
cristy49dd6a02011-09-24 23:08:01 +0000494 if ((reconstruct_traits & UpdatePixelTrait) == 0)
495 continue;
cristy0beccfa2011-09-25 20:47:53 +0000496 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
497 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000498 distance*=distance;
499 channel_distortion[i]+=distance;
500 channel_distortion[MaxPixelChannels]+=distance;
501 }
cristyed231572011-07-14 02:18:59 +0000502 p+=GetPixelChannels(image);
503 q+=GetPixelChannels(reconstruct_image);
cristy343eee92010-12-11 02:17:57 +0000504 }
505#if defined(MAGICKCORE_OPENMP_SUPPORT)
506 #pragma omp critical (MagickCore_GetMeanSquaredError)
507#endif
cristy3fc482f2011-09-23 00:43:35 +0000508 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000509 distortion[i]+=channel_distortion[i];
510 }
511 reconstruct_view=DestroyCacheView(reconstruct_view);
512 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000513 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000514 distortion[i]/=((double) image->columns*image->rows);
cristy49dd6a02011-09-24 23:08:01 +0000515 distortion[MaxPixelChannels]/=(double) GetImageChannels(image);
cristy3fc482f2011-09-23 00:43:35 +0000516 distortion[MaxPixelChannels]=sqrt(distortion[MaxPixelChannels]);
cristy343eee92010-12-11 02:17:57 +0000517 return(status);
518}
519
cristy3cc758f2010-11-27 01:33:49 +0000520static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000521 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000522{
cristyc4c8d132010-01-07 01:58:38 +0000523 CacheView
524 *image_view,
525 *reconstruct_view;
526
cristy3ed852e2009-09-05 21:47:34 +0000527 MagickBooleanType
528 status;
529
cristybb503372010-05-27 20:51:26 +0000530 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000531 i;
532
cristy9d314ff2011-03-09 01:30:28 +0000533 ssize_t
534 y;
535
cristy3ed852e2009-09-05 21:47:34 +0000536 status=MagickTrue;
537 image_view=AcquireCacheView(image);
538 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000539#if defined(MAGICKCORE_OPENMP_SUPPORT)
540 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000541#endif
cristybb503372010-05-27 20:51:26 +0000542 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000543 {
544 double
cristy3fc482f2011-09-23 00:43:35 +0000545 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000546
cristy4c08aed2011-07-01 19:47:50 +0000547 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000548 *restrict p,
549 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000550
cristybb503372010-05-27 20:51:26 +0000551 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000552 i,
553 x;
554
555 if (status == MagickFalse)
556 continue;
557 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000558 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
559 1,exception);
560 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000561 {
562 status=MagickFalse;
563 continue;
564 }
cristy3ed852e2009-09-05 21:47:34 +0000565 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000566 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000567 {
cristy3fc482f2011-09-23 00:43:35 +0000568 register ssize_t
569 i;
cristy3ed852e2009-09-05 21:47:34 +0000570
cristy3fc482f2011-09-23 00:43:35 +0000571 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
572 {
573 MagickRealType
574 distance;
575
576 PixelChannel
577 channel;
578
579 PixelTrait
580 reconstruct_traits,
581 traits;
582
583 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
584 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
585 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
586 if ((traits == UndefinedPixelTrait) ||
587 (reconstruct_traits == UndefinedPixelTrait))
588 continue;
cristy49dd6a02011-09-24 23:08:01 +0000589 if ((reconstruct_traits & UpdatePixelTrait) == 0)
590 continue;
cristy0beccfa2011-09-25 20:47:53 +0000591 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
592 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000593 channel_distortion[i]+=distance;
594 channel_distortion[MaxPixelChannels]+=distance;
595 }
cristyed231572011-07-14 02:18:59 +0000596 p+=GetPixelChannels(image);
597 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000598 }
cristyb5d5f722009-11-04 03:03:49 +0000599#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000600 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
601#endif
cristy3fc482f2011-09-23 00:43:35 +0000602 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000603 distortion[i]+=channel_distortion[i];
604 }
605 reconstruct_view=DestroyCacheView(reconstruct_view);
606 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000607 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000608 distortion[i]/=((double) image->columns*image->rows);
cristy49dd6a02011-09-24 23:08:01 +0000609 distortion[MaxPixelChannels]/=(double) GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000610 return(status);
611}
612
613static MagickBooleanType GetMeanErrorPerPixel(Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000614 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000615{
cristyc4c8d132010-01-07 01:58:38 +0000616 CacheView
617 *image_view,
618 *reconstruct_view;
619
cristy3ed852e2009-09-05 21:47:34 +0000620 MagickBooleanType
621 status;
622
623 MagickRealType
624 alpha,
625 area,
626 beta,
627 maximum_error,
628 mean_error;
629
cristy9d314ff2011-03-09 01:30:28 +0000630 ssize_t
631 y;
632
cristy3ed852e2009-09-05 21:47:34 +0000633 status=MagickTrue;
634 alpha=1.0;
635 beta=1.0;
636 area=0.0;
637 maximum_error=0.0;
638 mean_error=0.0;
639 image_view=AcquireCacheView(image);
640 reconstruct_view=AcquireCacheView(reconstruct_image);
cristybb503372010-05-27 20:51:26 +0000641 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000642 {
cristy4c08aed2011-07-01 19:47:50 +0000643 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000644 *restrict p,
645 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000646
cristybb503372010-05-27 20:51:26 +0000647 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000648 x;
649
650 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
651 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
652 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000653 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000654 {
655 status=MagickFalse;
656 break;
657 }
cristybb503372010-05-27 20:51:26 +0000658 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000659 {
cristy3fc482f2011-09-23 00:43:35 +0000660 register ssize_t
661 i;
cristy3ed852e2009-09-05 21:47:34 +0000662
cristy3fc482f2011-09-23 00:43:35 +0000663 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
664 {
665 MagickRealType
666 distance;
667
668 PixelChannel
669 channel;
670
671 PixelTrait
672 reconstruct_traits,
673 traits;
674
675 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
676 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
677 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
678 if ((traits == UndefinedPixelTrait) ||
679 (reconstruct_traits == UndefinedPixelTrait))
680 continue;
cristy49dd6a02011-09-24 23:08:01 +0000681 if ((reconstruct_traits & UpdatePixelTrait) == 0)
682 continue;
cristy0beccfa2011-09-25 20:47:53 +0000683 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
684 reconstruct_image,channel,q)));
cristy3fc482f2011-09-23 00:43:35 +0000685 distortion[i]+=distance;
686 distortion[MaxPixelChannels]+=distance;
687 mean_error+=distance*distance;
688 if (distance > maximum_error)
689 maximum_error=distance;
690 area++;
691 }
cristyed231572011-07-14 02:18:59 +0000692 p+=GetPixelChannels(image);
693 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000694 }
695 }
696 reconstruct_view=DestroyCacheView(reconstruct_view);
697 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000698 image->error.mean_error_per_pixel=distortion[MaxPixelChannels]/area;
cristy3ed852e2009-09-05 21:47:34 +0000699 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
700 image->error.normalized_maximum_error=QuantumScale*maximum_error;
701 return(status);
702}
703
cristy3cc758f2010-11-27 01:33:49 +0000704static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000705 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000706{
cristyc4c8d132010-01-07 01:58:38 +0000707 CacheView
708 *image_view,
709 *reconstruct_view;
710
cristy3ed852e2009-09-05 21:47:34 +0000711 MagickBooleanType
712 status;
713
cristybb503372010-05-27 20:51:26 +0000714 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000715 i;
716
cristy9d314ff2011-03-09 01:30:28 +0000717 ssize_t
718 y;
719
cristy3ed852e2009-09-05 21:47:34 +0000720 status=MagickTrue;
721 image_view=AcquireCacheView(image);
722 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000723#if defined(MAGICKCORE_OPENMP_SUPPORT)
724 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000725#endif
cristybb503372010-05-27 20:51:26 +0000726 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000727 {
728 double
cristy3fc482f2011-09-23 00:43:35 +0000729 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000730
cristy4c08aed2011-07-01 19:47:50 +0000731 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000732 *restrict p,
733 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000734
cristybb503372010-05-27 20:51:26 +0000735 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000736 i,
737 x;
738
739 if (status == MagickFalse)
740 continue;
741 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000742 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
743 1,exception);
744 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000745 {
746 status=MagickFalse;
747 continue;
748 }
cristy3ed852e2009-09-05 21:47:34 +0000749 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000750 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000751 {
cristy3fc482f2011-09-23 00:43:35 +0000752 register ssize_t
753 i;
cristy3ed852e2009-09-05 21:47:34 +0000754
cristy3fc482f2011-09-23 00:43:35 +0000755 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
756 {
757 MagickRealType
758 distance;
759
760 PixelChannel
761 channel;
762
763 PixelTrait
764 reconstruct_traits,
765 traits;
766
767 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
768 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
769 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
770 if ((traits == UndefinedPixelTrait) ||
771 (reconstruct_traits == UndefinedPixelTrait))
772 continue;
cristy49dd6a02011-09-24 23:08:01 +0000773 if ((reconstruct_traits & UpdatePixelTrait) == 0)
774 continue;
cristy0beccfa2011-09-25 20:47:53 +0000775 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
776 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000777 distance*=distance;
778 channel_distortion[i]+=distance;
779 channel_distortion[MaxPixelChannels]+=distance;
780 }
cristyed231572011-07-14 02:18:59 +0000781 p+=GetPixelChannels(image);
782 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000783 }
cristyb5d5f722009-11-04 03:03:49 +0000784#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000785 #pragma omp critical (MagickCore_GetMeanSquaredError)
786#endif
cristy3fc482f2011-09-23 00:43:35 +0000787 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000788 distortion[i]+=channel_distortion[i];
789 }
790 reconstruct_view=DestroyCacheView(reconstruct_view);
791 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000792 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000793 distortion[i]/=((double) image->columns*image->rows);
cristy49dd6a02011-09-24 23:08:01 +0000794 distortion[MaxPixelChannels]/=GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000795 return(status);
796}
797
cristy3cc758f2010-11-27 01:33:49 +0000798static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
cristy8a9106f2011-07-05 14:39:26 +0000799 const Image *image,const Image *reconstruct_image,double *distortion,
800 ExceptionInfo *exception)
cristy4c929a72010-11-24 18:54:42 +0000801{
cristy9f48ca62010-11-25 03:06:31 +0000802#define SimilarityImageTag "Similarity/Image"
803
cristy4c929a72010-11-24 18:54:42 +0000804 CacheView
805 *image_view,
806 *reconstruct_view;
807
cristy9f48ca62010-11-25 03:06:31 +0000808 ChannelStatistics
cristy9f48ca62010-11-25 03:06:31 +0000809 *image_statistics,
810 *reconstruct_statistics;
811
cristy4c929a72010-11-24 18:54:42 +0000812 MagickBooleanType
813 status;
814
cristy18a41362010-11-27 15:56:18 +0000815 MagickOffsetType
816 progress;
817
cristy34d6fdc2010-11-26 19:06:08 +0000818 MagickRealType
819 area;
820
cristy4c929a72010-11-24 18:54:42 +0000821 register ssize_t
822 i;
823
cristy3cc758f2010-11-27 01:33:49 +0000824 ssize_t
825 y;
826
cristy34d6fdc2010-11-26 19:06:08 +0000827 /*
cristy18a41362010-11-27 15:56:18 +0000828 Normalize to account for variation due to lighting and exposure condition.
cristy34d6fdc2010-11-26 19:06:08 +0000829 */
cristyd42d9952011-07-08 14:21:50 +0000830 image_statistics=GetImageStatistics(image,exception);
831 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
cristy4c929a72010-11-24 18:54:42 +0000832 status=MagickTrue;
cristy9f48ca62010-11-25 03:06:31 +0000833 progress=0;
cristy3fc482f2011-09-23 00:43:35 +0000834 for (i=0; i <= MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000835 distortion[i]=0.0;
cristye28f7af2011-10-17 18:21:57 +0000836 area=1.0/((MagickRealType) image->columns*image->rows-1);
cristy4c929a72010-11-24 18:54:42 +0000837 image_view=AcquireCacheView(image);
838 reconstruct_view=AcquireCacheView(reconstruct_image);
cristy4c929a72010-11-24 18:54:42 +0000839 for (y=0; y < (ssize_t) image->rows; y++)
840 {
cristy4c08aed2011-07-01 19:47:50 +0000841 register const Quantum
cristy4c929a72010-11-24 18:54:42 +0000842 *restrict p,
843 *restrict q;
844
845 register ssize_t
cristy4c929a72010-11-24 18:54:42 +0000846 x;
847
848 if (status == MagickFalse)
849 continue;
850 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy34d6fdc2010-11-26 19:06:08 +0000851 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
852 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000853 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy4c929a72010-11-24 18:54:42 +0000854 {
855 status=MagickFalse;
856 continue;
857 }
cristy4c929a72010-11-24 18:54:42 +0000858 for (x=0; x < (ssize_t) image->columns; x++)
859 {
cristy3fc482f2011-09-23 00:43:35 +0000860 register ssize_t
861 i;
862
863 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
864 {
865 PixelChannel
866 channel;
867
868 PixelTrait
869 reconstruct_traits,
870 traits;
871
872 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
873 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
874 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
875 if ((traits == UndefinedPixelTrait) ||
876 (reconstruct_traits == UndefinedPixelTrait))
877 continue;
cristy49dd6a02011-09-24 23:08:01 +0000878 if ((reconstruct_traits & UpdatePixelTrait) == 0)
879 continue;
cristy3fc482f2011-09-23 00:43:35 +0000880 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
cristy0beccfa2011-09-25 20:47:53 +0000881 (GetPixelChannel(reconstruct_image,channel,q)-
882 reconstruct_statistics[channel].mean);
cristy3fc482f2011-09-23 00:43:35 +0000883 }
cristyed231572011-07-14 02:18:59 +0000884 p+=GetPixelChannels(image);
885 q+=GetPixelChannels(image);
cristy4c929a72010-11-24 18:54:42 +0000886 }
cristy9f48ca62010-11-25 03:06:31 +0000887 if (image->progress_monitor != (MagickProgressMonitor) NULL)
888 {
889 MagickBooleanType
890 proceed;
891
cristy9f48ca62010-11-25 03:06:31 +0000892 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
893 image->rows);
894 if (proceed == MagickFalse)
895 status=MagickFalse;
896 }
cristy4c929a72010-11-24 18:54:42 +0000897 }
898 reconstruct_view=DestroyCacheView(reconstruct_view);
899 image_view=DestroyCacheView(image_view);
cristy34d6fdc2010-11-26 19:06:08 +0000900 /*
901 Divide by the standard deviation.
902 */
cristy3fc482f2011-09-23 00:43:35 +0000903 distortion[MaxPixelChannels]=0.0;
904 for (i=0; i < MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000905 {
906 MagickRealType
cristy18a41362010-11-27 15:56:18 +0000907 gamma;
cristy34d6fdc2010-11-26 19:06:08 +0000908
cristy3fc482f2011-09-23 00:43:35 +0000909 PixelChannel
910 channel;
911
912 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
cristy18a41362010-11-27 15:56:18 +0000913 gamma=image_statistics[i].standard_deviation*
cristy3fc482f2011-09-23 00:43:35 +0000914 reconstruct_statistics[channel].standard_deviation;
cristy18a41362010-11-27 15:56:18 +0000915 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
916 distortion[i]=QuantumRange*gamma*distortion[i];
cristy3fc482f2011-09-23 00:43:35 +0000917 distortion[MaxPixelChannels]+=distortion[i]*distortion[i];
cristy34d6fdc2010-11-26 19:06:08 +0000918 }
cristy3fc482f2011-09-23 00:43:35 +0000919 distortion[MaxPixelChannels]=sqrt(distortion[MaxPixelChannels]/
cristy49dd6a02011-09-24 23:08:01 +0000920 GetImageChannels(image));
cristy34d6fdc2010-11-26 19:06:08 +0000921 /*
922 Free resources.
923 */
cristy9f48ca62010-11-25 03:06:31 +0000924 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
925 reconstruct_statistics);
926 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
927 image_statistics);
cristy4c929a72010-11-24 18:54:42 +0000928 return(status);
929}
930
cristy3cc758f2010-11-27 01:33:49 +0000931static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000932 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000933{
cristyc4c8d132010-01-07 01:58:38 +0000934 CacheView
935 *image_view,
936 *reconstruct_view;
937
cristy3ed852e2009-09-05 21:47:34 +0000938 MagickBooleanType
939 status;
940
cristy9d314ff2011-03-09 01:30:28 +0000941 ssize_t
942 y;
943
cristy3ed852e2009-09-05 21:47:34 +0000944 status=MagickTrue;
945 image_view=AcquireCacheView(image);
946 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000947#if defined(MAGICKCORE_OPENMP_SUPPORT)
948 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000949#endif
cristybb503372010-05-27 20:51:26 +0000950 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000951 {
952 double
cristy3fc482f2011-09-23 00:43:35 +0000953 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000954
cristy4c08aed2011-07-01 19:47:50 +0000955 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000956 *restrict p,
957 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000958
cristybb503372010-05-27 20:51:26 +0000959 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000960 i,
961 x;
962
963 if (status == MagickFalse)
964 continue;
965 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
966 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
967 reconstruct_image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000968 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000969 {
970 status=MagickFalse;
971 continue;
972 }
cristy3ed852e2009-09-05 21:47:34 +0000973 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000974 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000975 {
cristy3fc482f2011-09-23 00:43:35 +0000976 register ssize_t
977 i;
cristy3ed852e2009-09-05 21:47:34 +0000978
cristy3fc482f2011-09-23 00:43:35 +0000979 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
980 {
981 MagickRealType
982 distance;
983
984 PixelChannel
985 channel;
986
987 PixelTrait
988 reconstruct_traits,
989 traits;
990
991 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
992 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
993 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
994 if ((traits == UndefinedPixelTrait) ||
995 (reconstruct_traits == UndefinedPixelTrait))
996 continue;
cristy49dd6a02011-09-24 23:08:01 +0000997 if ((reconstruct_traits & UpdatePixelTrait) == 0)
998 continue;
cristy0beccfa2011-09-25 20:47:53 +0000999 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
1000 reconstruct_image,channel,q));
cristy25a5f2f2011-09-24 14:09:43 +00001001 if (distance > channel_distortion[i])
cristy3fc482f2011-09-23 00:43:35 +00001002 channel_distortion[i]=distance;
1003 if (distance > channel_distortion[MaxPixelChannels])
1004 channel_distortion[MaxPixelChannels]=distance;
1005 }
cristyed231572011-07-14 02:18:59 +00001006 p+=GetPixelChannels(image);
1007 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001008 }
cristyb5d5f722009-11-04 03:03:49 +00001009#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001010 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1011#endif
cristy3fc482f2011-09-23 00:43:35 +00001012 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001013 if (channel_distortion[i] > distortion[i])
1014 distortion[i]=channel_distortion[i];
1015 }
1016 reconstruct_view=DestroyCacheView(reconstruct_view);
1017 image_view=DestroyCacheView(image_view);
1018 return(status);
1019}
1020
1021static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001022 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001023{
1024 MagickBooleanType
1025 status;
1026
cristy3fc482f2011-09-23 00:43:35 +00001027 register ssize_t
1028 i;
1029
cristy8a9106f2011-07-05 14:39:26 +00001030 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001031 for (i=0; i <= MaxPixelChannels; i++)
1032 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
cristy3ed852e2009-09-05 21:47:34 +00001033 return(status);
1034}
1035
cristy3cc758f2010-11-27 01:33:49 +00001036static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001037 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001038{
1039 MagickBooleanType
1040 status;
1041
cristy3fc482f2011-09-23 00:43:35 +00001042 register ssize_t
1043 i;
1044
cristy8a9106f2011-07-05 14:39:26 +00001045 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001046 for (i=0; i <= MaxPixelChannels; i++)
1047 distortion[i]=sqrt(distortion[i]);
cristy3ed852e2009-09-05 21:47:34 +00001048 return(status);
1049}
1050
cristy8a9106f2011-07-05 14:39:26 +00001051MagickExport MagickBooleanType GetImageDistortion(Image *image,
1052 const Image *reconstruct_image,const MetricType metric,double *distortion,
1053 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001054{
1055 double
1056 *channel_distortion;
1057
1058 MagickBooleanType
1059 status;
1060
1061 size_t
1062 length;
1063
1064 assert(image != (Image *) NULL);
1065 assert(image->signature == MagickSignature);
1066 if (image->debug != MagickFalse)
1067 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1068 assert(reconstruct_image != (const Image *) NULL);
1069 assert(reconstruct_image->signature == MagickSignature);
1070 assert(distortion != (double *) NULL);
1071 *distortion=0.0;
1072 if (image->debug != MagickFalse)
1073 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1074 if ((reconstruct_image->columns != image->columns) ||
1075 (reconstruct_image->rows != image->rows))
1076 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1077 /*
1078 Get image distortion.
1079 */
cristy3fc482f2011-09-23 00:43:35 +00001080 length=MaxPixelChannels+1;
cristy3ed852e2009-09-05 21:47:34 +00001081 channel_distortion=(double *) AcquireQuantumMemory(length,
1082 sizeof(*channel_distortion));
1083 if (channel_distortion == (double *) NULL)
1084 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1085 (void) ResetMagickMemory(channel_distortion,0,length*
1086 sizeof(*channel_distortion));
1087 switch (metric)
1088 {
1089 case AbsoluteErrorMetric:
1090 {
cristy8a9106f2011-07-05 14:39:26 +00001091 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1092 exception);
cristy3ed852e2009-09-05 21:47:34 +00001093 break;
1094 }
cristy343eee92010-12-11 02:17:57 +00001095 case FuzzErrorMetric:
1096 {
cristy8a9106f2011-07-05 14:39:26 +00001097 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1098 exception);
cristy343eee92010-12-11 02:17:57 +00001099 break;
1100 }
cristy3ed852e2009-09-05 21:47:34 +00001101 case MeanAbsoluteErrorMetric:
1102 {
cristy8a9106f2011-07-05 14:39:26 +00001103 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001104 channel_distortion,exception);
1105 break;
1106 }
1107 case MeanErrorPerPixelMetric:
1108 {
cristy8a9106f2011-07-05 14:39:26 +00001109 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1110 exception);
cristy3ed852e2009-09-05 21:47:34 +00001111 break;
1112 }
1113 case MeanSquaredErrorMetric:
1114 {
cristy8a9106f2011-07-05 14:39:26 +00001115 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001116 channel_distortion,exception);
1117 break;
1118 }
cristy4c929a72010-11-24 18:54:42 +00001119 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001120 default:
cristy4c929a72010-11-24 18:54:42 +00001121 {
cristy3cc758f2010-11-27 01:33:49 +00001122 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001123 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001124 break;
1125 }
cristy3ed852e2009-09-05 21:47:34 +00001126 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001127 {
cristy8a9106f2011-07-05 14:39:26 +00001128 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001129 channel_distortion,exception);
1130 break;
1131 }
1132 case PeakSignalToNoiseRatioMetric:
1133 {
cristy8a9106f2011-07-05 14:39:26 +00001134 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001135 channel_distortion,exception);
1136 break;
1137 }
1138 case RootMeanSquaredErrorMetric:
1139 {
cristy8a9106f2011-07-05 14:39:26 +00001140 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001141 channel_distortion,exception);
1142 break;
1143 }
1144 }
cristy3fc482f2011-09-23 00:43:35 +00001145 *distortion=channel_distortion[MaxPixelChannels];
cristy3ed852e2009-09-05 21:47:34 +00001146 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1147 return(status);
1148}
1149
1150/*
1151%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1152% %
1153% %
1154% %
cristy8a9106f2011-07-05 14:39:26 +00001155% G e t I m a g e D i s t o r t i o n s %
cristy3ed852e2009-09-05 21:47:34 +00001156% %
1157% %
1158% %
1159%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1160%
cristy8a9106f2011-07-05 14:39:26 +00001161% GetImageDistrortion() compares the pixel channels of an image to a
cristy3ed852e2009-09-05 21:47:34 +00001162% reconstructed image and returns the specified distortion metric for each
1163% channel.
1164%
cristy8a9106f2011-07-05 14:39:26 +00001165% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +00001166%
cristy8a9106f2011-07-05 14:39:26 +00001167% double *GetImageDistortions(const Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001168% const Image *reconstruct_image,const MetricType metric,
1169% ExceptionInfo *exception)
1170%
1171% A description of each parameter follows:
1172%
1173% o image: the image.
1174%
1175% o reconstruct_image: the reconstruct image.
1176%
1177% o metric: the metric.
1178%
1179% o exception: return any errors or warnings in this structure.
1180%
1181*/
cristy8a9106f2011-07-05 14:39:26 +00001182MagickExport double *GetImageDistortions(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001183 const Image *reconstruct_image,const MetricType metric,
1184 ExceptionInfo *exception)
1185{
1186 double
1187 *channel_distortion;
1188
1189 MagickBooleanType
1190 status;
1191
1192 size_t
1193 length;
1194
1195 assert(image != (Image *) NULL);
1196 assert(image->signature == MagickSignature);
1197 if (image->debug != MagickFalse)
1198 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1199 assert(reconstruct_image != (const Image *) NULL);
1200 assert(reconstruct_image->signature == MagickSignature);
1201 if (image->debug != MagickFalse)
1202 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1203 if ((reconstruct_image->columns != image->columns) ||
1204 (reconstruct_image->rows != image->rows))
1205 {
1206 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1207 ImageError,"ImageSizeDiffers","`%s'",image->filename);
1208 return((double *) NULL);
1209 }
1210 /*
1211 Get image distortion.
1212 */
cristy3fc482f2011-09-23 00:43:35 +00001213 length=MaxPixelChannels+1UL;
cristy3ed852e2009-09-05 21:47:34 +00001214 channel_distortion=(double *) AcquireQuantumMemory(length,
1215 sizeof(*channel_distortion));
1216 if (channel_distortion == (double *) NULL)
1217 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1218 (void) ResetMagickMemory(channel_distortion,0,length*
1219 sizeof(*channel_distortion));
cristyda16f162011-02-19 23:52:17 +00001220 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +00001221 switch (metric)
1222 {
1223 case AbsoluteErrorMetric:
1224 {
cristy8a9106f2011-07-05 14:39:26 +00001225 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1226 exception);
cristy3ed852e2009-09-05 21:47:34 +00001227 break;
1228 }
cristy343eee92010-12-11 02:17:57 +00001229 case FuzzErrorMetric:
1230 {
cristy8a9106f2011-07-05 14:39:26 +00001231 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1232 exception);
cristy343eee92010-12-11 02:17:57 +00001233 break;
1234 }
cristy3ed852e2009-09-05 21:47:34 +00001235 case MeanAbsoluteErrorMetric:
1236 {
cristy8a9106f2011-07-05 14:39:26 +00001237 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001238 channel_distortion,exception);
1239 break;
1240 }
1241 case MeanErrorPerPixelMetric:
1242 {
cristy8a9106f2011-07-05 14:39:26 +00001243 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1244 exception);
cristy3ed852e2009-09-05 21:47:34 +00001245 break;
1246 }
1247 case MeanSquaredErrorMetric:
1248 {
cristy8a9106f2011-07-05 14:39:26 +00001249 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001250 channel_distortion,exception);
1251 break;
1252 }
cristy4c929a72010-11-24 18:54:42 +00001253 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001254 default:
cristy4c929a72010-11-24 18:54:42 +00001255 {
cristy3cc758f2010-11-27 01:33:49 +00001256 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001257 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001258 break;
1259 }
cristy3ed852e2009-09-05 21:47:34 +00001260 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001261 {
cristy8a9106f2011-07-05 14:39:26 +00001262 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001263 channel_distortion,exception);
1264 break;
1265 }
1266 case PeakSignalToNoiseRatioMetric:
1267 {
cristy8a9106f2011-07-05 14:39:26 +00001268 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001269 channel_distortion,exception);
1270 break;
1271 }
1272 case RootMeanSquaredErrorMetric:
1273 {
cristy8a9106f2011-07-05 14:39:26 +00001274 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001275 channel_distortion,exception);
1276 break;
1277 }
1278 }
cristyda16f162011-02-19 23:52:17 +00001279 if (status == MagickFalse)
1280 {
1281 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1282 return((double *) NULL);
1283 }
cristy3ed852e2009-09-05 21:47:34 +00001284 return(channel_distortion);
1285}
1286
1287/*
1288%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1289% %
1290% %
1291% %
1292% I s I m a g e s E q u a l %
1293% %
1294% %
1295% %
1296%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1297%
1298% IsImagesEqual() measures the difference between colors at each pixel
1299% location of two images. A value other than 0 means the colors match
1300% exactly. Otherwise an error measure is computed by summing over all
1301% pixels in an image the distance squared in RGB space between each image
1302% pixel and its corresponding pixel in the reconstruct image. The error
1303% measure is assigned to these image members:
1304%
1305% o mean_error_per_pixel: The mean error for any single pixel in
1306% the image.
1307%
1308% o normalized_mean_error: The normalized mean quantization error for
1309% any single pixel in the image. This distance measure is normalized to
1310% a range between 0 and 1. It is independent of the range of red, green,
1311% and blue values in the image.
1312%
1313% o normalized_maximum_error: The normalized maximum quantization
1314% error for any single pixel in the image. This distance measure is
1315% normalized to a range between 0 and 1. It is independent of the range
1316% of red, green, and blue values in your image.
1317%
1318% A small normalized mean square error, accessed as
1319% image->normalized_mean_error, suggests the images are very similar in
1320% spatial layout and color.
1321%
1322% The format of the IsImagesEqual method is:
1323%
1324% MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001325% const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001326%
1327% A description of each parameter follows.
1328%
1329% o image: the image.
1330%
1331% o reconstruct_image: the reconstruct image.
1332%
cristy018f07f2011-09-04 21:15:19 +00001333% o exception: return any errors or warnings in this structure.
1334%
cristy3ed852e2009-09-05 21:47:34 +00001335*/
1336MagickExport MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001337 const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001338{
cristyc4c8d132010-01-07 01:58:38 +00001339 CacheView
1340 *image_view,
1341 *reconstruct_view;
1342
cristy3ed852e2009-09-05 21:47:34 +00001343 MagickBooleanType
1344 status;
1345
1346 MagickRealType
1347 area,
1348 maximum_error,
1349 mean_error,
1350 mean_error_per_pixel;
1351
cristy9d314ff2011-03-09 01:30:28 +00001352 ssize_t
1353 y;
1354
cristy3ed852e2009-09-05 21:47:34 +00001355 assert(image != (Image *) NULL);
1356 assert(image->signature == MagickSignature);
1357 assert(reconstruct_image != (const Image *) NULL);
1358 assert(reconstruct_image->signature == MagickSignature);
1359 if ((reconstruct_image->columns != image->columns) ||
1360 (reconstruct_image->rows != image->rows))
1361 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1362 area=0.0;
1363 maximum_error=0.0;
1364 mean_error_per_pixel=0.0;
1365 mean_error=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001366 image_view=AcquireCacheView(image);
1367 reconstruct_view=AcquireCacheView(reconstruct_image);
cristybb503372010-05-27 20:51:26 +00001368 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001369 {
cristy4c08aed2011-07-01 19:47:50 +00001370 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001371 *restrict p,
1372 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001373
cristybb503372010-05-27 20:51:26 +00001374 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001375 x;
1376
1377 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1378 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1379 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001380 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001381 break;
cristybb503372010-05-27 20:51:26 +00001382 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001383 {
cristyd5c15f92011-09-23 00:58:33 +00001384 register ssize_t
1385 i;
cristy3ed852e2009-09-05 21:47:34 +00001386
cristyd5c15f92011-09-23 00:58:33 +00001387 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1388 {
1389 MagickRealType
1390 distance;
1391
1392 PixelChannel
1393 channel;
1394
1395 PixelTrait
1396 reconstruct_traits,
1397 traits;
1398
1399 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1400 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1401 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1402 if ((traits == UndefinedPixelTrait) ||
1403 (reconstruct_traits == UndefinedPixelTrait))
1404 continue;
cristy49dd6a02011-09-24 23:08:01 +00001405 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1406 continue;
cristy0beccfa2011-09-25 20:47:53 +00001407 distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
1408 channel,q));
cristyd5c15f92011-09-23 00:58:33 +00001409 mean_error_per_pixel+=distance;
1410 mean_error+=distance*distance;
1411 if (distance > maximum_error)
1412 maximum_error=distance;
1413 area++;
1414 }
cristyed231572011-07-14 02:18:59 +00001415 p+=GetPixelChannels(image);
1416 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001417 }
1418 }
1419 reconstruct_view=DestroyCacheView(reconstruct_view);
1420 image_view=DestroyCacheView(image_view);
1421 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1422 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1423 mean_error/area);
1424 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1425 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1426 return(status);
1427}
1428
1429/*
1430%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1431% %
1432% %
1433% %
1434% S i m i l a r i t y I m a g e %
1435% %
1436% %
1437% %
1438%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1439%
1440% SimilarityImage() compares the reference image of the image and returns the
1441% best match offset. In addition, it returns a similarity image such that an
1442% exact match location is completely white and if none of the pixels match,
1443% black, otherwise some gray level in-between.
1444%
1445% The format of the SimilarityImageImage method is:
1446%
1447% Image *SimilarityImage(const Image *image,const Image *reference,
1448% RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1449%
1450% A description of each parameter follows:
1451%
1452% o image: the image.
1453%
1454% o reference: find an area of the image that closely resembles this image.
1455%
1456% o the best match offset of the reference image within the image.
1457%
1458% o similarity: the computed similarity between the images.
1459%
1460% o exception: return any errors or warnings in this structure.
1461%
1462*/
1463
cristy3cc758f2010-11-27 01:33:49 +00001464static double GetNCCDistortion(const Image *image,
1465 const Image *reconstruct_image,
1466 const ChannelStatistics *reconstruct_statistics,ExceptionInfo *exception)
1467{
1468#define SimilarityImageTag "Similarity/Image"
1469
1470 CacheView
1471 *image_view,
1472 *reconstruct_view;
1473
1474 ChannelStatistics
1475 *image_statistics;
1476
1477 double
1478 distortion;
1479
1480 MagickBooleanType
1481 status;
1482
1483 MagickRealType
cristy18a41362010-11-27 15:56:18 +00001484 area,
1485 gamma;
cristy3cc758f2010-11-27 01:33:49 +00001486
1487 ssize_t
1488 y;
1489
cristy3cc758f2010-11-27 01:33:49 +00001490 /*
cristy18a41362010-11-27 15:56:18 +00001491 Normalize to account for variation due to lighting and exposure condition.
cristy3cc758f2010-11-27 01:33:49 +00001492 */
cristyd42d9952011-07-08 14:21:50 +00001493 image_statistics=GetImageStatistics(image,exception);
cristy3cc758f2010-11-27 01:33:49 +00001494 status=MagickTrue;
1495 distortion=0.0;
cristye28f7af2011-10-17 18:21:57 +00001496 area=1.0/((MagickRealType) image->columns*image->rows-1);
cristy3cc758f2010-11-27 01:33:49 +00001497 image_view=AcquireCacheView(image);
1498 reconstruct_view=AcquireCacheView(reconstruct_image);
1499 for (y=0; y < (ssize_t) image->rows; y++)
1500 {
cristy4c08aed2011-07-01 19:47:50 +00001501 register const Quantum
cristy3cc758f2010-11-27 01:33:49 +00001502 *restrict p,
1503 *restrict q;
1504
1505 register ssize_t
1506 x;
1507
1508 if (status == MagickFalse)
1509 continue;
1510 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1511 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1512 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001513 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3cc758f2010-11-27 01:33:49 +00001514 {
1515 status=MagickFalse;
1516 continue;
1517 }
cristy3cc758f2010-11-27 01:33:49 +00001518 for (x=0; x < (ssize_t) image->columns; x++)
1519 {
cristyc3ca81b2011-10-17 18:05:54 +00001520 register ssize_t
cristyd5c15f92011-09-23 00:58:33 +00001521 i;
1522
1523 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1524 {
1525 PixelChannel
1526 channel;
1527
1528 PixelTrait
1529 reconstruct_traits,
1530 traits;
1531
1532 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1533 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1534 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1535 if ((traits == UndefinedPixelTrait) ||
1536 (reconstruct_traits == UndefinedPixelTrait))
1537 continue;
cristy49dd6a02011-09-24 23:08:01 +00001538 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1539 continue;
cristyd5c15f92011-09-23 00:58:33 +00001540 distortion+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
cristy0beccfa2011-09-25 20:47:53 +00001541 (GetPixelChannel(reconstruct_image,channel,q)-
1542 reconstruct_statistics[channel].mean);
cristyd5c15f92011-09-23 00:58:33 +00001543 }
cristyed231572011-07-14 02:18:59 +00001544 p+=GetPixelChannels(image);
1545 q+=GetPixelChannels(reconstruct_image);
cristy3cc758f2010-11-27 01:33:49 +00001546 }
1547 }
1548 reconstruct_view=DestroyCacheView(reconstruct_view);
1549 image_view=DestroyCacheView(image_view);
1550 /*
1551 Divide by the standard deviation.
1552 */
cristy3fc482f2011-09-23 00:43:35 +00001553 gamma=image_statistics[MaxPixelChannels].standard_deviation*
1554 reconstruct_statistics[MaxPixelChannels].standard_deviation;
cristy18a41362010-11-27 15:56:18 +00001555 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1556 distortion=QuantumRange*gamma*distortion;
cristy49dd6a02011-09-24 23:08:01 +00001557 distortion=sqrt(distortion/GetImageChannels(image));
cristy3cc758f2010-11-27 01:33:49 +00001558 /*
1559 Free resources.
1560 */
1561 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1562 image_statistics);
1563 return(1.0-distortion);
1564}
1565
cristy3ed852e2009-09-05 21:47:34 +00001566static double GetSimilarityMetric(const Image *image,const Image *reference,
cristy3cc758f2010-11-27 01:33:49 +00001567 const ChannelStatistics *reference_statistics,const ssize_t x_offset,
1568 const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001569{
1570 double
cristy3cc758f2010-11-27 01:33:49 +00001571 distortion;
cristy3ed852e2009-09-05 21:47:34 +00001572
cristy713ff212010-11-26 21:56:11 +00001573 Image
1574 *similarity_image;
cristy3ed852e2009-09-05 21:47:34 +00001575
cristy713ff212010-11-26 21:56:11 +00001576 RectangleInfo
1577 geometry;
cristy3ed852e2009-09-05 21:47:34 +00001578
cristy713ff212010-11-26 21:56:11 +00001579 SetGeometry(reference,&geometry);
1580 geometry.x=x_offset;
1581 geometry.y=y_offset;
1582 similarity_image=CropImage(image,&geometry,exception);
1583 if (similarity_image == (Image *) NULL)
1584 return(0.0);
cristy3cc758f2010-11-27 01:33:49 +00001585 distortion=GetNCCDistortion(reference,similarity_image,reference_statistics,
1586 exception);
cristy713ff212010-11-26 21:56:11 +00001587 similarity_image=DestroyImage(similarity_image);
cristy3cc758f2010-11-27 01:33:49 +00001588 return(distortion);
cristy3ed852e2009-09-05 21:47:34 +00001589}
1590
1591MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1592 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1593{
1594#define SimilarityImageTag "Similarity/Image"
1595
cristyc4c8d132010-01-07 01:58:38 +00001596 CacheView
1597 *similarity_view;
1598
cristy3cc758f2010-11-27 01:33:49 +00001599 ChannelStatistics
1600 *reference_statistics;
1601
cristy3ed852e2009-09-05 21:47:34 +00001602 Image
1603 *similarity_image;
1604
1605 MagickBooleanType
1606 status;
1607
cristybb503372010-05-27 20:51:26 +00001608 MagickOffsetType
1609 progress;
1610
1611 ssize_t
1612 y;
1613
cristy3ed852e2009-09-05 21:47:34 +00001614 assert(image != (const Image *) NULL);
1615 assert(image->signature == MagickSignature);
1616 if (image->debug != MagickFalse)
1617 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1618 assert(exception != (ExceptionInfo *) NULL);
1619 assert(exception->signature == MagickSignature);
1620 assert(offset != (RectangleInfo *) NULL);
1621 SetGeometry(reference,offset);
1622 *similarity_metric=1.0;
cristyf9255b92010-08-14 22:59:00 +00001623 if ((reference->columns > image->columns) || (reference->rows > image->rows))
cristy3ed852e2009-09-05 21:47:34 +00001624 ThrowImageException(ImageError,"ImageSizeDiffers");
1625 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1626 image->rows-reference->rows+1,MagickTrue,exception);
1627 if (similarity_image == (Image *) NULL)
1628 return((Image *) NULL);
cristyd5c15f92011-09-23 00:58:33 +00001629 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1630 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001631 {
cristy3ed852e2009-09-05 21:47:34 +00001632 similarity_image=DestroyImage(similarity_image);
1633 return((Image *) NULL);
1634 }
1635 /*
1636 Measure similarity of reference image against image.
1637 */
1638 status=MagickTrue;
1639 progress=0;
cristyd42d9952011-07-08 14:21:50 +00001640 reference_statistics=GetImageStatistics(reference,exception);
cristy3ed852e2009-09-05 21:47:34 +00001641 similarity_view=AcquireCacheView(similarity_image);
cristyb5d5f722009-11-04 03:03:49 +00001642#if defined(MAGICKCORE_OPENMP_SUPPORT)
1643 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001644#endif
cristybb503372010-05-27 20:51:26 +00001645 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
cristy3ed852e2009-09-05 21:47:34 +00001646 {
1647 double
1648 similarity;
1649
cristy4c08aed2011-07-01 19:47:50 +00001650 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001651 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001652
cristy49dd6a02011-09-24 23:08:01 +00001653 register ssize_t
1654 x;
1655
cristy3ed852e2009-09-05 21:47:34 +00001656 if (status == MagickFalse)
1657 continue;
cristy3cc758f2010-11-27 01:33:49 +00001658 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1659 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001660 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001661 {
1662 status=MagickFalse;
1663 continue;
1664 }
cristybb503372010-05-27 20:51:26 +00001665 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
cristy3ed852e2009-09-05 21:47:34 +00001666 {
cristy49dd6a02011-09-24 23:08:01 +00001667 register ssize_t
1668 i;
1669
cristy3cc758f2010-11-27 01:33:49 +00001670 similarity=GetSimilarityMetric(image,reference,reference_statistics,x,y,
1671 exception);
cristyb5d5f722009-11-04 03:03:49 +00001672#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001673 #pragma omp critical (MagickCore_SimilarityImage)
1674#endif
1675 if (similarity < *similarity_metric)
1676 {
1677 *similarity_metric=similarity;
1678 offset->x=x;
1679 offset->y=y;
1680 }
cristy49dd6a02011-09-24 23:08:01 +00001681 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1682 {
1683 PixelChannel
1684 channel;
1685
1686 PixelTrait
1687 similarity_traits,
1688 traits;
1689
1690 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1691 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1692 similarity_traits=GetPixelChannelMapTraits(similarity_image,channel);
1693 if ((traits == UndefinedPixelTrait) ||
1694 (similarity_traits == UndefinedPixelTrait))
1695 continue;
1696 if ((similarity_traits & UpdatePixelTrait) == 0)
1697 continue;
cristy0beccfa2011-09-25 20:47:53 +00001698 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1699 QuantumRange*similarity),q);
cristy49dd6a02011-09-24 23:08:01 +00001700 }
cristyed231572011-07-14 02:18:59 +00001701 q+=GetPixelChannels(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001702 }
1703 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1704 status=MagickFalse;
1705 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1706 {
1707 MagickBooleanType
1708 proceed;
1709
cristyb5d5f722009-11-04 03:03:49 +00001710#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001711 #pragma omp critical (MagickCore_SimilarityImage)
1712#endif
1713 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1714 image->rows);
1715 if (proceed == MagickFalse)
1716 status=MagickFalse;
1717 }
1718 }
1719 similarity_view=DestroyCacheView(similarity_view);
cristy3cc758f2010-11-27 01:33:49 +00001720 reference_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1721 reference_statistics);
cristy3ed852e2009-09-05 21:47:34 +00001722 return(similarity_image);
1723}