blob: f4e434c1387ee551d969082f1e3db1b9e8b28fc4 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% CCCC OOO M M PPPP AAA RRRR EEEEE %
7% C O O MM MM P P A A R R E %
8% C O O M M M PPPP AAAAA RRRR EEE %
9% C O O M M P A A R R E %
10% CCCC OOO M M P A A R R EEEEE %
11% %
12% %
13% MagickCore Image Comparison Methods %
14% %
15% Software Design %
cristy2fb99212014-04-18 22:15:04 +000016% Cristy %
cristy3ed852e2009-09-05 21:47:34 +000017% December 2003 %
18% %
19% %
cristyb56bb242014-11-25 17:12:48 +000020% Copyright 1999-2015 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/artifact.h"
cristyc97da4b2014-01-18 15:58:31 +000045#include "MagickCore/attribute.h"
cristy4c08aed2011-07-01 19:47:50 +000046#include "MagickCore/cache-view.h"
cristy6a2180c2013-05-27 10:28:36 +000047#include "MagickCore/channel.h"
cristy4c08aed2011-07-01 19:47:50 +000048#include "MagickCore/client.h"
49#include "MagickCore/color.h"
50#include "MagickCore/color-private.h"
51#include "MagickCore/colorspace.h"
52#include "MagickCore/colorspace-private.h"
53#include "MagickCore/compare.h"
54#include "MagickCore/composite-private.h"
55#include "MagickCore/constitute.h"
56#include "MagickCore/exception-private.h"
57#include "MagickCore/geometry.h"
58#include "MagickCore/image-private.h"
59#include "MagickCore/list.h"
60#include "MagickCore/log.h"
61#include "MagickCore/memory_.h"
62#include "MagickCore/monitor.h"
63#include "MagickCore/monitor-private.h"
64#include "MagickCore/option.h"
65#include "MagickCore/pixel-accessor.h"
cristyb92495a2013-08-20 00:10:59 +000066#include "MagickCore/property.h"
cristy4c08aed2011-07-01 19:47:50 +000067#include "MagickCore/resource_.h"
68#include "MagickCore/string_.h"
69#include "MagickCore/statistic.h"
cristy16881e62012-05-06 14:41:29 +000070#include "MagickCore/thread-private.h"
cristy4c08aed2011-07-01 19:47:50 +000071#include "MagickCore/transform.h"
72#include "MagickCore/utility.h"
73#include "MagickCore/version.h"
cristy3ed852e2009-09-05 21:47:34 +000074
75/*
76%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
77% %
78% %
79% %
cristy8a9106f2011-07-05 14:39:26 +000080% C o m p a r e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +000081% %
82% %
83% %
84%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
85%
cristyaeded782012-09-11 23:39:36 +000086% CompareImages() compares one or more pixel channels of an image to a
cristy8a9106f2011-07-05 14:39:26 +000087% reconstructed image and returns the difference image.
cristy3ed852e2009-09-05 21:47:34 +000088%
cristy8a9106f2011-07-05 14:39:26 +000089% The format of the CompareImages method is:
cristy3ed852e2009-09-05 21:47:34 +000090%
cristy8a9106f2011-07-05 14:39:26 +000091% Image *CompareImages(const Image *image,const Image *reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +000092% const MetricType metric,double *distortion,ExceptionInfo *exception)
93%
94% A description of each parameter follows:
95%
96% o image: the image.
97%
98% o reconstruct_image: the reconstruct image.
99%
cristy3ed852e2009-09-05 21:47:34 +0000100% o metric: the metric.
101%
102% o distortion: the computed distortion between the images.
103%
104% o exception: return any errors or warnings in this structure.
105%
106*/
cristy73626632014-07-19 20:52:21 +0000107
cristy68094062014-08-22 12:59:44 +0000108static size_t GetImageChannels(const Image *image)
109{
110 register ssize_t
111 i;
112
113 size_t
114 channels;
115
116 channels=0;
117 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
118 {
119 PixelChannel channel=GetPixelChannelChannel(image,i);
120 PixelTrait traits=GetPixelChannelTraits(image,channel);
121 if ((traits & UpdatePixelTrait) != 0)
122 channels++;
123 }
124 return(channels == 0 ? 1 : channels);
125}
126
cristy73626632014-07-19 20:52:21 +0000127static inline MagickBooleanType ValidateImageMorphology(
128 const Image *restrict image,const Image *restrict reconstruct_image)
129{
130 /*
131 Does the image match the reconstructed image morphology?
cristyc2e29bd2014-08-22 12:50:44 +0000132 */
cristy73626632014-07-19 20:52:21 +0000133 return(MagickTrue);
134}
135
cristy3ed852e2009-09-05 21:47:34 +0000136MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
137 const MetricType metric,double *distortion,ExceptionInfo *exception)
138{
cristyc4c8d132010-01-07 01:58:38 +0000139 CacheView
140 *highlight_view,
141 *image_view,
142 *reconstruct_view;
143
dirk8a7bbf42015-01-08 23:04:58 +0000144 double
145 fuzz;
146
cristy3ed852e2009-09-05 21:47:34 +0000147 const char
148 *artifact;
149
150 Image
151 *difference_image,
152 *highlight_image;
153
cristy3ed852e2009-09-05 21:47:34 +0000154 MagickBooleanType
155 status;
156
cristy4c08aed2011-07-01 19:47:50 +0000157 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000158 highlight,
cristy3fc482f2011-09-23 00:43:35 +0000159 lowlight;
cristy3ed852e2009-09-05 21:47:34 +0000160
cristy49dd6a02011-09-24 23:08:01 +0000161 ssize_t
162 y;
163
cristy3ed852e2009-09-05 21:47:34 +0000164 assert(image != (Image *) NULL);
165 assert(image->signature == MagickSignature);
166 if (image->debug != MagickFalse)
167 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
168 assert(reconstruct_image != (const Image *) NULL);
169 assert(reconstruct_image->signature == MagickSignature);
170 assert(distortion != (double *) NULL);
171 *distortion=0.0;
172 if (image->debug != MagickFalse)
173 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8b520b42014-01-30 00:58:45 +0000174 if (metric != PerceptualHashErrorMetric)
cristy73626632014-07-19 20:52:21 +0000175 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
176 ThrowImageException(ImageError,"ImageMorphologyDiffers");
cristy8a9106f2011-07-05 14:39:26 +0000177 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
178 exception);
cristy3ed852e2009-09-05 21:47:34 +0000179 if (status == MagickFalse)
180 return((Image *) NULL);
181 difference_image=CloneImage(image,0,0,MagickTrue,exception);
182 if (difference_image == (Image *) NULL)
183 return((Image *) NULL);
cristy63240882011-08-05 19:05:27 +0000184 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +0000185 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
186 exception);
187 if (highlight_image == (Image *) NULL)
188 {
189 difference_image=DestroyImage(difference_image);
190 return((Image *) NULL);
191 }
cristy3fc482f2011-09-23 00:43:35 +0000192 status=SetImageStorageClass(highlight_image,DirectClass,exception);
193 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000194 {
cristy3ed852e2009-09-05 21:47:34 +0000195 difference_image=DestroyImage(difference_image);
196 highlight_image=DestroyImage(highlight_image);
197 return((Image *) NULL);
198 }
cristy63240882011-08-05 19:05:27 +0000199 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
cristyca611542013-02-19 00:54:03 +0000200 (void) QueryColorCompliance("#f1001ecc",AllCompliance,&highlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000201 artifact=GetImageArtifact(image,"highlight-color");
202 if (artifact != (const char *) NULL)
cristyf9d6dc02012-01-19 02:14:44 +0000203 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception);
cristyca611542013-02-19 00:54:03 +0000204 (void) QueryColorCompliance("#ffffffcc",AllCompliance,&lowlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000205 artifact=GetImageArtifact(image,"lowlight-color");
206 if (artifact != (const char *) NULL)
cristy0b1a7972011-10-22 22:17:02 +0000207 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000208 /*
209 Generate difference image.
210 */
211 status=MagickTrue;
dirk8a7bbf42015-01-08 23:04:58 +0000212 fuzz=GetMaxImageFuzz(image,reconstruct_image);
cristy46ff2672012-12-14 15:32:26 +0000213 image_view=AcquireVirtualCacheView(image,exception);
214 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
215 highlight_view=AcquireAuthenticCacheView(highlight_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000216#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000217 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000218 magick_threads(image,highlight_image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000219#endif
cristybb503372010-05-27 20:51:26 +0000220 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000221 {
222 MagickBooleanType
223 sync;
224
cristy4c08aed2011-07-01 19:47:50 +0000225 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000226 *restrict p,
227 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000228
cristy4c08aed2011-07-01 19:47:50 +0000229 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000230 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000231
cristy49dd6a02011-09-24 23:08:01 +0000232 register ssize_t
233 x;
234
cristy3ed852e2009-09-05 21:47:34 +0000235 if (status == MagickFalse)
236 continue;
237 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristyd9f62302014-02-01 16:56:29 +0000238 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,image->columns,1,
239 exception);
cristy3ed852e2009-09-05 21:47:34 +0000240 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
241 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000242 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
243 (r == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000244 {
245 status=MagickFalse;
246 continue;
247 }
cristybb503372010-05-27 20:51:26 +0000248 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000249 {
cristydb5c82c2013-02-22 00:41:33 +0000250 double
251 Da,
252 Sa;
253
cristy3ed852e2009-09-05 21:47:34 +0000254 MagickStatusType
255 difference;
256
cristy3fc482f2011-09-23 00:43:35 +0000257 register ssize_t
258 i;
259
cristy883fde12013-04-08 00:50:13 +0000260 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000261 {
cristy11a06d32015-01-04 12:03:27 +0000262 SetPixelViaPixelInfo(highlight_image,&lowlight,r);
cristy10a6c612012-01-29 21:41:05 +0000263 p+=GetPixelChannels(image);
264 q+=GetPixelChannels(reconstruct_image);
265 r+=GetPixelChannels(highlight_image);
266 continue;
267 }
cristyd09f8802012-02-04 16:44:10 +0000268 difference=MagickFalse;
cristydb5c82c2013-02-22 00:41:33 +0000269 Sa=QuantumScale*GetPixelAlpha(image,p);
270 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000271 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
272 {
cristya19f1d72012-08-07 18:24:38 +0000273 double
cristy0beccfa2011-09-25 20:47:53 +0000274 distance;
275
cristy5a23c552013-02-13 14:34:28 +0000276 PixelChannel channel=GetPixelChannelChannel(image,i);
277 PixelTrait traits=GetPixelChannelTraits(image,channel);
278 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
279 channel);
cristy3fc482f2011-09-23 00:43:35 +0000280 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000281 (reconstruct_traits == UndefinedPixelTrait) ||
282 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy3fc482f2011-09-23 00:43:35 +0000283 continue;
cristydb5c82c2013-02-22 00:41:33 +0000284 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
dirk8a7bbf42015-01-08 23:04:58 +0000285 if ((distance*distance) > fuzz)
286 {
287 difference=MagickTrue;
288 break;
289 }
cristy3fc482f2011-09-23 00:43:35 +0000290 }
291 if (difference == MagickFalse)
cristy11a06d32015-01-04 12:03:27 +0000292 SetPixelViaPixelInfo(highlight_image,&lowlight,r);
cristy3fc482f2011-09-23 00:43:35 +0000293 else
cristy11a06d32015-01-04 12:03:27 +0000294 SetPixelViaPixelInfo(highlight_image,&highlight,r);
cristyed231572011-07-14 02:18:59 +0000295 p+=GetPixelChannels(image);
296 q+=GetPixelChannels(reconstruct_image);
297 r+=GetPixelChannels(highlight_image);
cristy3ed852e2009-09-05 21:47:34 +0000298 }
299 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
300 if (sync == MagickFalse)
301 status=MagickFalse;
302 }
303 highlight_view=DestroyCacheView(highlight_view);
304 reconstruct_view=DestroyCacheView(reconstruct_view);
305 image_view=DestroyCacheView(image_view);
cristyfeb3e962012-03-29 17:25:55 +0000306 (void) CompositeImage(difference_image,highlight_image,image->compose,
cristy39172402012-03-30 13:04:39 +0000307 MagickTrue,0,0,exception);
cristy3ed852e2009-09-05 21:47:34 +0000308 highlight_image=DestroyImage(highlight_image);
309 if (status == MagickFalse)
310 difference_image=DestroyImage(difference_image);
311 return(difference_image);
312}
313
314/*
315%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
316% %
317% %
318% %
cristy8a9106f2011-07-05 14:39:26 +0000319% G e t I m a g e D i s t o r t i o n %
cristy3ed852e2009-09-05 21:47:34 +0000320% %
321% %
322% %
323%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
324%
cristyaeded782012-09-11 23:39:36 +0000325% GetImageDistortion() compares one or more pixel channels of an image to a
cristy8a9106f2011-07-05 14:39:26 +0000326% reconstructed image and returns the specified distortion metric.
cristy3ed852e2009-09-05 21:47:34 +0000327%
cristy44097f52012-12-16 19:56:20 +0000328% The format of the GetImageDistortion method is:
cristy3ed852e2009-09-05 21:47:34 +0000329%
cristy8a9106f2011-07-05 14:39:26 +0000330% MagickBooleanType GetImageDistortion(const Image *image,
331% const Image *reconstruct_image,const MetricType metric,
332% double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000333%
334% A description of each parameter follows:
335%
336% o image: the image.
337%
338% o reconstruct_image: the reconstruct image.
339%
cristy3ed852e2009-09-05 21:47:34 +0000340% o metric: the metric.
341%
342% o distortion: the computed distortion between the images.
343%
344% o exception: return any errors or warnings in this structure.
345%
346*/
347
cristy3cc758f2010-11-27 01:33:49 +0000348static MagickBooleanType GetAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000349 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000350{
cristyc4c8d132010-01-07 01:58:38 +0000351 CacheView
352 *image_view,
353 *reconstruct_view;
354
cristydb5c82c2013-02-22 00:41:33 +0000355 double
356 fuzz;
357
cristy3ed852e2009-09-05 21:47:34 +0000358 MagickBooleanType
359 status;
360
cristy9d314ff2011-03-09 01:30:28 +0000361 ssize_t
362 y;
363
cristy3ed852e2009-09-05 21:47:34 +0000364 /*
365 Compute the absolute difference in pixels between two images.
366 */
367 status=MagickTrue;
dirk8a7bbf42015-01-08 23:04:58 +0000368 fuzz=GetMaxImageFuzz(image,reconstruct_image);
cristy46ff2672012-12-14 15:32:26 +0000369 image_view=AcquireVirtualCacheView(image,exception);
370 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000371#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000372 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000373 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000374#endif
cristybb503372010-05-27 20:51:26 +0000375 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000376 {
377 double
cristy3fc482f2011-09-23 00:43:35 +0000378 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000379
cristy4c08aed2011-07-01 19:47:50 +0000380 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000381 *restrict p,
382 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000383
cristybb503372010-05-27 20:51:26 +0000384 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000385 i,
386 x;
387
388 if (status == MagickFalse)
389 continue;
390 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
391 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
392 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000393 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000394 {
395 status=MagickFalse;
396 continue;
397 }
cristy3ed852e2009-09-05 21:47:34 +0000398 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000399 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000400 {
cristydb5c82c2013-02-22 00:41:33 +0000401 double
402 Da,
403 Sa;
404
cristy3fc482f2011-09-23 00:43:35 +0000405 MagickBooleanType
406 difference;
407
408 register ssize_t
409 i;
410
cristy883fde12013-04-08 00:50:13 +0000411 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000412 {
413 p+=GetPixelChannels(image);
414 q+=GetPixelChannels(reconstruct_image);
415 continue;
416 }
cristyd09f8802012-02-04 16:44:10 +0000417 difference=MagickFalse;
cristydb5c82c2013-02-22 00:41:33 +0000418 Sa=QuantumScale*GetPixelAlpha(image,p);
419 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000420 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
421 {
cristydb5c82c2013-02-22 00:41:33 +0000422 double
423 distance;
424
cristy5a23c552013-02-13 14:34:28 +0000425 PixelChannel channel=GetPixelChannelChannel(image,i);
426 PixelTrait traits=GetPixelChannelTraits(image,channel);
427 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
428 channel);
cristy3fc482f2011-09-23 00:43:35 +0000429 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000430 (reconstruct_traits == UndefinedPixelTrait) ||
431 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000432 continue;
cristydb5c82c2013-02-22 00:41:33 +0000433 distance=Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q);
434 if ((distance*distance) > fuzz)
435 {
dirk8a7bbf42015-01-08 23:04:58 +0000436 channel_distortion[i]++;
cristydb5c82c2013-02-22 00:41:33 +0000437 difference=MagickTrue;
cristydb5c82c2013-02-22 00:41:33 +0000438 }
cristy3fc482f2011-09-23 00:43:35 +0000439 }
440 if (difference != MagickFalse)
dirk8a7bbf42015-01-08 23:04:58 +0000441 channel_distortion[CompositePixelChannel]++;
cristyed231572011-07-14 02:18:59 +0000442 p+=GetPixelChannels(image);
443 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000444 }
cristyb5d5f722009-11-04 03:03:49 +0000445#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000446 #pragma omp critical (MagickCore_GetAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +0000447#endif
cristy3fc482f2011-09-23 00:43:35 +0000448 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000449 distortion[i]+=channel_distortion[i];
450 }
451 reconstruct_view=DestroyCacheView(reconstruct_view);
452 image_view=DestroyCacheView(image_view);
453 return(status);
454}
455
cristy343eee92010-12-11 02:17:57 +0000456static MagickBooleanType GetFuzzDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000457 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy343eee92010-12-11 02:17:57 +0000458{
459 CacheView
460 *image_view,
461 *reconstruct_view;
462
cristy343eee92010-12-11 02:17:57 +0000463 MagickBooleanType
464 status;
465
466 register ssize_t
467 i;
468
cristy9d314ff2011-03-09 01:30:28 +0000469 ssize_t
470 y;
471
cristy343eee92010-12-11 02:17:57 +0000472 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000473 image_view=AcquireVirtualCacheView(image,exception);
474 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristy343eee92010-12-11 02:17:57 +0000475#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000476 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000477 magick_threads(image,image,image->rows,1)
cristy343eee92010-12-11 02:17:57 +0000478#endif
479 for (y=0; y < (ssize_t) image->rows; y++)
480 {
481 double
cristy3fc482f2011-09-23 00:43:35 +0000482 channel_distortion[MaxPixelChannels+1];
cristy343eee92010-12-11 02:17:57 +0000483
cristy4c08aed2011-07-01 19:47:50 +0000484 register const Quantum
cristy343eee92010-12-11 02:17:57 +0000485 *restrict p,
486 *restrict q;
487
488 register ssize_t
489 i,
490 x;
491
492 if (status == MagickFalse)
493 continue;
494 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristye6529ff2011-02-04 20:05:32 +0000495 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
496 1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000497 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy343eee92010-12-11 02:17:57 +0000498 {
499 status=MagickFalse;
500 continue;
501 }
cristy343eee92010-12-11 02:17:57 +0000502 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
503 for (x=0; x < (ssize_t) image->columns; x++)
504 {
cristydb5c82c2013-02-22 00:41:33 +0000505 double
506 Da,
507 Sa;
508
cristy3fc482f2011-09-23 00:43:35 +0000509 register ssize_t
510 i;
cristy343eee92010-12-11 02:17:57 +0000511
cristy883fde12013-04-08 00:50:13 +0000512 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000513 {
514 p+=GetPixelChannels(image);
515 q+=GetPixelChannels(reconstruct_image);
516 continue;
517 }
cristydb5c82c2013-02-22 00:41:33 +0000518 Sa=QuantumScale*GetPixelAlpha(image,p);
519 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000520 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
521 {
cristya19f1d72012-08-07 18:24:38 +0000522 double
cristy3fc482f2011-09-23 00:43:35 +0000523 distance;
524
cristy5a23c552013-02-13 14:34:28 +0000525 PixelChannel channel=GetPixelChannelChannel(image,i);
526 PixelTrait traits=GetPixelChannelTraits(image,channel);
527 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
528 channel);
cristy3fc482f2011-09-23 00:43:35 +0000529 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000530 (reconstruct_traits == UndefinedPixelTrait) ||
531 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000532 continue;
cristydb5c82c2013-02-22 00:41:33 +0000533 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
cristy1eced092012-08-10 23:10:56 +0000534 channel,q));
cristydb5c82c2013-02-22 00:41:33 +0000535 channel_distortion[i]+=distance*distance;
536 channel_distortion[CompositePixelChannel]+=distance*distance;
cristy3fc482f2011-09-23 00:43:35 +0000537 }
cristyed231572011-07-14 02:18:59 +0000538 p+=GetPixelChannels(image);
539 q+=GetPixelChannels(reconstruct_image);
cristy343eee92010-12-11 02:17:57 +0000540 }
541#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ad5c2a2012-05-05 21:32:54 +0000542 #pragma omp critical (MagickCore_GetFuzzDistortion)
cristy343eee92010-12-11 02:17:57 +0000543#endif
cristy3fc482f2011-09-23 00:43:35 +0000544 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000545 distortion[i]+=channel_distortion[i];
546 }
547 reconstruct_view=DestroyCacheView(reconstruct_view);
548 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000549 for (i=0; i <= MaxPixelChannels; i++)
cristy343eee92010-12-11 02:17:57 +0000550 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000551 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
552 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]);
cristy343eee92010-12-11 02:17:57 +0000553 return(status);
554}
555
cristy3cc758f2010-11-27 01:33:49 +0000556static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000557 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000558{
cristyc4c8d132010-01-07 01:58:38 +0000559 CacheView
560 *image_view,
561 *reconstruct_view;
562
cristy3ed852e2009-09-05 21:47:34 +0000563 MagickBooleanType
564 status;
565
cristybb503372010-05-27 20:51:26 +0000566 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000567 i;
568
cristy9d314ff2011-03-09 01:30:28 +0000569 ssize_t
570 y;
571
cristy3ed852e2009-09-05 21:47:34 +0000572 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000573 image_view=AcquireVirtualCacheView(image,exception);
574 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000575#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000576 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000577 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000578#endif
cristybb503372010-05-27 20:51:26 +0000579 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000580 {
581 double
cristy3fc482f2011-09-23 00:43:35 +0000582 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000583
cristy4c08aed2011-07-01 19:47:50 +0000584 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000585 *restrict p,
586 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000587
cristybb503372010-05-27 20:51:26 +0000588 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000589 i,
590 x;
591
592 if (status == MagickFalse)
593 continue;
594 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000595 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
596 1,exception);
597 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000598 {
599 status=MagickFalse;
600 continue;
601 }
cristy3ed852e2009-09-05 21:47:34 +0000602 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000603 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000604 {
cristydb5c82c2013-02-22 00:41:33 +0000605 double
606 Da,
607 Sa;
608
cristy3fc482f2011-09-23 00:43:35 +0000609 register ssize_t
610 i;
cristy3ed852e2009-09-05 21:47:34 +0000611
cristy883fde12013-04-08 00:50:13 +0000612 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000613 {
614 p+=GetPixelChannels(image);
615 q+=GetPixelChannels(reconstruct_image);
616 continue;
617 }
cristydb5c82c2013-02-22 00:41:33 +0000618 Sa=QuantumScale*GetPixelAlpha(image,p);
619 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000620 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
621 {
cristya19f1d72012-08-07 18:24:38 +0000622 double
cristy3fc482f2011-09-23 00:43:35 +0000623 distance;
624
cristy5a23c552013-02-13 14:34:28 +0000625 PixelChannel channel=GetPixelChannelChannel(image,i);
626 PixelTrait traits=GetPixelChannelTraits(image,channel);
627 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
628 channel);
cristy3fc482f2011-09-23 00:43:35 +0000629 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000630 (reconstruct_traits == UndefinedPixelTrait) ||
631 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000632 continue;
cristydb5c82c2013-02-22 00:41:33 +0000633 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
634 channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000635 channel_distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000636 channel_distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000637 }
cristyed231572011-07-14 02:18:59 +0000638 p+=GetPixelChannels(image);
639 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000640 }
cristyb5d5f722009-11-04 03:03:49 +0000641#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000642 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +0000643#endif
cristy3fc482f2011-09-23 00:43:35 +0000644 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000645 distortion[i]+=channel_distortion[i];
646 }
647 reconstruct_view=DestroyCacheView(reconstruct_view);
648 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000649 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000650 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000651 distortion[CompositePixelChannel]/=(double) GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000652 return(status);
653}
654
655static MagickBooleanType GetMeanErrorPerPixel(Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000656 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000657{
cristyc4c8d132010-01-07 01:58:38 +0000658 CacheView
659 *image_view,
660 *reconstruct_view;
661
cristy3ed852e2009-09-05 21:47:34 +0000662 MagickBooleanType
663 status;
664
cristya19f1d72012-08-07 18:24:38 +0000665 double
cristy3ed852e2009-09-05 21:47:34 +0000666 area,
cristy3ed852e2009-09-05 21:47:34 +0000667 maximum_error,
668 mean_error;
669
cristy9d314ff2011-03-09 01:30:28 +0000670 ssize_t
671 y;
672
cristy3ed852e2009-09-05 21:47:34 +0000673 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000674 area=0.0;
675 maximum_error=0.0;
676 mean_error=0.0;
cristy46ff2672012-12-14 15:32:26 +0000677 image_view=AcquireVirtualCacheView(image,exception);
678 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristybb503372010-05-27 20:51:26 +0000679 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000680 {
cristy4c08aed2011-07-01 19:47:50 +0000681 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000682 *restrict p,
683 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000684
cristybb503372010-05-27 20:51:26 +0000685 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000686 x;
687
688 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
689 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
690 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000691 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000692 {
693 status=MagickFalse;
694 break;
695 }
cristybb503372010-05-27 20:51:26 +0000696 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000697 {
cristydb5c82c2013-02-22 00:41:33 +0000698 double
699 Da,
700 Sa;
701
cristy3fc482f2011-09-23 00:43:35 +0000702 register ssize_t
703 i;
cristy3ed852e2009-09-05 21:47:34 +0000704
cristy883fde12013-04-08 00:50:13 +0000705 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000706 {
707 p+=GetPixelChannels(image);
708 q+=GetPixelChannels(reconstruct_image);
709 continue;
710 }
cristydb5c82c2013-02-22 00:41:33 +0000711 Sa=QuantumScale*GetPixelAlpha(image,p);
712 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000713 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
714 {
cristya19f1d72012-08-07 18:24:38 +0000715 double
cristy3fc482f2011-09-23 00:43:35 +0000716 distance;
717
cristy5a23c552013-02-13 14:34:28 +0000718 PixelChannel channel=GetPixelChannelChannel(image,i);
719 PixelTrait traits=GetPixelChannelTraits(image,channel);
720 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
721 channel);
cristy3fc482f2011-09-23 00:43:35 +0000722 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000723 (reconstruct_traits == UndefinedPixelTrait) ||
724 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000725 continue;
cristy0c935612014-08-21 16:44:46 +0000726 distance=fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,channel,q));
cristy3fc482f2011-09-23 00:43:35 +0000727 distortion[i]+=distance;
cristy5f95f4f2011-10-23 01:01:01 +0000728 distortion[CompositePixelChannel]+=distance;
cristy3fc482f2011-09-23 00:43:35 +0000729 mean_error+=distance*distance;
730 if (distance > maximum_error)
731 maximum_error=distance;
732 area++;
733 }
cristyed231572011-07-14 02:18:59 +0000734 p+=GetPixelChannels(image);
735 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000736 }
737 }
738 reconstruct_view=DestroyCacheView(reconstruct_view);
739 image_view=DestroyCacheView(image_view);
cristy5f95f4f2011-10-23 01:01:01 +0000740 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area;
cristy3ed852e2009-09-05 21:47:34 +0000741 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
742 image->error.normalized_maximum_error=QuantumScale*maximum_error;
743 return(status);
744}
745
cristy3cc758f2010-11-27 01:33:49 +0000746static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000747 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000748{
cristyc4c8d132010-01-07 01:58:38 +0000749 CacheView
750 *image_view,
751 *reconstruct_view;
752
cristy3ed852e2009-09-05 21:47:34 +0000753 MagickBooleanType
754 status;
755
cristybb503372010-05-27 20:51:26 +0000756 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000757 i;
758
cristy9d314ff2011-03-09 01:30:28 +0000759 ssize_t
760 y;
761
cristy3ed852e2009-09-05 21:47:34 +0000762 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000763 image_view=AcquireVirtualCacheView(image,exception);
764 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000765#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000766 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000767 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000768#endif
cristybb503372010-05-27 20:51:26 +0000769 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000770 {
771 double
cristy3fc482f2011-09-23 00:43:35 +0000772 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +0000773
cristy4c08aed2011-07-01 19:47:50 +0000774 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000775 *restrict p,
776 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000777
cristybb503372010-05-27 20:51:26 +0000778 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000779 i,
780 x;
781
782 if (status == MagickFalse)
783 continue;
784 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000785 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
786 1,exception);
787 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000788 {
789 status=MagickFalse;
790 continue;
791 }
cristy3ed852e2009-09-05 21:47:34 +0000792 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +0000793 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000794 {
cristydb5c82c2013-02-22 00:41:33 +0000795 double
796 Da,
797 Sa;
798
cristy3fc482f2011-09-23 00:43:35 +0000799 register ssize_t
800 i;
cristy3ed852e2009-09-05 21:47:34 +0000801
cristy883fde12013-04-08 00:50:13 +0000802 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000803 {
804 p+=GetPixelChannels(image);
805 q+=GetPixelChannels(reconstruct_image);
806 continue;
807 }
cristydb5c82c2013-02-22 00:41:33 +0000808 Sa=QuantumScale*GetPixelAlpha(image,p);
809 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000810 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
811 {
cristya19f1d72012-08-07 18:24:38 +0000812 double
cristy3fc482f2011-09-23 00:43:35 +0000813 distance;
814
cristy5a23c552013-02-13 14:34:28 +0000815 PixelChannel channel=GetPixelChannelChannel(image,i);
816 PixelTrait traits=GetPixelChannelTraits(image,channel);
817 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
818 channel);
cristy3fc482f2011-09-23 00:43:35 +0000819 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000820 (reconstruct_traits == UndefinedPixelTrait) ||
821 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000822 continue;
cristydb5c82c2013-02-22 00:41:33 +0000823 distance=QuantumScale*(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
824 channel,q));
825 channel_distortion[i]+=distance*distance;
826 channel_distortion[CompositePixelChannel]+=distance*distance;
cristy3fc482f2011-09-23 00:43:35 +0000827 }
cristyed231572011-07-14 02:18:59 +0000828 p+=GetPixelChannels(image);
829 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +0000830 }
cristyb5d5f722009-11-04 03:03:49 +0000831#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000832 #pragma omp critical (MagickCore_GetMeanSquaredError)
cristy3ed852e2009-09-05 21:47:34 +0000833#endif
cristy3fc482f2011-09-23 00:43:35 +0000834 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000835 distortion[i]+=channel_distortion[i];
836 }
837 reconstruct_view=DestroyCacheView(reconstruct_view);
838 image_view=DestroyCacheView(image_view);
cristy3fc482f2011-09-23 00:43:35 +0000839 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +0000840 distortion[i]/=((double) image->columns*image->rows);
cristy5f95f4f2011-10-23 01:01:01 +0000841 distortion[CompositePixelChannel]/=GetImageChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000842 return(status);
843}
844
cristy3cc758f2010-11-27 01:33:49 +0000845static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
cristy8a9106f2011-07-05 14:39:26 +0000846 const Image *image,const Image *reconstruct_image,double *distortion,
847 ExceptionInfo *exception)
cristy4c929a72010-11-24 18:54:42 +0000848{
cristy9f48ca62010-11-25 03:06:31 +0000849#define SimilarityImageTag "Similarity/Image"
850
cristy4c929a72010-11-24 18:54:42 +0000851 CacheView
852 *image_view,
853 *reconstruct_view;
854
cristy9f48ca62010-11-25 03:06:31 +0000855 ChannelStatistics
cristy9f48ca62010-11-25 03:06:31 +0000856 *image_statistics,
857 *reconstruct_statistics;
858
cristydb5c82c2013-02-22 00:41:33 +0000859 double
860 area;
861
cristy4c929a72010-11-24 18:54:42 +0000862 MagickBooleanType
863 status;
864
cristy18a41362010-11-27 15:56:18 +0000865 MagickOffsetType
866 progress;
867
cristy4c929a72010-11-24 18:54:42 +0000868 register ssize_t
869 i;
870
cristy3cc758f2010-11-27 01:33:49 +0000871 ssize_t
872 y;
873
cristy34d6fdc2010-11-26 19:06:08 +0000874 /*
cristy18a41362010-11-27 15:56:18 +0000875 Normalize to account for variation due to lighting and exposure condition.
cristy34d6fdc2010-11-26 19:06:08 +0000876 */
cristyd42d9952011-07-08 14:21:50 +0000877 image_statistics=GetImageStatistics(image,exception);
878 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
cristye287bba2013-09-02 20:04:42 +0000879 if ((image_statistics == (ChannelStatistics *) NULL) ||
880 (reconstruct_statistics == (ChannelStatistics *) NULL))
881 {
882 if (image_statistics != (ChannelStatistics *) NULL)
883 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
884 image_statistics);
885 if (reconstruct_statistics != (ChannelStatistics *) NULL)
886 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
887 reconstruct_statistics);
888 return(MagickFalse);
889 }
cristy4c929a72010-11-24 18:54:42 +0000890 status=MagickTrue;
cristy9f48ca62010-11-25 03:06:31 +0000891 progress=0;
cristy3fc482f2011-09-23 00:43:35 +0000892 for (i=0; i <= MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000893 distortion[i]=0.0;
cristy2cb55d52013-07-10 22:20:51 +0000894 area=1.0/((double) image->columns*image->rows);
cristy46ff2672012-12-14 15:32:26 +0000895 image_view=AcquireVirtualCacheView(image,exception);
896 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristy4c929a72010-11-24 18:54:42 +0000897 for (y=0; y < (ssize_t) image->rows; y++)
898 {
cristy4c08aed2011-07-01 19:47:50 +0000899 register const Quantum
cristy4c929a72010-11-24 18:54:42 +0000900 *restrict p,
901 *restrict q;
902
903 register ssize_t
cristy4c929a72010-11-24 18:54:42 +0000904 x;
905
906 if (status == MagickFalse)
907 continue;
908 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy34d6fdc2010-11-26 19:06:08 +0000909 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
910 1,exception);
cristy3fc482f2011-09-23 00:43:35 +0000911 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy4c929a72010-11-24 18:54:42 +0000912 {
913 status=MagickFalse;
914 continue;
915 }
cristy4c929a72010-11-24 18:54:42 +0000916 for (x=0; x < (ssize_t) image->columns; x++)
917 {
cristydb5c82c2013-02-22 00:41:33 +0000918 double
919 Da,
920 Sa;
921
cristy3fc482f2011-09-23 00:43:35 +0000922 register ssize_t
923 i;
924
cristy883fde12013-04-08 00:50:13 +0000925 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000926 {
927 p+=GetPixelChannels(image);
928 q+=GetPixelChannels(reconstruct_image);
929 continue;
930 }
cristydb5c82c2013-02-22 00:41:33 +0000931 Sa=QuantumScale*GetPixelAlpha(image,p);
932 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +0000933 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
934 {
cristy5a23c552013-02-13 14:34:28 +0000935 PixelChannel channel=GetPixelChannelChannel(image,i);
936 PixelTrait traits=GetPixelChannelTraits(image,channel);
937 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
938 channel);
cristy3fc482f2011-09-23 00:43:35 +0000939 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +0000940 (reconstruct_traits == UndefinedPixelTrait) ||
941 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +0000942 continue;
cristydb5c82c2013-02-22 00:41:33 +0000943 distortion[i]+=area*QuantumScale*(Sa*p[i]-image_statistics[i].mean)*
944 (Da*GetPixelChannel(reconstruct_image,channel,q)-
cristy0beccfa2011-09-25 20:47:53 +0000945 reconstruct_statistics[channel].mean);
cristy3fc482f2011-09-23 00:43:35 +0000946 }
cristyed231572011-07-14 02:18:59 +0000947 p+=GetPixelChannels(image);
cristy10a6c612012-01-29 21:41:05 +0000948 q+=GetPixelChannels(reconstruct_image);
cristy4c929a72010-11-24 18:54:42 +0000949 }
cristy9f48ca62010-11-25 03:06:31 +0000950 if (image->progress_monitor != (MagickProgressMonitor) NULL)
951 {
952 MagickBooleanType
953 proceed;
954
cristy9f48ca62010-11-25 03:06:31 +0000955 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
956 image->rows);
957 if (proceed == MagickFalse)
958 status=MagickFalse;
959 }
cristy4c929a72010-11-24 18:54:42 +0000960 }
961 reconstruct_view=DestroyCacheView(reconstruct_view);
962 image_view=DestroyCacheView(image_view);
cristy34d6fdc2010-11-26 19:06:08 +0000963 /*
964 Divide by the standard deviation.
965 */
cristy5f95f4f2011-10-23 01:01:01 +0000966 distortion[CompositePixelChannel]=0.0;
cristy3fc482f2011-09-23 00:43:35 +0000967 for (i=0; i < MaxPixelChannels; i++)
cristy34d6fdc2010-11-26 19:06:08 +0000968 {
cristya19f1d72012-08-07 18:24:38 +0000969 double
cristy18a41362010-11-27 15:56:18 +0000970 gamma;
cristy34d6fdc2010-11-26 19:06:08 +0000971
cristy5a23c552013-02-13 14:34:28 +0000972 PixelChannel channel=GetPixelChannelChannel(image,i);
cristy18a41362010-11-27 15:56:18 +0000973 gamma=image_statistics[i].standard_deviation*
cristy3fc482f2011-09-23 00:43:35 +0000974 reconstruct_statistics[channel].standard_deviation;
cristy3e3ec3a2012-11-03 23:11:06 +0000975 gamma=PerceptibleReciprocal(gamma);
cristy18a41362010-11-27 15:56:18 +0000976 distortion[i]=QuantumRange*gamma*distortion[i];
cristy5f95f4f2011-10-23 01:01:01 +0000977 distortion[CompositePixelChannel]+=distortion[i]*distortion[i];
cristy34d6fdc2010-11-26 19:06:08 +0000978 }
cristy5f95f4f2011-10-23 01:01:01 +0000979 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/
cristy49dd6a02011-09-24 23:08:01 +0000980 GetImageChannels(image));
cristy34d6fdc2010-11-26 19:06:08 +0000981 /*
982 Free resources.
983 */
cristy9f48ca62010-11-25 03:06:31 +0000984 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
985 reconstruct_statistics);
986 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
987 image_statistics);
cristy4c929a72010-11-24 18:54:42 +0000988 return(status);
989}
990
cristy3cc758f2010-11-27 01:33:49 +0000991static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +0000992 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000993{
cristyc4c8d132010-01-07 01:58:38 +0000994 CacheView
995 *image_view,
996 *reconstruct_view;
997
cristy3ed852e2009-09-05 21:47:34 +0000998 MagickBooleanType
999 status;
1000
cristy9d314ff2011-03-09 01:30:28 +00001001 ssize_t
1002 y;
1003
cristy3ed852e2009-09-05 21:47:34 +00001004 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +00001005 image_view=AcquireVirtualCacheView(image,exception);
1006 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001007#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001008 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +00001009 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001010#endif
cristybb503372010-05-27 20:51:26 +00001011 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001012 {
1013 double
cristy3fc482f2011-09-23 00:43:35 +00001014 channel_distortion[MaxPixelChannels+1];
cristy3ed852e2009-09-05 21:47:34 +00001015
cristy4c08aed2011-07-01 19:47:50 +00001016 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001017 *restrict p,
1018 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001019
cristybb503372010-05-27 20:51:26 +00001020 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001021 i,
1022 x;
1023
1024 if (status == MagickFalse)
1025 continue;
1026 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy10a6c612012-01-29 21:41:05 +00001027 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1028 1,exception);
cristy3fc482f2011-09-23 00:43:35 +00001029 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001030 {
1031 status=MagickFalse;
1032 continue;
1033 }
cristy3ed852e2009-09-05 21:47:34 +00001034 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
cristybb503372010-05-27 20:51:26 +00001035 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001036 {
cristydb5c82c2013-02-22 00:41:33 +00001037 double
1038 Da,
1039 Sa;
1040
cristy3fc482f2011-09-23 00:43:35 +00001041 register ssize_t
1042 i;
cristy3ed852e2009-09-05 21:47:34 +00001043
cristy883fde12013-04-08 00:50:13 +00001044 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +00001045 {
1046 p+=GetPixelChannels(image);
1047 q+=GetPixelChannels(reconstruct_image);
1048 continue;
1049 }
cristydb5c82c2013-02-22 00:41:33 +00001050 Sa=QuantumScale*GetPixelAlpha(image,p);
1051 Da=QuantumScale*GetPixelAlpha(reconstruct_image,q);
cristy3fc482f2011-09-23 00:43:35 +00001052 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1053 {
cristya19f1d72012-08-07 18:24:38 +00001054 double
cristy3fc482f2011-09-23 00:43:35 +00001055 distance;
1056
cristy5a23c552013-02-13 14:34:28 +00001057 PixelChannel channel=GetPixelChannelChannel(image,i);
1058 PixelTrait traits=GetPixelChannelTraits(image,channel);
1059 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1060 channel);
cristy3fc482f2011-09-23 00:43:35 +00001061 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001062 (reconstruct_traits == UndefinedPixelTrait) ||
1063 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001064 continue;
cristydb5c82c2013-02-22 00:41:33 +00001065 distance=QuantumScale*fabs(Sa*p[i]-Da*GetPixelChannel(reconstruct_image,
1066 channel,q));
cristy25a5f2f2011-09-24 14:09:43 +00001067 if (distance > channel_distortion[i])
cristy3fc482f2011-09-23 00:43:35 +00001068 channel_distortion[i]=distance;
cristy5f95f4f2011-10-23 01:01:01 +00001069 if (distance > channel_distortion[CompositePixelChannel])
1070 channel_distortion[CompositePixelChannel]=distance;
cristy3fc482f2011-09-23 00:43:35 +00001071 }
cristyed231572011-07-14 02:18:59 +00001072 p+=GetPixelChannels(image);
cristy10a6c612012-01-29 21:41:05 +00001073 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001074 }
cristyb5d5f722009-11-04 03:03:49 +00001075#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001076 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
cristy3ed852e2009-09-05 21:47:34 +00001077#endif
cristy3fc482f2011-09-23 00:43:35 +00001078 for (i=0; i <= MaxPixelChannels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001079 if (channel_distortion[i] > distortion[i])
1080 distortion[i]=channel_distortion[i];
1081 }
1082 reconstruct_view=DestroyCacheView(reconstruct_view);
1083 image_view=DestroyCacheView(image_view);
1084 return(status);
1085}
1086
cristy0633a1f2014-02-01 21:54:58 +00001087static inline double MagickLog10(const double x)
cristyd94b0b32014-01-30 23:19:20 +00001088{
cristy1a895c32014-02-05 18:01:41 +00001089#define Log10Epsilon (1.0e-11)
cristycb9af4a2014-02-05 13:31:55 +00001090
1091 if (fabs(x) < Log10Epsilon)
1092 return(log10(Log10Epsilon));
cristya6f32bf2014-02-02 19:08:58 +00001093 return(log10(fabs(x)));
cristyd94b0b32014-01-30 23:19:20 +00001094}
1095
cristy3ed852e2009-09-05 21:47:34 +00001096static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001097 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001098{
1099 MagickBooleanType
1100 status;
1101
cristy3fc482f2011-09-23 00:43:35 +00001102 register ssize_t
1103 i;
1104
cristy8a9106f2011-07-05 14:39:26 +00001105 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001106 for (i=0; i <= MaxPixelChannels; i++)
cristy0633a1f2014-02-01 21:54:58 +00001107 distortion[i]=20.0*MagickLog10((double) 1.0/sqrt(distortion[i]));
cristy3ed852e2009-09-05 21:47:34 +00001108 return(status);
1109}
1110
cristy03d6f862014-01-08 18:34:48 +00001111static MagickBooleanType GetPerceptualHashDistortion(const Image *image,
1112 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1113{
cristyf8f39d42014-02-22 21:44:38 +00001114 ChannelPerceptualHash
1115 *image_phash,
1116 *reconstruct_phash;
cristyd9244cd2014-01-30 23:09:31 +00001117
cristya4a66012014-02-08 19:39:34 +00001118 ssize_t
1119 channel;
cristybc0adce2014-01-08 23:15:49 +00001120
cristy21b687b2014-01-10 00:17:34 +00001121 /*
cristyf8f39d42014-02-22 21:44:38 +00001122 Compute perceptual hash in the sRGB colorspace.
cristy21b687b2014-01-10 00:17:34 +00001123 */
cristyb3538ec2014-02-22 23:13:34 +00001124 image_phash=GetImagePerceptualHash(image,exception);
cristyf8f39d42014-02-22 21:44:38 +00001125 if (image_phash == (ChannelPerceptualHash *) NULL)
cristybc0adce2014-01-08 23:15:49 +00001126 return(MagickFalse);
cristyb3538ec2014-02-22 23:13:34 +00001127 reconstruct_phash=GetImagePerceptualHash(reconstruct_image,exception);
cristy4d0ca342014-05-01 00:42:09 +00001128 if (reconstruct_phash == (ChannelPerceptualHash *) NULL)
cristy2661ac12014-01-10 14:21:07 +00001129 {
cristyf8f39d42014-02-22 21:44:38 +00001130 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
cristy2661ac12014-01-10 14:21:07 +00001131 return(MagickFalse);
1132 }
cristya4a66012014-02-08 19:39:34 +00001133#if defined(MAGICKCORE_OPENMP_SUPPORT)
1134 #pragma omp parallel for schedule(static,4)
1135#endif
1136 for (channel=0; channel < MaxPixelChannels; channel++)
cristybc0adce2014-01-08 23:15:49 +00001137 {
cristya4a66012014-02-08 19:39:34 +00001138 double
1139 difference;
cristy6bd008f2014-01-30 23:24:06 +00001140
cristya4a66012014-02-08 19:39:34 +00001141 register ssize_t
1142 i;
1143
1144 difference=0.0;
cristy3ac6aa92014-09-08 11:48:28 +00001145 for (i=0; i < MaximumNumberOfImageMoments; i++)
cristybc0adce2014-01-08 23:15:49 +00001146 {
cristya4a66012014-02-08 19:39:34 +00001147 double
1148 alpha,
1149 beta;
1150
cristyc0187622014-09-06 00:45:58 +00001151 alpha=image_phash[channel].srgb_hu_phash[i];
1152 beta=reconstruct_phash[channel].srgb_hu_phash[i];
cristya4a66012014-02-08 19:39:34 +00001153 difference+=(beta-alpha)*(beta-alpha);
cristybc0adce2014-01-08 23:15:49 +00001154 }
cristya4a66012014-02-08 19:39:34 +00001155 distortion[channel]+=difference;
1156#if defined(MAGICKCORE_OPENMP_SUPPORT)
1157 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1158#endif
1159 distortion[CompositePixelChannel]+=difference;
cristybc0adce2014-01-08 23:15:49 +00001160 }
cristy21b687b2014-01-10 00:17:34 +00001161 /*
1162 Compute perceptual hash in the HCLP colorspace.
1163 */
cristya4a66012014-02-08 19:39:34 +00001164#if defined(MAGICKCORE_OPENMP_SUPPORT)
1165 #pragma omp parallel for schedule(static,4)
1166#endif
1167 for (channel=0; channel < MaxPixelChannels; channel++)
cristy21b687b2014-01-10 00:17:34 +00001168 {
cristya4a66012014-02-08 19:39:34 +00001169 double
1170 difference;
cristy6bd008f2014-01-30 23:24:06 +00001171
cristya4a66012014-02-08 19:39:34 +00001172 register ssize_t
1173 i;
1174
1175 difference=0.0;
cristy3ac6aa92014-09-08 11:48:28 +00001176 for (i=0; i < MaximumNumberOfImageMoments; i++)
cristy21b687b2014-01-10 00:17:34 +00001177 {
cristya4a66012014-02-08 19:39:34 +00001178 double
1179 alpha,
1180 beta;
1181
cristyc0187622014-09-06 00:45:58 +00001182 alpha=image_phash[channel].hclp_hu_phash[i];
1183 beta=reconstruct_phash[channel].hclp_hu_phash[i];
cristya4a66012014-02-08 19:39:34 +00001184 difference+=(beta-alpha)*(beta-alpha);
cristy21b687b2014-01-10 00:17:34 +00001185 }
cristya4a66012014-02-08 19:39:34 +00001186 distortion[channel]+=difference;
1187#if defined(MAGICKCORE_OPENMP_SUPPORT)
1188 #pragma omp critical (MagickCore_GetPerceptualHashDistortion)
1189#endif
1190 distortion[CompositePixelChannel]+=difference;
cristy21b687b2014-01-10 00:17:34 +00001191 }
cristyf8f39d42014-02-22 21:44:38 +00001192 /*
1193 Free resources.
1194 */
1195 reconstruct_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1196 reconstruct_phash);
1197 image_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(image_phash);
cristy03d6f862014-01-08 18:34:48 +00001198 return(MagickTrue);
1199}
1200
cristy3cc758f2010-11-27 01:33:49 +00001201static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
cristy8a9106f2011-07-05 14:39:26 +00001202 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001203{
1204 MagickBooleanType
1205 status;
1206
cristy3fc482f2011-09-23 00:43:35 +00001207 register ssize_t
1208 i;
1209
cristy8a9106f2011-07-05 14:39:26 +00001210 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
cristy3fc482f2011-09-23 00:43:35 +00001211 for (i=0; i <= MaxPixelChannels; i++)
1212 distortion[i]=sqrt(distortion[i]);
cristy3ed852e2009-09-05 21:47:34 +00001213 return(status);
1214}
1215
cristy8a9106f2011-07-05 14:39:26 +00001216MagickExport MagickBooleanType GetImageDistortion(Image *image,
1217 const Image *reconstruct_image,const MetricType metric,double *distortion,
1218 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001219{
1220 double
1221 *channel_distortion;
1222
1223 MagickBooleanType
1224 status;
1225
1226 size_t
1227 length;
1228
1229 assert(image != (Image *) NULL);
1230 assert(image->signature == MagickSignature);
1231 if (image->debug != MagickFalse)
1232 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1233 assert(reconstruct_image != (const Image *) NULL);
1234 assert(reconstruct_image->signature == MagickSignature);
1235 assert(distortion != (double *) NULL);
1236 *distortion=0.0;
1237 if (image->debug != MagickFalse)
1238 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8b520b42014-01-30 00:58:45 +00001239 if (metric != PerceptualHashErrorMetric)
cristy73626632014-07-19 20:52:21 +00001240 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1241 ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001242 /*
1243 Get image distortion.
1244 */
cristy3fc482f2011-09-23 00:43:35 +00001245 length=MaxPixelChannels+1;
cristy3ed852e2009-09-05 21:47:34 +00001246 channel_distortion=(double *) AcquireQuantumMemory(length,
1247 sizeof(*channel_distortion));
1248 if (channel_distortion == (double *) NULL)
1249 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1250 (void) ResetMagickMemory(channel_distortion,0,length*
1251 sizeof(*channel_distortion));
1252 switch (metric)
1253 {
1254 case AbsoluteErrorMetric:
1255 {
cristy8a9106f2011-07-05 14:39:26 +00001256 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1257 exception);
cristy3ed852e2009-09-05 21:47:34 +00001258 break;
1259 }
cristy343eee92010-12-11 02:17:57 +00001260 case FuzzErrorMetric:
1261 {
cristy8a9106f2011-07-05 14:39:26 +00001262 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1263 exception);
cristy343eee92010-12-11 02:17:57 +00001264 break;
1265 }
cristy3ed852e2009-09-05 21:47:34 +00001266 case MeanAbsoluteErrorMetric:
1267 {
cristy8a9106f2011-07-05 14:39:26 +00001268 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001269 channel_distortion,exception);
1270 break;
1271 }
cristy03fa69f2014-01-08 18:36:49 +00001272 case MeanErrorPerPixelErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001273 {
cristy8a9106f2011-07-05 14:39:26 +00001274 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1275 exception);
cristy3ed852e2009-09-05 21:47:34 +00001276 break;
1277 }
1278 case MeanSquaredErrorMetric:
1279 {
cristy8a9106f2011-07-05 14:39:26 +00001280 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001281 channel_distortion,exception);
1282 break;
1283 }
cristy4c929a72010-11-24 18:54:42 +00001284 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001285 default:
cristy4c929a72010-11-24 18:54:42 +00001286 {
cristy3cc758f2010-11-27 01:33:49 +00001287 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001288 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001289 break;
1290 }
cristy3ed852e2009-09-05 21:47:34 +00001291 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001292 {
cristy8a9106f2011-07-05 14:39:26 +00001293 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001294 channel_distortion,exception);
1295 break;
1296 }
cristy03d6f862014-01-08 18:34:48 +00001297 case PeakSignalToNoiseRatioErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001298 {
cristy8a9106f2011-07-05 14:39:26 +00001299 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001300 channel_distortion,exception);
1301 break;
1302 }
cristy03d6f862014-01-08 18:34:48 +00001303 case PerceptualHashErrorMetric:
1304 {
1305 status=GetPerceptualHashDistortion(image,reconstruct_image,
1306 channel_distortion,exception);
1307 break;
1308 }
cristy3ed852e2009-09-05 21:47:34 +00001309 case RootMeanSquaredErrorMetric:
1310 {
cristy8a9106f2011-07-05 14:39:26 +00001311 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001312 channel_distortion,exception);
1313 break;
1314 }
1315 }
cristy5f95f4f2011-10-23 01:01:01 +00001316 *distortion=channel_distortion[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001317 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
cristy65aa9342013-09-05 12:04:27 +00001318 (void) FormatImageProperty(image,"distortion","%.*g",GetMagickPrecision(),
cristye863c052013-09-04 11:31:30 +00001319 *distortion);
cristy3ed852e2009-09-05 21:47:34 +00001320 return(status);
1321}
1322
1323/*
1324%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1325% %
1326% %
1327% %
cristy8a9106f2011-07-05 14:39:26 +00001328% 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 +00001329% %
1330% %
1331% %
1332%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1333%
cristy44097f52012-12-16 19:56:20 +00001334% GetImageDistortions() compares the pixel channels of an image to a
cristy3ed852e2009-09-05 21:47:34 +00001335% reconstructed image and returns the specified distortion metric for each
1336% channel.
1337%
cristy44097f52012-12-16 19:56:20 +00001338% The format of the GetImageDistortions method is:
cristy3ed852e2009-09-05 21:47:34 +00001339%
cristy8a9106f2011-07-05 14:39:26 +00001340% double *GetImageDistortions(const Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001341% const Image *reconstruct_image,const MetricType metric,
1342% ExceptionInfo *exception)
1343%
1344% A description of each parameter follows:
1345%
1346% o image: the image.
1347%
1348% o reconstruct_image: the reconstruct image.
1349%
1350% o metric: the metric.
1351%
1352% o exception: return any errors or warnings in this structure.
1353%
1354*/
cristy8a9106f2011-07-05 14:39:26 +00001355MagickExport double *GetImageDistortions(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00001356 const Image *reconstruct_image,const MetricType metric,
1357 ExceptionInfo *exception)
1358{
1359 double
1360 *channel_distortion;
1361
1362 MagickBooleanType
1363 status;
1364
1365 size_t
1366 length;
1367
1368 assert(image != (Image *) NULL);
1369 assert(image->signature == MagickSignature);
1370 if (image->debug != MagickFalse)
1371 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1372 assert(reconstruct_image != (const Image *) NULL);
1373 assert(reconstruct_image->signature == MagickSignature);
1374 if (image->debug != MagickFalse)
1375 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8b520b42014-01-30 00:58:45 +00001376 if (metric != PerceptualHashErrorMetric)
cristy73626632014-07-19 20:52:21 +00001377 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
cristy8b520b42014-01-30 00:58:45 +00001378 {
1379 (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
cristy73626632014-07-19 20:52:21 +00001380 "ImageMorphologyDiffers","`%s'",image->filename);
cristy8b520b42014-01-30 00:58:45 +00001381 return((double *) NULL);
1382 }
cristy3ed852e2009-09-05 21:47:34 +00001383 /*
1384 Get image distortion.
1385 */
cristy3fc482f2011-09-23 00:43:35 +00001386 length=MaxPixelChannels+1UL;
cristy3ed852e2009-09-05 21:47:34 +00001387 channel_distortion=(double *) AcquireQuantumMemory(length,
1388 sizeof(*channel_distortion));
1389 if (channel_distortion == (double *) NULL)
1390 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1391 (void) ResetMagickMemory(channel_distortion,0,length*
1392 sizeof(*channel_distortion));
cristyda16f162011-02-19 23:52:17 +00001393 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +00001394 switch (metric)
1395 {
1396 case AbsoluteErrorMetric:
1397 {
cristy8a9106f2011-07-05 14:39:26 +00001398 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1399 exception);
cristy3ed852e2009-09-05 21:47:34 +00001400 break;
1401 }
cristy343eee92010-12-11 02:17:57 +00001402 case FuzzErrorMetric:
1403 {
cristy8a9106f2011-07-05 14:39:26 +00001404 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1405 exception);
cristy343eee92010-12-11 02:17:57 +00001406 break;
1407 }
cristy3ed852e2009-09-05 21:47:34 +00001408 case MeanAbsoluteErrorMetric:
1409 {
cristy8a9106f2011-07-05 14:39:26 +00001410 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001411 channel_distortion,exception);
1412 break;
1413 }
cristy03fa69f2014-01-08 18:36:49 +00001414 case MeanErrorPerPixelErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001415 {
cristy8a9106f2011-07-05 14:39:26 +00001416 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1417 exception);
cristy3ed852e2009-09-05 21:47:34 +00001418 break;
1419 }
1420 case MeanSquaredErrorMetric:
1421 {
cristy8a9106f2011-07-05 14:39:26 +00001422 status=GetMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001423 channel_distortion,exception);
1424 break;
1425 }
cristy4c929a72010-11-24 18:54:42 +00001426 case NormalizedCrossCorrelationErrorMetric:
cristy3cc758f2010-11-27 01:33:49 +00001427 default:
cristy4c929a72010-11-24 18:54:42 +00001428 {
cristy3cc758f2010-11-27 01:33:49 +00001429 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
cristy8a9106f2011-07-05 14:39:26 +00001430 channel_distortion,exception);
cristy4c929a72010-11-24 18:54:42 +00001431 break;
1432 }
cristy3ed852e2009-09-05 21:47:34 +00001433 case PeakAbsoluteErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001434 {
cristy8a9106f2011-07-05 14:39:26 +00001435 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001436 channel_distortion,exception);
1437 break;
1438 }
cristy03d6f862014-01-08 18:34:48 +00001439 case PeakSignalToNoiseRatioErrorMetric:
cristy3ed852e2009-09-05 21:47:34 +00001440 {
cristy8a9106f2011-07-05 14:39:26 +00001441 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001442 channel_distortion,exception);
1443 break;
1444 }
cristy03d6f862014-01-08 18:34:48 +00001445 case PerceptualHashErrorMetric:
1446 {
1447 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1448 channel_distortion,exception);
1449 break;
1450 }
cristy3ed852e2009-09-05 21:47:34 +00001451 case RootMeanSquaredErrorMetric:
1452 {
cristy8a9106f2011-07-05 14:39:26 +00001453 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
cristy3ed852e2009-09-05 21:47:34 +00001454 channel_distortion,exception);
1455 break;
1456 }
1457 }
cristyda16f162011-02-19 23:52:17 +00001458 if (status == MagickFalse)
1459 {
1460 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1461 return((double *) NULL);
1462 }
cristy3ed852e2009-09-05 21:47:34 +00001463 return(channel_distortion);
1464}
1465
1466/*
1467%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1468% %
1469% %
1470% %
1471% I s I m a g e s E q u a l %
1472% %
1473% %
1474% %
1475%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1476%
1477% IsImagesEqual() measures the difference between colors at each pixel
1478% location of two images. A value other than 0 means the colors match
1479% exactly. Otherwise an error measure is computed by summing over all
1480% pixels in an image the distance squared in RGB space between each image
1481% pixel and its corresponding pixel in the reconstruct image. The error
1482% measure is assigned to these image members:
1483%
1484% o mean_error_per_pixel: The mean error for any single pixel in
1485% the image.
1486%
1487% o normalized_mean_error: The normalized mean quantization error for
1488% any single pixel in the image. This distance measure is normalized to
1489% a range between 0 and 1. It is independent of the range of red, green,
1490% and blue values in the image.
1491%
1492% o normalized_maximum_error: The normalized maximum quantization
1493% error for any single pixel in the image. This distance measure is
1494% normalized to a range between 0 and 1. It is independent of the range
1495% of red, green, and blue values in your image.
1496%
1497% A small normalized mean square error, accessed as
1498% image->normalized_mean_error, suggests the images are very similar in
1499% spatial layout and color.
1500%
1501% The format of the IsImagesEqual method is:
1502%
1503% MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001504% const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001505%
1506% A description of each parameter follows.
1507%
1508% o image: the image.
1509%
1510% o reconstruct_image: the reconstruct image.
1511%
cristy018f07f2011-09-04 21:15:19 +00001512% o exception: return any errors or warnings in this structure.
1513%
cristy3ed852e2009-09-05 21:47:34 +00001514*/
1515MagickExport MagickBooleanType IsImagesEqual(Image *image,
cristy018f07f2011-09-04 21:15:19 +00001516 const Image *reconstruct_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001517{
cristyc4c8d132010-01-07 01:58:38 +00001518 CacheView
1519 *image_view,
1520 *reconstruct_view;
1521
cristy3ed852e2009-09-05 21:47:34 +00001522 MagickBooleanType
1523 status;
1524
cristya19f1d72012-08-07 18:24:38 +00001525 double
cristy3ed852e2009-09-05 21:47:34 +00001526 area,
1527 maximum_error,
1528 mean_error,
1529 mean_error_per_pixel;
1530
cristy9d314ff2011-03-09 01:30:28 +00001531 ssize_t
1532 y;
1533
cristy3ed852e2009-09-05 21:47:34 +00001534 assert(image != (Image *) NULL);
1535 assert(image->signature == MagickSignature);
1536 assert(reconstruct_image != (const Image *) NULL);
1537 assert(reconstruct_image->signature == MagickSignature);
cristy73626632014-07-19 20:52:21 +00001538 if (ValidateImageMorphology(image,reconstruct_image) == MagickFalse)
1539 ThrowBinaryException(ImageError,"ImageMorphologyDiffers",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001540 area=0.0;
1541 maximum_error=0.0;
1542 mean_error_per_pixel=0.0;
1543 mean_error=0.0;
cristy46ff2672012-12-14 15:32:26 +00001544 image_view=AcquireVirtualCacheView(image,exception);
1545 reconstruct_view=AcquireVirtualCacheView(reconstruct_image,exception);
cristybb503372010-05-27 20:51:26 +00001546 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001547 {
cristy4c08aed2011-07-01 19:47:50 +00001548 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001549 *restrict p,
1550 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001551
cristybb503372010-05-27 20:51:26 +00001552 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001553 x;
1554
1555 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1556 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1557 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001558 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001559 break;
cristybb503372010-05-27 20:51:26 +00001560 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001561 {
cristyd5c15f92011-09-23 00:58:33 +00001562 register ssize_t
1563 i;
cristy3ed852e2009-09-05 21:47:34 +00001564
cristy883fde12013-04-08 00:50:13 +00001565 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +00001566 {
1567 p+=GetPixelChannels(image);
1568 q+=GetPixelChannels(reconstruct_image);
1569 continue;
1570 }
cristyd5c15f92011-09-23 00:58:33 +00001571 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1572 {
cristya19f1d72012-08-07 18:24:38 +00001573 double
cristyd5c15f92011-09-23 00:58:33 +00001574 distance;
1575
cristy5a23c552013-02-13 14:34:28 +00001576 PixelChannel channel=GetPixelChannelChannel(image,i);
1577 PixelTrait traits=GetPixelChannelTraits(image,channel);
1578 PixelTrait reconstruct_traits=GetPixelChannelTraits(reconstruct_image,
1579 channel);
cristyd5c15f92011-09-23 00:58:33 +00001580 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001581 (reconstruct_traits == UndefinedPixelTrait) ||
1582 ((reconstruct_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001583 continue;
cristya19f1d72012-08-07 18:24:38 +00001584 distance=fabs(p[i]-(double) GetPixelChannel(reconstruct_image,
cristy0beccfa2011-09-25 20:47:53 +00001585 channel,q));
dirk86d34132014-08-31 10:25:26 +00001586 if (distance >= MagickEpsilon)
1587 {
1588 mean_error_per_pixel+=distance;
1589 mean_error+=distance*distance;
1590 if (distance > maximum_error)
1591 maximum_error=distance;
1592 }
cristyd5c15f92011-09-23 00:58:33 +00001593 area++;
1594 }
cristyed231572011-07-14 02:18:59 +00001595 p+=GetPixelChannels(image);
1596 q+=GetPixelChannels(reconstruct_image);
cristy3ed852e2009-09-05 21:47:34 +00001597 }
1598 }
1599 reconstruct_view=DestroyCacheView(reconstruct_view);
1600 image_view=DestroyCacheView(image_view);
1601 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1602 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1603 mean_error/area);
1604 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1605 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1606 return(status);
1607}
1608
1609/*
1610%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1611% %
1612% %
1613% %
1614% S i m i l a r i t y I m a g e %
1615% %
1616% %
1617% %
1618%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1619%
1620% SimilarityImage() compares the reference image of the image and returns the
1621% best match offset. In addition, it returns a similarity image such that an
1622% exact match location is completely white and if none of the pixels match,
1623% black, otherwise some gray level in-between.
1624%
1625% The format of the SimilarityImageImage method is:
1626%
1627% Image *SimilarityImage(const Image *image,const Image *reference,
cristy62e52182013-03-15 14:26:17 +00001628% const MetricType metric,const double similarity_threshold,
1629% RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001630%
1631% A description of each parameter follows:
1632%
1633% o image: the image.
1634%
1635% o reference: find an area of the image that closely resembles this image.
1636%
cristy09136812011-10-18 15:24:30 +00001637% o metric: the metric.
1638%
cristy62e52182013-03-15 14:26:17 +00001639% o similarity_threshold: minimum distortion for (sub)image match.
1640%
1641% o offset: the best match offset of the reference image within the image.
cristy3ed852e2009-09-05 21:47:34 +00001642%
1643% o similarity: the computed similarity between the images.
1644%
1645% o exception: return any errors or warnings in this structure.
1646%
1647*/
1648
1649static double GetSimilarityMetric(const Image *image,const Image *reference,
cristy09136812011-10-18 15:24:30 +00001650 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset,
1651 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001652{
1653 double
cristy3cc758f2010-11-27 01:33:49 +00001654 distortion;
cristy3ed852e2009-09-05 21:47:34 +00001655
cristy713ff212010-11-26 21:56:11 +00001656 Image
1657 *similarity_image;
cristy3ed852e2009-09-05 21:47:34 +00001658
cristy09136812011-10-18 15:24:30 +00001659 MagickBooleanType
1660 status;
1661
cristy713ff212010-11-26 21:56:11 +00001662 RectangleInfo
1663 geometry;
cristy3ed852e2009-09-05 21:47:34 +00001664
cristy713ff212010-11-26 21:56:11 +00001665 SetGeometry(reference,&geometry);
1666 geometry.x=x_offset;
1667 geometry.y=y_offset;
1668 similarity_image=CropImage(image,&geometry,exception);
1669 if (similarity_image == (Image *) NULL)
1670 return(0.0);
cristy09136812011-10-18 15:24:30 +00001671 distortion=0.0;
1672 status=GetImageDistortion(similarity_image,reference,metric,&distortion,
cristy3cc758f2010-11-27 01:33:49 +00001673 exception);
cristy713ff212010-11-26 21:56:11 +00001674 similarity_image=DestroyImage(similarity_image);
cristy09136812011-10-18 15:24:30 +00001675 if (status == MagickFalse)
1676 return(0.0);
cristy3cc758f2010-11-27 01:33:49 +00001677 return(distortion);
cristy3ed852e2009-09-05 21:47:34 +00001678}
1679
1680MagickExport Image *SimilarityImage(Image *image,const Image *reference,
cristy62e52182013-03-15 14:26:17 +00001681 const MetricType metric,const double similarity_threshold,
1682 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001683{
1684#define SimilarityImageTag "Similarity/Image"
1685
cristyc4c8d132010-01-07 01:58:38 +00001686 CacheView
1687 *similarity_view;
1688
cristy3ed852e2009-09-05 21:47:34 +00001689 Image
1690 *similarity_image;
1691
1692 MagickBooleanType
1693 status;
1694
cristybb503372010-05-27 20:51:26 +00001695 MagickOffsetType
1696 progress;
1697
1698 ssize_t
1699 y;
1700
cristy3ed852e2009-09-05 21:47:34 +00001701 assert(image != (const Image *) NULL);
1702 assert(image->signature == MagickSignature);
1703 if (image->debug != MagickFalse)
1704 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1705 assert(exception != (ExceptionInfo *) NULL);
1706 assert(exception->signature == MagickSignature);
1707 assert(offset != (RectangleInfo *) NULL);
1708 SetGeometry(reference,offset);
cristyfe181a72014-02-02 21:17:43 +00001709 *similarity_metric=MagickMaximumValue;
cristy73626632014-07-19 20:52:21 +00001710 if (ValidateImageMorphology(image,reference) == MagickFalse)
1711 ThrowImageException(ImageError,"ImageMorphologyDiffers");
cristy3ed852e2009-09-05 21:47:34 +00001712 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1713 image->rows-reference->rows+1,MagickTrue,exception);
1714 if (similarity_image == (Image *) NULL)
1715 return((Image *) NULL);
cristyd5c15f92011-09-23 00:58:33 +00001716 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1717 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001718 {
cristy3ed852e2009-09-05 21:47:34 +00001719 similarity_image=DestroyImage(similarity_image);
1720 return((Image *) NULL);
1721 }
cristyb979cea2013-03-01 14:22:42 +00001722 (void) SetImageAlphaChannel(similarity_image,DeactivateAlphaChannel,
1723 exception);
cristy3ed852e2009-09-05 21:47:34 +00001724 /*
1725 Measure similarity of reference image against image.
1726 */
1727 status=MagickTrue;
1728 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001729 similarity_view=AcquireAuthenticCacheView(similarity_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001730#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy70ea54f2013-03-15 18:49:14 +00001731 #pragma omp parallel for schedule(static,4) \
1732 shared(progress,status,similarity_metric) \
cristy5e6b2592012-12-19 14:08:11 +00001733 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001734#endif
cristybb503372010-05-27 20:51:26 +00001735 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
cristy3ed852e2009-09-05 21:47:34 +00001736 {
1737 double
1738 similarity;
1739
cristy4c08aed2011-07-01 19:47:50 +00001740 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001741 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001742
cristy49dd6a02011-09-24 23:08:01 +00001743 register ssize_t
1744 x;
1745
cristy3ed852e2009-09-05 21:47:34 +00001746 if (status == MagickFalse)
1747 continue;
cristy266f58b2013-05-15 11:47:01 +00001748#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy1aadacc2013-04-27 16:31:24 +00001749 #pragma omp flush(similarity_metric)
cristy266f58b2013-05-15 11:47:01 +00001750#endif
cristy24856ba2013-03-15 18:24:00 +00001751 if (*similarity_metric <= similarity_threshold)
1752 continue;
cristy3cc758f2010-11-27 01:33:49 +00001753 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1754 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001755 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001756 {
1757 status=MagickFalse;
1758 continue;
1759 }
cristybb503372010-05-27 20:51:26 +00001760 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
cristy3ed852e2009-09-05 21:47:34 +00001761 {
cristy49dd6a02011-09-24 23:08:01 +00001762 register ssize_t
1763 i;
1764
cristy266f58b2013-05-15 11:47:01 +00001765#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy1aadacc2013-04-27 16:31:24 +00001766 #pragma omp flush(similarity_metric)
cristy266f58b2013-05-15 11:47:01 +00001767#endif
cristy24856ba2013-03-15 18:24:00 +00001768 if (*similarity_metric <= similarity_threshold)
1769 break;
cristy09136812011-10-18 15:24:30 +00001770 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception);
cristyb5d5f722009-11-04 03:03:49 +00001771#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001772 #pragma omp critical (MagickCore_SimilarityImage)
cristy3ed852e2009-09-05 21:47:34 +00001773#endif
1774 if (similarity < *similarity_metric)
1775 {
cristy3ed852e2009-09-05 21:47:34 +00001776 offset->x=x;
1777 offset->y=y;
cristy24856ba2013-03-15 18:24:00 +00001778 *similarity_metric=similarity;
cristy3ed852e2009-09-05 21:47:34 +00001779 }
cristyefdcd372014-02-03 18:40:08 +00001780 if (metric == PerceptualHashErrorMetric)
1781 similarity=MagickMin(0.01*similarity,1.0);
cristy883fde12013-04-08 00:50:13 +00001782 if (GetPixelReadMask(similarity_image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00001783 {
cristyc3a58022013-10-09 23:22:42 +00001784 SetPixelBackgoundColor(similarity_image,q);
cristyc94ba6f2012-01-29 23:19:58 +00001785 q+=GetPixelChannels(similarity_image);
cristy10a6c612012-01-29 21:41:05 +00001786 continue;
1787 }
cristyc94ba6f2012-01-29 23:19:58 +00001788 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++)
cristy49dd6a02011-09-24 23:08:01 +00001789 {
cristy5a23c552013-02-13 14:34:28 +00001790 PixelChannel channel=GetPixelChannelChannel(image,i);
1791 PixelTrait traits=GetPixelChannelTraits(image,channel);
1792 PixelTrait similarity_traits=GetPixelChannelTraits(similarity_image,
1793 channel);
cristy49dd6a02011-09-24 23:08:01 +00001794 if ((traits == UndefinedPixelTrait) ||
cristyd09f8802012-02-04 16:44:10 +00001795 (similarity_traits == UndefinedPixelTrait) ||
1796 ((similarity_traits & UpdatePixelTrait) == 0))
cristy49dd6a02011-09-24 23:08:01 +00001797 continue;
cristy0beccfa2011-09-25 20:47:53 +00001798 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
cristyefdcd372014-02-03 18:40:08 +00001799 QuantumRange*similarity),q);
cristy49dd6a02011-09-24 23:08:01 +00001800 }
cristyed231572011-07-14 02:18:59 +00001801 q+=GetPixelChannels(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001802 }
cristyb979cea2013-03-01 14:22:42 +00001803 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001804 status=MagickFalse;
1805 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1806 {
cristyb979cea2013-03-01 14:22:42 +00001807 MagickBooleanType
1808 proceed;
1809
cristyb5d5f722009-11-04 03:03:49 +00001810#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001811 #pragma omp critical (MagickCore_SimilarityImage)
cristy3ed852e2009-09-05 21:47:34 +00001812#endif
cristyb979cea2013-03-01 14:22:42 +00001813 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1814 image->rows);
1815 if (proceed == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001816 status=MagickFalse;
1817 }
1818 }
1819 similarity_view=DestroyCacheView(similarity_view);
cristy1c2f48d2012-12-14 01:20:55 +00001820 if (status == MagickFalse)
1821 similarity_image=DestroyImage(similarity_image);
cristy3ed852e2009-09-05 21:47:34 +00001822 return(similarity_image);
1823}