blob: dc9f62307037bf2e423c9ee7a6eec4eae3698d4a [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);
268 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
269 highlight_image=DestroyImage(highlight_image);
270 if (status == MagickFalse)
271 difference_image=DestroyImage(difference_image);
272 return(difference_image);
273}
274
275/*
276%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
277% %
278% %
279% %
cristy8a9106f2011-07-05 14:39:26 +0000280% G e t I m a g e D i s t o r t i o n %
cristy3ed852e2009-09-05 21:47:34 +0000281% %
282% %
283% %
284%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
285%
cristy8a9106f2011-07-05 14:39:26 +0000286% GetImageDistortion() compares one or more pixel channels of an image to a
287% reconstructed image and returns the specified distortion metric.
cristy3ed852e2009-09-05 21:47:34 +0000288%
cristy8a9106f2011-07-05 14:39:26 +0000289% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +0000290%
cristy8a9106f2011-07-05 14:39:26 +0000291% MagickBooleanType GetImageDistortion(const Image *image,
292% const Image *reconstruct_image,const MetricType metric,
293% double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000294%
295% A description of each parameter follows:
296%
297% o image: the image.
298%
299% o reconstruct_image: the reconstruct image.
300%
cristy3ed852e2009-09-05 21:47:34 +0000301% o metric: the metric.
302%
303% o distortion: the computed distortion between the images.
304%
305% o exception: return any errors or warnings in this structure.
306%
307*/
308
cristy3cc758f2010-11-27 01:33:49 +0000309static MagickBooleanType GetAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000310 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000311{
cristyc4c8d132010-01-07 01:58:38 +0000312 CacheView
313 *image_view,
314 *reconstruct_view;
315
cristy3ed852e2009-09-05 21:47:34 +0000316 MagickBooleanType
317 status;
318
cristy9d314ff2011-03-09 01:30:28 +0000319 ssize_t
320 y;
321
cristy3ed852e2009-09-05 21:47:34 +0000322 /*
323 Compute the absolute difference in pixels between two images.
324 */
325 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000326 image_view=AcquireCacheView(image);
327 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000328#if defined(MAGICKCORE_OPENMP_SUPPORT)
329 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000330#endif
cristybb503372010-05-27 20:51:26 +0000331 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000332 {
333 double
cristy3fc482f2011-09-23 00:43:35 +0000334 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000335
cristy4c08aed2011-07-01 19:47:50 +0000336 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000337 *restrict p,
338 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000339
cristybb503372010-05-27 20:51:26 +0000340 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000341 i,
342 x;
343
344 if (status == MagickFalse)
345 continue;
346 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
347 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
348 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000349 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000350 {
351 status=MagickFalse;
352 continue;
353 }
cristy3ed852e2009-09-05 21:47:34 +0000354 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000355 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000356 {
cristy3fc482f2011-09-23 00:43:35 +0000357 MagickBooleanType
358 difference;
359
360 register ssize_t
361 i;
362
363 difference=MagickFalse;
364 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
365 {
366 PixelChannel
367 channel;
368
369 PixelTrait
370 reconstruct_traits,
371 traits;
372
373 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
374 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
375 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
376 if ((traits == UndefinedPixelTrait) ||
377 (reconstruct_traits == UndefinedPixelTrait))
378 continue;
cristy49dd6a02011-09-24 23:08:01 +0000379 if ((reconstruct_traits & UpdatePixelTrait) == 0)
380 continue;
cristy0beccfa2011-09-25 20:47:53 +0000381 if (p[i] != GetPixelChannel(reconstruct_image,channel,q))
cristy3fc482f2011-09-23 00:43:35 +0000382 difference=MagickTrue;
383 }
384 if (difference != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000385 {
cristy3fc482f2011-09-23 00:43:35 +0000386 channel_distortion[i]++;
387 channel_distortion[MaxPixelChannels]++;
cristy3ed852e2009-09-05 21:47:34 +0000388 }
cristyed231572011-07-14 02:18:59 +0000389 p+=GetPixelChannels(image);
390 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000391 }
cristyb5d5f722009-11-04 03:03:49 +0000392#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000393 #pragma omp critical (MagickCore_GetAbsoluteError)
394#endif
cristy3fc482f2011-09-23 00:43:35 +0000395 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000396 distortion[i]+=channel_distortion[i];
397 }
398 reconstruct_view=DestroyCacheView(reconstruct_view);
399 image_view=DestroyCacheView(image_view);
400 return(status);
401}
402
cristy49dd6a02011-09-24 23:08:01 +0000403static size_t GetImageChannels(const Image *image)
cristy3ed852e2009-09-05 21:47:34 +0000404{
cristy3fc482f2011-09-23 00:43:35 +0000405 register ssize_t
406 i;
407
cristy49dd6a02011-09-24 23:08:01 +0000408 size_t
cristy3ed852e2009-09-05 21:47:34 +0000409 channels;
410
411 channels=0;
cristy3fc482f2011-09-23 00:43:35 +0000412 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
413 {
414 PixelTrait
415 traits;
416
417 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
cristy25a5f2f2011-09-24 14:09:43 +0000418 if ((traits & UpdatePixelTrait) != 0)
cristy3fc482f2011-09-23 00:43:35 +0000419 channels++;
420 }
cristy3ed852e2009-09-05 21:47:34 +0000421 return(channels);
422}
423
cristy343eee92010-12-11 02:17:57 +0000424static MagickBooleanType GetFuzzDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000425 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy343eee92010-12-11 02:17:57 +0000426{
427 CacheView
428 *image_view,
429 *reconstruct_view;
430
cristy343eee92010-12-11 02:17:57 +0000431 MagickBooleanType
432 status;
433
434 register ssize_t
435 i;
436
cristy9d314ff2011-03-09 01:30:28 +0000437 ssize_t
438 y;
439
cristy343eee92010-12-11 02:17:57 +0000440 status=MagickTrue;
441 image_view=AcquireCacheView(image);
442 reconstruct_view=AcquireCacheView(reconstruct_image);
443#if defined(MAGICKCORE_OPENMP_SUPPORT)
444 #pragma omp parallel for schedule(dynamic,4) shared(status)
445#endif
446 for (y=0; y < (ssize_t) image->rows; y++)
447 {
448 double
cristy3fc482f2011-09-23 00:43:35 +0000449 channel_distortion[MaxPixelChannels+1];
cristy343eee92010-12-11 02:17:57 +0000450
cristy4c08aed2011-07-01 19:47:50 +0000451 register const Quantum
cristy343eee92010-12-11 02:17:57 +0000452 *restrict p,
453 *restrict q;
454
455 register ssize_t
456 i,
457 x;
458
459 if (status == MagickFalse)
460 continue;
461 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristye6529ff2011-02-04 20:05:32 +0000462 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
463 1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000464 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy343eee92010-12-11 02:17:57 +0000465 {
466 status=MagickFalse;
467 continue;
468 }
cristy343eee92010-12-11 02:17:57 +0000469 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
470 for (x=0; x < (ssize_t) image->columns; x++)
471 {
cristy3fc482f2011-09-23 00:43:35 +0000472 register ssize_t
473 i;
cristy343eee92010-12-11 02:17:57 +0000474
cristy3fc482f2011-09-23 00:43:35 +0000475 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
476 {
477 MagickRealType
478 distance;
479
480 PixelChannel
481 channel;
482
483 PixelTrait
484 reconstruct_traits,
485 traits;
486
487 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
488 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
489 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
490 if ((traits == UndefinedPixelTrait) ||
491 (reconstruct_traits == UndefinedPixelTrait))
492 continue;
cristy49dd6a02011-09-24 23:08:01 +0000493 if ((reconstruct_traits & UpdatePixelTrait) == 0)
494 continue;
cristy0beccfa2011-09-25 20:47:53 +0000495 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
496 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000497 distance*=distance;
498 channel_distortion[i]+=distance;
499 channel_distortion[MaxPixelChannels]+=distance;
500 }
cristyed231572011-07-14 02:18:59 +0000501 p+=GetPixelChannels(image);
502 q+=GetPixelChannels(reconstruct_image);
cristy343eee92010-12-11 02:17:57 +0000503 }
504#if defined(MAGICKCORE_OPENMP_SUPPORT)
505 #pragma omp critical (MagickCore_GetMeanSquaredError)
506#endif
cristy3fc482f2011-09-23 00:43:35 +0000507 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000508 distortion[i]+=channel_distortion[i];
509 }
510 reconstruct_view=DestroyCacheView(reconstruct_view);
511 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000512 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000513 distortion[i]/=((double) image->columns*image->rows);
cristy49dd6a02011-09-24 23:08:01 +0000514 distortion[MaxPixelChannels]/=(double) GetImageChannels(image);
cristy3fc482f2011-09-23 00:43:35 +0000515 distortion[MaxPixelChannels]=sqrt(distortion[MaxPixelChannels]);
cristy343eee92010-12-11 02:17:57 +0000516 return(status);
517}
518
cristy3cc758f2010-11-27 01:33:49 +0000519static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000520 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000521{
cristyc4c8d132010-01-07 01:58:38 +0000522 CacheView
523 *image_view,
524 *reconstruct_view;
525
cristy3ed852e2009-09-05 21:47:34 +0000526 MagickBooleanType
527 status;
528
cristybb503372010-05-27 20:51:26 +0000529 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000530 i;
531
cristy9d314ff2011-03-09 01:30:28 +0000532 ssize_t
533 y;
534
cristy3ed852e2009-09-05 21:47:34 +0000535 status=MagickTrue;
536 image_view=AcquireCacheView(image);
537 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000538#if defined(MAGICKCORE_OPENMP_SUPPORT)
539 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000540#endif
cristybb503372010-05-27 20:51:26 +0000541 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000542 {
543 double
cristy3fc482f2011-09-23 00:43:35 +0000544 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000545
cristy4c08aed2011-07-01 19:47:50 +0000546 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000547 *restrict p,
548 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000549
cristybb503372010-05-27 20:51:26 +0000550 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000551 i,
552 x;
553
554 if (status == MagickFalse)
555 continue;
556 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000557 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
558 1,exception);
559 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000560 {
561 status=MagickFalse;
562 continue;
563 }
cristy3ed852e2009-09-05 21:47:34 +0000564 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000565 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000566 {
cristy3fc482f2011-09-23 00:43:35 +0000567 register ssize_t
568 i;
cristy3ed852e2009-09-05 21:47:34 +0000569
cristy3fc482f2011-09-23 00:43:35 +0000570 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
571 {
572 MagickRealType
573 distance;
574
575 PixelChannel
576 channel;
577
578 PixelTrait
579 reconstruct_traits,
580 traits;
581
582 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
583 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
584 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
585 if ((traits == UndefinedPixelTrait) ||
586 (reconstruct_traits == UndefinedPixelTrait))
587 continue;
cristy49dd6a02011-09-24 23:08:01 +0000588 if ((reconstruct_traits & UpdatePixelTrait) == 0)
589 continue;
cristy0beccfa2011-09-25 20:47:53 +0000590 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
591 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000592 channel_distortion[i]+=distance;
593 channel_distortion[MaxPixelChannels]+=distance;
594 }
cristyed231572011-07-14 02:18:59 +0000595 p+=GetPixelChannels(image);
596 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000597 }
cristyb5d5f722009-11-04 03:03:49 +0000598#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000599 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
600#endif
cristy3fc482f2011-09-23 00:43:35 +0000601 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000602 distortion[i]+=channel_distortion[i];
603 }
604 reconstruct_view=DestroyCacheView(reconstruct_view);
605 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000606 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000607 distortion[i]/=((double) image->columns*image->rows);
cristy49dd6a02011-09-24 23:08:01 +0000608 distortion[MaxPixelChannels]/=(double) GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000609 return(status);
610}
611
612static MagickBooleanType GetMeanErrorPerPixel(Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000613 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000614{
cristyc4c8d132010-01-07 01:58:38 +0000615 CacheView
616 *image_view,
617 *reconstruct_view;
618
cristy3ed852e2009-09-05 21:47:34 +0000619 MagickBooleanType
620 status;
621
622 MagickRealType
623 alpha,
624 area,
625 beta,
626 maximum_error,
627 mean_error;
628
cristy9d314ff2011-03-09 01:30:28 +0000629 ssize_t
630 y;
631
cristy3ed852e2009-09-05 21:47:34 +0000632 status=MagickTrue;
633 alpha=1.0;
634 beta=1.0;
635 area=0.0;
636 maximum_error=0.0;
637 mean_error=0.0;
638 image_view=AcquireCacheView(image);
639 reconstruct_view=AcquireCacheView(reconstruct_image);
cristybb503372010-05-27 20:51:26 +0000640 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000641 {
cristy4c08aed2011-07-01 19:47:50 +0000642 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000643 *restrict p,
644 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000645
cristybb503372010-05-27 20:51:26 +0000646 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000647 x;
648
649 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
650 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
651 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000652 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000653 {
654 status=MagickFalse;
655 break;
656 }
cristybb503372010-05-27 20:51:26 +0000657 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000658 {
cristy3fc482f2011-09-23 00:43:35 +0000659 register ssize_t
660 i;
cristy3ed852e2009-09-05 21:47:34 +0000661
cristy3fc482f2011-09-23 00:43:35 +0000662 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
663 {
664 MagickRealType
665 distance;
666
667 PixelChannel
668 channel;
669
670 PixelTrait
671 reconstruct_traits,
672 traits;
673
674 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
675 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
676 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
677 if ((traits == UndefinedPixelTrait) ||
678 (reconstruct_traits == UndefinedPixelTrait))
679 continue;
cristy49dd6a02011-09-24 23:08:01 +0000680 if ((reconstruct_traits & UpdatePixelTrait) == 0)
681 continue;
cristy0beccfa2011-09-25 20:47:53 +0000682 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
683 reconstruct_image,channel,q)));
cristy3fc482f2011-09-23 00:43:35 +0000684 distortion[i]+=distance;
685 distortion[MaxPixelChannels]+=distance;
686 mean_error+=distance*distance;
687 if (distance > maximum_error)
688 maximum_error=distance;
689 area++;
690 }
cristyed231572011-07-14 02:18:59 +0000691 p+=GetPixelChannels(image);
692 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000693 }
694 }
695 reconstruct_view=DestroyCacheView(reconstruct_view);
696 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000697 image->error.mean_error_per_pixel=distortion[MaxPixelChannels]/area;
cristy3ed852e2009-09-05 21:47:34 +0000698 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
699 image->error.normalized_maximum_error=QuantumScale*maximum_error;
700 return(status);
701}
702
cristy3cc758f2010-11-27 01:33:49 +0000703static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000704 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000705{
cristyc4c8d132010-01-07 01:58:38 +0000706 CacheView
707 *image_view,
708 *reconstruct_view;
709
cristy3ed852e2009-09-05 21:47:34 +0000710 MagickBooleanType
711 status;
712
cristybb503372010-05-27 20:51:26 +0000713 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000714 i;
715
cristy9d314ff2011-03-09 01:30:28 +0000716 ssize_t
717 y;
718
cristy3ed852e2009-09-05 21:47:34 +0000719 status=MagickTrue;
720 image_view=AcquireCacheView(image);
721 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000722#if defined(MAGICKCORE_OPENMP_SUPPORT)
723 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000724#endif
cristybb503372010-05-27 20:51:26 +0000725 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000726 {
727 double
cristy3fc482f2011-09-23 00:43:35 +0000728 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000729
cristy4c08aed2011-07-01 19:47:50 +0000730 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000731 *restrict p,
732 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000733
cristybb503372010-05-27 20:51:26 +0000734 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000735 i,
736 x;
737
738 if (status == MagickFalse)
739 continue;
740 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000741 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
742 1,exception);
743 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000744 {
745 status=MagickFalse;
746 continue;
747 }
cristy3ed852e2009-09-05 21:47:34 +0000748 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000749 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000750 {
cristy3fc482f2011-09-23 00:43:35 +0000751 register ssize_t
752 i;
cristy3ed852e2009-09-05 21:47:34 +0000753
cristy3fc482f2011-09-23 00:43:35 +0000754 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
755 {
756 MagickRealType
757 distance;
758
759 PixelChannel
760 channel;
761
762 PixelTrait
763 reconstruct_traits,
764 traits;
765
766 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
767 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
768 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
769 if ((traits == UndefinedPixelTrait) ||
770 (reconstruct_traits == UndefinedPixelTrait))
771 continue;
cristy49dd6a02011-09-24 23:08:01 +0000772 if ((reconstruct_traits & UpdatePixelTrait) == 0)
773 continue;
cristy0beccfa2011-09-25 20:47:53 +0000774 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
775 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000776 distance*=distance;
777 channel_distortion[i]+=distance;
778 channel_distortion[MaxPixelChannels]+=distance;
779 }
cristyed231572011-07-14 02:18:59 +0000780 p+=GetPixelChannels(image);
781 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000782 }
cristyb5d5f722009-11-04 03:03:49 +0000783#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000784 #pragma omp critical (MagickCore_GetMeanSquaredError)
785#endif
cristy3fc482f2011-09-23 00:43:35 +0000786 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000787 distortion[i]+=channel_distortion[i];
788 }
789 reconstruct_view=DestroyCacheView(reconstruct_view);
790 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000791 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000792 distortion[i]/=((double) image->columns*image->rows);
cristy49dd6a02011-09-24 23:08:01 +0000793 distortion[MaxPixelChannels]/=GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000794 return(status);
795}
796
cristy3cc758f2010-11-27 01:33:49 +0000797static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
cristy8a9106f2011-07-05 14:39:26 +0000798 const Image *image,const Image *reconstruct_image,double *distortion,
799 ExceptionInfo *exception)
cristy4c929a72010-11-24 18:54:42 +0000800{
cristy9f48ca62010-11-25 03:06:31 +0000801#define SimilarityImageTag "Similarity/Image"
802
cristy4c929a72010-11-24 18:54:42 +0000803 CacheView
804 *image_view,
805 *reconstruct_view;
806
cristy9f48ca62010-11-25 03:06:31 +0000807 ChannelStatistics
cristy9f48ca62010-11-25 03:06:31 +0000808 *image_statistics,
809 *reconstruct_statistics;
810
cristy4c929a72010-11-24 18:54:42 +0000811 MagickBooleanType
812 status;
813
cristy18a41362010-11-27 15:56:18 +0000814 MagickOffsetType
815 progress;
816
cristy34d6fdc2010-11-26 19:06:08 +0000817 MagickRealType
818 area;
819
cristy4c929a72010-11-24 18:54:42 +0000820 register ssize_t
821 i;
822
cristy3cc758f2010-11-27 01:33:49 +0000823 ssize_t
824 y;
825
cristy34d6fdc2010-11-26 19:06:08 +0000826 /*
cristy18a41362010-11-27 15:56:18 +0000827 Normalize to account for variation due to lighting and exposure condition.
cristy34d6fdc2010-11-26 19:06:08 +0000828 */
cristyd42d9952011-07-08 14:21:50 +0000829 image_statistics=GetImageStatistics(image,exception);
830 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
cristy4c929a72010-11-24 18:54:42 +0000831 status=MagickTrue;
cristy9f48ca62010-11-25 03:06:31 +0000832 progress=0;
cristy3fc482f2011-09-23 00:43:35 +0000833 for (i=0; i <= MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000834 distortion[i]=0.0;
835 area=1.0/((MagickRealType) image->columns*image->rows);
cristy4c929a72010-11-24 18:54:42 +0000836 image_view=AcquireCacheView(image);
837 reconstruct_view=AcquireCacheView(reconstruct_image);
cristy4c929a72010-11-24 18:54:42 +0000838 for (y=0; y < (ssize_t) image->rows; y++)
839 {
cristy4c08aed2011-07-01 19:47:50 +0000840 register const Quantum
cristy4c929a72010-11-24 18:54:42 +0000841 *restrict p,
842 *restrict q;
843
844 register ssize_t
cristy4c929a72010-11-24 18:54:42 +0000845 x;
846
847 if (status == MagickFalse)
848 continue;
849 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy34d6fdc2010-11-26 19:06:08 +0000850 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
851 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000852 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy4c929a72010-11-24 18:54:42 +0000853 {
854 status=MagickFalse;
855 continue;
856 }
cristy4c929a72010-11-24 18:54:42 +0000857 for (x=0; x < (ssize_t) image->columns; x++)
858 {
cristy3fc482f2011-09-23 00:43:35 +0000859 register ssize_t
860 i;
861
862 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
863 {
864 PixelChannel
865 channel;
866
867 PixelTrait
868 reconstruct_traits,
869 traits;
870
871 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
872 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
873 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
874 if ((traits == UndefinedPixelTrait) ||
875 (reconstruct_traits == UndefinedPixelTrait))
876 continue;
cristy49dd6a02011-09-24 23:08:01 +0000877 if ((reconstruct_traits & UpdatePixelTrait) == 0)
878 continue;
cristy3fc482f2011-09-23 00:43:35 +0000879 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
cristy0beccfa2011-09-25 20:47:53 +0000880 (GetPixelChannel(reconstruct_image,channel,q)-
881 reconstruct_statistics[channel].mean);
cristy3fc482f2011-09-23 00:43:35 +0000882 }
cristyed231572011-07-14 02:18:59 +0000883 p+=GetPixelChannels(image);
884 q+=GetPixelChannels(image);
cristy4c929a72010-11-24 18:54:42 +0000885 }
cristy9f48ca62010-11-25 03:06:31 +0000886 if (image->progress_monitor != (MagickProgressMonitor) NULL)
887 {
888 MagickBooleanType
889 proceed;
890
cristy9f48ca62010-11-25 03:06:31 +0000891 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
892 image->rows);
893 if (proceed == MagickFalse)
894 status=MagickFalse;
895 }
cristy4c929a72010-11-24 18:54:42 +0000896 }
897 reconstruct_view=DestroyCacheView(reconstruct_view);
898 image_view=DestroyCacheView(image_view);
cristy34d6fdc2010-11-26 19:06:08 +0000899 /*
900 Divide by the standard deviation.
901 */
cristy3fc482f2011-09-23 00:43:35 +0000902 distortion[MaxPixelChannels]=0.0;
903 for (i=0; i < MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000904 {
905 MagickRealType
cristy18a41362010-11-27 15:56:18 +0000906 gamma;
cristy34d6fdc2010-11-26 19:06:08 +0000907
cristy3fc482f2011-09-23 00:43:35 +0000908 PixelChannel
909 channel;
910
911 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
cristy18a41362010-11-27 15:56:18 +0000912 gamma=image_statistics[i].standard_deviation*
cristy3fc482f2011-09-23 00:43:35 +0000913 reconstruct_statistics[channel].standard_deviation;
cristy18a41362010-11-27 15:56:18 +0000914 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
915 distortion[i]=QuantumRange*gamma*distortion[i];
cristy3fc482f2011-09-23 00:43:35 +0000916 distortion[MaxPixelChannels]+=distortion[i]*distortion[i];
cristy34d6fdc2010-11-26 19:06:08 +0000917 }
cristy3fc482f2011-09-23 00:43:35 +0000918 distortion[MaxPixelChannels]=sqrt(distortion[MaxPixelChannels]/
cristy49dd6a02011-09-24 23:08:01 +0000919 GetImageChannels(image));
cristy34d6fdc2010-11-26 19:06:08 +0000920 /*
921 Free resources.
922 */
cristy9f48ca62010-11-25 03:06:31 +0000923 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
924 reconstruct_statistics);
925 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
926 image_statistics);
cristy4c929a72010-11-24 18:54:42 +0000927 return(status);
928}
929
cristy3cc758f2010-11-27 01:33:49 +0000930static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000931 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000932{
cristyc4c8d132010-01-07 01:58:38 +0000933 CacheView
934 *image_view,
935 *reconstruct_view;
936
cristy3ed852e2009-09-05 21:47:34 +0000937 MagickBooleanType
938 status;
939
cristy9d314ff2011-03-09 01:30:28 +0000940 ssize_t
941 y;
942
cristy3ed852e2009-09-05 21:47:34 +0000943 status=MagickTrue;
944 image_view=AcquireCacheView(image);
945 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000946#if defined(MAGICKCORE_OPENMP_SUPPORT)
947 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000948#endif
cristybb503372010-05-27 20:51:26 +0000949 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000950 {
951 double
cristy3fc482f2011-09-23 00:43:35 +0000952 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000953
cristy4c08aed2011-07-01 19:47:50 +0000954 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000955 *restrict p,
956 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000957
cristybb503372010-05-27 20:51:26 +0000958 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000959 i,
960 x;
961
962 if (status == MagickFalse)
963 continue;
964 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
965 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
966 reconstruct_image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000967 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000968 {
969 status=MagickFalse;
970 continue;
971 }
cristy3ed852e2009-09-05 21:47:34 +0000972 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000973 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000974 {
cristy3fc482f2011-09-23 00:43:35 +0000975 register ssize_t
976 i;
cristy3ed852e2009-09-05 21:47:34 +0000977
cristy3fc482f2011-09-23 00:43:35 +0000978 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
979 {
980 MagickRealType
981 distance;
982
983 PixelChannel
984 channel;
985
986 PixelTrait
987 reconstruct_traits,
988 traits;
989
990 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
991 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
992 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
993 if ((traits == UndefinedPixelTrait) ||
994 (reconstruct_traits == UndefinedPixelTrait))
995 continue;
cristy49dd6a02011-09-24 23:08:01 +0000996 if ((reconstruct_traits & UpdatePixelTrait) == 0)
997 continue;
cristy0beccfa2011-09-25 20:47:53 +0000998 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
999 reconstruct_image,channel,q));
cristy25a5f2f2011-09-24 14:09:43 +00001000 if (distance > channel_distortion[i])
cristy3fc482f2011-09-23 00:43:35 +00001001 channel_distortion[i]=distance;
1002 if (distance > channel_distortion[MaxPixelChannels])
1003 channel_distortion[MaxPixelChannels]=distance;
1004 }
cristyed231572011-07-14 02:18:59 +00001005 p+=GetPixelChannels(image);
1006 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001007 }
cristyb5d5f722009-11-04 03:03:49 +00001008#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001009 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1010#endif
cristy3fc482f2011-09-23 00:43:35 +00001011 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001012 if (channel_distortion[i] > distortion[i])
1013 distortion[i]=channel_distortion[i];
1014 }
1015 reconstruct_view=DestroyCacheView(reconstruct_view);
1016 image_view=DestroyCacheView(image_view);
1017 return(status);
1018}
1019
1020static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001021 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001022{
1023 MagickBooleanType
1024 status;
1025
cristy3fc482f2011-09-23 00:43:35 +00001026 register ssize_t
1027 i;
1028
cristy8a9106f2011-07-05 14:39:26 +00001029 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001030 for (i=0; i <= MaxPixelChannels; i++)
1031 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
cristy3ed852e2009-09-05 21:47:34 +00001032 return(status);
1033}
1034
cristy3cc758f2010-11-27 01:33:49 +00001035static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001036 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001037{
1038 MagickBooleanType
1039 status;
1040
cristy3fc482f2011-09-23 00:43:35 +00001041 register ssize_t
1042 i;
1043
cristy8a9106f2011-07-05 14:39:26 +00001044 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001045 for (i=0; i <= MaxPixelChannels; i++)
1046 distortion[i]=sqrt(distortion[i]);
cristy3ed852e2009-09-05 21:47:34 +00001047 return(status);
1048}
1049
cristy8a9106f2011-07-05 14:39:26 +00001050MagickExport MagickBooleanType GetImageDistortion(Image *image,
1051 const Image *reconstruct_image,const MetricType metric,double *distortion,
1052 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001053{
1054 double
1055 *channel_distortion;
1056
1057 MagickBooleanType
1058 status;
1059
1060 size_t
1061 length;
1062
1063 assert(image != (Image *) NULL);
1064 assert(image->signature == MagickSignature);
1065 if (image->debug != MagickFalse)
1066 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1067 assert(reconstruct_image != (const Image *) NULL);
1068 assert(reconstruct_image->signature == MagickSignature);
1069 assert(distortion != (double *) NULL);
1070 *distortion=0.0;
1071 if (image->debug != MagickFalse)
1072 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1073 if ((reconstruct_image->columns != image->columns) ||
1074 (reconstruct_image->rows != image->rows))
1075 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1076 /*
1077 Get image distortion.
1078 */
cristy3fc482f2011-09-23 00:43:35 +00001079 length=MaxPixelChannels+1;
cristy3ed852e2009-09-05 21:47:34 +00001080 channel_distortion=(double *) AcquireQuantumMemory(length,
1081 sizeof(*channel_distortion));
1082 if (channel_distortion == (double *) NULL)
1083 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1084 (void) ResetMagickMemory(channel_distortion,0,length*
1085 sizeof(*channel_distortion));
1086 switch (metric)
1087 {
1088 case AbsoluteErrorMetric:
1089 {
cristy8a9106f2011-07-05 14:39:26 +00001090 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1091 exception);
cristy3ed852e2009-09-05 21:47:34 +00001092 break;
1093 }
cristy343eee92010-12-11 02:17:57 +00001094 case FuzzErrorMetric:
1095 {
cristy8a9106f2011-07-05 14:39:26 +00001096 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1097 exception);
cristy343eee92010-12-11 02:17:57 +00001098 break;
1099 }
cristy3ed852e2009-09-05 21:47:34 +00001100 case MeanAbsoluteErrorMetric:
1101 {
cristy8a9106f2011-07-05 14:39:26 +00001102 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001103 channel_distortion,exception);
1104 break;
1105 }
1106 case MeanErrorPerPixelMetric:
1107 {
cristy8a9106f2011-07-05 14:39:26 +00001108 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1109 exception);
cristy3ed852e2009-09-05 21:47:34 +00001110 break;
1111 }
1112 case MeanSquaredErrorMetric:
1113 {
cristy8a9106f2011-07-05 14:39:26 +00001114 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001115 channel_distortion,exception);
1116 break;
1117 }
cristy4c929a72010-11-24 18:54:42 +00001118 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001119 default:
cristy4c929a72010-11-24 18:54:42 +00001120 {
cristy3cc758f2010-11-27 01:33:49 +00001121 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001122 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001123 break;
1124 }
cristy3ed852e2009-09-05 21:47:34 +00001125 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001126 {
cristy8a9106f2011-07-05 14:39:26 +00001127 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001128 channel_distortion,exception);
1129 break;
1130 }
1131 case PeakSignalToNoiseRatioMetric:
1132 {
cristy8a9106f2011-07-05 14:39:26 +00001133 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001134 channel_distortion,exception);
1135 break;
1136 }
1137 case RootMeanSquaredErrorMetric:
1138 {
cristy8a9106f2011-07-05 14:39:26 +00001139 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001140 channel_distortion,exception);
1141 break;
1142 }
1143 }
cristy3fc482f2011-09-23 00:43:35 +00001144 *distortion=channel_distortion[MaxPixelChannels];
cristy3ed852e2009-09-05 21:47:34 +00001145 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1146 return(status);
1147}
1148
1149/*
1150%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1151% %
1152% %
1153% %
cristy8a9106f2011-07-05 14:39:26 +00001154% 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 +00001155% %
1156% %
1157% %
1158%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1159%
cristy8a9106f2011-07-05 14:39:26 +00001160% GetImageDistrortion() compares the pixel channels of an image to a
cristy3ed852e2009-09-05 21:47:34 +00001161% reconstructed image and returns the specified distortion metric for each
1162% channel.
1163%
cristy8a9106f2011-07-05 14:39:26 +00001164% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +00001165%
cristy8a9106f2011-07-05 14:39:26 +00001166% double *GetImageDistortions(const Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001167% const Image *reconstruct_image,const MetricType metric,
1168% ExceptionInfo *exception)
1169%
1170% A description of each parameter follows:
1171%
1172% o image: the image.
1173%
1174% o reconstruct_image: the reconstruct image.
1175%
1176% o metric: the metric.
1177%
1178% o exception: return any errors or warnings in this structure.
1179%
1180*/
cristy8a9106f2011-07-05 14:39:26 +00001181MagickExport double *GetImageDistortions(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001182 const Image *reconstruct_image,const MetricType metric,
1183 ExceptionInfo *exception)
1184{
1185 double
1186 *channel_distortion;
1187
1188 MagickBooleanType
1189 status;
1190
1191 size_t
1192 length;
1193
1194 assert(image != (Image *) NULL);
1195 assert(image->signature == MagickSignature);
1196 if (image->debug != MagickFalse)
1197 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1198 assert(reconstruct_image != (const Image *) NULL);
1199 assert(reconstruct_image->signature == MagickSignature);
1200 if (image->debug != MagickFalse)
1201 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1202 if ((reconstruct_image->columns != image->columns) ||
1203 (reconstruct_image->rows != image->rows))
1204 {
1205 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1206 ImageError,"ImageSizeDiffers","`%s'",image->filename);
1207 return((double *) NULL);
1208 }
1209 /*
1210 Get image distortion.
1211 */
cristy3fc482f2011-09-23 00:43:35 +00001212 length=MaxPixelChannels+1UL;
cristy3ed852e2009-09-05 21:47:34 +00001213 channel_distortion=(double *) AcquireQuantumMemory(length,
1214 sizeof(*channel_distortion));
1215 if (channel_distortion == (double *) NULL)
1216 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1217 (void) ResetMagickMemory(channel_distortion,0,length*
1218 sizeof(*channel_distortion));
cristyda16f162011-02-19 23:52:17 +00001219 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +00001220 switch (metric)
1221 {
1222 case AbsoluteErrorMetric:
1223 {
cristy8a9106f2011-07-05 14:39:26 +00001224 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1225 exception);
cristy3ed852e2009-09-05 21:47:34 +00001226 break;
1227 }
cristy343eee92010-12-11 02:17:57 +00001228 case FuzzErrorMetric:
1229 {
cristy8a9106f2011-07-05 14:39:26 +00001230 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1231 exception);
cristy343eee92010-12-11 02:17:57 +00001232 break;
1233 }
cristy3ed852e2009-09-05 21:47:34 +00001234 case MeanAbsoluteErrorMetric:
1235 {
cristy8a9106f2011-07-05 14:39:26 +00001236 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001237 channel_distortion,exception);
1238 break;
1239 }
1240 case MeanErrorPerPixelMetric:
1241 {
cristy8a9106f2011-07-05 14:39:26 +00001242 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1243 exception);
cristy3ed852e2009-09-05 21:47:34 +00001244 break;
1245 }
1246 case MeanSquaredErrorMetric:
1247 {
cristy8a9106f2011-07-05 14:39:26 +00001248 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001249 channel_distortion,exception);
1250 break;
1251 }
cristy4c929a72010-11-24 18:54:42 +00001252 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001253 default:
cristy4c929a72010-11-24 18:54:42 +00001254 {
cristy3cc758f2010-11-27 01:33:49 +00001255 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001256 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001257 break;
1258 }
cristy3ed852e2009-09-05 21:47:34 +00001259 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001260 {
cristy8a9106f2011-07-05 14:39:26 +00001261 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001262 channel_distortion,exception);
1263 break;
1264 }
1265 case PeakSignalToNoiseRatioMetric:
1266 {
cristy8a9106f2011-07-05 14:39:26 +00001267 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001268 channel_distortion,exception);
1269 break;
1270 }
1271 case RootMeanSquaredErrorMetric:
1272 {
cristy8a9106f2011-07-05 14:39:26 +00001273 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001274 channel_distortion,exception);
1275 break;
1276 }
1277 }
cristyda16f162011-02-19 23:52:17 +00001278 if (status == MagickFalse)
1279 {
1280 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1281 return((double *) NULL);
1282 }
cristy3ed852e2009-09-05 21:47:34 +00001283 return(channel_distortion);
1284}
1285
1286/*
1287%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1288% %
1289% %
1290% %
1291% I s I m a g e s E q u a l %
1292% %
1293% %
1294% %
1295%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1296%
1297% IsImagesEqual() measures the difference between colors at each pixel
1298% location of two images. A value other than 0 means the colors match
1299% exactly. Otherwise an error measure is computed by summing over all
1300% pixels in an image the distance squared in RGB space between each image
1301% pixel and its corresponding pixel in the reconstruct image. The error
1302% measure is assigned to these image members:
1303%
1304% o mean_error_per_pixel: The mean error for any single pixel in
1305% the image.
1306%
1307% o normalized_mean_error: The normalized mean quantization error for
1308% any single pixel in the image. This distance measure is normalized to
1309% a range between 0 and 1. It is independent of the range of red, green,
1310% and blue values in the image.
1311%
1312% o normalized_maximum_error: The normalized maximum quantization
1313% error for any single pixel in the image. This distance measure is
1314% normalized to a range between 0 and 1. It is independent of the range
1315% of red, green, and blue values in your image.
1316%
1317% A small normalized mean square error, accessed as
1318% image->normalized_mean_error, suggests the images are very similar in
1319% spatial layout and color.
1320%
1321% The format of the IsImagesEqual method is:
1322%
1323% MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001324% const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001325%
1326% A description of each parameter follows.
1327%
1328% o image: the image.
1329%
1330% o reconstruct_image: the reconstruct image.
1331%
cristy018f07f2011-09-04 21:15:19 +00001332% o exception: return any errors or warnings in this structure.
1333%
cristy3ed852e2009-09-05 21:47:34 +00001334*/
1335MagickExport MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001336 const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001337{
cristyc4c8d132010-01-07 01:58:38 +00001338 CacheView
1339 *image_view,
1340 *reconstruct_view;
1341
cristy3ed852e2009-09-05 21:47:34 +00001342 MagickBooleanType
1343 status;
1344
1345 MagickRealType
1346 area,
1347 maximum_error,
1348 mean_error,
1349 mean_error_per_pixel;
1350
cristy9d314ff2011-03-09 01:30:28 +00001351 ssize_t
1352 y;
1353
cristy3ed852e2009-09-05 21:47:34 +00001354 assert(image != (Image *) NULL);
1355 assert(image->signature == MagickSignature);
1356 assert(reconstruct_image != (const Image *) NULL);
1357 assert(reconstruct_image->signature == MagickSignature);
1358 if ((reconstruct_image->columns != image->columns) ||
1359 (reconstruct_image->rows != image->rows))
1360 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1361 area=0.0;
1362 maximum_error=0.0;
1363 mean_error_per_pixel=0.0;
1364 mean_error=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001365 image_view=AcquireCacheView(image);
1366 reconstruct_view=AcquireCacheView(reconstruct_image);
cristybb503372010-05-27 20:51:26 +00001367 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001368 {
cristy4c08aed2011-07-01 19:47:50 +00001369 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001370 *restrict p,
1371 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001372
cristybb503372010-05-27 20:51:26 +00001373 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001374 x;
1375
1376 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1377 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1378 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001379 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001380 break;
cristybb503372010-05-27 20:51:26 +00001381 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001382 {
cristyd5c15f92011-09-23 00:58:33 +00001383 register ssize_t
1384 i;
cristy3ed852e2009-09-05 21:47:34 +00001385
cristyd5c15f92011-09-23 00:58:33 +00001386 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1387 {
1388 MagickRealType
1389 distance;
1390
1391 PixelChannel
1392 channel;
1393
1394 PixelTrait
1395 reconstruct_traits,
1396 traits;
1397
1398 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1399 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1400 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1401 if ((traits == UndefinedPixelTrait) ||
1402 (reconstruct_traits == UndefinedPixelTrait))
1403 continue;
cristy49dd6a02011-09-24 23:08:01 +00001404 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1405 continue;
cristy0beccfa2011-09-25 20:47:53 +00001406 distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
1407 channel,q));
cristyd5c15f92011-09-23 00:58:33 +00001408 mean_error_per_pixel+=distance;
1409 mean_error+=distance*distance;
1410 if (distance > maximum_error)
1411 maximum_error=distance;
1412 area++;
1413 }
cristyed231572011-07-14 02:18:59 +00001414 p+=GetPixelChannels(image);
1415 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001416 }
1417 }
1418 reconstruct_view=DestroyCacheView(reconstruct_view);
1419 image_view=DestroyCacheView(image_view);
1420 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1421 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1422 mean_error/area);
1423 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1424 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1425 return(status);
1426}
1427
1428/*
1429%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1430% %
1431% %
1432% %
1433% S i m i l a r i t y I m a g e %
1434% %
1435% %
1436% %
1437%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1438%
1439% SimilarityImage() compares the reference image of the image and returns the
1440% best match offset. In addition, it returns a similarity image such that an
1441% exact match location is completely white and if none of the pixels match,
1442% black, otherwise some gray level in-between.
1443%
1444% The format of the SimilarityImageImage method is:
1445%
1446% Image *SimilarityImage(const Image *image,const Image *reference,
1447% RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1448%
1449% A description of each parameter follows:
1450%
1451% o image: the image.
1452%
1453% o reference: find an area of the image that closely resembles this image.
1454%
1455% o the best match offset of the reference image within the image.
1456%
1457% o similarity: the computed similarity between the images.
1458%
1459% o exception: return any errors or warnings in this structure.
1460%
1461*/
1462
cristy3cc758f2010-11-27 01:33:49 +00001463static double GetNCCDistortion(const Image *image,
1464 const Image *reconstruct_image,
1465 const ChannelStatistics *reconstruct_statistics,ExceptionInfo *exception)
1466{
1467#define SimilarityImageTag "Similarity/Image"
1468
1469 CacheView
1470 *image_view,
1471 *reconstruct_view;
1472
1473 ChannelStatistics
1474 *image_statistics;
1475
1476 double
1477 distortion;
1478
1479 MagickBooleanType
1480 status;
1481
1482 MagickRealType
cristy18a41362010-11-27 15:56:18 +00001483 area,
1484 gamma;
cristy3cc758f2010-11-27 01:33:49 +00001485
1486 ssize_t
1487 y;
1488
cristy3cc758f2010-11-27 01:33:49 +00001489 /*
cristy18a41362010-11-27 15:56:18 +00001490 Normalize to account for variation due to lighting and exposure condition.
cristy3cc758f2010-11-27 01:33:49 +00001491 */
cristyd42d9952011-07-08 14:21:50 +00001492 image_statistics=GetImageStatistics(image,exception);
cristy3cc758f2010-11-27 01:33:49 +00001493 status=MagickTrue;
1494 distortion=0.0;
1495 area=1.0/((MagickRealType) image->columns*image->rows);
1496 image_view=AcquireCacheView(image);
1497 reconstruct_view=AcquireCacheView(reconstruct_image);
1498 for (y=0; y < (ssize_t) image->rows; y++)
1499 {
cristy4c08aed2011-07-01 19:47:50 +00001500 register const Quantum
cristy3cc758f2010-11-27 01:33:49 +00001501 *restrict p,
1502 *restrict q;
1503
1504 register ssize_t
1505 x;
1506
1507 if (status == MagickFalse)
1508 continue;
1509 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1510 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1511 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001512 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3cc758f2010-11-27 01:33:49 +00001513 {
1514 status=MagickFalse;
1515 continue;
1516 }
cristy3cc758f2010-11-27 01:33:49 +00001517 for (x=0; x < (ssize_t) image->columns; x++)
1518 {
cristyd5c15f92011-09-23 00:58:33 +00001519 register ssize_t
1520 i;
1521
1522 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1523 {
1524 PixelChannel
1525 channel;
1526
1527 PixelTrait
1528 reconstruct_traits,
1529 traits;
1530
1531 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1532 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1533 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1534 if ((traits == UndefinedPixelTrait) ||
1535 (reconstruct_traits == UndefinedPixelTrait))
1536 continue;
cristy49dd6a02011-09-24 23:08:01 +00001537 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1538 continue;
cristyd5c15f92011-09-23 00:58:33 +00001539 distortion+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
cristy0beccfa2011-09-25 20:47:53 +00001540 (GetPixelChannel(reconstruct_image,channel,q)-
1541 reconstruct_statistics[channel].mean);
cristyd5c15f92011-09-23 00:58:33 +00001542 }
cristyed231572011-07-14 02:18:59 +00001543 p+=GetPixelChannels(image);
1544 q+=GetPixelChannels(reconstruct_image);
cristy3cc758f2010-11-27 01:33:49 +00001545 }
1546 }
1547 reconstruct_view=DestroyCacheView(reconstruct_view);
1548 image_view=DestroyCacheView(image_view);
1549 /*
1550 Divide by the standard deviation.
1551 */
cristy3fc482f2011-09-23 00:43:35 +00001552 gamma=image_statistics[MaxPixelChannels].standard_deviation*
1553 reconstruct_statistics[MaxPixelChannels].standard_deviation;
cristy18a41362010-11-27 15:56:18 +00001554 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1555 distortion=QuantumRange*gamma*distortion;
cristy49dd6a02011-09-24 23:08:01 +00001556 distortion=sqrt(distortion/GetImageChannels(image));
cristy3cc758f2010-11-27 01:33:49 +00001557 /*
1558 Free resources.
1559 */
1560 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1561 image_statistics);
1562 return(1.0-distortion);
1563}
1564
cristy3ed852e2009-09-05 21:47:34 +00001565static double GetSimilarityMetric(const Image *image,const Image *reference,
cristy3cc758f2010-11-27 01:33:49 +00001566 const ChannelStatistics *reference_statistics,const ssize_t x_offset,
1567 const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001568{
1569 double
cristy3cc758f2010-11-27 01:33:49 +00001570 distortion;
cristy3ed852e2009-09-05 21:47:34 +00001571
cristy713ff212010-11-26 21:56:11 +00001572 Image
1573 *similarity_image;
cristy3ed852e2009-09-05 21:47:34 +00001574
cristy713ff212010-11-26 21:56:11 +00001575 RectangleInfo
1576 geometry;
cristy3ed852e2009-09-05 21:47:34 +00001577
cristy713ff212010-11-26 21:56:11 +00001578 SetGeometry(reference,&geometry);
1579 geometry.x=x_offset;
1580 geometry.y=y_offset;
1581 similarity_image=CropImage(image,&geometry,exception);
1582 if (similarity_image == (Image *) NULL)
1583 return(0.0);
cristy3cc758f2010-11-27 01:33:49 +00001584 distortion=GetNCCDistortion(reference,similarity_image,reference_statistics,
1585 exception);
cristy713ff212010-11-26 21:56:11 +00001586 similarity_image=DestroyImage(similarity_image);
cristy3cc758f2010-11-27 01:33:49 +00001587 return(distortion);
cristy3ed852e2009-09-05 21:47:34 +00001588}
1589
1590MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1591 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1592{
1593#define SimilarityImageTag "Similarity/Image"
1594
cristyc4c8d132010-01-07 01:58:38 +00001595 CacheView
1596 *similarity_view;
1597
cristy3cc758f2010-11-27 01:33:49 +00001598 ChannelStatistics
1599 *reference_statistics;
1600
cristy3ed852e2009-09-05 21:47:34 +00001601 Image
1602 *similarity_image;
1603
1604 MagickBooleanType
1605 status;
1606
cristybb503372010-05-27 20:51:26 +00001607 MagickOffsetType
1608 progress;
1609
1610 ssize_t
1611 y;
1612
cristy3ed852e2009-09-05 21:47:34 +00001613 assert(image != (const Image *) NULL);
1614 assert(image->signature == MagickSignature);
1615 if (image->debug != MagickFalse)
1616 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1617 assert(exception != (ExceptionInfo *) NULL);
1618 assert(exception->signature == MagickSignature);
1619 assert(offset != (RectangleInfo *) NULL);
1620 SetGeometry(reference,offset);
1621 *similarity_metric=1.0;
cristyf9255b92010-08-14 22:59:00 +00001622 if ((reference->columns > image->columns) || (reference->rows > image->rows))
cristy3ed852e2009-09-05 21:47:34 +00001623 ThrowImageException(ImageError,"ImageSizeDiffers");
1624 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1625 image->rows-reference->rows+1,MagickTrue,exception);
1626 if (similarity_image == (Image *) NULL)
1627 return((Image *) NULL);
cristyd5c15f92011-09-23 00:58:33 +00001628 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1629 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001630 {
cristy3ed852e2009-09-05 21:47:34 +00001631 similarity_image=DestroyImage(similarity_image);
1632 return((Image *) NULL);
1633 }
1634 /*
1635 Measure similarity of reference image against image.
1636 */
1637 status=MagickTrue;
1638 progress=0;
cristyd42d9952011-07-08 14:21:50 +00001639 reference_statistics=GetImageStatistics(reference,exception);
cristy3ed852e2009-09-05 21:47:34 +00001640 similarity_view=AcquireCacheView(similarity_image);
cristyb5d5f722009-11-04 03:03:49 +00001641#if defined(MAGICKCORE_OPENMP_SUPPORT)
1642 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001643#endif
cristybb503372010-05-27 20:51:26 +00001644 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
cristy3ed852e2009-09-05 21:47:34 +00001645 {
1646 double
1647 similarity;
1648
cristy4c08aed2011-07-01 19:47:50 +00001649 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001650 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001651
cristy49dd6a02011-09-24 23:08:01 +00001652 register ssize_t
1653 x;
1654
cristy3ed852e2009-09-05 21:47:34 +00001655 if (status == MagickFalse)
1656 continue;
cristy3cc758f2010-11-27 01:33:49 +00001657 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1658 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001659 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001660 {
1661 status=MagickFalse;
1662 continue;
1663 }
cristybb503372010-05-27 20:51:26 +00001664 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
cristy3ed852e2009-09-05 21:47:34 +00001665 {
cristy49dd6a02011-09-24 23:08:01 +00001666 register ssize_t
1667 i;
1668
cristy3cc758f2010-11-27 01:33:49 +00001669 similarity=GetSimilarityMetric(image,reference,reference_statistics,x,y,
1670 exception);
cristyb5d5f722009-11-04 03:03:49 +00001671#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001672 #pragma omp critical (MagickCore_SimilarityImage)
1673#endif
1674 if (similarity < *similarity_metric)
1675 {
1676 *similarity_metric=similarity;
1677 offset->x=x;
1678 offset->y=y;
1679 }
cristy49dd6a02011-09-24 23:08:01 +00001680 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1681 {
1682 PixelChannel
1683 channel;
1684
1685 PixelTrait
1686 similarity_traits,
1687 traits;
1688
1689 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1690 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1691 similarity_traits=GetPixelChannelMapTraits(similarity_image,channel);
1692 if ((traits == UndefinedPixelTrait) ||
1693 (similarity_traits == UndefinedPixelTrait))
1694 continue;
1695 if ((similarity_traits & UpdatePixelTrait) == 0)
1696 continue;
cristy0beccfa2011-09-25 20:47:53 +00001697 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1698 QuantumRange*similarity),q);
cristy49dd6a02011-09-24 23:08:01 +00001699 }
cristyed231572011-07-14 02:18:59 +00001700 q+=GetPixelChannels(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001701 }
1702 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1703 status=MagickFalse;
1704 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1705 {
1706 MagickBooleanType
1707 proceed;
1708
cristyb5d5f722009-11-04 03:03:49 +00001709#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001710 #pragma omp critical (MagickCore_SimilarityImage)
1711#endif
1712 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1713 image->rows);
1714 if (proceed == MagickFalse)
1715 status=MagickFalse;
1716 }
1717 }
1718 similarity_view=DestroyCacheView(similarity_view);
cristy3cc758f2010-11-27 01:33:49 +00001719 reference_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1720 reference_statistics);
cristy3ed852e2009-09-05 21:47:34 +00001721 return(similarity_image);
1722}