blob: 477aeb4ceda669dfffc7b0f0968de669024e6761 [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);
cristy269c9412011-10-13 23:41:15 +0000164 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,
cristy9950d572011-10-01 18:22:35 +0000165 exception);
cristy3ed852e2009-09-05 21:47:34 +0000166 artifact=GetImageArtifact(image,"highlight-color");
167 if (artifact != (const char *) NULL)
cristy269c9412011-10-13 23:41:15 +0000168 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,
cristy9950d572011-10-01 18:22:35 +0000169 exception);
cristy269c9412011-10-13 23:41:15 +0000170 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,
cristy9950d572011-10-01 18:22:35 +0000171 exception);
cristy3ed852e2009-09-05 21:47:34 +0000172 artifact=GetImageArtifact(image,"lowlight-color");
173 if (artifact != (const char *) NULL)
cristy0b1a7972011-10-22 22:17:02 +0000174 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000175 /*
176 Generate difference image.
177 */
178 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000179 image_view=AcquireCacheView(image);
180 reconstruct_view=AcquireCacheView(reconstruct_image);
181 highlight_view=AcquireCacheView(highlight_image);
cristyb5d5f722009-11-04 03:03:49 +0000182#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000183 #pragma omp parallel for schedule(static,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000184#endif
cristybb503372010-05-27 20:51:26 +0000185 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000186 {
187 MagickBooleanType
188 sync;
189
cristy4c08aed2011-07-01 19:47:50 +0000190 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000191 *restrict p,
192 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000193
cristy4c08aed2011-07-01 19:47:50 +0000194 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000195 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000196
cristy49dd6a02011-09-24 23:08:01 +0000197 register ssize_t
198 x;
199
cristy3ed852e2009-09-05 21:47:34 +0000200 if (status == MagickFalse)
201 continue;
202 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
203 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
204 1,exception);
205 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
206 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000207 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
208 (r == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000209 {
210 status=MagickFalse;
211 continue;
212 }
cristybb503372010-05-27 20:51:26 +0000213 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000214 {
215 MagickStatusType
216 difference;
217
cristy3fc482f2011-09-23 00:43:35 +0000218 register ssize_t
219 i;
220
cristy3ed852e2009-09-05 21:47:34 +0000221 difference=MagickFalse;
cristy3fc482f2011-09-23 00:43:35 +0000222 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
223 {
cristy0beccfa2011-09-25 20:47:53 +0000224 MagickRealType
225 distance;
226
cristy3fc482f2011-09-23 00:43:35 +0000227 PixelChannel
228 channel;
229
230 PixelTrait
231 reconstruct_traits,
232 traits;
233
cristye2a912b2011-12-05 20:02:07 +0000234 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000235 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000236 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
237 if ((traits == UndefinedPixelTrait) ||
238 (reconstruct_traits == UndefinedPixelTrait))
239 continue;
cristy49dd6a02011-09-24 23:08:01 +0000240 if ((reconstruct_traits & UpdatePixelTrait) == 0)
241 continue;
cristy0beccfa2011-09-25 20:47:53 +0000242 distance=p[i]-(MagickRealType)
243 GetPixelChannel(reconstruct_image,channel,q);
244 if (fabs((double) distance) >= MagickEpsilon)
cristy3fc482f2011-09-23 00:43:35 +0000245 difference=MagickTrue;
246 }
247 if (difference == MagickFalse)
cristy803640d2011-11-17 02:11:32 +0000248 SetPixelInfoPixel(highlight_image,&lowlight,r);
cristy3fc482f2011-09-23 00:43:35 +0000249 else
cristy803640d2011-11-17 02:11:32 +0000250 SetPixelInfoPixel(highlight_image,&highlight,r);
cristyed231572011-07-14 02:18:59 +0000251 p+=GetPixelChannels(image);
252 q+=GetPixelChannels(reconstruct_image);
253 r+=GetPixelChannels(highlight_image);
cristy3ed852e2009-09-05 21:47:34 +0000254 }
255 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
256 if (sync == MagickFalse)
257 status=MagickFalse;
258 }
259 highlight_view=DestroyCacheView(highlight_view);
260 reconstruct_view=DestroyCacheView(reconstruct_view);
261 image_view=DestroyCacheView(image_view);
cristye941a752011-10-15 01:52:48 +0000262 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0,
263 exception);
cristy3ed852e2009-09-05 21:47:34 +0000264 highlight_image=DestroyImage(highlight_image);
265 if (status == MagickFalse)
266 difference_image=DestroyImage(difference_image);
267 return(difference_image);
268}
269
270/*
271%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
272% %
273% %
274% %
cristy8a9106f2011-07-05 14:39:26 +0000275% G e t I m a g e D i s t o r t i o n %
cristy3ed852e2009-09-05 21:47:34 +0000276% %
277% %
278% %
279%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
280%
cristy8a9106f2011-07-05 14:39:26 +0000281% GetImageDistortion() compares one or more pixel channels of an image to a
282% reconstructed image and returns the specified distortion metric.
cristy3ed852e2009-09-05 21:47:34 +0000283%
cristy8a9106f2011-07-05 14:39:26 +0000284% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +0000285%
cristy8a9106f2011-07-05 14:39:26 +0000286% MagickBooleanType GetImageDistortion(const Image *image,
287% const Image *reconstruct_image,const MetricType metric,
288% double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000289%
290% A description of each parameter follows:
291%
292% o image: the image.
293%
294% o reconstruct_image: the reconstruct image.
295%
cristy3ed852e2009-09-05 21:47:34 +0000296% o metric: the metric.
297%
298% o distortion: the computed distortion between the images.
299%
300% o exception: return any errors or warnings in this structure.
301%
302*/
303
cristy3cc758f2010-11-27 01:33:49 +0000304static MagickBooleanType GetAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000305 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000306{
cristyc4c8d132010-01-07 01:58:38 +0000307 CacheView
308 *image_view,
309 *reconstruct_view;
310
cristy3ed852e2009-09-05 21:47:34 +0000311 MagickBooleanType
312 status;
313
cristy9d314ff2011-03-09 01:30:28 +0000314 ssize_t
315 y;
316
cristy3ed852e2009-09-05 21:47:34 +0000317 /*
318 Compute the absolute difference in pixels between two images.
319 */
320 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000321 image_view=AcquireCacheView(image);
322 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000323#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000324 #pragma omp parallel for schedule(static,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000325#endif
cristybb503372010-05-27 20:51:26 +0000326 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000327 {
328 double
cristy3fc482f2011-09-23 00:43:35 +0000329 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000330
cristy4c08aed2011-07-01 19:47:50 +0000331 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000332 *restrict p,
333 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000334
cristybb503372010-05-27 20:51:26 +0000335 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000336 i,
337 x;
338
339 if (status == MagickFalse)
340 continue;
341 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
342 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
343 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000344 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000345 {
346 status=MagickFalse;
347 continue;
348 }
cristy3ed852e2009-09-05 21:47:34 +0000349 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000350 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000351 {
cristy3fc482f2011-09-23 00:43:35 +0000352 MagickBooleanType
353 difference;
354
355 register ssize_t
356 i;
357
358 difference=MagickFalse;
359 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
360 {
361 PixelChannel
362 channel;
363
364 PixelTrait
365 reconstruct_traits,
366 traits;
367
cristye2a912b2011-12-05 20:02:07 +0000368 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000369 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000370 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
371 if ((traits == UndefinedPixelTrait) ||
372 (reconstruct_traits == UndefinedPixelTrait))
373 continue;
cristy49dd6a02011-09-24 23:08:01 +0000374 if ((reconstruct_traits & UpdatePixelTrait) == 0)
375 continue;
cristy0beccfa2011-09-25 20:47:53 +0000376 if (p[i] != GetPixelChannel(reconstruct_image,channel,q))
cristy3fc482f2011-09-23 00:43:35 +0000377 difference=MagickTrue;
378 }
379 if (difference != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000380 {
cristy3fc482f2011-09-23 00:43:35 +0000381 channel_distortion[i]++;
cristy5f95f4f2011-10-23 01:01:01 +0000382 channel_distortion[CompositePixelChannel]++;
cristy3ed852e2009-09-05 21:47:34 +0000383 }
cristyed231572011-07-14 02:18:59 +0000384 p+=GetPixelChannels(image);
385 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000386 }
cristyb5d5f722009-11-04 03:03:49 +0000387#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000388 #pragma omp critical (MagickCore_GetAbsoluteError)
389#endif
cristy3fc482f2011-09-23 00:43:35 +0000390 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000391 distortion[i]+=channel_distortion[i];
392 }
393 reconstruct_view=DestroyCacheView(reconstruct_view);
394 image_view=DestroyCacheView(image_view);
395 return(status);
396}
397
cristy49dd6a02011-09-24 23:08:01 +0000398static size_t GetImageChannels(const Image *image)
cristy3ed852e2009-09-05 21:47:34 +0000399{
cristy3fc482f2011-09-23 00:43:35 +0000400 register ssize_t
401 i;
402
cristy49dd6a02011-09-24 23:08:01 +0000403 size_t
cristy3ed852e2009-09-05 21:47:34 +0000404 channels;
405
406 channels=0;
cristy3fc482f2011-09-23 00:43:35 +0000407 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
408 {
cristyabace412011-12-11 15:56:53 +0000409 PixelChannel
410 channel;
411
cristy3fc482f2011-09-23 00:43:35 +0000412 PixelTrait
413 traits;
414
cristyabace412011-12-11 15:56:53 +0000415 channel=GetPixelChannelMapChannel(image,i);
416 traits=GetPixelChannelMapTraits(image,channel);
cristy25a5f2f2011-09-24 14:09:43 +0000417 if ((traits & UpdatePixelTrait) != 0)
cristy3fc482f2011-09-23 00:43:35 +0000418 channels++;
419 }
cristy3ed852e2009-09-05 21:47:34 +0000420 return(channels);
421}
422
cristy343eee92010-12-11 02:17:57 +0000423static MagickBooleanType GetFuzzDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000424 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy343eee92010-12-11 02:17:57 +0000425{
426 CacheView
427 *image_view,
428 *reconstruct_view;
429
cristy343eee92010-12-11 02:17:57 +0000430 MagickBooleanType
431 status;
432
433 register ssize_t
434 i;
435
cristy9d314ff2011-03-09 01:30:28 +0000436 ssize_t
437 y;
438
cristy343eee92010-12-11 02:17:57 +0000439 status=MagickTrue;
440 image_view=AcquireCacheView(image);
441 reconstruct_view=AcquireCacheView(reconstruct_image);
442#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000443 #pragma omp parallel for schedule(static,4) shared(status)
cristy343eee92010-12-11 02:17:57 +0000444#endif
445 for (y=0; y < (ssize_t) image->rows; y++)
446 {
447 double
cristy3fc482f2011-09-23 00:43:35 +0000448 channel_distortion[MaxPixelChannels+1];
cristy343eee92010-12-11 02:17:57 +0000449
cristy4c08aed2011-07-01 19:47:50 +0000450 register const Quantum
cristy343eee92010-12-11 02:17:57 +0000451 *restrict p,
452 *restrict q;
453
454 register ssize_t
455 i,
456 x;
457
458 if (status == MagickFalse)
459 continue;
460 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristye6529ff2011-02-04 20:05:32 +0000461 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
462 1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000463 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy343eee92010-12-11 02:17:57 +0000464 {
465 status=MagickFalse;
466 continue;
467 }
cristy343eee92010-12-11 02:17:57 +0000468 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
469 for (x=0; x < (ssize_t) image->columns; x++)
470 {
cristy3fc482f2011-09-23 00:43:35 +0000471 register ssize_t
472 i;
cristy343eee92010-12-11 02:17:57 +0000473
cristy3fc482f2011-09-23 00:43:35 +0000474 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
475 {
476 MagickRealType
477 distance;
478
479 PixelChannel
480 channel;
481
482 PixelTrait
483 reconstruct_traits,
484 traits;
485
cristye2a912b2011-12-05 20:02:07 +0000486 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000487 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000488 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
489 if ((traits == UndefinedPixelTrait) ||
490 (reconstruct_traits == UndefinedPixelTrait))
491 continue;
cristy49dd6a02011-09-24 23:08:01 +0000492 if ((reconstruct_traits & UpdatePixelTrait) == 0)
493 continue;
cristy0beccfa2011-09-25 20:47:53 +0000494 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
495 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000496 distance*=distance;
497 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000498 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000499 }
cristyed231572011-07-14 02:18:59 +0000500 p+=GetPixelChannels(image);
501 q+=GetPixelChannels(reconstruct_image);
cristy343eee92010-12-11 02:17:57 +0000502 }
503#if defined(MAGICKCORE_OPENMP_SUPPORT)
504 #pragma omp critical (MagickCore_GetMeanSquaredError)
505#endif
cristy3fc482f2011-09-23 00:43:35 +0000506 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000507 distortion[i]+=channel_distortion[i];
508 }
509 reconstruct_view=DestroyCacheView(reconstruct_view);
510 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000511 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000512 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000513 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
514 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
cristy343eee92010-12-11 02:17:57 +0000515 return(status);
516}
517
cristy3cc758f2010-11-27 01:33:49 +0000518static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000519 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000520{
cristyc4c8d132010-01-07 01:58:38 +0000521 CacheView
522 *image_view,
523 *reconstruct_view;
524
cristy3ed852e2009-09-05 21:47:34 +0000525 MagickBooleanType
526 status;
527
cristybb503372010-05-27 20:51:26 +0000528 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000529 i;
530
cristy9d314ff2011-03-09 01:30:28 +0000531 ssize_t
532 y;
533
cristy3ed852e2009-09-05 21:47:34 +0000534 status=MagickTrue;
535 image_view=AcquireCacheView(image);
536 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000537#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000538 #pragma omp parallel for schedule(static,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000539#endif
cristybb503372010-05-27 20:51:26 +0000540 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000541 {
542 double
cristy3fc482f2011-09-23 00:43:35 +0000543 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000544
cristy4c08aed2011-07-01 19:47:50 +0000545 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000546 *restrict p,
547 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000548
cristybb503372010-05-27 20:51:26 +0000549 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000550 i,
551 x;
552
553 if (status == MagickFalse)
554 continue;
555 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000556 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
557 1,exception);
558 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000559 {
560 status=MagickFalse;
561 continue;
562 }
cristy3ed852e2009-09-05 21:47:34 +0000563 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000564 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000565 {
cristy3fc482f2011-09-23 00:43:35 +0000566 register ssize_t
567 i;
cristy3ed852e2009-09-05 21:47:34 +0000568
cristy3fc482f2011-09-23 00:43:35 +0000569 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
570 {
571 MagickRealType
572 distance;
573
574 PixelChannel
575 channel;
576
577 PixelTrait
578 reconstruct_traits,
579 traits;
580
cristye2a912b2011-12-05 20:02:07 +0000581 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000582 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000583 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
584 if ((traits == UndefinedPixelTrait) ||
585 (reconstruct_traits == UndefinedPixelTrait))
586 continue;
cristy49dd6a02011-09-24 23:08:01 +0000587 if ((reconstruct_traits & UpdatePixelTrait) == 0)
588 continue;
cristy0beccfa2011-09-25 20:47:53 +0000589 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
590 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000591 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000592 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000593 }
cristyed231572011-07-14 02:18:59 +0000594 p+=GetPixelChannels(image);
595 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000596 }
cristyb5d5f722009-11-04 03:03:49 +0000597#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000598 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
599#endif
cristy3fc482f2011-09-23 00:43:35 +0000600 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000601 distortion[i]+=channel_distortion[i];
602 }
603 reconstruct_view=DestroyCacheView(reconstruct_view);
604 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000605 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000606 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000607 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000608 return(status);
609}
610
611static MagickBooleanType GetMeanErrorPerPixel(Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000612 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000613{
cristyc4c8d132010-01-07 01:58:38 +0000614 CacheView
615 *image_view,
616 *reconstruct_view;
617
cristy3ed852e2009-09-05 21:47:34 +0000618 MagickBooleanType
619 status;
620
621 MagickRealType
622 alpha,
623 area,
624 beta,
625 maximum_error,
626 mean_error;
627
cristy9d314ff2011-03-09 01:30:28 +0000628 ssize_t
629 y;
630
cristy3ed852e2009-09-05 21:47:34 +0000631 status=MagickTrue;
632 alpha=1.0;
633 beta=1.0;
634 area=0.0;
635 maximum_error=0.0;
636 mean_error=0.0;
637 image_view=AcquireCacheView(image);
638 reconstruct_view=AcquireCacheView(reconstruct_image);
cristybb503372010-05-27 20:51:26 +0000639 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000640 {
cristy4c08aed2011-07-01 19:47:50 +0000641 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000642 *restrict p,
643 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000644
cristybb503372010-05-27 20:51:26 +0000645 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000646 x;
647
648 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
649 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
650 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000651 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000652 {
653 status=MagickFalse;
654 break;
655 }
cristybb503372010-05-27 20:51:26 +0000656 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000657 {
cristy3fc482f2011-09-23 00:43:35 +0000658 register ssize_t
659 i;
cristy3ed852e2009-09-05 21:47:34 +0000660
cristy3fc482f2011-09-23 00:43:35 +0000661 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
662 {
663 MagickRealType
664 distance;
665
666 PixelChannel
667 channel;
668
669 PixelTrait
670 reconstruct_traits,
671 traits;
672
cristye2a912b2011-12-05 20:02:07 +0000673 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000674 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000675 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
676 if ((traits == UndefinedPixelTrait) ||
677 (reconstruct_traits == UndefinedPixelTrait))
678 continue;
cristy49dd6a02011-09-24 23:08:01 +0000679 if ((reconstruct_traits & UpdatePixelTrait) == 0)
680 continue;
cristy0beccfa2011-09-25 20:47:53 +0000681 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
682 reconstruct_image,channel,q)));
cristy3fc482f2011-09-23 00:43:35 +0000683 distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000684 distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000685 mean_error+=distance*distance;
686 if (distance > maximum_error)
687 maximum_error=distance;
688 area++;
689 }
cristyed231572011-07-14 02:18:59 +0000690 p+=GetPixelChannels(image);
691 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000692 }
693 }
694 reconstruct_view=DestroyCacheView(reconstruct_view);
695 image_view=DestroyCacheView(image_view);
cristy5f95f4f2011-10-23 01:01:01 +0000696 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
cristy3ed852e2009-09-05 21:47:34 +0000697 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
698 image->error.normalized_maximum_error=QuantumScale*maximum_error;
699 return(status);
700}
701
cristy3cc758f2010-11-27 01:33:49 +0000702static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000703 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000704{
cristyc4c8d132010-01-07 01:58:38 +0000705 CacheView
706 *image_view,
707 *reconstruct_view;
708
cristy3ed852e2009-09-05 21:47:34 +0000709 MagickBooleanType
710 status;
711
cristybb503372010-05-27 20:51:26 +0000712 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000713 i;
714
cristy9d314ff2011-03-09 01:30:28 +0000715 ssize_t
716 y;
717
cristy3ed852e2009-09-05 21:47:34 +0000718 status=MagickTrue;
719 image_view=AcquireCacheView(image);
720 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000721#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000722 #pragma omp parallel for schedule(static,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000723#endif
cristybb503372010-05-27 20:51:26 +0000724 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000725 {
726 double
cristy3fc482f2011-09-23 00:43:35 +0000727 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000728
cristy4c08aed2011-07-01 19:47:50 +0000729 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000730 *restrict p,
731 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000732
cristybb503372010-05-27 20:51:26 +0000733 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000734 i,
735 x;
736
737 if (status == MagickFalse)
738 continue;
739 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000740 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
741 1,exception);
742 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000743 {
744 status=MagickFalse;
745 continue;
746 }
cristy3ed852e2009-09-05 21:47:34 +0000747 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000748 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000749 {
cristy3fc482f2011-09-23 00:43:35 +0000750 register ssize_t
751 i;
cristy3ed852e2009-09-05 21:47:34 +0000752
cristy3fc482f2011-09-23 00:43:35 +0000753 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
754 {
755 MagickRealType
756 distance;
757
758 PixelChannel
759 channel;
760
761 PixelTrait
762 reconstruct_traits,
763 traits;
764
cristye2a912b2011-12-05 20:02:07 +0000765 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000766 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000767 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
768 if ((traits == UndefinedPixelTrait) ||
769 (reconstruct_traits == UndefinedPixelTrait))
770 continue;
cristy49dd6a02011-09-24 23:08:01 +0000771 if ((reconstruct_traits & UpdatePixelTrait) == 0)
772 continue;
cristy0beccfa2011-09-25 20:47:53 +0000773 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
774 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000775 distance*=distance;
776 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000777 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000778 }
cristyed231572011-07-14 02:18:59 +0000779 p+=GetPixelChannels(image);
780 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000781 }
cristyb5d5f722009-11-04 03:03:49 +0000782#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000783 #pragma omp critical (MagickCore_GetMeanSquaredError)
784#endif
cristy3fc482f2011-09-23 00:43:35 +0000785 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000786 distortion[i]+=channel_distortion[i];
787 }
788 reconstruct_view=DestroyCacheView(reconstruct_view);
789 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000790 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000791 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000792 distortion[CompositePixelChannel]/=GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000793 return(status);
794}
795
cristy3cc758f2010-11-27 01:33:49 +0000796static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
cristy8a9106f2011-07-05 14:39:26 +0000797 const Image *image,const Image *reconstruct_image,double *distortion,
798 ExceptionInfo *exception)
cristy4c929a72010-11-24 18:54:42 +0000799{
cristy9f48ca62010-11-25 03:06:31 +0000800#define SimilarityImageTag "Similarity/Image"
801
cristy4c929a72010-11-24 18:54:42 +0000802 CacheView
803 *image_view,
804 *reconstruct_view;
805
cristy9f48ca62010-11-25 03:06:31 +0000806 ChannelStatistics
cristy9f48ca62010-11-25 03:06:31 +0000807 *image_statistics,
808 *reconstruct_statistics;
809
cristy4c929a72010-11-24 18:54:42 +0000810 MagickBooleanType
811 status;
812
cristy18a41362010-11-27 15:56:18 +0000813 MagickOffsetType
814 progress;
815
cristy34d6fdc2010-11-26 19:06:08 +0000816 MagickRealType
817 area;
818
cristy4c929a72010-11-24 18:54:42 +0000819 register ssize_t
820 i;
821
cristy3cc758f2010-11-27 01:33:49 +0000822 ssize_t
823 y;
824
cristy34d6fdc2010-11-26 19:06:08 +0000825 /*
cristy18a41362010-11-27 15:56:18 +0000826 Normalize to account for variation due to lighting and exposure condition.
cristy34d6fdc2010-11-26 19:06:08 +0000827 */
cristyd42d9952011-07-08 14:21:50 +0000828 image_statistics=GetImageStatistics(image,exception);
829 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
cristy4c929a72010-11-24 18:54:42 +0000830 status=MagickTrue;
cristy9f48ca62010-11-25 03:06:31 +0000831 progress=0;
cristy3fc482f2011-09-23 00:43:35 +0000832 for (i=0; i <= MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000833 distortion[i]=0.0;
cristye28f7af2011-10-17 18:21:57 +0000834 area=1.0/((MagickRealType) image->columns*image->rows-1);
cristy4c929a72010-11-24 18:54:42 +0000835 image_view=AcquireCacheView(image);
836 reconstruct_view=AcquireCacheView(reconstruct_image);
cristy4c929a72010-11-24 18:54:42 +0000837 for (y=0; y < (ssize_t) image->rows; y++)
838 {
cristy4c08aed2011-07-01 19:47:50 +0000839 register const Quantum
cristy4c929a72010-11-24 18:54:42 +0000840 *restrict p,
841 *restrict q;
842
843 register ssize_t
cristy4c929a72010-11-24 18:54:42 +0000844 x;
845
846 if (status == MagickFalse)
847 continue;
848 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy34d6fdc2010-11-26 19:06:08 +0000849 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
850 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000851 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy4c929a72010-11-24 18:54:42 +0000852 {
853 status=MagickFalse;
854 continue;
855 }
cristy4c929a72010-11-24 18:54:42 +0000856 for (x=0; x < (ssize_t) image->columns; x++)
857 {
cristy3fc482f2011-09-23 00:43:35 +0000858 register ssize_t
859 i;
860
861 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
862 {
863 PixelChannel
864 channel;
865
866 PixelTrait
867 reconstruct_traits,
868 traits;
869
cristye2a912b2011-12-05 20:02:07 +0000870 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000871 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000872 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
873 if ((traits == UndefinedPixelTrait) ||
874 (reconstruct_traits == UndefinedPixelTrait))
875 continue;
cristy49dd6a02011-09-24 23:08:01 +0000876 if ((reconstruct_traits & UpdatePixelTrait) == 0)
877 continue;
cristy3fc482f2011-09-23 00:43:35 +0000878 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
cristy0beccfa2011-09-25 20:47:53 +0000879 (GetPixelChannel(reconstruct_image,channel,q)-
880 reconstruct_statistics[channel].mean);
cristy3fc482f2011-09-23 00:43:35 +0000881 }
cristyed231572011-07-14 02:18:59 +0000882 p+=GetPixelChannels(image);
883 q+=GetPixelChannels(image);
cristy4c929a72010-11-24 18:54:42 +0000884 }
cristy9f48ca62010-11-25 03:06:31 +0000885 if (image->progress_monitor != (MagickProgressMonitor) NULL)
886 {
887 MagickBooleanType
888 proceed;
889
cristy9f48ca62010-11-25 03:06:31 +0000890 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
891 image->rows);
892 if (proceed == MagickFalse)
893 status=MagickFalse;
894 }
cristy4c929a72010-11-24 18:54:42 +0000895 }
896 reconstruct_view=DestroyCacheView(reconstruct_view);
897 image_view=DestroyCacheView(image_view);
cristy34d6fdc2010-11-26 19:06:08 +0000898 /*
899 Divide by the standard deviation.
900 */
cristy5f95f4f2011-10-23 01:01:01 +0000901 distortion[CompositePixelChannel]=0.0;
cristy3fc482f2011-09-23 00:43:35 +0000902 for (i=0; i < MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000903 {
904 MagickRealType
cristy18a41362010-11-27 15:56:18 +0000905 gamma;
cristy34d6fdc2010-11-26 19:06:08 +0000906
cristy3fc482f2011-09-23 00:43:35 +0000907 PixelChannel
908 channel;
909
cristye2a912b2011-12-05 20:02:07 +0000910 channel=GetPixelChannelMapChannel(image,i);
cristy18a41362010-11-27 15:56:18 +0000911 gamma=image_statistics[i].standard_deviation*
cristy3fc482f2011-09-23 00:43:35 +0000912 reconstruct_statistics[channel].standard_deviation;
cristy18a41362010-11-27 15:56:18 +0000913 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
914 distortion[i]=QuantumRange*gamma*distortion[i];
cristy5f95f4f2011-10-23 01:01:01 +0000915 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
cristy34d6fdc2010-11-26 19:06:08 +0000916 }
cristy5f95f4f2011-10-23 01:01:01 +0000917 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
cristy49dd6a02011-09-24 23:08:01 +0000918 GetImageChannels(image));
cristy34d6fdc2010-11-26 19:06:08 +0000919 /*
920 Free resources.
921 */
cristy9f48ca62010-11-25 03:06:31 +0000922 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
923 reconstruct_statistics);
924 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
925 image_statistics);
cristy4c929a72010-11-24 18:54:42 +0000926 return(status);
927}
928
cristy3cc758f2010-11-27 01:33:49 +0000929static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000930 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000931{
cristyc4c8d132010-01-07 01:58:38 +0000932 CacheView
933 *image_view,
934 *reconstruct_view;
935
cristy3ed852e2009-09-05 21:47:34 +0000936 MagickBooleanType
937 status;
938
cristy9d314ff2011-03-09 01:30:28 +0000939 ssize_t
940 y;
941
cristy3ed852e2009-09-05 21:47:34 +0000942 status=MagickTrue;
943 image_view=AcquireCacheView(image);
944 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000945#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000946 #pragma omp parallel for schedule(static,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000947#endif
cristybb503372010-05-27 20:51:26 +0000948 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000949 {
950 double
cristy3fc482f2011-09-23 00:43:35 +0000951 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000952
cristy4c08aed2011-07-01 19:47:50 +0000953 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000954 *restrict p,
955 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000956
cristybb503372010-05-27 20:51:26 +0000957 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000958 i,
959 x;
960
961 if (status == MagickFalse)
962 continue;
963 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
964 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
965 reconstruct_image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000966 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000967 {
968 status=MagickFalse;
969 continue;
970 }
cristy3ed852e2009-09-05 21:47:34 +0000971 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000972 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000973 {
cristy3fc482f2011-09-23 00:43:35 +0000974 register ssize_t
975 i;
cristy3ed852e2009-09-05 21:47:34 +0000976
cristy3fc482f2011-09-23 00:43:35 +0000977 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
978 {
979 MagickRealType
980 distance;
981
982 PixelChannel
983 channel;
984
985 PixelTrait
986 reconstruct_traits,
987 traits;
988
cristye2a912b2011-12-05 20:02:07 +0000989 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000990 traits=GetPixelChannelMapTraits(image,channel);
cristy3fc482f2011-09-23 00:43:35 +0000991 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
992 if ((traits == UndefinedPixelTrait) ||
993 (reconstruct_traits == UndefinedPixelTrait))
994 continue;
cristy49dd6a02011-09-24 23:08:01 +0000995 if ((reconstruct_traits & UpdatePixelTrait) == 0)
996 continue;
cristy0beccfa2011-09-25 20:47:53 +0000997 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
998 reconstruct_image,channel,q));
cristy25a5f2f2011-09-24 14:09:43 +0000999 if (distance > channel_distortion[i])
cristy3fc482f2011-09-23 00:43:35 +00001000 channel_distortion[i]=distance;
cristy5f95f4f2011-10-23 01:01:01 +00001001 if (distance > channel_distortion[CompositePixelChannel])
1002 channel_distortion[CompositePixelChannel]=distance;
cristy3fc482f2011-09-23 00:43:35 +00001003 }
cristyed231572011-07-14 02:18:59 +00001004 p+=GetPixelChannels(image);
1005 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001006 }
cristyb5d5f722009-11-04 03:03:49 +00001007#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001008 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1009#endif
cristy3fc482f2011-09-23 00:43:35 +00001010 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001011 if (channel_distortion[i] > distortion[i])
1012 distortion[i]=channel_distortion[i];
1013 }
1014 reconstruct_view=DestroyCacheView(reconstruct_view);
1015 image_view=DestroyCacheView(image_view);
1016 return(status);
1017}
1018
1019static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001020 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001021{
1022 MagickBooleanType
1023 status;
1024
cristy3fc482f2011-09-23 00:43:35 +00001025 register ssize_t
1026 i;
1027
cristy8a9106f2011-07-05 14:39:26 +00001028 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001029 for (i=0; i <= MaxPixelChannels; i++)
1030 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
cristy3ed852e2009-09-05 21:47:34 +00001031 return(status);
1032}
1033
cristy3cc758f2010-11-27 01:33:49 +00001034static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001035 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001036{
1037 MagickBooleanType
1038 status;
1039
cristy3fc482f2011-09-23 00:43:35 +00001040 register ssize_t
1041 i;
1042
cristy8a9106f2011-07-05 14:39:26 +00001043 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001044 for (i=0; i <= MaxPixelChannels; i++)
1045 distortion[i]=sqrt(distortion[i]);
cristy3ed852e2009-09-05 21:47:34 +00001046 return(status);
1047}
1048
cristy8a9106f2011-07-05 14:39:26 +00001049MagickExport MagickBooleanType GetImageDistortion(Image *image,
1050 const Image *reconstruct_image,const MetricType metric,double *distortion,
1051 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001052{
1053 double
1054 *channel_distortion;
1055
1056 MagickBooleanType
1057 status;
1058
1059 size_t
1060 length;
1061
1062 assert(image != (Image *) NULL);
1063 assert(image->signature == MagickSignature);
1064 if (image->debug != MagickFalse)
1065 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1066 assert(reconstruct_image != (const Image *) NULL);
1067 assert(reconstruct_image->signature == MagickSignature);
1068 assert(distortion != (double *) NULL);
1069 *distortion=0.0;
1070 if (image->debug != MagickFalse)
1071 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1072 if ((reconstruct_image->columns != image->columns) ||
1073 (reconstruct_image->rows != image->rows))
1074 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1075 /*
1076 Get image distortion.
1077 */
cristy3fc482f2011-09-23 00:43:35 +00001078 length=MaxPixelChannels+1;
cristy3ed852e2009-09-05 21:47:34 +00001079 channel_distortion=(double *) AcquireQuantumMemory(length,
1080 sizeof(*channel_distortion));
1081 if (channel_distortion == (double *) NULL)
1082 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1083 (void) ResetMagickMemory(channel_distortion,0,length*
1084 sizeof(*channel_distortion));
1085 switch (metric)
1086 {
1087 case AbsoluteErrorMetric:
1088 {
cristy8a9106f2011-07-05 14:39:26 +00001089 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1090 exception);
cristy3ed852e2009-09-05 21:47:34 +00001091 break;
1092 }
cristy343eee92010-12-11 02:17:57 +00001093 case FuzzErrorMetric:
1094 {
cristy8a9106f2011-07-05 14:39:26 +00001095 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1096 exception);
cristy343eee92010-12-11 02:17:57 +00001097 break;
1098 }
cristy3ed852e2009-09-05 21:47:34 +00001099 case MeanAbsoluteErrorMetric:
1100 {
cristy8a9106f2011-07-05 14:39:26 +00001101 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001102 channel_distortion,exception);
1103 break;
1104 }
1105 case MeanErrorPerPixelMetric:
1106 {
cristy8a9106f2011-07-05 14:39:26 +00001107 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1108 exception);
cristy3ed852e2009-09-05 21:47:34 +00001109 break;
1110 }
1111 case MeanSquaredErrorMetric:
1112 {
cristy8a9106f2011-07-05 14:39:26 +00001113 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001114 channel_distortion,exception);
1115 break;
1116 }
cristy4c929a72010-11-24 18:54:42 +00001117 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001118 default:
cristy4c929a72010-11-24 18:54:42 +00001119 {
cristy3cc758f2010-11-27 01:33:49 +00001120 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001121 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001122 break;
1123 }
cristy3ed852e2009-09-05 21:47:34 +00001124 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001125 {
cristy8a9106f2011-07-05 14:39:26 +00001126 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001127 channel_distortion,exception);
1128 break;
1129 }
1130 case PeakSignalToNoiseRatioMetric:
1131 {
cristy8a9106f2011-07-05 14:39:26 +00001132 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001133 channel_distortion,exception);
1134 break;
1135 }
1136 case RootMeanSquaredErrorMetric:
1137 {
cristy8a9106f2011-07-05 14:39:26 +00001138 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001139 channel_distortion,exception);
1140 break;
1141 }
1142 }
cristy5f95f4f2011-10-23 01:01:01 +00001143 *distortion=channel_distortion[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001144 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1145 return(status);
1146}
1147
1148/*
1149%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1150% %
1151% %
1152% %
cristy8a9106f2011-07-05 14:39:26 +00001153% 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 +00001154% %
1155% %
1156% %
1157%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1158%
cristy8a9106f2011-07-05 14:39:26 +00001159% GetImageDistrortion() compares the pixel channels of an image to a
cristy3ed852e2009-09-05 21:47:34 +00001160% reconstructed image and returns the specified distortion metric for each
1161% channel.
1162%
cristy8a9106f2011-07-05 14:39:26 +00001163% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +00001164%
cristy8a9106f2011-07-05 14:39:26 +00001165% double *GetImageDistortions(const Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001166% const Image *reconstruct_image,const MetricType metric,
1167% ExceptionInfo *exception)
1168%
1169% A description of each parameter follows:
1170%
1171% o image: the image.
1172%
1173% o reconstruct_image: the reconstruct image.
1174%
1175% o metric: the metric.
1176%
1177% o exception: return any errors or warnings in this structure.
1178%
1179*/
cristy8a9106f2011-07-05 14:39:26 +00001180MagickExport double *GetImageDistortions(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001181 const Image *reconstruct_image,const MetricType metric,
1182 ExceptionInfo *exception)
1183{
1184 double
1185 *channel_distortion;
1186
1187 MagickBooleanType
1188 status;
1189
1190 size_t
1191 length;
1192
1193 assert(image != (Image *) NULL);
1194 assert(image->signature == MagickSignature);
1195 if (image->debug != MagickFalse)
1196 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1197 assert(reconstruct_image != (const Image *) NULL);
1198 assert(reconstruct_image->signature == MagickSignature);
1199 if (image->debug != MagickFalse)
1200 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1201 if ((reconstruct_image->columns != image->columns) ||
1202 (reconstruct_image->rows != image->rows))
1203 {
cristyc82a27b2011-10-21 01:07:16 +00001204 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1205 "ImageSizeDiffers","`%s'",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001206 return((double *) NULL);
1207 }
1208 /*
1209 Get image distortion.
1210 */
cristy3fc482f2011-09-23 00:43:35 +00001211 length=MaxPixelChannels+1UL;
cristy3ed852e2009-09-05 21:47:34 +00001212 channel_distortion=(double *) AcquireQuantumMemory(length,
1213 sizeof(*channel_distortion));
1214 if (channel_distortion == (double *) NULL)
1215 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1216 (void) ResetMagickMemory(channel_distortion,0,length*
1217 sizeof(*channel_distortion));
cristyda16f162011-02-19 23:52:17 +00001218 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +00001219 switch (metric)
1220 {
1221 case AbsoluteErrorMetric:
1222 {
cristy8a9106f2011-07-05 14:39:26 +00001223 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1224 exception);
cristy3ed852e2009-09-05 21:47:34 +00001225 break;
1226 }
cristy343eee92010-12-11 02:17:57 +00001227 case FuzzErrorMetric:
1228 {
cristy8a9106f2011-07-05 14:39:26 +00001229 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1230 exception);
cristy343eee92010-12-11 02:17:57 +00001231 break;
1232 }
cristy3ed852e2009-09-05 21:47:34 +00001233 case MeanAbsoluteErrorMetric:
1234 {
cristy8a9106f2011-07-05 14:39:26 +00001235 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001236 channel_distortion,exception);
1237 break;
1238 }
1239 case MeanErrorPerPixelMetric:
1240 {
cristy8a9106f2011-07-05 14:39:26 +00001241 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1242 exception);
cristy3ed852e2009-09-05 21:47:34 +00001243 break;
1244 }
1245 case MeanSquaredErrorMetric:
1246 {
cristy8a9106f2011-07-05 14:39:26 +00001247 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001248 channel_distortion,exception);
1249 break;
1250 }
cristy4c929a72010-11-24 18:54:42 +00001251 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001252 default:
cristy4c929a72010-11-24 18:54:42 +00001253 {
cristy3cc758f2010-11-27 01:33:49 +00001254 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001255 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001256 break;
1257 }
cristy3ed852e2009-09-05 21:47:34 +00001258 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001259 {
cristy8a9106f2011-07-05 14:39:26 +00001260 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001261 channel_distortion,exception);
1262 break;
1263 }
1264 case PeakSignalToNoiseRatioMetric:
1265 {
cristy8a9106f2011-07-05 14:39:26 +00001266 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001267 channel_distortion,exception);
1268 break;
1269 }
1270 case RootMeanSquaredErrorMetric:
1271 {
cristy8a9106f2011-07-05 14:39:26 +00001272 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001273 channel_distortion,exception);
1274 break;
1275 }
1276 }
cristyda16f162011-02-19 23:52:17 +00001277 if (status == MagickFalse)
1278 {
1279 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1280 return((double *) NULL);
1281 }
cristy3ed852e2009-09-05 21:47:34 +00001282 return(channel_distortion);
1283}
1284
1285/*
1286%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1287% %
1288% %
1289% %
1290% I s I m a g e s E q u a l %
1291% %
1292% %
1293% %
1294%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1295%
1296% IsImagesEqual() measures the difference between colors at each pixel
1297% location of two images. A value other than 0 means the colors match
1298% exactly. Otherwise an error measure is computed by summing over all
1299% pixels in an image the distance squared in RGB space between each image
1300% pixel and its corresponding pixel in the reconstruct image. The error
1301% measure is assigned to these image members:
1302%
1303% o mean_error_per_pixel: The mean error for any single pixel in
1304% the image.
1305%
1306% o normalized_mean_error: The normalized mean quantization error for
1307% any single pixel in the image. This distance measure is normalized to
1308% a range between 0 and 1. It is independent of the range of red, green,
1309% and blue values in the image.
1310%
1311% o normalized_maximum_error: The normalized maximum quantization
1312% error for any single pixel in the image. This distance measure is
1313% normalized to a range between 0 and 1. It is independent of the range
1314% of red, green, and blue values in your image.
1315%
1316% A small normalized mean square error, accessed as
1317% image->normalized_mean_error, suggests the images are very similar in
1318% spatial layout and color.
1319%
1320% The format of the IsImagesEqual method is:
1321%
1322% MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001323% const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001324%
1325% A description of each parameter follows.
1326%
1327% o image: the image.
1328%
1329% o reconstruct_image: the reconstruct image.
1330%
cristy018f07f2011-09-04 21:15:19 +00001331% o exception: return any errors or warnings in this structure.
1332%
cristy3ed852e2009-09-05 21:47:34 +00001333*/
1334MagickExport MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001335 const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001336{
cristyc4c8d132010-01-07 01:58:38 +00001337 CacheView
1338 *image_view,
1339 *reconstruct_view;
1340
cristy3ed852e2009-09-05 21:47:34 +00001341 MagickBooleanType
1342 status;
1343
1344 MagickRealType
1345 area,
1346 maximum_error,
1347 mean_error,
1348 mean_error_per_pixel;
1349
cristy9d314ff2011-03-09 01:30:28 +00001350 ssize_t
1351 y;
1352
cristy3ed852e2009-09-05 21:47:34 +00001353 assert(image != (Image *) NULL);
1354 assert(image->signature == MagickSignature);
1355 assert(reconstruct_image != (const Image *) NULL);
1356 assert(reconstruct_image->signature == MagickSignature);
1357 if ((reconstruct_image->columns != image->columns) ||
1358 (reconstruct_image->rows != image->rows))
1359 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1360 area=0.0;
1361 maximum_error=0.0;
1362 mean_error_per_pixel=0.0;
1363 mean_error=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001364 image_view=AcquireCacheView(image);
1365 reconstruct_view=AcquireCacheView(reconstruct_image);
cristybb503372010-05-27 20:51:26 +00001366 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001367 {
cristy4c08aed2011-07-01 19:47:50 +00001368 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001369 *restrict p,
1370 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001371
cristybb503372010-05-27 20:51:26 +00001372 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001373 x;
1374
1375 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1376 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1377 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001378 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001379 break;
cristybb503372010-05-27 20:51:26 +00001380 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001381 {
cristyd5c15f92011-09-23 00:58:33 +00001382 register ssize_t
1383 i;
cristy3ed852e2009-09-05 21:47:34 +00001384
cristyd5c15f92011-09-23 00:58:33 +00001385 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1386 {
1387 MagickRealType
1388 distance;
1389
1390 PixelChannel
1391 channel;
1392
1393 PixelTrait
1394 reconstruct_traits,
1395 traits;
1396
cristye2a912b2011-12-05 20:02:07 +00001397 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001398 traits=GetPixelChannelMapTraits(image,channel);
cristyd5c15f92011-09-23 00:58:33 +00001399 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1400 if ((traits == UndefinedPixelTrait) ||
1401 (reconstruct_traits == UndefinedPixelTrait))
1402 continue;
cristy49dd6a02011-09-24 23:08:01 +00001403 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1404 continue;
cristy0beccfa2011-09-25 20:47:53 +00001405 distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
1406 channel,q));
cristyd5c15f92011-09-23 00:58:33 +00001407 mean_error_per_pixel+=distance;
1408 mean_error+=distance*distance;
1409 if (distance > maximum_error)
1410 maximum_error=distance;
1411 area++;
1412 }
cristyed231572011-07-14 02:18:59 +00001413 p+=GetPixelChannels(image);
1414 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001415 }
1416 }
1417 reconstruct_view=DestroyCacheView(reconstruct_view);
1418 image_view=DestroyCacheView(image_view);
1419 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1420 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1421 mean_error/area);
1422 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1423 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1424 return(status);
1425}
1426
1427/*
1428%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1429% %
1430% %
1431% %
1432% S i m i l a r i t y I m a g e %
1433% %
1434% %
1435% %
1436%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1437%
1438% SimilarityImage() compares the reference image of the image and returns the
1439% best match offset. In addition, it returns a similarity image such that an
1440% exact match location is completely white and if none of the pixels match,
1441% black, otherwise some gray level in-between.
1442%
1443% The format of the SimilarityImageImage method is:
1444%
1445% Image *SimilarityImage(const Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001446% const MetricType metric,RectangleInfo *offset,double *similarity,
1447% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001448%
1449% A description of each parameter follows:
1450%
1451% o image: the image.
1452%
1453% o reference: find an area of the image that closely resembles this image.
1454%
cristy09136812011-10-18 15:24:30 +00001455% o metric: the metric.
1456%
cristy3ed852e2009-09-05 21:47:34 +00001457% o the best match offset of the reference image within the image.
1458%
1459% o similarity: the computed similarity between the images.
1460%
1461% o exception: return any errors or warnings in this structure.
1462%
1463*/
1464
1465static double GetSimilarityMetric(const Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001466 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1467 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001468{
1469 double
cristy3cc758f2010-11-27 01:33:49 +00001470 distortion;
cristy3ed852e2009-09-05 21:47:34 +00001471
cristy713ff212010-11-26 21:56:11 +00001472 Image
1473 *similarity_image;
cristy3ed852e2009-09-05 21:47:34 +00001474
cristy09136812011-10-18 15:24:30 +00001475 MagickBooleanType
1476 status;
1477
cristy713ff212010-11-26 21:56:11 +00001478 RectangleInfo
1479 geometry;
cristy3ed852e2009-09-05 21:47:34 +00001480
cristy713ff212010-11-26 21:56:11 +00001481 SetGeometry(reference,&geometry);
1482 geometry.x=x_offset;
1483 geometry.y=y_offset;
1484 similarity_image=CropImage(image,&geometry,exception);
1485 if (similarity_image == (Image *) NULL)
1486 return(0.0);
cristy09136812011-10-18 15:24:30 +00001487 distortion=0.0;
1488 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
cristy3cc758f2010-11-27 01:33:49 +00001489 exception);
cristy713ff212010-11-26 21:56:11 +00001490 similarity_image=DestroyImage(similarity_image);
cristy09136812011-10-18 15:24:30 +00001491 if (status == MagickFalse)
1492 return(0.0);
cristy3cc758f2010-11-27 01:33:49 +00001493 return(distortion);
cristy3ed852e2009-09-05 21:47:34 +00001494}
1495
1496MagickExport Image *SimilarityImage(Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001497 const MetricType metric,RectangleInfo *offset,double *similarity_metric,
1498 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001499{
1500#define SimilarityImageTag "Similarity/Image"
1501
cristyc4c8d132010-01-07 01:58:38 +00001502 CacheView
1503 *similarity_view;
1504
cristy3ed852e2009-09-05 21:47:34 +00001505 Image
1506 *similarity_image;
1507
1508 MagickBooleanType
1509 status;
1510
cristybb503372010-05-27 20:51:26 +00001511 MagickOffsetType
1512 progress;
1513
1514 ssize_t
1515 y;
1516
cristy3ed852e2009-09-05 21:47:34 +00001517 assert(image != (const Image *) NULL);
1518 assert(image->signature == MagickSignature);
1519 if (image->debug != MagickFalse)
1520 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1521 assert(exception != (ExceptionInfo *) NULL);
1522 assert(exception->signature == MagickSignature);
1523 assert(offset != (RectangleInfo *) NULL);
1524 SetGeometry(reference,offset);
1525 *similarity_metric=1.0;
cristyf9255b92010-08-14 22:59:00 +00001526 if ((reference->columns > image->columns) || (reference->rows > image->rows))
cristy3ed852e2009-09-05 21:47:34 +00001527 ThrowImageException(ImageError,"ImageSizeDiffers");
1528 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1529 image->rows-reference->rows+1,MagickTrue,exception);
1530 if (similarity_image == (Image *) NULL)
1531 return((Image *) NULL);
cristyd5c15f92011-09-23 00:58:33 +00001532 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1533 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001534 {
cristy3ed852e2009-09-05 21:47:34 +00001535 similarity_image=DestroyImage(similarity_image);
1536 return((Image *) NULL);
1537 }
1538 /*
1539 Measure similarity of reference image against image.
1540 */
1541 status=MagickTrue;
1542 progress=0;
1543 similarity_view=AcquireCacheView(similarity_image);
cristyb5d5f722009-11-04 03:03:49 +00001544#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001545 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001546#endif
cristybb503372010-05-27 20:51:26 +00001547 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
cristy3ed852e2009-09-05 21:47:34 +00001548 {
1549 double
1550 similarity;
1551
cristy4c08aed2011-07-01 19:47:50 +00001552 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001553 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001554
cristy49dd6a02011-09-24 23:08:01 +00001555 register ssize_t
1556 x;
1557
cristy3ed852e2009-09-05 21:47:34 +00001558 if (status == MagickFalse)
1559 continue;
cristy3cc758f2010-11-27 01:33:49 +00001560 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1561 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001562 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001563 {
1564 status=MagickFalse;
1565 continue;
1566 }
cristybb503372010-05-27 20:51:26 +00001567 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
cristy3ed852e2009-09-05 21:47:34 +00001568 {
cristy49dd6a02011-09-24 23:08:01 +00001569 register ssize_t
1570 i;
1571
cristy09136812011-10-18 15:24:30 +00001572 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
cristyb5d5f722009-11-04 03:03:49 +00001573#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001574 #pragma omp critical (MagickCore_SimilarityImage)
1575#endif
1576 if (similarity < *similarity_metric)
1577 {
1578 *similarity_metric=similarity;
1579 offset->x=x;
1580 offset->y=y;
1581 }
cristy49dd6a02011-09-24 23:08:01 +00001582 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1583 {
1584 PixelChannel
1585 channel;
1586
1587 PixelTrait
1588 similarity_traits,
1589 traits;
1590
cristye2a912b2011-12-05 20:02:07 +00001591 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001592 traits=GetPixelChannelMapTraits(image,channel);
cristy49dd6a02011-09-24 23:08:01 +00001593 similarity_traits=GetPixelChannelMapTraits(similarity_image,channel);
1594 if ((traits == UndefinedPixelTrait) ||
1595 (similarity_traits == UndefinedPixelTrait))
1596 continue;
1597 if ((similarity_traits & UpdatePixelTrait) == 0)
1598 continue;
cristy0beccfa2011-09-25 20:47:53 +00001599 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1600 QuantumRange*similarity),q);
cristy49dd6a02011-09-24 23:08:01 +00001601 }
cristyed231572011-07-14 02:18:59 +00001602 q+=GetPixelChannels(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001603 }
1604 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1605 status=MagickFalse;
1606 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1607 {
1608 MagickBooleanType
1609 proceed;
1610
cristyb5d5f722009-11-04 03:03:49 +00001611#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001612 #pragma omp critical (MagickCore_SimilarityImage)
1613#endif
1614 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1615 image->rows);
1616 if (proceed == MagickFalse)
1617 status=MagickFalse;
1618 }
1619 }
1620 similarity_view=DestroyCacheView(similarity_view);
cristy3ed852e2009-09-05 21:47:34 +00001621 return(similarity_image);
1622}