|
MagickCore
6.7.5
|
00001 /* 00002 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00003 % % 00004 % % 00005 % % 00006 % CCCC OOO M M PPPP AAA RRRR EEEEE % 00007 % C O O MM MM P P A A R R E % 00008 % C O O M M M PPPP AAAAA RRRR EEE % 00009 % C O O M M P A A R R E % 00010 % CCCC OOO M M P A A R R EEEEE % 00011 % % 00012 % % 00013 % MagickCore Image Comparison Methods % 00014 % % 00015 % Software Design % 00016 % John Cristy % 00017 % December 2003 % 00018 % % 00019 % % 00020 % Copyright 1999-2012 ImageMagick Studio LLC, a non-profit organization % 00021 % dedicated to making software imaging solutions freely available. % 00022 % % 00023 % You may not use this file except in compliance with the License. You may % 00024 % obtain a copy of the License at % 00025 % % 00026 % http://www.imagemagick.org/script/license.php % 00027 % % 00028 % Unless required by applicable law or agreed to in writing, software % 00029 % distributed under the License is distributed on an "AS IS" BASIS, % 00030 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % 00031 % See the License for the specific language governing permissions and % 00032 % limitations under the License. % 00033 % % 00034 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00035 % 00036 % 00037 % 00038 */ 00039 00040 /* 00041 Include declarations. 00042 */ 00043 #include "MagickCore/studio.h" 00044 #include "MagickCore/artifact.h" 00045 #include "MagickCore/cache-view.h" 00046 #include "MagickCore/client.h" 00047 #include "MagickCore/color.h" 00048 #include "MagickCore/color-private.h" 00049 #include "MagickCore/colorspace.h" 00050 #include "MagickCore/colorspace-private.h" 00051 #include "MagickCore/compare.h" 00052 #include "MagickCore/composite-private.h" 00053 #include "MagickCore/constitute.h" 00054 #include "MagickCore/exception-private.h" 00055 #include "MagickCore/geometry.h" 00056 #include "MagickCore/image-private.h" 00057 #include "MagickCore/list.h" 00058 #include "MagickCore/log.h" 00059 #include "MagickCore/memory_.h" 00060 #include "MagickCore/monitor.h" 00061 #include "MagickCore/monitor-private.h" 00062 #include "MagickCore/option.h" 00063 #include "MagickCore/pixel-accessor.h" 00064 #include "MagickCore/resource_.h" 00065 #include "MagickCore/string_.h" 00066 #include "MagickCore/statistic.h" 00067 #include "MagickCore/transform.h" 00068 #include "MagickCore/utility.h" 00069 #include "MagickCore/version.h" 00070 00071 /* 00072 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00073 % % 00074 % % 00075 % % 00076 % C o m p a r e I m a g e % 00077 % % 00078 % % 00079 % % 00080 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00081 % 00082 % CompareImages() compares one or more pixel channels of an image to a 00083 % reconstructed image and returns the difference image. 00084 % 00085 % The format of the CompareImages method is: 00086 % 00087 % Image *CompareImages(const Image *image,const Image *reconstruct_image, 00088 % const MetricType metric,double *distortion,ExceptionInfo *exception) 00089 % 00090 % A description of each parameter follows: 00091 % 00092 % o image: the image. 00093 % 00094 % o reconstruct_image: the reconstruct image. 00095 % 00096 % o metric: the metric. 00097 % 00098 % o distortion: the computed distortion between the images. 00099 % 00100 % o exception: return any errors or warnings in this structure. 00101 % 00102 */ 00103 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image, 00104 const MetricType metric,double *distortion,ExceptionInfo *exception) 00105 { 00106 CacheView 00107 *highlight_view, 00108 *image_view, 00109 *reconstruct_view; 00110 00111 const char 00112 *artifact; 00113 00114 Image 00115 *difference_image, 00116 *highlight_image; 00117 00118 MagickBooleanType 00119 status; 00120 00121 PixelInfo 00122 highlight, 00123 lowlight; 00124 00125 ssize_t 00126 y; 00127 00128 assert(image != (Image *) NULL); 00129 assert(image->signature == MagickSignature); 00130 if (image->debug != MagickFalse) 00131 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 00132 assert(reconstruct_image != (const Image *) NULL); 00133 assert(reconstruct_image->signature == MagickSignature); 00134 assert(distortion != (double *) NULL); 00135 *distortion=0.0; 00136 if (image->debug != MagickFalse) 00137 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 00138 if ((reconstruct_image->columns != image->columns) || 00139 (reconstruct_image->rows != image->rows)) 00140 ThrowImageException(ImageError,"ImageSizeDiffers"); 00141 status=GetImageDistortion(image,reconstruct_image,metric,distortion, 00142 exception); 00143 if (status == MagickFalse) 00144 return((Image *) NULL); 00145 difference_image=CloneImage(image,0,0,MagickTrue,exception); 00146 if (difference_image == (Image *) NULL) 00147 return((Image *) NULL); 00148 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception); 00149 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue, 00150 exception); 00151 if (highlight_image == (Image *) NULL) 00152 { 00153 difference_image=DestroyImage(difference_image); 00154 return((Image *) NULL); 00155 } 00156 status=SetImageStorageClass(highlight_image,DirectClass,exception); 00157 if (status == MagickFalse) 00158 { 00159 difference_image=DestroyImage(difference_image); 00160 highlight_image=DestroyImage(highlight_image); 00161 return((Image *) NULL); 00162 } 00163 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception); 00164 (void) QueryColorCompliance("#f1001e33",AllCompliance,&highlight,exception); 00165 artifact=GetImageArtifact(image,"highlight-color"); 00166 if (artifact != (const char *) NULL) 00167 (void) QueryColorCompliance(artifact,AllCompliance,&highlight,exception); 00168 (void) QueryColorCompliance("#ffffff33",AllCompliance,&lowlight,exception); 00169 artifact=GetImageArtifact(image,"lowlight-color"); 00170 if (artifact != (const char *) NULL) 00171 (void) QueryColorCompliance(artifact,AllCompliance,&lowlight,exception); 00172 /* 00173 Generate difference image. 00174 */ 00175 status=MagickTrue; 00176 image_view=AcquireCacheView(image); 00177 reconstruct_view=AcquireCacheView(reconstruct_image); 00178 highlight_view=AcquireCacheView(highlight_image); 00179 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00180 #pragma omp parallel for schedule(static,4) shared(status) 00181 #endif 00182 for (y=0; y < (ssize_t) image->rows; y++) 00183 { 00184 MagickBooleanType 00185 sync; 00186 00187 register const Quantum 00188 *restrict p, 00189 *restrict q; 00190 00191 register Quantum 00192 *restrict r; 00193 00194 register ssize_t 00195 x; 00196 00197 if (status == MagickFalse) 00198 continue; 00199 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 00200 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns, 00201 1,exception); 00202 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns, 00203 1,exception); 00204 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) || 00205 (r == (Quantum *) NULL)) 00206 { 00207 status=MagickFalse; 00208 continue; 00209 } 00210 for (x=0; x < (ssize_t) image->columns; x++) 00211 { 00212 MagickStatusType 00213 difference; 00214 00215 register ssize_t 00216 i; 00217 00218 if (GetPixelMask(image,p) != 0) 00219 { 00220 SetPixelInfoPixel(highlight_image,&lowlight,r); 00221 p+=GetPixelChannels(image); 00222 q+=GetPixelChannels(reconstruct_image); 00223 r+=GetPixelChannels(highlight_image); 00224 continue; 00225 } 00226 difference=MagickFalse; 00227 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 00228 { 00229 MagickRealType 00230 distance; 00231 00232 PixelChannel 00233 channel; 00234 00235 PixelTrait 00236 reconstruct_traits, 00237 traits; 00238 00239 channel=GetPixelChannelMapChannel(image,i); 00240 traits=GetPixelChannelMapTraits(image,channel); 00241 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel); 00242 if ((traits == UndefinedPixelTrait) || 00243 (reconstruct_traits == UndefinedPixelTrait) || 00244 ((reconstruct_traits & UpdatePixelTrait) == 0)) 00245 continue; 00246 distance=p[i]-(MagickRealType) GetPixelChannel(reconstruct_image, 00247 channel,q); 00248 if (fabs((double) distance) >= MagickEpsilon) 00249 difference=MagickTrue; 00250 } 00251 if (difference == MagickFalse) 00252 SetPixelInfoPixel(highlight_image,&lowlight,r); 00253 else 00254 SetPixelInfoPixel(highlight_image,&highlight,r); 00255 p+=GetPixelChannels(image); 00256 q+=GetPixelChannels(reconstruct_image); 00257 r+=GetPixelChannels(highlight_image); 00258 } 00259 sync=SyncCacheViewAuthenticPixels(highlight_view,exception); 00260 if (sync == MagickFalse) 00261 status=MagickFalse; 00262 } 00263 highlight_view=DestroyCacheView(highlight_view); 00264 reconstruct_view=DestroyCacheView(reconstruct_view); 00265 image_view=DestroyCacheView(image_view); 00266 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0, 00267 exception); 00268 highlight_image=DestroyImage(highlight_image); 00269 if (status == MagickFalse) 00270 difference_image=DestroyImage(difference_image); 00271 return(difference_image); 00272 } 00273 00274 /* 00275 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00276 % % 00277 % % 00278 % % 00279 % G e t I m a g e D i s t o r t i o n % 00280 % % 00281 % % 00282 % % 00283 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00284 % 00285 % GetImageDistortion() compares one or more pixel channels of an image to a 00286 % reconstructed image and returns the specified distortion metric. 00287 % 00288 % The format of the CompareImages method is: 00289 % 00290 % MagickBooleanType GetImageDistortion(const Image *image, 00291 % const Image *reconstruct_image,const MetricType metric, 00292 % double *distortion,ExceptionInfo *exception) 00293 % 00294 % A description of each parameter follows: 00295 % 00296 % o image: the image. 00297 % 00298 % o reconstruct_image: the reconstruct image. 00299 % 00300 % o metric: the metric. 00301 % 00302 % o distortion: the computed distortion between the images. 00303 % 00304 % o exception: return any errors or warnings in this structure. 00305 % 00306 */ 00307 00308 static MagickBooleanType GetAbsoluteDistortion(const Image *image, 00309 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception) 00310 { 00311 CacheView 00312 *image_view, 00313 *reconstruct_view; 00314 00315 MagickBooleanType 00316 status; 00317 00318 ssize_t 00319 y; 00320 00321 /* 00322 Compute the absolute difference in pixels between two images. 00323 */ 00324 status=MagickTrue; 00325 image_view=AcquireCacheView(image); 00326 reconstruct_view=AcquireCacheView(reconstruct_image); 00327 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00328 #pragma omp parallel for schedule(static,4) shared(status) 00329 #endif 00330 for (y=0; y < (ssize_t) image->rows; y++) 00331 { 00332 double 00333 channel_distortion[MaxPixelChannels+1]; 00334 00335 register const Quantum 00336 *restrict p, 00337 *restrict q; 00338 00339 register ssize_t 00340 i, 00341 x; 00342 00343 if (status == MagickFalse) 00344 continue; 00345 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 00346 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns, 00347 1,exception); 00348 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL)) 00349 { 00350 status=MagickFalse; 00351 continue; 00352 } 00353 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion)); 00354 for (x=0; x < (ssize_t) image->columns; x++) 00355 { 00356 MagickBooleanType 00357 difference; 00358 00359 register ssize_t 00360 i; 00361 00362 if (GetPixelMask(image,p) != 0) 00363 { 00364 p+=GetPixelChannels(image); 00365 q+=GetPixelChannels(reconstruct_image); 00366 continue; 00367 } 00368 difference=MagickFalse; 00369 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 00370 { 00371 PixelChannel 00372 channel; 00373 00374 PixelTrait 00375 reconstruct_traits, 00376 traits; 00377 00378 channel=GetPixelChannelMapChannel(image,i); 00379 traits=GetPixelChannelMapTraits(image,channel); 00380 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel); 00381 if ((traits == UndefinedPixelTrait) || 00382 (reconstruct_traits == UndefinedPixelTrait) || 00383 ((reconstruct_traits & UpdatePixelTrait) == 0)) 00384 continue; 00385 if (p[i] != GetPixelChannel(reconstruct_image,channel,q)) 00386 difference=MagickTrue; 00387 } 00388 if (difference != MagickFalse) 00389 { 00390 channel_distortion[i]++; 00391 channel_distortion[CompositePixelChannel]++; 00392 } 00393 p+=GetPixelChannels(image); 00394 q+=GetPixelChannels(reconstruct_image); 00395 } 00396 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00397 #pragma omp critical (MagickCore_GetAbsoluteError) 00398 #endif 00399 for (i=0; i <= MaxPixelChannels; i++) 00400 distortion[i]+=channel_distortion[i]; 00401 } 00402 reconstruct_view=DestroyCacheView(reconstruct_view); 00403 image_view=DestroyCacheView(image_view); 00404 return(status); 00405 } 00406 00407 static size_t GetImageChannels(const Image *image) 00408 { 00409 register ssize_t 00410 i; 00411 00412 size_t 00413 channels; 00414 00415 channels=0; 00416 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 00417 { 00418 PixelChannel 00419 channel; 00420 00421 PixelTrait 00422 traits; 00423 00424 channel=GetPixelChannelMapChannel(image,i); 00425 traits=GetPixelChannelMapTraits(image,channel); 00426 if ((traits & UpdatePixelTrait) != 0) 00427 channels++; 00428 } 00429 return(channels); 00430 } 00431 00432 static MagickBooleanType GetFuzzDistortion(const Image *image, 00433 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception) 00434 { 00435 CacheView 00436 *image_view, 00437 *reconstruct_view; 00438 00439 MagickBooleanType 00440 status; 00441 00442 register ssize_t 00443 i; 00444 00445 ssize_t 00446 y; 00447 00448 status=MagickTrue; 00449 image_view=AcquireCacheView(image); 00450 reconstruct_view=AcquireCacheView(reconstruct_image); 00451 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00452 #pragma omp parallel for schedule(static,4) shared(status) 00453 #endif 00454 for (y=0; y < (ssize_t) image->rows; y++) 00455 { 00456 double 00457 channel_distortion[MaxPixelChannels+1]; 00458 00459 register const Quantum 00460 *restrict p, 00461 *restrict q; 00462 00463 register ssize_t 00464 i, 00465 x; 00466 00467 if (status == MagickFalse) 00468 continue; 00469 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 00470 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns, 00471 1,exception); 00472 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 00473 { 00474 status=MagickFalse; 00475 continue; 00476 } 00477 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion)); 00478 for (x=0; x < (ssize_t) image->columns; x++) 00479 { 00480 register ssize_t 00481 i; 00482 00483 if (GetPixelMask(image,p) != 0) 00484 { 00485 p+=GetPixelChannels(image); 00486 q+=GetPixelChannels(reconstruct_image); 00487 continue; 00488 } 00489 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 00490 { 00491 MagickRealType 00492 distance; 00493 00494 PixelChannel 00495 channel; 00496 00497 PixelTrait 00498 reconstruct_traits, 00499 traits; 00500 00501 channel=GetPixelChannelMapChannel(image,i); 00502 traits=GetPixelChannelMapTraits(image,channel); 00503 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel); 00504 if ((traits == UndefinedPixelTrait) || 00505 (reconstruct_traits == UndefinedPixelTrait) || 00506 ((reconstruct_traits & UpdatePixelTrait) == 0)) 00507 continue; 00508 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel( 00509 reconstruct_image,channel,q)); 00510 distance*=distance; 00511 channel_distortion[i]+=distance; 00512 channel_distortion[CompositePixelChannel]+=distance; 00513 } 00514 p+=GetPixelChannels(image); 00515 q+=GetPixelChannels(reconstruct_image); 00516 } 00517 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00518 #pragma omp critical (MagickCore_GetMeanSquaredError) 00519 #endif 00520 for (i=0; i <= MaxPixelChannels; i++) 00521 distortion[i]+=channel_distortion[i]; 00522 } 00523 reconstruct_view=DestroyCacheView(reconstruct_view); 00524 image_view=DestroyCacheView(image_view); 00525 for (i=0; i <= MaxPixelChannels; i++) 00526 distortion[i]/=((double) image->columns*image->rows); 00527 distortion[CompositePixelChannel]/=(double) GetImageChannels(image); 00528 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]); 00529 return(status); 00530 } 00531 00532 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image, 00533 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception) 00534 { 00535 CacheView 00536 *image_view, 00537 *reconstruct_view; 00538 00539 MagickBooleanType 00540 status; 00541 00542 register ssize_t 00543 i; 00544 00545 ssize_t 00546 y; 00547 00548 status=MagickTrue; 00549 image_view=AcquireCacheView(image); 00550 reconstruct_view=AcquireCacheView(reconstruct_image); 00551 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00552 #pragma omp parallel for schedule(static,4) shared(status) 00553 #endif 00554 for (y=0; y < (ssize_t) image->rows; y++) 00555 { 00556 double 00557 channel_distortion[MaxPixelChannels+1]; 00558 00559 register const Quantum 00560 *restrict p, 00561 *restrict q; 00562 00563 register ssize_t 00564 i, 00565 x; 00566 00567 if (status == MagickFalse) 00568 continue; 00569 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 00570 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns, 00571 1,exception); 00572 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL)) 00573 { 00574 status=MagickFalse; 00575 continue; 00576 } 00577 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion)); 00578 for (x=0; x < (ssize_t) image->columns; x++) 00579 { 00580 register ssize_t 00581 i; 00582 00583 if (GetPixelMask(image,p) != 0) 00584 { 00585 p+=GetPixelChannels(image); 00586 q+=GetPixelChannels(reconstruct_image); 00587 continue; 00588 } 00589 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 00590 { 00591 MagickRealType 00592 distance; 00593 00594 PixelChannel 00595 channel; 00596 00597 PixelTrait 00598 reconstruct_traits, 00599 traits; 00600 00601 channel=GetPixelChannelMapChannel(image,i); 00602 traits=GetPixelChannelMapTraits(image,channel); 00603 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel); 00604 if ((traits == UndefinedPixelTrait) || 00605 (reconstruct_traits == UndefinedPixelTrait) || 00606 ((reconstruct_traits & UpdatePixelTrait) == 0)) 00607 continue; 00608 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel( 00609 reconstruct_image,channel,q)); 00610 channel_distortion[i]+=distance; 00611 channel_distortion[CompositePixelChannel]+=distance; 00612 } 00613 p+=GetPixelChannels(image); 00614 q+=GetPixelChannels(reconstruct_image); 00615 } 00616 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00617 #pragma omp critical (MagickCore_GetMeanAbsoluteError) 00618 #endif 00619 for (i=0; i <= MaxPixelChannels; i++) 00620 distortion[i]+=channel_distortion[i]; 00621 } 00622 reconstruct_view=DestroyCacheView(reconstruct_view); 00623 image_view=DestroyCacheView(image_view); 00624 for (i=0; i <= MaxPixelChannels; i++) 00625 distortion[i]/=((double) image->columns*image->rows); 00626 distortion[CompositePixelChannel]/=(double) GetImageChannels(image); 00627 return(status); 00628 } 00629 00630 static MagickBooleanType GetMeanErrorPerPixel(Image *image, 00631 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception) 00632 { 00633 CacheView 00634 *image_view, 00635 *reconstruct_view; 00636 00637 MagickBooleanType 00638 status; 00639 00640 MagickRealType 00641 alpha, 00642 area, 00643 beta, 00644 maximum_error, 00645 mean_error; 00646 00647 ssize_t 00648 y; 00649 00650 status=MagickTrue; 00651 alpha=1.0; 00652 beta=1.0; 00653 area=0.0; 00654 maximum_error=0.0; 00655 mean_error=0.0; 00656 image_view=AcquireCacheView(image); 00657 reconstruct_view=AcquireCacheView(reconstruct_image); 00658 for (y=0; y < (ssize_t) image->rows; y++) 00659 { 00660 register const Quantum 00661 *restrict p, 00662 *restrict q; 00663 00664 register ssize_t 00665 x; 00666 00667 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 00668 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns, 00669 1,exception); 00670 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL)) 00671 { 00672 status=MagickFalse; 00673 break; 00674 } 00675 for (x=0; x < (ssize_t) image->columns; x++) 00676 { 00677 register ssize_t 00678 i; 00679 00680 if (GetPixelMask(image,p) != 0) 00681 { 00682 p+=GetPixelChannels(image); 00683 q+=GetPixelChannels(reconstruct_image); 00684 continue; 00685 } 00686 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 00687 { 00688 MagickRealType 00689 distance; 00690 00691 PixelChannel 00692 channel; 00693 00694 PixelTrait 00695 reconstruct_traits, 00696 traits; 00697 00698 channel=GetPixelChannelMapChannel(image,i); 00699 traits=GetPixelChannelMapTraits(image,channel); 00700 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel); 00701 if ((traits == UndefinedPixelTrait) || 00702 (reconstruct_traits == UndefinedPixelTrait) || 00703 ((reconstruct_traits & UpdatePixelTrait) == 0)) 00704 continue; 00705 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel( 00706 reconstruct_image,channel,q))); 00707 distortion[i]+=distance; 00708 distortion[CompositePixelChannel]+=distance; 00709 mean_error+=distance*distance; 00710 if (distance > maximum_error) 00711 maximum_error=distance; 00712 area++; 00713 } 00714 p+=GetPixelChannels(image); 00715 q+=GetPixelChannels(reconstruct_image); 00716 } 00717 } 00718 reconstruct_view=DestroyCacheView(reconstruct_view); 00719 image_view=DestroyCacheView(image_view); 00720 image->error.mean_error_per_pixel=distortion[CompositePixelChannel]/area; 00721 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area; 00722 image->error.normalized_maximum_error=QuantumScale*maximum_error; 00723 return(status); 00724 } 00725 00726 static MagickBooleanType GetMeanSquaredDistortion(const Image *image, 00727 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception) 00728 { 00729 CacheView 00730 *image_view, 00731 *reconstruct_view; 00732 00733 MagickBooleanType 00734 status; 00735 00736 register ssize_t 00737 i; 00738 00739 ssize_t 00740 y; 00741 00742 status=MagickTrue; 00743 image_view=AcquireCacheView(image); 00744 reconstruct_view=AcquireCacheView(reconstruct_image); 00745 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00746 #pragma omp parallel for schedule(static,4) shared(status) 00747 #endif 00748 for (y=0; y < (ssize_t) image->rows; y++) 00749 { 00750 double 00751 channel_distortion[MaxPixelChannels+1]; 00752 00753 register const Quantum 00754 *restrict p, 00755 *restrict q; 00756 00757 register ssize_t 00758 i, 00759 x; 00760 00761 if (status == MagickFalse) 00762 continue; 00763 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 00764 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns, 00765 1,exception); 00766 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL)) 00767 { 00768 status=MagickFalse; 00769 continue; 00770 } 00771 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion)); 00772 for (x=0; x < (ssize_t) image->columns; x++) 00773 { 00774 register ssize_t 00775 i; 00776 00777 if (GetPixelMask(image,p) != 0) 00778 { 00779 p+=GetPixelChannels(image); 00780 q+=GetPixelChannels(reconstruct_image); 00781 continue; 00782 } 00783 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 00784 { 00785 MagickRealType 00786 distance; 00787 00788 PixelChannel 00789 channel; 00790 00791 PixelTrait 00792 reconstruct_traits, 00793 traits; 00794 00795 channel=GetPixelChannelMapChannel(image,i); 00796 traits=GetPixelChannelMapTraits(image,channel); 00797 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel); 00798 if ((traits == UndefinedPixelTrait) || 00799 (reconstruct_traits == UndefinedPixelTrait) || 00800 ((reconstruct_traits & UpdatePixelTrait) == 0)) 00801 continue; 00802 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel( 00803 reconstruct_image,channel,q)); 00804 distance*=distance; 00805 channel_distortion[i]+=distance; 00806 channel_distortion[CompositePixelChannel]+=distance; 00807 } 00808 p+=GetPixelChannels(image); 00809 q+=GetPixelChannels(reconstruct_image); 00810 } 00811 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00812 #pragma omp critical (MagickCore_GetMeanSquaredError) 00813 #endif 00814 for (i=0; i <= MaxPixelChannels; i++) 00815 distortion[i]+=channel_distortion[i]; 00816 } 00817 reconstruct_view=DestroyCacheView(reconstruct_view); 00818 image_view=DestroyCacheView(image_view); 00819 for (i=0; i <= MaxPixelChannels; i++) 00820 distortion[i]/=((double) image->columns*image->rows); 00821 distortion[CompositePixelChannel]/=GetImageChannels(image); 00822 return(status); 00823 } 00824 00825 static MagickBooleanType GetNormalizedCrossCorrelationDistortion( 00826 const Image *image,const Image *reconstruct_image,double *distortion, 00827 ExceptionInfo *exception) 00828 { 00829 #define SimilarityImageTag "Similarity/Image" 00830 00831 CacheView 00832 *image_view, 00833 *reconstruct_view; 00834 00835 ChannelStatistics 00836 *image_statistics, 00837 *reconstruct_statistics; 00838 00839 MagickBooleanType 00840 status; 00841 00842 MagickOffsetType 00843 progress; 00844 00845 MagickRealType 00846 area; 00847 00848 register ssize_t 00849 i; 00850 00851 ssize_t 00852 y; 00853 00854 /* 00855 Normalize to account for variation due to lighting and exposure condition. 00856 */ 00857 image_statistics=GetImageStatistics(image,exception); 00858 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception); 00859 status=MagickTrue; 00860 progress=0; 00861 for (i=0; i <= MaxPixelChannels; i++) 00862 distortion[i]=0.0; 00863 area=1.0/((MagickRealType) image->columns*image->rows-1); 00864 image_view=AcquireCacheView(image); 00865 reconstruct_view=AcquireCacheView(reconstruct_image); 00866 for (y=0; y < (ssize_t) image->rows; y++) 00867 { 00868 register const Quantum 00869 *restrict p, 00870 *restrict q; 00871 00872 register ssize_t 00873 x; 00874 00875 if (status == MagickFalse) 00876 continue; 00877 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 00878 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns, 00879 1,exception); 00880 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL)) 00881 { 00882 status=MagickFalse; 00883 continue; 00884 } 00885 for (x=0; x < (ssize_t) image->columns; x++) 00886 { 00887 register ssize_t 00888 i; 00889 00890 if (GetPixelMask(image,p) != 0) 00891 { 00892 p+=GetPixelChannels(image); 00893 q+=GetPixelChannels(reconstruct_image); 00894 continue; 00895 } 00896 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 00897 { 00898 PixelChannel 00899 channel; 00900 00901 PixelTrait 00902 reconstruct_traits, 00903 traits; 00904 00905 channel=GetPixelChannelMapChannel(image,i); 00906 traits=GetPixelChannelMapTraits(image,channel); 00907 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel); 00908 if ((traits == UndefinedPixelTrait) || 00909 (reconstruct_traits == UndefinedPixelTrait) || 00910 ((reconstruct_traits & UpdatePixelTrait) == 0)) 00911 continue; 00912 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)* 00913 (GetPixelChannel(reconstruct_image,channel,q)- 00914 reconstruct_statistics[channel].mean); 00915 } 00916 p+=GetPixelChannels(image); 00917 q+=GetPixelChannels(reconstruct_image); 00918 } 00919 if (image->progress_monitor != (MagickProgressMonitor) NULL) 00920 { 00921 MagickBooleanType 00922 proceed; 00923 00924 proceed=SetImageProgress(image,SimilarityImageTag,progress++, 00925 image->rows); 00926 if (proceed == MagickFalse) 00927 status=MagickFalse; 00928 } 00929 } 00930 reconstruct_view=DestroyCacheView(reconstruct_view); 00931 image_view=DestroyCacheView(image_view); 00932 /* 00933 Divide by the standard deviation. 00934 */ 00935 distortion[CompositePixelChannel]=0.0; 00936 for (i=0; i < MaxPixelChannels; i++) 00937 { 00938 MagickRealType 00939 gamma; 00940 00941 PixelChannel 00942 channel; 00943 00944 channel=GetPixelChannelMapChannel(image,i); 00945 gamma=image_statistics[i].standard_deviation* 00946 reconstruct_statistics[channel].standard_deviation; 00947 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); 00948 distortion[i]=QuantumRange*gamma*distortion[i]; 00949 distortion[CompositePixelChannel]+=distortion[i]*distortion[i]; 00950 } 00951 distortion[CompositePixelChannel]=sqrt(distortion[CompositePixelChannel]/ 00952 GetImageChannels(image)); 00953 /* 00954 Free resources. 00955 */ 00956 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory( 00957 reconstruct_statistics); 00958 image_statistics=(ChannelStatistics *) RelinquishMagickMemory( 00959 image_statistics); 00960 return(status); 00961 } 00962 00963 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image, 00964 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception) 00965 { 00966 CacheView 00967 *image_view, 00968 *reconstruct_view; 00969 00970 MagickBooleanType 00971 status; 00972 00973 ssize_t 00974 y; 00975 00976 status=MagickTrue; 00977 image_view=AcquireCacheView(image); 00978 reconstruct_view=AcquireCacheView(reconstruct_image); 00979 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00980 #pragma omp parallel for schedule(static,4) shared(status) 00981 #endif 00982 for (y=0; y < (ssize_t) image->rows; y++) 00983 { 00984 double 00985 channel_distortion[MaxPixelChannels+1]; 00986 00987 register const Quantum 00988 *restrict p, 00989 *restrict q; 00990 00991 register ssize_t 00992 i, 00993 x; 00994 00995 if (status == MagickFalse) 00996 continue; 00997 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 00998 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns, 00999 1,exception); 01000 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL)) 01001 { 01002 status=MagickFalse; 01003 continue; 01004 } 01005 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion)); 01006 for (x=0; x < (ssize_t) image->columns; x++) 01007 { 01008 register ssize_t 01009 i; 01010 01011 if (GetPixelMask(image,p) != 0) 01012 { 01013 p+=GetPixelChannels(image); 01014 q+=GetPixelChannels(reconstruct_image); 01015 continue; 01016 } 01017 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 01018 { 01019 MagickRealType 01020 distance; 01021 01022 PixelChannel 01023 channel; 01024 01025 PixelTrait 01026 reconstruct_traits, 01027 traits; 01028 01029 channel=GetPixelChannelMapChannel(image,i); 01030 traits=GetPixelChannelMapTraits(image,channel); 01031 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel); 01032 if ((traits == UndefinedPixelTrait) || 01033 (reconstruct_traits == UndefinedPixelTrait) || 01034 ((reconstruct_traits & UpdatePixelTrait) == 0)) 01035 continue; 01036 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel( 01037 reconstruct_image,channel,q)); 01038 if (distance > channel_distortion[i]) 01039 channel_distortion[i]=distance; 01040 if (distance > channel_distortion[CompositePixelChannel]) 01041 channel_distortion[CompositePixelChannel]=distance; 01042 } 01043 p+=GetPixelChannels(image); 01044 q+=GetPixelChannels(reconstruct_image); 01045 } 01046 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01047 #pragma omp critical (MagickCore_GetPeakAbsoluteError) 01048 #endif 01049 for (i=0; i <= MaxPixelChannels; i++) 01050 if (channel_distortion[i] > distortion[i]) 01051 distortion[i]=channel_distortion[i]; 01052 } 01053 reconstruct_view=DestroyCacheView(reconstruct_view); 01054 image_view=DestroyCacheView(image_view); 01055 return(status); 01056 } 01057 01058 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image, 01059 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception) 01060 { 01061 MagickBooleanType 01062 status; 01063 01064 register ssize_t 01065 i; 01066 01067 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception); 01068 for (i=0; i <= MaxPixelChannels; i++) 01069 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i])); 01070 return(status); 01071 } 01072 01073 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image, 01074 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception) 01075 { 01076 MagickBooleanType 01077 status; 01078 01079 register ssize_t 01080 i; 01081 01082 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception); 01083 for (i=0; i <= MaxPixelChannels; i++) 01084 distortion[i]=sqrt(distortion[i]); 01085 return(status); 01086 } 01087 01088 MagickExport MagickBooleanType GetImageDistortion(Image *image, 01089 const Image *reconstruct_image,const MetricType metric,double *distortion, 01090 ExceptionInfo *exception) 01091 { 01092 double 01093 *channel_distortion; 01094 01095 MagickBooleanType 01096 status; 01097 01098 size_t 01099 length; 01100 01101 assert(image != (Image *) NULL); 01102 assert(image->signature == MagickSignature); 01103 if (image->debug != MagickFalse) 01104 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 01105 assert(reconstruct_image != (const Image *) NULL); 01106 assert(reconstruct_image->signature == MagickSignature); 01107 assert(distortion != (double *) NULL); 01108 *distortion=0.0; 01109 if (image->debug != MagickFalse) 01110 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 01111 if ((reconstruct_image->columns != image->columns) || 01112 (reconstruct_image->rows != image->rows)) 01113 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename); 01114 /* 01115 Get image distortion. 01116 */ 01117 length=MaxPixelChannels+1; 01118 channel_distortion=(double *) AcquireQuantumMemory(length, 01119 sizeof(*channel_distortion)); 01120 if (channel_distortion == (double *) NULL) 01121 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed"); 01122 (void) ResetMagickMemory(channel_distortion,0,length* 01123 sizeof(*channel_distortion)); 01124 switch (metric) 01125 { 01126 case AbsoluteErrorMetric: 01127 { 01128 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion, 01129 exception); 01130 break; 01131 } 01132 case FuzzErrorMetric: 01133 { 01134 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion, 01135 exception); 01136 break; 01137 } 01138 case MeanAbsoluteErrorMetric: 01139 { 01140 status=GetMeanAbsoluteDistortion(image,reconstruct_image, 01141 channel_distortion,exception); 01142 break; 01143 } 01144 case MeanErrorPerPixelMetric: 01145 { 01146 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion, 01147 exception); 01148 break; 01149 } 01150 case MeanSquaredErrorMetric: 01151 { 01152 status=GetMeanSquaredDistortion(image,reconstruct_image, 01153 channel_distortion,exception); 01154 break; 01155 } 01156 case NormalizedCrossCorrelationErrorMetric: 01157 default: 01158 { 01159 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image, 01160 channel_distortion,exception); 01161 break; 01162 } 01163 case PeakAbsoluteErrorMetric: 01164 { 01165 status=GetPeakAbsoluteDistortion(image,reconstruct_image, 01166 channel_distortion,exception); 01167 break; 01168 } 01169 case PeakSignalToNoiseRatioMetric: 01170 { 01171 status=GetPeakSignalToNoiseRatio(image,reconstruct_image, 01172 channel_distortion,exception); 01173 break; 01174 } 01175 case RootMeanSquaredErrorMetric: 01176 { 01177 status=GetRootMeanSquaredDistortion(image,reconstruct_image, 01178 channel_distortion,exception); 01179 break; 01180 } 01181 } 01182 *distortion=channel_distortion[CompositePixelChannel]; 01183 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion); 01184 return(status); 01185 } 01186 01187 /* 01188 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01189 % % 01190 % % 01191 % % 01192 % G e t I m a g e D i s t o r t i o n s % 01193 % % 01194 % % 01195 % % 01196 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01197 % 01198 % GetImageDistrortion() compares the pixel channels of an image to a 01199 % reconstructed image and returns the specified distortion metric for each 01200 % channel. 01201 % 01202 % The format of the CompareImages method is: 01203 % 01204 % double *GetImageDistortions(const Image *image, 01205 % const Image *reconstruct_image,const MetricType metric, 01206 % ExceptionInfo *exception) 01207 % 01208 % A description of each parameter follows: 01209 % 01210 % o image: the image. 01211 % 01212 % o reconstruct_image: the reconstruct image. 01213 % 01214 % o metric: the metric. 01215 % 01216 % o exception: return any errors or warnings in this structure. 01217 % 01218 */ 01219 MagickExport double *GetImageDistortions(Image *image, 01220 const Image *reconstruct_image,const MetricType metric, 01221 ExceptionInfo *exception) 01222 { 01223 double 01224 *channel_distortion; 01225 01226 MagickBooleanType 01227 status; 01228 01229 size_t 01230 length; 01231 01232 assert(image != (Image *) NULL); 01233 assert(image->signature == MagickSignature); 01234 if (image->debug != MagickFalse) 01235 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 01236 assert(reconstruct_image != (const Image *) NULL); 01237 assert(reconstruct_image->signature == MagickSignature); 01238 if (image->debug != MagickFalse) 01239 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 01240 if ((reconstruct_image->columns != image->columns) || 01241 (reconstruct_image->rows != image->rows)) 01242 { 01243 (void) ThrowMagickException(exception,GetMagickModule(),ImageError, 01244 "ImageSizeDiffers","`%s'",image->filename); 01245 return((double *) NULL); 01246 } 01247 /* 01248 Get image distortion. 01249 */ 01250 length=MaxPixelChannels+1UL; 01251 channel_distortion=(double *) AcquireQuantumMemory(length, 01252 sizeof(*channel_distortion)); 01253 if (channel_distortion == (double *) NULL) 01254 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed"); 01255 (void) ResetMagickMemory(channel_distortion,0,length* 01256 sizeof(*channel_distortion)); 01257 status=MagickTrue; 01258 switch (metric) 01259 { 01260 case AbsoluteErrorMetric: 01261 { 01262 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion, 01263 exception); 01264 break; 01265 } 01266 case FuzzErrorMetric: 01267 { 01268 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion, 01269 exception); 01270 break; 01271 } 01272 case MeanAbsoluteErrorMetric: 01273 { 01274 status=GetMeanAbsoluteDistortion(image,reconstruct_image, 01275 channel_distortion,exception); 01276 break; 01277 } 01278 case MeanErrorPerPixelMetric: 01279 { 01280 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion, 01281 exception); 01282 break; 01283 } 01284 case MeanSquaredErrorMetric: 01285 { 01286 status=GetMeanSquaredDistortion(image,reconstruct_image, 01287 channel_distortion,exception); 01288 break; 01289 } 01290 case NormalizedCrossCorrelationErrorMetric: 01291 default: 01292 { 01293 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image, 01294 channel_distortion,exception); 01295 break; 01296 } 01297 case PeakAbsoluteErrorMetric: 01298 { 01299 status=GetPeakAbsoluteDistortion(image,reconstruct_image, 01300 channel_distortion,exception); 01301 break; 01302 } 01303 case PeakSignalToNoiseRatioMetric: 01304 { 01305 status=GetPeakSignalToNoiseRatio(image,reconstruct_image, 01306 channel_distortion,exception); 01307 break; 01308 } 01309 case RootMeanSquaredErrorMetric: 01310 { 01311 status=GetRootMeanSquaredDistortion(image,reconstruct_image, 01312 channel_distortion,exception); 01313 break; 01314 } 01315 } 01316 if (status == MagickFalse) 01317 { 01318 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion); 01319 return((double *) NULL); 01320 } 01321 return(channel_distortion); 01322 } 01323 01324 /* 01325 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01326 % % 01327 % % 01328 % % 01329 % I s I m a g e s E q u a l % 01330 % % 01331 % % 01332 % % 01333 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01334 % 01335 % IsImagesEqual() measures the difference between colors at each pixel 01336 % location of two images. A value other than 0 means the colors match 01337 % exactly. Otherwise an error measure is computed by summing over all 01338 % pixels in an image the distance squared in RGB space between each image 01339 % pixel and its corresponding pixel in the reconstruct image. The error 01340 % measure is assigned to these image members: 01341 % 01342 % o mean_error_per_pixel: The mean error for any single pixel in 01343 % the image. 01344 % 01345 % o normalized_mean_error: The normalized mean quantization error for 01346 % any single pixel in the image. This distance measure is normalized to 01347 % a range between 0 and 1. It is independent of the range of red, green, 01348 % and blue values in the image. 01349 % 01350 % o normalized_maximum_error: The normalized maximum quantization 01351 % error for any single pixel in the image. This distance measure is 01352 % normalized to a range between 0 and 1. It is independent of the range 01353 % of red, green, and blue values in your image. 01354 % 01355 % A small normalized mean square error, accessed as 01356 % image->normalized_mean_error, suggests the images are very similar in 01357 % spatial layout and color. 01358 % 01359 % The format of the IsImagesEqual method is: 01360 % 01361 % MagickBooleanType IsImagesEqual(Image *image, 01362 % const Image *reconstruct_image,ExceptionInfo *exception) 01363 % 01364 % A description of each parameter follows. 01365 % 01366 % o image: the image. 01367 % 01368 % o reconstruct_image: the reconstruct image. 01369 % 01370 % o exception: return any errors or warnings in this structure. 01371 % 01372 */ 01373 MagickExport MagickBooleanType IsImagesEqual(Image *image, 01374 const Image *reconstruct_image,ExceptionInfo *exception) 01375 { 01376 CacheView 01377 *image_view, 01378 *reconstruct_view; 01379 01380 MagickBooleanType 01381 status; 01382 01383 MagickRealType 01384 area, 01385 maximum_error, 01386 mean_error, 01387 mean_error_per_pixel; 01388 01389 ssize_t 01390 y; 01391 01392 assert(image != (Image *) NULL); 01393 assert(image->signature == MagickSignature); 01394 assert(reconstruct_image != (const Image *) NULL); 01395 assert(reconstruct_image->signature == MagickSignature); 01396 if ((reconstruct_image->columns != image->columns) || 01397 (reconstruct_image->rows != image->rows)) 01398 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename); 01399 area=0.0; 01400 maximum_error=0.0; 01401 mean_error_per_pixel=0.0; 01402 mean_error=0.0; 01403 image_view=AcquireCacheView(image); 01404 reconstruct_view=AcquireCacheView(reconstruct_image); 01405 for (y=0; y < (ssize_t) image->rows; y++) 01406 { 01407 register const Quantum 01408 *restrict p, 01409 *restrict q; 01410 01411 register ssize_t 01412 x; 01413 01414 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 01415 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns, 01416 1,exception); 01417 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 01418 break; 01419 for (x=0; x < (ssize_t) image->columns; x++) 01420 { 01421 register ssize_t 01422 i; 01423 01424 if (GetPixelMask(image,p) != 0) 01425 { 01426 p+=GetPixelChannels(image); 01427 q+=GetPixelChannels(reconstruct_image); 01428 continue; 01429 } 01430 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 01431 { 01432 MagickRealType 01433 distance; 01434 01435 PixelChannel 01436 channel; 01437 01438 PixelTrait 01439 reconstruct_traits, 01440 traits; 01441 01442 channel=GetPixelChannelMapChannel(image,i); 01443 traits=GetPixelChannelMapTraits(image,channel); 01444 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel); 01445 if ((traits == UndefinedPixelTrait) || 01446 (reconstruct_traits == UndefinedPixelTrait) || 01447 ((reconstruct_traits & UpdatePixelTrait) == 0)) 01448 continue; 01449 distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image, 01450 channel,q)); 01451 mean_error_per_pixel+=distance; 01452 mean_error+=distance*distance; 01453 if (distance > maximum_error) 01454 maximum_error=distance; 01455 area++; 01456 } 01457 p+=GetPixelChannels(image); 01458 q+=GetPixelChannels(reconstruct_image); 01459 } 01460 } 01461 reconstruct_view=DestroyCacheView(reconstruct_view); 01462 image_view=DestroyCacheView(image_view); 01463 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area); 01464 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale* 01465 mean_error/area); 01466 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error); 01467 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse; 01468 return(status); 01469 } 01470 01471 /* 01472 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01473 % % 01474 % % 01475 % % 01476 % S i m i l a r i t y I m a g e % 01477 % % 01478 % % 01479 % % 01480 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01481 % 01482 % SimilarityImage() compares the reference image of the image and returns the 01483 % best match offset. In addition, it returns a similarity image such that an 01484 % exact match location is completely white and if none of the pixels match, 01485 % black, otherwise some gray level in-between. 01486 % 01487 % The format of the SimilarityImageImage method is: 01488 % 01489 % Image *SimilarityImage(const Image *image,const Image *reference, 01490 % const MetricType metric,RectangleInfo *offset,double *similarity, 01491 % ExceptionInfo *exception) 01492 % 01493 % A description of each parameter follows: 01494 % 01495 % o image: the image. 01496 % 01497 % o reference: find an area of the image that closely resembles this image. 01498 % 01499 % o metric: the metric. 01500 % 01501 % o the best match offset of the reference image within the image. 01502 % 01503 % o similarity: the computed similarity between the images. 01504 % 01505 % o exception: return any errors or warnings in this structure. 01506 % 01507 */ 01508 01509 static double GetSimilarityMetric(const Image *image,const Image *reference, 01510 const MetricType metric,const ssize_t x_offset,const ssize_t y_offset, 01511 ExceptionInfo *exception) 01512 { 01513 double 01514 distortion; 01515 01516 Image 01517 *similarity_image; 01518 01519 MagickBooleanType 01520 status; 01521 01522 RectangleInfo 01523 geometry; 01524 01525 SetGeometry(reference,&geometry); 01526 geometry.x=x_offset; 01527 geometry.y=y_offset; 01528 similarity_image=CropImage(image,&geometry,exception); 01529 if (similarity_image == (Image *) NULL) 01530 return(0.0); 01531 distortion=0.0; 01532 status=GetImageDistortion(similarity_image,reference,metric,&distortion, 01533 exception); 01534 similarity_image=DestroyImage(similarity_image); 01535 if (status == MagickFalse) 01536 return(0.0); 01537 return(distortion); 01538 } 01539 01540 MagickExport Image *SimilarityImage(Image *image,const Image *reference, 01541 const MetricType metric,RectangleInfo *offset,double *similarity_metric, 01542 ExceptionInfo *exception) 01543 { 01544 #define SimilarityImageTag "Similarity/Image" 01545 01546 CacheView 01547 *similarity_view; 01548 01549 Image 01550 *similarity_image; 01551 01552 MagickBooleanType 01553 status; 01554 01555 MagickOffsetType 01556 progress; 01557 01558 ssize_t 01559 y; 01560 01561 assert(image != (const Image *) NULL); 01562 assert(image->signature == MagickSignature); 01563 if (image->debug != MagickFalse) 01564 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 01565 assert(exception != (ExceptionInfo *) NULL); 01566 assert(exception->signature == MagickSignature); 01567 assert(offset != (RectangleInfo *) NULL); 01568 SetGeometry(reference,offset); 01569 *similarity_metric=1.0; 01570 if ((reference->columns > image->columns) || (reference->rows > image->rows)) 01571 ThrowImageException(ImageError,"ImageSizeDiffers"); 01572 similarity_image=CloneImage(image,image->columns-reference->columns+1, 01573 image->rows-reference->rows+1,MagickTrue,exception); 01574 if (similarity_image == (Image *) NULL) 01575 return((Image *) NULL); 01576 status=SetImageStorageClass(similarity_image,DirectClass,exception); 01577 if (status == MagickFalse) 01578 { 01579 similarity_image=DestroyImage(similarity_image); 01580 return((Image *) NULL); 01581 } 01582 /* 01583 Measure similarity of reference image against image. 01584 */ 01585 status=MagickTrue; 01586 progress=0; 01587 similarity_view=AcquireCacheView(similarity_image); 01588 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01589 #pragma omp parallel for schedule(static,4) shared(progress,status) 01590 #endif 01591 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++) 01592 { 01593 double 01594 similarity; 01595 01596 register Quantum 01597 *restrict q; 01598 01599 register ssize_t 01600 x; 01601 01602 if (status == MagickFalse) 01603 continue; 01604 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns, 01605 1,exception); 01606 if (q == (Quantum *) NULL) 01607 { 01608 status=MagickFalse; 01609 continue; 01610 } 01611 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++) 01612 { 01613 register ssize_t 01614 i; 01615 01616 similarity=GetSimilarityMetric(image,reference,metric,x,y,exception); 01617 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01618 #pragma omp critical (MagickCore_SimilarityImage) 01619 #endif 01620 if (similarity < *similarity_metric) 01621 { 01622 *similarity_metric=similarity; 01623 offset->x=x; 01624 offset->y=y; 01625 } 01626 if (GetPixelMask(similarity_image,q) != 0) 01627 { 01628 q+=GetPixelChannels(similarity_image); 01629 continue; 01630 } 01631 for (i=0; i < (ssize_t) GetPixelChannels(similarity_image); i++) 01632 { 01633 PixelChannel 01634 channel; 01635 01636 PixelTrait 01637 similarity_traits, 01638 traits; 01639 01640 channel=GetPixelChannelMapChannel(image,i); 01641 traits=GetPixelChannelMapTraits(image,channel); 01642 similarity_traits=GetPixelChannelMapTraits(similarity_image,channel); 01643 if ((traits == UndefinedPixelTrait) || 01644 (similarity_traits == UndefinedPixelTrait) || 01645 ((similarity_traits & UpdatePixelTrait) == 0)) 01646 continue; 01647 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange- 01648 QuantumRange*similarity),q); 01649 } 01650 q+=GetPixelChannels(similarity_image); 01651 } 01652 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse) 01653 status=MagickFalse; 01654 if (image->progress_monitor != (MagickProgressMonitor) NULL) 01655 { 01656 MagickBooleanType 01657 proceed; 01658 01659 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01660 #pragma omp critical (MagickCore_SimilarityImage) 01661 #endif 01662 proceed=SetImageProgress(image,SimilarityImageTag,progress++, 01663 image->rows); 01664 if (proceed == MagickFalse) 01665 status=MagickFalse; 01666 } 01667 } 01668 similarity_view=DestroyCacheView(similarity_view); 01669 return(similarity_image); 01670 }