blob: 029e9f0393f28949a138e198424f7fea5ff27128 [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% %
cristy1454be72011-12-19 01:52:48 +000020% Copyright 1999-2012 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);
cristyf9d6dc02012-01-19 02:14:44 +0000164 (void) QueryColorCompliance("#f1001e33",AllCompliance,&highlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000165 artifact=GetImageArtifact(image,"highlight-color");
166 if (artifact != (const char *) NULL)
cristyf9d6dc02012-01-19 02:14:44 +0000167 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
168 (void) QueryColorCompliance("#ffffff33",AllCompliance,&lowlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000169 artifact=GetImageArtifact(image,"lowlight-color");
170 if (artifact != (const char *) NULL)
cristy0b1a7972011-10-22 22:17:02 +0000171 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000172 /*
173 Generate difference image.
174 */
175 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000176 image_view=AcquireCacheView(image);
177 reconstruct_view=AcquireCacheView(reconstruct_image);
178 highlight_view=AcquireCacheView(highlight_image);
cristyb5d5f722009-11-04 03:03:49 +0000179#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000180 #pragma omp parallel for schedule(static,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000181#endif
cristybb503372010-05-27 20:51:26 +0000182 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000183 {
184 MagickBooleanType
185 sync;
186
cristy4c08aed2011-07-01 19:47:50 +0000187 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000188 *restrict p,
189 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000190
cristy4c08aed2011-07-01 19:47:50 +0000191 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000192 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000193
cristy49dd6a02011-09-24 23:08:01 +0000194 register ssize_t
195 x;
196
cristy3ed852e2009-09-05 21:47:34 +0000197 if (status == MagickFalse)
198 continue;
199 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
200 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
201 1,exception);
202 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
203 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000204 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
205 (r == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000206 {
207 status=MagickFalse;
208 continue;
209 }
cristybb503372010-05-27 20:51:26 +0000210 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000211 {
212 MagickStatusType
213 difference;
214
cristy3fc482f2011-09-23 00:43:35 +0000215 register ssize_t
216 i;
217
cristy10a6c612012-01-29 21:41:05 +0000218 if (GetPixelMask(image,p) != 0)
219 {
cristyd09f8802012-02-04 16:44:10 +0000220 SetPixelInfoPixel(highlight_image,&lowlight,r);
cristy10a6c612012-01-29 21:41:05 +0000221 p+=GetPixelChannels(image);
222 q+=GetPixelChannels(reconstruct_image);
223 r+=GetPixelChannels(highlight_image);
224 continue;
225 }
cristyd09f8802012-02-04 16:44:10 +0000226 difference=MagickFalse;
cristy3fc482f2011-09-23 00:43:35 +0000227 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
228 {
cristy0beccfa2011-09-25 20:47:53 +0000229 MagickRealType
230 distance;
231
cristy3fc482f2011-09-23 00:43:35 +0000232 PixelChannel
233 channel;
234
235 PixelTrait
236 reconstruct_traits,
237 traits;
238
cristye2a912b2011-12-05 20:02:07 +0000239 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000240 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000241 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
242 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000243 (reconstruct_traits == UndefinedPixelTrait) ||
244 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy3fc482f2011-09-23 00:43:35 +0000245 continue;
cristyd09f8802012-02-04 16:44:10 +0000246 distance=p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
247 channel,q);
cristy0beccfa2011-09-25 20:47:53 +0000248 if (fabs((double) distance) >= MagickEpsilon)
cristy3fc482f2011-09-23 00:43:35 +0000249 difference=MagickTrue;
250 }
251 if (difference == MagickFalse)
cristy803640d2011-11-17 02:11:32 +0000252 SetPixelInfoPixel(highlight_image,&lowlight,r);
cristy3fc482f2011-09-23 00:43:35 +0000253 else
cristy803640d2011-11-17 02:11:32 +0000254 SetPixelInfoPixel(highlight_image,&highlight,r);
cristyed231572011-07-14 02:18:59 +0000255 p+=GetPixelChannels(image);
256 q+=GetPixelChannels(reconstruct_image);
257 r+=GetPixelChannels(highlight_image);
cristy3ed852e2009-09-05 21:47:34 +0000258 }
259 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
260 if (sync == MagickFalse)
261 status=MagickFalse;
262 }
263 highlight_view=DestroyCacheView(highlight_view);
264 reconstruct_view=DestroyCacheView(reconstruct_view);
265 image_view=DestroyCacheView(image_view);
cristye941a752011-10-15 01:52:48 +0000266 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0,
267 exception);
cristy3ed852e2009-09-05 21:47:34 +0000268 highlight_image=DestroyImage(highlight_image);
269 if (status == MagickFalse)
270 difference_image=DestroyImage(difference_image);
271 return(difference_image);
272}
273
274/*
275%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
276% %
277% %
278% %
cristy8a9106f2011-07-05 14:39:26 +0000279% G e t I m a g e D i s t o r t i o n %
cristy3ed852e2009-09-05 21:47:34 +0000280% %
281% %
282% %
283%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
284%
cristy8a9106f2011-07-05 14:39:26 +0000285% GetImageDistortion() compares one or more pixel channels of an image to a
286% reconstructed image and returns the specified distortion metric.
cristy3ed852e2009-09-05 21:47:34 +0000287%
cristy8a9106f2011-07-05 14:39:26 +0000288% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +0000289%
cristy8a9106f2011-07-05 14:39:26 +0000290% MagickBooleanType GetImageDistortion(const Image *image,
291% const Image *reconstruct_image,const MetricType metric,
292% double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000293%
294% A description of each parameter follows:
295%
296% o image: the image.
297%
298% o reconstruct_image: the reconstruct image.
299%
cristy3ed852e2009-09-05 21:47:34 +0000300% o metric: the metric.
301%
302% o distortion: the computed distortion between the images.
303%
304% o exception: return any errors or warnings in this structure.
305%
306*/
307
cristy3cc758f2010-11-27 01:33:49 +0000308static MagickBooleanType GetAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000309 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000310{
cristyc4c8d132010-01-07 01:58:38 +0000311 CacheView
312 *image_view,
313 *reconstruct_view;
314
cristy3ed852e2009-09-05 21:47:34 +0000315 MagickBooleanType
316 status;
317
cristy9d314ff2011-03-09 01:30:28 +0000318 ssize_t
319 y;
320
cristy3ed852e2009-09-05 21:47:34 +0000321 /*
322 Compute the absolute difference in pixels between two images.
323 */
324 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000325 image_view=AcquireCacheView(image);
326 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000327#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000328 #pragma omp parallel for schedule(static,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000329#endif
cristybb503372010-05-27 20:51:26 +0000330 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000331 {
332 double
cristy3fc482f2011-09-23 00:43:35 +0000333 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000334
cristy4c08aed2011-07-01 19:47:50 +0000335 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000336 *restrict p,
337 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000338
cristybb503372010-05-27 20:51:26 +0000339 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000340 i,
341 x;
342
343 if (status == MagickFalse)
344 continue;
345 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
346 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
347 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000348 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000349 {
350 status=MagickFalse;
351 continue;
352 }
cristy3ed852e2009-09-05 21:47:34 +0000353 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000354 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000355 {
cristy3fc482f2011-09-23 00:43:35 +0000356 MagickBooleanType
357 difference;
358
359 register ssize_t
360 i;
361
cristy10a6c612012-01-29 21:41:05 +0000362 if (GetPixelMask(image,p) != 0)
363 {
364 p+=GetPixelChannels(image);
365 q+=GetPixelChannels(reconstruct_image);
366 continue;
367 }
cristyd09f8802012-02-04 16:44:10 +0000368 difference=MagickFalse;
cristy3fc482f2011-09-23 00:43:35 +0000369 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
370 {
371 PixelChannel
372 channel;
373
374 PixelTrait
375 reconstruct_traits,
376 traits;
377
cristye2a912b2011-12-05 20:02:07 +0000378 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000379 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000380 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
381 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000382 (reconstruct_traits == UndefinedPixelTrait) ||
383 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000384 continue;
cristy0beccfa2011-09-25 20:47:53 +0000385 if (p[i] != GetPixelChannel(reconstruct_image,channel,q))
cristy3fc482f2011-09-23 00:43:35 +0000386 difference=MagickTrue;
387 }
388 if (difference != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000389 {
cristy3fc482f2011-09-23 00:43:35 +0000390 channel_distortion[i]++;
cristy5f95f4f2011-10-23 01:01:01 +0000391 channel_distortion[CompositePixelChannel]++;
cristy3ed852e2009-09-05 21:47:34 +0000392 }
cristyed231572011-07-14 02:18:59 +0000393 p+=GetPixelChannels(image);
394 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000395 }
cristyb5d5f722009-11-04 03:03:49 +0000396#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000397 #pragma omp critical (MagickCore_GetAbsoluteError)
398#endif
cristy3fc482f2011-09-23 00:43:35 +0000399 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000400 distortion[i]+=channel_distortion[i];
401 }
402 reconstruct_view=DestroyCacheView(reconstruct_view);
403 image_view=DestroyCacheView(image_view);
404 return(status);
405}
406
cristy49dd6a02011-09-24 23:08:01 +0000407static size_t GetImageChannels(const Image *image)
cristy3ed852e2009-09-05 21:47:34 +0000408{
cristy3fc482f2011-09-23 00:43:35 +0000409 register ssize_t
410 i;
411
cristy49dd6a02011-09-24 23:08:01 +0000412 size_t
cristy3ed852e2009-09-05 21:47:34 +0000413 channels;
414
415 channels=0;
cristy3fc482f2011-09-23 00:43:35 +0000416 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
417 {
cristyabace412011-12-11 15:56:53 +0000418 PixelChannel
419 channel;
420
cristy3fc482f2011-09-23 00:43:35 +0000421 PixelTrait
422 traits;
423
cristyabace412011-12-11 15:56:53 +0000424 channel=GetPixelChannelMapChannel(image,i);
425 traits=GetPixelChannelMapTraits(image,channel);
cristy25a5f2f2011-09-24 14:09:43 +0000426 if ((traits & UpdatePixelTrait) != 0)
cristy3fc482f2011-09-23 00:43:35 +0000427 channels++;
428 }
cristy3ed852e2009-09-05 21:47:34 +0000429 return(channels);
430}
431
cristy343eee92010-12-11 02:17:57 +0000432static MagickBooleanType GetFuzzDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000433 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy343eee92010-12-11 02:17:57 +0000434{
435 CacheView
436 *image_view,
437 *reconstruct_view;
438
cristy343eee92010-12-11 02:17:57 +0000439 MagickBooleanType
440 status;
441
442 register ssize_t
443 i;
444
cristy9d314ff2011-03-09 01:30:28 +0000445 ssize_t
446 y;
447
cristy343eee92010-12-11 02:17:57 +0000448 status=MagickTrue;
449 image_view=AcquireCacheView(image);
450 reconstruct_view=AcquireCacheView(reconstruct_image);
451#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000452 #pragma omp parallel for schedule(static,4) shared(status)
cristy343eee92010-12-11 02:17:57 +0000453#endif
454 for (y=0; y < (ssize_t) image->rows; y++)
455 {
456 double
cristy3fc482f2011-09-23 00:43:35 +0000457 channel_distortion[MaxPixelChannels+1];
cristy343eee92010-12-11 02:17:57 +0000458
cristy4c08aed2011-07-01 19:47:50 +0000459 register const Quantum
cristy343eee92010-12-11 02:17:57 +0000460 *restrict p,
461 *restrict q;
462
463 register ssize_t
464 i,
465 x;
466
467 if (status == MagickFalse)
468 continue;
469 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristye6529ff2011-02-04 20:05:32 +0000470 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
471 1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000472 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy343eee92010-12-11 02:17:57 +0000473 {
474 status=MagickFalse;
475 continue;
476 }
cristy343eee92010-12-11 02:17:57 +0000477 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
478 for (x=0; x < (ssize_t) image->columns; x++)
479 {
cristy3fc482f2011-09-23 00:43:35 +0000480 register ssize_t
481 i;
cristy343eee92010-12-11 02:17:57 +0000482
cristy10a6c612012-01-29 21:41:05 +0000483 if (GetPixelMask(image,p) != 0)
484 {
485 p+=GetPixelChannels(image);
486 q+=GetPixelChannels(reconstruct_image);
487 continue;
488 }
cristy3fc482f2011-09-23 00:43:35 +0000489 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
490 {
491 MagickRealType
492 distance;
493
494 PixelChannel
495 channel;
496
497 PixelTrait
498 reconstruct_traits,
499 traits;
500
cristye2a912b2011-12-05 20:02:07 +0000501 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000502 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000503 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
504 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000505 (reconstruct_traits == UndefinedPixelTrait) ||
506 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000507 continue;
cristy0beccfa2011-09-25 20:47:53 +0000508 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
509 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000510 distance*=distance;
511 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000512 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000513 }
cristyed231572011-07-14 02:18:59 +0000514 p+=GetPixelChannels(image);
515 q+=GetPixelChannels(reconstruct_image);
cristy343eee92010-12-11 02:17:57 +0000516 }
517#if defined(MAGICKCORE_OPENMP_SUPPORT)
518 #pragma omp critical (MagickCore_GetMeanSquaredError)
519#endif
cristy3fc482f2011-09-23 00:43:35 +0000520 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000521 distortion[i]+=channel_distortion[i];
522 }
523 reconstruct_view=DestroyCacheView(reconstruct_view);
524 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000525 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000526 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000527 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
528 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
cristy343eee92010-12-11 02:17:57 +0000529 return(status);
530}
531
cristy3cc758f2010-11-27 01:33:49 +0000532static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000533 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000534{
cristyc4c8d132010-01-07 01:58:38 +0000535 CacheView
536 *image_view,
537 *reconstruct_view;
538
cristy3ed852e2009-09-05 21:47:34 +0000539 MagickBooleanType
540 status;
541
cristybb503372010-05-27 20:51:26 +0000542 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000543 i;
544
cristy9d314ff2011-03-09 01:30:28 +0000545 ssize_t
546 y;
547
cristy3ed852e2009-09-05 21:47:34 +0000548 status=MagickTrue;
549 image_view=AcquireCacheView(image);
550 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000551#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000552 #pragma omp parallel for schedule(static,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000553#endif
cristybb503372010-05-27 20:51:26 +0000554 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000555 {
556 double
cristy3fc482f2011-09-23 00:43:35 +0000557 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000558
cristy4c08aed2011-07-01 19:47:50 +0000559 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000560 *restrict p,
561 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000562
cristybb503372010-05-27 20:51:26 +0000563 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000564 i,
565 x;
566
567 if (status == MagickFalse)
568 continue;
569 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000570 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
571 1,exception);
572 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000573 {
574 status=MagickFalse;
575 continue;
576 }
cristy3ed852e2009-09-05 21:47:34 +0000577 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000578 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000579 {
cristy3fc482f2011-09-23 00:43:35 +0000580 register ssize_t
581 i;
cristy3ed852e2009-09-05 21:47:34 +0000582
cristy10a6c612012-01-29 21:41:05 +0000583 if (GetPixelMask(image,p) != 0)
584 {
585 p+=GetPixelChannels(image);
586 q+=GetPixelChannels(reconstruct_image);
587 continue;
588 }
cristy3fc482f2011-09-23 00:43:35 +0000589 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
590 {
591 MagickRealType
592 distance;
593
594 PixelChannel
595 channel;
596
597 PixelTrait
598 reconstruct_traits,
599 traits;
600
cristye2a912b2011-12-05 20:02:07 +0000601 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000602 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000603 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
604 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000605 (reconstruct_traits == UndefinedPixelTrait) ||
606 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000607 continue;
cristy0beccfa2011-09-25 20:47:53 +0000608 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
609 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000610 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000611 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000612 }
cristyed231572011-07-14 02:18:59 +0000613 p+=GetPixelChannels(image);
614 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000615 }
cristyb5d5f722009-11-04 03:03:49 +0000616#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000617 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
618#endif
cristy3fc482f2011-09-23 00:43:35 +0000619 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000620 distortion[i]+=channel_distortion[i];
621 }
622 reconstruct_view=DestroyCacheView(reconstruct_view);
623 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000624 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000625 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000626 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000627 return(status);
628}
629
630static MagickBooleanType GetMeanErrorPerPixel(Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000631 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000632{
cristyc4c8d132010-01-07 01:58:38 +0000633 CacheView
634 *image_view,
635 *reconstruct_view;
636
cristy3ed852e2009-09-05 21:47:34 +0000637 MagickBooleanType
638 status;
639
640 MagickRealType
641 alpha,
642 area,
643 beta,
644 maximum_error,
645 mean_error;
646
cristy9d314ff2011-03-09 01:30:28 +0000647 ssize_t
648 y;
649
cristy3ed852e2009-09-05 21:47:34 +0000650 status=MagickTrue;
651 alpha=1.0;
652 beta=1.0;
653 area=0.0;
654 maximum_error=0.0;
655 mean_error=0.0;
656 image_view=AcquireCacheView(image);
657 reconstruct_view=AcquireCacheView(reconstruct_image);
cristybb503372010-05-27 20:51:26 +0000658 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000659 {
cristy4c08aed2011-07-01 19:47:50 +0000660 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000661 *restrict p,
662 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000663
cristybb503372010-05-27 20:51:26 +0000664 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000665 x;
666
667 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
668 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
669 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000670 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000671 {
672 status=MagickFalse;
673 break;
674 }
cristybb503372010-05-27 20:51:26 +0000675 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000676 {
cristy3fc482f2011-09-23 00:43:35 +0000677 register ssize_t
678 i;
cristy3ed852e2009-09-05 21:47:34 +0000679
cristy10a6c612012-01-29 21:41:05 +0000680 if (GetPixelMask(image,p) != 0)
681 {
682 p+=GetPixelChannels(image);
683 q+=GetPixelChannels(reconstruct_image);
684 continue;
685 }
cristy3fc482f2011-09-23 00:43:35 +0000686 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
687 {
688 MagickRealType
689 distance;
690
691 PixelChannel
692 channel;
693
694 PixelTrait
695 reconstruct_traits,
696 traits;
697
cristye2a912b2011-12-05 20:02:07 +0000698 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000699 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000700 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
701 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000702 (reconstruct_traits == UndefinedPixelTrait) ||
703 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000704 continue;
cristy0beccfa2011-09-25 20:47:53 +0000705 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
706 reconstruct_image,channel,q)));
cristy3fc482f2011-09-23 00:43:35 +0000707 distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000708 distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000709 mean_error+=distance*distance;
710 if (distance > maximum_error)
711 maximum_error=distance;
712 area++;
713 }
cristyed231572011-07-14 02:18:59 +0000714 p+=GetPixelChannels(image);
715 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000716 }
717 }
718 reconstruct_view=DestroyCacheView(reconstruct_view);
719 image_view=DestroyCacheView(image_view);
cristy5f95f4f2011-10-23 01:01:01 +0000720 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
cristy3ed852e2009-09-05 21:47:34 +0000721 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
722 image->error.normalized_maximum_error=QuantumScale*maximum_error;
723 return(status);
724}
725
cristy3cc758f2010-11-27 01:33:49 +0000726static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000727 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000728{
cristyc4c8d132010-01-07 01:58:38 +0000729 CacheView
730 *image_view,
731 *reconstruct_view;
732
cristy3ed852e2009-09-05 21:47:34 +0000733 MagickBooleanType
734 status;
735
cristybb503372010-05-27 20:51:26 +0000736 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000737 i;
738
cristy9d314ff2011-03-09 01:30:28 +0000739 ssize_t
740 y;
741
cristy3ed852e2009-09-05 21:47:34 +0000742 status=MagickTrue;
743 image_view=AcquireCacheView(image);
744 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000745#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000746 #pragma omp parallel for schedule(static,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000747#endif
cristybb503372010-05-27 20:51:26 +0000748 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000749 {
750 double
cristy3fc482f2011-09-23 00:43:35 +0000751 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000752
cristy4c08aed2011-07-01 19:47:50 +0000753 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000754 *restrict p,
755 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000756
cristybb503372010-05-27 20:51:26 +0000757 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000758 i,
759 x;
760
761 if (status == MagickFalse)
762 continue;
763 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000764 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
765 1,exception);
766 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000767 {
768 status=MagickFalse;
769 continue;
770 }
cristy3ed852e2009-09-05 21:47:34 +0000771 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000772 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000773 {
cristy3fc482f2011-09-23 00:43:35 +0000774 register ssize_t
775 i;
cristy3ed852e2009-09-05 21:47:34 +0000776
cristy10a6c612012-01-29 21:41:05 +0000777 if (GetPixelMask(image,p) != 0)
778 {
779 p+=GetPixelChannels(image);
780 q+=GetPixelChannels(reconstruct_image);
781 continue;
782 }
cristy3fc482f2011-09-23 00:43:35 +0000783 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
784 {
785 MagickRealType
786 distance;
787
788 PixelChannel
789 channel;
790
791 PixelTrait
792 reconstruct_traits,
793 traits;
794
cristye2a912b2011-12-05 20:02:07 +0000795 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000796 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000797 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
798 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000799 (reconstruct_traits == UndefinedPixelTrait) ||
800 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000801 continue;
cristy0beccfa2011-09-25 20:47:53 +0000802 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
803 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000804 distance*=distance;
805 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000806 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000807 }
cristyed231572011-07-14 02:18:59 +0000808 p+=GetPixelChannels(image);
809 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000810 }
cristyb5d5f722009-11-04 03:03:49 +0000811#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000812 #pragma omp critical (MagickCore_GetMeanSquaredError)
813#endif
cristy3fc482f2011-09-23 00:43:35 +0000814 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000815 distortion[i]+=channel_distortion[i];
816 }
817 reconstruct_view=DestroyCacheView(reconstruct_view);
818 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000819 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000820 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000821 distortion[CompositePixelChannel]/=GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000822 return(status);
823}
824
cristy3cc758f2010-11-27 01:33:49 +0000825static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
cristy8a9106f2011-07-05 14:39:26 +0000826 const Image *image,const Image *reconstruct_image,double *distortion,
827 ExceptionInfo *exception)
cristy4c929a72010-11-24 18:54:42 +0000828{
cristy9f48ca62010-11-25 03:06:31 +0000829#define SimilarityImageTag "Similarity/Image"
830
cristy4c929a72010-11-24 18:54:42 +0000831 CacheView
832 *image_view,
833 *reconstruct_view;
834
cristy9f48ca62010-11-25 03:06:31 +0000835 ChannelStatistics
cristy9f48ca62010-11-25 03:06:31 +0000836 *image_statistics,
837 *reconstruct_statistics;
838
cristy4c929a72010-11-24 18:54:42 +0000839 MagickBooleanType
840 status;
841
cristy18a41362010-11-27 15:56:18 +0000842 MagickOffsetType
843 progress;
844
cristy34d6fdc2010-11-26 19:06:08 +0000845 MagickRealType
846 area;
847
cristy4c929a72010-11-24 18:54:42 +0000848 register ssize_t
849 i;
850
cristy3cc758f2010-11-27 01:33:49 +0000851 ssize_t
852 y;
853
cristy34d6fdc2010-11-26 19:06:08 +0000854 /*
cristy18a41362010-11-27 15:56:18 +0000855 Normalize to account for variation due to lighting and exposure condition.
cristy34d6fdc2010-11-26 19:06:08 +0000856 */
cristyd42d9952011-07-08 14:21:50 +0000857 image_statistics=GetImageStatistics(image,exception);
858 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
cristy4c929a72010-11-24 18:54:42 +0000859 status=MagickTrue;
cristy9f48ca62010-11-25 03:06:31 +0000860 progress=0;
cristy3fc482f2011-09-23 00:43:35 +0000861 for (i=0; i <= MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000862 distortion[i]=0.0;
cristye28f7af2011-10-17 18:21:57 +0000863 area=1.0/((MagickRealType) image->columns*image->rows-1);
cristy4c929a72010-11-24 18:54:42 +0000864 image_view=AcquireCacheView(image);
865 reconstruct_view=AcquireCacheView(reconstruct_image);
cristy4c929a72010-11-24 18:54:42 +0000866 for (y=0; y < (ssize_t) image->rows; y++)
867 {
cristy4c08aed2011-07-01 19:47:50 +0000868 register const Quantum
cristy4c929a72010-11-24 18:54:42 +0000869 *restrict p,
870 *restrict q;
871
872 register ssize_t
cristy4c929a72010-11-24 18:54:42 +0000873 x;
874
875 if (status == MagickFalse)
876 continue;
877 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy34d6fdc2010-11-26 19:06:08 +0000878 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
879 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000880 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy4c929a72010-11-24 18:54:42 +0000881 {
882 status=MagickFalse;
883 continue;
884 }
cristy4c929a72010-11-24 18:54:42 +0000885 for (x=0; x < (ssize_t) image->columns; x++)
886 {
cristy3fc482f2011-09-23 00:43:35 +0000887 register ssize_t
888 i;
889
cristy10a6c612012-01-29 21:41:05 +0000890 if (GetPixelMask(image,p) != 0)
891 {
892 p+=GetPixelChannels(image);
893 q+=GetPixelChannels(reconstruct_image);
894 continue;
895 }
cristy3fc482f2011-09-23 00:43:35 +0000896 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
897 {
898 PixelChannel
899 channel;
900
901 PixelTrait
902 reconstruct_traits,
903 traits;
904
cristye2a912b2011-12-05 20:02:07 +0000905 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000906 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000907 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
908 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000909 (reconstruct_traits == UndefinedPixelTrait) ||
910 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000911 continue;
cristy3fc482f2011-09-23 00:43:35 +0000912 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
cristy0beccfa2011-09-25 20:47:53 +0000913 (GetPixelChannel(reconstruct_image,channel,q)-
914 reconstruct_statistics[channel].mean);
cristy3fc482f2011-09-23 00:43:35 +0000915 }
cristyed231572011-07-14 02:18:59 +0000916 p+=GetPixelChannels(image);
cristy10a6c612012-01-29 21:41:05 +0000917 q+=GetPixelChannels(reconstruct_image);
cristy4c929a72010-11-24 18:54:42 +0000918 }
cristy9f48ca62010-11-25 03:06:31 +0000919 if (image->progress_monitor != (MagickProgressMonitor) NULL)
920 {
921 MagickBooleanType
922 proceed;
923
cristy9f48ca62010-11-25 03:06:31 +0000924 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
925 image->rows);
926 if (proceed == MagickFalse)
927 status=MagickFalse;
928 }
cristy4c929a72010-11-24 18:54:42 +0000929 }
930 reconstruct_view=DestroyCacheView(reconstruct_view);
931 image_view=DestroyCacheView(image_view);
cristy34d6fdc2010-11-26 19:06:08 +0000932 /*
933 Divide by the standard deviation.
934 */
cristy5f95f4f2011-10-23 01:01:01 +0000935 distortion[CompositePixelChannel]=0.0;
cristy3fc482f2011-09-23 00:43:35 +0000936 for (i=0; i < MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000937 {
938 MagickRealType
cristy18a41362010-11-27 15:56:18 +0000939 gamma;
cristy34d6fdc2010-11-26 19:06:08 +0000940
cristy3fc482f2011-09-23 00:43:35 +0000941 PixelChannel
942 channel;
943
cristye2a912b2011-12-05 20:02:07 +0000944 channel=GetPixelChannelMapChannel(image,i);
cristy18a41362010-11-27 15:56:18 +0000945 gamma=image_statistics[i].standard_deviation*
cristy3fc482f2011-09-23 00:43:35 +0000946 reconstruct_statistics[channel].standard_deviation;
cristy18a41362010-11-27 15:56:18 +0000947 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
948 distortion[i]=QuantumRange*gamma*distortion[i];
cristy5f95f4f2011-10-23 01:01:01 +0000949 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
cristy34d6fdc2010-11-26 19:06:08 +0000950 }
cristy5f95f4f2011-10-23 01:01:01 +0000951 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
cristy49dd6a02011-09-24 23:08:01 +0000952 GetImageChannels(image));
cristy34d6fdc2010-11-26 19:06:08 +0000953 /*
954 Free resources.
955 */
cristy9f48ca62010-11-25 03:06:31 +0000956 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
957 reconstruct_statistics);
958 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
959 image_statistics);
cristy4c929a72010-11-24 18:54:42 +0000960 return(status);
961}
962
cristy3cc758f2010-11-27 01:33:49 +0000963static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000964 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000965{
cristyc4c8d132010-01-07 01:58:38 +0000966 CacheView
967 *image_view,
968 *reconstruct_view;
969
cristy3ed852e2009-09-05 21:47:34 +0000970 MagickBooleanType
971 status;
972
cristy9d314ff2011-03-09 01:30:28 +0000973 ssize_t
974 y;
975
cristy3ed852e2009-09-05 21:47:34 +0000976 status=MagickTrue;
977 image_view=AcquireCacheView(image);
978 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000979#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000980 #pragma omp parallel for schedule(static,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000981#endif
cristybb503372010-05-27 20:51:26 +0000982 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000983 {
984 double
cristy3fc482f2011-09-23 00:43:35 +0000985 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000986
cristy4c08aed2011-07-01 19:47:50 +0000987 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000988 *restrict p,
989 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000990
cristybb503372010-05-27 20:51:26 +0000991 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000992 i,
993 x;
994
995 if (status == MagickFalse)
996 continue;
997 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy10a6c612012-01-29 21:41:05 +0000998 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
999 1,exception);
cristy3fc482f2011-09-23 00:43:35 +00001000 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001001 {
1002 status=MagickFalse;
1003 continue;
1004 }
cristy3ed852e2009-09-05 21:47:34 +00001005 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +00001006 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001007 {
cristy3fc482f2011-09-23 00:43:35 +00001008 register ssize_t
1009 i;
cristy3ed852e2009-09-05 21:47:34 +00001010
cristy10a6c612012-01-29 21:41:05 +00001011 if (GetPixelMask(image,p) != 0)
1012 {
1013 p+=GetPixelChannels(image);
1014 q+=GetPixelChannels(reconstruct_image);
1015 continue;
1016 }
cristy3fc482f2011-09-23 00:43:35 +00001017 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1018 {
1019 MagickRealType
1020 distance;
1021
1022 PixelChannel
1023 channel;
1024
1025 PixelTrait
1026 reconstruct_traits,
1027 traits;
1028
cristye2a912b2011-12-05 20:02:07 +00001029 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001030 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +00001031 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1032 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001033 (reconstruct_traits == UndefinedPixelTrait) ||
1034 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001035 continue;
cristy0beccfa2011-09-25 20:47:53 +00001036 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
1037 reconstruct_image,channel,q));
cristy25a5f2f2011-09-24 14:09:43 +00001038 if (distance > channel_distortion[i])
cristy3fc482f2011-09-23 00:43:35 +00001039 channel_distortion[i]=distance;
cristy5f95f4f2011-10-23 01:01:01 +00001040 if (distance > channel_distortion[CompositePixelChannel])
1041 channel_distortion[CompositePixelChannel]=distance;
cristy3fc482f2011-09-23 00:43:35 +00001042 }
cristyed231572011-07-14 02:18:59 +00001043 p+=GetPixelChannels(image);
cristy10a6c612012-01-29 21:41:05 +00001044 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001045 }
cristyb5d5f722009-11-04 03:03:49 +00001046#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001047 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1048#endif
cristy3fc482f2011-09-23 00:43:35 +00001049 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001050 if (channel_distortion[i] > distortion[i])
1051 distortion[i]=channel_distortion[i];
1052 }
1053 reconstruct_view=DestroyCacheView(reconstruct_view);
1054 image_view=DestroyCacheView(image_view);
1055 return(status);
1056}
1057
1058static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001059 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001060{
1061 MagickBooleanType
1062 status;
1063
cristy3fc482f2011-09-23 00:43:35 +00001064 register ssize_t
1065 i;
1066
cristy8a9106f2011-07-05 14:39:26 +00001067 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001068 for (i=0; i <= MaxPixelChannels; i++)
1069 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
cristy3ed852e2009-09-05 21:47:34 +00001070 return(status);
1071}
1072
cristy3cc758f2010-11-27 01:33:49 +00001073static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001074 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001075{
1076 MagickBooleanType
1077 status;
1078
cristy3fc482f2011-09-23 00:43:35 +00001079 register ssize_t
1080 i;
1081
cristy8a9106f2011-07-05 14:39:26 +00001082 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001083 for (i=0; i <= MaxPixelChannels; i++)
1084 distortion[i]=sqrt(distortion[i]);
cristy3ed852e2009-09-05 21:47:34 +00001085 return(status);
1086}
1087
cristy8a9106f2011-07-05 14:39:26 +00001088MagickExport MagickBooleanType GetImageDistortion(Image *image,
1089 const Image *reconstruct_image,const MetricType metric,double *distortion,
1090 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001091{
1092 double
1093 *channel_distortion;
1094
1095 MagickBooleanType
1096 status;
1097
1098 size_t
1099 length;
1100
1101 assert(image != (Image *) NULL);
1102 assert(image->signature == MagickSignature);
1103 if (image->debug != MagickFalse)
1104 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1105 assert(reconstruct_image != (const Image *) NULL);
1106 assert(reconstruct_image->signature == MagickSignature);
1107 assert(distortion != (double *) NULL);
1108 *distortion=0.0;
1109 if (image->debug != MagickFalse)
1110 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1111 if ((reconstruct_image->columns != image->columns) ||
1112 (reconstruct_image->rows != image->rows))
1113 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1114 /*
1115 Get image distortion.
1116 */
cristy3fc482f2011-09-23 00:43:35 +00001117 length=MaxPixelChannels+1;
cristy3ed852e2009-09-05 21:47:34 +00001118 channel_distortion=(double *) AcquireQuantumMemory(length,
1119 sizeof(*channel_distortion));
1120 if (channel_distortion == (double *) NULL)
1121 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1122 (void) ResetMagickMemory(channel_distortion,0,length*
1123 sizeof(*channel_distortion));
1124 switch (metric)
1125 {
1126 case AbsoluteErrorMetric:
1127 {
cristy8a9106f2011-07-05 14:39:26 +00001128 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1129 exception);
cristy3ed852e2009-09-05 21:47:34 +00001130 break;
1131 }
cristy343eee92010-12-11 02:17:57 +00001132 case FuzzErrorMetric:
1133 {
cristy8a9106f2011-07-05 14:39:26 +00001134 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1135 exception);
cristy343eee92010-12-11 02:17:57 +00001136 break;
1137 }
cristy3ed852e2009-09-05 21:47:34 +00001138 case MeanAbsoluteErrorMetric:
1139 {
cristy8a9106f2011-07-05 14:39:26 +00001140 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001141 channel_distortion,exception);
1142 break;
1143 }
1144 case MeanErrorPerPixelMetric:
1145 {
cristy8a9106f2011-07-05 14:39:26 +00001146 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1147 exception);
cristy3ed852e2009-09-05 21:47:34 +00001148 break;
1149 }
1150 case MeanSquaredErrorMetric:
1151 {
cristy8a9106f2011-07-05 14:39:26 +00001152 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001153 channel_distortion,exception);
1154 break;
1155 }
cristy4c929a72010-11-24 18:54:42 +00001156 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001157 default:
cristy4c929a72010-11-24 18:54:42 +00001158 {
cristy3cc758f2010-11-27 01:33:49 +00001159 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001160 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001161 break;
1162 }
cristy3ed852e2009-09-05 21:47:34 +00001163 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001164 {
cristy8a9106f2011-07-05 14:39:26 +00001165 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001166 channel_distortion,exception);
1167 break;
1168 }
1169 case PeakSignalToNoiseRatioMetric:
1170 {
cristy8a9106f2011-07-05 14:39:26 +00001171 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001172 channel_distortion,exception);
1173 break;
1174 }
1175 case RootMeanSquaredErrorMetric:
1176 {
cristy8a9106f2011-07-05 14:39:26 +00001177 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001178 channel_distortion,exception);
1179 break;
1180 }
1181 }
cristy5f95f4f2011-10-23 01:01:01 +00001182 *distortion=channel_distortion[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001183 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1184 return(status);
1185}
1186
1187/*
1188%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1189% %
1190% %
1191% %
cristy8a9106f2011-07-05 14:39:26 +00001192% 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 +00001193% %
1194% %
1195% %
1196%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1197%
cristy8a9106f2011-07-05 14:39:26 +00001198% GetImageDistrortion() compares the pixel channels of an image to a
cristy3ed852e2009-09-05 21:47:34 +00001199% reconstructed image and returns the specified distortion metric for each
1200% channel.
1201%
cristy8a9106f2011-07-05 14:39:26 +00001202% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +00001203%
cristy8a9106f2011-07-05 14:39:26 +00001204% double *GetImageDistortions(const Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001205% const Image *reconstruct_image,const MetricType metric,
1206% ExceptionInfo *exception)
1207%
1208% A description of each parameter follows:
1209%
1210% o image: the image.
1211%
1212% o reconstruct_image: the reconstruct image.
1213%
1214% o metric: the metric.
1215%
1216% o exception: return any errors or warnings in this structure.
1217%
1218*/
cristy8a9106f2011-07-05 14:39:26 +00001219MagickExport double *GetImageDistortions(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001220 const Image *reconstruct_image,const MetricType metric,
1221 ExceptionInfo *exception)
1222{
1223 double
1224 *channel_distortion;
1225
1226 MagickBooleanType
1227 status;
1228
1229 size_t
1230 length;
1231
1232 assert(image != (Image *) NULL);
1233 assert(image->signature == MagickSignature);
1234 if (image->debug != MagickFalse)
1235 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1236 assert(reconstruct_image != (const Image *) NULL);
1237 assert(reconstruct_image->signature == MagickSignature);
1238 if (image->debug != MagickFalse)
1239 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1240 if ((reconstruct_image->columns != image->columns) ||
1241 (reconstruct_image->rows != image->rows))
1242 {
cristyc82a27b2011-10-21 01:07:16 +00001243 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1244 "ImageSizeDiffers","`%s'",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001245 return((double *) NULL);
1246 }
1247 /*
1248 Get image distortion.
1249 */
cristy3fc482f2011-09-23 00:43:35 +00001250 length=MaxPixelChannels+1UL;
cristy3ed852e2009-09-05 21:47:34 +00001251 channel_distortion=(double *) AcquireQuantumMemory(length,
1252 sizeof(*channel_distortion));
1253 if (channel_distortion == (double *) NULL)
1254 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1255 (void) ResetMagickMemory(channel_distortion,0,length*
1256 sizeof(*channel_distortion));
cristyda16f162011-02-19 23:52:17 +00001257 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +00001258 switch (metric)
1259 {
1260 case AbsoluteErrorMetric:
1261 {
cristy8a9106f2011-07-05 14:39:26 +00001262 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1263 exception);
cristy3ed852e2009-09-05 21:47:34 +00001264 break;
1265 }
cristy343eee92010-12-11 02:17:57 +00001266 case FuzzErrorMetric:
1267 {
cristy8a9106f2011-07-05 14:39:26 +00001268 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1269 exception);
cristy343eee92010-12-11 02:17:57 +00001270 break;
1271 }
cristy3ed852e2009-09-05 21:47:34 +00001272 case MeanAbsoluteErrorMetric:
1273 {
cristy8a9106f2011-07-05 14:39:26 +00001274 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001275 channel_distortion,exception);
1276 break;
1277 }
1278 case MeanErrorPerPixelMetric:
1279 {
cristy8a9106f2011-07-05 14:39:26 +00001280 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1281 exception);
cristy3ed852e2009-09-05 21:47:34 +00001282 break;
1283 }
1284 case MeanSquaredErrorMetric:
1285 {
cristy8a9106f2011-07-05 14:39:26 +00001286 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001287 channel_distortion,exception);
1288 break;
1289 }
cristy4c929a72010-11-24 18:54:42 +00001290 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001291 default:
cristy4c929a72010-11-24 18:54:42 +00001292 {
cristy3cc758f2010-11-27 01:33:49 +00001293 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001294 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001295 break;
1296 }
cristy3ed852e2009-09-05 21:47:34 +00001297 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001298 {
cristy8a9106f2011-07-05 14:39:26 +00001299 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001300 channel_distortion,exception);
1301 break;
1302 }
1303 case PeakSignalToNoiseRatioMetric:
1304 {
cristy8a9106f2011-07-05 14:39:26 +00001305 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001306 channel_distortion,exception);
1307 break;
1308 }
1309 case RootMeanSquaredErrorMetric:
1310 {
cristy8a9106f2011-07-05 14:39:26 +00001311 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001312 channel_distortion,exception);
1313 break;
1314 }
1315 }
cristyda16f162011-02-19 23:52:17 +00001316 if (status == MagickFalse)
1317 {
1318 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1319 return((double *) NULL);
1320 }
cristy3ed852e2009-09-05 21:47:34 +00001321 return(channel_distortion);
1322}
1323
1324/*
1325%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1326% %
1327% %
1328% %
1329% I s I m a g e s E q u a l %
1330% %
1331% %
1332% %
1333%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1334%
1335% IsImagesEqual() measures the difference between colors at each pixel
1336% location of two images. A value other than 0 means the colors match
1337% exactly. Otherwise an error measure is computed by summing over all
1338% pixels in an image the distance squared in RGB space between each image
1339% pixel and its corresponding pixel in the reconstruct image. The error
1340% measure is assigned to these image members:
1341%
1342% o mean_error_per_pixel: The mean error for any single pixel in
1343% the image.
1344%
1345% o normalized_mean_error: The normalized mean quantization error for
1346% any single pixel in the image. This distance measure is normalized to
1347% a range between 0 and 1. It is independent of the range of red, green,
1348% and blue values in the image.
1349%
1350% o normalized_maximum_error: The normalized maximum quantization
1351% error for any single pixel in the image. This distance measure is
1352% normalized to a range between 0 and 1. It is independent of the range
1353% of red, green, and blue values in your image.
1354%
1355% A small normalized mean square error, accessed as
1356% image->normalized_mean_error, suggests the images are very similar in
1357% spatial layout and color.
1358%
1359% The format of the IsImagesEqual method is:
1360%
1361% MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001362% const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001363%
1364% A description of each parameter follows.
1365%
1366% o image: the image.
1367%
1368% o reconstruct_image: the reconstruct image.
1369%
cristy018f07f2011-09-04 21:15:19 +00001370% o exception: return any errors or warnings in this structure.
1371%
cristy3ed852e2009-09-05 21:47:34 +00001372*/
1373MagickExport MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001374 const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001375{
cristyc4c8d132010-01-07 01:58:38 +00001376 CacheView
1377 *image_view,
1378 *reconstruct_view;
1379
cristy3ed852e2009-09-05 21:47:34 +00001380 MagickBooleanType
1381 status;
1382
1383 MagickRealType
1384 area,
1385 maximum_error,
1386 mean_error,
1387 mean_error_per_pixel;
1388
cristy9d314ff2011-03-09 01:30:28 +00001389 ssize_t
1390 y;
1391
cristy3ed852e2009-09-05 21:47:34 +00001392 assert(image != (Image *) NULL);
1393 assert(image->signature == MagickSignature);
1394 assert(reconstruct_image != (const Image *) NULL);
1395 assert(reconstruct_image->signature == MagickSignature);
1396 if ((reconstruct_image->columns != image->columns) ||
1397 (reconstruct_image->rows != image->rows))
1398 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1399 area=0.0;
1400 maximum_error=0.0;
1401 mean_error_per_pixel=0.0;
1402 mean_error=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001403 image_view=AcquireCacheView(image);
1404 reconstruct_view=AcquireCacheView(reconstruct_image);
cristybb503372010-05-27 20:51:26 +00001405 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001406 {
cristy4c08aed2011-07-01 19:47:50 +00001407 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001408 *restrict p,
1409 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001410
cristybb503372010-05-27 20:51:26 +00001411 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001412 x;
1413
1414 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1415 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1416 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001417 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001418 break;
cristybb503372010-05-27 20:51:26 +00001419 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001420 {
cristyd5c15f92011-09-23 00:58:33 +00001421 register ssize_t
1422 i;
cristy3ed852e2009-09-05 21:47:34 +00001423
cristy10a6c612012-01-29 21:41:05 +00001424 if (GetPixelMask(image,p) != 0)
1425 {
1426 p+=GetPixelChannels(image);
1427 q+=GetPixelChannels(reconstruct_image);
1428 continue;
1429 }
cristyd5c15f92011-09-23 00:58:33 +00001430 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1431 {
1432 MagickRealType
1433 distance;
1434
1435 PixelChannel
1436 channel;
1437
1438 PixelTrait
1439 reconstruct_traits,
1440 traits;
1441
cristye2a912b2011-12-05 20:02:07 +00001442 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001443 traits=GetPixelChannelMapTraits(image,channel);
cristyd5c15f92011-09-23 00:58:33 +00001444 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1445 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001446 (reconstruct_traits == UndefinedPixelTrait) ||
1447 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001448 continue;
cristy0beccfa2011-09-25 20:47:53 +00001449 distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
1450 channel,q));
cristyd5c15f92011-09-23 00:58:33 +00001451 mean_error_per_pixel+=distance;
1452 mean_error+=distance*distance;
1453 if (distance > maximum_error)
1454 maximum_error=distance;
1455 area++;
1456 }
cristyed231572011-07-14 02:18:59 +00001457 p+=GetPixelChannels(image);
1458 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001459 }
1460 }
1461 reconstruct_view=DestroyCacheView(reconstruct_view);
1462 image_view=DestroyCacheView(image_view);
1463 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1464 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1465 mean_error/area);
1466 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1467 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1468 return(status);
1469}
1470
1471/*
1472%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1473% %
1474% %
1475% %
1476% S i m i l a r i t y I m a g e %
1477% %
1478% %
1479% %
1480%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1481%
1482% SimilarityImage() compares the reference image of the image and returns the
1483% best match offset. In addition, it returns a similarity image such that an
1484% exact match location is completely white and if none of the pixels match,
1485% black, otherwise some gray level in-between.
1486%
1487% The format of the SimilarityImageImage method is:
1488%
1489% Image *SimilarityImage(const Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001490% const MetricType metric,RectangleInfo *offset,double *similarity,
1491% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001492%
1493% A description of each parameter follows:
1494%
1495% o image: the image.
1496%
1497% o reference: find an area of the image that closely resembles this image.
1498%
cristy09136812011-10-18 15:24:30 +00001499% o metric: the metric.
1500%
cristy3ed852e2009-09-05 21:47:34 +00001501% o the best match offset of the reference image within the image.
1502%
1503% o similarity: the computed similarity between the images.
1504%
1505% o exception: return any errors or warnings in this structure.
1506%
1507*/
1508
1509static double GetSimilarityMetric(const Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001510 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1511 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001512{
1513 double
cristy3cc758f2010-11-27 01:33:49 +00001514 distortion;
cristy3ed852e2009-09-05 21:47:34 +00001515
cristy713ff212010-11-26 21:56:11 +00001516 Image
1517 *similarity_image;
cristy3ed852e2009-09-05 21:47:34 +00001518
cristy09136812011-10-18 15:24:30 +00001519 MagickBooleanType
1520 status;
1521
cristy713ff212010-11-26 21:56:11 +00001522 RectangleInfo
1523 geometry;
cristy3ed852e2009-09-05 21:47:34 +00001524
cristy713ff212010-11-26 21:56:11 +00001525 SetGeometry(reference,&geometry);
1526 geometry.x=x_offset;
1527 geometry.y=y_offset;
1528 similarity_image=CropImage(image,&geometry,exception);
1529 if (similarity_image == (Image *) NULL)
1530 return(0.0);
cristy09136812011-10-18 15:24:30 +00001531 distortion=0.0;
1532 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
cristy3cc758f2010-11-27 01:33:49 +00001533 exception);
cristy713ff212010-11-26 21:56:11 +00001534 similarity_image=DestroyImage(similarity_image);
cristy09136812011-10-18 15:24:30 +00001535 if (status == MagickFalse)
1536 return(0.0);
cristy3cc758f2010-11-27 01:33:49 +00001537 return(distortion);
cristy3ed852e2009-09-05 21:47:34 +00001538}
1539
1540MagickExport Image *SimilarityImage(Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001541 const MetricType metric,RectangleInfo *offset,double *similarity_metric,
1542 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001543{
1544#define SimilarityImageTag "Similarity/Image"
1545
cristyc4c8d132010-01-07 01:58:38 +00001546 CacheView
1547 *similarity_view;
1548
cristy3ed852e2009-09-05 21:47:34 +00001549 Image
1550 *similarity_image;
1551
1552 MagickBooleanType
1553 status;
1554
cristybb503372010-05-27 20:51:26 +00001555 MagickOffsetType
1556 progress;
1557
1558 ssize_t
1559 y;
1560
cristy3ed852e2009-09-05 21:47:34 +00001561 assert(image != (const Image *) NULL);
1562 assert(image->signature == MagickSignature);
1563 if (image->debug != MagickFalse)
1564 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1565 assert(exception != (ExceptionInfo *) NULL);
1566 assert(exception->signature == MagickSignature);
1567 assert(offset != (RectangleInfo *) NULL);
1568 SetGeometry(reference,offset);
1569 *similarity_metric=1.0;
cristyf9255b92010-08-14 22:59:00 +00001570 if ((reference->columns > image->columns) || (reference->rows > image->rows))
cristy3ed852e2009-09-05 21:47:34 +00001571 ThrowImageException(ImageError,"ImageSizeDiffers");
1572 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1573 image->rows-reference->rows+1,MagickTrue,exception);
1574 if (similarity_image == (Image *) NULL)
1575 return((Image *) NULL);
cristyd5c15f92011-09-23 00:58:33 +00001576 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1577 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001578 {
cristy3ed852e2009-09-05 21:47:34 +00001579 similarity_image=DestroyImage(similarity_image);
1580 return((Image *) NULL);
1581 }
1582 /*
1583 Measure similarity of reference image against image.
1584 */
1585 status=MagickTrue;
1586 progress=0;
1587 similarity_view=AcquireCacheView(similarity_image);
cristyb5d5f722009-11-04 03:03:49 +00001588#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001589 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001590#endif
cristybb503372010-05-27 20:51:26 +00001591 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
cristy3ed852e2009-09-05 21:47:34 +00001592 {
1593 double
1594 similarity;
1595
cristy4c08aed2011-07-01 19:47:50 +00001596 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001597 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001598
cristy49dd6a02011-09-24 23:08:01 +00001599 register ssize_t
1600 x;
1601
cristy3ed852e2009-09-05 21:47:34 +00001602 if (status == MagickFalse)
1603 continue;
cristy3cc758f2010-11-27 01:33:49 +00001604 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1605 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001606 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001607 {
1608 status=MagickFalse;
1609 continue;
1610 }
cristybb503372010-05-27 20:51:26 +00001611 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
cristy3ed852e2009-09-05 21:47:34 +00001612 {
cristy49dd6a02011-09-24 23:08:01 +00001613 register ssize_t
1614 i;
1615
cristy09136812011-10-18 15:24:30 +00001616 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
cristyb5d5f722009-11-04 03:03:49 +00001617#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001618 #pragma omp critical (MagickCore_SimilarityImage)
1619#endif
1620 if (similarity < *similarity_metric)
1621 {
1622 *similarity_metric=similarity;
1623 offset->x=x;
1624 offset->y=y;
1625 }
cristyc94ba6f2012-01-29 23:19:58 +00001626 if (GetPixelMask(similarity_image,q) != 0)
cristy10a6c612012-01-29 21:41:05 +00001627 {
cristyc94ba6f2012-01-29 23:19:58 +00001628 q+=GetPixelChannels(similarity_image);
cristy10a6c612012-01-29 21:41:05 +00001629 continue;
1630 }
cristyc94ba6f2012-01-29 23:19:58 +00001631 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
cristy49dd6a02011-09-24 23:08:01 +00001632 {
1633 PixelChannel
1634 channel;
1635
1636 PixelTrait
1637 similarity_traits,
1638 traits;
1639
cristye2a912b2011-12-05 20:02:07 +00001640 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001641 traits=GetPixelChannelMapTraits(image,channel);
cristy49dd6a02011-09-24 23:08:01 +00001642 similarity_traits=GetPixelChannelMapTraits(similarity_image,channel);
1643 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001644 (similarity_traits == UndefinedPixelTrait) ||
1645 ((similarity_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001646 continue;
cristy0beccfa2011-09-25 20:47:53 +00001647 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1648 QuantumRange*similarity),q);
cristy49dd6a02011-09-24 23:08:01 +00001649 }
cristyed231572011-07-14 02:18:59 +00001650 q+=GetPixelChannels(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001651 }
1652 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1653 status=MagickFalse;
1654 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1655 {
1656 MagickBooleanType
1657 proceed;
1658
cristyb5d5f722009-11-04 03:03:49 +00001659#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001660 #pragma omp critical (MagickCore_SimilarityImage)
1661#endif
1662 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1663 image->rows);
1664 if (proceed == MagickFalse)
1665 status=MagickFalse;
1666 }
1667 }
1668 similarity_view=DestroyCacheView(similarity_view);
cristy3ed852e2009-09-05 21:47:34 +00001669 return(similarity_image);
1670}