blob: d5a57c6d6dc9ad0585c3c045c17889d8b54d3204 [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% %
cristy45ef08f2012-12-07 13:13:34 +000020% Copyright 1999-2013 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"
cristy16881e62012-05-06 14:41:29 +000067#include "MagickCore/thread-private.h"
cristy4c08aed2011-07-01 19:47:50 +000068#include "MagickCore/transform.h"
69#include "MagickCore/utility.h"
70#include "MagickCore/version.h"
cristy3ed852e2009-09-05 21:47:34 +000071
72/*
73%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
74% %
75% %
76% %
cristy8a9106f2011-07-05 14:39:26 +000077% C o m p a r e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +000078% %
79% %
80% %
81%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
82%
cristyaeded782012-09-11 23:39:36 +000083% CompareImages() compares one or more pixel channels of an image to a
cristy8a9106f2011-07-05 14:39:26 +000084% reconstructed image and returns the difference image.
cristy3ed852e2009-09-05 21:47:34 +000085%
cristy8a9106f2011-07-05 14:39:26 +000086% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +000087%
cristy8a9106f2011-07-05 14:39:26 +000088% Image *CompareImages(const Image *image,const Image *reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +000089% const MetricType metric,double *distortion,ExceptionInfo *exception)
90%
91% A description of each parameter follows:
92%
93% o image: the image.
94%
95% o reconstruct_image: the reconstruct image.
96%
cristy3ed852e2009-09-05 21:47:34 +000097% o metric: the metric.
98%
99% o distortion: the computed distortion between the images.
100%
101% o exception: return any errors or warnings in this structure.
102%
103*/
cristy3ed852e2009-09-05 21:47:34 +0000104MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
105 const MetricType metric,double *distortion,ExceptionInfo *exception)
106{
cristyc4c8d132010-01-07 01:58:38 +0000107 CacheView
108 *highlight_view,
109 *image_view,
110 *reconstruct_view;
111
cristy3ed852e2009-09-05 21:47:34 +0000112 const char
113 *artifact;
114
115 Image
116 *difference_image,
117 *highlight_image;
118
cristy3ed852e2009-09-05 21:47:34 +0000119 MagickBooleanType
120 status;
121
cristy4c08aed2011-07-01 19:47:50 +0000122 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000123 highlight,
cristy3fc482f2011-09-23 00:43:35 +0000124 lowlight;
cristy3ed852e2009-09-05 21:47:34 +0000125
cristy49dd6a02011-09-24 23:08:01 +0000126 ssize_t
127 y;
128
cristy3ed852e2009-09-05 21:47:34 +0000129 assert(image != (Image *) NULL);
130 assert(image->signature == MagickSignature);
131 if (image->debug != MagickFalse)
132 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
133 assert(reconstruct_image != (const Image *) NULL);
134 assert(reconstruct_image->signature == MagickSignature);
135 assert(distortion != (double *) NULL);
136 *distortion=0.0;
137 if (image->debug != MagickFalse)
138 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
139 if ((reconstruct_image->columns != image->columns) ||
140 (reconstruct_image->rows != image->rows))
141 ThrowImageException(ImageError,"ImageSizeDiffers");
cristy8a9106f2011-07-05 14:39:26 +0000142 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
143 exception);
cristy3ed852e2009-09-05 21:47:34 +0000144 if (status == MagickFalse)
145 return((Image *) NULL);
146 difference_image=CloneImage(image,0,0,MagickTrue,exception);
147 if (difference_image == (Image *) NULL)
148 return((Image *) NULL);
cristy63240882011-08-05 19:05:27 +0000149 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +0000150 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
151 exception);
152 if (highlight_image == (Image *) NULL)
153 {
154 difference_image=DestroyImage(difference_image);
155 return((Image *) NULL);
156 }
cristy3fc482f2011-09-23 00:43:35 +0000157 status=SetImageStorageClass(highlight_image,DirectClass,exception);
158 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000159 {
cristy3ed852e2009-09-05 21:47:34 +0000160 difference_image=DestroyImage(difference_image);
161 highlight_image=DestroyImage(highlight_image);
162 return((Image *) NULL);
163 }
cristy63240882011-08-05 19:05:27 +0000164 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
cristyca611542013-02-19 00:54:03 +0000165 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000166 artifact=GetImageArtifact(image,"highlight-color");
167 if (artifact != (const char *) NULL)
cristyf9d6dc02012-01-19 02:14:44 +0000168 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
cristyca611542013-02-19 00:54:03 +0000169 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000170 artifact=GetImageArtifact(image,"lowlight-color");
171 if (artifact != (const char *) NULL)
cristy0b1a7972011-10-22 22:17:02 +0000172 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000173 /*
174 Generate difference image.
175 */
176 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000177 image_view=AcquireVirtualCacheView(image,exception);
178 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
179 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000180#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000181 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000182 magick_threads(image,highlight_image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000183#endif
cristybb503372010-05-27 20:51:26 +0000184 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000185 {
186 MagickBooleanType
187 sync;
188
cristy4c08aed2011-07-01 19:47:50 +0000189 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000190 *restrict p,
191 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000192
cristy4c08aed2011-07-01 19:47:50 +0000193 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000194 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000195
cristy49dd6a02011-09-24 23:08:01 +0000196 register ssize_t
197 x;
198
cristy3ed852e2009-09-05 21:47:34 +0000199 if (status == MagickFalse)
200 continue;
201 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
202 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
203 1,exception);
204 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
205 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000206 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
207 (r == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000208 {
209 status=MagickFalse;
210 continue;
211 }
cristybb503372010-05-27 20:51:26 +0000212 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000213 {
cristydb5c82c2013-02-22 00:41:33 +0000214 double
215 Da,
216 Sa;
217
cristy3ed852e2009-09-05 21:47:34 +0000218 MagickStatusType
219 difference;
220
cristy3fc482f2011-09-23 00:43:35 +0000221 register ssize_t
222 i;
223
cristy10a6c612012-01-29 21:41:05 +0000224 if (GetPixelMask(image,p) != 0)
225 {
cristyd09f8802012-02-04 16:44:10 +0000226 SetPixelInfoPixel(highlight_image,&lowlight,r);
cristy10a6c612012-01-29 21:41:05 +0000227 p+=GetPixelChannels(image);
228 q+=GetPixelChannels(reconstruct_image);
229 r+=GetPixelChannels(highlight_image);
230 continue;
231 }
cristyd09f8802012-02-04 16:44:10 +0000232 difference=MagickFalse;
cristydb5c82c2013-02-22 00:41:33 +0000233 Sa=QuantumScale*GetPixelAlpha(image,p);
234 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000235 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
236 {
cristya19f1d72012-08-07 18:24:38 +0000237 double
cristy0beccfa2011-09-25 20:47:53 +0000238 distance;
239
cristy5a23c552013-02-13 14:34:28 +0000240 PixelChannel channel=GetPixelChannelChannel(image,i);
241 PixelTrait traits=GetPixelChannelTraits(image,channel);
242 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
243 channel);
cristy3fc482f2011-09-23 00:43:35 +0000244 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000245 (reconstruct_traits == UndefinedPixelTrait) ||
246 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy3fc482f2011-09-23 00:43:35 +0000247 continue;
cristydb5c82c2013-02-22 00:41:33 +0000248 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
cristy0beccfa2011-09-25 20:47:53 +0000249 if (fabs((double) distance) >= MagickEpsilon)
cristy3fc482f2011-09-23 00:43:35 +0000250 difference=MagickTrue;
251 }
252 if (difference == MagickFalse)
cristy803640d2011-11-17 02:11:32 +0000253 SetPixelInfoPixel(highlight_image,&lowlight,r);
cristy3fc482f2011-09-23 00:43:35 +0000254 else
cristy803640d2011-11-17 02:11:32 +0000255 SetPixelInfoPixel(highlight_image,&highlight,r);
cristyed231572011-07-14 02:18:59 +0000256 p+=GetPixelChannels(image);
257 q+=GetPixelChannels(reconstruct_image);
258 r+=GetPixelChannels(highlight_image);
cristy3ed852e2009-09-05 21:47:34 +0000259 }
260 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
261 if (sync == MagickFalse)
262 status=MagickFalse;
263 }
264 highlight_view=DestroyCacheView(highlight_view);
265 reconstruct_view=DestroyCacheView(reconstruct_view);
266 image_view=DestroyCacheView(image_view);
cristyfeb3e962012-03-29 17:25:55 +0000267 (void) CompositeImage(difference_image,highlight_image,image->compose,
cristy39172402012-03-30 13:04:39 +0000268 MagickTrue,0,0,exception);
cristy3ed852e2009-09-05 21:47:34 +0000269 highlight_image=DestroyImage(highlight_image);
270 if (status == MagickFalse)
271 difference_image=DestroyImage(difference_image);
272 return(difference_image);
273}
274
275/*
276%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
277% %
278% %
279% %
cristy8a9106f2011-07-05 14:39:26 +0000280% G e t I m a g e D i s t o r t i o n %
cristy3ed852e2009-09-05 21:47:34 +0000281% %
282% %
283% %
284%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
285%
cristyaeded782012-09-11 23:39:36 +0000286% GetImageDistortion() compares one or more pixel channels of an image to a
cristy8a9106f2011-07-05 14:39:26 +0000287% reconstructed image and returns the specified distortion metric.
cristy3ed852e2009-09-05 21:47:34 +0000288%
cristy44097f52012-12-16 19:56:20 +0000289% The format of the GetImageDistortion method is:
cristy3ed852e2009-09-05 21:47:34 +0000290%
cristy8a9106f2011-07-05 14:39:26 +0000291% MagickBooleanType GetImageDistortion(const Image *image,
292% const Image *reconstruct_image,const MetricType metric,
293% double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000294%
295% A description of each parameter follows:
296%
297% o image: the image.
298%
299% o reconstruct_image: the reconstruct image.
300%
cristy3ed852e2009-09-05 21:47:34 +0000301% o metric: the metric.
302%
303% o distortion: the computed distortion between the images.
304%
305% o exception: return any errors or warnings in this structure.
306%
307*/
308
cristydb5c82c2013-02-22 00:41:33 +0000309static inline double MagickMax(const double x,const double y)
310{
311 if (x > y)
312 return(x);
313 return(y);
314}
315
cristy3cc758f2010-11-27 01:33:49 +0000316static MagickBooleanType GetAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000317 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000318{
cristyc4c8d132010-01-07 01:58:38 +0000319 CacheView
320 *image_view,
321 *reconstruct_view;
322
cristydb5c82c2013-02-22 00:41:33 +0000323 double
324 fuzz;
325
cristy3ed852e2009-09-05 21:47:34 +0000326 MagickBooleanType
327 status;
328
cristy9d314ff2011-03-09 01:30:28 +0000329 ssize_t
330 y;
331
cristy3ed852e2009-09-05 21:47:34 +0000332 /*
333 Compute the absolute difference in pixels between two images.
334 */
335 status=MagickTrue;
cristydb5c82c2013-02-22 00:41:33 +0000336 if (image->fuzz == 0.0)
337 fuzz=MagickMax(reconstruct_image->fuzz,MagickSQ1_2)*
338 MagickMax(reconstruct_image->fuzz,MagickSQ1_2);
339 else
340 if (reconstruct_image->fuzz == 0.0)
341 fuzz=MagickMax(image->fuzz,MagickSQ1_2)*
342 MagickMax(image->fuzz,MagickSQ1_2);
343 else
344 fuzz=MagickMax(image->fuzz,MagickSQ1_2)*
345 MagickMax(reconstruct_image->fuzz,MagickSQ1_2);
cristy46ff2672012-12-14 15:32:26 +0000346 image_view=AcquireVirtualCacheView(image,exception);
347 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000348#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000349 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000350 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000351#endif
cristybb503372010-05-27 20:51:26 +0000352 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000353 {
354 double
cristy3fc482f2011-09-23 00:43:35 +0000355 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000356
cristy4c08aed2011-07-01 19:47:50 +0000357 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000358 *restrict p,
359 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000360
cristybb503372010-05-27 20:51:26 +0000361 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000362 i,
363 x;
364
365 if (status == MagickFalse)
366 continue;
367 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
368 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
369 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000370 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000371 {
372 status=MagickFalse;
373 continue;
374 }
cristy3ed852e2009-09-05 21:47:34 +0000375 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000376 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000377 {
cristydb5c82c2013-02-22 00:41:33 +0000378 double
379 Da,
380 Sa;
381
cristy3fc482f2011-09-23 00:43:35 +0000382 MagickBooleanType
383 difference;
384
385 register ssize_t
386 i;
387
cristy10a6c612012-01-29 21:41:05 +0000388 if (GetPixelMask(image,p) != 0)
389 {
390 p+=GetPixelChannels(image);
391 q+=GetPixelChannels(reconstruct_image);
392 continue;
393 }
cristyd09f8802012-02-04 16:44:10 +0000394 difference=MagickFalse;
cristydb5c82c2013-02-22 00:41:33 +0000395 Sa=QuantumScale*GetPixelAlpha(image,p);
396 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000397 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
398 {
cristydb5c82c2013-02-22 00:41:33 +0000399 double
400 distance;
401
cristy5a23c552013-02-13 14:34:28 +0000402 PixelChannel channel=GetPixelChannelChannel(image,i);
403 PixelTrait traits=GetPixelChannelTraits(image,channel);
404 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
405 channel);
cristy3fc482f2011-09-23 00:43:35 +0000406 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000407 (reconstruct_traits == UndefinedPixelTrait) ||
408 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000409 continue;
cristydb5c82c2013-02-22 00:41:33 +0000410 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
411 if ((distance*distance) > fuzz)
412 {
413 difference=MagickTrue;
414 break;
415 }
cristy3fc482f2011-09-23 00:43:35 +0000416 }
417 if (difference != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000418 {
cristy3fc482f2011-09-23 00:43:35 +0000419 channel_distortion[i]++;
cristy5f95f4f2011-10-23 01:01:01 +0000420 channel_distortion[CompositePixelChannel]++;
cristy3ed852e2009-09-05 21:47:34 +0000421 }
cristyed231572011-07-14 02:18:59 +0000422 p+=GetPixelChannels(image);
423 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000424 }
cristyb5d5f722009-11-04 03:03:49 +0000425#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000426 #pragma omp critical (MagickCore_GetAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +0000427#endif
cristy3fc482f2011-09-23 00:43:35 +0000428 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000429 distortion[i]+=channel_distortion[i];
430 }
431 reconstruct_view=DestroyCacheView(reconstruct_view);
432 image_view=DestroyCacheView(image_view);
433 return(status);
434}
435
cristy49dd6a02011-09-24 23:08:01 +0000436static size_t GetImageChannels(const Image *image)
cristy3ed852e2009-09-05 21:47:34 +0000437{
cristy3fc482f2011-09-23 00:43:35 +0000438 register ssize_t
439 i;
440
cristy49dd6a02011-09-24 23:08:01 +0000441 size_t
cristy3ed852e2009-09-05 21:47:34 +0000442 channels;
443
444 channels=0;
cristy3fc482f2011-09-23 00:43:35 +0000445 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
446 {
cristy5a23c552013-02-13 14:34:28 +0000447 PixelChannel channel=GetPixelChannelChannel(image,i);
448 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristy25a5f2f2011-09-24 14:09:43 +0000449 if ((traits & UpdatePixelTrait) != 0)
cristy3fc482f2011-09-23 00:43:35 +0000450 channels++;
451 }
cristy3ed852e2009-09-05 21:47:34 +0000452 return(channels);
453}
454
cristy343eee92010-12-11 02:17:57 +0000455static MagickBooleanType GetFuzzDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000456 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy343eee92010-12-11 02:17:57 +0000457{
458 CacheView
459 *image_view,
460 *reconstruct_view;
461
cristy343eee92010-12-11 02:17:57 +0000462 MagickBooleanType
463 status;
464
465 register ssize_t
466 i;
467
cristy9d314ff2011-03-09 01:30:28 +0000468 ssize_t
469 y;
470
cristy343eee92010-12-11 02:17:57 +0000471 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000472 image_view=AcquireVirtualCacheView(image,exception);
473 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristy343eee92010-12-11 02:17:57 +0000474#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000475 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000476 magick_threads(image,image,image->rows,1)
cristy343eee92010-12-11 02:17:57 +0000477#endif
478 for (y=0; y < (ssize_t) image->rows; y++)
479 {
480 double
cristy3fc482f2011-09-23 00:43:35 +0000481 channel_distortion[MaxPixelChannels+1];
cristy343eee92010-12-11 02:17:57 +0000482
cristy4c08aed2011-07-01 19:47:50 +0000483 register const Quantum
cristy343eee92010-12-11 02:17:57 +0000484 *restrict p,
485 *restrict q;
486
487 register ssize_t
488 i,
489 x;
490
491 if (status == MagickFalse)
492 continue;
493 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristye6529ff2011-02-04 20:05:32 +0000494 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
495 1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000496 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy343eee92010-12-11 02:17:57 +0000497 {
498 status=MagickFalse;
499 continue;
500 }
cristy343eee92010-12-11 02:17:57 +0000501 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
502 for (x=0; x < (ssize_t) image->columns; x++)
503 {
cristydb5c82c2013-02-22 00:41:33 +0000504 double
505 Da,
506 Sa;
507
cristy3fc482f2011-09-23 00:43:35 +0000508 register ssize_t
509 i;
cristy343eee92010-12-11 02:17:57 +0000510
cristy10a6c612012-01-29 21:41:05 +0000511 if (GetPixelMask(image,p) != 0)
512 {
513 p+=GetPixelChannels(image);
514 q+=GetPixelChannels(reconstruct_image);
515 continue;
516 }
cristydb5c82c2013-02-22 00:41:33 +0000517 Sa=QuantumScale*GetPixelAlpha(image,p);
518 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000519 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
520 {
cristya19f1d72012-08-07 18:24:38 +0000521 double
cristy3fc482f2011-09-23 00:43:35 +0000522 distance;
523
cristy5a23c552013-02-13 14:34:28 +0000524 PixelChannel channel=GetPixelChannelChannel(image,i);
525 PixelTrait traits=GetPixelChannelTraits(image,channel);
526 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
527 channel);
cristy3fc482f2011-09-23 00:43:35 +0000528 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000529 (reconstruct_traits == UndefinedPixelTrait) ||
530 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000531 continue;
cristydb5c82c2013-02-22 00:41:33 +0000532 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
cristy1eced092012-08-10 23:10:56 +0000533 channel,q));
cristydb5c82c2013-02-22 00:41:33 +0000534 channel_distortion[i]+=distance*distance;
535 channel_distortion[CompositePixelChannel]+=distance*distance;
cristy3fc482f2011-09-23 00:43:35 +0000536 }
cristyed231572011-07-14 02:18:59 +0000537 p+=GetPixelChannels(image);
538 q+=GetPixelChannels(reconstruct_image);
cristy343eee92010-12-11 02:17:57 +0000539 }
540#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ad5c2a2012-05-05 21:32:54 +0000541 #pragma omp critical (MagickCore_GetFuzzDistortion)
cristy343eee92010-12-11 02:17:57 +0000542#endif
cristy3fc482f2011-09-23 00:43:35 +0000543 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000544 distortion[i]+=channel_distortion[i];
545 }
546 reconstruct_view=DestroyCacheView(reconstruct_view);
547 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000548 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000549 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000550 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
551 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
cristy343eee92010-12-11 02:17:57 +0000552 return(status);
553}
554
cristy3cc758f2010-11-27 01:33:49 +0000555static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000556 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000557{
cristyc4c8d132010-01-07 01:58:38 +0000558 CacheView
559 *image_view,
560 *reconstruct_view;
561
cristy3ed852e2009-09-05 21:47:34 +0000562 MagickBooleanType
563 status;
564
cristybb503372010-05-27 20:51:26 +0000565 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000566 i;
567
cristy9d314ff2011-03-09 01:30:28 +0000568 ssize_t
569 y;
570
cristy3ed852e2009-09-05 21:47:34 +0000571 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000572 image_view=AcquireVirtualCacheView(image,exception);
573 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000574#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000575 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000576 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000577#endif
cristybb503372010-05-27 20:51:26 +0000578 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000579 {
580 double
cristy3fc482f2011-09-23 00:43:35 +0000581 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000582
cristy4c08aed2011-07-01 19:47:50 +0000583 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000584 *restrict p,
585 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000586
cristybb503372010-05-27 20:51:26 +0000587 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000588 i,
589 x;
590
591 if (status == MagickFalse)
592 continue;
593 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000594 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
595 1,exception);
596 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000597 {
598 status=MagickFalse;
599 continue;
600 }
cristy3ed852e2009-09-05 21:47:34 +0000601 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000602 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000603 {
cristydb5c82c2013-02-22 00:41:33 +0000604 double
605 Da,
606 Sa;
607
cristy3fc482f2011-09-23 00:43:35 +0000608 register ssize_t
609 i;
cristy3ed852e2009-09-05 21:47:34 +0000610
cristy10a6c612012-01-29 21:41:05 +0000611 if (GetPixelMask(image,p) != 0)
612 {
613 p+=GetPixelChannels(image);
614 q+=GetPixelChannels(reconstruct_image);
615 continue;
616 }
cristydb5c82c2013-02-22 00:41:33 +0000617 Sa=QuantumScale*GetPixelAlpha(image,p);
618 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000619 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
620 {
cristya19f1d72012-08-07 18:24:38 +0000621 double
cristy3fc482f2011-09-23 00:43:35 +0000622 distance;
623
cristy5a23c552013-02-13 14:34:28 +0000624 PixelChannel channel=GetPixelChannelChannel(image,i);
625 PixelTrait traits=GetPixelChannelTraits(image,channel);
626 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
627 channel);
cristy3fc482f2011-09-23 00:43:35 +0000628 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000629 (reconstruct_traits == UndefinedPixelTrait) ||
630 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000631 continue;
cristydb5c82c2013-02-22 00:41:33 +0000632 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
633 channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000634 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000635 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000636 }
cristyed231572011-07-14 02:18:59 +0000637 p+=GetPixelChannels(image);
638 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000639 }
cristyb5d5f722009-11-04 03:03:49 +0000640#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000641 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +0000642#endif
cristy3fc482f2011-09-23 00:43:35 +0000643 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000644 distortion[i]+=channel_distortion[i];
645 }
646 reconstruct_view=DestroyCacheView(reconstruct_view);
647 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000648 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000649 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000650 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000651 return(status);
652}
653
654static MagickBooleanType GetMeanErrorPerPixel(Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000655 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000656{
cristyc4c8d132010-01-07 01:58:38 +0000657 CacheView
658 *image_view,
659 *reconstruct_view;
660
cristy3ed852e2009-09-05 21:47:34 +0000661 MagickBooleanType
662 status;
663
cristya19f1d72012-08-07 18:24:38 +0000664 double
cristy3ed852e2009-09-05 21:47:34 +0000665 alpha,
666 area,
667 beta,
668 maximum_error,
669 mean_error;
670
cristy9d314ff2011-03-09 01:30:28 +0000671 ssize_t
672 y;
673
cristy3ed852e2009-09-05 21:47:34 +0000674 status=MagickTrue;
675 alpha=1.0;
676 beta=1.0;
677 area=0.0;
678 maximum_error=0.0;
679 mean_error=0.0;
cristy46ff2672012-12-14 15:32:26 +0000680 image_view=AcquireVirtualCacheView(image,exception);
681 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristybb503372010-05-27 20:51:26 +0000682 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000683 {
cristy4c08aed2011-07-01 19:47:50 +0000684 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000685 *restrict p,
686 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000687
cristybb503372010-05-27 20:51:26 +0000688 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000689 x;
690
691 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
692 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
693 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000694 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000695 {
696 status=MagickFalse;
697 break;
698 }
cristybb503372010-05-27 20:51:26 +0000699 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000700 {
cristydb5c82c2013-02-22 00:41:33 +0000701 double
702 Da,
703 Sa;
704
cristy3fc482f2011-09-23 00:43:35 +0000705 register ssize_t
706 i;
cristy3ed852e2009-09-05 21:47:34 +0000707
cristy10a6c612012-01-29 21:41:05 +0000708 if (GetPixelMask(image,p) != 0)
709 {
710 p+=GetPixelChannels(image);
711 q+=GetPixelChannels(reconstruct_image);
712 continue;
713 }
cristydb5c82c2013-02-22 00:41:33 +0000714 Sa=QuantumScale*GetPixelAlpha(image,p);
715 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000716 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
717 {
cristya19f1d72012-08-07 18:24:38 +0000718 double
cristy3fc482f2011-09-23 00:43:35 +0000719 distance;
720
cristy5a23c552013-02-13 14:34:28 +0000721 PixelChannel channel=GetPixelChannelChannel(image,i);
722 PixelTrait traits=GetPixelChannelTraits(image,channel);
723 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
724 channel);
cristy3fc482f2011-09-23 00:43:35 +0000725 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000726 (reconstruct_traits == UndefinedPixelTrait) ||
727 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000728 continue;
cristydb5c82c2013-02-22 00:41:33 +0000729 distance=fabs((double) (alpha*Sa*p[i]-beta*Da*GetPixelChannel(
cristy0beccfa2011-09-25 20:47:53 +0000730 reconstruct_image,channel,q)));
cristy3fc482f2011-09-23 00:43:35 +0000731 distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000732 distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000733 mean_error+=distance*distance;
734 if (distance > maximum_error)
735 maximum_error=distance;
736 area++;
737 }
cristyed231572011-07-14 02:18:59 +0000738 p+=GetPixelChannels(image);
739 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000740 }
741 }
742 reconstruct_view=DestroyCacheView(reconstruct_view);
743 image_view=DestroyCacheView(image_view);
cristy5f95f4f2011-10-23 01:01:01 +0000744 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
cristy3ed852e2009-09-05 21:47:34 +0000745 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
746 image->error.normalized_maximum_error=QuantumScale*maximum_error;
747 return(status);
748}
749
cristy3cc758f2010-11-27 01:33:49 +0000750static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000751 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000752{
cristyc4c8d132010-01-07 01:58:38 +0000753 CacheView
754 *image_view,
755 *reconstruct_view;
756
cristy3ed852e2009-09-05 21:47:34 +0000757 MagickBooleanType
758 status;
759
cristybb503372010-05-27 20:51:26 +0000760 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000761 i;
762
cristy9d314ff2011-03-09 01:30:28 +0000763 ssize_t
764 y;
765
cristy3ed852e2009-09-05 21:47:34 +0000766 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000767 image_view=AcquireVirtualCacheView(image,exception);
768 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000769#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000770 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000771 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000772#endif
cristybb503372010-05-27 20:51:26 +0000773 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000774 {
775 double
cristy3fc482f2011-09-23 00:43:35 +0000776 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000777
cristy4c08aed2011-07-01 19:47:50 +0000778 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000779 *restrict p,
780 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000781
cristybb503372010-05-27 20:51:26 +0000782 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000783 i,
784 x;
785
786 if (status == MagickFalse)
787 continue;
788 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000789 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
790 1,exception);
791 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000792 {
793 status=MagickFalse;
794 continue;
795 }
cristy3ed852e2009-09-05 21:47:34 +0000796 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000797 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000798 {
cristydb5c82c2013-02-22 00:41:33 +0000799 double
800 Da,
801 Sa;
802
cristy3fc482f2011-09-23 00:43:35 +0000803 register ssize_t
804 i;
cristy3ed852e2009-09-05 21:47:34 +0000805
cristy10a6c612012-01-29 21:41:05 +0000806 if (GetPixelMask(image,p) != 0)
807 {
808 p+=GetPixelChannels(image);
809 q+=GetPixelChannels(reconstruct_image);
810 continue;
811 }
cristydb5c82c2013-02-22 00:41:33 +0000812 Sa=QuantumScale*GetPixelAlpha(image,p);
813 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000814 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
815 {
cristya19f1d72012-08-07 18:24:38 +0000816 double
cristy3fc482f2011-09-23 00:43:35 +0000817 distance;
818
cristy5a23c552013-02-13 14:34:28 +0000819 PixelChannel channel=GetPixelChannelChannel(image,i);
820 PixelTrait traits=GetPixelChannelTraits(image,channel);
821 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
822 channel);
cristy3fc482f2011-09-23 00:43:35 +0000823 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000824 (reconstruct_traits == UndefinedPixelTrait) ||
825 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000826 continue;
cristydb5c82c2013-02-22 00:41:33 +0000827 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
828 channel,q));
829 channel_distortion[i]+=distance*distance;
830 channel_distortion[CompositePixelChannel]+=distance*distance;
cristy3fc482f2011-09-23 00:43:35 +0000831 }
cristyed231572011-07-14 02:18:59 +0000832 p+=GetPixelChannels(image);
833 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000834 }
cristyb5d5f722009-11-04 03:03:49 +0000835#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000836 #pragma omp critical (MagickCore_GetMeanSquaredError)
cristy3ed852e2009-09-05 21:47:34 +0000837#endif
cristy3fc482f2011-09-23 00:43:35 +0000838 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000839 distortion[i]+=channel_distortion[i];
840 }
841 reconstruct_view=DestroyCacheView(reconstruct_view);
842 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000843 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000844 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000845 distortion[CompositePixelChannel]/=GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000846 return(status);
847}
848
cristy3cc758f2010-11-27 01:33:49 +0000849static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
cristy8a9106f2011-07-05 14:39:26 +0000850 const Image *image,const Image *reconstruct_image,double *distortion,
851 ExceptionInfo *exception)
cristy4c929a72010-11-24 18:54:42 +0000852{
cristy9f48ca62010-11-25 03:06:31 +0000853#define SimilarityImageTag "Similarity/Image"
854
cristy4c929a72010-11-24 18:54:42 +0000855 CacheView
856 *image_view,
857 *reconstruct_view;
858
cristy9f48ca62010-11-25 03:06:31 +0000859 ChannelStatistics
cristy9f48ca62010-11-25 03:06:31 +0000860 *image_statistics,
861 *reconstruct_statistics;
862
cristydb5c82c2013-02-22 00:41:33 +0000863 double
864 area;
865
cristy4c929a72010-11-24 18:54:42 +0000866 MagickBooleanType
867 status;
868
cristy18a41362010-11-27 15:56:18 +0000869 MagickOffsetType
870 progress;
871
cristy4c929a72010-11-24 18:54:42 +0000872 register ssize_t
873 i;
874
cristy3cc758f2010-11-27 01:33:49 +0000875 ssize_t
876 y;
877
cristy34d6fdc2010-11-26 19:06:08 +0000878 /*
cristy18a41362010-11-27 15:56:18 +0000879 Normalize to account for variation due to lighting and exposure condition.
cristy34d6fdc2010-11-26 19:06:08 +0000880 */
cristyd42d9952011-07-08 14:21:50 +0000881 image_statistics=GetImageStatistics(image,exception);
882 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
cristy4c929a72010-11-24 18:54:42 +0000883 status=MagickTrue;
cristy9f48ca62010-11-25 03:06:31 +0000884 progress=0;
cristy3fc482f2011-09-23 00:43:35 +0000885 for (i=0; i <= MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000886 distortion[i]=0.0;
cristya19f1d72012-08-07 18:24:38 +0000887 area=1.0/((double) image->columns*image->rows-1);
cristy46ff2672012-12-14 15:32:26 +0000888 image_view=AcquireVirtualCacheView(image,exception);
889 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristy4c929a72010-11-24 18:54:42 +0000890 for (y=0; y < (ssize_t) image->rows; y++)
891 {
cristy4c08aed2011-07-01 19:47:50 +0000892 register const Quantum
cristy4c929a72010-11-24 18:54:42 +0000893 *restrict p,
894 *restrict q;
895
896 register ssize_t
cristy4c929a72010-11-24 18:54:42 +0000897 x;
898
899 if (status == MagickFalse)
900 continue;
901 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy34d6fdc2010-11-26 19:06:08 +0000902 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
903 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000904 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy4c929a72010-11-24 18:54:42 +0000905 {
906 status=MagickFalse;
907 continue;
908 }
cristy4c929a72010-11-24 18:54:42 +0000909 for (x=0; x < (ssize_t) image->columns; x++)
910 {
cristydb5c82c2013-02-22 00:41:33 +0000911 double
912 Da,
913 Sa;
914
cristy3fc482f2011-09-23 00:43:35 +0000915 register ssize_t
916 i;
917
cristy10a6c612012-01-29 21:41:05 +0000918 if (GetPixelMask(image,p) != 0)
919 {
920 p+=GetPixelChannels(image);
921 q+=GetPixelChannels(reconstruct_image);
922 continue;
923 }
cristydb5c82c2013-02-22 00:41:33 +0000924 Sa=QuantumScale*GetPixelAlpha(image,p);
925 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000926 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
927 {
cristy5a23c552013-02-13 14:34:28 +0000928 PixelChannel channel=GetPixelChannelChannel(image,i);
929 PixelTrait traits=GetPixelChannelTraits(image,channel);
930 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
931 channel);
cristy3fc482f2011-09-23 00:43:35 +0000932 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000933 (reconstruct_traits == UndefinedPixelTrait) ||
934 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000935 continue;
cristydb5c82c2013-02-22 00:41:33 +0000936 distortion[i]+=area*QuantumScale*(Sa*p[i]-image_statistics[i].mean)*
937 (Da*GetPixelChannel(reconstruct_image,channel,q)-
cristy0beccfa2011-09-25 20:47:53 +0000938 reconstruct_statistics[channel].mean);
cristy3fc482f2011-09-23 00:43:35 +0000939 }
cristyed231572011-07-14 02:18:59 +0000940 p+=GetPixelChannels(image);
cristy10a6c612012-01-29 21:41:05 +0000941 q+=GetPixelChannels(reconstruct_image);
cristy4c929a72010-11-24 18:54:42 +0000942 }
cristy9f48ca62010-11-25 03:06:31 +0000943 if (image->progress_monitor != (MagickProgressMonitor) NULL)
944 {
945 MagickBooleanType
946 proceed;
947
cristy9f48ca62010-11-25 03:06:31 +0000948 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
949 image->rows);
950 if (proceed == MagickFalse)
951 status=MagickFalse;
952 }
cristy4c929a72010-11-24 18:54:42 +0000953 }
954 reconstruct_view=DestroyCacheView(reconstruct_view);
955 image_view=DestroyCacheView(image_view);
cristy34d6fdc2010-11-26 19:06:08 +0000956 /*
957 Divide by the standard deviation.
958 */
cristy5f95f4f2011-10-23 01:01:01 +0000959 distortion[CompositePixelChannel]=0.0;
cristy3fc482f2011-09-23 00:43:35 +0000960 for (i=0; i < MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000961 {
cristya19f1d72012-08-07 18:24:38 +0000962 double
cristy18a41362010-11-27 15:56:18 +0000963 gamma;
cristy34d6fdc2010-11-26 19:06:08 +0000964
cristy5a23c552013-02-13 14:34:28 +0000965 PixelChannel channel=GetPixelChannelChannel(image,i);
cristy18a41362010-11-27 15:56:18 +0000966 gamma=image_statistics[i].standard_deviation*
cristy3fc482f2011-09-23 00:43:35 +0000967 reconstruct_statistics[channel].standard_deviation;
cristy3e3ec3a2012-11-03 23:11:06 +0000968 gamma=PerceptibleReciprocal(gamma);
cristy18a41362010-11-27 15:56:18 +0000969 distortion[i]=QuantumRange*gamma*distortion[i];
cristy5f95f4f2011-10-23 01:01:01 +0000970 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
cristy34d6fdc2010-11-26 19:06:08 +0000971 }
cristy5f95f4f2011-10-23 01:01:01 +0000972 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
cristy49dd6a02011-09-24 23:08:01 +0000973 GetImageChannels(image));
cristy34d6fdc2010-11-26 19:06:08 +0000974 /*
975 Free resources.
976 */
cristy9f48ca62010-11-25 03:06:31 +0000977 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
978 reconstruct_statistics);
979 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
980 image_statistics);
cristy4c929a72010-11-24 18:54:42 +0000981 return(status);
982}
983
cristy3cc758f2010-11-27 01:33:49 +0000984static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000985 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000986{
cristyc4c8d132010-01-07 01:58:38 +0000987 CacheView
988 *image_view,
989 *reconstruct_view;
990
cristy3ed852e2009-09-05 21:47:34 +0000991 MagickBooleanType
992 status;
993
cristy9d314ff2011-03-09 01:30:28 +0000994 ssize_t
995 y;
996
cristy3ed852e2009-09-05 21:47:34 +0000997 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000998 image_view=AcquireVirtualCacheView(image,exception);
999 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001000#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001001 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +00001002 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001003#endif
cristybb503372010-05-27 20:51:26 +00001004 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001005 {
1006 double
cristy3fc482f2011-09-23 00:43:35 +00001007 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +00001008
cristy4c08aed2011-07-01 19:47:50 +00001009 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001010 *restrict p,
1011 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001012
cristybb503372010-05-27 20:51:26 +00001013 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001014 i,
1015 x;
1016
1017 if (status == MagickFalse)
1018 continue;
1019 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy10a6c612012-01-29 21:41:05 +00001020 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1021 1,exception);
cristy3fc482f2011-09-23 00:43:35 +00001022 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001023 {
1024 status=MagickFalse;
1025 continue;
1026 }
cristy3ed852e2009-09-05 21:47:34 +00001027 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +00001028 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001029 {
cristydb5c82c2013-02-22 00:41:33 +00001030 double
1031 Da,
1032 Sa;
1033
cristy3fc482f2011-09-23 00:43:35 +00001034 register ssize_t
1035 i;
cristy3ed852e2009-09-05 21:47:34 +00001036
cristy10a6c612012-01-29 21:41:05 +00001037 if (GetPixelMask(image,p) != 0)
1038 {
1039 p+=GetPixelChannels(image);
1040 q+=GetPixelChannels(reconstruct_image);
1041 continue;
1042 }
cristydb5c82c2013-02-22 00:41:33 +00001043 Sa=QuantumScale*GetPixelAlpha(image,p);
1044 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +00001045 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1046 {
cristya19f1d72012-08-07 18:24:38 +00001047 double
cristy3fc482f2011-09-23 00:43:35 +00001048 distance;
1049
cristy5a23c552013-02-13 14:34:28 +00001050 PixelChannel channel=GetPixelChannelChannel(image,i);
1051 PixelTrait traits=GetPixelChannelTraits(image,channel);
1052 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1053 channel);
cristy3fc482f2011-09-23 00:43:35 +00001054 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001055 (reconstruct_traits == UndefinedPixelTrait) ||
1056 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001057 continue;
cristydb5c82c2013-02-22 00:41:33 +00001058 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
1059 channel,q));
cristy25a5f2f2011-09-24 14:09:43 +00001060 if (distance > channel_distortion[i])
cristy3fc482f2011-09-23 00:43:35 +00001061 channel_distortion[i]=distance;
cristy5f95f4f2011-10-23 01:01:01 +00001062 if (distance > channel_distortion[CompositePixelChannel])
1063 channel_distortion[CompositePixelChannel]=distance;
cristy3fc482f2011-09-23 00:43:35 +00001064 }
cristyed231572011-07-14 02:18:59 +00001065 p+=GetPixelChannels(image);
cristy10a6c612012-01-29 21:41:05 +00001066 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001067 }
cristyb5d5f722009-11-04 03:03:49 +00001068#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001069 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +00001070#endif
cristy3fc482f2011-09-23 00:43:35 +00001071 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001072 if (channel_distortion[i] > distortion[i])
1073 distortion[i]=channel_distortion[i];
1074 }
1075 reconstruct_view=DestroyCacheView(reconstruct_view);
1076 image_view=DestroyCacheView(image_view);
1077 return(status);
1078}
1079
1080static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001081 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001082{
1083 MagickBooleanType
1084 status;
1085
cristy3fc482f2011-09-23 00:43:35 +00001086 register ssize_t
1087 i;
1088
cristy8a9106f2011-07-05 14:39:26 +00001089 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001090 for (i=0; i <= MaxPixelChannels; i++)
1091 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
cristy3ed852e2009-09-05 21:47:34 +00001092 return(status);
1093}
1094
cristy3cc758f2010-11-27 01:33:49 +00001095static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001096 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001097{
1098 MagickBooleanType
1099 status;
1100
cristy3fc482f2011-09-23 00:43:35 +00001101 register ssize_t
1102 i;
1103
cristy8a9106f2011-07-05 14:39:26 +00001104 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001105 for (i=0; i <= MaxPixelChannels; i++)
1106 distortion[i]=sqrt(distortion[i]);
cristy3ed852e2009-09-05 21:47:34 +00001107 return(status);
1108}
1109
cristy8a9106f2011-07-05 14:39:26 +00001110MagickExport MagickBooleanType GetImageDistortion(Image *image,
1111 const Image *reconstruct_image,const MetricType metric,double *distortion,
1112 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001113{
1114 double
1115 *channel_distortion;
1116
1117 MagickBooleanType
1118 status;
1119
1120 size_t
1121 length;
1122
1123 assert(image != (Image *) NULL);
1124 assert(image->signature == MagickSignature);
1125 if (image->debug != MagickFalse)
1126 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1127 assert(reconstruct_image != (const Image *) NULL);
1128 assert(reconstruct_image->signature == MagickSignature);
1129 assert(distortion != (double *) NULL);
1130 *distortion=0.0;
1131 if (image->debug != MagickFalse)
1132 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1133 if ((reconstruct_image->columns != image->columns) ||
1134 (reconstruct_image->rows != image->rows))
1135 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1136 /*
1137 Get image distortion.
1138 */
cristy3fc482f2011-09-23 00:43:35 +00001139 length=MaxPixelChannels+1;
cristy3ed852e2009-09-05 21:47:34 +00001140 channel_distortion=(double *) AcquireQuantumMemory(length,
1141 sizeof(*channel_distortion));
1142 if (channel_distortion == (double *) NULL)
1143 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1144 (void) ResetMagickMemory(channel_distortion,0,length*
1145 sizeof(*channel_distortion));
1146 switch (metric)
1147 {
1148 case AbsoluteErrorMetric:
1149 {
cristy8a9106f2011-07-05 14:39:26 +00001150 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1151 exception);
cristy3ed852e2009-09-05 21:47:34 +00001152 break;
1153 }
cristy343eee92010-12-11 02:17:57 +00001154 case FuzzErrorMetric:
1155 {
cristy8a9106f2011-07-05 14:39:26 +00001156 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1157 exception);
cristy343eee92010-12-11 02:17:57 +00001158 break;
1159 }
cristy3ed852e2009-09-05 21:47:34 +00001160 case MeanAbsoluteErrorMetric:
1161 {
cristy8a9106f2011-07-05 14:39:26 +00001162 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001163 channel_distortion,exception);
1164 break;
1165 }
1166 case MeanErrorPerPixelMetric:
1167 {
cristy8a9106f2011-07-05 14:39:26 +00001168 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1169 exception);
cristy3ed852e2009-09-05 21:47:34 +00001170 break;
1171 }
1172 case MeanSquaredErrorMetric:
1173 {
cristy8a9106f2011-07-05 14:39:26 +00001174 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001175 channel_distortion,exception);
1176 break;
1177 }
cristy4c929a72010-11-24 18:54:42 +00001178 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001179 default:
cristy4c929a72010-11-24 18:54:42 +00001180 {
cristy3cc758f2010-11-27 01:33:49 +00001181 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001182 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001183 break;
1184 }
cristy3ed852e2009-09-05 21:47:34 +00001185 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001186 {
cristy8a9106f2011-07-05 14:39:26 +00001187 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001188 channel_distortion,exception);
1189 break;
1190 }
1191 case PeakSignalToNoiseRatioMetric:
1192 {
cristy8a9106f2011-07-05 14:39:26 +00001193 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001194 channel_distortion,exception);
1195 break;
1196 }
1197 case RootMeanSquaredErrorMetric:
1198 {
cristy8a9106f2011-07-05 14:39:26 +00001199 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001200 channel_distortion,exception);
1201 break;
1202 }
1203 }
cristy5f95f4f2011-10-23 01:01:01 +00001204 *distortion=channel_distortion[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001205 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1206 return(status);
1207}
1208
1209/*
1210%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1211% %
1212% %
1213% %
cristy8a9106f2011-07-05 14:39:26 +00001214% 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 +00001215% %
1216% %
1217% %
1218%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1219%
cristy44097f52012-12-16 19:56:20 +00001220% GetImageDistortions() compares the pixel channels of an image to a
cristy3ed852e2009-09-05 21:47:34 +00001221% reconstructed image and returns the specified distortion metric for each
1222% channel.
1223%
cristy44097f52012-12-16 19:56:20 +00001224% The format of the GetImageDistortions method is:
cristy3ed852e2009-09-05 21:47:34 +00001225%
cristy8a9106f2011-07-05 14:39:26 +00001226% double *GetImageDistortions(const Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001227% const Image *reconstruct_image,const MetricType metric,
1228% ExceptionInfo *exception)
1229%
1230% A description of each parameter follows:
1231%
1232% o image: the image.
1233%
1234% o reconstruct_image: the reconstruct image.
1235%
1236% o metric: the metric.
1237%
1238% o exception: return any errors or warnings in this structure.
1239%
1240*/
cristy8a9106f2011-07-05 14:39:26 +00001241MagickExport double *GetImageDistortions(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001242 const Image *reconstruct_image,const MetricType metric,
1243 ExceptionInfo *exception)
1244{
1245 double
1246 *channel_distortion;
1247
1248 MagickBooleanType
1249 status;
1250
1251 size_t
1252 length;
1253
1254 assert(image != (Image *) NULL);
1255 assert(image->signature == MagickSignature);
1256 if (image->debug != MagickFalse)
1257 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1258 assert(reconstruct_image != (const Image *) NULL);
1259 assert(reconstruct_image->signature == MagickSignature);
1260 if (image->debug != MagickFalse)
1261 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1262 if ((reconstruct_image->columns != image->columns) ||
1263 (reconstruct_image->rows != image->rows))
1264 {
cristyc82a27b2011-10-21 01:07:16 +00001265 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
cristyefe601c2013-01-05 17:51:12 +00001266 "ImageSizeDiffers","`%s'",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001267 return((double *) NULL);
1268 }
1269 /*
1270 Get image distortion.
1271 */
cristy3fc482f2011-09-23 00:43:35 +00001272 length=MaxPixelChannels+1UL;
cristy3ed852e2009-09-05 21:47:34 +00001273 channel_distortion=(double *) AcquireQuantumMemory(length,
1274 sizeof(*channel_distortion));
1275 if (channel_distortion == (double *) NULL)
1276 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1277 (void) ResetMagickMemory(channel_distortion,0,length*
1278 sizeof(*channel_distortion));
cristyda16f162011-02-19 23:52:17 +00001279 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +00001280 switch (metric)
1281 {
1282 case AbsoluteErrorMetric:
1283 {
cristy8a9106f2011-07-05 14:39:26 +00001284 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1285 exception);
cristy3ed852e2009-09-05 21:47:34 +00001286 break;
1287 }
cristy343eee92010-12-11 02:17:57 +00001288 case FuzzErrorMetric:
1289 {
cristy8a9106f2011-07-05 14:39:26 +00001290 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1291 exception);
cristy343eee92010-12-11 02:17:57 +00001292 break;
1293 }
cristy3ed852e2009-09-05 21:47:34 +00001294 case MeanAbsoluteErrorMetric:
1295 {
cristy8a9106f2011-07-05 14:39:26 +00001296 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001297 channel_distortion,exception);
1298 break;
1299 }
1300 case MeanErrorPerPixelMetric:
1301 {
cristy8a9106f2011-07-05 14:39:26 +00001302 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1303 exception);
cristy3ed852e2009-09-05 21:47:34 +00001304 break;
1305 }
1306 case MeanSquaredErrorMetric:
1307 {
cristy8a9106f2011-07-05 14:39:26 +00001308 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001309 channel_distortion,exception);
1310 break;
1311 }
cristy4c929a72010-11-24 18:54:42 +00001312 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001313 default:
cristy4c929a72010-11-24 18:54:42 +00001314 {
cristy3cc758f2010-11-27 01:33:49 +00001315 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001316 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001317 break;
1318 }
cristy3ed852e2009-09-05 21:47:34 +00001319 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001320 {
cristy8a9106f2011-07-05 14:39:26 +00001321 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001322 channel_distortion,exception);
1323 break;
1324 }
1325 case PeakSignalToNoiseRatioMetric:
1326 {
cristy8a9106f2011-07-05 14:39:26 +00001327 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001328 channel_distortion,exception);
1329 break;
1330 }
1331 case RootMeanSquaredErrorMetric:
1332 {
cristy8a9106f2011-07-05 14:39:26 +00001333 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001334 channel_distortion,exception);
1335 break;
1336 }
1337 }
cristyda16f162011-02-19 23:52:17 +00001338 if (status == MagickFalse)
1339 {
1340 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1341 return((double *) NULL);
1342 }
cristy3ed852e2009-09-05 21:47:34 +00001343 return(channel_distortion);
1344}
1345
1346/*
1347%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1348% %
1349% %
1350% %
1351% I s I m a g e s E q u a l %
1352% %
1353% %
1354% %
1355%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1356%
1357% IsImagesEqual() measures the difference between colors at each pixel
1358% location of two images. A value other than 0 means the colors match
1359% exactly. Otherwise an error measure is computed by summing over all
1360% pixels in an image the distance squared in RGB space between each image
1361% pixel and its corresponding pixel in the reconstruct image. The error
1362% measure is assigned to these image members:
1363%
1364% o mean_error_per_pixel: The mean error for any single pixel in
1365% the image.
1366%
1367% o normalized_mean_error: The normalized mean quantization error for
1368% any single pixel in the image. This distance measure is normalized to
1369% a range between 0 and 1. It is independent of the range of red, green,
1370% and blue values in the image.
1371%
1372% o normalized_maximum_error: The normalized maximum quantization
1373% error for any single pixel in the image. This distance measure is
1374% normalized to a range between 0 and 1. It is independent of the range
1375% of red, green, and blue values in your image.
1376%
1377% A small normalized mean square error, accessed as
1378% image->normalized_mean_error, suggests the images are very similar in
1379% spatial layout and color.
1380%
1381% The format of the IsImagesEqual method is:
1382%
1383% MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001384% const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001385%
1386% A description of each parameter follows.
1387%
1388% o image: the image.
1389%
1390% o reconstruct_image: the reconstruct image.
1391%
cristy018f07f2011-09-04 21:15:19 +00001392% o exception: return any errors or warnings in this structure.
1393%
cristy3ed852e2009-09-05 21:47:34 +00001394*/
1395MagickExport MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001396 const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001397{
cristyc4c8d132010-01-07 01:58:38 +00001398 CacheView
1399 *image_view,
1400 *reconstruct_view;
1401
cristy3ed852e2009-09-05 21:47:34 +00001402 MagickBooleanType
1403 status;
1404
cristya19f1d72012-08-07 18:24:38 +00001405 double
cristy3ed852e2009-09-05 21:47:34 +00001406 area,
1407 maximum_error,
1408 mean_error,
1409 mean_error_per_pixel;
1410
cristy9d314ff2011-03-09 01:30:28 +00001411 ssize_t
1412 y;
1413
cristy3ed852e2009-09-05 21:47:34 +00001414 assert(image != (Image *) NULL);
1415 assert(image->signature == MagickSignature);
1416 assert(reconstruct_image != (const Image *) NULL);
1417 assert(reconstruct_image->signature == MagickSignature);
1418 if ((reconstruct_image->columns != image->columns) ||
1419 (reconstruct_image->rows != image->rows))
1420 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1421 area=0.0;
1422 maximum_error=0.0;
1423 mean_error_per_pixel=0.0;
1424 mean_error=0.0;
cristy46ff2672012-12-14 15:32:26 +00001425 image_view=AcquireVirtualCacheView(image,exception);
1426 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristybb503372010-05-27 20:51:26 +00001427 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001428 {
cristy4c08aed2011-07-01 19:47:50 +00001429 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001430 *restrict p,
1431 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001432
cristybb503372010-05-27 20:51:26 +00001433 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001434 x;
1435
1436 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1437 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1438 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001439 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001440 break;
cristybb503372010-05-27 20:51:26 +00001441 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001442 {
cristyd5c15f92011-09-23 00:58:33 +00001443 register ssize_t
1444 i;
cristy3ed852e2009-09-05 21:47:34 +00001445
cristy10a6c612012-01-29 21:41:05 +00001446 if (GetPixelMask(image,p) != 0)
1447 {
1448 p+=GetPixelChannels(image);
1449 q+=GetPixelChannels(reconstruct_image);
1450 continue;
1451 }
cristyd5c15f92011-09-23 00:58:33 +00001452 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1453 {
cristya19f1d72012-08-07 18:24:38 +00001454 double
cristyd5c15f92011-09-23 00:58:33 +00001455 distance;
1456
cristy5a23c552013-02-13 14:34:28 +00001457 PixelChannel channel=GetPixelChannelChannel(image,i);
1458 PixelTrait traits=GetPixelChannelTraits(image,channel);
1459 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1460 channel);
cristyd5c15f92011-09-23 00:58:33 +00001461 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001462 (reconstruct_traits == UndefinedPixelTrait) ||
1463 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001464 continue;
cristya19f1d72012-08-07 18:24:38 +00001465 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
cristy0beccfa2011-09-25 20:47:53 +00001466 channel,q));
cristyd5c15f92011-09-23 00:58:33 +00001467 mean_error_per_pixel+=distance;
1468 mean_error+=distance*distance;
1469 if (distance > maximum_error)
1470 maximum_error=distance;
1471 area++;
1472 }
cristyed231572011-07-14 02:18:59 +00001473 p+=GetPixelChannels(image);
1474 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001475 }
1476 }
1477 reconstruct_view=DestroyCacheView(reconstruct_view);
1478 image_view=DestroyCacheView(image_view);
1479 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1480 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1481 mean_error/area);
1482 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1483 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1484 return(status);
1485}
1486
1487/*
1488%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1489% %
1490% %
1491% %
1492% S i m i l a r i t y I m a g e %
1493% %
1494% %
1495% %
1496%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1497%
1498% SimilarityImage() compares the reference image of the image and returns the
1499% best match offset. In addition, it returns a similarity image such that an
1500% exact match location is completely white and if none of the pixels match,
1501% black, otherwise some gray level in-between.
1502%
1503% The format of the SimilarityImageImage method is:
1504%
1505% Image *SimilarityImage(const Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001506% const MetricType metric,RectangleInfo *offset,double *similarity,
1507% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001508%
1509% A description of each parameter follows:
1510%
1511% o image: the image.
1512%
1513% o reference: find an area of the image that closely resembles this image.
1514%
cristy09136812011-10-18 15:24:30 +00001515% o metric: the metric.
1516%
cristy3ed852e2009-09-05 21:47:34 +00001517% o the best match offset of the reference image within the image.
1518%
1519% o similarity: the computed similarity between the images.
1520%
1521% o exception: return any errors or warnings in this structure.
1522%
1523*/
1524
1525static double GetSimilarityMetric(const Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001526 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1527 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001528{
1529 double
cristy3cc758f2010-11-27 01:33:49 +00001530 distortion;
cristy3ed852e2009-09-05 21:47:34 +00001531
cristy713ff212010-11-26 21:56:11 +00001532 Image
1533 *similarity_image;
cristy3ed852e2009-09-05 21:47:34 +00001534
cristy09136812011-10-18 15:24:30 +00001535 MagickBooleanType
1536 status;
1537
cristy713ff212010-11-26 21:56:11 +00001538 RectangleInfo
1539 geometry;
cristy3ed852e2009-09-05 21:47:34 +00001540
cristy713ff212010-11-26 21:56:11 +00001541 SetGeometry(reference,&geometry);
1542 geometry.x=x_offset;
1543 geometry.y=y_offset;
1544 similarity_image=CropImage(image,&geometry,exception);
1545 if (similarity_image == (Image *) NULL)
1546 return(0.0);
cristy09136812011-10-18 15:24:30 +00001547 distortion=0.0;
1548 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
cristy3cc758f2010-11-27 01:33:49 +00001549 exception);
cristy713ff212010-11-26 21:56:11 +00001550 similarity_image=DestroyImage(similarity_image);
cristy09136812011-10-18 15:24:30 +00001551 if (status == MagickFalse)
1552 return(0.0);
cristy3cc758f2010-11-27 01:33:49 +00001553 return(distortion);
cristy3ed852e2009-09-05 21:47:34 +00001554}
1555
1556MagickExport Image *SimilarityImage(Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001557 const MetricType metric,RectangleInfo *offset,double *similarity_metric,
1558 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001559{
1560#define SimilarityImageTag "Similarity/Image"
1561
cristyc4c8d132010-01-07 01:58:38 +00001562 CacheView
1563 *similarity_view;
1564
cristy3ed852e2009-09-05 21:47:34 +00001565 Image
1566 *similarity_image;
1567
1568 MagickBooleanType
1569 status;
1570
cristybb503372010-05-27 20:51:26 +00001571 MagickOffsetType
1572 progress;
1573
1574 ssize_t
1575 y;
1576
cristy3ed852e2009-09-05 21:47:34 +00001577 assert(image != (const Image *) NULL);
1578 assert(image->signature == MagickSignature);
1579 if (image->debug != MagickFalse)
1580 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1581 assert(exception != (ExceptionInfo *) NULL);
1582 assert(exception->signature == MagickSignature);
1583 assert(offset != (RectangleInfo *) NULL);
1584 SetGeometry(reference,offset);
1585 *similarity_metric=1.0;
cristyf9255b92010-08-14 22:59:00 +00001586 if ((reference->columns > image->columns) || (reference->rows > image->rows))
cristy3ed852e2009-09-05 21:47:34 +00001587 ThrowImageException(ImageError,"ImageSizeDiffers");
1588 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1589 image->rows-reference->rows+1,MagickTrue,exception);
1590 if (similarity_image == (Image *) NULL)
1591 return((Image *) NULL);
cristyd5c15f92011-09-23 00:58:33 +00001592 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1593 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001594 {
cristy3ed852e2009-09-05 21:47:34 +00001595 similarity_image=DestroyImage(similarity_image);
1596 return((Image *) NULL);
1597 }
1598 /*
1599 Measure similarity of reference image against image.
1600 */
1601 status=MagickTrue;
1602 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001603 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001604#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001605 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001606 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001607#endif
cristybb503372010-05-27 20:51:26 +00001608 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
cristy3ed852e2009-09-05 21:47:34 +00001609 {
1610 double
1611 similarity;
1612
cristy4c08aed2011-07-01 19:47:50 +00001613 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001614 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001615
cristy49dd6a02011-09-24 23:08:01 +00001616 register ssize_t
1617 x;
1618
cristy3ed852e2009-09-05 21:47:34 +00001619 if (status == MagickFalse)
1620 continue;
cristy3cc758f2010-11-27 01:33:49 +00001621 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1622 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001623 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001624 {
1625 status=MagickFalse;
1626 continue;
1627 }
cristybb503372010-05-27 20:51:26 +00001628 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
cristy3ed852e2009-09-05 21:47:34 +00001629 {
cristy49dd6a02011-09-24 23:08:01 +00001630 register ssize_t
1631 i;
1632
cristy09136812011-10-18 15:24:30 +00001633 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
cristyb5d5f722009-11-04 03:03:49 +00001634#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001635 #pragma omp critical (MagickCore_SimilarityImage)
cristy3ed852e2009-09-05 21:47:34 +00001636#endif
1637 if (similarity < *similarity_metric)
1638 {
1639 *similarity_metric=similarity;
1640 offset->x=x;
1641 offset->y=y;
1642 }
cristyc94ba6f2012-01-29 23:19:58 +00001643 if (GetPixelMask(similarity_image,q) != 0)
cristy10a6c612012-01-29 21:41:05 +00001644 {
cristyc94ba6f2012-01-29 23:19:58 +00001645 q+=GetPixelChannels(similarity_image);
cristy10a6c612012-01-29 21:41:05 +00001646 continue;
1647 }
cristyc94ba6f2012-01-29 23:19:58 +00001648 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
cristy49dd6a02011-09-24 23:08:01 +00001649 {
cristy5a23c552013-02-13 14:34:28 +00001650 PixelChannel channel=GetPixelChannelChannel(image,i);
1651 PixelTrait traits=GetPixelChannelTraits(image,channel);
1652 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1653 channel);
cristy49dd6a02011-09-24 23:08:01 +00001654 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001655 (similarity_traits == UndefinedPixelTrait) ||
1656 ((similarity_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001657 continue;
cristy0beccfa2011-09-25 20:47:53 +00001658 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1659 QuantumRange*similarity),q);
cristy49dd6a02011-09-24 23:08:01 +00001660 }
cristyed231572011-07-14 02:18:59 +00001661 q+=GetPixelChannels(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001662 }
anthonye5b39652012-04-21 05:37:29 +00001663 if (IfMagickFalse( SyncCacheViewAuthenticPixels(similarity_view,exception) ))
cristy3ed852e2009-09-05 21:47:34 +00001664 status=MagickFalse;
1665 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1666 {
cristyb5d5f722009-11-04 03:03:49 +00001667#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001668 #pragma omp critical (MagickCore_SimilarityImage)
cristy3ed852e2009-09-05 21:47:34 +00001669#endif
cristyac245f82012-05-05 17:13:57 +00001670 if (IfMagickFalse(SetImageProgress(image,SimilarityImageTag,
anthonye5b39652012-04-21 05:37:29 +00001671 progress++,image->rows) ))
cristy3ed852e2009-09-05 21:47:34 +00001672 status=MagickFalse;
1673 }
1674 }
1675 similarity_view=DestroyCacheView(similarity_view);
cristy1c2f48d2012-12-14 01:20:55 +00001676 if (status == MagickFalse)
1677 similarity_image=DestroyImage(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001678 return(similarity_image);
1679}