blob: 07c24d2e8c1f78b4f779dbb2ea10649bce4a5ae3 [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 %
cristy2fb99212014-04-18 22:15:04 +000016% Cristy %
cristy3ed852e2009-09-05 21:47:34 +000017% December 2003 %
18% %
19% %
cristyb56bb242014-11-25 17:12:48 +000020% Copyright 1999-2015 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*/
cristy73626632014-07-19 20:52:21 +0000107
cristy68094062014-08-22 12:59:44 +0000108static size_t GetImageChannels(const Image *image)
109{
110 register ssize_t
111 i;
112
113 size_t
114 channels;
115
116 channels=0;
117 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
118 {
119 PixelChannel channel=GetPixelChannelChannel(image,i);
120 PixelTrait traits=GetPixelChannelTraits(image,channel);
121 if ((traits & UpdatePixelTrait) != 0)
122 channels++;
123 }
124 return(channels == 0 ? 1 : channels);
125}
126
cristy73626632014-07-19 20:52:21 +0000127static inline MagickBooleanType ValidateImageMorphology(
128 const Image *restrict image,const Image *restrict reconstruct_image)
129{
130 /*
131 Does the image match the reconstructed image morphology?
cristyc2e29bd2014-08-22 12:50:44 +0000132 */
cristy73626632014-07-19 20:52:21 +0000133 return(MagickTrue);
134}
135
cristy3ed852e2009-09-05 21:47:34 +0000136MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
137 const MetricType metric,double *distortion,ExceptionInfo *exception)
138{
cristyc4c8d132010-01-07 01:58:38 +0000139 CacheView
140 *highlight_view,
141 *image_view,
142 *reconstruct_view;
143
dirk8a7bbf42015-01-08 23:04:58 +0000144 double
145 fuzz;
146
cristy3ed852e2009-09-05 21:47:34 +0000147 const char
148 *artifact;
149
150 Image
151 *difference_image,
152 *highlight_image;
153
cristy3ed852e2009-09-05 21:47:34 +0000154 MagickBooleanType
155 status;
156
cristy4c08aed2011-07-01 19:47:50 +0000157 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000158 highlight,
cristy3fc482f2011-09-23 00:43:35 +0000159 lowlight;
cristy3ed852e2009-09-05 21:47:34 +0000160
cristyb730ca22015-03-31 15:50:52 +0000161 RectangleInfo
162 geometry;
163
cristye68f5992015-03-03 17:44:51 +0000164 size_t
165 columns,
166 rows;
167
cristy49dd6a02011-09-24 23:08:01 +0000168 ssize_t
169 y;
170
cristy3ed852e2009-09-05 21:47:34 +0000171 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000172 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000173 if (image->debug != MagickFalse)
174 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
175 assert(reconstruct_image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000176 assert(reconstruct_image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000177 assert(distortion != (double *) NULL);
178 *distortion=0.0;
179 if (image->debug != MagickFalse)
180 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8b520b42014-01-30 00:58:45 +0000181 if (metric != PerceptualHashErrorMetric)
cristy73626632014-07-19 20:52:21 +0000182 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
183 ThrowImageException(ImageError,"ImageMorphologyDiffers");
cristy8a9106f2011-07-05 14:39:26 +0000184 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
185 exception);
cristy3ed852e2009-09-05 21:47:34 +0000186 if (status == MagickFalse)
187 return((Image *) NULL);
cristye68f5992015-03-03 17:44:51 +0000188 columns=MagickMax(image->columns,reconstruct_image->columns);
cristyb730ca22015-03-31 15:50:52 +0000189 rows=MagickMax(image->rows,reconstruct_image->rows);
190 SetGeometry(image,&geometry);
191 geometry.width=columns;
192 geometry.height=rows;
193 difference_image=ExtentImage(image,&geometry,exception);
cristy3ed852e2009-09-05 21:47:34 +0000194 if (difference_image == (Image *) NULL)
195 return((Image *) NULL);
cristy63240882011-08-05 19:05:27 +0000196 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
cristye68f5992015-03-03 17:44:51 +0000197 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000198 if (highlight_image == (Image *) NULL)
199 {
200 difference_image=DestroyImage(difference_image);
201 return((Image *) NULL);
202 }
cristy3fc482f2011-09-23 00:43:35 +0000203 status=SetImageStorageClass(highlight_image,DirectClass,exception);
204 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000205 {
cristy3ed852e2009-09-05 21:47:34 +0000206 difference_image=DestroyImage(difference_image);
207 highlight_image=DestroyImage(highlight_image);
208 return((Image *) NULL);
209 }
cristy63240882011-08-05 19:05:27 +0000210 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
cristyca611542013-02-19 00:54:03 +0000211 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000212 artifact=GetImageArtifact(image,"highlight-color");
213 if (artifact != (const char *) NULL)
cristyf9d6dc02012-01-19 02:14:44 +0000214 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
cristyca611542013-02-19 00:54:03 +0000215 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000216 artifact=GetImageArtifact(image,"lowlight-color");
217 if (artifact != (const char *) NULL)
cristy0b1a7972011-10-22 22:17:02 +0000218 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000219 /*
220 Generate difference image.
221 */
222 status=MagickTrue;
cristya664f962015-01-09 00:25:16 +0000223 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
cristy46ff2672012-12-14 15:32:26 +0000224 image_view=AcquireVirtualCacheView(image,exception);
225 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
226 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000227#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000228 #pragma omp parallel for schedule(static,4) shared(status) \
cristye68f5992015-03-03 17:44:51 +0000229 magick_threads(image,highlight_image,rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000230#endif
cristye68f5992015-03-03 17:44:51 +0000231 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000232 {
233 MagickBooleanType
234 sync;
235
cristy4c08aed2011-07-01 19:47:50 +0000236 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000237 *restrict p,
238 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000239
cristy4c08aed2011-07-01 19:47:50 +0000240 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000241 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000242
cristy49dd6a02011-09-24 23:08:01 +0000243 register ssize_t
244 x;
245
cristy3ed852e2009-09-05 21:47:34 +0000246 if (status == MagickFalse)
247 continue;
cristye68f5992015-03-03 17:44:51 +0000248 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
249 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
250 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000251 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
252 (r == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000253 {
254 status=MagickFalse;
255 continue;
256 }
cristye68f5992015-03-03 17:44:51 +0000257 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000258 {
cristydb5c82c2013-02-22 00:41:33 +0000259 double
260 Da,
261 Sa;
262
cristy3ed852e2009-09-05 21:47:34 +0000263 MagickStatusType
264 difference;
265
cristy3fc482f2011-09-23 00:43:35 +0000266 register ssize_t
267 i;
268
cristy883fde12013-04-08 00:50:13 +0000269 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000270 {
cristy11a06d32015-01-04 12:03:27 +0000271 SetPixelViaPixelInfo(highlight_image,&lowlight,r);
cristy10a6c612012-01-29 21:41:05 +0000272 p+=GetPixelChannels(image);
273 q+=GetPixelChannels(reconstruct_image);
274 r+=GetPixelChannels(highlight_image);
275 continue;
276 }
cristyd09f8802012-02-04 16:44:10 +0000277 difference=MagickFalse;
cristydb5c82c2013-02-22 00:41:33 +0000278 Sa=QuantumScale*GetPixelAlpha(image,p);
279 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000280 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
281 {
cristya19f1d72012-08-07 18:24:38 +0000282 double
cristy0beccfa2011-09-25 20:47:53 +0000283 distance;
284
cristy5a23c552013-02-13 14:34:28 +0000285 PixelChannel channel=GetPixelChannelChannel(image,i);
286 PixelTrait traits=GetPixelChannelTraits(image,channel);
287 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
288 channel);
cristy3fc482f2011-09-23 00:43:35 +0000289 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000290 (reconstruct_traits == UndefinedPixelTrait) ||
291 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy3fc482f2011-09-23 00:43:35 +0000292 continue;
cristydb5c82c2013-02-22 00:41:33 +0000293 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
dirk8a7bbf42015-01-08 23:04:58 +0000294 if ((distance*distance) > fuzz)
295 {
296 difference=MagickTrue;
297 break;
298 }
cristy3fc482f2011-09-23 00:43:35 +0000299 }
300 if (difference == MagickFalse)
cristy11a06d32015-01-04 12:03:27 +0000301 SetPixelViaPixelInfo(highlight_image,&lowlight,r);
cristy3fc482f2011-09-23 00:43:35 +0000302 else
cristy11a06d32015-01-04 12:03:27 +0000303 SetPixelViaPixelInfo(highlight_image,&highlight,r);
cristyed231572011-07-14 02:18:59 +0000304 p+=GetPixelChannels(image);
305 q+=GetPixelChannels(reconstruct_image);
306 r+=GetPixelChannels(highlight_image);
cristy3ed852e2009-09-05 21:47:34 +0000307 }
308 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
309 if (sync == MagickFalse)
310 status=MagickFalse;
311 }
312 highlight_view=DestroyCacheView(highlight_view);
313 reconstruct_view=DestroyCacheView(reconstruct_view);
314 image_view=DestroyCacheView(image_view);
cristyfeb3e962012-03-29 17:25:55 +0000315 (void) CompositeImage(difference_image,highlight_image,image->compose,
cristy39172402012-03-30 13:04:39 +0000316 MagickTrue,0,0,exception);
cristy3ed852e2009-09-05 21:47:34 +0000317 highlight_image=DestroyImage(highlight_image);
318 if (status == MagickFalse)
319 difference_image=DestroyImage(difference_image);
320 return(difference_image);
321}
322
323/*
324%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
325% %
326% %
327% %
cristy8a9106f2011-07-05 14:39:26 +0000328% G e t I m a g e D i s t o r t i o n %
cristy3ed852e2009-09-05 21:47:34 +0000329% %
330% %
331% %
332%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
333%
cristyaeded782012-09-11 23:39:36 +0000334% GetImageDistortion() compares one or more pixel channels of an image to a
cristy8a9106f2011-07-05 14:39:26 +0000335% reconstructed image and returns the specified distortion metric.
cristy3ed852e2009-09-05 21:47:34 +0000336%
cristy44097f52012-12-16 19:56:20 +0000337% The format of the GetImageDistortion method is:
cristy3ed852e2009-09-05 21:47:34 +0000338%
cristy8a9106f2011-07-05 14:39:26 +0000339% MagickBooleanType GetImageDistortion(const Image *image,
340% const Image *reconstruct_image,const MetricType metric,
341% double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000342%
343% A description of each parameter follows:
344%
345% o image: the image.
346%
347% o reconstruct_image: the reconstruct image.
348%
cristy3ed852e2009-09-05 21:47:34 +0000349% o metric: the metric.
350%
351% o distortion: the computed distortion between the images.
352%
353% o exception: return any errors or warnings in this structure.
354%
355*/
356
cristy3cc758f2010-11-27 01:33:49 +0000357static MagickBooleanType GetAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000358 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000359{
cristyc4c8d132010-01-07 01:58:38 +0000360 CacheView
361 *image_view,
362 *reconstruct_view;
363
cristydb5c82c2013-02-22 00:41:33 +0000364 double
365 fuzz;
366
cristy3ed852e2009-09-05 21:47:34 +0000367 MagickBooleanType
368 status;
369
cristye68f5992015-03-03 17:44:51 +0000370 size_t
371 columns,
372 rows;
373
cristy9d314ff2011-03-09 01:30:28 +0000374 ssize_t
375 y;
376
cristy3ed852e2009-09-05 21:47:34 +0000377 /*
378 Compute the absolute difference in pixels between two images.
379 */
380 status=MagickTrue;
cristya664f962015-01-09 00:25:16 +0000381 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
cristye68f5992015-03-03 17:44:51 +0000382 rows=MagickMax(image->rows,reconstruct_image->rows);
383 columns=MagickMax(image->columns,reconstruct_image->columns);
cristy46ff2672012-12-14 15:32:26 +0000384 image_view=AcquireVirtualCacheView(image,exception);
385 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000386#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000387 #pragma omp parallel for schedule(static,4) shared(status) \
cristye68f5992015-03-03 17:44:51 +0000388 magick_threads(image,image,rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000389#endif
cristye68f5992015-03-03 17:44:51 +0000390 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000391 {
392 double
cristy3fc482f2011-09-23 00:43:35 +0000393 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000394
cristy4c08aed2011-07-01 19:47:50 +0000395 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000396 *restrict p,
397 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000398
cristybb503372010-05-27 20:51:26 +0000399 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000400 i,
401 x;
402
403 if (status == MagickFalse)
404 continue;
cristye68f5992015-03-03 17:44:51 +0000405 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
406 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000407 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000408 {
409 status=MagickFalse;
410 continue;
411 }
cristy3ed852e2009-09-05 21:47:34 +0000412 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristye68f5992015-03-03 17:44:51 +0000413 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000414 {
cristydb5c82c2013-02-22 00:41:33 +0000415 double
416 Da,
417 Sa;
418
cristy3fc482f2011-09-23 00:43:35 +0000419 MagickBooleanType
420 difference;
421
422 register ssize_t
423 i;
424
cristy883fde12013-04-08 00:50:13 +0000425 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000426 {
427 p+=GetPixelChannels(image);
428 q+=GetPixelChannels(reconstruct_image);
429 continue;
430 }
cristyd09f8802012-02-04 16:44:10 +0000431 difference=MagickFalse;
cristydb5c82c2013-02-22 00:41:33 +0000432 Sa=QuantumScale*GetPixelAlpha(image,p);
433 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000434 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
435 {
cristydb5c82c2013-02-22 00:41:33 +0000436 double
437 distance;
438
cristy5a23c552013-02-13 14:34:28 +0000439 PixelChannel channel=GetPixelChannelChannel(image,i);
440 PixelTrait traits=GetPixelChannelTraits(image,channel);
441 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
442 channel);
cristy3fc482f2011-09-23 00:43:35 +0000443 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000444 (reconstruct_traits == UndefinedPixelTrait) ||
445 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000446 continue;
cristydb5c82c2013-02-22 00:41:33 +0000447 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
448 if ((distance*distance) > fuzz)
449 {
dirk8a7bbf42015-01-08 23:04:58 +0000450 channel_distortion[i]++;
cristydb5c82c2013-02-22 00:41:33 +0000451 difference=MagickTrue;
cristydb5c82c2013-02-22 00:41:33 +0000452 }
cristy3fc482f2011-09-23 00:43:35 +0000453 }
454 if (difference != MagickFalse)
dirk8a7bbf42015-01-08 23:04:58 +0000455 channel_distortion[CompositePixelChannel]++;
cristyed231572011-07-14 02:18:59 +0000456 p+=GetPixelChannels(image);
457 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000458 }
cristyb5d5f722009-11-04 03:03:49 +0000459#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000460 #pragma omp critical (MagickCore_GetAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +0000461#endif
cristy3fc482f2011-09-23 00:43:35 +0000462 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000463 distortion[i]+=channel_distortion[i];
464 }
465 reconstruct_view=DestroyCacheView(reconstruct_view);
466 image_view=DestroyCacheView(image_view);
467 return(status);
468}
469
cristy343eee92010-12-11 02:17:57 +0000470static MagickBooleanType GetFuzzDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000471 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy343eee92010-12-11 02:17:57 +0000472{
473 CacheView
474 *image_view,
475 *reconstruct_view;
476
cristy343eee92010-12-11 02:17:57 +0000477 MagickBooleanType
478 status;
479
480 register ssize_t
481 i;
482
cristye68f5992015-03-03 17:44:51 +0000483 size_t
484 columns,
485 rows;
486
cristy9d314ff2011-03-09 01:30:28 +0000487 ssize_t
488 y;
489
cristy343eee92010-12-11 02:17:57 +0000490 status=MagickTrue;
cristye68f5992015-03-03 17:44:51 +0000491 rows=MagickMax(image->rows,reconstruct_image->rows);
492 columns=MagickMax(image->columns,reconstruct_image->columns);
cristy46ff2672012-12-14 15:32:26 +0000493 image_view=AcquireVirtualCacheView(image,exception);
494 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristy343eee92010-12-11 02:17:57 +0000495#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000496 #pragma omp parallel for schedule(static,4) shared(status) \
cristye68f5992015-03-03 17:44:51 +0000497 magick_threads(image,image,rows,1)
cristy343eee92010-12-11 02:17:57 +0000498#endif
cristye68f5992015-03-03 17:44:51 +0000499 for (y=0; y < (ssize_t) rows; y++)
cristy343eee92010-12-11 02:17:57 +0000500 {
501 double
cristy3fc482f2011-09-23 00:43:35 +0000502 channel_distortion[MaxPixelChannels+1];
cristy343eee92010-12-11 02:17:57 +0000503
cristy4c08aed2011-07-01 19:47:50 +0000504 register const Quantum
cristy343eee92010-12-11 02:17:57 +0000505 *restrict p,
506 *restrict q;
507
508 register ssize_t
509 i,
510 x;
511
512 if (status == MagickFalse)
513 continue;
cristye68f5992015-03-03 17:44:51 +0000514 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
515 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000516 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy343eee92010-12-11 02:17:57 +0000517 {
518 status=MagickFalse;
519 continue;
520 }
cristy343eee92010-12-11 02:17:57 +0000521 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristye68f5992015-03-03 17:44:51 +0000522 for (x=0; x < (ssize_t) columns; x++)
cristy343eee92010-12-11 02:17:57 +0000523 {
cristydb5c82c2013-02-22 00:41:33 +0000524 double
525 Da,
526 Sa;
527
cristy3fc482f2011-09-23 00:43:35 +0000528 register ssize_t
529 i;
cristy343eee92010-12-11 02:17:57 +0000530
cristy883fde12013-04-08 00:50:13 +0000531 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000532 {
533 p+=GetPixelChannels(image);
534 q+=GetPixelChannels(reconstruct_image);
535 continue;
536 }
cristydb5c82c2013-02-22 00:41:33 +0000537 Sa=QuantumScale*GetPixelAlpha(image,p);
538 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000539 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
540 {
cristya19f1d72012-08-07 18:24:38 +0000541 double
cristy3fc482f2011-09-23 00:43:35 +0000542 distance;
543
cristy5a23c552013-02-13 14:34:28 +0000544 PixelChannel channel=GetPixelChannelChannel(image,i);
545 PixelTrait traits=GetPixelChannelTraits(image,channel);
546 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
547 channel);
cristy3fc482f2011-09-23 00:43:35 +0000548 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000549 (reconstruct_traits == UndefinedPixelTrait) ||
550 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000551 continue;
cristydb5c82c2013-02-22 00:41:33 +0000552 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
cristy1eced092012-08-10 23:10:56 +0000553 channel,q));
cristydb5c82c2013-02-22 00:41:33 +0000554 channel_distortion[i]+=distance*distance;
555 channel_distortion[CompositePixelChannel]+=distance*distance;
cristy3fc482f2011-09-23 00:43:35 +0000556 }
cristyed231572011-07-14 02:18:59 +0000557 p+=GetPixelChannels(image);
558 q+=GetPixelChannels(reconstruct_image);
cristy343eee92010-12-11 02:17:57 +0000559 }
560#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ad5c2a2012-05-05 21:32:54 +0000561 #pragma omp critical (MagickCore_GetFuzzDistortion)
cristy343eee92010-12-11 02:17:57 +0000562#endif
cristy3fc482f2011-09-23 00:43:35 +0000563 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000564 distortion[i]+=channel_distortion[i];
565 }
566 reconstruct_view=DestroyCacheView(reconstruct_view);
567 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000568 for (i=0; i <= MaxPixelChannels; i++)
cristye68f5992015-03-03 17:44:51 +0000569 distortion[i]/=((double) columns*rows);
cristy5f95f4f2011-10-23 01:01:01 +0000570 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
571 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
cristy343eee92010-12-11 02:17:57 +0000572 return(status);
573}
574
cristy3cc758f2010-11-27 01:33:49 +0000575static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000576 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000577{
cristyc4c8d132010-01-07 01:58:38 +0000578 CacheView
579 *image_view,
580 *reconstruct_view;
581
cristy3ed852e2009-09-05 21:47:34 +0000582 MagickBooleanType
583 status;
584
cristybb503372010-05-27 20:51:26 +0000585 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000586 i;
587
cristye68f5992015-03-03 17:44:51 +0000588 size_t
589 columns,
590 rows;
591
cristy9d314ff2011-03-09 01:30:28 +0000592 ssize_t
593 y;
594
cristy3ed852e2009-09-05 21:47:34 +0000595 status=MagickTrue;
cristye68f5992015-03-03 17:44:51 +0000596 rows=MagickMax(image->rows,reconstruct_image->rows);
597 columns=MagickMax(image->columns,reconstruct_image->columns);
cristy46ff2672012-12-14 15:32:26 +0000598 image_view=AcquireVirtualCacheView(image,exception);
599 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000600#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000601 #pragma omp parallel for schedule(static,4) shared(status) \
cristye68f5992015-03-03 17:44:51 +0000602 magick_threads(image,image,rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000603#endif
cristye68f5992015-03-03 17:44:51 +0000604 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000605 {
606 double
cristy3fc482f2011-09-23 00:43:35 +0000607 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000608
cristy4c08aed2011-07-01 19:47:50 +0000609 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000610 *restrict p,
611 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000612
cristybb503372010-05-27 20:51:26 +0000613 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000614 i,
615 x;
616
617 if (status == MagickFalse)
618 continue;
cristye68f5992015-03-03 17:44:51 +0000619 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
620 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000621 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000622 {
623 status=MagickFalse;
624 continue;
625 }
cristy3ed852e2009-09-05 21:47:34 +0000626 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristye68f5992015-03-03 17:44:51 +0000627 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000628 {
cristydb5c82c2013-02-22 00:41:33 +0000629 double
630 Da,
631 Sa;
632
cristy3fc482f2011-09-23 00:43:35 +0000633 register ssize_t
634 i;
cristy3ed852e2009-09-05 21:47:34 +0000635
cristy883fde12013-04-08 00:50:13 +0000636 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000637 {
638 p+=GetPixelChannels(image);
639 q+=GetPixelChannels(reconstruct_image);
640 continue;
641 }
cristydb5c82c2013-02-22 00:41:33 +0000642 Sa=QuantumScale*GetPixelAlpha(image,p);
643 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000644 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
645 {
cristya19f1d72012-08-07 18:24:38 +0000646 double
cristy3fc482f2011-09-23 00:43:35 +0000647 distance;
648
cristy5a23c552013-02-13 14:34:28 +0000649 PixelChannel channel=GetPixelChannelChannel(image,i);
650 PixelTrait traits=GetPixelChannelTraits(image,channel);
651 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
652 channel);
cristy3fc482f2011-09-23 00:43:35 +0000653 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000654 (reconstruct_traits == UndefinedPixelTrait) ||
655 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000656 continue;
cristydb5c82c2013-02-22 00:41:33 +0000657 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
658 channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000659 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000660 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000661 }
cristyed231572011-07-14 02:18:59 +0000662 p+=GetPixelChannels(image);
663 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000664 }
cristyb5d5f722009-11-04 03:03:49 +0000665#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000666 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +0000667#endif
cristy3fc482f2011-09-23 00:43:35 +0000668 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000669 distortion[i]+=channel_distortion[i];
670 }
671 reconstruct_view=DestroyCacheView(reconstruct_view);
672 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000673 for (i=0; i <= MaxPixelChannels; i++)
cristye68f5992015-03-03 17:44:51 +0000674 distortion[i]/=((double) columns*rows);
cristy5f95f4f2011-10-23 01:01:01 +0000675 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000676 return(status);
677}
678
679static MagickBooleanType GetMeanErrorPerPixel(Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000680 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000681{
cristyc4c8d132010-01-07 01:58:38 +0000682 CacheView
683 *image_view,
684 *reconstruct_view;
685
cristy3ed852e2009-09-05 21:47:34 +0000686 MagickBooleanType
687 status;
688
cristya19f1d72012-08-07 18:24:38 +0000689 double
cristy3ed852e2009-09-05 21:47:34 +0000690 area,
cristy3ed852e2009-09-05 21:47:34 +0000691 maximum_error,
692 mean_error;
693
cristye68f5992015-03-03 17:44:51 +0000694 size_t
695 columns,
696 rows;
697
cristy9d314ff2011-03-09 01:30:28 +0000698 ssize_t
699 y;
700
cristy3ed852e2009-09-05 21:47:34 +0000701 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000702 area=0.0;
703 maximum_error=0.0;
704 mean_error=0.0;
cristye68f5992015-03-03 17:44:51 +0000705 rows=MagickMax(image->rows,reconstruct_image->rows);
706 columns=MagickMax(image->columns,reconstruct_image->columns);
cristy46ff2672012-12-14 15:32:26 +0000707 image_view=AcquireVirtualCacheView(image,exception);
708 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristye68f5992015-03-03 17:44:51 +0000709 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000710 {
cristy4c08aed2011-07-01 19:47:50 +0000711 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000712 *restrict p,
713 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000714
cristybb503372010-05-27 20:51:26 +0000715 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000716 x;
717
cristye68f5992015-03-03 17:44:51 +0000718 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
719 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000720 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000721 {
722 status=MagickFalse;
723 break;
724 }
cristye68f5992015-03-03 17:44:51 +0000725 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000726 {
cristydb5c82c2013-02-22 00:41:33 +0000727 double
728 Da,
729 Sa;
730
cristy3fc482f2011-09-23 00:43:35 +0000731 register ssize_t
732 i;
cristy3ed852e2009-09-05 21:47:34 +0000733
cristy883fde12013-04-08 00:50:13 +0000734 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000735 {
736 p+=GetPixelChannels(image);
737 q+=GetPixelChannels(reconstruct_image);
738 continue;
739 }
cristydb5c82c2013-02-22 00:41:33 +0000740 Sa=QuantumScale*GetPixelAlpha(image,p);
741 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000742 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
743 {
cristya19f1d72012-08-07 18:24:38 +0000744 double
cristy3fc482f2011-09-23 00:43:35 +0000745 distance;
746
cristy5a23c552013-02-13 14:34:28 +0000747 PixelChannel channel=GetPixelChannelChannel(image,i);
748 PixelTrait traits=GetPixelChannelTraits(image,channel);
749 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
750 channel);
cristy3fc482f2011-09-23 00:43:35 +0000751 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000752 (reconstruct_traits == UndefinedPixelTrait) ||
753 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000754 continue;
cristy0c935612014-08-21 16:44:46 +0000755 distance=fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000756 distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000757 distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000758 mean_error+=distance*distance;
759 if (distance > maximum_error)
760 maximum_error=distance;
761 area++;
762 }
cristyed231572011-07-14 02:18:59 +0000763 p+=GetPixelChannels(image);
764 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000765 }
766 }
767 reconstruct_view=DestroyCacheView(reconstruct_view);
768 image_view=DestroyCacheView(image_view);
cristy5f95f4f2011-10-23 01:01:01 +0000769 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
cristy3ed852e2009-09-05 21:47:34 +0000770 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
771 image->error.normalized_maximum_error=QuantumScale*maximum_error;
772 return(status);
773}
774
cristy3cc758f2010-11-27 01:33:49 +0000775static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000776 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000777{
cristyc4c8d132010-01-07 01:58:38 +0000778 CacheView
779 *image_view,
780 *reconstruct_view;
781
cristy3ed852e2009-09-05 21:47:34 +0000782 MagickBooleanType
783 status;
784
cristybb503372010-05-27 20:51:26 +0000785 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000786 i;
787
cristye68f5992015-03-03 17:44:51 +0000788 size_t
789 columns,
790 rows;
791
cristy9d314ff2011-03-09 01:30:28 +0000792 ssize_t
793 y;
794
cristy3ed852e2009-09-05 21:47:34 +0000795 status=MagickTrue;
cristye68f5992015-03-03 17:44:51 +0000796 rows=MagickMax(image->rows,reconstruct_image->rows);
797 columns=MagickMax(image->columns,reconstruct_image->columns);
cristy46ff2672012-12-14 15:32:26 +0000798 image_view=AcquireVirtualCacheView(image,exception);
799 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000800#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000801 #pragma omp parallel for schedule(static,4) shared(status) \
cristye68f5992015-03-03 17:44:51 +0000802 magick_threads(image,image,rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000803#endif
cristye68f5992015-03-03 17:44:51 +0000804 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000805 {
806 double
cristy3fc482f2011-09-23 00:43:35 +0000807 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000808
cristy4c08aed2011-07-01 19:47:50 +0000809 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000810 *restrict p,
811 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000812
cristybb503372010-05-27 20:51:26 +0000813 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000814 i,
815 x;
816
817 if (status == MagickFalse)
818 continue;
cristye68f5992015-03-03 17:44:51 +0000819 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
820 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000821 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000822 {
823 status=MagickFalse;
824 continue;
825 }
cristy3ed852e2009-09-05 21:47:34 +0000826 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristye68f5992015-03-03 17:44:51 +0000827 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000828 {
cristydb5c82c2013-02-22 00:41:33 +0000829 double
830 Da,
831 Sa;
832
cristy3fc482f2011-09-23 00:43:35 +0000833 register ssize_t
834 i;
cristy3ed852e2009-09-05 21:47:34 +0000835
cristy883fde12013-04-08 00:50:13 +0000836 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000837 {
838 p+=GetPixelChannels(image);
839 q+=GetPixelChannels(reconstruct_image);
840 continue;
841 }
cristydb5c82c2013-02-22 00:41:33 +0000842 Sa=QuantumScale*GetPixelAlpha(image,p);
843 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000844 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
845 {
cristya19f1d72012-08-07 18:24:38 +0000846 double
cristy3fc482f2011-09-23 00:43:35 +0000847 distance;
848
cristy5a23c552013-02-13 14:34:28 +0000849 PixelChannel channel=GetPixelChannelChannel(image,i);
850 PixelTrait traits=GetPixelChannelTraits(image,channel);
851 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
852 channel);
cristy3fc482f2011-09-23 00:43:35 +0000853 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000854 (reconstruct_traits == UndefinedPixelTrait) ||
855 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000856 continue;
cristydb5c82c2013-02-22 00:41:33 +0000857 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
858 channel,q));
859 channel_distortion[i]+=distance*distance;
860 channel_distortion[CompositePixelChannel]+=distance*distance;
cristy3fc482f2011-09-23 00:43:35 +0000861 }
cristyed231572011-07-14 02:18:59 +0000862 p+=GetPixelChannels(image);
863 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000864 }
cristyb5d5f722009-11-04 03:03:49 +0000865#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000866 #pragma omp critical (MagickCore_GetMeanSquaredError)
cristy3ed852e2009-09-05 21:47:34 +0000867#endif
cristy3fc482f2011-09-23 00:43:35 +0000868 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000869 distortion[i]+=channel_distortion[i];
870 }
871 reconstruct_view=DestroyCacheView(reconstruct_view);
872 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000873 for (i=0; i <= MaxPixelChannels; i++)
cristye68f5992015-03-03 17:44:51 +0000874 distortion[i]/=((double) columns*rows);
cristy5f95f4f2011-10-23 01:01:01 +0000875 distortion[CompositePixelChannel]/=GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000876 return(status);
877}
878
cristy3cc758f2010-11-27 01:33:49 +0000879static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
cristy8a9106f2011-07-05 14:39:26 +0000880 const Image *image,const Image *reconstruct_image,double *distortion,
881 ExceptionInfo *exception)
cristy4c929a72010-11-24 18:54:42 +0000882{
cristy9f48ca62010-11-25 03:06:31 +0000883#define SimilarityImageTag "Similarity/Image"
884
cristy4c929a72010-11-24 18:54:42 +0000885 CacheView
886 *image_view,
887 *reconstruct_view;
888
cristy9f48ca62010-11-25 03:06:31 +0000889 ChannelStatistics
cristy9f48ca62010-11-25 03:06:31 +0000890 *image_statistics,
891 *reconstruct_statistics;
892
cristydb5c82c2013-02-22 00:41:33 +0000893 double
894 area;
895
cristy4c929a72010-11-24 18:54:42 +0000896 MagickBooleanType
897 status;
898
cristy18a41362010-11-27 15:56:18 +0000899 MagickOffsetType
900 progress;
901
cristy4c929a72010-11-24 18:54:42 +0000902 register ssize_t
903 i;
904
cristye68f5992015-03-03 17:44:51 +0000905 size_t
906 columns,
907 rows;
908
cristy3cc758f2010-11-27 01:33:49 +0000909 ssize_t
910 y;
911
cristy34d6fdc2010-11-26 19:06:08 +0000912 /*
cristy18a41362010-11-27 15:56:18 +0000913 Normalize to account for variation due to lighting and exposure condition.
cristy34d6fdc2010-11-26 19:06:08 +0000914 */
cristyd42d9952011-07-08 14:21:50 +0000915 image_statistics=GetImageStatistics(image,exception);
916 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
cristye287bba2013-09-02 20:04:42 +0000917 if ((image_statistics == (ChannelStatistics *) NULL) ||
918 (reconstruct_statistics == (ChannelStatistics *) NULL))
919 {
920 if (image_statistics != (ChannelStatistics *) NULL)
921 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
922 image_statistics);
923 if (reconstruct_statistics != (ChannelStatistics *) NULL)
924 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
925 reconstruct_statistics);
926 return(MagickFalse);
927 }
cristy4c929a72010-11-24 18:54:42 +0000928 status=MagickTrue;
cristy9f48ca62010-11-25 03:06:31 +0000929 progress=0;
cristy3fc482f2011-09-23 00:43:35 +0000930 for (i=0; i <= MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000931 distortion[i]=0.0;
cristye68f5992015-03-03 17:44:51 +0000932 rows=MagickMax(image->rows,reconstruct_image->rows);
933 columns=MagickMax(image->columns,reconstruct_image->columns);
dirk4285cbc2015-08-23 13:45:22 +0200934 area=1.0/((double) columns*rows-1);
cristy46ff2672012-12-14 15:32:26 +0000935 image_view=AcquireVirtualCacheView(image,exception);
936 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristye68f5992015-03-03 17:44:51 +0000937 for (y=0; y < (ssize_t) rows; y++)
cristy4c929a72010-11-24 18:54:42 +0000938 {
cristy4c08aed2011-07-01 19:47:50 +0000939 register const Quantum
cristy4c929a72010-11-24 18:54:42 +0000940 *restrict p,
941 *restrict q;
942
943 register ssize_t
cristy4c929a72010-11-24 18:54:42 +0000944 x;
945
cristye68f5992015-03-03 17:44:51 +0000946 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
947 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000948 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy4c929a72010-11-24 18:54:42 +0000949 {
950 status=MagickFalse;
dirk4285cbc2015-08-23 13:45:22 +0200951 break;
cristy4c929a72010-11-24 18:54:42 +0000952 }
cristye68f5992015-03-03 17:44:51 +0000953 for (x=0; x < (ssize_t) columns; x++)
cristy4c929a72010-11-24 18:54:42 +0000954 {
cristydb5c82c2013-02-22 00:41:33 +0000955 double
956 Da,
957 Sa;
958
cristy883fde12013-04-08 00:50:13 +0000959 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000960 {
961 p+=GetPixelChannels(image);
962 q+=GetPixelChannels(reconstruct_image);
963 continue;
964 }
dirk4285cbc2015-08-23 13:45:22 +0200965 Sa=QuantumScale*(image->alpha_trait != UndefinedPixelTrait ?
966 GetPixelAlpha(image,p) : OpaqueAlpha);
967 Da=QuantumScale*(reconstruct_image->alpha_trait != UndefinedPixelTrait ?
968 GetPixelAlpha(reconstruct_image,p) : OpaqueAlpha);
cristy3fc482f2011-09-23 00:43:35 +0000969 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
970 {
cristy5a23c552013-02-13 14:34:28 +0000971 PixelChannel channel=GetPixelChannelChannel(image,i);
972 PixelTrait traits=GetPixelChannelTraits(image,channel);
973 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
974 channel);
cristy3fc482f2011-09-23 00:43:35 +0000975 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000976 (reconstruct_traits == UndefinedPixelTrait) ||
977 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000978 continue;
dirk4285cbc2015-08-23 13:45:22 +0200979 if (channel == AlphaPixelChannel)
980 {
981 distortion[i]+=area*QuantumScale*(p[i]-
982 image_statistics[channel].mean)*(GetPixelChannel(
983 reconstruct_image,channel,q)-
984 reconstruct_statistics[channel].mean);
985 }
986 else
987 {
988 distortion[i]+=area*QuantumScale*(Sa*p[i]-
989 image_statistics[channel].mean)*(Da*GetPixelChannel(
990 reconstruct_image,channel,q)-
991 reconstruct_statistics[channel].mean);
992 }
cristy3fc482f2011-09-23 00:43:35 +0000993 }
cristyed231572011-07-14 02:18:59 +0000994 p+=GetPixelChannels(image);
cristy10a6c612012-01-29 21:41:05 +0000995 q+=GetPixelChannels(reconstruct_image);
cristy4c929a72010-11-24 18:54:42 +0000996 }
cristy9f48ca62010-11-25 03:06:31 +0000997 if (image->progress_monitor != (MagickProgressMonitor) NULL)
998 {
999 MagickBooleanType
1000 proceed;
1001
cristy8967d582015-03-03 22:59:12 +00001002 proceed=SetImageProgress(image,SimilarityImageTag,progress++,rows);
cristy9f48ca62010-11-25 03:06:31 +00001003 if (proceed == MagickFalse)
dirk4285cbc2015-08-23 13:45:22 +02001004 {
1005 status=MagickFalse;
1006 break;
1007 }
cristy9f48ca62010-11-25 03:06:31 +00001008 }
cristy4c929a72010-11-24 18:54:42 +00001009 }
1010 reconstruct_view=DestroyCacheView(reconstruct_view);
1011 image_view=DestroyCacheView(image_view);
cristy34d6fdc2010-11-26 19:06:08 +00001012 /*
1013 Divide by the standard deviation.
1014 */
cristy5f95f4f2011-10-23 01:01:01 +00001015 distortion[CompositePixelChannel]=0.0;
dirk4285cbc2015-08-23 13:45:22 +02001016 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy34d6fdc2010-11-26 19:06:08 +00001017 {
cristya19f1d72012-08-07 18:24:38 +00001018 double
cristy18a41362010-11-27 15:56:18 +00001019 gamma;
cristy34d6fdc2010-11-26 19:06:08 +00001020
cristy5a23c552013-02-13 14:34:28 +00001021 PixelChannel channel=GetPixelChannelChannel(image,i);
dirk4285cbc2015-08-23 13:45:22 +02001022 gamma=image_statistics[channel].standard_deviation*
cristy3fc482f2011-09-23 00:43:35 +00001023 reconstruct_statistics[channel].standard_deviation;
cristy3e3ec3a2012-11-03 23:11:06 +00001024 gamma=PerceptibleReciprocal(gamma);
cristy18a41362010-11-27 15:56:18 +00001025 distortion[i]=QuantumRange*gamma*distortion[i];
cristy5f95f4f2011-10-23 01:01:01 +00001026 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
cristy34d6fdc2010-11-26 19:06:08 +00001027 }
cristy5f95f4f2011-10-23 01:01:01 +00001028 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
cristy49dd6a02011-09-24 23:08:01 +00001029 GetImageChannels(image));
cristy34d6fdc2010-11-26 19:06:08 +00001030 /*
1031 Free resources.
1032 */
cristy9f48ca62010-11-25 03:06:31 +00001033 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1034 reconstruct_statistics);
1035 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1036 image_statistics);
cristy4c929a72010-11-24 18:54:42 +00001037 return(status);
1038}
1039
cristy3cc758f2010-11-27 01:33:49 +00001040static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001041 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001042{
cristyc4c8d132010-01-07 01:58:38 +00001043 CacheView
1044 *image_view,
1045 *reconstruct_view;
1046
cristy3ed852e2009-09-05 21:47:34 +00001047 MagickBooleanType
1048 status;
1049
cristye68f5992015-03-03 17:44:51 +00001050 size_t
1051 columns,
1052 rows;
1053
cristy9d314ff2011-03-09 01:30:28 +00001054 ssize_t
1055 y;
1056
cristy3ed852e2009-09-05 21:47:34 +00001057 status=MagickTrue;
cristye68f5992015-03-03 17:44:51 +00001058 rows=MagickMax(image->rows,reconstruct_image->rows);
1059 columns=MagickMax(image->columns,reconstruct_image->columns);
cristy46ff2672012-12-14 15:32:26 +00001060 image_view=AcquireVirtualCacheView(image,exception);
1061 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001062#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001063 #pragma omp parallel for schedule(static,4) shared(status) \
cristye68f5992015-03-03 17:44:51 +00001064 magick_threads(image,image,rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001065#endif
cristye68f5992015-03-03 17:44:51 +00001066 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001067 {
1068 double
cristy3fc482f2011-09-23 00:43:35 +00001069 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +00001070
cristy4c08aed2011-07-01 19:47:50 +00001071 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001072 *restrict p,
1073 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001074
cristybb503372010-05-27 20:51:26 +00001075 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001076 i,
1077 x;
1078
1079 if (status == MagickFalse)
1080 continue;
cristye68f5992015-03-03 17:44:51 +00001081 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1082 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +00001083 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001084 {
1085 status=MagickFalse;
1086 continue;
1087 }
cristy3ed852e2009-09-05 21:47:34 +00001088 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristye68f5992015-03-03 17:44:51 +00001089 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001090 {
cristydb5c82c2013-02-22 00:41:33 +00001091 double
1092 Da,
1093 Sa;
1094
cristy3fc482f2011-09-23 00:43:35 +00001095 register ssize_t
1096 i;
cristy3ed852e2009-09-05 21:47:34 +00001097
cristy883fde12013-04-08 00:50:13 +00001098 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +00001099 {
1100 p+=GetPixelChannels(image);
1101 q+=GetPixelChannels(reconstruct_image);
1102 continue;
1103 }
cristydb5c82c2013-02-22 00:41:33 +00001104 Sa=QuantumScale*GetPixelAlpha(image,p);
1105 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +00001106 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1107 {
cristya19f1d72012-08-07 18:24:38 +00001108 double
cristy3fc482f2011-09-23 00:43:35 +00001109 distance;
1110
cristy5a23c552013-02-13 14:34:28 +00001111 PixelChannel channel=GetPixelChannelChannel(image,i);
1112 PixelTrait traits=GetPixelChannelTraits(image,channel);
1113 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1114 channel);
cristy3fc482f2011-09-23 00:43:35 +00001115 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001116 (reconstruct_traits == UndefinedPixelTrait) ||
1117 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001118 continue;
cristydb5c82c2013-02-22 00:41:33 +00001119 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
1120 channel,q));
cristy25a5f2f2011-09-24 14:09:43 +00001121 if (distance > channel_distortion[i])
cristy3fc482f2011-09-23 00:43:35 +00001122 channel_distortion[i]=distance;
cristy5f95f4f2011-10-23 01:01:01 +00001123 if (distance > channel_distortion[CompositePixelChannel])
1124 channel_distortion[CompositePixelChannel]=distance;
cristy3fc482f2011-09-23 00:43:35 +00001125 }
cristyed231572011-07-14 02:18:59 +00001126 p+=GetPixelChannels(image);
cristy10a6c612012-01-29 21:41:05 +00001127 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001128 }
cristyb5d5f722009-11-04 03:03:49 +00001129#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001130 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +00001131#endif
cristy3fc482f2011-09-23 00:43:35 +00001132 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001133 if (channel_distortion[i] > distortion[i])
1134 distortion[i]=channel_distortion[i];
1135 }
1136 reconstruct_view=DestroyCacheView(reconstruct_view);
1137 image_view=DestroyCacheView(image_view);
1138 return(status);
1139}
1140
cristy0633a1f2014-02-01 21:54:58 +00001141static inline double MagickLog10(const double x)
cristyd94b0b32014-01-30 23:19:20 +00001142{
cristy1a895c32014-02-05 18:01:41 +00001143#define Log10Epsilon (1.0e-11)
cristycb9af4a2014-02-05 13:31:55 +00001144
1145 if (fabs(x) < Log10Epsilon)
1146 return(log10(Log10Epsilon));
cristya6f32bf2014-02-02 19:08:58 +00001147 return(log10(fabs(x)));
cristyd94b0b32014-01-30 23:19:20 +00001148}
1149
cristy3ed852e2009-09-05 21:47:34 +00001150static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001151 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001152{
1153 MagickBooleanType
1154 status;
1155
cristy3fc482f2011-09-23 00:43:35 +00001156 register ssize_t
1157 i;
1158
cristy8a9106f2011-07-05 14:39:26 +00001159 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001160 for (i=0; i <= MaxPixelChannels; i++)
cristy0633a1f2014-02-01 21:54:58 +00001161 distortion[i]=20.0*MagickLog10((double) 1.0/sqrt(distortion[i]));
cristy3ed852e2009-09-05 21:47:34 +00001162 return(status);
1163}
1164
cristy03d6f862014-01-08 18:34:48 +00001165static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
1166 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1167{
cristyf8f39d42014-02-22 21:44:38 +00001168 ChannelPerceptualHash
1169 *image_phash,
1170 *reconstruct_phash;
cristyd9244cd2014-01-30 23:09:31 +00001171
cristya4a66012014-02-08 19:39:34 +00001172 ssize_t
1173 channel;
cristybc0adce2014-01-08 23:15:49 +00001174
cristy21b687b2014-01-10 00:17:34 +00001175 /*
cristyf8f39d42014-02-22 21:44:38 +00001176 Compute perceptual hash in the sRGB colorspace.
cristy21b687b2014-01-10 00:17:34 +00001177 */
cristyb3538ec2014-02-22 23:13:34 +00001178 image_phash=GetImagePerceptualHash(image,exception);
cristyf8f39d42014-02-22 21:44:38 +00001179 if (image_phash == (ChannelPerceptualHash *) NULL)
cristybc0adce2014-01-08 23:15:49 +00001180 return(MagickFalse);
cristyb3538ec2014-02-22 23:13:34 +00001181 reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
cristy4d0ca342014-05-01 00:42:09 +00001182 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
cristy2661ac12014-01-10 14:21:07 +00001183 {
cristyf8f39d42014-02-22 21:44:38 +00001184 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
cristy2661ac12014-01-10 14:21:07 +00001185 return(MagickFalse);
1186 }
cristya4a66012014-02-08 19:39:34 +00001187#if defined(MAGICKCORE_OPENMP_SUPPORT)
1188 #pragma omp parallel for schedule(static,4)
1189#endif
1190 for (channel=0; channel < MaxPixelChannels; channel++)
cristybc0adce2014-01-08 23:15:49 +00001191 {
cristya4a66012014-02-08 19:39:34 +00001192 double
1193 difference;
cristy6bd008f2014-01-30 23:24:06 +00001194
cristya4a66012014-02-08 19:39:34 +00001195 register ssize_t
1196 i;
1197
1198 difference=0.0;
cristy3ac6aa92014-09-08 11:48:28 +00001199 for (i=0; i < MaximumNumberOfImageMoments; i++)
cristybc0adce2014-01-08 23:15:49 +00001200 {
cristya4a66012014-02-08 19:39:34 +00001201 double
1202 alpha,
1203 beta;
1204
cristyc0187622014-09-06 00:45:58 +00001205 alpha=image_phash[channel].srgb_hu_phash[i];
1206 beta=reconstruct_phash[channel].srgb_hu_phash[i];
cristya4a66012014-02-08 19:39:34 +00001207 difference+=(beta-alpha)*(beta-alpha);
cristybc0adce2014-01-08 23:15:49 +00001208 }
cristya4a66012014-02-08 19:39:34 +00001209 distortion[channel]+=difference;
1210#if defined(MAGICKCORE_OPENMP_SUPPORT)
1211 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1212#endif
1213 distortion[CompositePixelChannel]+=difference;
cristybc0adce2014-01-08 23:15:49 +00001214 }
cristy21b687b2014-01-10 00:17:34 +00001215 /*
1216 Compute perceptual hash in the HCLP colorspace.
1217 */
cristya4a66012014-02-08 19:39:34 +00001218#if defined(MAGICKCORE_OPENMP_SUPPORT)
1219 #pragma omp parallel for schedule(static,4)
1220#endif
1221 for (channel=0; channel < MaxPixelChannels; channel++)
cristy21b687b2014-01-10 00:17:34 +00001222 {
cristya4a66012014-02-08 19:39:34 +00001223 double
1224 difference;
cristy6bd008f2014-01-30 23:24:06 +00001225
cristya4a66012014-02-08 19:39:34 +00001226 register ssize_t
1227 i;
1228
1229 difference=0.0;
cristy3ac6aa92014-09-08 11:48:28 +00001230 for (i=0; i < MaximumNumberOfImageMoments; i++)
cristy21b687b2014-01-10 00:17:34 +00001231 {
cristya4a66012014-02-08 19:39:34 +00001232 double
1233 alpha,
1234 beta;
1235
cristyc0187622014-09-06 00:45:58 +00001236 alpha=image_phash[channel].hclp_hu_phash[i];
1237 beta=reconstruct_phash[channel].hclp_hu_phash[i];
cristya4a66012014-02-08 19:39:34 +00001238 difference+=(beta-alpha)*(beta-alpha);
cristy21b687b2014-01-10 00:17:34 +00001239 }
cristya4a66012014-02-08 19:39:34 +00001240 distortion[channel]+=difference;
1241#if defined(MAGICKCORE_OPENMP_SUPPORT)
1242 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1243#endif
1244 distortion[CompositePixelChannel]+=difference;
cristy21b687b2014-01-10 00:17:34 +00001245 }
cristyf8f39d42014-02-22 21:44:38 +00001246 /*
1247 Free resources.
1248 */
1249 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1250 reconstruct_phash);
1251 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
cristy03d6f862014-01-08 18:34:48 +00001252 return(MagickTrue);
1253}
1254
cristy3cc758f2010-11-27 01:33:49 +00001255static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001256 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001257{
1258 MagickBooleanType
1259 status;
1260
cristy3fc482f2011-09-23 00:43:35 +00001261 register ssize_t
1262 i;
1263
cristy8a9106f2011-07-05 14:39:26 +00001264 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001265 for (i=0; i <= MaxPixelChannels; i++)
1266 distortion[i]=sqrt(distortion[i]);
cristy3ed852e2009-09-05 21:47:34 +00001267 return(status);
1268}
1269
cristy8a9106f2011-07-05 14:39:26 +00001270MagickExport MagickBooleanType GetImageDistortion(Image *image,
1271 const Image *reconstruct_image,const MetricType metric,double *distortion,
1272 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001273{
1274 double
1275 *channel_distortion;
1276
1277 MagickBooleanType
1278 status;
1279
1280 size_t
1281 length;
1282
1283 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001284 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001285 if (image->debug != MagickFalse)
1286 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1287 assert(reconstruct_image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001288 assert(reconstruct_image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001289 assert(distortion != (double *) NULL);
1290 *distortion=0.0;
1291 if (image->debug != MagickFalse)
1292 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8b520b42014-01-30 00:58:45 +00001293 if (metric != PerceptualHashErrorMetric)
cristy73626632014-07-19 20:52:21 +00001294 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1295 ThrowBinaryException(ImageError,"ImageMorphologyDiffers",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);
cristye1c94d92015-06-28 12:16:33 +00001423 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001424 if (image->debug != MagickFalse)
1425 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1426 assert(reconstruct_image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001427 assert(reconstruct_image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001428 if (image->debug != MagickFalse)
1429 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8b520b42014-01-30 00:58:45 +00001430 if (metric != PerceptualHashErrorMetric)
cristy73626632014-07-19 20:52:21 +00001431 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
cristy8b520b42014-01-30 00:58:45 +00001432 {
1433 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
cristy73626632014-07-19 20:52:21 +00001434 "ImageMorphologyDiffers","`%s'",image->filename);
cristy8b520b42014-01-30 00:58:45 +00001435 return((double *) NULL);
1436 }
cristy3ed852e2009-09-05 21:47:34 +00001437 /*
1438 Get image distortion.
1439 */
cristy3fc482f2011-09-23 00:43:35 +00001440 length=MaxPixelChannels+1UL;
cristy3ed852e2009-09-05 21:47:34 +00001441 channel_distortion=(double *) AcquireQuantumMemory(length,
1442 sizeof(*channel_distortion));
1443 if (channel_distortion == (double *) NULL)
1444 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1445 (void) ResetMagickMemory(channel_distortion,0,length*
1446 sizeof(*channel_distortion));
cristyda16f162011-02-19 23:52:17 +00001447 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +00001448 switch (metric)
1449 {
1450 case AbsoluteErrorMetric:
1451 {
cristy8a9106f2011-07-05 14:39:26 +00001452 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1453 exception);
cristy3ed852e2009-09-05 21:47:34 +00001454 break;
1455 }
cristy343eee92010-12-11 02:17:57 +00001456 case FuzzErrorMetric:
1457 {
cristy8a9106f2011-07-05 14:39:26 +00001458 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1459 exception);
cristy343eee92010-12-11 02:17:57 +00001460 break;
1461 }
cristy3ed852e2009-09-05 21:47:34 +00001462 case MeanAbsoluteErrorMetric:
1463 {
cristy8a9106f2011-07-05 14:39:26 +00001464 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001465 channel_distortion,exception);
1466 break;
1467 }
cristy03fa69f2014-01-08 18:36:49 +00001468 case MeanErrorPerPixelErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001469 {
cristy8a9106f2011-07-05 14:39:26 +00001470 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1471 exception);
cristy3ed852e2009-09-05 21:47:34 +00001472 break;
1473 }
1474 case MeanSquaredErrorMetric:
1475 {
cristy8a9106f2011-07-05 14:39:26 +00001476 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001477 channel_distortion,exception);
1478 break;
1479 }
cristy4c929a72010-11-24 18:54:42 +00001480 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001481 default:
cristy4c929a72010-11-24 18:54:42 +00001482 {
cristy3cc758f2010-11-27 01:33:49 +00001483 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001484 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001485 break;
1486 }
cristy3ed852e2009-09-05 21:47:34 +00001487 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001488 {
cristy8a9106f2011-07-05 14:39:26 +00001489 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001490 channel_distortion,exception);
1491 break;
1492 }
cristy03d6f862014-01-08 18:34:48 +00001493 case PeakSignalToNoiseRatioErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001494 {
cristy8a9106f2011-07-05 14:39:26 +00001495 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001496 channel_distortion,exception);
1497 break;
1498 }
cristy03d6f862014-01-08 18:34:48 +00001499 case PerceptualHashErrorMetric:
1500 {
1501 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1502 channel_distortion,exception);
1503 break;
1504 }
cristy3ed852e2009-09-05 21:47:34 +00001505 case RootMeanSquaredErrorMetric:
1506 {
cristy8a9106f2011-07-05 14:39:26 +00001507 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001508 channel_distortion,exception);
1509 break;
1510 }
1511 }
cristyda16f162011-02-19 23:52:17 +00001512 if (status == MagickFalse)
1513 {
1514 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1515 return((double *) NULL);
1516 }
cristy3ed852e2009-09-05 21:47:34 +00001517 return(channel_distortion);
1518}
1519
1520/*
1521%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1522% %
1523% %
1524% %
1525% I s I m a g e s E q u a l %
1526% %
1527% %
1528% %
1529%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1530%
1531% IsImagesEqual() measures the difference between colors at each pixel
1532% location of two images. A value other than 0 means the colors match
1533% exactly. Otherwise an error measure is computed by summing over all
1534% pixels in an image the distance squared in RGB space between each image
1535% pixel and its corresponding pixel in the reconstruct image. The error
1536% measure is assigned to these image members:
1537%
1538% o mean_error_per_pixel: The mean error for any single pixel in
1539% the image.
1540%
1541% o normalized_mean_error: The normalized mean quantization error for
1542% any single pixel in the image. This distance measure is normalized to
1543% a range between 0 and 1. It is independent of the range of red, green,
1544% and blue values in the image.
1545%
1546% o normalized_maximum_error: The normalized maximum quantization
1547% error for any single pixel in the image. This distance measure is
1548% normalized to a range between 0 and 1. It is independent of the range
1549% of red, green, and blue values in your image.
1550%
1551% A small normalized mean square error, accessed as
1552% image->normalized_mean_error, suggests the images are very similar in
1553% spatial layout and color.
1554%
1555% The format of the IsImagesEqual method is:
1556%
1557% MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001558% const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001559%
1560% A description of each parameter follows.
1561%
1562% o image: the image.
1563%
1564% o reconstruct_image: the reconstruct image.
1565%
cristy018f07f2011-09-04 21:15:19 +00001566% o exception: return any errors or warnings in this structure.
1567%
cristy3ed852e2009-09-05 21:47:34 +00001568*/
1569MagickExport MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001570 const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001571{
cristyc4c8d132010-01-07 01:58:38 +00001572 CacheView
1573 *image_view,
1574 *reconstruct_view;
1575
cristy3ed852e2009-09-05 21:47:34 +00001576 MagickBooleanType
1577 status;
1578
cristya19f1d72012-08-07 18:24:38 +00001579 double
cristy3ed852e2009-09-05 21:47:34 +00001580 area,
1581 maximum_error,
1582 mean_error,
1583 mean_error_per_pixel;
1584
cristye68f5992015-03-03 17:44:51 +00001585 size_t
1586 columns,
1587 rows;
1588
cristy9d314ff2011-03-09 01:30:28 +00001589 ssize_t
1590 y;
1591
cristy3ed852e2009-09-05 21:47:34 +00001592 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001593 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001594 assert(reconstruct_image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001595 assert(reconstruct_image->signature == MagickCoreSignature);
cristy73626632014-07-19 20:52:21 +00001596 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1597 ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001598 area=0.0;
1599 maximum_error=0.0;
1600 mean_error_per_pixel=0.0;
1601 mean_error=0.0;
cristye68f5992015-03-03 17:44:51 +00001602 rows=MagickMax(image->rows,reconstruct_image->rows);
1603 columns=MagickMax(image->columns,reconstruct_image->columns);
cristy46ff2672012-12-14 15:32:26 +00001604 image_view=AcquireVirtualCacheView(image,exception);
1605 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristye68f5992015-03-03 17:44:51 +00001606 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001607 {
cristy4c08aed2011-07-01 19:47:50 +00001608 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001609 *restrict p,
1610 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001611
cristybb503372010-05-27 20:51:26 +00001612 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001613 x;
1614
cristye68f5992015-03-03 17:44:51 +00001615 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1616 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001617 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001618 break;
cristye68f5992015-03-03 17:44:51 +00001619 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001620 {
cristyd5c15f92011-09-23 00:58:33 +00001621 register ssize_t
1622 i;
cristy3ed852e2009-09-05 21:47:34 +00001623
cristy883fde12013-04-08 00:50:13 +00001624 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +00001625 {
1626 p+=GetPixelChannels(image);
1627 q+=GetPixelChannels(reconstruct_image);
1628 continue;
1629 }
cristyd5c15f92011-09-23 00:58:33 +00001630 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1631 {
cristya19f1d72012-08-07 18:24:38 +00001632 double
cristyd5c15f92011-09-23 00:58:33 +00001633 distance;
1634
cristy5a23c552013-02-13 14:34:28 +00001635 PixelChannel channel=GetPixelChannelChannel(image,i);
1636 PixelTrait traits=GetPixelChannelTraits(image,channel);
1637 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1638 channel);
cristyd5c15f92011-09-23 00:58:33 +00001639 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001640 (reconstruct_traits == UndefinedPixelTrait) ||
1641 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001642 continue;
cristya19f1d72012-08-07 18:24:38 +00001643 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
cristy0beccfa2011-09-25 20:47:53 +00001644 channel,q));
dirk86d34132014-08-31 10:25:26 +00001645 if (distance >= MagickEpsilon)
1646 {
1647 mean_error_per_pixel+=distance;
1648 mean_error+=distance*distance;
1649 if (distance > maximum_error)
1650 maximum_error=distance;
1651 }
cristyd5c15f92011-09-23 00:58:33 +00001652 area++;
1653 }
cristyed231572011-07-14 02:18:59 +00001654 p+=GetPixelChannels(image);
1655 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001656 }
1657 }
1658 reconstruct_view=DestroyCacheView(reconstruct_view);
1659 image_view=DestroyCacheView(image_view);
1660 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1661 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1662 mean_error/area);
1663 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1664 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1665 return(status);
1666}
1667
1668/*
1669%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1670% %
1671% %
1672% %
1673% S i m i l a r i t y I m a g e %
1674% %
1675% %
1676% %
1677%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1678%
1679% SimilarityImage() compares the reference image of the image and returns the
1680% best match offset. In addition, it returns a similarity image such that an
1681% exact match location is completely white and if none of the pixels match,
1682% black, otherwise some gray level in-between.
1683%
1684% The format of the SimilarityImageImage method is:
1685%
1686% Image *SimilarityImage(const Image *image,const Image *reference,
cristy62e52182013-03-15 14:26:17 +00001687% const MetricType metric,const double similarity_threshold,
1688% RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001689%
1690% A description of each parameter follows:
1691%
1692% o image: the image.
1693%
1694% o reference: find an area of the image that closely resembles this image.
1695%
cristy09136812011-10-18 15:24:30 +00001696% o metric: the metric.
1697%
cristy62e52182013-03-15 14:26:17 +00001698% o similarity_threshold: minimum distortion for (sub)image match.
1699%
1700% o offset: the best match offset of the reference image within the image.
cristy3ed852e2009-09-05 21:47:34 +00001701%
1702% o similarity: the computed similarity between the images.
1703%
1704% o exception: return any errors or warnings in this structure.
1705%
1706*/
1707
1708static double GetSimilarityMetric(const Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001709 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1710 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001711{
1712 double
cristy3cc758f2010-11-27 01:33:49 +00001713 distortion;
cristy3ed852e2009-09-05 21:47:34 +00001714
cristy713ff212010-11-26 21:56:11 +00001715 Image
1716 *similarity_image;
cristy3ed852e2009-09-05 21:47:34 +00001717
cristy09136812011-10-18 15:24:30 +00001718 MagickBooleanType
1719 status;
1720
cristy713ff212010-11-26 21:56:11 +00001721 RectangleInfo
1722 geometry;
cristy3ed852e2009-09-05 21:47:34 +00001723
cristy713ff212010-11-26 21:56:11 +00001724 SetGeometry(reference,&geometry);
1725 geometry.x=x_offset;
1726 geometry.y=y_offset;
1727 similarity_image=CropImage(image,&geometry,exception);
1728 if (similarity_image == (Image *) NULL)
1729 return(0.0);
cristy09136812011-10-18 15:24:30 +00001730 distortion=0.0;
1731 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
cristy3cc758f2010-11-27 01:33:49 +00001732 exception);
cristy713ff212010-11-26 21:56:11 +00001733 similarity_image=DestroyImage(similarity_image);
cristy09136812011-10-18 15:24:30 +00001734 if (status == MagickFalse)
1735 return(0.0);
cristy3cc758f2010-11-27 01:33:49 +00001736 return(distortion);
cristy3ed852e2009-09-05 21:47:34 +00001737}
1738
1739MagickExport Image *SimilarityImage(Image *image,const Image *reference,
cristy62e52182013-03-15 14:26:17 +00001740 const MetricType metric,const double similarity_threshold,
1741 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001742{
1743#define SimilarityImageTag "Similarity/Image"
1744
cristyc4c8d132010-01-07 01:58:38 +00001745 CacheView
1746 *similarity_view;
1747
cristy3ed852e2009-09-05 21:47:34 +00001748 Image
1749 *similarity_image;
1750
1751 MagickBooleanType
1752 status;
1753
cristybb503372010-05-27 20:51:26 +00001754 MagickOffsetType
1755 progress;
1756
1757 ssize_t
1758 y;
1759
cristy3ed852e2009-09-05 21:47:34 +00001760 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001761 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001762 if (image->debug != MagickFalse)
1763 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1764 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001765 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001766 assert(offset != (RectangleInfo *) NULL);
1767 SetGeometry(reference,offset);
cristyfe181a72014-02-02 21:17:43 +00001768 *similarity_metric=MagickMaximumValue;
cristy73626632014-07-19 20:52:21 +00001769 if (ValidateImageMorphology(image,reference) == MagickFalse)
1770 ThrowImageException(ImageError,"ImageMorphologyDiffers");
cristy3ed852e2009-09-05 21:47:34 +00001771 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1772 image->rows-reference->rows+1,MagickTrue,exception);
1773 if (similarity_image == (Image *) NULL)
1774 return((Image *) NULL);
cristyd5c15f92011-09-23 00:58:33 +00001775 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1776 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001777 {
cristy3ed852e2009-09-05 21:47:34 +00001778 similarity_image=DestroyImage(similarity_image);
1779 return((Image *) NULL);
1780 }
cristyb979cea2013-03-01 14:22:42 +00001781 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
1782 exception);
cristy3ed852e2009-09-05 21:47:34 +00001783 /*
1784 Measure similarity of reference image against image.
1785 */
1786 status=MagickTrue;
1787 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001788 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001789#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy70ea54f2013-03-15 18:49:14 +00001790 #pragma omp parallel for schedule(static,4) \
1791 shared(progress,status,similarity_metric) \
cristy5e6b2592012-12-19 14:08:11 +00001792 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001793#endif
cristybb503372010-05-27 20:51:26 +00001794 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
cristy3ed852e2009-09-05 21:47:34 +00001795 {
1796 double
1797 similarity;
1798
cristy4c08aed2011-07-01 19:47:50 +00001799 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001800 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001801
cristy49dd6a02011-09-24 23:08:01 +00001802 register ssize_t
1803 x;
1804
cristy3ed852e2009-09-05 21:47:34 +00001805 if (status == MagickFalse)
1806 continue;
cristy266f58b2013-05-15 11:47:01 +00001807#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy1aadacc2013-04-27 16:31:24 +00001808 #pragma omp flush(similarity_metric)
cristy266f58b2013-05-15 11:47:01 +00001809#endif
cristy24856ba2013-03-15 18:24:00 +00001810 if (*similarity_metric <= similarity_threshold)
1811 continue;
cristy3cc758f2010-11-27 01:33:49 +00001812 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1813 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001814 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001815 {
1816 status=MagickFalse;
1817 continue;
1818 }
cristybb503372010-05-27 20:51:26 +00001819 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
cristy3ed852e2009-09-05 21:47:34 +00001820 {
cristy49dd6a02011-09-24 23:08:01 +00001821 register ssize_t
1822 i;
1823
cristy266f58b2013-05-15 11:47:01 +00001824#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy1aadacc2013-04-27 16:31:24 +00001825 #pragma omp flush(similarity_metric)
cristy266f58b2013-05-15 11:47:01 +00001826#endif
cristy24856ba2013-03-15 18:24:00 +00001827 if (*similarity_metric <= similarity_threshold)
1828 break;
cristy09136812011-10-18 15:24:30 +00001829 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
cristyb5d5f722009-11-04 03:03:49 +00001830#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001831 #pragma omp critical (MagickCore_SimilarityImage)
cristy3ed852e2009-09-05 21:47:34 +00001832#endif
cristy12a73d32015-03-16 23:03:18 +00001833 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
1834 (metric == UndefinedErrorMetric))
1835 similarity=1.0-similarity;
cristy3ed852e2009-09-05 21:47:34 +00001836 if (similarity < *similarity_metric)
1837 {
cristy3ed852e2009-09-05 21:47:34 +00001838 offset->x=x;
1839 offset->y=y;
cristy24856ba2013-03-15 18:24:00 +00001840 *similarity_metric=similarity;
cristy3ed852e2009-09-05 21:47:34 +00001841 }
cristyefdcd372014-02-03 18:40:08 +00001842 if (metric == PerceptualHashErrorMetric)
1843 similarity=MagickMin(0.01*similarity,1.0);
cristy883fde12013-04-08 00:50:13 +00001844 if (GetPixelReadMask(similarity_image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00001845 {
cristyc3a58022013-10-09 23:22:42 +00001846 SetPixelBackgoundColor(similarity_image,q);
cristyc94ba6f2012-01-29 23:19:58 +00001847 q+=GetPixelChannels(similarity_image);
cristy10a6c612012-01-29 21:41:05 +00001848 continue;
1849 }
cristyc94ba6f2012-01-29 23:19:58 +00001850 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
cristy49dd6a02011-09-24 23:08:01 +00001851 {
cristy5a23c552013-02-13 14:34:28 +00001852 PixelChannel channel=GetPixelChannelChannel(image,i);
1853 PixelTrait traits=GetPixelChannelTraits(image,channel);
1854 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1855 channel);
cristy49dd6a02011-09-24 23:08:01 +00001856 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001857 (similarity_traits == UndefinedPixelTrait) ||
1858 ((similarity_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001859 continue;
cristy0beccfa2011-09-25 20:47:53 +00001860 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
cristyefdcd372014-02-03 18:40:08 +00001861 QuantumRange*similarity),q);
cristy49dd6a02011-09-24 23:08:01 +00001862 }
cristyed231572011-07-14 02:18:59 +00001863 q+=GetPixelChannels(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001864 }
cristyb979cea2013-03-01 14:22:42 +00001865 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001866 status=MagickFalse;
1867 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1868 {
cristyb979cea2013-03-01 14:22:42 +00001869 MagickBooleanType
1870 proceed;
1871
cristyb5d5f722009-11-04 03:03:49 +00001872#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001873 #pragma omp critical (MagickCore_SimilarityImage)
cristy3ed852e2009-09-05 21:47:34 +00001874#endif
cristyb979cea2013-03-01 14:22:42 +00001875 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1876 image->rows);
1877 if (proceed == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001878 status=MagickFalse;
1879 }
1880 }
1881 similarity_view=DestroyCacheView(similarity_view);
cristy1c2f48d2012-12-14 01:20:55 +00001882 if (status == MagickFalse)
1883 similarity_image=DestroyImage(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001884 return(similarity_image);
1885}