blob: ba113d34e93b9e5987b0be6f629a810a70f8a576 [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 %
cristyde984cd2013-12-01 14:49:27 +000016% Cristy %
cristy3ed852e2009-09-05 21:47:34 +000017% December 2003 %
18% %
19% %
cristyfe676ee2013-11-18 13:03:38 +000020% Copyright 1999-2014 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"
cristyc97da4b2014-01-18 15:58:31 +000045#include "MagickCore/attribute.h"
cristy4c08aed2011-07-01 19:47:50 +000046#include "MagickCore/cache-view.h"
cristy6a2180c2013-05-27 10:28:36 +000047#include "MagickCore/channel.h"
cristy4c08aed2011-07-01 19:47:50 +000048#include "MagickCore/client.h"
49#include "MagickCore/color.h"
50#include "MagickCore/color-private.h"
51#include "MagickCore/colorspace.h"
52#include "MagickCore/colorspace-private.h"
53#include "MagickCore/compare.h"
54#include "MagickCore/composite-private.h"
55#include "MagickCore/constitute.h"
56#include "MagickCore/exception-private.h"
57#include "MagickCore/geometry.h"
58#include "MagickCore/image-private.h"
59#include "MagickCore/list.h"
60#include "MagickCore/log.h"
61#include "MagickCore/memory_.h"
62#include "MagickCore/monitor.h"
63#include "MagickCore/monitor-private.h"
64#include "MagickCore/option.h"
65#include "MagickCore/pixel-accessor.h"
cristyb92495a2013-08-20 00:10:59 +000066#include "MagickCore/property.h"
cristy4c08aed2011-07-01 19:47:50 +000067#include "MagickCore/resource_.h"
68#include "MagickCore/string_.h"
69#include "MagickCore/statistic.h"
cristy16881e62012-05-06 14:41:29 +000070#include "MagickCore/thread-private.h"
cristy4c08aed2011-07-01 19:47:50 +000071#include "MagickCore/transform.h"
72#include "MagickCore/utility.h"
73#include "MagickCore/version.h"
cristy3ed852e2009-09-05 21:47:34 +000074
75/*
76%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
77% %
78% %
79% %
cristy8a9106f2011-07-05 14:39:26 +000080% C o m p a r e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +000081% %
82% %
83% %
84%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
85%
cristyaeded782012-09-11 23:39:36 +000086% CompareImages() compares one or more pixel channels of an image to a
cristy8a9106f2011-07-05 14:39:26 +000087% reconstructed image and returns the difference image.
cristy3ed852e2009-09-05 21:47:34 +000088%
cristy8a9106f2011-07-05 14:39:26 +000089% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +000090%
cristy8a9106f2011-07-05 14:39:26 +000091% Image *CompareImages(const Image *image,const Image *reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +000092% const MetricType metric,double *distortion,ExceptionInfo *exception)
93%
94% A description of each parameter follows:
95%
96% o image: the image.
97%
98% o reconstruct_image: the reconstruct image.
99%
cristy3ed852e2009-09-05 21:47:34 +0000100% o metric: the metric.
101%
102% o distortion: the computed distortion between the images.
103%
104% o exception: return any errors or warnings in this structure.
105%
106*/
cristy3ed852e2009-09-05 21:47:34 +0000107MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
108 const MetricType metric,double *distortion,ExceptionInfo *exception)
109{
cristyc4c8d132010-01-07 01:58:38 +0000110 CacheView
111 *highlight_view,
112 *image_view,
113 *reconstruct_view;
114
cristy3ed852e2009-09-05 21:47:34 +0000115 const char
116 *artifact;
117
118 Image
119 *difference_image,
120 *highlight_image;
121
cristy3ed852e2009-09-05 21:47:34 +0000122 MagickBooleanType
123 status;
124
cristy4c08aed2011-07-01 19:47:50 +0000125 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000126 highlight,
cristy3fc482f2011-09-23 00:43:35 +0000127 lowlight;
cristy3ed852e2009-09-05 21:47:34 +0000128
cristy49dd6a02011-09-24 23:08:01 +0000129 ssize_t
130 y;
131
cristy3ed852e2009-09-05 21:47:34 +0000132 assert(image != (Image *) NULL);
133 assert(image->signature == MagickSignature);
134 if (image->debug != MagickFalse)
135 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
136 assert(reconstruct_image != (const Image *) NULL);
137 assert(reconstruct_image->signature == MagickSignature);
138 assert(distortion != (double *) NULL);
139 *distortion=0.0;
140 if (image->debug != MagickFalse)
141 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8b520b42014-01-30 00:58:45 +0000142 if (metric != PerceptualHashErrorMetric)
143 if ((reconstruct_image->columns != image->columns) ||
144 (reconstruct_image->rows != image->rows))
145 ThrowImageException(ImageError,"ImageSizeDiffers");
cristy8a9106f2011-07-05 14:39:26 +0000146 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
147 exception);
cristy3ed852e2009-09-05 21:47:34 +0000148 if (status == MagickFalse)
149 return((Image *) NULL);
150 difference_image=CloneImage(image,0,0,MagickTrue,exception);
151 if (difference_image == (Image *) NULL)
152 return((Image *) NULL);
cristy63240882011-08-05 19:05:27 +0000153 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +0000154 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
155 exception);
156 if (highlight_image == (Image *) NULL)
157 {
158 difference_image=DestroyImage(difference_image);
159 return((Image *) NULL);
160 }
cristy3fc482f2011-09-23 00:43:35 +0000161 status=SetImageStorageClass(highlight_image,DirectClass,exception);
162 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000163 {
cristy3ed852e2009-09-05 21:47:34 +0000164 difference_image=DestroyImage(difference_image);
165 highlight_image=DestroyImage(highlight_image);
166 return((Image *) NULL);
167 }
cristy63240882011-08-05 19:05:27 +0000168 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
cristyca611542013-02-19 00:54:03 +0000169 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000170 artifact=GetImageArtifact(image,"highlight-color");
171 if (artifact != (const char *) NULL)
cristyf9d6dc02012-01-19 02:14:44 +0000172 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
cristyca611542013-02-19 00:54:03 +0000173 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000174 artifact=GetImageArtifact(image,"lowlight-color");
175 if (artifact != (const char *) NULL)
cristy0b1a7972011-10-22 22:17:02 +0000176 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000177 /*
178 Generate difference image.
179 */
180 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000181 image_view=AcquireVirtualCacheView(image,exception);
182 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
183 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000184#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000185 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000186 magick_threads(image,highlight_image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000187#endif
cristybb503372010-05-27 20:51:26 +0000188 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000189 {
190 MagickBooleanType
191 sync;
192
cristy4c08aed2011-07-01 19:47:50 +0000193 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000194 *restrict p,
195 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000196
cristy4c08aed2011-07-01 19:47:50 +0000197 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000198 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000199
cristy49dd6a02011-09-24 23:08:01 +0000200 register ssize_t
201 x;
202
cristy3ed852e2009-09-05 21:47:34 +0000203 if (status == MagickFalse)
204 continue;
205 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
206 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
207 1,exception);
208 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
209 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000210 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
211 (r == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000212 {
213 status=MagickFalse;
214 continue;
215 }
cristybb503372010-05-27 20:51:26 +0000216 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000217 {
cristydb5c82c2013-02-22 00:41:33 +0000218 double
219 Da,
220 Sa;
221
cristy3ed852e2009-09-05 21:47:34 +0000222 MagickStatusType
223 difference;
224
cristy3fc482f2011-09-23 00:43:35 +0000225 register ssize_t
226 i;
227
cristy883fde12013-04-08 00:50:13 +0000228 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000229 {
cristyd09f8802012-02-04 16:44:10 +0000230 SetPixelInfoPixel(highlight_image,&lowlight,r);
cristy10a6c612012-01-29 21:41:05 +0000231 p+=GetPixelChannels(image);
232 q+=GetPixelChannels(reconstruct_image);
233 r+=GetPixelChannels(highlight_image);
234 continue;
235 }
cristyd09f8802012-02-04 16:44:10 +0000236 difference=MagickFalse;
cristydb5c82c2013-02-22 00:41:33 +0000237 Sa=QuantumScale*GetPixelAlpha(image,p);
238 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000239 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
240 {
cristya19f1d72012-08-07 18:24:38 +0000241 double
cristy0beccfa2011-09-25 20:47:53 +0000242 distance;
243
cristy5a23c552013-02-13 14:34:28 +0000244 PixelChannel channel=GetPixelChannelChannel(image,i);
245 PixelTrait traits=GetPixelChannelTraits(image,channel);
246 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
247 channel);
cristy3fc482f2011-09-23 00:43:35 +0000248 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000249 (reconstruct_traits == UndefinedPixelTrait) ||
250 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy3fc482f2011-09-23 00:43:35 +0000251 continue;
cristydb5c82c2013-02-22 00:41:33 +0000252 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
cristy0beccfa2011-09-25 20:47:53 +0000253 if (fabs((double) distance) >= MagickEpsilon)
cristy3fc482f2011-09-23 00:43:35 +0000254 difference=MagickTrue;
255 }
256 if (difference == MagickFalse)
cristy803640d2011-11-17 02:11:32 +0000257 SetPixelInfoPixel(highlight_image,&lowlight,r);
cristy3fc482f2011-09-23 00:43:35 +0000258 else
cristy803640d2011-11-17 02:11:32 +0000259 SetPixelInfoPixel(highlight_image,&highlight,r);
cristyed231572011-07-14 02:18:59 +0000260 p+=GetPixelChannels(image);
261 q+=GetPixelChannels(reconstruct_image);
262 r+=GetPixelChannels(highlight_image);
cristy3ed852e2009-09-05 21:47:34 +0000263 }
264 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
265 if (sync == MagickFalse)
266 status=MagickFalse;
267 }
268 highlight_view=DestroyCacheView(highlight_view);
269 reconstruct_view=DestroyCacheView(reconstruct_view);
270 image_view=DestroyCacheView(image_view);
cristyfeb3e962012-03-29 17:25:55 +0000271 (void) CompositeImage(difference_image,highlight_image,image->compose,
cristy39172402012-03-30 13:04:39 +0000272 MagickTrue,0,0,exception);
cristy3ed852e2009-09-05 21:47:34 +0000273 highlight_image=DestroyImage(highlight_image);
274 if (status == MagickFalse)
275 difference_image=DestroyImage(difference_image);
276 return(difference_image);
277}
278
279/*
280%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
281% %
282% %
283% %
cristy8a9106f2011-07-05 14:39:26 +0000284% G e t I m a g e D i s t o r t i o n %
cristy3ed852e2009-09-05 21:47:34 +0000285% %
286% %
287% %
288%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
289%
cristyaeded782012-09-11 23:39:36 +0000290% GetImageDistortion() compares one or more pixel channels of an image to a
cristy8a9106f2011-07-05 14:39:26 +0000291% reconstructed image and returns the specified distortion metric.
cristy3ed852e2009-09-05 21:47:34 +0000292%
cristy44097f52012-12-16 19:56:20 +0000293% The format of the GetImageDistortion method is:
cristy3ed852e2009-09-05 21:47:34 +0000294%
cristy8a9106f2011-07-05 14:39:26 +0000295% MagickBooleanType GetImageDistortion(const Image *image,
296% const Image *reconstruct_image,const MetricType metric,
297% double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000298%
299% A description of each parameter follows:
300%
301% o image: the image.
302%
303% o reconstruct_image: the reconstruct image.
304%
cristy3ed852e2009-09-05 21:47:34 +0000305% o metric: the metric.
306%
307% o distortion: the computed distortion between the images.
308%
309% o exception: return any errors or warnings in this structure.
310%
311*/
312
cristydb5c82c2013-02-22 00:41:33 +0000313static inline double MagickMax(const double x,const double y)
314{
315 if (x > y)
316 return(x);
317 return(y);
318}
319
cristy3cc758f2010-11-27 01:33:49 +0000320static MagickBooleanType GetAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000321 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000322{
cristyc4c8d132010-01-07 01:58:38 +0000323 CacheView
324 *image_view,
325 *reconstruct_view;
326
cristydb5c82c2013-02-22 00:41:33 +0000327 double
328 fuzz;
329
cristy3ed852e2009-09-05 21:47:34 +0000330 MagickBooleanType
331 status;
332
cristy9d314ff2011-03-09 01:30:28 +0000333 ssize_t
334 y;
335
cristy3ed852e2009-09-05 21:47:34 +0000336 /*
337 Compute the absolute difference in pixels between two images.
338 */
339 status=MagickTrue;
cristydb5c82c2013-02-22 00:41:33 +0000340 if (image->fuzz == 0.0)
341 fuzz=MagickMax(reconstruct_image->fuzz,MagickSQ1_2)*
342 MagickMax(reconstruct_image->fuzz,MagickSQ1_2);
343 else
344 if (reconstruct_image->fuzz == 0.0)
345 fuzz=MagickMax(image->fuzz,MagickSQ1_2)*
346 MagickMax(image->fuzz,MagickSQ1_2);
347 else
348 fuzz=MagickMax(image->fuzz,MagickSQ1_2)*
349 MagickMax(reconstruct_image->fuzz,MagickSQ1_2);
cristy46ff2672012-12-14 15:32:26 +0000350 image_view=AcquireVirtualCacheView(image,exception);
351 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000352#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000353 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000354 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000355#endif
cristybb503372010-05-27 20:51:26 +0000356 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000357 {
358 double
cristy3fc482f2011-09-23 00:43:35 +0000359 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000360
cristy4c08aed2011-07-01 19:47:50 +0000361 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000362 *restrict p,
363 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000364
cristybb503372010-05-27 20:51:26 +0000365 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000366 i,
367 x;
368
369 if (status == MagickFalse)
370 continue;
371 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
372 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
373 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000374 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000375 {
376 status=MagickFalse;
377 continue;
378 }
cristy3ed852e2009-09-05 21:47:34 +0000379 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000380 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000381 {
cristydb5c82c2013-02-22 00:41:33 +0000382 double
383 Da,
384 Sa;
385
cristy3fc482f2011-09-23 00:43:35 +0000386 MagickBooleanType
387 difference;
388
389 register ssize_t
390 i;
391
cristy883fde12013-04-08 00:50:13 +0000392 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000393 {
394 p+=GetPixelChannels(image);
395 q+=GetPixelChannels(reconstruct_image);
396 continue;
397 }
cristyd09f8802012-02-04 16:44:10 +0000398 difference=MagickFalse;
cristydb5c82c2013-02-22 00:41:33 +0000399 Sa=QuantumScale*GetPixelAlpha(image,p);
400 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000401 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
402 {
cristydb5c82c2013-02-22 00:41:33 +0000403 double
404 distance;
405
cristy5a23c552013-02-13 14:34:28 +0000406 PixelChannel channel=GetPixelChannelChannel(image,i);
407 PixelTrait traits=GetPixelChannelTraits(image,channel);
408 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
409 channel);
cristy3fc482f2011-09-23 00:43:35 +0000410 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000411 (reconstruct_traits == UndefinedPixelTrait) ||
412 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000413 continue;
cristydb5c82c2013-02-22 00:41:33 +0000414 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
415 if ((distance*distance) > fuzz)
416 {
417 difference=MagickTrue;
418 break;
419 }
cristy3fc482f2011-09-23 00:43:35 +0000420 }
421 if (difference != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000422 {
cristy3fc482f2011-09-23 00:43:35 +0000423 channel_distortion[i]++;
cristy5f95f4f2011-10-23 01:01:01 +0000424 channel_distortion[CompositePixelChannel]++;
cristy3ed852e2009-09-05 21:47:34 +0000425 }
cristyed231572011-07-14 02:18:59 +0000426 p+=GetPixelChannels(image);
427 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000428 }
cristyb5d5f722009-11-04 03:03:49 +0000429#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000430 #pragma omp critical (MagickCore_GetAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +0000431#endif
cristy3fc482f2011-09-23 00:43:35 +0000432 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000433 distortion[i]+=channel_distortion[i];
434 }
435 reconstruct_view=DestroyCacheView(reconstruct_view);
436 image_view=DestroyCacheView(image_view);
437 return(status);
438}
439
cristy49dd6a02011-09-24 23:08:01 +0000440static size_t GetImageChannels(const Image *image)
cristy3ed852e2009-09-05 21:47:34 +0000441{
cristy3fc482f2011-09-23 00:43:35 +0000442 register ssize_t
443 i;
444
cristy49dd6a02011-09-24 23:08:01 +0000445 size_t
cristy3ed852e2009-09-05 21:47:34 +0000446 channels;
447
448 channels=0;
cristy3fc482f2011-09-23 00:43:35 +0000449 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
450 {
cristy5a23c552013-02-13 14:34:28 +0000451 PixelChannel channel=GetPixelChannelChannel(image,i);
452 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristy25a5f2f2011-09-24 14:09:43 +0000453 if ((traits & UpdatePixelTrait) != 0)
cristy3fc482f2011-09-23 00:43:35 +0000454 channels++;
455 }
cristy3ed852e2009-09-05 21:47:34 +0000456 return(channels);
457}
458
cristy343eee92010-12-11 02:17:57 +0000459static MagickBooleanType GetFuzzDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000460 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy343eee92010-12-11 02:17:57 +0000461{
462 CacheView
463 *image_view,
464 *reconstruct_view;
465
cristy343eee92010-12-11 02:17:57 +0000466 MagickBooleanType
467 status;
468
469 register ssize_t
470 i;
471
cristy9d314ff2011-03-09 01:30:28 +0000472 ssize_t
473 y;
474
cristy343eee92010-12-11 02:17:57 +0000475 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000476 image_view=AcquireVirtualCacheView(image,exception);
477 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristy343eee92010-12-11 02:17:57 +0000478#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000479 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000480 magick_threads(image,image,image->rows,1)
cristy343eee92010-12-11 02:17:57 +0000481#endif
482 for (y=0; y < (ssize_t) image->rows; y++)
483 {
484 double
cristy3fc482f2011-09-23 00:43:35 +0000485 channel_distortion[MaxPixelChannels+1];
cristy343eee92010-12-11 02:17:57 +0000486
cristy4c08aed2011-07-01 19:47:50 +0000487 register const Quantum
cristy343eee92010-12-11 02:17:57 +0000488 *restrict p,
489 *restrict q;
490
491 register ssize_t
492 i,
493 x;
494
495 if (status == MagickFalse)
496 continue;
497 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristye6529ff2011-02-04 20:05:32 +0000498 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
499 1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000500 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy343eee92010-12-11 02:17:57 +0000501 {
502 status=MagickFalse;
503 continue;
504 }
cristy343eee92010-12-11 02:17:57 +0000505 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
506 for (x=0; x < (ssize_t) image->columns; x++)
507 {
cristydb5c82c2013-02-22 00:41:33 +0000508 double
509 Da,
510 Sa;
511
cristy3fc482f2011-09-23 00:43:35 +0000512 register ssize_t
513 i;
cristy343eee92010-12-11 02:17:57 +0000514
cristy883fde12013-04-08 00:50:13 +0000515 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000516 {
517 p+=GetPixelChannels(image);
518 q+=GetPixelChannels(reconstruct_image);
519 continue;
520 }
cristydb5c82c2013-02-22 00:41:33 +0000521 Sa=QuantumScale*GetPixelAlpha(image,p);
522 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000523 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
524 {
cristya19f1d72012-08-07 18:24:38 +0000525 double
cristy3fc482f2011-09-23 00:43:35 +0000526 distance;
527
cristy5a23c552013-02-13 14:34:28 +0000528 PixelChannel channel=GetPixelChannelChannel(image,i);
529 PixelTrait traits=GetPixelChannelTraits(image,channel);
530 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
531 channel);
cristy3fc482f2011-09-23 00:43:35 +0000532 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000533 (reconstruct_traits == UndefinedPixelTrait) ||
534 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000535 continue;
cristydb5c82c2013-02-22 00:41:33 +0000536 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
cristy1eced092012-08-10 23:10:56 +0000537 channel,q));
cristydb5c82c2013-02-22 00:41:33 +0000538 channel_distortion[i]+=distance*distance;
539 channel_distortion[CompositePixelChannel]+=distance*distance;
cristy3fc482f2011-09-23 00:43:35 +0000540 }
cristyed231572011-07-14 02:18:59 +0000541 p+=GetPixelChannels(image);
542 q+=GetPixelChannels(reconstruct_image);
cristy343eee92010-12-11 02:17:57 +0000543 }
544#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ad5c2a2012-05-05 21:32:54 +0000545 #pragma omp critical (MagickCore_GetFuzzDistortion)
cristy343eee92010-12-11 02:17:57 +0000546#endif
cristy3fc482f2011-09-23 00:43:35 +0000547 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000548 distortion[i]+=channel_distortion[i];
549 }
550 reconstruct_view=DestroyCacheView(reconstruct_view);
551 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000552 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000553 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000554 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
555 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
cristy343eee92010-12-11 02:17:57 +0000556 return(status);
557}
558
cristy3cc758f2010-11-27 01:33:49 +0000559static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000560 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000561{
cristyc4c8d132010-01-07 01:58:38 +0000562 CacheView
563 *image_view,
564 *reconstruct_view;
565
cristy3ed852e2009-09-05 21:47:34 +0000566 MagickBooleanType
567 status;
568
cristybb503372010-05-27 20:51:26 +0000569 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000570 i;
571
cristy9d314ff2011-03-09 01:30:28 +0000572 ssize_t
573 y;
574
cristy3ed852e2009-09-05 21:47:34 +0000575 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000576 image_view=AcquireVirtualCacheView(image,exception);
577 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000578#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000579 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000580 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000581#endif
cristybb503372010-05-27 20:51:26 +0000582 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000583 {
584 double
cristy3fc482f2011-09-23 00:43:35 +0000585 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000586
cristy4c08aed2011-07-01 19:47:50 +0000587 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000588 *restrict p,
589 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000590
cristybb503372010-05-27 20:51:26 +0000591 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000592 i,
593 x;
594
595 if (status == MagickFalse)
596 continue;
597 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000598 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
599 1,exception);
600 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000601 {
602 status=MagickFalse;
603 continue;
604 }
cristy3ed852e2009-09-05 21:47:34 +0000605 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000606 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000607 {
cristydb5c82c2013-02-22 00:41:33 +0000608 double
609 Da,
610 Sa;
611
cristy3fc482f2011-09-23 00:43:35 +0000612 register ssize_t
613 i;
cristy3ed852e2009-09-05 21:47:34 +0000614
cristy883fde12013-04-08 00:50:13 +0000615 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000616 {
617 p+=GetPixelChannels(image);
618 q+=GetPixelChannels(reconstruct_image);
619 continue;
620 }
cristydb5c82c2013-02-22 00:41:33 +0000621 Sa=QuantumScale*GetPixelAlpha(image,p);
622 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000623 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
624 {
cristya19f1d72012-08-07 18:24:38 +0000625 double
cristy3fc482f2011-09-23 00:43:35 +0000626 distance;
627
cristy5a23c552013-02-13 14:34:28 +0000628 PixelChannel channel=GetPixelChannelChannel(image,i);
629 PixelTrait traits=GetPixelChannelTraits(image,channel);
630 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
631 channel);
cristy3fc482f2011-09-23 00:43:35 +0000632 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000633 (reconstruct_traits == UndefinedPixelTrait) ||
634 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000635 continue;
cristydb5c82c2013-02-22 00:41:33 +0000636 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
637 channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000638 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000639 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000640 }
cristyed231572011-07-14 02:18:59 +0000641 p+=GetPixelChannels(image);
642 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000643 }
cristyb5d5f722009-11-04 03:03:49 +0000644#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000645 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +0000646#endif
cristy3fc482f2011-09-23 00:43:35 +0000647 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000648 distortion[i]+=channel_distortion[i];
649 }
650 reconstruct_view=DestroyCacheView(reconstruct_view);
651 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000652 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000653 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000654 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000655 return(status);
656}
657
658static MagickBooleanType GetMeanErrorPerPixel(Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000659 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000660{
cristyc4c8d132010-01-07 01:58:38 +0000661 CacheView
662 *image_view,
663 *reconstruct_view;
664
cristy3ed852e2009-09-05 21:47:34 +0000665 MagickBooleanType
666 status;
667
cristya19f1d72012-08-07 18:24:38 +0000668 double
cristy3ed852e2009-09-05 21:47:34 +0000669 area,
cristy3ed852e2009-09-05 21:47:34 +0000670 maximum_error,
671 mean_error;
672
cristy9d314ff2011-03-09 01:30:28 +0000673 ssize_t
674 y;
675
cristy3ed852e2009-09-05 21:47:34 +0000676 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000677 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
cristy883fde12013-04-08 00:50:13 +0000708 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000709 {
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;
cristyce31db42013-02-22 02:20:31 +0000729 distance=fabs((double) (Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
730 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
cristy883fde12013-04-08 00:50:13 +0000806 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000807 {
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);
cristye287bba2013-09-02 20:04:42 +0000883 if ((image_statistics == (ChannelStatistics *) NULL) ||
884 (reconstruct_statistics == (ChannelStatistics *) NULL))
885 {
886 if (image_statistics != (ChannelStatistics *) NULL)
887 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
888 image_statistics);
889 if (reconstruct_statistics != (ChannelStatistics *) NULL)
890 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
891 reconstruct_statistics);
892 return(MagickFalse);
893 }
cristy4c929a72010-11-24 18:54:42 +0000894 status=MagickTrue;
cristy9f48ca62010-11-25 03:06:31 +0000895 progress=0;
cristy3fc482f2011-09-23 00:43:35 +0000896 for (i=0; i <= MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000897 distortion[i]=0.0;
cristy2cb55d52013-07-10 22:20:51 +0000898 area=1.0/((double) image->columns*image->rows);
cristy46ff2672012-12-14 15:32:26 +0000899 image_view=AcquireVirtualCacheView(image,exception);
900 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristy4c929a72010-11-24 18:54:42 +0000901 for (y=0; y < (ssize_t) image->rows; y++)
902 {
cristy4c08aed2011-07-01 19:47:50 +0000903 register const Quantum
cristy4c929a72010-11-24 18:54:42 +0000904 *restrict p,
905 *restrict q;
906
907 register ssize_t
cristy4c929a72010-11-24 18:54:42 +0000908 x;
909
910 if (status == MagickFalse)
911 continue;
912 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy34d6fdc2010-11-26 19:06:08 +0000913 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
914 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000915 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy4c929a72010-11-24 18:54:42 +0000916 {
917 status=MagickFalse;
918 continue;
919 }
cristy4c929a72010-11-24 18:54:42 +0000920 for (x=0; x < (ssize_t) image->columns; x++)
921 {
cristydb5c82c2013-02-22 00:41:33 +0000922 double
923 Da,
924 Sa;
925
cristy3fc482f2011-09-23 00:43:35 +0000926 register ssize_t
927 i;
928
cristy883fde12013-04-08 00:50:13 +0000929 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000930 {
931 p+=GetPixelChannels(image);
932 q+=GetPixelChannels(reconstruct_image);
933 continue;
934 }
cristydb5c82c2013-02-22 00:41:33 +0000935 Sa=QuantumScale*GetPixelAlpha(image,p);
936 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000937 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
938 {
cristy5a23c552013-02-13 14:34:28 +0000939 PixelChannel channel=GetPixelChannelChannel(image,i);
940 PixelTrait traits=GetPixelChannelTraits(image,channel);
941 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
942 channel);
cristy3fc482f2011-09-23 00:43:35 +0000943 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000944 (reconstruct_traits == UndefinedPixelTrait) ||
945 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000946 continue;
cristydb5c82c2013-02-22 00:41:33 +0000947 distortion[i]+=area*QuantumScale*(Sa*p[i]-image_statistics[i].mean)*
948 (Da*GetPixelChannel(reconstruct_image,channel,q)-
cristy0beccfa2011-09-25 20:47:53 +0000949 reconstruct_statistics[channel].mean);
cristy3fc482f2011-09-23 00:43:35 +0000950 }
cristyed231572011-07-14 02:18:59 +0000951 p+=GetPixelChannels(image);
cristy10a6c612012-01-29 21:41:05 +0000952 q+=GetPixelChannels(reconstruct_image);
cristy4c929a72010-11-24 18:54:42 +0000953 }
cristy9f48ca62010-11-25 03:06:31 +0000954 if (image->progress_monitor != (MagickProgressMonitor) NULL)
955 {
956 MagickBooleanType
957 proceed;
958
cristy9f48ca62010-11-25 03:06:31 +0000959 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
960 image->rows);
961 if (proceed == MagickFalse)
962 status=MagickFalse;
963 }
cristy4c929a72010-11-24 18:54:42 +0000964 }
965 reconstruct_view=DestroyCacheView(reconstruct_view);
966 image_view=DestroyCacheView(image_view);
cristy34d6fdc2010-11-26 19:06:08 +0000967 /*
968 Divide by the standard deviation.
969 */
cristy5f95f4f2011-10-23 01:01:01 +0000970 distortion[CompositePixelChannel]=0.0;
cristy3fc482f2011-09-23 00:43:35 +0000971 for (i=0; i < MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000972 {
cristya19f1d72012-08-07 18:24:38 +0000973 double
cristy18a41362010-11-27 15:56:18 +0000974 gamma;
cristy34d6fdc2010-11-26 19:06:08 +0000975
cristy5a23c552013-02-13 14:34:28 +0000976 PixelChannel channel=GetPixelChannelChannel(image,i);
cristy18a41362010-11-27 15:56:18 +0000977 gamma=image_statistics[i].standard_deviation*
cristy3fc482f2011-09-23 00:43:35 +0000978 reconstruct_statistics[channel].standard_deviation;
cristy3e3ec3a2012-11-03 23:11:06 +0000979 gamma=PerceptibleReciprocal(gamma);
cristy18a41362010-11-27 15:56:18 +0000980 distortion[i]=QuantumRange*gamma*distortion[i];
cristy5f95f4f2011-10-23 01:01:01 +0000981 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
cristy34d6fdc2010-11-26 19:06:08 +0000982 }
cristy5f95f4f2011-10-23 01:01:01 +0000983 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
cristy49dd6a02011-09-24 23:08:01 +0000984 GetImageChannels(image));
cristy34d6fdc2010-11-26 19:06:08 +0000985 /*
986 Free resources.
987 */
cristy9f48ca62010-11-25 03:06:31 +0000988 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
989 reconstruct_statistics);
990 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
991 image_statistics);
cristy4c929a72010-11-24 18:54:42 +0000992 return(status);
993}
994
cristy3cc758f2010-11-27 01:33:49 +0000995static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000996 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000997{
cristyc4c8d132010-01-07 01:58:38 +0000998 CacheView
999 *image_view,
1000 *reconstruct_view;
1001
cristy3ed852e2009-09-05 21:47:34 +00001002 MagickBooleanType
1003 status;
1004
cristy9d314ff2011-03-09 01:30:28 +00001005 ssize_t
1006 y;
1007
cristy3ed852e2009-09-05 21:47:34 +00001008 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +00001009 image_view=AcquireVirtualCacheView(image,exception);
1010 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001011#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001012 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +00001013 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001014#endif
cristybb503372010-05-27 20:51:26 +00001015 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001016 {
1017 double
cristy3fc482f2011-09-23 00:43:35 +00001018 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +00001019
cristy4c08aed2011-07-01 19:47:50 +00001020 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001021 *restrict p,
1022 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001023
cristybb503372010-05-27 20:51:26 +00001024 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001025 i,
1026 x;
1027
1028 if (status == MagickFalse)
1029 continue;
1030 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy10a6c612012-01-29 21:41:05 +00001031 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1032 1,exception);
cristy3fc482f2011-09-23 00:43:35 +00001033 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001034 {
1035 status=MagickFalse;
1036 continue;
1037 }
cristy3ed852e2009-09-05 21:47:34 +00001038 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +00001039 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001040 {
cristydb5c82c2013-02-22 00:41:33 +00001041 double
1042 Da,
1043 Sa;
1044
cristy3fc482f2011-09-23 00:43:35 +00001045 register ssize_t
1046 i;
cristy3ed852e2009-09-05 21:47:34 +00001047
cristy883fde12013-04-08 00:50:13 +00001048 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +00001049 {
1050 p+=GetPixelChannels(image);
1051 q+=GetPixelChannels(reconstruct_image);
1052 continue;
1053 }
cristydb5c82c2013-02-22 00:41:33 +00001054 Sa=QuantumScale*GetPixelAlpha(image,p);
1055 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +00001056 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1057 {
cristya19f1d72012-08-07 18:24:38 +00001058 double
cristy3fc482f2011-09-23 00:43:35 +00001059 distance;
1060
cristy5a23c552013-02-13 14:34:28 +00001061 PixelChannel channel=GetPixelChannelChannel(image,i);
1062 PixelTrait traits=GetPixelChannelTraits(image,channel);
1063 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1064 channel);
cristy3fc482f2011-09-23 00:43:35 +00001065 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001066 (reconstruct_traits == UndefinedPixelTrait) ||
1067 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001068 continue;
cristydb5c82c2013-02-22 00:41:33 +00001069 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
1070 channel,q));
cristy25a5f2f2011-09-24 14:09:43 +00001071 if (distance > channel_distortion[i])
cristy3fc482f2011-09-23 00:43:35 +00001072 channel_distortion[i]=distance;
cristy5f95f4f2011-10-23 01:01:01 +00001073 if (distance > channel_distortion[CompositePixelChannel])
1074 channel_distortion[CompositePixelChannel]=distance;
cristy3fc482f2011-09-23 00:43:35 +00001075 }
cristyed231572011-07-14 02:18:59 +00001076 p+=GetPixelChannels(image);
cristy10a6c612012-01-29 21:41:05 +00001077 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001078 }
cristyb5d5f722009-11-04 03:03:49 +00001079#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001080 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +00001081#endif
cristy3fc482f2011-09-23 00:43:35 +00001082 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001083 if (channel_distortion[i] > distortion[i])
1084 distortion[i]=channel_distortion[i];
1085 }
1086 reconstruct_view=DestroyCacheView(reconstruct_view);
1087 image_view=DestroyCacheView(image_view);
1088 return(status);
1089}
1090
cristyd94b0b32014-01-30 23:19:20 +00001091static double Decibels(double linear)
1092{
1093 if (linear < MagickEpsilon)
1094 linear=MagickEpsilon;
1095 return(20.0*log10(linear));
1096}
1097
cristy3ed852e2009-09-05 21:47:34 +00001098static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001099 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001100{
1101 MagickBooleanType
1102 status;
1103
cristy3fc482f2011-09-23 00:43:35 +00001104 register ssize_t
1105 i;
1106
cristy8a9106f2011-07-05 14:39:26 +00001107 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001108 for (i=0; i <= MaxPixelChannels; i++)
cristyd94b0b32014-01-30 23:19:20 +00001109 distortion[i]=Decibels((double) 1.0/sqrt(distortion[i]));
cristy3ed852e2009-09-05 21:47:34 +00001110 return(status);
1111}
1112
cristye80f15f2014-01-30 19:13:38 +00001113static Image *PerceptualImageHash(const Image *image,
cristy337bf652014-01-30 12:54:20 +00001114 const ColorspaceType colorspace,ExceptionInfo *exception)
1115{
1116 Image
cristy337bf652014-01-30 12:54:20 +00001117 *phash_image;
1118
1119 MagickBooleanType
1120 status;
1121
1122 /*
1123 Transform colorspace then blur perceptual hash image.
1124 */
cristye80f15f2014-01-30 19:13:38 +00001125 phash_image=BlurImage(image,0.0,1.0,exception);
1126 if (phash_image == (Image *) NULL)
cristy337bf652014-01-30 12:54:20 +00001127 return((Image *) NULL);
cristye80f15f2014-01-30 19:13:38 +00001128 phash_image->depth=8;
cristy339da3a2014-01-30 19:15:06 +00001129 status=TransformImageColorspace(phash_image,colorspace,exception);
cristy337bf652014-01-30 12:54:20 +00001130 if (status == MagickFalse)
cristye80f15f2014-01-30 19:13:38 +00001131 phash_image=DestroyImage(phash_image);
cristy337bf652014-01-30 12:54:20 +00001132 return(phash_image);
1133}
1134
cristy03d6f862014-01-08 18:34:48 +00001135static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
1136 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1137{
cristybc0adce2014-01-08 23:15:49 +00001138 ChannelMoments
1139 *image_moments,
1140 *reconstruct_moments;
1141
cristyfc441962014-01-30 21:57:14 +00001142 double
1143 alpha,
1144 beta,
1145 difference;
1146
cristy21b687b2014-01-10 00:17:34 +00001147 Image
cristy56c028a2014-01-30 13:00:44 +00001148 *phash_image;
cristy21b687b2014-01-10 00:17:34 +00001149
cristyd9244cd2014-01-30 23:09:31 +00001150 MagickBooleanType
1151 grayscale;
1152
cristybc0adce2014-01-08 23:15:49 +00001153 register ssize_t
1154 i;
1155
cristy21b687b2014-01-10 00:17:34 +00001156 /*
1157 Compute perceptual hash in the native image colorspace.
1158 */
cristye80f15f2014-01-30 19:13:38 +00001159 phash_image=PerceptualImageHash(image,image->colorspace,exception);
cristy337bf652014-01-30 12:54:20 +00001160 if (phash_image == (Image *) NULL)
cristybc0adce2014-01-08 23:15:49 +00001161 return(MagickFalse);
cristy337bf652014-01-30 12:54:20 +00001162 image_moments=GetImageMoments(phash_image,exception);
1163 phash_image=DestroyImage(phash_image);
cristy2661ac12014-01-10 14:21:07 +00001164 if (image_moments == (ChannelMoments *) NULL)
cristy337bf652014-01-30 12:54:20 +00001165 return(MagickFalse);
cristye80f15f2014-01-30 19:13:38 +00001166 phash_image=PerceptualImageHash(reconstruct_image,
cristy337bf652014-01-30 12:54:20 +00001167 reconstruct_image->colorspace,exception);
cristy56c028a2014-01-30 13:00:44 +00001168 if (phash_image == (Image *) NULL)
cristy2661ac12014-01-10 14:21:07 +00001169 {
1170 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
cristy2661ac12014-01-10 14:21:07 +00001171 return(MagickFalse);
1172 }
cristy56c028a2014-01-30 13:00:44 +00001173 reconstruct_moments=GetImageMoments(phash_image,exception);
1174 phash_image=DestroyImage(phash_image);
cristybc0adce2014-01-08 23:15:49 +00001175 if (reconstruct_moments == (ChannelMoments *) NULL)
1176 {
1177 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1178 return(MagickFalse);
1179 }
cristydc9136b2014-01-24 01:39:00 +00001180 for (i=0; i < 7; i++)
cristybc0adce2014-01-08 23:15:49 +00001181 {
cristyfc441962014-01-30 21:57:14 +00001182 ssize_t
cristybc0adce2014-01-08 23:15:49 +00001183 channel;
cristy6bd008f2014-01-30 23:24:06 +00001184
cristy5401eb32014-01-09 14:38:12 +00001185 /*
1186 Compute sum of moment differences squared.
1187 */
cristybc0adce2014-01-08 23:15:49 +00001188 for (channel=0; channel < MaxPixelChannels; channel++)
1189 {
cristyfc441962014-01-30 21:57:14 +00001190 alpha=fabs(image_moments[channel].I[i]);
1191 beta=fabs(reconstruct_moments[channel].I[i]);
cristy6bd008f2014-01-30 23:24:06 +00001192 difference=(beta < MagickEpsilon ? -MagickEpsilon : log10(beta))-
1193 (alpha < MagickEpsilon ? -MagickEpsilon : log10(alpha));
cristy70be7932014-01-09 13:18:24 +00001194 distortion[channel]+=difference*difference;
1195 distortion[CompositePixelChannel]+=difference*difference;
cristybc0adce2014-01-08 23:15:49 +00001196 }
1197 }
cristy2661ac12014-01-10 14:21:07 +00001198 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
cristybc0adce2014-01-08 23:15:49 +00001199 reconstruct_moments=(ChannelMoments *) RelinquishMagickMemory(
1200 reconstruct_moments);
cristyd9244cd2014-01-30 23:09:31 +00001201 grayscale=(IsImageGray(image,exception) != MagickFalse) &&
1202 (IsImageGray(reconstruct_image,exception) != MagickFalse) ? MagickTrue :
1203 MagickFalse;
cristy7a589a32014-01-30 21:27:40 +00001204 if (grayscale != MagickFalse)
cristy337bf652014-01-30 12:54:20 +00001205 return(MagickTrue);
cristy21b687b2014-01-10 00:17:34 +00001206 /*
1207 Compute perceptual hash in the HCLP colorspace.
1208 */
cristye80f15f2014-01-30 19:13:38 +00001209 phash_image=PerceptualImageHash(image,HCLpColorspace,exception);
cristy337bf652014-01-30 12:54:20 +00001210 if (phash_image == (Image *) NULL)
1211 return(MagickFalse);
1212 image_moments=GetImageMoments(phash_image,exception);
1213 phash_image=DestroyImage(phash_image);
cristy21b687b2014-01-10 00:17:34 +00001214 if (image_moments == (ChannelMoments *) NULL)
cristy337bf652014-01-30 12:54:20 +00001215 return(MagickFalse);
cristye80f15f2014-01-30 19:13:38 +00001216 phash_image=PerceptualImageHash(reconstruct_image,HCLpColorspace,exception);
cristy56c028a2014-01-30 13:00:44 +00001217 if (phash_image == (Image *) NULL)
cristy21b687b2014-01-10 00:17:34 +00001218 {
cristy337bf652014-01-30 12:54:20 +00001219 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
cristy21b687b2014-01-10 00:17:34 +00001220 return(MagickFalse);
1221 }
cristy56c028a2014-01-30 13:00:44 +00001222 reconstruct_moments=GetImageMoments(phash_image,exception);
1223 phash_image=DestroyImage(phash_image);
cristy21b687b2014-01-10 00:17:34 +00001224 if (reconstruct_moments == (ChannelMoments *) NULL)
1225 {
1226 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1227 return(MagickFalse);
1228 }
cristydc9136b2014-01-24 01:39:00 +00001229 for (i=0; i < 7; i++)
cristy21b687b2014-01-10 00:17:34 +00001230 {
cristyfc441962014-01-30 21:57:14 +00001231 ssize_t
cristy21b687b2014-01-10 00:17:34 +00001232 channel;
cristy6bd008f2014-01-30 23:24:06 +00001233
cristy21b687b2014-01-10 00:17:34 +00001234 /*
1235 Compute sum of moment differences squared.
1236 */
cristy21b687b2014-01-10 00:17:34 +00001237 for (channel=0; channel < MaxPixelChannels; channel++)
1238 {
cristyfc441962014-01-30 21:57:14 +00001239 alpha=fabs(image_moments[channel].I[i]);
1240 beta=fabs(reconstruct_moments[channel].I[i]);
cristy6bd008f2014-01-30 23:24:06 +00001241 difference=(beta < MagickEpsilon ? -MagickEpsilon : log10(beta))-
1242 (alpha < MagickEpsilon ? -MagickEpsilon : log10(alpha));
cristy21b687b2014-01-10 00:17:34 +00001243 distortion[channel]+=difference*difference;
1244 distortion[CompositePixelChannel]+=difference*difference;
1245 }
1246 }
1247 image_moments=(ChannelMoments *) RelinquishMagickMemory(image_moments);
1248 reconstruct_moments=(ChannelMoments *) RelinquishMagickMemory(
1249 reconstruct_moments);
cristy03d6f862014-01-08 18:34:48 +00001250 return(MagickTrue);
1251}
1252
cristy337bf652014-01-30 12:54:20 +00001253
cristy3cc758f2010-11-27 01:33:49 +00001254static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001255 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001256{
1257 MagickBooleanType
1258 status;
1259
cristy3fc482f2011-09-23 00:43:35 +00001260 register ssize_t
1261 i;
1262
cristy8a9106f2011-07-05 14:39:26 +00001263 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001264 for (i=0; i <= MaxPixelChannels; i++)
1265 distortion[i]=sqrt(distortion[i]);
cristy3ed852e2009-09-05 21:47:34 +00001266 return(status);
1267}
1268
cristy8a9106f2011-07-05 14:39:26 +00001269MagickExport MagickBooleanType GetImageDistortion(Image *image,
1270 const Image *reconstruct_image,const MetricType metric,double *distortion,
1271 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001272{
1273 double
1274 *channel_distortion;
1275
1276 MagickBooleanType
1277 status;
1278
1279 size_t
1280 length;
1281
1282 assert(image != (Image *) NULL);
1283 assert(image->signature == MagickSignature);
1284 if (image->debug != MagickFalse)
1285 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1286 assert(reconstruct_image != (const Image *) NULL);
1287 assert(reconstruct_image->signature == MagickSignature);
1288 assert(distortion != (double *) NULL);
1289 *distortion=0.0;
1290 if (image->debug != MagickFalse)
1291 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8b520b42014-01-30 00:58:45 +00001292 if (metric != PerceptualHashErrorMetric)
1293 if ((reconstruct_image->columns != image->columns) ||
1294 (reconstruct_image->rows != image->rows))
1295 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001296 /*
1297 Get image distortion.
1298 */
cristy3fc482f2011-09-23 00:43:35 +00001299 length=MaxPixelChannels+1;
cristy3ed852e2009-09-05 21:47:34 +00001300 channel_distortion=(double *) AcquireQuantumMemory(length,
1301 sizeof(*channel_distortion));
1302 if (channel_distortion == (double *) NULL)
1303 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1304 (void) ResetMagickMemory(channel_distortion,0,length*
1305 sizeof(*channel_distortion));
1306 switch (metric)
1307 {
1308 case AbsoluteErrorMetric:
1309 {
cristy8a9106f2011-07-05 14:39:26 +00001310 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1311 exception);
cristy3ed852e2009-09-05 21:47:34 +00001312 break;
1313 }
cristy343eee92010-12-11 02:17:57 +00001314 case FuzzErrorMetric:
1315 {
cristy8a9106f2011-07-05 14:39:26 +00001316 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1317 exception);
cristy343eee92010-12-11 02:17:57 +00001318 break;
1319 }
cristy3ed852e2009-09-05 21:47:34 +00001320 case MeanAbsoluteErrorMetric:
1321 {
cristy8a9106f2011-07-05 14:39:26 +00001322 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001323 channel_distortion,exception);
1324 break;
1325 }
cristy03fa69f2014-01-08 18:36:49 +00001326 case MeanErrorPerPixelErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001327 {
cristy8a9106f2011-07-05 14:39:26 +00001328 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1329 exception);
cristy3ed852e2009-09-05 21:47:34 +00001330 break;
1331 }
1332 case MeanSquaredErrorMetric:
1333 {
cristy8a9106f2011-07-05 14:39:26 +00001334 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001335 channel_distortion,exception);
1336 break;
1337 }
cristy4c929a72010-11-24 18:54:42 +00001338 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001339 default:
cristy4c929a72010-11-24 18:54:42 +00001340 {
cristy3cc758f2010-11-27 01:33:49 +00001341 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001342 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001343 break;
1344 }
cristy3ed852e2009-09-05 21:47:34 +00001345 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001346 {
cristy8a9106f2011-07-05 14:39:26 +00001347 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001348 channel_distortion,exception);
1349 break;
1350 }
cristy03d6f862014-01-08 18:34:48 +00001351 case PeakSignalToNoiseRatioErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001352 {
cristy8a9106f2011-07-05 14:39:26 +00001353 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001354 channel_distortion,exception);
1355 break;
1356 }
cristy03d6f862014-01-08 18:34:48 +00001357 case PerceptualHashErrorMetric:
1358 {
1359 status=GetPerceptualHashDistortion(image,reconstruct_image,
1360 channel_distortion,exception);
1361 break;
1362 }
cristy3ed852e2009-09-05 21:47:34 +00001363 case RootMeanSquaredErrorMetric:
1364 {
cristy8a9106f2011-07-05 14:39:26 +00001365 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001366 channel_distortion,exception);
1367 break;
1368 }
1369 }
cristy5f95f4f2011-10-23 01:01:01 +00001370 *distortion=channel_distortion[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001371 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
cristy65aa9342013-09-05 12:04:27 +00001372 (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
cristye863c052013-09-04 11:31:30 +00001373 *distortion);
cristy3ed852e2009-09-05 21:47:34 +00001374 return(status);
1375}
1376
1377/*
1378%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1379% %
1380% %
1381% %
cristy8a9106f2011-07-05 14:39:26 +00001382% 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 +00001383% %
1384% %
1385% %
1386%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1387%
cristy44097f52012-12-16 19:56:20 +00001388% GetImageDistortions() compares the pixel channels of an image to a
cristy3ed852e2009-09-05 21:47:34 +00001389% reconstructed image and returns the specified distortion metric for each
1390% channel.
1391%
cristy44097f52012-12-16 19:56:20 +00001392% The format of the GetImageDistortions method is:
cristy3ed852e2009-09-05 21:47:34 +00001393%
cristy8a9106f2011-07-05 14:39:26 +00001394% double *GetImageDistortions(const Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001395% const Image *reconstruct_image,const MetricType metric,
1396% ExceptionInfo *exception)
1397%
1398% A description of each parameter follows:
1399%
1400% o image: the image.
1401%
1402% o reconstruct_image: the reconstruct image.
1403%
1404% o metric: the metric.
1405%
1406% o exception: return any errors or warnings in this structure.
1407%
1408*/
cristy8a9106f2011-07-05 14:39:26 +00001409MagickExport double *GetImageDistortions(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001410 const Image *reconstruct_image,const MetricType metric,
1411 ExceptionInfo *exception)
1412{
1413 double
1414 *channel_distortion;
1415
1416 MagickBooleanType
1417 status;
1418
1419 size_t
1420 length;
1421
1422 assert(image != (Image *) NULL);
1423 assert(image->signature == MagickSignature);
1424 if (image->debug != MagickFalse)
1425 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1426 assert(reconstruct_image != (const Image *) NULL);
1427 assert(reconstruct_image->signature == MagickSignature);
1428 if (image->debug != MagickFalse)
1429 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8b520b42014-01-30 00:58:45 +00001430 if (metric != PerceptualHashErrorMetric)
1431 if ((reconstruct_image->columns != image->columns) ||
1432 (reconstruct_image->rows != image->rows))
1433 {
1434 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1435 "ImageSizeDiffers","`%s'",image->filename);
1436 return((double *) NULL);
1437 }
cristy3ed852e2009-09-05 21:47:34 +00001438 /*
1439 Get image distortion.
1440 */
cristy3fc482f2011-09-23 00:43:35 +00001441 length=MaxPixelChannels+1UL;
cristy3ed852e2009-09-05 21:47:34 +00001442 channel_distortion=(double *) AcquireQuantumMemory(length,
1443 sizeof(*channel_distortion));
1444 if (channel_distortion == (double *) NULL)
1445 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1446 (void) ResetMagickMemory(channel_distortion,0,length*
1447 sizeof(*channel_distortion));
cristyda16f162011-02-19 23:52:17 +00001448 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +00001449 switch (metric)
1450 {
1451 case AbsoluteErrorMetric:
1452 {
cristy8a9106f2011-07-05 14:39:26 +00001453 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1454 exception);
cristy3ed852e2009-09-05 21:47:34 +00001455 break;
1456 }
cristy343eee92010-12-11 02:17:57 +00001457 case FuzzErrorMetric:
1458 {
cristy8a9106f2011-07-05 14:39:26 +00001459 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1460 exception);
cristy343eee92010-12-11 02:17:57 +00001461 break;
1462 }
cristy3ed852e2009-09-05 21:47:34 +00001463 case MeanAbsoluteErrorMetric:
1464 {
cristy8a9106f2011-07-05 14:39:26 +00001465 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001466 channel_distortion,exception);
1467 break;
1468 }
cristy03fa69f2014-01-08 18:36:49 +00001469 case MeanErrorPerPixelErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001470 {
cristy8a9106f2011-07-05 14:39:26 +00001471 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1472 exception);
cristy3ed852e2009-09-05 21:47:34 +00001473 break;
1474 }
1475 case MeanSquaredErrorMetric:
1476 {
cristy8a9106f2011-07-05 14:39:26 +00001477 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001478 channel_distortion,exception);
1479 break;
1480 }
cristy4c929a72010-11-24 18:54:42 +00001481 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001482 default:
cristy4c929a72010-11-24 18:54:42 +00001483 {
cristy3cc758f2010-11-27 01:33:49 +00001484 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001485 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001486 break;
1487 }
cristy3ed852e2009-09-05 21:47:34 +00001488 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001489 {
cristy8a9106f2011-07-05 14:39:26 +00001490 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001491 channel_distortion,exception);
1492 break;
1493 }
cristy03d6f862014-01-08 18:34:48 +00001494 case PeakSignalToNoiseRatioErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001495 {
cristy8a9106f2011-07-05 14:39:26 +00001496 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001497 channel_distortion,exception);
1498 break;
1499 }
cristy03d6f862014-01-08 18:34:48 +00001500 case PerceptualHashErrorMetric:
1501 {
1502 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1503 channel_distortion,exception);
1504 break;
1505 }
cristy3ed852e2009-09-05 21:47:34 +00001506 case RootMeanSquaredErrorMetric:
1507 {
cristy8a9106f2011-07-05 14:39:26 +00001508 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001509 channel_distortion,exception);
1510 break;
1511 }
1512 }
cristyda16f162011-02-19 23:52:17 +00001513 if (status == MagickFalse)
1514 {
1515 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1516 return((double *) NULL);
1517 }
cristy3ed852e2009-09-05 21:47:34 +00001518 return(channel_distortion);
1519}
1520
1521/*
1522%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1523% %
1524% %
1525% %
1526% I s I m a g e s E q u a l %
1527% %
1528% %
1529% %
1530%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1531%
1532% IsImagesEqual() measures the difference between colors at each pixel
1533% location of two images. A value other than 0 means the colors match
1534% exactly. Otherwise an error measure is computed by summing over all
1535% pixels in an image the distance squared in RGB space between each image
1536% pixel and its corresponding pixel in the reconstruct image. The error
1537% measure is assigned to these image members:
1538%
1539% o mean_error_per_pixel: The mean error for any single pixel in
1540% the image.
1541%
1542% o normalized_mean_error: The normalized mean quantization error for
1543% any single pixel in the image. This distance measure is normalized to
1544% a range between 0 and 1. It is independent of the range of red, green,
1545% and blue values in the image.
1546%
1547% o normalized_maximum_error: The normalized maximum quantization
1548% error for any single pixel in the image. This distance measure is
1549% normalized to a range between 0 and 1. It is independent of the range
1550% of red, green, and blue values in your image.
1551%
1552% A small normalized mean square error, accessed as
1553% image->normalized_mean_error, suggests the images are very similar in
1554% spatial layout and color.
1555%
1556% The format of the IsImagesEqual method is:
1557%
1558% MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001559% const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001560%
1561% A description of each parameter follows.
1562%
1563% o image: the image.
1564%
1565% o reconstruct_image: the reconstruct image.
1566%
cristy018f07f2011-09-04 21:15:19 +00001567% o exception: return any errors or warnings in this structure.
1568%
cristy3ed852e2009-09-05 21:47:34 +00001569*/
1570MagickExport MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001571 const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001572{
cristyc4c8d132010-01-07 01:58:38 +00001573 CacheView
1574 *image_view,
1575 *reconstruct_view;
1576
cristy3ed852e2009-09-05 21:47:34 +00001577 MagickBooleanType
1578 status;
1579
cristya19f1d72012-08-07 18:24:38 +00001580 double
cristy3ed852e2009-09-05 21:47:34 +00001581 area,
1582 maximum_error,
1583 mean_error,
1584 mean_error_per_pixel;
1585
cristy9d314ff2011-03-09 01:30:28 +00001586 ssize_t
1587 y;
1588
cristy3ed852e2009-09-05 21:47:34 +00001589 assert(image != (Image *) NULL);
1590 assert(image->signature == MagickSignature);
1591 assert(reconstruct_image != (const Image *) NULL);
1592 assert(reconstruct_image->signature == MagickSignature);
1593 if ((reconstruct_image->columns != image->columns) ||
1594 (reconstruct_image->rows != image->rows))
1595 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1596 area=0.0;
1597 maximum_error=0.0;
1598 mean_error_per_pixel=0.0;
1599 mean_error=0.0;
cristy46ff2672012-12-14 15:32:26 +00001600 image_view=AcquireVirtualCacheView(image,exception);
1601 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristybb503372010-05-27 20:51:26 +00001602 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001603 {
cristy4c08aed2011-07-01 19:47:50 +00001604 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001605 *restrict p,
1606 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001607
cristybb503372010-05-27 20:51:26 +00001608 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001609 x;
1610
1611 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1612 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1613 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001614 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001615 break;
cristybb503372010-05-27 20:51:26 +00001616 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001617 {
cristyd5c15f92011-09-23 00:58:33 +00001618 register ssize_t
1619 i;
cristy3ed852e2009-09-05 21:47:34 +00001620
cristy883fde12013-04-08 00:50:13 +00001621 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +00001622 {
1623 p+=GetPixelChannels(image);
1624 q+=GetPixelChannels(reconstruct_image);
1625 continue;
1626 }
cristyd5c15f92011-09-23 00:58:33 +00001627 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1628 {
cristya19f1d72012-08-07 18:24:38 +00001629 double
cristyd5c15f92011-09-23 00:58:33 +00001630 distance;
1631
cristy5a23c552013-02-13 14:34:28 +00001632 PixelChannel channel=GetPixelChannelChannel(image,i);
1633 PixelTrait traits=GetPixelChannelTraits(image,channel);
1634 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1635 channel);
cristyd5c15f92011-09-23 00:58:33 +00001636 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001637 (reconstruct_traits == UndefinedPixelTrait) ||
1638 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001639 continue;
cristya19f1d72012-08-07 18:24:38 +00001640 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
cristy0beccfa2011-09-25 20:47:53 +00001641 channel,q));
cristyd5c15f92011-09-23 00:58:33 +00001642 mean_error_per_pixel+=distance;
1643 mean_error+=distance*distance;
1644 if (distance > maximum_error)
1645 maximum_error=distance;
1646 area++;
1647 }
cristyed231572011-07-14 02:18:59 +00001648 p+=GetPixelChannels(image);
1649 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001650 }
1651 }
1652 reconstruct_view=DestroyCacheView(reconstruct_view);
1653 image_view=DestroyCacheView(image_view);
1654 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1655 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1656 mean_error/area);
1657 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1658 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1659 return(status);
1660}
1661
1662/*
1663%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1664% %
1665% %
1666% %
1667% S i m i l a r i t y I m a g e %
1668% %
1669% %
1670% %
1671%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1672%
1673% SimilarityImage() compares the reference image of the image and returns the
1674% best match offset. In addition, it returns a similarity image such that an
1675% exact match location is completely white and if none of the pixels match,
1676% black, otherwise some gray level in-between.
1677%
1678% The format of the SimilarityImageImage method is:
1679%
1680% Image *SimilarityImage(const Image *image,const Image *reference,
cristy62e52182013-03-15 14:26:17 +00001681% const MetricType metric,const double similarity_threshold,
1682% RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001683%
1684% A description of each parameter follows:
1685%
1686% o image: the image.
1687%
1688% o reference: find an area of the image that closely resembles this image.
1689%
cristy09136812011-10-18 15:24:30 +00001690% o metric: the metric.
1691%
cristy62e52182013-03-15 14:26:17 +00001692% o similarity_threshold: minimum distortion for (sub)image match.
1693%
1694% o offset: the best match offset of the reference image within the image.
cristy3ed852e2009-09-05 21:47:34 +00001695%
1696% o similarity: the computed similarity between the images.
1697%
1698% o exception: return any errors or warnings in this structure.
1699%
1700*/
1701
1702static double GetSimilarityMetric(const Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001703 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1704 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001705{
1706 double
cristy3cc758f2010-11-27 01:33:49 +00001707 distortion;
cristy3ed852e2009-09-05 21:47:34 +00001708
cristy713ff212010-11-26 21:56:11 +00001709 Image
1710 *similarity_image;
cristy3ed852e2009-09-05 21:47:34 +00001711
cristy09136812011-10-18 15:24:30 +00001712 MagickBooleanType
1713 status;
1714
cristy713ff212010-11-26 21:56:11 +00001715 RectangleInfo
1716 geometry;
cristy3ed852e2009-09-05 21:47:34 +00001717
cristy713ff212010-11-26 21:56:11 +00001718 SetGeometry(reference,&geometry);
1719 geometry.x=x_offset;
1720 geometry.y=y_offset;
1721 similarity_image=CropImage(image,&geometry,exception);
1722 if (similarity_image == (Image *) NULL)
1723 return(0.0);
cristy09136812011-10-18 15:24:30 +00001724 distortion=0.0;
1725 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
cristy3cc758f2010-11-27 01:33:49 +00001726 exception);
cristy713ff212010-11-26 21:56:11 +00001727 similarity_image=DestroyImage(similarity_image);
cristy09136812011-10-18 15:24:30 +00001728 if (status == MagickFalse)
1729 return(0.0);
cristy3cc758f2010-11-27 01:33:49 +00001730 return(distortion);
cristy3ed852e2009-09-05 21:47:34 +00001731}
1732
1733MagickExport Image *SimilarityImage(Image *image,const Image *reference,
cristy62e52182013-03-15 14:26:17 +00001734 const MetricType metric,const double similarity_threshold,
1735 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001736{
1737#define SimilarityImageTag "Similarity/Image"
1738
cristyc4c8d132010-01-07 01:58:38 +00001739 CacheView
1740 *similarity_view;
1741
cristy3ed852e2009-09-05 21:47:34 +00001742 Image
1743 *similarity_image;
1744
1745 MagickBooleanType
1746 status;
1747
cristybb503372010-05-27 20:51:26 +00001748 MagickOffsetType
1749 progress;
1750
1751 ssize_t
1752 y;
1753
cristy3ed852e2009-09-05 21:47:34 +00001754 assert(image != (const Image *) NULL);
1755 assert(image->signature == MagickSignature);
1756 if (image->debug != MagickFalse)
1757 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1758 assert(exception != (ExceptionInfo *) NULL);
1759 assert(exception->signature == MagickSignature);
1760 assert(offset != (RectangleInfo *) NULL);
1761 SetGeometry(reference,offset);
1762 *similarity_metric=1.0;
cristyf9255b92010-08-14 22:59:00 +00001763 if ((reference->columns > image->columns) || (reference->rows > image->rows))
cristy3ed852e2009-09-05 21:47:34 +00001764 ThrowImageException(ImageError,"ImageSizeDiffers");
1765 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1766 image->rows-reference->rows+1,MagickTrue,exception);
1767 if (similarity_image == (Image *) NULL)
1768 return((Image *) NULL);
cristyd5c15f92011-09-23 00:58:33 +00001769 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1770 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001771 {
cristy3ed852e2009-09-05 21:47:34 +00001772 similarity_image=DestroyImage(similarity_image);
1773 return((Image *) NULL);
1774 }
cristyb979cea2013-03-01 14:22:42 +00001775 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
1776 exception);
cristy3ed852e2009-09-05 21:47:34 +00001777 /*
1778 Measure similarity of reference image against image.
1779 */
1780 status=MagickTrue;
1781 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001782 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001783#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy70ea54f2013-03-15 18:49:14 +00001784 #pragma omp parallel for schedule(static,4) \
1785 shared(progress,status,similarity_metric) \
cristy5e6b2592012-12-19 14:08:11 +00001786 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001787#endif
cristybb503372010-05-27 20:51:26 +00001788 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
cristy3ed852e2009-09-05 21:47:34 +00001789 {
1790 double
1791 similarity;
1792
cristy4c08aed2011-07-01 19:47:50 +00001793 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001794 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001795
cristy49dd6a02011-09-24 23:08:01 +00001796 register ssize_t
1797 x;
1798
cristy3ed852e2009-09-05 21:47:34 +00001799 if (status == MagickFalse)
1800 continue;
cristy266f58b2013-05-15 11:47:01 +00001801#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy1aadacc2013-04-27 16:31:24 +00001802 #pragma omp flush(similarity_metric)
cristy266f58b2013-05-15 11:47:01 +00001803#endif
cristy24856ba2013-03-15 18:24:00 +00001804 if (*similarity_metric <= similarity_threshold)
1805 continue;
cristy3cc758f2010-11-27 01:33:49 +00001806 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1807 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001808 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001809 {
1810 status=MagickFalse;
1811 continue;
1812 }
cristybb503372010-05-27 20:51:26 +00001813 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
cristy3ed852e2009-09-05 21:47:34 +00001814 {
cristy49dd6a02011-09-24 23:08:01 +00001815 register ssize_t
1816 i;
1817
cristy266f58b2013-05-15 11:47:01 +00001818#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy1aadacc2013-04-27 16:31:24 +00001819 #pragma omp flush(similarity_metric)
cristy266f58b2013-05-15 11:47:01 +00001820#endif
cristy24856ba2013-03-15 18:24:00 +00001821 if (*similarity_metric <= similarity_threshold)
1822 break;
cristy09136812011-10-18 15:24:30 +00001823 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
cristyb5d5f722009-11-04 03:03:49 +00001824#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001825 #pragma omp critical (MagickCore_SimilarityImage)
cristy3ed852e2009-09-05 21:47:34 +00001826#endif
1827 if (similarity < *similarity_metric)
1828 {
cristy3ed852e2009-09-05 21:47:34 +00001829 offset->x=x;
1830 offset->y=y;
cristy24856ba2013-03-15 18:24:00 +00001831 *similarity_metric=similarity;
cristy3ed852e2009-09-05 21:47:34 +00001832 }
cristy883fde12013-04-08 00:50:13 +00001833 if (GetPixelReadMask(similarity_image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00001834 {
cristyc3a58022013-10-09 23:22:42 +00001835 SetPixelBackgoundColor(similarity_image,q);
cristyc94ba6f2012-01-29 23:19:58 +00001836 q+=GetPixelChannels(similarity_image);
cristy10a6c612012-01-29 21:41:05 +00001837 continue;
1838 }
cristyc94ba6f2012-01-29 23:19:58 +00001839 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
cristy49dd6a02011-09-24 23:08:01 +00001840 {
cristy5a23c552013-02-13 14:34:28 +00001841 PixelChannel channel=GetPixelChannelChannel(image,i);
1842 PixelTrait traits=GetPixelChannelTraits(image,channel);
1843 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1844 channel);
cristy49dd6a02011-09-24 23:08:01 +00001845 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001846 (similarity_traits == UndefinedPixelTrait) ||
1847 ((similarity_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001848 continue;
cristy0beccfa2011-09-25 20:47:53 +00001849 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1850 QuantumRange*similarity),q);
cristy49dd6a02011-09-24 23:08:01 +00001851 }
cristyed231572011-07-14 02:18:59 +00001852 q+=GetPixelChannels(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001853 }
cristyb979cea2013-03-01 14:22:42 +00001854 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001855 status=MagickFalse;
1856 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1857 {
cristyb979cea2013-03-01 14:22:42 +00001858 MagickBooleanType
1859 proceed;
1860
cristyb5d5f722009-11-04 03:03:49 +00001861#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001862 #pragma omp critical (MagickCore_SimilarityImage)
cristy3ed852e2009-09-05 21:47:34 +00001863#endif
cristyb979cea2013-03-01 14:22:42 +00001864 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1865 image->rows);
1866 if (proceed == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001867 status=MagickFalse;
1868 }
1869 }
1870 similarity_view=DestroyCacheView(similarity_view);
cristy1c2f48d2012-12-14 01:20:55 +00001871 if (status == MagickFalse)
1872 similarity_image=DestroyImage(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001873 return(similarity_image);
1874}