blob: d2ee64264498de54d98e6feaa23a05045a516c84 [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);
cristy3ed852e2009-09-05 21:47:34 +0000164 (void) QueryMagickColor("#f1001ecc",&highlight,exception);
165 artifact=GetImageArtifact(image,"highlight-color");
166 if (artifact != (const char *) NULL)
167 (void) QueryMagickColor(artifact,&highlight,exception);
168 (void) QueryMagickColor("#ffffffcc",&lowlight,exception);
169 artifact=GetImageArtifact(image,"lowlight-color");
170 if (artifact != (const char *) NULL)
171 (void) QueryMagickColor(artifact,&lowlight,exception);
172 if (highlight_image->colorspace == CMYKColorspace)
173 {
174 ConvertRGBToCMYK(&highlight);
175 ConvertRGBToCMYK(&lowlight);
176 }
177 /*
178 Generate difference image.
179 */
180 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000181 image_view=AcquireCacheView(image);
182 reconstruct_view=AcquireCacheView(reconstruct_image);
183 highlight_view=AcquireCacheView(highlight_image);
cristyb5d5f722009-11-04 03:03:49 +0000184#if defined(MAGICKCORE_OPENMP_SUPPORT)
185 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000186#endif
cristybb503372010-05-27 20:51:26 +0000187 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000188 {
189 MagickBooleanType
190 sync;
191
cristy4c08aed2011-07-01 19:47:50 +0000192 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000193 *restrict p,
194 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000195
cristy4c08aed2011-07-01 19:47:50 +0000196 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000197 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000198
cristy49dd6a02011-09-24 23:08:01 +0000199 register ssize_t
200 x;
201
cristy3ed852e2009-09-05 21:47:34 +0000202 if (status == MagickFalse)
203 continue;
204 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
205 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
206 1,exception);
207 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
208 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000209 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
210 (r == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000211 {
212 status=MagickFalse;
213 continue;
214 }
cristybb503372010-05-27 20:51:26 +0000215 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000216 {
217 MagickStatusType
218 difference;
219
cristy3fc482f2011-09-23 00:43:35 +0000220 register ssize_t
221 i;
222
cristy3ed852e2009-09-05 21:47:34 +0000223 difference=MagickFalse;
cristy3fc482f2011-09-23 00:43:35 +0000224 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
225 {
cristy0beccfa2011-09-25 20:47:53 +0000226 MagickRealType
227 distance;
228
cristy3fc482f2011-09-23 00:43:35 +0000229 PixelChannel
230 channel;
231
232 PixelTrait
233 reconstruct_traits,
234 traits;
235
236 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
237 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
238 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
239 if ((traits == UndefinedPixelTrait) ||
240 (reconstruct_traits == UndefinedPixelTrait))
241 continue;
cristy49dd6a02011-09-24 23:08:01 +0000242 if ((reconstruct_traits & UpdatePixelTrait) == 0)
243 continue;
cristy0beccfa2011-09-25 20:47:53 +0000244 distance=p[i]-(MagickRealType)
245 GetPixelChannel(reconstruct_image,channel,q);
246 if (fabs((double) distance) >= MagickEpsilon)
cristy3fc482f2011-09-23 00:43:35 +0000247 difference=MagickTrue;
248 }
249 if (difference == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000250 SetPixelPixelInfo(highlight_image,&lowlight,r);
cristy3fc482f2011-09-23 00:43:35 +0000251 else
252 SetPixelPixelInfo(highlight_image,&highlight,r);
cristyed231572011-07-14 02:18:59 +0000253 p+=GetPixelChannels(image);
254 q+=GetPixelChannels(reconstruct_image);
255 r+=GetPixelChannels(highlight_image);
cristy3ed852e2009-09-05 21:47:34 +0000256 }
257 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
258 if (sync == MagickFalse)
259 status=MagickFalse;
260 }
261 highlight_view=DestroyCacheView(highlight_view);
262 reconstruct_view=DestroyCacheView(reconstruct_view);
263 image_view=DestroyCacheView(image_view);
264 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
265 highlight_image=DestroyImage(highlight_image);
266 if (status == MagickFalse)
267 difference_image=DestroyImage(difference_image);
268 return(difference_image);
269}
270
271/*
272%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
273% %
274% %
275% %
cristy8a9106f2011-07-05 14:39:26 +0000276% G e t I m a g e D i s t o r t i o n %
cristy3ed852e2009-09-05 21:47:34 +0000277% %
278% %
279% %
280%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
281%
cristy8a9106f2011-07-05 14:39:26 +0000282% GetImageDistortion() compares one or more pixel channels of an image to a
283% reconstructed image and returns the specified distortion metric.
cristy3ed852e2009-09-05 21:47:34 +0000284%
cristy8a9106f2011-07-05 14:39:26 +0000285% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +0000286%
cristy8a9106f2011-07-05 14:39:26 +0000287% MagickBooleanType GetImageDistortion(const Image *image,
288% const Image *reconstruct_image,const MetricType metric,
289% double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000290%
291% A description of each parameter follows:
292%
293% o image: the image.
294%
295% o reconstruct_image: the reconstruct image.
296%
cristy3ed852e2009-09-05 21:47:34 +0000297% o metric: the metric.
298%
299% o distortion: the computed distortion between the images.
300%
301% o exception: return any errors or warnings in this structure.
302%
303*/
304
cristy3cc758f2010-11-27 01:33:49 +0000305static MagickBooleanType GetAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000306 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000307{
cristyc4c8d132010-01-07 01:58:38 +0000308 CacheView
309 *image_view,
310 *reconstruct_view;
311
cristy3ed852e2009-09-05 21:47:34 +0000312 MagickBooleanType
313 status;
314
cristy9d314ff2011-03-09 01:30:28 +0000315 ssize_t
316 y;
317
cristy3ed852e2009-09-05 21:47:34 +0000318 /*
319 Compute the absolute difference in pixels between two images.
320 */
321 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000322 image_view=AcquireCacheView(image);
323 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000324#if defined(MAGICKCORE_OPENMP_SUPPORT)
325 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000326#endif
cristybb503372010-05-27 20:51:26 +0000327 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000328 {
329 double
cristy3fc482f2011-09-23 00:43:35 +0000330 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000331
cristy4c08aed2011-07-01 19:47:50 +0000332 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000333 *restrict p,
334 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000335
cristybb503372010-05-27 20:51:26 +0000336 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000337 i,
338 x;
339
340 if (status == MagickFalse)
341 continue;
342 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
343 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
344 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000345 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000346 {
347 status=MagickFalse;
348 continue;
349 }
cristy3ed852e2009-09-05 21:47:34 +0000350 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000351 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000352 {
cristy3fc482f2011-09-23 00:43:35 +0000353 MagickBooleanType
354 difference;
355
356 register ssize_t
357 i;
358
359 difference=MagickFalse;
360 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
361 {
362 PixelChannel
363 channel;
364
365 PixelTrait
366 reconstruct_traits,
367 traits;
368
369 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
370 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
371 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
372 if ((traits == UndefinedPixelTrait) ||
373 (reconstruct_traits == UndefinedPixelTrait))
374 continue;
cristy49dd6a02011-09-24 23:08:01 +0000375 if ((reconstruct_traits & UpdatePixelTrait) == 0)
376 continue;
cristy0beccfa2011-09-25 20:47:53 +0000377 if (p[i] != GetPixelChannel(reconstruct_image,channel,q))
cristy3fc482f2011-09-23 00:43:35 +0000378 difference=MagickTrue;
379 }
380 if (difference != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000381 {
cristy3fc482f2011-09-23 00:43:35 +0000382 channel_distortion[i]++;
383 channel_distortion[MaxPixelChannels]++;
cristy3ed852e2009-09-05 21:47:34 +0000384 }
cristyed231572011-07-14 02:18:59 +0000385 p+=GetPixelChannels(image);
386 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000387 }
cristyb5d5f722009-11-04 03:03:49 +0000388#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000389 #pragma omp critical (MagickCore_GetAbsoluteError)
390#endif
cristy3fc482f2011-09-23 00:43:35 +0000391 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000392 distortion[i]+=channel_distortion[i];
393 }
394 reconstruct_view=DestroyCacheView(reconstruct_view);
395 image_view=DestroyCacheView(image_view);
396 return(status);
397}
398
cristy49dd6a02011-09-24 23:08:01 +0000399static size_t GetImageChannels(const Image *image)
cristy3ed852e2009-09-05 21:47:34 +0000400{
cristy3fc482f2011-09-23 00:43:35 +0000401 register ssize_t
402 i;
403
cristy49dd6a02011-09-24 23:08:01 +0000404 size_t
cristy3ed852e2009-09-05 21:47:34 +0000405 channels;
406
407 channels=0;
cristy3fc482f2011-09-23 00:43:35 +0000408 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
409 {
410 PixelTrait
411 traits;
412
413 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
cristy25a5f2f2011-09-24 14:09:43 +0000414 if ((traits & UpdatePixelTrait) != 0)
cristy3fc482f2011-09-23 00:43:35 +0000415 channels++;
416 }
cristy3ed852e2009-09-05 21:47:34 +0000417 return(channels);
418}
419
cristy343eee92010-12-11 02:17:57 +0000420static MagickBooleanType GetFuzzDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000421 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy343eee92010-12-11 02:17:57 +0000422{
423 CacheView
424 *image_view,
425 *reconstruct_view;
426
cristy343eee92010-12-11 02:17:57 +0000427 MagickBooleanType
428 status;
429
430 register ssize_t
431 i;
432
cristy9d314ff2011-03-09 01:30:28 +0000433 ssize_t
434 y;
435
cristy343eee92010-12-11 02:17:57 +0000436 status=MagickTrue;
437 image_view=AcquireCacheView(image);
438 reconstruct_view=AcquireCacheView(reconstruct_image);
439#if defined(MAGICKCORE_OPENMP_SUPPORT)
440 #pragma omp parallel for schedule(dynamic,4) shared(status)
441#endif
442 for (y=0; y < (ssize_t) image->rows; y++)
443 {
444 double
cristy3fc482f2011-09-23 00:43:35 +0000445 channel_distortion[MaxPixelChannels+1];
cristy343eee92010-12-11 02:17:57 +0000446
cristy4c08aed2011-07-01 19:47:50 +0000447 register const Quantum
cristy343eee92010-12-11 02:17:57 +0000448 *restrict p,
449 *restrict q;
450
451 register ssize_t
452 i,
453 x;
454
455 if (status == MagickFalse)
456 continue;
457 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristye6529ff2011-02-04 20:05:32 +0000458 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
459 1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000460 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy343eee92010-12-11 02:17:57 +0000461 {
462 status=MagickFalse;
463 continue;
464 }
cristy343eee92010-12-11 02:17:57 +0000465 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
466 for (x=0; x < (ssize_t) image->columns; x++)
467 {
cristy3fc482f2011-09-23 00:43:35 +0000468 register ssize_t
469 i;
cristy343eee92010-12-11 02:17:57 +0000470
cristy3fc482f2011-09-23 00:43:35 +0000471 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
472 {
473 MagickRealType
474 distance;
475
476 PixelChannel
477 channel;
478
479 PixelTrait
480 reconstruct_traits,
481 traits;
482
483 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
484 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
485 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
486 if ((traits == UndefinedPixelTrait) ||
487 (reconstruct_traits == UndefinedPixelTrait))
488 continue;
cristy49dd6a02011-09-24 23:08:01 +0000489 if ((reconstruct_traits & UpdatePixelTrait) == 0)
490 continue;
cristy0beccfa2011-09-25 20:47:53 +0000491 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
492 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000493 distance*=distance;
494 channel_distortion[i]+=distance;
495 channel_distortion[MaxPixelChannels]+=distance;
496 }
cristyed231572011-07-14 02:18:59 +0000497 p+=GetPixelChannels(image);
498 q+=GetPixelChannels(reconstruct_image);
cristy343eee92010-12-11 02:17:57 +0000499 }
500#if defined(MAGICKCORE_OPENMP_SUPPORT)
501 #pragma omp critical (MagickCore_GetMeanSquaredError)
502#endif
cristy3fc482f2011-09-23 00:43:35 +0000503 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000504 distortion[i]+=channel_distortion[i];
505 }
506 reconstruct_view=DestroyCacheView(reconstruct_view);
507 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000508 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000509 distortion[i]/=((double) image->columns*image->rows);
cristy49dd6a02011-09-24 23:08:01 +0000510 distortion[MaxPixelChannels]/=(double) GetImageChannels(image);
cristy3fc482f2011-09-23 00:43:35 +0000511 distortion[MaxPixelChannels]=sqrt(distortion[MaxPixelChannels]);
cristy343eee92010-12-11 02:17:57 +0000512 return(status);
513}
514
cristy3cc758f2010-11-27 01:33:49 +0000515static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000516 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000517{
cristyc4c8d132010-01-07 01:58:38 +0000518 CacheView
519 *image_view,
520 *reconstruct_view;
521
cristy3ed852e2009-09-05 21:47:34 +0000522 MagickBooleanType
523 status;
524
cristybb503372010-05-27 20:51:26 +0000525 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000526 i;
527
cristy9d314ff2011-03-09 01:30:28 +0000528 ssize_t
529 y;
530
cristy3ed852e2009-09-05 21:47:34 +0000531 status=MagickTrue;
532 image_view=AcquireCacheView(image);
533 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000534#if defined(MAGICKCORE_OPENMP_SUPPORT)
535 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000536#endif
cristybb503372010-05-27 20:51:26 +0000537 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000538 {
539 double
cristy3fc482f2011-09-23 00:43:35 +0000540 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000541
cristy4c08aed2011-07-01 19:47:50 +0000542 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000543 *restrict p,
544 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000545
cristybb503372010-05-27 20:51:26 +0000546 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000547 i,
548 x;
549
550 if (status == MagickFalse)
551 continue;
552 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000553 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
554 1,exception);
555 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000556 {
557 status=MagickFalse;
558 continue;
559 }
cristy3ed852e2009-09-05 21:47:34 +0000560 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000561 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000562 {
cristy3fc482f2011-09-23 00:43:35 +0000563 register ssize_t
564 i;
cristy3ed852e2009-09-05 21:47:34 +0000565
cristy3fc482f2011-09-23 00:43:35 +0000566 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
567 {
568 MagickRealType
569 distance;
570
571 PixelChannel
572 channel;
573
574 PixelTrait
575 reconstruct_traits,
576 traits;
577
578 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
579 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
580 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
581 if ((traits == UndefinedPixelTrait) ||
582 (reconstruct_traits == UndefinedPixelTrait))
583 continue;
cristy49dd6a02011-09-24 23:08:01 +0000584 if ((reconstruct_traits & UpdatePixelTrait) == 0)
585 continue;
cristy0beccfa2011-09-25 20:47:53 +0000586 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
587 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000588 channel_distortion[i]+=distance;
589 channel_distortion[MaxPixelChannels]+=distance;
590 }
cristyed231572011-07-14 02:18:59 +0000591 p+=GetPixelChannels(image);
592 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000593 }
cristyb5d5f722009-11-04 03:03:49 +0000594#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000595 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
596#endif
cristy3fc482f2011-09-23 00:43:35 +0000597 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000598 distortion[i]+=channel_distortion[i];
599 }
600 reconstruct_view=DestroyCacheView(reconstruct_view);
601 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000602 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000603 distortion[i]/=((double) image->columns*image->rows);
cristy49dd6a02011-09-24 23:08:01 +0000604 distortion[MaxPixelChannels]/=(double) GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000605 return(status);
606}
607
608static MagickBooleanType GetMeanErrorPerPixel(Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000609 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000610{
cristyc4c8d132010-01-07 01:58:38 +0000611 CacheView
612 *image_view,
613 *reconstruct_view;
614
cristy3ed852e2009-09-05 21:47:34 +0000615 MagickBooleanType
616 status;
617
618 MagickRealType
619 alpha,
620 area,
621 beta,
622 maximum_error,
623 mean_error;
624
cristy9d314ff2011-03-09 01:30:28 +0000625 ssize_t
626 y;
627
cristy3ed852e2009-09-05 21:47:34 +0000628 status=MagickTrue;
629 alpha=1.0;
630 beta=1.0;
631 area=0.0;
632 maximum_error=0.0;
633 mean_error=0.0;
634 image_view=AcquireCacheView(image);
635 reconstruct_view=AcquireCacheView(reconstruct_image);
cristybb503372010-05-27 20:51:26 +0000636 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000637 {
cristy4c08aed2011-07-01 19:47:50 +0000638 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000639 *restrict p,
640 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000641
cristybb503372010-05-27 20:51:26 +0000642 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000643 x;
644
645 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
646 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
647 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000648 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000649 {
650 status=MagickFalse;
651 break;
652 }
cristybb503372010-05-27 20:51:26 +0000653 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000654 {
cristy3fc482f2011-09-23 00:43:35 +0000655 register ssize_t
656 i;
cristy3ed852e2009-09-05 21:47:34 +0000657
cristy3fc482f2011-09-23 00:43:35 +0000658 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
659 {
660 MagickRealType
661 distance;
662
663 PixelChannel
664 channel;
665
666 PixelTrait
667 reconstruct_traits,
668 traits;
669
670 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
671 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
672 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
673 if ((traits == UndefinedPixelTrait) ||
674 (reconstruct_traits == UndefinedPixelTrait))
675 continue;
cristy49dd6a02011-09-24 23:08:01 +0000676 if ((reconstruct_traits & UpdatePixelTrait) == 0)
677 continue;
cristy0beccfa2011-09-25 20:47:53 +0000678 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
679 reconstruct_image,channel,q)));
cristy3fc482f2011-09-23 00:43:35 +0000680 distortion[i]+=distance;
681 distortion[MaxPixelChannels]+=distance;
682 mean_error+=distance*distance;
683 if (distance > maximum_error)
684 maximum_error=distance;
685 area++;
686 }
cristyed231572011-07-14 02:18:59 +0000687 p+=GetPixelChannels(image);
688 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000689 }
690 }
691 reconstruct_view=DestroyCacheView(reconstruct_view);
692 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000693 image->error.mean_error_per_pixel=distortion[MaxPixelChannels]/area;
cristy3ed852e2009-09-05 21:47:34 +0000694 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
695 image->error.normalized_maximum_error=QuantumScale*maximum_error;
696 return(status);
697}
698
cristy3cc758f2010-11-27 01:33:49 +0000699static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000700 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000701{
cristyc4c8d132010-01-07 01:58:38 +0000702 CacheView
703 *image_view,
704 *reconstruct_view;
705
cristy3ed852e2009-09-05 21:47:34 +0000706 MagickBooleanType
707 status;
708
cristybb503372010-05-27 20:51:26 +0000709 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000710 i;
711
cristy9d314ff2011-03-09 01:30:28 +0000712 ssize_t
713 y;
714
cristy3ed852e2009-09-05 21:47:34 +0000715 status=MagickTrue;
716 image_view=AcquireCacheView(image);
717 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000718#if defined(MAGICKCORE_OPENMP_SUPPORT)
719 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000720#endif
cristybb503372010-05-27 20:51:26 +0000721 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000722 {
723 double
cristy3fc482f2011-09-23 00:43:35 +0000724 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000725
cristy4c08aed2011-07-01 19:47:50 +0000726 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000727 *restrict p,
728 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000729
cristybb503372010-05-27 20:51:26 +0000730 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000731 i,
732 x;
733
734 if (status == MagickFalse)
735 continue;
736 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000737 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
738 1,exception);
739 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000740 {
741 status=MagickFalse;
742 continue;
743 }
cristy3ed852e2009-09-05 21:47:34 +0000744 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000745 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000746 {
cristy3fc482f2011-09-23 00:43:35 +0000747 register ssize_t
748 i;
cristy3ed852e2009-09-05 21:47:34 +0000749
cristy3fc482f2011-09-23 00:43:35 +0000750 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
751 {
752 MagickRealType
753 distance;
754
755 PixelChannel
756 channel;
757
758 PixelTrait
759 reconstruct_traits,
760 traits;
761
762 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
763 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
764 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
765 if ((traits == UndefinedPixelTrait) ||
766 (reconstruct_traits == UndefinedPixelTrait))
767 continue;
cristy49dd6a02011-09-24 23:08:01 +0000768 if ((reconstruct_traits & UpdatePixelTrait) == 0)
769 continue;
cristy0beccfa2011-09-25 20:47:53 +0000770 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
771 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000772 distance*=distance;
773 channel_distortion[i]+=distance;
774 channel_distortion[MaxPixelChannels]+=distance;
775 }
cristyed231572011-07-14 02:18:59 +0000776 p+=GetPixelChannels(image);
777 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000778 }
cristyb5d5f722009-11-04 03:03:49 +0000779#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000780 #pragma omp critical (MagickCore_GetMeanSquaredError)
781#endif
cristy3fc482f2011-09-23 00:43:35 +0000782 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000783 distortion[i]+=channel_distortion[i];
784 }
785 reconstruct_view=DestroyCacheView(reconstruct_view);
786 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000787 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000788 distortion[i]/=((double) image->columns*image->rows);
cristy49dd6a02011-09-24 23:08:01 +0000789 distortion[MaxPixelChannels]/=GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000790 return(status);
791}
792
cristy3cc758f2010-11-27 01:33:49 +0000793static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
cristy8a9106f2011-07-05 14:39:26 +0000794 const Image *image,const Image *reconstruct_image,double *distortion,
795 ExceptionInfo *exception)
cristy4c929a72010-11-24 18:54:42 +0000796{
cristy9f48ca62010-11-25 03:06:31 +0000797#define SimilarityImageTag "Similarity/Image"
798
cristy4c929a72010-11-24 18:54:42 +0000799 CacheView
800 *image_view,
801 *reconstruct_view;
802
cristy9f48ca62010-11-25 03:06:31 +0000803 ChannelStatistics
cristy9f48ca62010-11-25 03:06:31 +0000804 *image_statistics,
805 *reconstruct_statistics;
806
cristy4c929a72010-11-24 18:54:42 +0000807 MagickBooleanType
808 status;
809
cristy18a41362010-11-27 15:56:18 +0000810 MagickOffsetType
811 progress;
812
cristy34d6fdc2010-11-26 19:06:08 +0000813 MagickRealType
814 area;
815
cristy4c929a72010-11-24 18:54:42 +0000816 register ssize_t
817 i;
818
cristy3cc758f2010-11-27 01:33:49 +0000819 ssize_t
820 y;
821
cristy34d6fdc2010-11-26 19:06:08 +0000822 /*
cristy18a41362010-11-27 15:56:18 +0000823 Normalize to account for variation due to lighting and exposure condition.
cristy34d6fdc2010-11-26 19:06:08 +0000824 */
cristyd42d9952011-07-08 14:21:50 +0000825 image_statistics=GetImageStatistics(image,exception);
826 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
cristy4c929a72010-11-24 18:54:42 +0000827 status=MagickTrue;
cristy9f48ca62010-11-25 03:06:31 +0000828 progress=0;
cristy3fc482f2011-09-23 00:43:35 +0000829 for (i=0; i <= MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000830 distortion[i]=0.0;
831 area=1.0/((MagickRealType) image->columns*image->rows);
cristy4c929a72010-11-24 18:54:42 +0000832 image_view=AcquireCacheView(image);
833 reconstruct_view=AcquireCacheView(reconstruct_image);
cristy4c929a72010-11-24 18:54:42 +0000834 for (y=0; y < (ssize_t) image->rows; y++)
835 {
cristy4c08aed2011-07-01 19:47:50 +0000836 register const Quantum
cristy4c929a72010-11-24 18:54:42 +0000837 *restrict p,
838 *restrict q;
839
840 register ssize_t
cristy4c929a72010-11-24 18:54:42 +0000841 x;
842
843 if (status == MagickFalse)
844 continue;
845 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy34d6fdc2010-11-26 19:06:08 +0000846 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
847 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000848 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy4c929a72010-11-24 18:54:42 +0000849 {
850 status=MagickFalse;
851 continue;
852 }
cristy4c929a72010-11-24 18:54:42 +0000853 for (x=0; x < (ssize_t) image->columns; x++)
854 {
cristy3fc482f2011-09-23 00:43:35 +0000855 register ssize_t
856 i;
857
858 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
859 {
860 PixelChannel
861 channel;
862
863 PixelTrait
864 reconstruct_traits,
865 traits;
866
867 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
868 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
869 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
870 if ((traits == UndefinedPixelTrait) ||
871 (reconstruct_traits == UndefinedPixelTrait))
872 continue;
cristy49dd6a02011-09-24 23:08:01 +0000873 if ((reconstruct_traits & UpdatePixelTrait) == 0)
874 continue;
cristy3fc482f2011-09-23 00:43:35 +0000875 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
cristy0beccfa2011-09-25 20:47:53 +0000876 (GetPixelChannel(reconstruct_image,channel,q)-
877 reconstruct_statistics[channel].mean);
cristy3fc482f2011-09-23 00:43:35 +0000878 }
cristyed231572011-07-14 02:18:59 +0000879 p+=GetPixelChannels(image);
880 q+=GetPixelChannels(image);
cristy4c929a72010-11-24 18:54:42 +0000881 }
cristy9f48ca62010-11-25 03:06:31 +0000882 if (image->progress_monitor != (MagickProgressMonitor) NULL)
883 {
884 MagickBooleanType
885 proceed;
886
cristy9f48ca62010-11-25 03:06:31 +0000887 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
888 image->rows);
889 if (proceed == MagickFalse)
890 status=MagickFalse;
891 }
cristy4c929a72010-11-24 18:54:42 +0000892 }
893 reconstruct_view=DestroyCacheView(reconstruct_view);
894 image_view=DestroyCacheView(image_view);
cristy34d6fdc2010-11-26 19:06:08 +0000895 /*
896 Divide by the standard deviation.
897 */
cristy3fc482f2011-09-23 00:43:35 +0000898 distortion[MaxPixelChannels]=0.0;
899 for (i=0; i < MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000900 {
901 MagickRealType
cristy18a41362010-11-27 15:56:18 +0000902 gamma;
cristy34d6fdc2010-11-26 19:06:08 +0000903
cristy3fc482f2011-09-23 00:43:35 +0000904 PixelChannel
905 channel;
906
907 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
cristy18a41362010-11-27 15:56:18 +0000908 gamma=image_statistics[i].standard_deviation*
cristy3fc482f2011-09-23 00:43:35 +0000909 reconstruct_statistics[channel].standard_deviation;
cristy18a41362010-11-27 15:56:18 +0000910 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
911 distortion[i]=QuantumRange*gamma*distortion[i];
cristy3fc482f2011-09-23 00:43:35 +0000912 distortion[MaxPixelChannels]+=distortion[i]*distortion[i];
cristy34d6fdc2010-11-26 19:06:08 +0000913 }
cristy3fc482f2011-09-23 00:43:35 +0000914 distortion[MaxPixelChannels]=sqrt(distortion[MaxPixelChannels]/
cristy49dd6a02011-09-24 23:08:01 +0000915 GetImageChannels(image));
cristy34d6fdc2010-11-26 19:06:08 +0000916 /*
917 Free resources.
918 */
cristy9f48ca62010-11-25 03:06:31 +0000919 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
920 reconstruct_statistics);
921 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
922 image_statistics);
cristy4c929a72010-11-24 18:54:42 +0000923 return(status);
924}
925
cristy3cc758f2010-11-27 01:33:49 +0000926static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000927 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000928{
cristyc4c8d132010-01-07 01:58:38 +0000929 CacheView
930 *image_view,
931 *reconstruct_view;
932
cristy3ed852e2009-09-05 21:47:34 +0000933 MagickBooleanType
934 status;
935
cristy9d314ff2011-03-09 01:30:28 +0000936 ssize_t
937 y;
938
cristy3ed852e2009-09-05 21:47:34 +0000939 status=MagickTrue;
940 image_view=AcquireCacheView(image);
941 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000942#if defined(MAGICKCORE_OPENMP_SUPPORT)
943 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000944#endif
cristybb503372010-05-27 20:51:26 +0000945 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000946 {
947 double
cristy3fc482f2011-09-23 00:43:35 +0000948 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000949
cristy4c08aed2011-07-01 19:47:50 +0000950 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000951 *restrict p,
952 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000953
cristybb503372010-05-27 20:51:26 +0000954 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000955 i,
956 x;
957
958 if (status == MagickFalse)
959 continue;
960 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
961 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
962 reconstruct_image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000963 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000964 {
965 status=MagickFalse;
966 continue;
967 }
cristy3ed852e2009-09-05 21:47:34 +0000968 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000969 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000970 {
cristy3fc482f2011-09-23 00:43:35 +0000971 register ssize_t
972 i;
cristy3ed852e2009-09-05 21:47:34 +0000973
cristy3fc482f2011-09-23 00:43:35 +0000974 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
975 {
976 MagickRealType
977 distance;
978
979 PixelChannel
980 channel;
981
982 PixelTrait
983 reconstruct_traits,
984 traits;
985
986 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
987 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
988 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
989 if ((traits == UndefinedPixelTrait) ||
990 (reconstruct_traits == UndefinedPixelTrait))
991 continue;
cristy49dd6a02011-09-24 23:08:01 +0000992 if ((reconstruct_traits & UpdatePixelTrait) == 0)
993 continue;
cristy0beccfa2011-09-25 20:47:53 +0000994 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
995 reconstruct_image,channel,q));
cristy25a5f2f2011-09-24 14:09:43 +0000996 if (distance > channel_distortion[i])
cristy3fc482f2011-09-23 00:43:35 +0000997 channel_distortion[i]=distance;
998 if (distance > channel_distortion[MaxPixelChannels])
999 channel_distortion[MaxPixelChannels]=distance;
1000 }
cristyed231572011-07-14 02:18:59 +00001001 p+=GetPixelChannels(image);
1002 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001003 }
cristyb5d5f722009-11-04 03:03:49 +00001004#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001005 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1006#endif
cristy3fc482f2011-09-23 00:43:35 +00001007 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001008 if (channel_distortion[i] > distortion[i])
1009 distortion[i]=channel_distortion[i];
1010 }
1011 reconstruct_view=DestroyCacheView(reconstruct_view);
1012 image_view=DestroyCacheView(image_view);
1013 return(status);
1014}
1015
1016static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001017 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001018{
1019 MagickBooleanType
1020 status;
1021
cristy3fc482f2011-09-23 00:43:35 +00001022 register ssize_t
1023 i;
1024
cristy8a9106f2011-07-05 14:39:26 +00001025 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001026 for (i=0; i <= MaxPixelChannels; i++)
1027 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
cristy3ed852e2009-09-05 21:47:34 +00001028 return(status);
1029}
1030
cristy3cc758f2010-11-27 01:33:49 +00001031static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001032 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001033{
1034 MagickBooleanType
1035 status;
1036
cristy3fc482f2011-09-23 00:43:35 +00001037 register ssize_t
1038 i;
1039
cristy8a9106f2011-07-05 14:39:26 +00001040 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001041 for (i=0; i <= MaxPixelChannels; i++)
1042 distortion[i]=sqrt(distortion[i]);
cristy3ed852e2009-09-05 21:47:34 +00001043 return(status);
1044}
1045
cristy8a9106f2011-07-05 14:39:26 +00001046MagickExport MagickBooleanType GetImageDistortion(Image *image,
1047 const Image *reconstruct_image,const MetricType metric,double *distortion,
1048 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001049{
1050 double
1051 *channel_distortion;
1052
1053 MagickBooleanType
1054 status;
1055
1056 size_t
1057 length;
1058
1059 assert(image != (Image *) NULL);
1060 assert(image->signature == MagickSignature);
1061 if (image->debug != MagickFalse)
1062 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1063 assert(reconstruct_image != (const Image *) NULL);
1064 assert(reconstruct_image->signature == MagickSignature);
1065 assert(distortion != (double *) NULL);
1066 *distortion=0.0;
1067 if (image->debug != MagickFalse)
1068 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1069 if ((reconstruct_image->columns != image->columns) ||
1070 (reconstruct_image->rows != image->rows))
1071 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1072 /*
1073 Get image distortion.
1074 */
cristy3fc482f2011-09-23 00:43:35 +00001075 length=MaxPixelChannels+1;
cristy3ed852e2009-09-05 21:47:34 +00001076 channel_distortion=(double *) AcquireQuantumMemory(length,
1077 sizeof(*channel_distortion));
1078 if (channel_distortion == (double *) NULL)
1079 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1080 (void) ResetMagickMemory(channel_distortion,0,length*
1081 sizeof(*channel_distortion));
1082 switch (metric)
1083 {
1084 case AbsoluteErrorMetric:
1085 {
cristy8a9106f2011-07-05 14:39:26 +00001086 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1087 exception);
cristy3ed852e2009-09-05 21:47:34 +00001088 break;
1089 }
cristy343eee92010-12-11 02:17:57 +00001090 case FuzzErrorMetric:
1091 {
cristy8a9106f2011-07-05 14:39:26 +00001092 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1093 exception);
cristy343eee92010-12-11 02:17:57 +00001094 break;
1095 }
cristy3ed852e2009-09-05 21:47:34 +00001096 case MeanAbsoluteErrorMetric:
1097 {
cristy8a9106f2011-07-05 14:39:26 +00001098 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001099 channel_distortion,exception);
1100 break;
1101 }
1102 case MeanErrorPerPixelMetric:
1103 {
cristy8a9106f2011-07-05 14:39:26 +00001104 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1105 exception);
cristy3ed852e2009-09-05 21:47:34 +00001106 break;
1107 }
1108 case MeanSquaredErrorMetric:
1109 {
cristy8a9106f2011-07-05 14:39:26 +00001110 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001111 channel_distortion,exception);
1112 break;
1113 }
cristy4c929a72010-11-24 18:54:42 +00001114 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001115 default:
cristy4c929a72010-11-24 18:54:42 +00001116 {
cristy3cc758f2010-11-27 01:33:49 +00001117 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001118 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001119 break;
1120 }
cristy3ed852e2009-09-05 21:47:34 +00001121 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001122 {
cristy8a9106f2011-07-05 14:39:26 +00001123 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001124 channel_distortion,exception);
1125 break;
1126 }
1127 case PeakSignalToNoiseRatioMetric:
1128 {
cristy8a9106f2011-07-05 14:39:26 +00001129 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001130 channel_distortion,exception);
1131 break;
1132 }
1133 case RootMeanSquaredErrorMetric:
1134 {
cristy8a9106f2011-07-05 14:39:26 +00001135 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001136 channel_distortion,exception);
1137 break;
1138 }
1139 }
cristy3fc482f2011-09-23 00:43:35 +00001140 *distortion=channel_distortion[MaxPixelChannels];
cristy3ed852e2009-09-05 21:47:34 +00001141 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1142 return(status);
1143}
1144
1145/*
1146%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1147% %
1148% %
1149% %
cristy8a9106f2011-07-05 14:39:26 +00001150% 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 +00001151% %
1152% %
1153% %
1154%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1155%
cristy8a9106f2011-07-05 14:39:26 +00001156% GetImageDistrortion() compares the pixel channels of an image to a
cristy3ed852e2009-09-05 21:47:34 +00001157% reconstructed image and returns the specified distortion metric for each
1158% channel.
1159%
cristy8a9106f2011-07-05 14:39:26 +00001160% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +00001161%
cristy8a9106f2011-07-05 14:39:26 +00001162% double *GetImageDistortions(const Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001163% const Image *reconstruct_image,const MetricType metric,
1164% ExceptionInfo *exception)
1165%
1166% A description of each parameter follows:
1167%
1168% o image: the image.
1169%
1170% o reconstruct_image: the reconstruct image.
1171%
1172% o metric: the metric.
1173%
1174% o exception: return any errors or warnings in this structure.
1175%
1176*/
cristy8a9106f2011-07-05 14:39:26 +00001177MagickExport double *GetImageDistortions(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001178 const Image *reconstruct_image,const MetricType metric,
1179 ExceptionInfo *exception)
1180{
1181 double
1182 *channel_distortion;
1183
1184 MagickBooleanType
1185 status;
1186
1187 size_t
1188 length;
1189
1190 assert(image != (Image *) NULL);
1191 assert(image->signature == MagickSignature);
1192 if (image->debug != MagickFalse)
1193 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1194 assert(reconstruct_image != (const Image *) NULL);
1195 assert(reconstruct_image->signature == MagickSignature);
1196 if (image->debug != MagickFalse)
1197 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1198 if ((reconstruct_image->columns != image->columns) ||
1199 (reconstruct_image->rows != image->rows))
1200 {
1201 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1202 ImageError,"ImageSizeDiffers","`%s'",image->filename);
1203 return((double *) NULL);
1204 }
1205 /*
1206 Get image distortion.
1207 */
cristy3fc482f2011-09-23 00:43:35 +00001208 length=MaxPixelChannels+1UL;
cristy3ed852e2009-09-05 21:47:34 +00001209 channel_distortion=(double *) AcquireQuantumMemory(length,
1210 sizeof(*channel_distortion));
1211 if (channel_distortion == (double *) NULL)
1212 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1213 (void) ResetMagickMemory(channel_distortion,0,length*
1214 sizeof(*channel_distortion));
cristyda16f162011-02-19 23:52:17 +00001215 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +00001216 switch (metric)
1217 {
1218 case AbsoluteErrorMetric:
1219 {
cristy8a9106f2011-07-05 14:39:26 +00001220 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1221 exception);
cristy3ed852e2009-09-05 21:47:34 +00001222 break;
1223 }
cristy343eee92010-12-11 02:17:57 +00001224 case FuzzErrorMetric:
1225 {
cristy8a9106f2011-07-05 14:39:26 +00001226 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1227 exception);
cristy343eee92010-12-11 02:17:57 +00001228 break;
1229 }
cristy3ed852e2009-09-05 21:47:34 +00001230 case MeanAbsoluteErrorMetric:
1231 {
cristy8a9106f2011-07-05 14:39:26 +00001232 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001233 channel_distortion,exception);
1234 break;
1235 }
1236 case MeanErrorPerPixelMetric:
1237 {
cristy8a9106f2011-07-05 14:39:26 +00001238 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1239 exception);
cristy3ed852e2009-09-05 21:47:34 +00001240 break;
1241 }
1242 case MeanSquaredErrorMetric:
1243 {
cristy8a9106f2011-07-05 14:39:26 +00001244 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001245 channel_distortion,exception);
1246 break;
1247 }
cristy4c929a72010-11-24 18:54:42 +00001248 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001249 default:
cristy4c929a72010-11-24 18:54:42 +00001250 {
cristy3cc758f2010-11-27 01:33:49 +00001251 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001252 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001253 break;
1254 }
cristy3ed852e2009-09-05 21:47:34 +00001255 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001256 {
cristy8a9106f2011-07-05 14:39:26 +00001257 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001258 channel_distortion,exception);
1259 break;
1260 }
1261 case PeakSignalToNoiseRatioMetric:
1262 {
cristy8a9106f2011-07-05 14:39:26 +00001263 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001264 channel_distortion,exception);
1265 break;
1266 }
1267 case RootMeanSquaredErrorMetric:
1268 {
cristy8a9106f2011-07-05 14:39:26 +00001269 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001270 channel_distortion,exception);
1271 break;
1272 }
1273 }
cristyda16f162011-02-19 23:52:17 +00001274 if (status == MagickFalse)
1275 {
1276 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1277 return((double *) NULL);
1278 }
cristy3ed852e2009-09-05 21:47:34 +00001279 return(channel_distortion);
1280}
1281
1282/*
1283%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1284% %
1285% %
1286% %
1287% I s I m a g e s E q u a l %
1288% %
1289% %
1290% %
1291%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1292%
1293% IsImagesEqual() measures the difference between colors at each pixel
1294% location of two images. A value other than 0 means the colors match
1295% exactly. Otherwise an error measure is computed by summing over all
1296% pixels in an image the distance squared in RGB space between each image
1297% pixel and its corresponding pixel in the reconstruct image. The error
1298% measure is assigned to these image members:
1299%
1300% o mean_error_per_pixel: The mean error for any single pixel in
1301% the image.
1302%
1303% o normalized_mean_error: The normalized mean quantization error for
1304% any single pixel in the image. This distance measure is normalized to
1305% a range between 0 and 1. It is independent of the range of red, green,
1306% and blue values in the image.
1307%
1308% o normalized_maximum_error: The normalized maximum quantization
1309% error for any single pixel in the image. This distance measure is
1310% normalized to a range between 0 and 1. It is independent of the range
1311% of red, green, and blue values in your image.
1312%
1313% A small normalized mean square error, accessed as
1314% image->normalized_mean_error, suggests the images are very similar in
1315% spatial layout and color.
1316%
1317% The format of the IsImagesEqual method is:
1318%
1319% MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001320% const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001321%
1322% A description of each parameter follows.
1323%
1324% o image: the image.
1325%
1326% o reconstruct_image: the reconstruct image.
1327%
cristy018f07f2011-09-04 21:15:19 +00001328% o exception: return any errors or warnings in this structure.
1329%
cristy3ed852e2009-09-05 21:47:34 +00001330*/
1331MagickExport MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001332 const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001333{
cristyc4c8d132010-01-07 01:58:38 +00001334 CacheView
1335 *image_view,
1336 *reconstruct_view;
1337
cristy3ed852e2009-09-05 21:47:34 +00001338 MagickBooleanType
1339 status;
1340
1341 MagickRealType
1342 area,
1343 maximum_error,
1344 mean_error,
1345 mean_error_per_pixel;
1346
cristy9d314ff2011-03-09 01:30:28 +00001347 ssize_t
1348 y;
1349
cristy3ed852e2009-09-05 21:47:34 +00001350 assert(image != (Image *) NULL);
1351 assert(image->signature == MagickSignature);
1352 assert(reconstruct_image != (const Image *) NULL);
1353 assert(reconstruct_image->signature == MagickSignature);
1354 if ((reconstruct_image->columns != image->columns) ||
1355 (reconstruct_image->rows != image->rows))
1356 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1357 area=0.0;
1358 maximum_error=0.0;
1359 mean_error_per_pixel=0.0;
1360 mean_error=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001361 image_view=AcquireCacheView(image);
1362 reconstruct_view=AcquireCacheView(reconstruct_image);
cristybb503372010-05-27 20:51:26 +00001363 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001364 {
cristy4c08aed2011-07-01 19:47:50 +00001365 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001366 *restrict p,
1367 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001368
cristybb503372010-05-27 20:51:26 +00001369 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001370 x;
1371
1372 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1373 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1374 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001375 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001376 break;
cristybb503372010-05-27 20:51:26 +00001377 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001378 {
cristyd5c15f92011-09-23 00:58:33 +00001379 register ssize_t
1380 i;
cristy3ed852e2009-09-05 21:47:34 +00001381
cristyd5c15f92011-09-23 00:58:33 +00001382 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1383 {
1384 MagickRealType
1385 distance;
1386
1387 PixelChannel
1388 channel;
1389
1390 PixelTrait
1391 reconstruct_traits,
1392 traits;
1393
1394 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1395 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1396 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1397 if ((traits == UndefinedPixelTrait) ||
1398 (reconstruct_traits == UndefinedPixelTrait))
1399 continue;
cristy49dd6a02011-09-24 23:08:01 +00001400 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1401 continue;
cristy0beccfa2011-09-25 20:47:53 +00001402 distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
1403 channel,q));
cristyd5c15f92011-09-23 00:58:33 +00001404 mean_error_per_pixel+=distance;
1405 mean_error+=distance*distance;
1406 if (distance > maximum_error)
1407 maximum_error=distance;
1408 area++;
1409 }
cristyed231572011-07-14 02:18:59 +00001410 p+=GetPixelChannels(image);
1411 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001412 }
1413 }
1414 reconstruct_view=DestroyCacheView(reconstruct_view);
1415 image_view=DestroyCacheView(image_view);
1416 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1417 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1418 mean_error/area);
1419 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1420 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1421 return(status);
1422}
1423
1424/*
1425%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1426% %
1427% %
1428% %
1429% S i m i l a r i t y I m a g e %
1430% %
1431% %
1432% %
1433%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1434%
1435% SimilarityImage() compares the reference image of the image and returns the
1436% best match offset. In addition, it returns a similarity image such that an
1437% exact match location is completely white and if none of the pixels match,
1438% black, otherwise some gray level in-between.
1439%
1440% The format of the SimilarityImageImage method is:
1441%
1442% Image *SimilarityImage(const Image *image,const Image *reference,
1443% RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1444%
1445% A description of each parameter follows:
1446%
1447% o image: the image.
1448%
1449% o reference: find an area of the image that closely resembles this image.
1450%
1451% o the best match offset of the reference image within the image.
1452%
1453% o similarity: the computed similarity between the images.
1454%
1455% o exception: return any errors or warnings in this structure.
1456%
1457*/
1458
cristy3cc758f2010-11-27 01:33:49 +00001459static double GetNCCDistortion(const Image *image,
1460 const Image *reconstruct_image,
1461 const ChannelStatistics *reconstruct_statistics,ExceptionInfo *exception)
1462{
1463#define SimilarityImageTag "Similarity/Image"
1464
1465 CacheView
1466 *image_view,
1467 *reconstruct_view;
1468
1469 ChannelStatistics
1470 *image_statistics;
1471
1472 double
1473 distortion;
1474
1475 MagickBooleanType
1476 status;
1477
1478 MagickRealType
cristy18a41362010-11-27 15:56:18 +00001479 area,
1480 gamma;
cristy3cc758f2010-11-27 01:33:49 +00001481
1482 ssize_t
1483 y;
1484
cristy3cc758f2010-11-27 01:33:49 +00001485 /*
cristy18a41362010-11-27 15:56:18 +00001486 Normalize to account for variation due to lighting and exposure condition.
cristy3cc758f2010-11-27 01:33:49 +00001487 */
cristyd42d9952011-07-08 14:21:50 +00001488 image_statistics=GetImageStatistics(image,exception);
cristy3cc758f2010-11-27 01:33:49 +00001489 status=MagickTrue;
1490 distortion=0.0;
1491 area=1.0/((MagickRealType) image->columns*image->rows);
1492 image_view=AcquireCacheView(image);
1493 reconstruct_view=AcquireCacheView(reconstruct_image);
1494 for (y=0; y < (ssize_t) image->rows; y++)
1495 {
cristy4c08aed2011-07-01 19:47:50 +00001496 register const Quantum
cristy3cc758f2010-11-27 01:33:49 +00001497 *restrict p,
1498 *restrict q;
1499
1500 register ssize_t
1501 x;
1502
1503 if (status == MagickFalse)
1504 continue;
1505 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1506 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1507 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001508 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3cc758f2010-11-27 01:33:49 +00001509 {
1510 status=MagickFalse;
1511 continue;
1512 }
cristy3cc758f2010-11-27 01:33:49 +00001513 for (x=0; x < (ssize_t) image->columns; x++)
1514 {
cristyd5c15f92011-09-23 00:58:33 +00001515 register ssize_t
1516 i;
1517
1518 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1519 {
1520 PixelChannel
1521 channel;
1522
1523 PixelTrait
1524 reconstruct_traits,
1525 traits;
1526
1527 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1528 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1529 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1530 if ((traits == UndefinedPixelTrait) ||
1531 (reconstruct_traits == UndefinedPixelTrait))
1532 continue;
cristy49dd6a02011-09-24 23:08:01 +00001533 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1534 continue;
cristyd5c15f92011-09-23 00:58:33 +00001535 distortion+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
cristy0beccfa2011-09-25 20:47:53 +00001536 (GetPixelChannel(reconstruct_image,channel,q)-
1537 reconstruct_statistics[channel].mean);
cristyd5c15f92011-09-23 00:58:33 +00001538 }
cristyed231572011-07-14 02:18:59 +00001539 p+=GetPixelChannels(image);
1540 q+=GetPixelChannels(reconstruct_image);
cristy3cc758f2010-11-27 01:33:49 +00001541 }
1542 }
1543 reconstruct_view=DestroyCacheView(reconstruct_view);
1544 image_view=DestroyCacheView(image_view);
1545 /*
1546 Divide by the standard deviation.
1547 */
cristy3fc482f2011-09-23 00:43:35 +00001548 gamma=image_statistics[MaxPixelChannels].standard_deviation*
1549 reconstruct_statistics[MaxPixelChannels].standard_deviation;
cristy18a41362010-11-27 15:56:18 +00001550 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1551 distortion=QuantumRange*gamma*distortion;
cristy49dd6a02011-09-24 23:08:01 +00001552 distortion=sqrt(distortion/GetImageChannels(image));
cristy3cc758f2010-11-27 01:33:49 +00001553 /*
1554 Free resources.
1555 */
1556 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1557 image_statistics);
1558 return(1.0-distortion);
1559}
1560
cristy3ed852e2009-09-05 21:47:34 +00001561static double GetSimilarityMetric(const Image *image,const Image *reference,
cristy3cc758f2010-11-27 01:33:49 +00001562 const ChannelStatistics *reference_statistics,const ssize_t x_offset,
1563 const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001564{
1565 double
cristy3cc758f2010-11-27 01:33:49 +00001566 distortion;
cristy3ed852e2009-09-05 21:47:34 +00001567
cristy713ff212010-11-26 21:56:11 +00001568 Image
1569 *similarity_image;
cristy3ed852e2009-09-05 21:47:34 +00001570
cristy713ff212010-11-26 21:56:11 +00001571 RectangleInfo
1572 geometry;
cristy3ed852e2009-09-05 21:47:34 +00001573
cristy713ff212010-11-26 21:56:11 +00001574 SetGeometry(reference,&geometry);
1575 geometry.x=x_offset;
1576 geometry.y=y_offset;
1577 similarity_image=CropImage(image,&geometry,exception);
1578 if (similarity_image == (Image *) NULL)
1579 return(0.0);
cristy3cc758f2010-11-27 01:33:49 +00001580 distortion=GetNCCDistortion(reference,similarity_image,reference_statistics,
1581 exception);
cristy713ff212010-11-26 21:56:11 +00001582 similarity_image=DestroyImage(similarity_image);
cristy3cc758f2010-11-27 01:33:49 +00001583 return(distortion);
cristy3ed852e2009-09-05 21:47:34 +00001584}
1585
1586MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1587 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1588{
1589#define SimilarityImageTag "Similarity/Image"
1590
cristyc4c8d132010-01-07 01:58:38 +00001591 CacheView
1592 *similarity_view;
1593
cristy3cc758f2010-11-27 01:33:49 +00001594 ChannelStatistics
1595 *reference_statistics;
1596
cristy3ed852e2009-09-05 21:47:34 +00001597 Image
1598 *similarity_image;
1599
1600 MagickBooleanType
1601 status;
1602
cristybb503372010-05-27 20:51:26 +00001603 MagickOffsetType
1604 progress;
1605
1606 ssize_t
1607 y;
1608
cristy3ed852e2009-09-05 21:47:34 +00001609 assert(image != (const Image *) NULL);
1610 assert(image->signature == MagickSignature);
1611 if (image->debug != MagickFalse)
1612 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1613 assert(exception != (ExceptionInfo *) NULL);
1614 assert(exception->signature == MagickSignature);
1615 assert(offset != (RectangleInfo *) NULL);
1616 SetGeometry(reference,offset);
1617 *similarity_metric=1.0;
cristyf9255b92010-08-14 22:59:00 +00001618 if ((reference->columns > image->columns) || (reference->rows > image->rows))
cristy3ed852e2009-09-05 21:47:34 +00001619 ThrowImageException(ImageError,"ImageSizeDiffers");
1620 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1621 image->rows-reference->rows+1,MagickTrue,exception);
1622 if (similarity_image == (Image *) NULL)
1623 return((Image *) NULL);
cristyd5c15f92011-09-23 00:58:33 +00001624 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1625 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001626 {
cristy3ed852e2009-09-05 21:47:34 +00001627 similarity_image=DestroyImage(similarity_image);
1628 return((Image *) NULL);
1629 }
1630 /*
1631 Measure similarity of reference image against image.
1632 */
1633 status=MagickTrue;
1634 progress=0;
cristyd42d9952011-07-08 14:21:50 +00001635 reference_statistics=GetImageStatistics(reference,exception);
cristy3ed852e2009-09-05 21:47:34 +00001636 similarity_view=AcquireCacheView(similarity_image);
cristyb5d5f722009-11-04 03:03:49 +00001637#if defined(MAGICKCORE_OPENMP_SUPPORT)
1638 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001639#endif
cristybb503372010-05-27 20:51:26 +00001640 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
cristy3ed852e2009-09-05 21:47:34 +00001641 {
1642 double
1643 similarity;
1644
cristy4c08aed2011-07-01 19:47:50 +00001645 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001646 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001647
cristy49dd6a02011-09-24 23:08:01 +00001648 register ssize_t
1649 x;
1650
cristy3ed852e2009-09-05 21:47:34 +00001651 if (status == MagickFalse)
1652 continue;
cristy3cc758f2010-11-27 01:33:49 +00001653 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1654 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001655 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001656 {
1657 status=MagickFalse;
1658 continue;
1659 }
cristybb503372010-05-27 20:51:26 +00001660 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
cristy3ed852e2009-09-05 21:47:34 +00001661 {
cristy49dd6a02011-09-24 23:08:01 +00001662 register ssize_t
1663 i;
1664
cristy3cc758f2010-11-27 01:33:49 +00001665 similarity=GetSimilarityMetric(image,reference,reference_statistics,x,y,
1666 exception);
cristyb5d5f722009-11-04 03:03:49 +00001667#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001668 #pragma omp critical (MagickCore_SimilarityImage)
1669#endif
1670 if (similarity < *similarity_metric)
1671 {
1672 *similarity_metric=similarity;
1673 offset->x=x;
1674 offset->y=y;
1675 }
cristy49dd6a02011-09-24 23:08:01 +00001676 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1677 {
1678 PixelChannel
1679 channel;
1680
1681 PixelTrait
1682 similarity_traits,
1683 traits;
1684
1685 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1686 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1687 similarity_traits=GetPixelChannelMapTraits(similarity_image,channel);
1688 if ((traits == UndefinedPixelTrait) ||
1689 (similarity_traits == UndefinedPixelTrait))
1690 continue;
1691 if ((similarity_traits & UpdatePixelTrait) == 0)
1692 continue;
cristy0beccfa2011-09-25 20:47:53 +00001693 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1694 QuantumRange*similarity),q);
cristy49dd6a02011-09-24 23:08:01 +00001695 }
cristyed231572011-07-14 02:18:59 +00001696 q+=GetPixelChannels(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001697 }
1698 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1699 status=MagickFalse;
1700 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1701 {
1702 MagickBooleanType
1703 proceed;
1704
cristyb5d5f722009-11-04 03:03:49 +00001705#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001706 #pragma omp critical (MagickCore_SimilarityImage)
1707#endif
1708 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1709 image->rows);
1710 if (proceed == MagickFalse)
1711 status=MagickFalse;
1712 }
1713 }
1714 similarity_view=DestroyCacheView(similarity_view);
cristy3cc758f2010-11-27 01:33:49 +00001715 reference_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1716 reference_statistics);
cristy3ed852e2009-09-05 21:47:34 +00001717 return(similarity_image);
1718}