blob: 5909207bcff74a0a90928263b07347076bbd6c33 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% CCCC OOO M M PPPP AAA RRRR EEEEE %
7% C O O MM MM P P A A R R E %
8% C O O M M M PPPP AAAAA RRRR EEE %
9% C O O M M P A A R R E %
10% CCCC OOO M M P A A R R EEEEE %
11% %
12% %
13% MagickCore Image Comparison Methods %
14% %
15% Software Design %
16% John Cristy %
17% December 2003 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/artifact.h"
45#include "MagickCore/cache-view.h"
46#include "MagickCore/client.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/colorspace-private.h"
51#include "MagickCore/compare.h"
52#include "MagickCore/composite-private.h"
53#include "MagickCore/constitute.h"
54#include "MagickCore/exception-private.h"
55#include "MagickCore/geometry.h"
56#include "MagickCore/image-private.h"
57#include "MagickCore/list.h"
58#include "MagickCore/log.h"
59#include "MagickCore/memory_.h"
60#include "MagickCore/monitor.h"
61#include "MagickCore/monitor-private.h"
62#include "MagickCore/option.h"
63#include "MagickCore/pixel-accessor.h"
64#include "MagickCore/resource_.h"
65#include "MagickCore/string_.h"
66#include "MagickCore/statistic.h"
67#include "MagickCore/transform.h"
68#include "MagickCore/utility.h"
69#include "MagickCore/version.h"
cristy3ed852e2009-09-05 21:47:34 +000070
71/*
72%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
73% %
74% %
75% %
cristy8a9106f2011-07-05 14:39:26 +000076% C o m p a r e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +000077% %
78% %
79% %
80%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
81%
cristy8a9106f2011-07-05 14:39:26 +000082% CompareImages() compares one or more pixel channels of an image to a
83% reconstructed image and returns the difference image.
cristy3ed852e2009-09-05 21:47:34 +000084%
cristy8a9106f2011-07-05 14:39:26 +000085% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +000086%
cristy8a9106f2011-07-05 14:39:26 +000087% Image *CompareImages(const Image *image,const Image *reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +000088% const MetricType metric,double *distortion,ExceptionInfo *exception)
89%
90% A description of each parameter follows:
91%
92% o image: the image.
93%
94% o reconstruct_image: the reconstruct image.
95%
cristy3ed852e2009-09-05 21:47:34 +000096% o metric: the metric.
97%
98% o distortion: the computed distortion between the images.
99%
100% o exception: return any errors or warnings in this structure.
101%
102*/
cristy3ed852e2009-09-05 21:47:34 +0000103MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
104 const MetricType metric,double *distortion,ExceptionInfo *exception)
105{
cristyc4c8d132010-01-07 01:58:38 +0000106 CacheView
107 *highlight_view,
108 *image_view,
109 *reconstruct_view;
110
cristy3ed852e2009-09-05 21:47:34 +0000111 const char
112 *artifact;
113
114 Image
115 *difference_image,
116 *highlight_image;
117
cristy3ed852e2009-09-05 21:47:34 +0000118 MagickBooleanType
119 status;
120
cristy4c08aed2011-07-01 19:47:50 +0000121 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000122 highlight,
cristy3fc482f2011-09-23 00:43:35 +0000123 lowlight;
cristy3ed852e2009-09-05 21:47:34 +0000124
cristy49dd6a02011-09-24 23:08:01 +0000125 ssize_t
126 y;
127
cristy3ed852e2009-09-05 21:47:34 +0000128 assert(image != (Image *) NULL);
129 assert(image->signature == MagickSignature);
130 if (image->debug != MagickFalse)
131 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
132 assert(reconstruct_image != (const Image *) NULL);
133 assert(reconstruct_image->signature == MagickSignature);
134 assert(distortion != (double *) NULL);
135 *distortion=0.0;
136 if (image->debug != MagickFalse)
137 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
138 if ((reconstruct_image->columns != image->columns) ||
139 (reconstruct_image->rows != image->rows))
140 ThrowImageException(ImageError,"ImageSizeDiffers");
cristy8a9106f2011-07-05 14:39:26 +0000141 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
142 exception);
cristy3ed852e2009-09-05 21:47:34 +0000143 if (status == MagickFalse)
144 return((Image *) NULL);
145 difference_image=CloneImage(image,0,0,MagickTrue,exception);
146 if (difference_image == (Image *) NULL)
147 return((Image *) NULL);
cristy63240882011-08-05 19:05:27 +0000148 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +0000149 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
150 exception);
151 if (highlight_image == (Image *) NULL)
152 {
153 difference_image=DestroyImage(difference_image);
154 return((Image *) NULL);
155 }
cristy3fc482f2011-09-23 00:43:35 +0000156 status=SetImageStorageClass(highlight_image,DirectClass,exception);
157 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000158 {
cristy3ed852e2009-09-05 21:47:34 +0000159 difference_image=DestroyImage(difference_image);
160 highlight_image=DestroyImage(highlight_image);
161 return((Image *) NULL);
162 }
cristy63240882011-08-05 19:05:27 +0000163 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
cristy269c9412011-10-13 23:41:15 +0000164 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,
cristy9950d572011-10-01 18:22:35 +0000165 exception);
cristy3ed852e2009-09-05 21:47:34 +0000166 artifact=GetImageArtifact(image,"highlight-color");
167 if (artifact != (const char *) NULL)
cristy269c9412011-10-13 23:41:15 +0000168 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,
cristy9950d572011-10-01 18:22:35 +0000169 exception);
cristy269c9412011-10-13 23:41:15 +0000170 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,
cristy9950d572011-10-01 18:22:35 +0000171 exception);
cristy3ed852e2009-09-05 21:47:34 +0000172 artifact=GetImageArtifact(image,"lowlight-color");
173 if (artifact != (const char *) NULL)
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)
183 #pragma omp parallel for schedule(dynamic,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 traits=GetPixelChannelMapTraits(image,i);
235 channel=GetPixelChannelMapChannel(image,i);
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)
324 #pragma omp parallel for schedule(dynamic,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 traits=GetPixelChannelMapTraits(image,i);
369 channel=GetPixelChannelMapChannel(image,i);
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 {
409 PixelTrait
410 traits;
411
cristye2a912b2011-12-05 20:02:07 +0000412 traits=GetPixelChannelMapTraits(image,i);
cristy25a5f2f2011-09-24 14:09:43 +0000413 if ((traits & UpdatePixelTrait) != 0)
cristy3fc482f2011-09-23 00:43:35 +0000414 channels++;
415 }
cristy3ed852e2009-09-05 21:47:34 +0000416 return(channels);
417}
418
cristy343eee92010-12-11 02:17:57 +0000419static MagickBooleanType GetFuzzDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000420 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy343eee92010-12-11 02:17:57 +0000421{
422 CacheView
423 *image_view,
424 *reconstruct_view;
425
cristy343eee92010-12-11 02:17:57 +0000426 MagickBooleanType
427 status;
428
429 register ssize_t
430 i;
431
cristy9d314ff2011-03-09 01:30:28 +0000432 ssize_t
433 y;
434
cristy343eee92010-12-11 02:17:57 +0000435 status=MagickTrue;
436 image_view=AcquireCacheView(image);
437 reconstruct_view=AcquireCacheView(reconstruct_image);
438#if defined(MAGICKCORE_OPENMP_SUPPORT)
439 #pragma omp parallel for schedule(dynamic,4) shared(status)
440#endif
441 for (y=0; y < (ssize_t) image->rows; y++)
442 {
443 double
cristy3fc482f2011-09-23 00:43:35 +0000444 channel_distortion[MaxPixelChannels+1];
cristy343eee92010-12-11 02:17:57 +0000445
cristy4c08aed2011-07-01 19:47:50 +0000446 register const Quantum
cristy343eee92010-12-11 02:17:57 +0000447 *restrict p,
448 *restrict q;
449
450 register ssize_t
451 i,
452 x;
453
454 if (status == MagickFalse)
455 continue;
456 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristye6529ff2011-02-04 20:05:32 +0000457 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
458 1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000459 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy343eee92010-12-11 02:17:57 +0000460 {
461 status=MagickFalse;
462 continue;
463 }
cristy343eee92010-12-11 02:17:57 +0000464 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
465 for (x=0; x < (ssize_t) image->columns; x++)
466 {
cristy3fc482f2011-09-23 00:43:35 +0000467 register ssize_t
468 i;
cristy343eee92010-12-11 02:17:57 +0000469
cristy3fc482f2011-09-23 00:43:35 +0000470 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
471 {
472 MagickRealType
473 distance;
474
475 PixelChannel
476 channel;
477
478 PixelTrait
479 reconstruct_traits,
480 traits;
481
cristye2a912b2011-12-05 20:02:07 +0000482 traits=GetPixelChannelMapTraits(image,i);
483 channel=GetPixelChannelMapChannel(image,i);
cristy3fc482f2011-09-23 00:43:35 +0000484 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
485 if ((traits == UndefinedPixelTrait) ||
486 (reconstruct_traits == UndefinedPixelTrait))
487 continue;
cristy49dd6a02011-09-24 23:08:01 +0000488 if ((reconstruct_traits & UpdatePixelTrait) == 0)
489 continue;
cristy0beccfa2011-09-25 20:47:53 +0000490 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
491 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000492 distance*=distance;
493 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000494 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000495 }
cristyed231572011-07-14 02:18:59 +0000496 p+=GetPixelChannels(image);
497 q+=GetPixelChannels(reconstruct_image);
cristy343eee92010-12-11 02:17:57 +0000498 }
499#if defined(MAGICKCORE_OPENMP_SUPPORT)
500 #pragma omp critical (MagickCore_GetMeanSquaredError)
501#endif
cristy3fc482f2011-09-23 00:43:35 +0000502 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000503 distortion[i]+=channel_distortion[i];
504 }
505 reconstruct_view=DestroyCacheView(reconstruct_view);
506 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000507 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000508 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000509 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
510 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
cristy343eee92010-12-11 02:17:57 +0000511 return(status);
512}
513
cristy3cc758f2010-11-27 01:33:49 +0000514static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000515 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000516{
cristyc4c8d132010-01-07 01:58:38 +0000517 CacheView
518 *image_view,
519 *reconstruct_view;
520
cristy3ed852e2009-09-05 21:47:34 +0000521 MagickBooleanType
522 status;
523
cristybb503372010-05-27 20:51:26 +0000524 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000525 i;
526
cristy9d314ff2011-03-09 01:30:28 +0000527 ssize_t
528 y;
529
cristy3ed852e2009-09-05 21:47:34 +0000530 status=MagickTrue;
531 image_view=AcquireCacheView(image);
532 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000533#if defined(MAGICKCORE_OPENMP_SUPPORT)
534 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000535#endif
cristybb503372010-05-27 20:51:26 +0000536 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000537 {
538 double
cristy3fc482f2011-09-23 00:43:35 +0000539 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000540
cristy4c08aed2011-07-01 19:47:50 +0000541 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000542 *restrict p,
543 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000544
cristybb503372010-05-27 20:51:26 +0000545 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000546 i,
547 x;
548
549 if (status == MagickFalse)
550 continue;
551 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000552 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
553 1,exception);
554 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000555 {
556 status=MagickFalse;
557 continue;
558 }
cristy3ed852e2009-09-05 21:47:34 +0000559 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000560 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000561 {
cristy3fc482f2011-09-23 00:43:35 +0000562 register ssize_t
563 i;
cristy3ed852e2009-09-05 21:47:34 +0000564
cristy3fc482f2011-09-23 00:43:35 +0000565 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
566 {
567 MagickRealType
568 distance;
569
570 PixelChannel
571 channel;
572
573 PixelTrait
574 reconstruct_traits,
575 traits;
576
cristye2a912b2011-12-05 20:02:07 +0000577 traits=GetPixelChannelMapTraits(image,i);
578 channel=GetPixelChannelMapChannel(image,i);
cristy3fc482f2011-09-23 00:43:35 +0000579 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
580 if ((traits == UndefinedPixelTrait) ||
581 (reconstruct_traits == UndefinedPixelTrait))
582 continue;
cristy49dd6a02011-09-24 23:08:01 +0000583 if ((reconstruct_traits & UpdatePixelTrait) == 0)
584 continue;
cristy0beccfa2011-09-25 20:47:53 +0000585 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
586 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000587 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000588 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000589 }
cristyed231572011-07-14 02:18:59 +0000590 p+=GetPixelChannels(image);
591 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000592 }
cristyb5d5f722009-11-04 03:03:49 +0000593#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000594 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
595#endif
cristy3fc482f2011-09-23 00:43:35 +0000596 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000597 distortion[i]+=channel_distortion[i];
598 }
599 reconstruct_view=DestroyCacheView(reconstruct_view);
600 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000601 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000602 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000603 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000604 return(status);
605}
606
607static MagickBooleanType GetMeanErrorPerPixel(Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000608 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000609{
cristyc4c8d132010-01-07 01:58:38 +0000610 CacheView
611 *image_view,
612 *reconstruct_view;
613
cristy3ed852e2009-09-05 21:47:34 +0000614 MagickBooleanType
615 status;
616
617 MagickRealType
618 alpha,
619 area,
620 beta,
621 maximum_error,
622 mean_error;
623
cristy9d314ff2011-03-09 01:30:28 +0000624 ssize_t
625 y;
626
cristy3ed852e2009-09-05 21:47:34 +0000627 status=MagickTrue;
628 alpha=1.0;
629 beta=1.0;
630 area=0.0;
631 maximum_error=0.0;
632 mean_error=0.0;
633 image_view=AcquireCacheView(image);
634 reconstruct_view=AcquireCacheView(reconstruct_image);
cristybb503372010-05-27 20:51:26 +0000635 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000636 {
cristy4c08aed2011-07-01 19:47:50 +0000637 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000638 *restrict p,
639 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000640
cristybb503372010-05-27 20:51:26 +0000641 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000642 x;
643
644 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
645 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
646 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000647 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000648 {
649 status=MagickFalse;
650 break;
651 }
cristybb503372010-05-27 20:51:26 +0000652 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000653 {
cristy3fc482f2011-09-23 00:43:35 +0000654 register ssize_t
655 i;
cristy3ed852e2009-09-05 21:47:34 +0000656
cristy3fc482f2011-09-23 00:43:35 +0000657 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
658 {
659 MagickRealType
660 distance;
661
662 PixelChannel
663 channel;
664
665 PixelTrait
666 reconstruct_traits,
667 traits;
668
cristye2a912b2011-12-05 20:02:07 +0000669 traits=GetPixelChannelMapTraits(image,i);
670 channel=GetPixelChannelMapChannel(image,i);
cristy3fc482f2011-09-23 00:43:35 +0000671 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
672 if ((traits == UndefinedPixelTrait) ||
673 (reconstruct_traits == UndefinedPixelTrait))
674 continue;
cristy49dd6a02011-09-24 23:08:01 +0000675 if ((reconstruct_traits & UpdatePixelTrait) == 0)
676 continue;
cristy0beccfa2011-09-25 20:47:53 +0000677 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
678 reconstruct_image,channel,q)));
cristy3fc482f2011-09-23 00:43:35 +0000679 distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000680 distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000681 mean_error+=distance*distance;
682 if (distance > maximum_error)
683 maximum_error=distance;
684 area++;
685 }
cristyed231572011-07-14 02:18:59 +0000686 p+=GetPixelChannels(image);
687 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000688 }
689 }
690 reconstruct_view=DestroyCacheView(reconstruct_view);
691 image_view=DestroyCacheView(image_view);
cristy5f95f4f2011-10-23 01:01:01 +0000692 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
cristy3ed852e2009-09-05 21:47:34 +0000693 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
694 image->error.normalized_maximum_error=QuantumScale*maximum_error;
695 return(status);
696}
697
cristy3cc758f2010-11-27 01:33:49 +0000698static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000699 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000700{
cristyc4c8d132010-01-07 01:58:38 +0000701 CacheView
702 *image_view,
703 *reconstruct_view;
704
cristy3ed852e2009-09-05 21:47:34 +0000705 MagickBooleanType
706 status;
707
cristybb503372010-05-27 20:51:26 +0000708 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000709 i;
710
cristy9d314ff2011-03-09 01:30:28 +0000711 ssize_t
712 y;
713
cristy3ed852e2009-09-05 21:47:34 +0000714 status=MagickTrue;
715 image_view=AcquireCacheView(image);
716 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000717#if defined(MAGICKCORE_OPENMP_SUPPORT)
718 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000719#endif
cristybb503372010-05-27 20:51:26 +0000720 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000721 {
722 double
cristy3fc482f2011-09-23 00:43:35 +0000723 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000724
cristy4c08aed2011-07-01 19:47:50 +0000725 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000726 *restrict p,
727 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000728
cristybb503372010-05-27 20:51:26 +0000729 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000730 i,
731 x;
732
733 if (status == MagickFalse)
734 continue;
735 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000736 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
737 1,exception);
738 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000739 {
740 status=MagickFalse;
741 continue;
742 }
cristy3ed852e2009-09-05 21:47:34 +0000743 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000744 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000745 {
cristy3fc482f2011-09-23 00:43:35 +0000746 register ssize_t
747 i;
cristy3ed852e2009-09-05 21:47:34 +0000748
cristy3fc482f2011-09-23 00:43:35 +0000749 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
750 {
751 MagickRealType
752 distance;
753
754 PixelChannel
755 channel;
756
757 PixelTrait
758 reconstruct_traits,
759 traits;
760
cristye2a912b2011-12-05 20:02:07 +0000761 traits=GetPixelChannelMapTraits(image,i);
762 channel=GetPixelChannelMapChannel(image,i);
cristy3fc482f2011-09-23 00:43:35 +0000763 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
764 if ((traits == UndefinedPixelTrait) ||
765 (reconstruct_traits == UndefinedPixelTrait))
766 continue;
cristy49dd6a02011-09-24 23:08:01 +0000767 if ((reconstruct_traits & UpdatePixelTrait) == 0)
768 continue;
cristy0beccfa2011-09-25 20:47:53 +0000769 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
770 reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000771 distance*=distance;
772 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000773 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000774 }
cristyed231572011-07-14 02:18:59 +0000775 p+=GetPixelChannels(image);
776 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000777 }
cristyb5d5f722009-11-04 03:03:49 +0000778#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000779 #pragma omp critical (MagickCore_GetMeanSquaredError)
780#endif
cristy3fc482f2011-09-23 00:43:35 +0000781 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000782 distortion[i]+=channel_distortion[i];
783 }
784 reconstruct_view=DestroyCacheView(reconstruct_view);
785 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000786 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000787 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000788 distortion[CompositePixelChannel]/=GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000789 return(status);
790}
791
cristy3cc758f2010-11-27 01:33:49 +0000792static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
cristy8a9106f2011-07-05 14:39:26 +0000793 const Image *image,const Image *reconstruct_image,double *distortion,
794 ExceptionInfo *exception)
cristy4c929a72010-11-24 18:54:42 +0000795{
cristy9f48ca62010-11-25 03:06:31 +0000796#define SimilarityImageTag "Similarity/Image"
797
cristy4c929a72010-11-24 18:54:42 +0000798 CacheView
799 *image_view,
800 *reconstruct_view;
801
cristy9f48ca62010-11-25 03:06:31 +0000802 ChannelStatistics
cristy9f48ca62010-11-25 03:06:31 +0000803 *image_statistics,
804 *reconstruct_statistics;
805
cristy4c929a72010-11-24 18:54:42 +0000806 MagickBooleanType
807 status;
808
cristy18a41362010-11-27 15:56:18 +0000809 MagickOffsetType
810 progress;
811
cristy34d6fdc2010-11-26 19:06:08 +0000812 MagickRealType
813 area;
814
cristy4c929a72010-11-24 18:54:42 +0000815 register ssize_t
816 i;
817
cristy3cc758f2010-11-27 01:33:49 +0000818 ssize_t
819 y;
820
cristy34d6fdc2010-11-26 19:06:08 +0000821 /*
cristy18a41362010-11-27 15:56:18 +0000822 Normalize to account for variation due to lighting and exposure condition.
cristy34d6fdc2010-11-26 19:06:08 +0000823 */
cristyd42d9952011-07-08 14:21:50 +0000824 image_statistics=GetImageStatistics(image,exception);
825 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
cristy4c929a72010-11-24 18:54:42 +0000826 status=MagickTrue;
cristy9f48ca62010-11-25 03:06:31 +0000827 progress=0;
cristy3fc482f2011-09-23 00:43:35 +0000828 for (i=0; i <= MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000829 distortion[i]=0.0;
cristye28f7af2011-10-17 18:21:57 +0000830 area=1.0/((MagickRealType) image->columns*image->rows-1);
cristy4c929a72010-11-24 18:54:42 +0000831 image_view=AcquireCacheView(image);
832 reconstruct_view=AcquireCacheView(reconstruct_image);
cristy4c929a72010-11-24 18:54:42 +0000833 for (y=0; y < (ssize_t) image->rows; y++)
834 {
cristy4c08aed2011-07-01 19:47:50 +0000835 register const Quantum
cristy4c929a72010-11-24 18:54:42 +0000836 *restrict p,
837 *restrict q;
838
839 register ssize_t
cristy4c929a72010-11-24 18:54:42 +0000840 x;
841
842 if (status == MagickFalse)
843 continue;
844 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy34d6fdc2010-11-26 19:06:08 +0000845 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
846 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000847 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy4c929a72010-11-24 18:54:42 +0000848 {
849 status=MagickFalse;
850 continue;
851 }
cristy4c929a72010-11-24 18:54:42 +0000852 for (x=0; x < (ssize_t) image->columns; x++)
853 {
cristy3fc482f2011-09-23 00:43:35 +0000854 register ssize_t
855 i;
856
857 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
858 {
859 PixelChannel
860 channel;
861
862 PixelTrait
863 reconstruct_traits,
864 traits;
865
cristye2a912b2011-12-05 20:02:07 +0000866 traits=GetPixelChannelMapTraits(image,i);
867 channel=GetPixelChannelMapChannel(image,i);
cristy3fc482f2011-09-23 00:43:35 +0000868 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
869 if ((traits == UndefinedPixelTrait) ||
870 (reconstruct_traits == UndefinedPixelTrait))
871 continue;
cristy49dd6a02011-09-24 23:08:01 +0000872 if ((reconstruct_traits & UpdatePixelTrait) == 0)
873 continue;
cristy3fc482f2011-09-23 00:43:35 +0000874 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
cristy0beccfa2011-09-25 20:47:53 +0000875 (GetPixelChannel(reconstruct_image,channel,q)-
876 reconstruct_statistics[channel].mean);
cristy3fc482f2011-09-23 00:43:35 +0000877 }
cristyed231572011-07-14 02:18:59 +0000878 p+=GetPixelChannels(image);
879 q+=GetPixelChannels(image);
cristy4c929a72010-11-24 18:54:42 +0000880 }
cristy9f48ca62010-11-25 03:06:31 +0000881 if (image->progress_monitor != (MagickProgressMonitor) NULL)
882 {
883 MagickBooleanType
884 proceed;
885
cristy9f48ca62010-11-25 03:06:31 +0000886 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
887 image->rows);
888 if (proceed == MagickFalse)
889 status=MagickFalse;
890 }
cristy4c929a72010-11-24 18:54:42 +0000891 }
892 reconstruct_view=DestroyCacheView(reconstruct_view);
893 image_view=DestroyCacheView(image_view);
cristy34d6fdc2010-11-26 19:06:08 +0000894 /*
895 Divide by the standard deviation.
896 */
cristy5f95f4f2011-10-23 01:01:01 +0000897 distortion[CompositePixelChannel]=0.0;
cristy3fc482f2011-09-23 00:43:35 +0000898 for (i=0; i < MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000899 {
900 MagickRealType
cristy18a41362010-11-27 15:56:18 +0000901 gamma;
cristy34d6fdc2010-11-26 19:06:08 +0000902
cristy3fc482f2011-09-23 00:43:35 +0000903 PixelChannel
904 channel;
905
cristye2a912b2011-12-05 20:02:07 +0000906 channel=GetPixelChannelMapChannel(image,i);
cristy18a41362010-11-27 15:56:18 +0000907 gamma=image_statistics[i].standard_deviation*
cristy3fc482f2011-09-23 00:43:35 +0000908 reconstruct_statistics[channel].standard_deviation;
cristy18a41362010-11-27 15:56:18 +0000909 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
910 distortion[i]=QuantumRange*gamma*distortion[i];
cristy5f95f4f2011-10-23 01:01:01 +0000911 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
cristy34d6fdc2010-11-26 19:06:08 +0000912 }
cristy5f95f4f2011-10-23 01:01:01 +0000913 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
cristy49dd6a02011-09-24 23:08:01 +0000914 GetImageChannels(image));
cristy34d6fdc2010-11-26 19:06:08 +0000915 /*
916 Free resources.
917 */
cristy9f48ca62010-11-25 03:06:31 +0000918 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
919 reconstruct_statistics);
920 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
921 image_statistics);
cristy4c929a72010-11-24 18:54:42 +0000922 return(status);
923}
924
cristy3cc758f2010-11-27 01:33:49 +0000925static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000926 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000927{
cristyc4c8d132010-01-07 01:58:38 +0000928 CacheView
929 *image_view,
930 *reconstruct_view;
931
cristy3ed852e2009-09-05 21:47:34 +0000932 MagickBooleanType
933 status;
934
cristy9d314ff2011-03-09 01:30:28 +0000935 ssize_t
936 y;
937
cristy3ed852e2009-09-05 21:47:34 +0000938 status=MagickTrue;
939 image_view=AcquireCacheView(image);
940 reconstruct_view=AcquireCacheView(reconstruct_image);
cristyb5d5f722009-11-04 03:03:49 +0000941#if defined(MAGICKCORE_OPENMP_SUPPORT)
942 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000943#endif
cristybb503372010-05-27 20:51:26 +0000944 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000945 {
946 double
cristy3fc482f2011-09-23 00:43:35 +0000947 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000948
cristy4c08aed2011-07-01 19:47:50 +0000949 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000950 *restrict p,
951 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000952
cristybb503372010-05-27 20:51:26 +0000953 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000954 i,
955 x;
956
957 if (status == MagickFalse)
958 continue;
959 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
960 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
961 reconstruct_image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000962 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000963 {
964 status=MagickFalse;
965 continue;
966 }
cristy3ed852e2009-09-05 21:47:34 +0000967 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000968 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000969 {
cristy3fc482f2011-09-23 00:43:35 +0000970 register ssize_t
971 i;
cristy3ed852e2009-09-05 21:47:34 +0000972
cristy3fc482f2011-09-23 00:43:35 +0000973 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
974 {
975 MagickRealType
976 distance;
977
978 PixelChannel
979 channel;
980
981 PixelTrait
982 reconstruct_traits,
983 traits;
984
cristye2a912b2011-12-05 20:02:07 +0000985 traits=GetPixelChannelMapTraits(image,i);
986 channel=GetPixelChannelMapChannel(image,i);
cristy3fc482f2011-09-23 00:43:35 +0000987 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
988 if ((traits == UndefinedPixelTrait) ||
989 (reconstruct_traits == UndefinedPixelTrait))
990 continue;
cristy49dd6a02011-09-24 23:08:01 +0000991 if ((reconstruct_traits & UpdatePixelTrait) == 0)
992 continue;
cristy0beccfa2011-09-25 20:47:53 +0000993 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
994 reconstruct_image,channel,q));
cristy25a5f2f2011-09-24 14:09:43 +0000995 if (distance > channel_distortion[i])
cristy3fc482f2011-09-23 00:43:35 +0000996 channel_distortion[i]=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000997 if (distance > channel_distortion[CompositePixelChannel])
998 channel_distortion[CompositePixelChannel]=distance;
cristy3fc482f2011-09-23 00:43:35 +0000999 }
cristyed231572011-07-14 02:18:59 +00001000 p+=GetPixelChannels(image);
1001 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001002 }
cristyb5d5f722009-11-04 03:03:49 +00001003#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001004 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1005#endif
cristy3fc482f2011-09-23 00:43:35 +00001006 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001007 if (channel_distortion[i] > distortion[i])
1008 distortion[i]=channel_distortion[i];
1009 }
1010 reconstruct_view=DestroyCacheView(reconstruct_view);
1011 image_view=DestroyCacheView(image_view);
1012 return(status);
1013}
1014
1015static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001016 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001017{
1018 MagickBooleanType
1019 status;
1020
cristy3fc482f2011-09-23 00:43:35 +00001021 register ssize_t
1022 i;
1023
cristy8a9106f2011-07-05 14:39:26 +00001024 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001025 for (i=0; i <= MaxPixelChannels; i++)
1026 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
cristy3ed852e2009-09-05 21:47:34 +00001027 return(status);
1028}
1029
cristy3cc758f2010-11-27 01:33:49 +00001030static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001031 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001032{
1033 MagickBooleanType
1034 status;
1035
cristy3fc482f2011-09-23 00:43:35 +00001036 register ssize_t
1037 i;
1038
cristy8a9106f2011-07-05 14:39:26 +00001039 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001040 for (i=0; i <= MaxPixelChannels; i++)
1041 distortion[i]=sqrt(distortion[i]);
cristy3ed852e2009-09-05 21:47:34 +00001042 return(status);
1043}
1044
cristy8a9106f2011-07-05 14:39:26 +00001045MagickExport MagickBooleanType GetImageDistortion(Image *image,
1046 const Image *reconstruct_image,const MetricType metric,double *distortion,
1047 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001048{
1049 double
1050 *channel_distortion;
1051
1052 MagickBooleanType
1053 status;
1054
1055 size_t
1056 length;
1057
1058 assert(image != (Image *) NULL);
1059 assert(image->signature == MagickSignature);
1060 if (image->debug != MagickFalse)
1061 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1062 assert(reconstruct_image != (const Image *) NULL);
1063 assert(reconstruct_image->signature == MagickSignature);
1064 assert(distortion != (double *) NULL);
1065 *distortion=0.0;
1066 if (image->debug != MagickFalse)
1067 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1068 if ((reconstruct_image->columns != image->columns) ||
1069 (reconstruct_image->rows != image->rows))
1070 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1071 /*
1072 Get image distortion.
1073 */
cristy3fc482f2011-09-23 00:43:35 +00001074 length=MaxPixelChannels+1;
cristy3ed852e2009-09-05 21:47:34 +00001075 channel_distortion=(double *) AcquireQuantumMemory(length,
1076 sizeof(*channel_distortion));
1077 if (channel_distortion == (double *) NULL)
1078 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1079 (void) ResetMagickMemory(channel_distortion,0,length*
1080 sizeof(*channel_distortion));
1081 switch (metric)
1082 {
1083 case AbsoluteErrorMetric:
1084 {
cristy8a9106f2011-07-05 14:39:26 +00001085 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1086 exception);
cristy3ed852e2009-09-05 21:47:34 +00001087 break;
1088 }
cristy343eee92010-12-11 02:17:57 +00001089 case FuzzErrorMetric:
1090 {
cristy8a9106f2011-07-05 14:39:26 +00001091 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1092 exception);
cristy343eee92010-12-11 02:17:57 +00001093 break;
1094 }
cristy3ed852e2009-09-05 21:47:34 +00001095 case MeanAbsoluteErrorMetric:
1096 {
cristy8a9106f2011-07-05 14:39:26 +00001097 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001098 channel_distortion,exception);
1099 break;
1100 }
1101 case MeanErrorPerPixelMetric:
1102 {
cristy8a9106f2011-07-05 14:39:26 +00001103 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1104 exception);
cristy3ed852e2009-09-05 21:47:34 +00001105 break;
1106 }
1107 case MeanSquaredErrorMetric:
1108 {
cristy8a9106f2011-07-05 14:39:26 +00001109 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001110 channel_distortion,exception);
1111 break;
1112 }
cristy4c929a72010-11-24 18:54:42 +00001113 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001114 default:
cristy4c929a72010-11-24 18:54:42 +00001115 {
cristy3cc758f2010-11-27 01:33:49 +00001116 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001117 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001118 break;
1119 }
cristy3ed852e2009-09-05 21:47:34 +00001120 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001121 {
cristy8a9106f2011-07-05 14:39:26 +00001122 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001123 channel_distortion,exception);
1124 break;
1125 }
1126 case PeakSignalToNoiseRatioMetric:
1127 {
cristy8a9106f2011-07-05 14:39:26 +00001128 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001129 channel_distortion,exception);
1130 break;
1131 }
1132 case RootMeanSquaredErrorMetric:
1133 {
cristy8a9106f2011-07-05 14:39:26 +00001134 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001135 channel_distortion,exception);
1136 break;
1137 }
1138 }
cristy5f95f4f2011-10-23 01:01:01 +00001139 *distortion=channel_distortion[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001140 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1141 return(status);
1142}
1143
1144/*
1145%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1146% %
1147% %
1148% %
cristy8a9106f2011-07-05 14:39:26 +00001149% 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 +00001150% %
1151% %
1152% %
1153%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1154%
cristy8a9106f2011-07-05 14:39:26 +00001155% GetImageDistrortion() compares the pixel channels of an image to a
cristy3ed852e2009-09-05 21:47:34 +00001156% reconstructed image and returns the specified distortion metric for each
1157% channel.
1158%
cristy8a9106f2011-07-05 14:39:26 +00001159% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +00001160%
cristy8a9106f2011-07-05 14:39:26 +00001161% double *GetImageDistortions(const Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001162% const Image *reconstruct_image,const MetricType metric,
1163% ExceptionInfo *exception)
1164%
1165% A description of each parameter follows:
1166%
1167% o image: the image.
1168%
1169% o reconstruct_image: the reconstruct image.
1170%
1171% o metric: the metric.
1172%
1173% o exception: return any errors or warnings in this structure.
1174%
1175*/
cristy8a9106f2011-07-05 14:39:26 +00001176MagickExport double *GetImageDistortions(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001177 const Image *reconstruct_image,const MetricType metric,
1178 ExceptionInfo *exception)
1179{
1180 double
1181 *channel_distortion;
1182
1183 MagickBooleanType
1184 status;
1185
1186 size_t
1187 length;
1188
1189 assert(image != (Image *) NULL);
1190 assert(image->signature == MagickSignature);
1191 if (image->debug != MagickFalse)
1192 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1193 assert(reconstruct_image != (const Image *) NULL);
1194 assert(reconstruct_image->signature == MagickSignature);
1195 if (image->debug != MagickFalse)
1196 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1197 if ((reconstruct_image->columns != image->columns) ||
1198 (reconstruct_image->rows != image->rows))
1199 {
cristyc82a27b2011-10-21 01:07:16 +00001200 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1201 "ImageSizeDiffers","`%s'",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001202 return((double *) NULL);
1203 }
1204 /*
1205 Get image distortion.
1206 */
cristy3fc482f2011-09-23 00:43:35 +00001207 length=MaxPixelChannels+1UL;
cristy3ed852e2009-09-05 21:47:34 +00001208 channel_distortion=(double *) AcquireQuantumMemory(length,
1209 sizeof(*channel_distortion));
1210 if (channel_distortion == (double *) NULL)
1211 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1212 (void) ResetMagickMemory(channel_distortion,0,length*
1213 sizeof(*channel_distortion));
cristyda16f162011-02-19 23:52:17 +00001214 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +00001215 switch (metric)
1216 {
1217 case AbsoluteErrorMetric:
1218 {
cristy8a9106f2011-07-05 14:39:26 +00001219 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1220 exception);
cristy3ed852e2009-09-05 21:47:34 +00001221 break;
1222 }
cristy343eee92010-12-11 02:17:57 +00001223 case FuzzErrorMetric:
1224 {
cristy8a9106f2011-07-05 14:39:26 +00001225 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1226 exception);
cristy343eee92010-12-11 02:17:57 +00001227 break;
1228 }
cristy3ed852e2009-09-05 21:47:34 +00001229 case MeanAbsoluteErrorMetric:
1230 {
cristy8a9106f2011-07-05 14:39:26 +00001231 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001232 channel_distortion,exception);
1233 break;
1234 }
1235 case MeanErrorPerPixelMetric:
1236 {
cristy8a9106f2011-07-05 14:39:26 +00001237 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1238 exception);
cristy3ed852e2009-09-05 21:47:34 +00001239 break;
1240 }
1241 case MeanSquaredErrorMetric:
1242 {
cristy8a9106f2011-07-05 14:39:26 +00001243 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001244 channel_distortion,exception);
1245 break;
1246 }
cristy4c929a72010-11-24 18:54:42 +00001247 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001248 default:
cristy4c929a72010-11-24 18:54:42 +00001249 {
cristy3cc758f2010-11-27 01:33:49 +00001250 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001251 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001252 break;
1253 }
cristy3ed852e2009-09-05 21:47:34 +00001254 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001255 {
cristy8a9106f2011-07-05 14:39:26 +00001256 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001257 channel_distortion,exception);
1258 break;
1259 }
1260 case PeakSignalToNoiseRatioMetric:
1261 {
cristy8a9106f2011-07-05 14:39:26 +00001262 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001263 channel_distortion,exception);
1264 break;
1265 }
1266 case RootMeanSquaredErrorMetric:
1267 {
cristy8a9106f2011-07-05 14:39:26 +00001268 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001269 channel_distortion,exception);
1270 break;
1271 }
1272 }
cristyda16f162011-02-19 23:52:17 +00001273 if (status == MagickFalse)
1274 {
1275 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1276 return((double *) NULL);
1277 }
cristy3ed852e2009-09-05 21:47:34 +00001278 return(channel_distortion);
1279}
1280
1281/*
1282%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1283% %
1284% %
1285% %
1286% I s I m a g e s E q u a l %
1287% %
1288% %
1289% %
1290%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1291%
1292% IsImagesEqual() measures the difference between colors at each pixel
1293% location of two images. A value other than 0 means the colors match
1294% exactly. Otherwise an error measure is computed by summing over all
1295% pixels in an image the distance squared in RGB space between each image
1296% pixel and its corresponding pixel in the reconstruct image. The error
1297% measure is assigned to these image members:
1298%
1299% o mean_error_per_pixel: The mean error for any single pixel in
1300% the image.
1301%
1302% o normalized_mean_error: The normalized mean quantization error for
1303% any single pixel in the image. This distance measure is normalized to
1304% a range between 0 and 1. It is independent of the range of red, green,
1305% and blue values in the image.
1306%
1307% o normalized_maximum_error: The normalized maximum quantization
1308% error for any single pixel in the image. This distance measure is
1309% normalized to a range between 0 and 1. It is independent of the range
1310% of red, green, and blue values in your image.
1311%
1312% A small normalized mean square error, accessed as
1313% image->normalized_mean_error, suggests the images are very similar in
1314% spatial layout and color.
1315%
1316% The format of the IsImagesEqual method is:
1317%
1318% MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001319% const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001320%
1321% A description of each parameter follows.
1322%
1323% o image: the image.
1324%
1325% o reconstruct_image: the reconstruct image.
1326%
cristy018f07f2011-09-04 21:15:19 +00001327% o exception: return any errors or warnings in this structure.
1328%
cristy3ed852e2009-09-05 21:47:34 +00001329*/
1330MagickExport MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001331 const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001332{
cristyc4c8d132010-01-07 01:58:38 +00001333 CacheView
1334 *image_view,
1335 *reconstruct_view;
1336
cristy3ed852e2009-09-05 21:47:34 +00001337 MagickBooleanType
1338 status;
1339
1340 MagickRealType
1341 area,
1342 maximum_error,
1343 mean_error,
1344 mean_error_per_pixel;
1345
cristy9d314ff2011-03-09 01:30:28 +00001346 ssize_t
1347 y;
1348
cristy3ed852e2009-09-05 21:47:34 +00001349 assert(image != (Image *) NULL);
1350 assert(image->signature == MagickSignature);
1351 assert(reconstruct_image != (const Image *) NULL);
1352 assert(reconstruct_image->signature == MagickSignature);
1353 if ((reconstruct_image->columns != image->columns) ||
1354 (reconstruct_image->rows != image->rows))
1355 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1356 area=0.0;
1357 maximum_error=0.0;
1358 mean_error_per_pixel=0.0;
1359 mean_error=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001360 image_view=AcquireCacheView(image);
1361 reconstruct_view=AcquireCacheView(reconstruct_image);
cristybb503372010-05-27 20:51:26 +00001362 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001363 {
cristy4c08aed2011-07-01 19:47:50 +00001364 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001365 *restrict p,
1366 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001367
cristybb503372010-05-27 20:51:26 +00001368 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001369 x;
1370
1371 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1372 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1373 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001374 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001375 break;
cristybb503372010-05-27 20:51:26 +00001376 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001377 {
cristyd5c15f92011-09-23 00:58:33 +00001378 register ssize_t
1379 i;
cristy3ed852e2009-09-05 21:47:34 +00001380
cristyd5c15f92011-09-23 00:58:33 +00001381 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1382 {
1383 MagickRealType
1384 distance;
1385
1386 PixelChannel
1387 channel;
1388
1389 PixelTrait
1390 reconstruct_traits,
1391 traits;
1392
cristye2a912b2011-12-05 20:02:07 +00001393 traits=GetPixelChannelMapTraits(image,i);
1394 channel=GetPixelChannelMapChannel(image,i);
cristyd5c15f92011-09-23 00:58:33 +00001395 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1396 if ((traits == UndefinedPixelTrait) ||
1397 (reconstruct_traits == UndefinedPixelTrait))
1398 continue;
cristy49dd6a02011-09-24 23:08:01 +00001399 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1400 continue;
cristy0beccfa2011-09-25 20:47:53 +00001401 distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
1402 channel,q));
cristyd5c15f92011-09-23 00:58:33 +00001403 mean_error_per_pixel+=distance;
1404 mean_error+=distance*distance;
1405 if (distance > maximum_error)
1406 maximum_error=distance;
1407 area++;
1408 }
cristyed231572011-07-14 02:18:59 +00001409 p+=GetPixelChannels(image);
1410 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001411 }
1412 }
1413 reconstruct_view=DestroyCacheView(reconstruct_view);
1414 image_view=DestroyCacheView(image_view);
1415 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1416 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1417 mean_error/area);
1418 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1419 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1420 return(status);
1421}
1422
1423/*
1424%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1425% %
1426% %
1427% %
1428% S i m i l a r i t y I m a g e %
1429% %
1430% %
1431% %
1432%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1433%
1434% SimilarityImage() compares the reference image of the image and returns the
1435% best match offset. In addition, it returns a similarity image such that an
1436% exact match location is completely white and if none of the pixels match,
1437% black, otherwise some gray level in-between.
1438%
1439% The format of the SimilarityImageImage method is:
1440%
1441% Image *SimilarityImage(const Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001442% const MetricType metric,RectangleInfo *offset,double *similarity,
1443% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001444%
1445% A description of each parameter follows:
1446%
1447% o image: the image.
1448%
1449% o reference: find an area of the image that closely resembles this image.
1450%
cristy09136812011-10-18 15:24:30 +00001451% o metric: the metric.
1452%
cristy3ed852e2009-09-05 21:47:34 +00001453% o the best match offset of the reference image within the image.
1454%
1455% o similarity: the computed similarity between the images.
1456%
1457% o exception: return any errors or warnings in this structure.
1458%
1459*/
1460
1461static double GetSimilarityMetric(const Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001462 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1463 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001464{
1465 double
cristy3cc758f2010-11-27 01:33:49 +00001466 distortion;
cristy3ed852e2009-09-05 21:47:34 +00001467
cristy713ff212010-11-26 21:56:11 +00001468 Image
1469 *similarity_image;
cristy3ed852e2009-09-05 21:47:34 +00001470
cristy09136812011-10-18 15:24:30 +00001471 MagickBooleanType
1472 status;
1473
cristy713ff212010-11-26 21:56:11 +00001474 RectangleInfo
1475 geometry;
cristy3ed852e2009-09-05 21:47:34 +00001476
cristy713ff212010-11-26 21:56:11 +00001477 SetGeometry(reference,&geometry);
1478 geometry.x=x_offset;
1479 geometry.y=y_offset;
1480 similarity_image=CropImage(image,&geometry,exception);
1481 if (similarity_image == (Image *) NULL)
1482 return(0.0);
cristy09136812011-10-18 15:24:30 +00001483 distortion=0.0;
1484 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
cristy3cc758f2010-11-27 01:33:49 +00001485 exception);
cristy713ff212010-11-26 21:56:11 +00001486 similarity_image=DestroyImage(similarity_image);
cristy09136812011-10-18 15:24:30 +00001487 if (status == MagickFalse)
1488 return(0.0);
cristy3cc758f2010-11-27 01:33:49 +00001489 return(distortion);
cristy3ed852e2009-09-05 21:47:34 +00001490}
1491
1492MagickExport Image *SimilarityImage(Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001493 const MetricType metric,RectangleInfo *offset,double *similarity_metric,
1494 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001495{
1496#define SimilarityImageTag "Similarity/Image"
1497
cristyc4c8d132010-01-07 01:58:38 +00001498 CacheView
1499 *similarity_view;
1500
cristy3ed852e2009-09-05 21:47:34 +00001501 Image
1502 *similarity_image;
1503
1504 MagickBooleanType
1505 status;
1506
cristybb503372010-05-27 20:51:26 +00001507 MagickOffsetType
1508 progress;
1509
1510 ssize_t
1511 y;
1512
cristy3ed852e2009-09-05 21:47:34 +00001513 assert(image != (const Image *) NULL);
1514 assert(image->signature == MagickSignature);
1515 if (image->debug != MagickFalse)
1516 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1517 assert(exception != (ExceptionInfo *) NULL);
1518 assert(exception->signature == MagickSignature);
1519 assert(offset != (RectangleInfo *) NULL);
1520 SetGeometry(reference,offset);
1521 *similarity_metric=1.0;
cristyf9255b92010-08-14 22:59:00 +00001522 if ((reference->columns > image->columns) || (reference->rows > image->rows))
cristy3ed852e2009-09-05 21:47:34 +00001523 ThrowImageException(ImageError,"ImageSizeDiffers");
1524 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1525 image->rows-reference->rows+1,MagickTrue,exception);
1526 if (similarity_image == (Image *) NULL)
1527 return((Image *) NULL);
cristyd5c15f92011-09-23 00:58:33 +00001528 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1529 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001530 {
cristy3ed852e2009-09-05 21:47:34 +00001531 similarity_image=DestroyImage(similarity_image);
1532 return((Image *) NULL);
1533 }
1534 /*
1535 Measure similarity of reference image against image.
1536 */
1537 status=MagickTrue;
1538 progress=0;
1539 similarity_view=AcquireCacheView(similarity_image);
cristyb5d5f722009-11-04 03:03:49 +00001540#if defined(MAGICKCORE_OPENMP_SUPPORT)
1541 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001542#endif
cristybb503372010-05-27 20:51:26 +00001543 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
cristy3ed852e2009-09-05 21:47:34 +00001544 {
1545 double
1546 similarity;
1547
cristy4c08aed2011-07-01 19:47:50 +00001548 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001549 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001550
cristy49dd6a02011-09-24 23:08:01 +00001551 register ssize_t
1552 x;
1553
cristy3ed852e2009-09-05 21:47:34 +00001554 if (status == MagickFalse)
1555 continue;
cristy3cc758f2010-11-27 01:33:49 +00001556 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1557 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001558 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001559 {
1560 status=MagickFalse;
1561 continue;
1562 }
cristybb503372010-05-27 20:51:26 +00001563 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
cristy3ed852e2009-09-05 21:47:34 +00001564 {
cristy49dd6a02011-09-24 23:08:01 +00001565 register ssize_t
1566 i;
1567
cristy09136812011-10-18 15:24:30 +00001568 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
cristyb5d5f722009-11-04 03:03:49 +00001569#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001570 #pragma omp critical (MagickCore_SimilarityImage)
1571#endif
1572 if (similarity < *similarity_metric)
1573 {
1574 *similarity_metric=similarity;
1575 offset->x=x;
1576 offset->y=y;
1577 }
cristy49dd6a02011-09-24 23:08:01 +00001578 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1579 {
1580 PixelChannel
1581 channel;
1582
1583 PixelTrait
1584 similarity_traits,
1585 traits;
1586
cristye2a912b2011-12-05 20:02:07 +00001587 traits=GetPixelChannelMapTraits(image,i);
1588 channel=GetPixelChannelMapChannel(image,i);
cristy49dd6a02011-09-24 23:08:01 +00001589 similarity_traits=GetPixelChannelMapTraits(similarity_image,channel);
1590 if ((traits == UndefinedPixelTrait) ||
1591 (similarity_traits == UndefinedPixelTrait))
1592 continue;
1593 if ((similarity_traits & UpdatePixelTrait) == 0)
1594 continue;
cristy0beccfa2011-09-25 20:47:53 +00001595 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1596 QuantumRange*similarity),q);
cristy49dd6a02011-09-24 23:08:01 +00001597 }
cristyed231572011-07-14 02:18:59 +00001598 q+=GetPixelChannels(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001599 }
1600 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1601 status=MagickFalse;
1602 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1603 {
1604 MagickBooleanType
1605 proceed;
1606
cristyb5d5f722009-11-04 03:03:49 +00001607#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001608 #pragma omp critical (MagickCore_SimilarityImage)
1609#endif
1610 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1611 image->rows);
1612 if (proceed == MagickFalse)
1613 status=MagickFalse;
1614 }
1615 }
1616 similarity_view=DestroyCacheView(similarity_view);
cristy3ed852e2009-09-05 21:47:34 +00001617 return(similarity_image);
1618}