blob: 3f4697e9759c0296686ddfb018181eab8767d2a8 [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% %
Cristy7ce65e72015-12-12 18:03:16 -050020% Copyright 1999-2016 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 }
Cristy4ed18982015-12-12 09:50:30 -0500124 return(channels == 0 ? (size_t) 1 : channels);
cristy68094062014-08-22 12:59:44 +0000125}
126
cristy73626632014-07-19 20:52:21 +0000127static inline MagickBooleanType ValidateImageMorphology(
dirk05d2ff72015-11-18 23:13:43 +0100128 const Image *magick_restrict image,
129 const Image *magick_restrict reconstruct_image)
cristy73626632014-07-19 20:52:21 +0000130{
131 /*
132 Does the image match the reconstructed image morphology?
cristyc2e29bd2014-08-22 12:50:44 +0000133 */
cristy73626632014-07-19 20:52:21 +0000134 return(MagickTrue);
135}
136
cristy3ed852e2009-09-05 21:47:34 +0000137MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
138 const MetricType metric,double *distortion,ExceptionInfo *exception)
139{
cristyc4c8d132010-01-07 01:58:38 +0000140 CacheView
141 *highlight_view,
142 *image_view,
143 *reconstruct_view;
144
dirk8a7bbf42015-01-08 23:04:58 +0000145 double
146 fuzz;
147
cristy3ed852e2009-09-05 21:47:34 +0000148 const char
149 *artifact;
150
151 Image
152 *difference_image,
153 *highlight_image;
154
cristy3ed852e2009-09-05 21:47:34 +0000155 MagickBooleanType
156 status;
157
cristy4c08aed2011-07-01 19:47:50 +0000158 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000159 highlight,
cristy3fc482f2011-09-23 00:43:35 +0000160 lowlight;
cristy3ed852e2009-09-05 21:47:34 +0000161
cristyb730ca22015-03-31 15:50:52 +0000162 RectangleInfo
163 geometry;
164
cristye68f5992015-03-03 17:44:51 +0000165 size_t
166 columns,
167 rows;
168
cristy49dd6a02011-09-24 23:08:01 +0000169 ssize_t
170 y;
171
cristy3ed852e2009-09-05 21:47:34 +0000172 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000173 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000174 if (image->debug != MagickFalse)
175 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
176 assert(reconstruct_image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000177 assert(reconstruct_image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000178 assert(distortion != (double *) NULL);
179 *distortion=0.0;
180 if (image->debug != MagickFalse)
181 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8b520b42014-01-30 00:58:45 +0000182 if (metric != PerceptualHashErrorMetric)
cristy73626632014-07-19 20:52:21 +0000183 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
184 ThrowImageException(ImageError,"ImageMorphologyDiffers");
cristy8a9106f2011-07-05 14:39:26 +0000185 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
186 exception);
cristy3ed852e2009-09-05 21:47:34 +0000187 if (status == MagickFalse)
188 return((Image *) NULL);
cristye68f5992015-03-03 17:44:51 +0000189 columns=MagickMax(image->columns,reconstruct_image->columns);
cristyb730ca22015-03-31 15:50:52 +0000190 rows=MagickMax(image->rows,reconstruct_image->rows);
191 SetGeometry(image,&geometry);
192 geometry.width=columns;
193 geometry.height=rows;
194 difference_image=ExtentImage(image,&geometry,exception);
cristy3ed852e2009-09-05 21:47:34 +0000195 if (difference_image == (Image *) NULL)
196 return((Image *) NULL);
cristy63240882011-08-05 19:05:27 +0000197 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
cristye68f5992015-03-03 17:44:51 +0000198 highlight_image=CloneImage(image,columns,rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000199 if (highlight_image == (Image *) NULL)
200 {
201 difference_image=DestroyImage(difference_image);
202 return((Image *) NULL);
203 }
cristy3fc482f2011-09-23 00:43:35 +0000204 status=SetImageStorageClass(highlight_image,DirectClass,exception);
205 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000206 {
cristy3ed852e2009-09-05 21:47:34 +0000207 difference_image=DestroyImage(difference_image);
208 highlight_image=DestroyImage(highlight_image);
209 return((Image *) NULL);
210 }
cristy63240882011-08-05 19:05:27 +0000211 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
cristyca611542013-02-19 00:54:03 +0000212 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000213 artifact=GetImageArtifact(image,"highlight-color");
214 if (artifact != (const char *) NULL)
cristyf9d6dc02012-01-19 02:14:44 +0000215 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
cristyca611542013-02-19 00:54:03 +0000216 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000217 artifact=GetImageArtifact(image,"lowlight-color");
218 if (artifact != (const char *) NULL)
cristy0b1a7972011-10-22 22:17:02 +0000219 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000220 /*
221 Generate difference image.
222 */
223 status=MagickTrue;
cristya664f962015-01-09 00:25:16 +0000224 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
cristy46ff2672012-12-14 15:32:26 +0000225 image_view=AcquireVirtualCacheView(image,exception);
226 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
227 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000228#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000229 #pragma omp parallel for schedule(static,4) shared(status) \
cristye68f5992015-03-03 17:44:51 +0000230 magick_threads(image,highlight_image,rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000231#endif
cristye68f5992015-03-03 17:44:51 +0000232 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000233 {
234 MagickBooleanType
235 sync;
236
cristy4c08aed2011-07-01 19:47:50 +0000237 register const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100238 *magick_restrict p,
239 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000240
cristy4c08aed2011-07-01 19:47:50 +0000241 register Quantum
dirk05d2ff72015-11-18 23:13:43 +0100242 *magick_restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000243
cristy49dd6a02011-09-24 23:08:01 +0000244 register ssize_t
245 x;
246
cristy3ed852e2009-09-05 21:47:34 +0000247 if (status == MagickFalse)
248 continue;
cristye68f5992015-03-03 17:44:51 +0000249 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
250 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
251 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000252 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
253 (r == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000254 {
255 status=MagickFalse;
256 continue;
257 }
cristye68f5992015-03-03 17:44:51 +0000258 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000259 {
cristydb5c82c2013-02-22 00:41:33 +0000260 double
261 Da,
262 Sa;
263
cristy3ed852e2009-09-05 21:47:34 +0000264 MagickStatusType
265 difference;
266
cristy3fc482f2011-09-23 00:43:35 +0000267 register ssize_t
268 i;
269
cristy883fde12013-04-08 00:50:13 +0000270 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000271 {
cristy11a06d32015-01-04 12:03:27 +0000272 SetPixelViaPixelInfo(highlight_image,&lowlight,r);
cristy10a6c612012-01-29 21:41:05 +0000273 p+=GetPixelChannels(image);
274 q+=GetPixelChannels(reconstruct_image);
275 r+=GetPixelChannels(highlight_image);
276 continue;
277 }
cristyd09f8802012-02-04 16:44:10 +0000278 difference=MagickFalse;
cristydb5c82c2013-02-22 00:41:33 +0000279 Sa=QuantumScale*GetPixelAlpha(image,p);
280 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000281 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
282 {
cristya19f1d72012-08-07 18:24:38 +0000283 double
cristy0beccfa2011-09-25 20:47:53 +0000284 distance;
285
cristy5a23c552013-02-13 14:34:28 +0000286 PixelChannel channel=GetPixelChannelChannel(image,i);
287 PixelTrait traits=GetPixelChannelTraits(image,channel);
288 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
289 channel);
cristy3fc482f2011-09-23 00:43:35 +0000290 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000291 (reconstruct_traits == UndefinedPixelTrait) ||
292 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy3fc482f2011-09-23 00:43:35 +0000293 continue;
cristydb5c82c2013-02-22 00:41:33 +0000294 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
dirk8a7bbf42015-01-08 23:04:58 +0000295 if ((distance*distance) > fuzz)
296 {
297 difference=MagickTrue;
298 break;
299 }
cristy3fc482f2011-09-23 00:43:35 +0000300 }
301 if (difference == MagickFalse)
cristy11a06d32015-01-04 12:03:27 +0000302 SetPixelViaPixelInfo(highlight_image,&lowlight,r);
cristy3fc482f2011-09-23 00:43:35 +0000303 else
cristy11a06d32015-01-04 12:03:27 +0000304 SetPixelViaPixelInfo(highlight_image,&highlight,r);
cristyed231572011-07-14 02:18:59 +0000305 p+=GetPixelChannels(image);
306 q+=GetPixelChannels(reconstruct_image);
307 r+=GetPixelChannels(highlight_image);
cristy3ed852e2009-09-05 21:47:34 +0000308 }
309 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
310 if (sync == MagickFalse)
311 status=MagickFalse;
312 }
313 highlight_view=DestroyCacheView(highlight_view);
314 reconstruct_view=DestroyCacheView(reconstruct_view);
315 image_view=DestroyCacheView(image_view);
cristyfeb3e962012-03-29 17:25:55 +0000316 (void) CompositeImage(difference_image,highlight_image,image->compose,
cristy39172402012-03-30 13:04:39 +0000317 MagickTrue,0,0,exception);
cristy3ed852e2009-09-05 21:47:34 +0000318 highlight_image=DestroyImage(highlight_image);
319 if (status == MagickFalse)
320 difference_image=DestroyImage(difference_image);
321 return(difference_image);
322}
323
324/*
325%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
326% %
327% %
328% %
cristy8a9106f2011-07-05 14:39:26 +0000329% G e t I m a g e D i s t o r t i o n %
cristy3ed852e2009-09-05 21:47:34 +0000330% %
331% %
332% %
333%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
334%
cristyaeded782012-09-11 23:39:36 +0000335% GetImageDistortion() compares one or more pixel channels of an image to a
cristy8a9106f2011-07-05 14:39:26 +0000336% reconstructed image and returns the specified distortion metric.
cristy3ed852e2009-09-05 21:47:34 +0000337%
cristy44097f52012-12-16 19:56:20 +0000338% The format of the GetImageDistortion method is:
cristy3ed852e2009-09-05 21:47:34 +0000339%
cristy8a9106f2011-07-05 14:39:26 +0000340% MagickBooleanType GetImageDistortion(const Image *image,
341% const Image *reconstruct_image,const MetricType metric,
342% double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000343%
344% A description of each parameter follows:
345%
346% o image: the image.
347%
348% o reconstruct_image: the reconstruct image.
349%
cristy3ed852e2009-09-05 21:47:34 +0000350% o metric: the metric.
351%
352% o distortion: the computed distortion between the images.
353%
354% o exception: return any errors or warnings in this structure.
355%
356*/
357
cristy3cc758f2010-11-27 01:33:49 +0000358static MagickBooleanType GetAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000359 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000360{
cristyc4c8d132010-01-07 01:58:38 +0000361 CacheView
362 *image_view,
363 *reconstruct_view;
364
cristydb5c82c2013-02-22 00:41:33 +0000365 double
366 fuzz;
367
cristy3ed852e2009-09-05 21:47:34 +0000368 MagickBooleanType
369 status;
370
cristye68f5992015-03-03 17:44:51 +0000371 size_t
372 columns,
373 rows;
374
cristy9d314ff2011-03-09 01:30:28 +0000375 ssize_t
376 y;
377
cristy3ed852e2009-09-05 21:47:34 +0000378 /*
379 Compute the absolute difference in pixels between two images.
380 */
381 status=MagickTrue;
cristya664f962015-01-09 00:25:16 +0000382 fuzz=GetFuzzyColorDistance(image,reconstruct_image);
cristye68f5992015-03-03 17:44:51 +0000383 rows=MagickMax(image->rows,reconstruct_image->rows);
384 columns=MagickMax(image->columns,reconstruct_image->columns);
cristy46ff2672012-12-14 15:32:26 +0000385 image_view=AcquireVirtualCacheView(image,exception);
386 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000387#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000388 #pragma omp parallel for schedule(static,4) shared(status) \
cristye68f5992015-03-03 17:44:51 +0000389 magick_threads(image,image,rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000390#endif
cristye68f5992015-03-03 17:44:51 +0000391 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000392 {
393 double
cristy3fc482f2011-09-23 00:43:35 +0000394 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000395
cristy4c08aed2011-07-01 19:47:50 +0000396 register const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100397 *magick_restrict p,
398 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000399
cristybb503372010-05-27 20:51:26 +0000400 register ssize_t
dirkfa589d62015-09-20 16:59:31 +0200401 j,
cristy3ed852e2009-09-05 21:47:34 +0000402 x;
403
404 if (status == MagickFalse)
405 continue;
cristye68f5992015-03-03 17:44:51 +0000406 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
407 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000408 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000409 {
410 status=MagickFalse;
411 continue;
412 }
cristy3ed852e2009-09-05 21:47:34 +0000413 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristye68f5992015-03-03 17:44:51 +0000414 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000415 {
cristydb5c82c2013-02-22 00:41:33 +0000416 double
417 Da,
418 Sa;
419
cristy3fc482f2011-09-23 00:43:35 +0000420 MagickBooleanType
421 difference;
422
423 register ssize_t
424 i;
425
cristy883fde12013-04-08 00:50:13 +0000426 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000427 {
428 p+=GetPixelChannels(image);
429 q+=GetPixelChannels(reconstruct_image);
430 continue;
431 }
cristyd09f8802012-02-04 16:44:10 +0000432 difference=MagickFalse;
cristydb5c82c2013-02-22 00:41:33 +0000433 Sa=QuantumScale*GetPixelAlpha(image,p);
434 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000435 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
436 {
cristydb5c82c2013-02-22 00:41:33 +0000437 double
438 distance;
439
cristy5a23c552013-02-13 14:34:28 +0000440 PixelChannel channel=GetPixelChannelChannel(image,i);
441 PixelTrait traits=GetPixelChannelTraits(image,channel);
442 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
443 channel);
cristy3fc482f2011-09-23 00:43:35 +0000444 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000445 (reconstruct_traits == UndefinedPixelTrait) ||
446 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000447 continue;
cristydb5c82c2013-02-22 00:41:33 +0000448 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
449 if ((distance*distance) > fuzz)
450 {
dirk8a7bbf42015-01-08 23:04:58 +0000451 channel_distortion[i]++;
cristydb5c82c2013-02-22 00:41:33 +0000452 difference=MagickTrue;
cristydb5c82c2013-02-22 00:41:33 +0000453 }
cristy3fc482f2011-09-23 00:43:35 +0000454 }
455 if (difference != MagickFalse)
dirk8a7bbf42015-01-08 23:04:58 +0000456 channel_distortion[CompositePixelChannel]++;
cristyed231572011-07-14 02:18:59 +0000457 p+=GetPixelChannels(image);
458 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000459 }
cristyb5d5f722009-11-04 03:03:49 +0000460#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000461 #pragma omp critical (MagickCore_GetAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +0000462#endif
dirkfa589d62015-09-20 16:59:31 +0200463 for (j=0; j <= MaxPixelChannels; j++)
464 distortion[j]+=channel_distortion[j];
cristy3ed852e2009-09-05 21:47:34 +0000465 }
466 reconstruct_view=DestroyCacheView(reconstruct_view);
467 image_view=DestroyCacheView(image_view);
468 return(status);
469}
470
cristy343eee92010-12-11 02:17:57 +0000471static MagickBooleanType GetFuzzDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000472 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy343eee92010-12-11 02:17:57 +0000473{
474 CacheView
475 *image_view,
476 *reconstruct_view;
477
cristy343eee92010-12-11 02:17:57 +0000478 MagickBooleanType
479 status;
480
481 register ssize_t
dirkfa589d62015-09-20 16:59:31 +0200482 j;
cristy343eee92010-12-11 02:17:57 +0000483
cristye68f5992015-03-03 17:44:51 +0000484 size_t
485 columns,
486 rows;
487
cristy9d314ff2011-03-09 01:30:28 +0000488 ssize_t
489 y;
490
cristy343eee92010-12-11 02:17:57 +0000491 status=MagickTrue;
cristye68f5992015-03-03 17:44:51 +0000492 rows=MagickMax(image->rows,reconstruct_image->rows);
493 columns=MagickMax(image->columns,reconstruct_image->columns);
cristy46ff2672012-12-14 15:32:26 +0000494 image_view=AcquireVirtualCacheView(image,exception);
495 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristy343eee92010-12-11 02:17:57 +0000496#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000497 #pragma omp parallel for schedule(static,4) shared(status) \
cristye68f5992015-03-03 17:44:51 +0000498 magick_threads(image,image,rows,1)
cristy343eee92010-12-11 02:17:57 +0000499#endif
cristye68f5992015-03-03 17:44:51 +0000500 for (y=0; y < (ssize_t) rows; y++)
cristy343eee92010-12-11 02:17:57 +0000501 {
502 double
cristy3fc482f2011-09-23 00:43:35 +0000503 channel_distortion[MaxPixelChannels+1];
cristy343eee92010-12-11 02:17:57 +0000504
cristy4c08aed2011-07-01 19:47:50 +0000505 register const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100506 *magick_restrict p,
507 *magick_restrict q;
cristy343eee92010-12-11 02:17:57 +0000508
509 register ssize_t
cristy343eee92010-12-11 02:17:57 +0000510 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
dirkfa589d62015-09-20 16:59:31 +0200563 for (j=0; j <= MaxPixelChannels; j++)
564 distortion[j]+=channel_distortion[j];
cristy343eee92010-12-11 02:17:57 +0000565 }
566 reconstruct_view=DestroyCacheView(reconstruct_view);
567 image_view=DestroyCacheView(image_view);
dirkfa589d62015-09-20 16:59:31 +0200568 for (j=0; j <= MaxPixelChannels; j++)
569 distortion[j]/=((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
dirkfa589d62015-09-20 16:59:31 +0200586 j;
cristy3ed852e2009-09-05 21:47:34 +0000587
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
dirk05d2ff72015-11-18 23:13:43 +0100610 *magick_restrict p,
611 *magick_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 x;
615
616 if (status == MagickFalse)
617 continue;
cristye68f5992015-03-03 17:44:51 +0000618 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
619 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000620 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000621 {
622 status=MagickFalse;
623 continue;
624 }
cristy3ed852e2009-09-05 21:47:34 +0000625 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristye68f5992015-03-03 17:44:51 +0000626 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000627 {
cristydb5c82c2013-02-22 00:41:33 +0000628 double
629 Da,
630 Sa;
631
cristy3fc482f2011-09-23 00:43:35 +0000632 register ssize_t
633 i;
cristy3ed852e2009-09-05 21:47:34 +0000634
cristy883fde12013-04-08 00:50:13 +0000635 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000636 {
637 p+=GetPixelChannels(image);
638 q+=GetPixelChannels(reconstruct_image);
639 continue;
640 }
cristydb5c82c2013-02-22 00:41:33 +0000641 Sa=QuantumScale*GetPixelAlpha(image,p);
642 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000643 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
644 {
cristya19f1d72012-08-07 18:24:38 +0000645 double
cristy3fc482f2011-09-23 00:43:35 +0000646 distance;
647
cristy5a23c552013-02-13 14:34:28 +0000648 PixelChannel channel=GetPixelChannelChannel(image,i);
649 PixelTrait traits=GetPixelChannelTraits(image,channel);
650 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
651 channel);
cristy3fc482f2011-09-23 00:43:35 +0000652 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000653 (reconstruct_traits == UndefinedPixelTrait) ||
654 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000655 continue;
cristydb5c82c2013-02-22 00:41:33 +0000656 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
657 channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000658 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000659 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000660 }
cristyed231572011-07-14 02:18:59 +0000661 p+=GetPixelChannels(image);
662 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000663 }
cristyb5d5f722009-11-04 03:03:49 +0000664#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000665 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +0000666#endif
dirkfa589d62015-09-20 16:59:31 +0200667 for (j=0; j <= MaxPixelChannels; j++)
668 distortion[j]+=channel_distortion[j];
cristy3ed852e2009-09-05 21:47:34 +0000669 }
670 reconstruct_view=DestroyCacheView(reconstruct_view);
671 image_view=DestroyCacheView(image_view);
dirkfa589d62015-09-20 16:59:31 +0200672 for (j=0; j <= MaxPixelChannels; j++)
673 distortion[j]/=((double) columns*rows);
cristy5f95f4f2011-10-23 01:01:01 +0000674 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000675 return(status);
676}
677
678static MagickBooleanType GetMeanErrorPerPixel(Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000679 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000680{
cristyc4c8d132010-01-07 01:58:38 +0000681 CacheView
682 *image_view,
683 *reconstruct_view;
684
cristy3ed852e2009-09-05 21:47:34 +0000685 MagickBooleanType
686 status;
687
cristya19f1d72012-08-07 18:24:38 +0000688 double
cristy3ed852e2009-09-05 21:47:34 +0000689 area,
cristy3ed852e2009-09-05 21:47:34 +0000690 maximum_error,
691 mean_error;
692
cristye68f5992015-03-03 17:44:51 +0000693 size_t
694 columns,
695 rows;
696
cristy9d314ff2011-03-09 01:30:28 +0000697 ssize_t
698 y;
699
cristy3ed852e2009-09-05 21:47:34 +0000700 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000701 area=0.0;
702 maximum_error=0.0;
703 mean_error=0.0;
cristye68f5992015-03-03 17:44:51 +0000704 rows=MagickMax(image->rows,reconstruct_image->rows);
705 columns=MagickMax(image->columns,reconstruct_image->columns);
cristy46ff2672012-12-14 15:32:26 +0000706 image_view=AcquireVirtualCacheView(image,exception);
707 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristye68f5992015-03-03 17:44:51 +0000708 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000709 {
cristy4c08aed2011-07-01 19:47:50 +0000710 register const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100711 *magick_restrict p,
712 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000713
cristybb503372010-05-27 20:51:26 +0000714 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000715 x;
716
cristye68f5992015-03-03 17:44:51 +0000717 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
718 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000719 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000720 {
721 status=MagickFalse;
722 break;
723 }
cristye68f5992015-03-03 17:44:51 +0000724 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000725 {
cristydb5c82c2013-02-22 00:41:33 +0000726 double
727 Da,
728 Sa;
729
cristy3fc482f2011-09-23 00:43:35 +0000730 register ssize_t
731 i;
cristy3ed852e2009-09-05 21:47:34 +0000732
cristy883fde12013-04-08 00:50:13 +0000733 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000734 {
735 p+=GetPixelChannels(image);
736 q+=GetPixelChannels(reconstruct_image);
737 continue;
738 }
cristydb5c82c2013-02-22 00:41:33 +0000739 Sa=QuantumScale*GetPixelAlpha(image,p);
740 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000741 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
742 {
cristya19f1d72012-08-07 18:24:38 +0000743 double
cristy3fc482f2011-09-23 00:43:35 +0000744 distance;
745
cristy5a23c552013-02-13 14:34:28 +0000746 PixelChannel channel=GetPixelChannelChannel(image,i);
747 PixelTrait traits=GetPixelChannelTraits(image,channel);
748 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
749 channel);
cristy3fc482f2011-09-23 00:43:35 +0000750 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000751 (reconstruct_traits == UndefinedPixelTrait) ||
752 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000753 continue;
cristy0c935612014-08-21 16:44:46 +0000754 distance=fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000755 distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000756 distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000757 mean_error+=distance*distance;
758 if (distance > maximum_error)
759 maximum_error=distance;
760 area++;
761 }
cristyed231572011-07-14 02:18:59 +0000762 p+=GetPixelChannels(image);
763 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000764 }
765 }
766 reconstruct_view=DestroyCacheView(reconstruct_view);
767 image_view=DestroyCacheView(image_view);
cristy5f95f4f2011-10-23 01:01:01 +0000768 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
cristy3ed852e2009-09-05 21:47:34 +0000769 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
770 image->error.normalized_maximum_error=QuantumScale*maximum_error;
771 return(status);
772}
773
cristy3cc758f2010-11-27 01:33:49 +0000774static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000775 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000776{
cristyc4c8d132010-01-07 01:58:38 +0000777 CacheView
778 *image_view,
779 *reconstruct_view;
780
cristy3ed852e2009-09-05 21:47:34 +0000781 MagickBooleanType
782 status;
783
cristybb503372010-05-27 20:51:26 +0000784 register ssize_t
dirkfa589d62015-09-20 16:59:31 +0200785 j;
cristy3ed852e2009-09-05 21:47:34 +0000786
cristye68f5992015-03-03 17:44:51 +0000787 size_t
788 columns,
789 rows;
790
cristy9d314ff2011-03-09 01:30:28 +0000791 ssize_t
792 y;
793
cristy3ed852e2009-09-05 21:47:34 +0000794 status=MagickTrue;
cristye68f5992015-03-03 17:44:51 +0000795 rows=MagickMax(image->rows,reconstruct_image->rows);
796 columns=MagickMax(image->columns,reconstruct_image->columns);
cristy46ff2672012-12-14 15:32:26 +0000797 image_view=AcquireVirtualCacheView(image,exception);
798 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000799#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000800 #pragma omp parallel for schedule(static,4) shared(status) \
cristye68f5992015-03-03 17:44:51 +0000801 magick_threads(image,image,rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000802#endif
cristye68f5992015-03-03 17:44:51 +0000803 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000804 {
805 double
cristy3fc482f2011-09-23 00:43:35 +0000806 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000807
cristy4c08aed2011-07-01 19:47:50 +0000808 register const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100809 *magick_restrict p,
810 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000811
cristybb503372010-05-27 20:51:26 +0000812 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000813 x;
814
815 if (status == MagickFalse)
816 continue;
cristye68f5992015-03-03 17:44:51 +0000817 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
818 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000819 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000820 {
821 status=MagickFalse;
822 continue;
823 }
cristy3ed852e2009-09-05 21:47:34 +0000824 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristye68f5992015-03-03 17:44:51 +0000825 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000826 {
cristydb5c82c2013-02-22 00:41:33 +0000827 double
828 Da,
829 Sa;
830
cristy3fc482f2011-09-23 00:43:35 +0000831 register ssize_t
832 i;
cristy3ed852e2009-09-05 21:47:34 +0000833
cristy883fde12013-04-08 00:50:13 +0000834 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000835 {
836 p+=GetPixelChannels(image);
837 q+=GetPixelChannels(reconstruct_image);
838 continue;
839 }
cristydb5c82c2013-02-22 00:41:33 +0000840 Sa=QuantumScale*GetPixelAlpha(image,p);
841 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000842 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
843 {
cristya19f1d72012-08-07 18:24:38 +0000844 double
cristy3fc482f2011-09-23 00:43:35 +0000845 distance;
846
cristy5a23c552013-02-13 14:34:28 +0000847 PixelChannel channel=GetPixelChannelChannel(image,i);
848 PixelTrait traits=GetPixelChannelTraits(image,channel);
849 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
850 channel);
cristy3fc482f2011-09-23 00:43:35 +0000851 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000852 (reconstruct_traits == UndefinedPixelTrait) ||
853 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000854 continue;
cristydb5c82c2013-02-22 00:41:33 +0000855 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
856 channel,q));
857 channel_distortion[i]+=distance*distance;
858 channel_distortion[CompositePixelChannel]+=distance*distance;
cristy3fc482f2011-09-23 00:43:35 +0000859 }
cristyed231572011-07-14 02:18:59 +0000860 p+=GetPixelChannels(image);
861 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000862 }
cristyb5d5f722009-11-04 03:03:49 +0000863#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000864 #pragma omp critical (MagickCore_GetMeanSquaredError)
cristy3ed852e2009-09-05 21:47:34 +0000865#endif
dirkfa589d62015-09-20 16:59:31 +0200866 for (j=0; j <= MaxPixelChannels; j++)
867 distortion[j]+=channel_distortion[j];
cristy3ed852e2009-09-05 21:47:34 +0000868 }
869 reconstruct_view=DestroyCacheView(reconstruct_view);
870 image_view=DestroyCacheView(image_view);
dirkfa589d62015-09-20 16:59:31 +0200871 for (j=0; j <= MaxPixelChannels; j++)
872 distortion[j]/=((double) columns*rows);
cristy5f95f4f2011-10-23 01:01:01 +0000873 distortion[CompositePixelChannel]/=GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000874 return(status);
875}
876
cristy3cc758f2010-11-27 01:33:49 +0000877static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
cristy8a9106f2011-07-05 14:39:26 +0000878 const Image *image,const Image *reconstruct_image,double *distortion,
879 ExceptionInfo *exception)
cristy4c929a72010-11-24 18:54:42 +0000880{
cristy9f48ca62010-11-25 03:06:31 +0000881#define SimilarityImageTag "Similarity/Image"
882
cristy4c929a72010-11-24 18:54:42 +0000883 CacheView
884 *image_view,
885 *reconstruct_view;
886
cristy9f48ca62010-11-25 03:06:31 +0000887 ChannelStatistics
cristy9f48ca62010-11-25 03:06:31 +0000888 *image_statistics,
889 *reconstruct_statistics;
890
cristydb5c82c2013-02-22 00:41:33 +0000891 double
892 area;
893
cristy4c929a72010-11-24 18:54:42 +0000894 MagickBooleanType
895 status;
896
cristy18a41362010-11-27 15:56:18 +0000897 MagickOffsetType
898 progress;
899
cristy4c929a72010-11-24 18:54:42 +0000900 register ssize_t
901 i;
902
cristye68f5992015-03-03 17:44:51 +0000903 size_t
904 columns,
905 rows;
906
cristy3cc758f2010-11-27 01:33:49 +0000907 ssize_t
908 y;
909
cristy34d6fdc2010-11-26 19:06:08 +0000910 /*
cristy18a41362010-11-27 15:56:18 +0000911 Normalize to account for variation due to lighting and exposure condition.
cristy34d6fdc2010-11-26 19:06:08 +0000912 */
cristyd42d9952011-07-08 14:21:50 +0000913 image_statistics=GetImageStatistics(image,exception);
914 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
cristye287bba2013-09-02 20:04:42 +0000915 if ((image_statistics == (ChannelStatistics *) NULL) ||
916 (reconstruct_statistics == (ChannelStatistics *) NULL))
917 {
918 if (image_statistics != (ChannelStatistics *) NULL)
919 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
920 image_statistics);
921 if (reconstruct_statistics != (ChannelStatistics *) NULL)
922 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
923 reconstruct_statistics);
924 return(MagickFalse);
925 }
cristy4c929a72010-11-24 18:54:42 +0000926 status=MagickTrue;
cristy9f48ca62010-11-25 03:06:31 +0000927 progress=0;
cristy3fc482f2011-09-23 00:43:35 +0000928 for (i=0; i <= MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000929 distortion[i]=0.0;
cristye68f5992015-03-03 17:44:51 +0000930 rows=MagickMax(image->rows,reconstruct_image->rows);
931 columns=MagickMax(image->columns,reconstruct_image->columns);
dirk4285cbc2015-08-23 13:45:22 +0200932 area=1.0/((double) columns*rows-1);
cristy46ff2672012-12-14 15:32:26 +0000933 image_view=AcquireVirtualCacheView(image,exception);
934 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristye68f5992015-03-03 17:44:51 +0000935 for (y=0; y < (ssize_t) rows; y++)
cristy4c929a72010-11-24 18:54:42 +0000936 {
cristy4c08aed2011-07-01 19:47:50 +0000937 register const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100938 *magick_restrict p,
939 *magick_restrict q;
cristy4c929a72010-11-24 18:54:42 +0000940
941 register ssize_t
cristy4c929a72010-11-24 18:54:42 +0000942 x;
943
cristye68f5992015-03-03 17:44:51 +0000944 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
945 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000946 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy4c929a72010-11-24 18:54:42 +0000947 {
948 status=MagickFalse;
dirk4285cbc2015-08-23 13:45:22 +0200949 break;
cristy4c929a72010-11-24 18:54:42 +0000950 }
cristye68f5992015-03-03 17:44:51 +0000951 for (x=0; x < (ssize_t) columns; x++)
cristy4c929a72010-11-24 18:54:42 +0000952 {
cristydb5c82c2013-02-22 00:41:33 +0000953 double
954 Da,
955 Sa;
956
cristy883fde12013-04-08 00:50:13 +0000957 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000958 {
959 p+=GetPixelChannels(image);
960 q+=GetPixelChannels(reconstruct_image);
961 continue;
962 }
dirk4285cbc2015-08-23 13:45:22 +0200963 Sa=QuantumScale*(image->alpha_trait != UndefinedPixelTrait ?
964 GetPixelAlpha(image,p) : OpaqueAlpha);
965 Da=QuantumScale*(reconstruct_image->alpha_trait != UndefinedPixelTrait ?
dirk6530db82015-08-25 15:12:12 +0200966 GetPixelAlpha(reconstruct_image,q) : OpaqueAlpha);
cristy3fc482f2011-09-23 00:43:35 +0000967 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
968 {
cristy5a23c552013-02-13 14:34:28 +0000969 PixelChannel channel=GetPixelChannelChannel(image,i);
970 PixelTrait traits=GetPixelChannelTraits(image,channel);
971 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
972 channel);
cristy3fc482f2011-09-23 00:43:35 +0000973 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000974 (reconstruct_traits == UndefinedPixelTrait) ||
975 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000976 continue;
dirk4285cbc2015-08-23 13:45:22 +0200977 if (channel == AlphaPixelChannel)
978 {
979 distortion[i]+=area*QuantumScale*(p[i]-
980 image_statistics[channel].mean)*(GetPixelChannel(
981 reconstruct_image,channel,q)-
982 reconstruct_statistics[channel].mean);
983 }
984 else
985 {
986 distortion[i]+=area*QuantumScale*(Sa*p[i]-
987 image_statistics[channel].mean)*(Da*GetPixelChannel(
988 reconstruct_image,channel,q)-
989 reconstruct_statistics[channel].mean);
990 }
cristy3fc482f2011-09-23 00:43:35 +0000991 }
cristyed231572011-07-14 02:18:59 +0000992 p+=GetPixelChannels(image);
cristy10a6c612012-01-29 21:41:05 +0000993 q+=GetPixelChannels(reconstruct_image);
cristy4c929a72010-11-24 18:54:42 +0000994 }
cristy9f48ca62010-11-25 03:06:31 +0000995 if (image->progress_monitor != (MagickProgressMonitor) NULL)
996 {
997 MagickBooleanType
998 proceed;
999
cristy8967d582015-03-03 22:59:12 +00001000 proceed=SetImageProgress(image,SimilarityImageTag,progress++,rows);
cristy9f48ca62010-11-25 03:06:31 +00001001 if (proceed == MagickFalse)
dirk4285cbc2015-08-23 13:45:22 +02001002 {
1003 status=MagickFalse;
1004 break;
1005 }
cristy9f48ca62010-11-25 03:06:31 +00001006 }
cristy4c929a72010-11-24 18:54:42 +00001007 }
1008 reconstruct_view=DestroyCacheView(reconstruct_view);
1009 image_view=DestroyCacheView(image_view);
cristy34d6fdc2010-11-26 19:06:08 +00001010 /*
1011 Divide by the standard deviation.
1012 */
cristy5f95f4f2011-10-23 01:01:01 +00001013 distortion[CompositePixelChannel]=0.0;
dirk4285cbc2015-08-23 13:45:22 +02001014 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy34d6fdc2010-11-26 19:06:08 +00001015 {
cristya19f1d72012-08-07 18:24:38 +00001016 double
cristy18a41362010-11-27 15:56:18 +00001017 gamma;
cristy34d6fdc2010-11-26 19:06:08 +00001018
cristy5a23c552013-02-13 14:34:28 +00001019 PixelChannel channel=GetPixelChannelChannel(image,i);
dirk4285cbc2015-08-23 13:45:22 +02001020 gamma=image_statistics[channel].standard_deviation*
cristy3fc482f2011-09-23 00:43:35 +00001021 reconstruct_statistics[channel].standard_deviation;
cristy3e3ec3a2012-11-03 23:11:06 +00001022 gamma=PerceptibleReciprocal(gamma);
cristy18a41362010-11-27 15:56:18 +00001023 distortion[i]=QuantumRange*gamma*distortion[i];
cristy5f95f4f2011-10-23 01:01:01 +00001024 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
cristy34d6fdc2010-11-26 19:06:08 +00001025 }
cristy5f95f4f2011-10-23 01:01:01 +00001026 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
cristy49dd6a02011-09-24 23:08:01 +00001027 GetImageChannels(image));
cristy34d6fdc2010-11-26 19:06:08 +00001028 /*
1029 Free resources.
1030 */
cristy9f48ca62010-11-25 03:06:31 +00001031 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1032 reconstruct_statistics);
1033 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1034 image_statistics);
cristy4c929a72010-11-24 18:54:42 +00001035 return(status);
1036}
1037
cristy3cc758f2010-11-27 01:33:49 +00001038static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001039 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001040{
cristyc4c8d132010-01-07 01:58:38 +00001041 CacheView
1042 *image_view,
1043 *reconstruct_view;
1044
cristy3ed852e2009-09-05 21:47:34 +00001045 MagickBooleanType
1046 status;
1047
cristye68f5992015-03-03 17:44:51 +00001048 size_t
1049 columns,
1050 rows;
1051
cristy9d314ff2011-03-09 01:30:28 +00001052 ssize_t
1053 y;
1054
cristy3ed852e2009-09-05 21:47:34 +00001055 status=MagickTrue;
cristye68f5992015-03-03 17:44:51 +00001056 rows=MagickMax(image->rows,reconstruct_image->rows);
1057 columns=MagickMax(image->columns,reconstruct_image->columns);
cristy46ff2672012-12-14 15:32:26 +00001058 image_view=AcquireVirtualCacheView(image,exception);
1059 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001060#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001061 #pragma omp parallel for schedule(static,4) shared(status) \
cristye68f5992015-03-03 17:44:51 +00001062 magick_threads(image,image,rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001063#endif
cristye68f5992015-03-03 17:44:51 +00001064 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001065 {
1066 double
cristy3fc482f2011-09-23 00:43:35 +00001067 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +00001068
cristy4c08aed2011-07-01 19:47:50 +00001069 register const Quantum
dirk05d2ff72015-11-18 23:13:43 +01001070 *magick_restrict p,
1071 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001072
cristybb503372010-05-27 20:51:26 +00001073 register ssize_t
dirkfa589d62015-09-20 16:59:31 +02001074 j,
cristy3ed852e2009-09-05 21:47:34 +00001075 x;
1076
1077 if (status == MagickFalse)
1078 continue;
cristye68f5992015-03-03 17:44:51 +00001079 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1080 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +00001081 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001082 {
1083 status=MagickFalse;
1084 continue;
1085 }
cristy3ed852e2009-09-05 21:47:34 +00001086 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristye68f5992015-03-03 17:44:51 +00001087 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001088 {
cristydb5c82c2013-02-22 00:41:33 +00001089 double
1090 Da,
1091 Sa;
1092
cristy3fc482f2011-09-23 00:43:35 +00001093 register ssize_t
1094 i;
cristy3ed852e2009-09-05 21:47:34 +00001095
cristy883fde12013-04-08 00:50:13 +00001096 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +00001097 {
1098 p+=GetPixelChannels(image);
1099 q+=GetPixelChannels(reconstruct_image);
1100 continue;
1101 }
cristydb5c82c2013-02-22 00:41:33 +00001102 Sa=QuantumScale*GetPixelAlpha(image,p);
1103 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +00001104 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1105 {
cristya19f1d72012-08-07 18:24:38 +00001106 double
cristy3fc482f2011-09-23 00:43:35 +00001107 distance;
1108
cristy5a23c552013-02-13 14:34:28 +00001109 PixelChannel channel=GetPixelChannelChannel(image,i);
1110 PixelTrait traits=GetPixelChannelTraits(image,channel);
1111 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1112 channel);
cristy3fc482f2011-09-23 00:43:35 +00001113 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001114 (reconstruct_traits == UndefinedPixelTrait) ||
1115 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001116 continue;
cristydb5c82c2013-02-22 00:41:33 +00001117 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
1118 channel,q));
cristy25a5f2f2011-09-24 14:09:43 +00001119 if (distance > channel_distortion[i])
cristy3fc482f2011-09-23 00:43:35 +00001120 channel_distortion[i]=distance;
cristy5f95f4f2011-10-23 01:01:01 +00001121 if (distance > channel_distortion[CompositePixelChannel])
1122 channel_distortion[CompositePixelChannel]=distance;
cristy3fc482f2011-09-23 00:43:35 +00001123 }
cristyed231572011-07-14 02:18:59 +00001124 p+=GetPixelChannels(image);
cristy10a6c612012-01-29 21:41:05 +00001125 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001126 }
cristyb5d5f722009-11-04 03:03:49 +00001127#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001128 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +00001129#endif
dirkfa589d62015-09-20 16:59:31 +02001130 for (j=0; j <= MaxPixelChannels; j++)
1131 if (channel_distortion[j] > distortion[j])
1132 distortion[j]=channel_distortion[j];
cristy3ed852e2009-09-05 21:47:34 +00001133 }
1134 reconstruct_view=DestroyCacheView(reconstruct_view);
1135 image_view=DestroyCacheView(image_view);
1136 return(status);
1137}
1138
cristy0633a1f2014-02-01 21:54:58 +00001139static inline double MagickLog10(const double x)
cristyd94b0b32014-01-30 23:19:20 +00001140{
cristy1a895c32014-02-05 18:01:41 +00001141#define Log10Epsilon (1.0e-11)
cristycb9af4a2014-02-05 13:31:55 +00001142
1143 if (fabs(x) < Log10Epsilon)
1144 return(log10(Log10Epsilon));
cristya6f32bf2014-02-02 19:08:58 +00001145 return(log10(fabs(x)));
cristyd94b0b32014-01-30 23:19:20 +00001146}
1147
cristy3ed852e2009-09-05 21:47:34 +00001148static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001149 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001150{
1151 MagickBooleanType
1152 status;
1153
cristy3fc482f2011-09-23 00:43:35 +00001154 register ssize_t
1155 i;
1156
cristy8a9106f2011-07-05 14:39:26 +00001157 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001158 for (i=0; i <= MaxPixelChannels; i++)
cristy0633a1f2014-02-01 21:54:58 +00001159 distortion[i]=20.0*MagickLog10((double) 1.0/sqrt(distortion[i]));
cristy3ed852e2009-09-05 21:47:34 +00001160 return(status);
1161}
1162
cristy03d6f862014-01-08 18:34:48 +00001163static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
1164 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1165{
cristyf8f39d42014-02-22 21:44:38 +00001166 ChannelPerceptualHash
1167 *image_phash,
1168 *reconstruct_phash;
cristyd9244cd2014-01-30 23:09:31 +00001169
cristya4a66012014-02-08 19:39:34 +00001170 ssize_t
1171 channel;
cristybc0adce2014-01-08 23:15:49 +00001172
cristy21b687b2014-01-10 00:17:34 +00001173 /*
cristyf8f39d42014-02-22 21:44:38 +00001174 Compute perceptual hash in the sRGB colorspace.
cristy21b687b2014-01-10 00:17:34 +00001175 */
cristyb3538ec2014-02-22 23:13:34 +00001176 image_phash=GetImagePerceptualHash(image,exception);
cristyf8f39d42014-02-22 21:44:38 +00001177 if (image_phash == (ChannelPerceptualHash *) NULL)
cristybc0adce2014-01-08 23:15:49 +00001178 return(MagickFalse);
cristyb3538ec2014-02-22 23:13:34 +00001179 reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
cristy4d0ca342014-05-01 00:42:09 +00001180 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
cristy2661ac12014-01-10 14:21:07 +00001181 {
cristyf8f39d42014-02-22 21:44:38 +00001182 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
cristy2661ac12014-01-10 14:21:07 +00001183 return(MagickFalse);
1184 }
cristya4a66012014-02-08 19:39:34 +00001185#if defined(MAGICKCORE_OPENMP_SUPPORT)
1186 #pragma omp parallel for schedule(static,4)
1187#endif
1188 for (channel=0; channel < MaxPixelChannels; channel++)
cristybc0adce2014-01-08 23:15:49 +00001189 {
cristya4a66012014-02-08 19:39:34 +00001190 double
1191 difference;
cristy6bd008f2014-01-30 23:24:06 +00001192
cristya4a66012014-02-08 19:39:34 +00001193 register ssize_t
1194 i;
1195
1196 difference=0.0;
cristy3ac6aa92014-09-08 11:48:28 +00001197 for (i=0; i < MaximumNumberOfImageMoments; i++)
cristybc0adce2014-01-08 23:15:49 +00001198 {
cristya4a66012014-02-08 19:39:34 +00001199 double
1200 alpha,
1201 beta;
1202
cristyc0187622014-09-06 00:45:58 +00001203 alpha=image_phash[channel].srgb_hu_phash[i];
1204 beta=reconstruct_phash[channel].srgb_hu_phash[i];
cristya4a66012014-02-08 19:39:34 +00001205 difference+=(beta-alpha)*(beta-alpha);
cristybc0adce2014-01-08 23:15:49 +00001206 }
cristya4a66012014-02-08 19:39:34 +00001207 distortion[channel]+=difference;
1208#if defined(MAGICKCORE_OPENMP_SUPPORT)
1209 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1210#endif
1211 distortion[CompositePixelChannel]+=difference;
cristybc0adce2014-01-08 23:15:49 +00001212 }
cristy21b687b2014-01-10 00:17:34 +00001213 /*
1214 Compute perceptual hash in the HCLP colorspace.
1215 */
cristya4a66012014-02-08 19:39:34 +00001216#if defined(MAGICKCORE_OPENMP_SUPPORT)
1217 #pragma omp parallel for schedule(static,4)
1218#endif
1219 for (channel=0; channel < MaxPixelChannels; channel++)
cristy21b687b2014-01-10 00:17:34 +00001220 {
cristya4a66012014-02-08 19:39:34 +00001221 double
1222 difference;
cristy6bd008f2014-01-30 23:24:06 +00001223
cristya4a66012014-02-08 19:39:34 +00001224 register ssize_t
1225 i;
1226
1227 difference=0.0;
cristy3ac6aa92014-09-08 11:48:28 +00001228 for (i=0; i < MaximumNumberOfImageMoments; i++)
cristy21b687b2014-01-10 00:17:34 +00001229 {
cristya4a66012014-02-08 19:39:34 +00001230 double
1231 alpha,
1232 beta;
1233
cristyc0187622014-09-06 00:45:58 +00001234 alpha=image_phash[channel].hclp_hu_phash[i];
1235 beta=reconstruct_phash[channel].hclp_hu_phash[i];
cristya4a66012014-02-08 19:39:34 +00001236 difference+=(beta-alpha)*(beta-alpha);
cristy21b687b2014-01-10 00:17:34 +00001237 }
cristya4a66012014-02-08 19:39:34 +00001238 distortion[channel]+=difference;
1239#if defined(MAGICKCORE_OPENMP_SUPPORT)
1240 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1241#endif
1242 distortion[CompositePixelChannel]+=difference;
cristy21b687b2014-01-10 00:17:34 +00001243 }
cristyf8f39d42014-02-22 21:44:38 +00001244 /*
1245 Free resources.
1246 */
1247 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1248 reconstruct_phash);
1249 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
cristy03d6f862014-01-08 18:34:48 +00001250 return(MagickTrue);
1251}
1252
cristy3cc758f2010-11-27 01:33:49 +00001253static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001254 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001255{
1256 MagickBooleanType
1257 status;
1258
cristy3fc482f2011-09-23 00:43:35 +00001259 register ssize_t
1260 i;
1261
cristy8a9106f2011-07-05 14:39:26 +00001262 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001263 for (i=0; i <= MaxPixelChannels; i++)
1264 distortion[i]=sqrt(distortion[i]);
cristy3ed852e2009-09-05 21:47:34 +00001265 return(status);
1266}
1267
cristy8a9106f2011-07-05 14:39:26 +00001268MagickExport MagickBooleanType GetImageDistortion(Image *image,
1269 const Image *reconstruct_image,const MetricType metric,double *distortion,
1270 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001271{
1272 double
1273 *channel_distortion;
1274
1275 MagickBooleanType
1276 status;
1277
1278 size_t
1279 length;
1280
1281 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001282 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001283 if (image->debug != MagickFalse)
1284 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1285 assert(reconstruct_image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001286 assert(reconstruct_image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001287 assert(distortion != (double *) NULL);
1288 *distortion=0.0;
1289 if (image->debug != MagickFalse)
1290 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8b520b42014-01-30 00:58:45 +00001291 if (metric != PerceptualHashErrorMetric)
cristy73626632014-07-19 20:52:21 +00001292 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1293 ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001294 /*
1295 Get image distortion.
1296 */
cristy3fc482f2011-09-23 00:43:35 +00001297 length=MaxPixelChannels+1;
cristy3ed852e2009-09-05 21:47:34 +00001298 channel_distortion=(double *) AcquireQuantumMemory(length,
1299 sizeof(*channel_distortion));
1300 if (channel_distortion == (double *) NULL)
1301 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1302 (void) ResetMagickMemory(channel_distortion,0,length*
1303 sizeof(*channel_distortion));
1304 switch (metric)
1305 {
1306 case AbsoluteErrorMetric:
1307 {
cristy8a9106f2011-07-05 14:39:26 +00001308 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1309 exception);
cristy3ed852e2009-09-05 21:47:34 +00001310 break;
1311 }
cristy343eee92010-12-11 02:17:57 +00001312 case FuzzErrorMetric:
1313 {
cristy8a9106f2011-07-05 14:39:26 +00001314 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1315 exception);
cristy343eee92010-12-11 02:17:57 +00001316 break;
1317 }
cristy3ed852e2009-09-05 21:47:34 +00001318 case MeanAbsoluteErrorMetric:
1319 {
cristy8a9106f2011-07-05 14:39:26 +00001320 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001321 channel_distortion,exception);
1322 break;
1323 }
cristy03fa69f2014-01-08 18:36:49 +00001324 case MeanErrorPerPixelErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001325 {
cristy8a9106f2011-07-05 14:39:26 +00001326 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1327 exception);
cristy3ed852e2009-09-05 21:47:34 +00001328 break;
1329 }
1330 case MeanSquaredErrorMetric:
1331 {
cristy8a9106f2011-07-05 14:39:26 +00001332 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001333 channel_distortion,exception);
1334 break;
1335 }
cristy4c929a72010-11-24 18:54:42 +00001336 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001337 default:
cristy4c929a72010-11-24 18:54:42 +00001338 {
cristy3cc758f2010-11-27 01:33:49 +00001339 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001340 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001341 break;
1342 }
cristy3ed852e2009-09-05 21:47:34 +00001343 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001344 {
cristy8a9106f2011-07-05 14:39:26 +00001345 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001346 channel_distortion,exception);
1347 break;
1348 }
cristy03d6f862014-01-08 18:34:48 +00001349 case PeakSignalToNoiseRatioErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001350 {
cristy8a9106f2011-07-05 14:39:26 +00001351 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001352 channel_distortion,exception);
1353 break;
1354 }
cristy03d6f862014-01-08 18:34:48 +00001355 case PerceptualHashErrorMetric:
1356 {
1357 status=GetPerceptualHashDistortion(image,reconstruct_image,
1358 channel_distortion,exception);
1359 break;
1360 }
cristy3ed852e2009-09-05 21:47:34 +00001361 case RootMeanSquaredErrorMetric:
1362 {
cristy8a9106f2011-07-05 14:39:26 +00001363 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001364 channel_distortion,exception);
1365 break;
1366 }
1367 }
cristy5f95f4f2011-10-23 01:01:01 +00001368 *distortion=channel_distortion[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001369 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
cristy65aa9342013-09-05 12:04:27 +00001370 (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
cristye863c052013-09-04 11:31:30 +00001371 *distortion);
cristy3ed852e2009-09-05 21:47:34 +00001372 return(status);
1373}
1374
1375/*
1376%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1377% %
1378% %
1379% %
cristy8a9106f2011-07-05 14:39:26 +00001380% 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 +00001381% %
1382% %
1383% %
1384%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1385%
cristy44097f52012-12-16 19:56:20 +00001386% GetImageDistortions() compares the pixel channels of an image to a
cristy3ed852e2009-09-05 21:47:34 +00001387% reconstructed image and returns the specified distortion metric for each
1388% channel.
1389%
cristy44097f52012-12-16 19:56:20 +00001390% The format of the GetImageDistortions method is:
cristy3ed852e2009-09-05 21:47:34 +00001391%
cristy8a9106f2011-07-05 14:39:26 +00001392% double *GetImageDistortions(const Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001393% const Image *reconstruct_image,const MetricType metric,
1394% ExceptionInfo *exception)
1395%
1396% A description of each parameter follows:
1397%
1398% o image: the image.
1399%
1400% o reconstruct_image: the reconstruct image.
1401%
1402% o metric: the metric.
1403%
1404% o exception: return any errors or warnings in this structure.
1405%
1406*/
cristy8a9106f2011-07-05 14:39:26 +00001407MagickExport double *GetImageDistortions(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001408 const Image *reconstruct_image,const MetricType metric,
1409 ExceptionInfo *exception)
1410{
1411 double
1412 *channel_distortion;
1413
1414 MagickBooleanType
1415 status;
1416
1417 size_t
1418 length;
1419
1420 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001421 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001422 if (image->debug != MagickFalse)
1423 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1424 assert(reconstruct_image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001425 assert(reconstruct_image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001426 if (image->debug != MagickFalse)
1427 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8b520b42014-01-30 00:58:45 +00001428 if (metric != PerceptualHashErrorMetric)
cristy73626632014-07-19 20:52:21 +00001429 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
cristy8b520b42014-01-30 00:58:45 +00001430 {
1431 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
cristy73626632014-07-19 20:52:21 +00001432 "ImageMorphologyDiffers","`%s'",image->filename);
cristy8b520b42014-01-30 00:58:45 +00001433 return((double *) NULL);
1434 }
cristy3ed852e2009-09-05 21:47:34 +00001435 /*
1436 Get image distortion.
1437 */
cristy3fc482f2011-09-23 00:43:35 +00001438 length=MaxPixelChannels+1UL;
cristy3ed852e2009-09-05 21:47:34 +00001439 channel_distortion=(double *) AcquireQuantumMemory(length,
1440 sizeof(*channel_distortion));
1441 if (channel_distortion == (double *) NULL)
1442 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1443 (void) ResetMagickMemory(channel_distortion,0,length*
1444 sizeof(*channel_distortion));
cristyda16f162011-02-19 23:52:17 +00001445 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +00001446 switch (metric)
1447 {
1448 case AbsoluteErrorMetric:
1449 {
cristy8a9106f2011-07-05 14:39:26 +00001450 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1451 exception);
cristy3ed852e2009-09-05 21:47:34 +00001452 break;
1453 }
cristy343eee92010-12-11 02:17:57 +00001454 case FuzzErrorMetric:
1455 {
cristy8a9106f2011-07-05 14:39:26 +00001456 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1457 exception);
cristy343eee92010-12-11 02:17:57 +00001458 break;
1459 }
cristy3ed852e2009-09-05 21:47:34 +00001460 case MeanAbsoluteErrorMetric:
1461 {
cristy8a9106f2011-07-05 14:39:26 +00001462 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001463 channel_distortion,exception);
1464 break;
1465 }
cristy03fa69f2014-01-08 18:36:49 +00001466 case MeanErrorPerPixelErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001467 {
cristy8a9106f2011-07-05 14:39:26 +00001468 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1469 exception);
cristy3ed852e2009-09-05 21:47:34 +00001470 break;
1471 }
1472 case MeanSquaredErrorMetric:
1473 {
cristy8a9106f2011-07-05 14:39:26 +00001474 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001475 channel_distortion,exception);
1476 break;
1477 }
cristy4c929a72010-11-24 18:54:42 +00001478 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001479 default:
cristy4c929a72010-11-24 18:54:42 +00001480 {
cristy3cc758f2010-11-27 01:33:49 +00001481 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001482 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001483 break;
1484 }
cristy3ed852e2009-09-05 21:47:34 +00001485 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001486 {
cristy8a9106f2011-07-05 14:39:26 +00001487 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001488 channel_distortion,exception);
1489 break;
1490 }
cristy03d6f862014-01-08 18:34:48 +00001491 case PeakSignalToNoiseRatioErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001492 {
cristy8a9106f2011-07-05 14:39:26 +00001493 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001494 channel_distortion,exception);
1495 break;
1496 }
cristy03d6f862014-01-08 18:34:48 +00001497 case PerceptualHashErrorMetric:
1498 {
1499 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1500 channel_distortion,exception);
1501 break;
1502 }
cristy3ed852e2009-09-05 21:47:34 +00001503 case RootMeanSquaredErrorMetric:
1504 {
cristy8a9106f2011-07-05 14:39:26 +00001505 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001506 channel_distortion,exception);
1507 break;
1508 }
1509 }
cristyda16f162011-02-19 23:52:17 +00001510 if (status == MagickFalse)
1511 {
1512 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1513 return((double *) NULL);
1514 }
cristy3ed852e2009-09-05 21:47:34 +00001515 return(channel_distortion);
1516}
1517
1518/*
1519%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1520% %
1521% %
1522% %
1523% I s I m a g e s E q u a l %
1524% %
1525% %
1526% %
1527%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1528%
Cristy4ed18982015-12-12 09:50:30 -05001529% IsImagesEqual() compare the pixels of two images and returns immediately
1530% if any pixel is not identical.
1531%
1532% The format of the IsImagesEqual method is:
1533%
1534% MagickBooleanType IsImagesEqual(const Image *image,
1535% const Image *reconstruct_image,ExceptionInfo *exception)
1536%
1537% A description of each parameter follows.
1538%
1539% o image: the image.
1540%
1541% o reconstruct_image: the reconstruct image.
1542%
1543% o exception: return any errors or warnings in this structure.
1544%
1545*/
1546MagickExport MagickBooleanType IsImagesEqual(const Image *image,
1547 const Image *reconstruct_image,ExceptionInfo *exception)
1548{
1549 CacheView
1550 *image_view,
1551 *reconstruct_view;
1552
1553 size_t
1554 columns,
1555 rows;
1556
1557 ssize_t
1558 y;
1559
1560 assert(image != (Image *) NULL);
1561 assert(image->signature == MagickCoreSignature);
1562 assert(reconstruct_image != (const Image *) NULL);
1563 assert(reconstruct_image->signature == MagickCoreSignature);
1564 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1565 ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
1566 rows=MagickMax(image->rows,reconstruct_image->rows);
1567 columns=MagickMax(image->columns,reconstruct_image->columns);
1568 image_view=AcquireVirtualCacheView(image,exception);
1569 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
1570 for (y=0; y < (ssize_t) rows; y++)
1571 {
1572 register const Quantum
1573 *magick_restrict p,
1574 *magick_restrict q;
1575
1576 register ssize_t
1577 x;
1578
1579 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1580 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
1581 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1582 break;
1583 for (x=0; x < (ssize_t) columns; x++)
1584 {
1585 register ssize_t
1586 i;
1587
1588 if (GetPixelReadMask(image,p) == 0)
1589 {
1590 p+=GetPixelChannels(image);
1591 q+=GetPixelChannels(reconstruct_image);
1592 continue;
1593 }
1594 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1595 {
1596 double
1597 distance;
1598
1599 PixelChannel channel=GetPixelChannelChannel(image,i);
1600 PixelTrait traits=GetPixelChannelTraits(image,channel);
1601 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1602 channel);
1603 if ((traits == UndefinedPixelTrait) ||
1604 (reconstruct_traits == UndefinedPixelTrait) ||
1605 ((reconstruct_traits & UpdatePixelTrait) == 0))
1606 continue;
1607 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
1608 channel,q));
1609 if (distance >= MagickEpsilon)
1610 break;
1611 }
1612 if (i < (ssize_t) GetPixelChannels(image))
1613 break;
1614 p+=GetPixelChannels(image);
1615 q+=GetPixelChannels(reconstruct_image);
1616 }
1617 if (x < (ssize_t) columns)
1618 break;
1619 }
1620 reconstruct_view=DestroyCacheView(reconstruct_view);
1621 image_view=DestroyCacheView(image_view);
1622 return(y < (ssize_t) rows ? MagickFalse : MagickTrue);
1623}
1624
1625/*
1626%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1627% %
1628% %
1629% %
1630% S e t I m a g e C o l o r M e t r i c %
1631% %
1632% %
1633% %
1634%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1635%
1636% SetImageColorMetric() measures the difference between colors at each pixel
cristy3ed852e2009-09-05 21:47:34 +00001637% location of two images. A value other than 0 means the colors match
1638% exactly. Otherwise an error measure is computed by summing over all
1639% pixels in an image the distance squared in RGB space between each image
1640% pixel and its corresponding pixel in the reconstruct image. The error
1641% measure is assigned to these image members:
1642%
1643% o mean_error_per_pixel: The mean error for any single pixel in
1644% the image.
1645%
1646% o normalized_mean_error: The normalized mean quantization error for
1647% any single pixel in the image. This distance measure is normalized to
1648% a range between 0 and 1. It is independent of the range of red, green,
1649% and blue values in the image.
1650%
1651% o normalized_maximum_error: The normalized maximum quantization
1652% error for any single pixel in the image. This distance measure is
1653% normalized to a range between 0 and 1. It is independent of the range
1654% of red, green, and blue values in your image.
1655%
1656% A small normalized mean square error, accessed as
1657% image->normalized_mean_error, suggests the images are very similar in
1658% spatial layout and color.
1659%
Cristy4ed18982015-12-12 09:50:30 -05001660% The format of the SetImageColorMetric method is:
cristy3ed852e2009-09-05 21:47:34 +00001661%
Cristy4ed18982015-12-12 09:50:30 -05001662% MagickBooleanType SetImageColorMetric(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001663% const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001664%
1665% A description of each parameter follows.
1666%
1667% o image: the image.
1668%
1669% o reconstruct_image: the reconstruct image.
1670%
cristy018f07f2011-09-04 21:15:19 +00001671% o exception: return any errors or warnings in this structure.
1672%
cristy3ed852e2009-09-05 21:47:34 +00001673*/
Cristy4ed18982015-12-12 09:50:30 -05001674MagickExport MagickBooleanType SetImageColorMetric(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001675 const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001676{
cristyc4c8d132010-01-07 01:58:38 +00001677 CacheView
1678 *image_view,
1679 *reconstruct_view;
1680
cristya19f1d72012-08-07 18:24:38 +00001681 double
cristy3ed852e2009-09-05 21:47:34 +00001682 area,
1683 maximum_error,
1684 mean_error,
1685 mean_error_per_pixel;
1686
Cristy4ed18982015-12-12 09:50:30 -05001687 MagickBooleanType
1688 status;
1689
cristye68f5992015-03-03 17:44:51 +00001690 size_t
1691 columns,
1692 rows;
1693
cristy9d314ff2011-03-09 01:30:28 +00001694 ssize_t
1695 y;
1696
cristy3ed852e2009-09-05 21:47:34 +00001697 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001698 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001699 assert(reconstruct_image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001700 assert(reconstruct_image->signature == MagickCoreSignature);
cristy73626632014-07-19 20:52:21 +00001701 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1702 ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001703 area=0.0;
1704 maximum_error=0.0;
1705 mean_error_per_pixel=0.0;
1706 mean_error=0.0;
cristye68f5992015-03-03 17:44:51 +00001707 rows=MagickMax(image->rows,reconstruct_image->rows);
1708 columns=MagickMax(image->columns,reconstruct_image->columns);
cristy46ff2672012-12-14 15:32:26 +00001709 image_view=AcquireVirtualCacheView(image,exception);
1710 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristye68f5992015-03-03 17:44:51 +00001711 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001712 {
cristy4c08aed2011-07-01 19:47:50 +00001713 register const Quantum
dirk05d2ff72015-11-18 23:13:43 +01001714 *magick_restrict p,
1715 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001716
cristybb503372010-05-27 20:51:26 +00001717 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001718 x;
1719
cristye68f5992015-03-03 17:44:51 +00001720 p=GetCacheViewVirtualPixels(image_view,0,y,columns,1,exception);
1721 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001722 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001723 break;
cristye68f5992015-03-03 17:44:51 +00001724 for (x=0; x < (ssize_t) columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001725 {
cristyd5c15f92011-09-23 00:58:33 +00001726 register ssize_t
1727 i;
cristy3ed852e2009-09-05 21:47:34 +00001728
cristy883fde12013-04-08 00:50:13 +00001729 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +00001730 {
1731 p+=GetPixelChannels(image);
1732 q+=GetPixelChannels(reconstruct_image);
1733 continue;
1734 }
cristyd5c15f92011-09-23 00:58:33 +00001735 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1736 {
cristya19f1d72012-08-07 18:24:38 +00001737 double
cristyd5c15f92011-09-23 00:58:33 +00001738 distance;
1739
cristy5a23c552013-02-13 14:34:28 +00001740 PixelChannel channel=GetPixelChannelChannel(image,i);
1741 PixelTrait traits=GetPixelChannelTraits(image,channel);
1742 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1743 channel);
cristyd5c15f92011-09-23 00:58:33 +00001744 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001745 (reconstruct_traits == UndefinedPixelTrait) ||
1746 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001747 continue;
cristya19f1d72012-08-07 18:24:38 +00001748 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
cristy0beccfa2011-09-25 20:47:53 +00001749 channel,q));
dirk86d34132014-08-31 10:25:26 +00001750 if (distance >= MagickEpsilon)
1751 {
1752 mean_error_per_pixel+=distance;
1753 mean_error+=distance*distance;
1754 if (distance > maximum_error)
1755 maximum_error=distance;
1756 }
cristyd5c15f92011-09-23 00:58:33 +00001757 area++;
1758 }
cristyed231572011-07-14 02:18:59 +00001759 p+=GetPixelChannels(image);
1760 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001761 }
1762 }
1763 reconstruct_view=DestroyCacheView(reconstruct_view);
1764 image_view=DestroyCacheView(image_view);
1765 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1766 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1767 mean_error/area);
1768 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1769 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1770 return(status);
1771}
1772
1773/*
1774%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1775% %
1776% %
1777% %
1778% S i m i l a r i t y I m a g e %
1779% %
1780% %
1781% %
1782%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1783%
1784% SimilarityImage() compares the reference image of the image and returns the
1785% best match offset. In addition, it returns a similarity image such that an
1786% exact match location is completely white and if none of the pixels match,
1787% black, otherwise some gray level in-between.
1788%
1789% The format of the SimilarityImageImage method is:
1790%
1791% Image *SimilarityImage(const Image *image,const Image *reference,
cristy62e52182013-03-15 14:26:17 +00001792% const MetricType metric,const double similarity_threshold,
1793% RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001794%
1795% A description of each parameter follows:
1796%
1797% o image: the image.
1798%
1799% o reference: find an area of the image that closely resembles this image.
1800%
cristy09136812011-10-18 15:24:30 +00001801% o metric: the metric.
1802%
cristy62e52182013-03-15 14:26:17 +00001803% o similarity_threshold: minimum distortion for (sub)image match.
1804%
1805% o offset: the best match offset of the reference image within the image.
cristy3ed852e2009-09-05 21:47:34 +00001806%
1807% o similarity: the computed similarity between the images.
1808%
1809% o exception: return any errors or warnings in this structure.
1810%
1811*/
1812
1813static double GetSimilarityMetric(const Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001814 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1815 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001816{
1817 double
cristy3cc758f2010-11-27 01:33:49 +00001818 distortion;
cristy3ed852e2009-09-05 21:47:34 +00001819
cristy713ff212010-11-26 21:56:11 +00001820 Image
1821 *similarity_image;
cristy3ed852e2009-09-05 21:47:34 +00001822
cristy09136812011-10-18 15:24:30 +00001823 MagickBooleanType
1824 status;
1825
cristy713ff212010-11-26 21:56:11 +00001826 RectangleInfo
1827 geometry;
cristy3ed852e2009-09-05 21:47:34 +00001828
cristy713ff212010-11-26 21:56:11 +00001829 SetGeometry(reference,&geometry);
1830 geometry.x=x_offset;
1831 geometry.y=y_offset;
1832 similarity_image=CropImage(image,&geometry,exception);
1833 if (similarity_image == (Image *) NULL)
1834 return(0.0);
cristy09136812011-10-18 15:24:30 +00001835 distortion=0.0;
1836 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
cristy3cc758f2010-11-27 01:33:49 +00001837 exception);
cristy713ff212010-11-26 21:56:11 +00001838 similarity_image=DestroyImage(similarity_image);
cristy09136812011-10-18 15:24:30 +00001839 if (status == MagickFalse)
1840 return(0.0);
cristy3cc758f2010-11-27 01:33:49 +00001841 return(distortion);
cristy3ed852e2009-09-05 21:47:34 +00001842}
1843
1844MagickExport Image *SimilarityImage(Image *image,const Image *reference,
cristy62e52182013-03-15 14:26:17 +00001845 const MetricType metric,const double similarity_threshold,
1846 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001847{
1848#define SimilarityImageTag "Similarity/Image"
1849
cristyc4c8d132010-01-07 01:58:38 +00001850 CacheView
1851 *similarity_view;
1852
cristy3ed852e2009-09-05 21:47:34 +00001853 Image
1854 *similarity_image;
1855
1856 MagickBooleanType
1857 status;
1858
cristybb503372010-05-27 20:51:26 +00001859 MagickOffsetType
1860 progress;
1861
1862 ssize_t
1863 y;
1864
cristy3ed852e2009-09-05 21:47:34 +00001865 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001866 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001867 if (image->debug != MagickFalse)
1868 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1869 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001870 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001871 assert(offset != (RectangleInfo *) NULL);
1872 SetGeometry(reference,offset);
cristyfe181a72014-02-02 21:17:43 +00001873 *similarity_metric=MagickMaximumValue;
cristy73626632014-07-19 20:52:21 +00001874 if (ValidateImageMorphology(image,reference) == MagickFalse)
1875 ThrowImageException(ImageError,"ImageMorphologyDiffers");
cristy3ed852e2009-09-05 21:47:34 +00001876 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1877 image->rows-reference->rows+1,MagickTrue,exception);
1878 if (similarity_image == (Image *) NULL)
1879 return((Image *) NULL);
cristyd5c15f92011-09-23 00:58:33 +00001880 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1881 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001882 {
cristy3ed852e2009-09-05 21:47:34 +00001883 similarity_image=DestroyImage(similarity_image);
1884 return((Image *) NULL);
1885 }
cristyb979cea2013-03-01 14:22:42 +00001886 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
1887 exception);
cristy3ed852e2009-09-05 21:47:34 +00001888 /*
1889 Measure similarity of reference image against image.
1890 */
1891 status=MagickTrue;
1892 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001893 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001894#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy70ea54f2013-03-15 18:49:14 +00001895 #pragma omp parallel for schedule(static,4) \
1896 shared(progress,status,similarity_metric) \
cristy5e6b2592012-12-19 14:08:11 +00001897 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001898#endif
cristybb503372010-05-27 20:51:26 +00001899 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
cristy3ed852e2009-09-05 21:47:34 +00001900 {
1901 double
1902 similarity;
1903
cristy4c08aed2011-07-01 19:47:50 +00001904 register Quantum
dirk05d2ff72015-11-18 23:13:43 +01001905 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001906
cristy49dd6a02011-09-24 23:08:01 +00001907 register ssize_t
1908 x;
1909
cristy3ed852e2009-09-05 21:47:34 +00001910 if (status == MagickFalse)
1911 continue;
cristy266f58b2013-05-15 11:47:01 +00001912#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy1aadacc2013-04-27 16:31:24 +00001913 #pragma omp flush(similarity_metric)
cristy266f58b2013-05-15 11:47:01 +00001914#endif
cristy24856ba2013-03-15 18:24:00 +00001915 if (*similarity_metric <= similarity_threshold)
1916 continue;
cristy3cc758f2010-11-27 01:33:49 +00001917 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1918 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001919 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001920 {
1921 status=MagickFalse;
1922 continue;
1923 }
cristybb503372010-05-27 20:51:26 +00001924 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
cristy3ed852e2009-09-05 21:47:34 +00001925 {
cristy49dd6a02011-09-24 23:08:01 +00001926 register ssize_t
1927 i;
1928
cristy266f58b2013-05-15 11:47:01 +00001929#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy1aadacc2013-04-27 16:31:24 +00001930 #pragma omp flush(similarity_metric)
cristy266f58b2013-05-15 11:47:01 +00001931#endif
cristy24856ba2013-03-15 18:24:00 +00001932 if (*similarity_metric <= similarity_threshold)
1933 break;
cristy09136812011-10-18 15:24:30 +00001934 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
cristyb5d5f722009-11-04 03:03:49 +00001935#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001936 #pragma omp critical (MagickCore_SimilarityImage)
cristy3ed852e2009-09-05 21:47:34 +00001937#endif
cristy12a73d32015-03-16 23:03:18 +00001938 if ((metric == NormalizedCrossCorrelationErrorMetric) ||
1939 (metric == UndefinedErrorMetric))
1940 similarity=1.0-similarity;
cristy3ed852e2009-09-05 21:47:34 +00001941 if (similarity < *similarity_metric)
1942 {
cristy3ed852e2009-09-05 21:47:34 +00001943 offset->x=x;
1944 offset->y=y;
cristy24856ba2013-03-15 18:24:00 +00001945 *similarity_metric=similarity;
cristy3ed852e2009-09-05 21:47:34 +00001946 }
cristyefdcd372014-02-03 18:40:08 +00001947 if (metric == PerceptualHashErrorMetric)
1948 similarity=MagickMin(0.01*similarity,1.0);
cristy883fde12013-04-08 00:50:13 +00001949 if (GetPixelReadMask(similarity_image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00001950 {
cristyc3a58022013-10-09 23:22:42 +00001951 SetPixelBackgoundColor(similarity_image,q);
cristyc94ba6f2012-01-29 23:19:58 +00001952 q+=GetPixelChannels(similarity_image);
cristy10a6c612012-01-29 21:41:05 +00001953 continue;
1954 }
cristyc94ba6f2012-01-29 23:19:58 +00001955 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
cristy49dd6a02011-09-24 23:08:01 +00001956 {
cristy5a23c552013-02-13 14:34:28 +00001957 PixelChannel channel=GetPixelChannelChannel(image,i);
1958 PixelTrait traits=GetPixelChannelTraits(image,channel);
1959 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1960 channel);
cristy49dd6a02011-09-24 23:08:01 +00001961 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001962 (similarity_traits == UndefinedPixelTrait) ||
1963 ((similarity_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001964 continue;
cristy0beccfa2011-09-25 20:47:53 +00001965 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
cristyefdcd372014-02-03 18:40:08 +00001966 QuantumRange*similarity),q);
cristy49dd6a02011-09-24 23:08:01 +00001967 }
cristyed231572011-07-14 02:18:59 +00001968 q+=GetPixelChannels(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001969 }
cristyb979cea2013-03-01 14:22:42 +00001970 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001971 status=MagickFalse;
1972 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1973 {
cristyb979cea2013-03-01 14:22:42 +00001974 MagickBooleanType
1975 proceed;
1976
cristyb5d5f722009-11-04 03:03:49 +00001977#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001978 #pragma omp critical (MagickCore_SimilarityImage)
cristy3ed852e2009-09-05 21:47:34 +00001979#endif
cristyb979cea2013-03-01 14:22:42 +00001980 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1981 image->rows);
1982 if (proceed == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001983 status=MagickFalse;
1984 }
1985 }
1986 similarity_view=DestroyCacheView(similarity_view);
cristy1c2f48d2012-12-14 01:20:55 +00001987 if (status == MagickFalse)
1988 similarity_image=DestroyImage(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001989 return(similarity_image);
1990}