1 /*
2 % Copyright (C) 2003 - 2018 GraphicsMagick Group
3 % Copyright (C) 2002 ImageMagick Studio
4 % Copyright 1991-1999 E. I. du Pont de Nemours and Company
5 %
6 % This program is covered by multiple licenses, which are described in
7 % Copyright.txt. You should have received a copy of Copyright.txt with this
8 % package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
9 %
10 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
11 % %
12 % %
13 % %
14 % CCCC OOO M M PPPP AAA RRRR EEEEE %
15 % C O O MM MM P P A A R R E %
16 % C O O M M M PPPP AAAAA RRRR EEE %
17 % C O O M M P A A R R E %
18 % CCCC OOO M M P A A R R EEEEE %
19 % %
20 % %
21 % GraphicsMagick Image Compare Methods %
22 % %
23 % %
24 % %
25 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
26 %
27 %
28 %
29 */
30
31 /*
32 Include declarations.
33 */
34 #include "magick/studio.h"
35 #include "magick/alpha_composite.h"
36 #include "magick/color.h"
37 #include "magick/color_lookup.h"
38 #include "magick/compare.h"
39 #include "magick/enum_strings.h"
40 #include "magick/pixel_iterator.h"
41 #include "magick/utility.h"
42
43 /*
44 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
45 % %
46 % %
47 % %
48 % D i f f e r e n c e I m a g e %
49 % %
50 % %
51 % %
52 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
53 %
54 % DifferenceImage() returns an annotated difference image based on the
55 % the difference between a reference image and a compare image.
56 %
57 % The format of the DifferenceImage method is:
58 %
59 % Image *DifferenceImage(const Image *reference_image,
60 % const Image *compare_image,
61 % const DifferenceImageOptions *difference_options,
62 % ExceptionInfo *exception)
63 %
64 % A description of each parameter follows:
65 %
66 % o reference_image: the reference image.
67 %
68 % o compare_image: the comparison image.
69 %
70 % o difference_options: options to use when differencing.
71 %
72 % o channel: the channel(s) to compare.
73 %
74 % o exception: Return any errors or warnings in this structure.
75 %
76 */
77 static MagickPassFail
DifferenceImagePixels(void * mutable_data,const void * immutable_data,const Image * restrict reference_image,const PixelPacket * restrict reference_pixels,const IndexPacket * restrict reference_indexes,const Image * restrict compare_image,const PixelPacket * restrict compare_pixels,const IndexPacket * restrict compare_indexes,Image * restrict result_image,PixelPacket * restrict result_pixels,IndexPacket * restrict result_indexes,const long npixels,ExceptionInfo * exception)78 DifferenceImagePixels(void *mutable_data, /* User provided mutable data */
79 const void *immutable_data, /* User provided immutable data */
80 const Image * restrict reference_image, /* Source 1 image */
81 const PixelPacket * restrict reference_pixels, /* Pixel row in source 1 image */
82 const IndexPacket * restrict reference_indexes,/* Pixel row indexes in source 1 image */
83 const Image * restrict compare_image, /* Source 2 image */
84 const PixelPacket * restrict compare_pixels, /* Pixel row in source 2 image */
85 const IndexPacket * restrict compare_indexes, /* Pixel row indexes in source 2 image */
86 Image * restrict result_image, /* Update image */
87 PixelPacket * restrict result_pixels, /* Pixel row in update image */
88 IndexPacket * restrict result_indexes, /* Pixel row indexes in update image */
89 const long npixels, /* Number of pixels in row */
90 ExceptionInfo *exception /* Exception report */
91 )
92 {
93 const DifferenceImageOptions
94 *difference_options = (const DifferenceImageOptions *) immutable_data;
95
96 register ChannelType
97 channels = difference_options->channel;
98
99 register long
100 i;
101
102 register MagickBool
103 change;
104
105 ARG_NOT_USED(mutable_data);
106 ARG_NOT_USED(compare_image);
107 ARG_NOT_USED(result_image);
108 ARG_NOT_USED(result_indexes);
109 ARG_NOT_USED(exception);
110
111 for (i=0; i < npixels; i++)
112 {
113 change=MagickFalse;
114
115 if (IsCMYKColorspace(reference_image->colorspace))
116 {
117 if (MagickChannelEnabled(channels,CyanChannel) &&
118 (GetCyanSample(&reference_pixels[i]) != GetCyanSample(&compare_pixels[i])))
119 change=MagickTrue;
120 if (MagickChannelEnabled(channels,MagentaChannel) &&
121 (GetMagentaSample(&reference_pixels[i]) != GetMagentaSample(&compare_pixels[i])))
122 change=MagickTrue;
123 if (MagickChannelEnabled(channels,YellowChannel) &&
124 (GetYellowSample(&reference_pixels[i]) != GetYellowSample(&compare_pixels[i])))
125 change=MagickTrue;
126 if (MagickChannelEnabled(channels,BlackChannel) &&
127 (GetBlackSample(&reference_pixels[i]) != GetBlackSample(&compare_pixels[i])))
128 change=MagickTrue;
129 if (MagickChannelEnabled(channels,OpacityChannel) &&
130 (reference_indexes[i] != compare_indexes[i]))
131 change=MagickTrue;
132 }
133 else
134 {
135 if (MagickChannelEnabled(channels,RedChannel) &&
136 (GetRedSample(&reference_pixels[i]) != GetRedSample(&compare_pixels[i])))
137 change=MagickTrue;
138 if (MagickChannelEnabled(channels,GreenChannel) &&
139 (GetGreenSample(&reference_pixels[i]) != GetGreenSample(&compare_pixels[i])))
140 change=MagickTrue;
141 if (MagickChannelEnabled(channels,BlueChannel) &&
142 (GetBlueSample(&reference_pixels[i]) != GetBlueSample(&compare_pixels[i])))
143 change=MagickTrue;
144 if (MagickChannelEnabled(channels,OpacityChannel) &&
145 (GetOpacitySample(&reference_pixels[i]) != GetOpacitySample(&compare_pixels[i])))
146 change=MagickTrue;
147 }
148 /*
149 Modify result image to reflect change.
150 */
151 switch (difference_options->highlight_style)
152 {
153 case UndefinedHighlightStyle:
154 break;
155 case AssignHighlightStyle:
156 {
157 /*
158 Changed pixels are assigned the highlight color.
159 */
160 if (change)
161 result_pixels[i]=difference_options->highlight_color;
162 else
163 result_pixels[i]=compare_pixels[i];
164 break;
165 }
166 case ThresholdHighlightStyle:
167 {
168 /*
169 For changed pixels, compare the pixel intensity. If the
170 pixel intensity in the compare image is higher than the
171 reference image, then set the pixel to white, otherwise
172 set it to black.
173 */
174 if (change)
175 {
176 Quantum
177 compare_intensity,
178 intensity,
179 reference_intensity;
180
181 compare_intensity=PixelIntensity(&compare_pixels[i]);
182 reference_intensity=PixelIntensity(&reference_pixels[i]);
183 if (compare_intensity > reference_intensity)
184 intensity=MaxRGB;
185 else
186 intensity=0U;
187 result_pixels[i].red = result_pixels[i].green = result_pixels[i].blue = intensity;
188 result_pixels[i].opacity=compare_pixels[i].opacity;
189 }
190 else
191 {
192 result_pixels[i]=compare_pixels[i];
193 }
194 break;
195 }
196 case TintHighlightStyle:
197 {
198 /*
199 Alpha composite highlight color on top of change pixels.
200 */
201 if (change)
202 AlphaCompositePixel(&result_pixels[i],&difference_options->highlight_color,0.75*MaxRGBDouble,
203 &compare_pixels[i],compare_pixels[i].opacity);
204 else
205 result_pixels[i]=compare_pixels[i];
206 break;
207 }
208 case XorHighlightStyle:
209 {
210 if (change)
211 {
212 result_pixels[i].red = compare_pixels[i].red ^ difference_options->highlight_color.red;
213 result_pixels[i].green = compare_pixels[i].green ^ difference_options->highlight_color.green;
214 result_pixels[i].blue = compare_pixels[i].blue ^ difference_options->highlight_color.blue;
215 result_pixels[i].opacity = compare_pixels[i].opacity ^ difference_options->highlight_color.opacity;
216 }
217 else
218 {
219 result_pixels[i]=compare_pixels[i];
220 }
221 break;
222 }
223 }
224 }
225
226 return MagickPass;
227 }
228
229 MagickExport Image *
DifferenceImage(const Image * reference_image,const Image * compare_image,const DifferenceImageOptions * difference_options,ExceptionInfo * exception)230 DifferenceImage(const Image *reference_image,const Image *compare_image,
231 const DifferenceImageOptions *difference_options,
232 ExceptionInfo *exception)
233 {
234 Image
235 *difference_image;
236
237 assert(reference_image != (const Image *) NULL);
238 assert(reference_image->signature == MagickSignature);
239 assert(compare_image != (const Image *) NULL);
240 assert(compare_image->signature == MagickSignature);
241 assert(difference_options != (const DifferenceImageOptions *) NULL);
242 assert(exception != (ExceptionInfo *) NULL);
243
244 difference_image=AllocateImage((ImageInfo *) NULL);
245 if (difference_image == (Image *) NULL)
246 {
247 ThrowImageException3(ResourceLimitError,MemoryAllocationFailed,
248 UnableToAllocateImage);
249 return ((Image *) NULL);
250 }
251 difference_image->storage_class = DirectClass;
252 difference_image->rows = reference_image->rows;
253 difference_image->columns = reference_image->columns;
254 difference_image->depth = Max(reference_image->depth, compare_image->depth);
255
256 /*
257 Update "difference" image to mark changes.
258 */
259 (void) PixelIterateTripleModify(DifferenceImagePixels,
260 NULL,
261 "[%s]*[%s]->[%s] Difference image pixels ...",
262 NULL,difference_options,
263 reference_image->columns,reference_image->rows,
264 reference_image, compare_image,0, 0,
265 difference_image, 0, 0,
266 exception);
267 return difference_image;
268 }
269
270 /*
271 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
272 % %
273 % %
274 % %
275 % G e t I m a g e C h a n n e l D i f f e r e n c e %
276 % %
277 % %
278 % %
279 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
280 %
281 % GetImageChannelDifference() updates a user provided statistics structure
282 % with per-channel, and totalized, difference statistics corresponding
283 % to a specified comparison metric.
284 %
285 % The format of the GetImageChannelDifference method is:
286 %
287 % MagickPassFail GetImageChannelDifference(const Image *reference_image,
288 % const Image *compare_image,
289 % const MetricType metric,
290 % DifferenceStatistics *statistics,
291 % ExceptionInfo *exception)
292 %
293 % A description of each parameter follows:
294 %
295 % o reference_image: the reference image.
296 %
297 % o compare_image: the comparison image.
298 %
299 % o metric: metric to use when differencing.
300 %
301 % o statistics: the statistics structure to populate.
302 %
303 % o exception: Return any errors or warnings in this structure.
304 %
305 */
306 /*
307 Compute the total absolute value difference.
308
309 In this case we sum the absolute value difference between channel
310 pixel quantums.
311 */
312 static MagickPassFail
ComputeAbsoluteError(void * mutable_data,const void * immutable_data,const Image * restrict first_image,const PixelPacket * restrict first_pixels,const IndexPacket * restrict first_indexes,const Image * restrict second_image,const PixelPacket * restrict second_pixels,const IndexPacket * restrict second_indexes,const long npixels,ExceptionInfo * exception)313 ComputeAbsoluteError(void *mutable_data,
314 const void *immutable_data,
315 const Image * restrict first_image,
316 const PixelPacket * restrict first_pixels,
317 const IndexPacket * restrict first_indexes,
318 const Image * restrict second_image,
319 const PixelPacket * restrict second_pixels,
320 const IndexPacket * restrict second_indexes,
321 const long npixels,
322 ExceptionInfo *exception)
323 {
324 DifferenceStatistics
325 lstats,
326 *stats = (DifferenceStatistics *) mutable_data;
327
328 register long
329 i;
330
331 ARG_NOT_USED(immutable_data);
332 ARG_NOT_USED(first_image);
333 ARG_NOT_USED(first_indexes);
334 ARG_NOT_USED(second_image);
335 ARG_NOT_USED(second_indexes);
336 ARG_NOT_USED(exception);
337
338 InitializeDifferenceStatistics(&lstats,exception);
339 for (i=0; i < npixels; i++)
340 {
341 lstats.red += fabs(first_pixels[i].red-(double) second_pixels[i].red)/MaxRGBDouble;
342 lstats.green += fabs(first_pixels[i].green-(double) second_pixels[i].green)/MaxRGBDouble;
343 lstats.blue += fabs(first_pixels[i].blue-(double) second_pixels[i].blue)/MaxRGBDouble;
344 lstats.opacity += fabs(first_pixels[i].opacity-(double) second_pixels[i].opacity)/MaxRGBDouble;
345 }
346
347 #if defined(HAVE_OPENMP)
348 # pragma omp critical (GM_ComputeAbsoluteError)
349 #endif
350 {
351 stats->red += lstats.red;
352 stats->green += lstats.green;
353 stats->blue += lstats.blue;
354 stats->opacity += lstats.opacity;
355 }
356
357 return (MagickPass);
358 }
359
360 /*
361 Compute the peak absolute difference.
362
363 In this case we compute the simple difference between channel pixel
364 quantums, obtain the absolute value, and store the value if it is
365 greater than the current peak value.
366 */
367 static MagickPassFail
ComputePeakAbsoluteError(void * mutable_data,const void * immutable_data,const Image * restrict first_image,const PixelPacket * restrict first_pixels,const IndexPacket * restrict first_indexes,const Image * restrict second_image,const PixelPacket * restrict second_pixels,const IndexPacket * restrict second_indexes,const long npixels,ExceptionInfo * exception)368 ComputePeakAbsoluteError(void *mutable_data,
369 const void *immutable_data,
370 const Image * restrict first_image,
371 const PixelPacket * restrict first_pixels,
372 const IndexPacket * restrict first_indexes,
373 const Image * restrict second_image,
374 const PixelPacket * restrict second_pixels,
375 const IndexPacket * restrict second_indexes,
376 const long npixels,
377 ExceptionInfo *exception)
378 {
379 DifferenceStatistics
380 lstats,
381 *stats = (DifferenceStatistics *) mutable_data;
382
383 double
384 difference;
385
386 register long
387 i;
388
389 ARG_NOT_USED(immutable_data);
390 ARG_NOT_USED(first_image);
391 ARG_NOT_USED(first_indexes);
392 ARG_NOT_USED(second_image);
393 ARG_NOT_USED(second_indexes);
394
395 InitializeDifferenceStatistics(&lstats,exception);
396 for (i=0; i < npixels; i++)
397 {
398 difference=fabs(first_pixels[i].red-(double) second_pixels[i].red)/MaxRGBDouble;
399 if (difference > lstats.red)
400 lstats.red=difference;
401
402 difference=fabs(first_pixels[i].green-(double) second_pixels[i].green)/MaxRGBDouble;
403 if (difference > lstats.green)
404 lstats.green=difference;
405
406 difference=fabs(first_pixels[i].blue-(double) second_pixels[i].blue)/MaxRGBDouble;
407 if (difference > lstats.blue)
408 lstats.blue=difference;
409
410 difference=fabs(first_pixels[i].opacity-(double) second_pixels[i].opacity)/MaxRGBDouble;
411 if (difference > lstats.opacity)
412 lstats.opacity=difference;
413 }
414
415 #if defined(HAVE_OPENMP)
416 # pragma omp critical (GM_ComputePeakAbsoluteError)
417 #endif
418 {
419 if (lstats.red > stats->red)
420 stats->red=lstats.red;
421 if (lstats.green > stats->green)
422 stats->green=lstats.green;
423 if (lstats.blue > stats->blue)
424 stats->blue=lstats.blue;
425 if (lstats.opacity > stats->opacity)
426 stats->opacity=lstats.opacity;
427 }
428
429 return (MagickPass);
430 }
431
432 /*
433 Compute the squared difference.
434
435 In this case we sum the square of the difference between channel
436 pixel quantums.
437 */
438 static MagickPassFail
ComputeSquaredError(void * mutable_data,const void * immutable_data,const Image * restrict first_image,const PixelPacket * restrict first_pixels,const IndexPacket * restrict first_indexes,const Image * restrict second_image,const PixelPacket * restrict second_pixels,const IndexPacket * restrict second_indexes,const long npixels,ExceptionInfo * exception)439 ComputeSquaredError(void *mutable_data,
440 const void *immutable_data,
441 const Image * restrict first_image,
442 const PixelPacket * restrict first_pixels,
443 const IndexPacket * restrict first_indexes,
444 const Image * restrict second_image,
445 const PixelPacket * restrict second_pixels,
446 const IndexPacket * restrict second_indexes,
447 const long npixels,
448 ExceptionInfo *exception)
449 {
450 DifferenceStatistics
451 lstats,
452 *stats = (DifferenceStatistics *) mutable_data;
453
454 double
455 difference;
456
457 register long
458 i;
459
460 ARG_NOT_USED(immutable_data);
461 ARG_NOT_USED(first_image);
462 ARG_NOT_USED(first_indexes);
463 ARG_NOT_USED(second_image);
464 ARG_NOT_USED(second_indexes);
465 ARG_NOT_USED(exception);
466
467 InitializeDifferenceStatistics(&lstats,exception);
468 for (i=0; i < npixels; i++)
469 {
470 difference=(first_pixels[i].red-(double) second_pixels[i].red)/MaxRGBDouble;
471 lstats.red += difference*difference;
472
473 difference=(first_pixels[i].green-(double) second_pixels[i].green)/MaxRGBDouble;
474 lstats.green += difference*difference;
475
476 difference=(first_pixels[i].blue-(double) second_pixels[i].blue)/MaxRGBDouble;
477 lstats.blue += difference*difference;
478
479 difference=(first_pixels[i].opacity-(double) second_pixels[i].opacity)/MaxRGBDouble;
480 lstats.opacity += difference*difference;
481 }
482
483 #if defined(HAVE_OPENMP)
484 # pragma omp critical (GM_ComputeSquaredError)
485 #endif
486 {
487 stats->red += lstats.red;
488 stats->green += lstats.green;
489 stats->blue += lstats.blue;
490 stats->opacity += lstats.opacity;
491 }
492
493 return (MagickPass);
494 }
495 MagickExport MagickPassFail
GetImageChannelDifference(const Image * reference_image,const Image * compare_image,const MetricType metric,DifferenceStatistics * statistics,ExceptionInfo * exception)496 GetImageChannelDifference(const Image *reference_image,
497 const Image *compare_image,
498 const MetricType metric,
499 DifferenceStatistics *statistics,
500 ExceptionInfo *exception)
501 {
502 PixelIteratorDualReadCallback
503 call_back = (PixelIteratorDualReadCallback) NULL;
504
505 MagickPassFail
506 status = MagickFail;
507
508 assert(reference_image != (const Image *) NULL);
509 assert(reference_image->signature == MagickSignature);
510 assert(compare_image != (const Image *) NULL);
511 assert(compare_image->signature == MagickSignature);
512 assert(statistics != (DifferenceStatistics *) NULL);
513 assert(exception != (ExceptionInfo *) NULL);
514
515 InitializeDifferenceStatistics(statistics,exception);
516
517 /*
518 Select basic differencing function to use.
519 */
520 switch (metric)
521 {
522 case UndefinedMetric:
523 break;
524 case MeanAbsoluteErrorMetric:
525 call_back=ComputeAbsoluteError;
526 break;
527 case MeanSquaredErrorMetric:
528 call_back=ComputeSquaredError;
529 break;
530 case PeakAbsoluteErrorMetric:
531 call_back=ComputePeakAbsoluteError;
532 break;
533 case PeakSignalToNoiseRatioMetric:
534 call_back=ComputeSquaredError;
535 break;
536 case RootMeanSquaredErrorMetric:
537 call_back=ComputeSquaredError;
538 break;
539 }
540
541 if (call_back != (PixelIteratorDualReadCallback) NULL)
542 {
543 double
544 number_channels,
545 number_pixels;
546
547 char
548 description[MaxTextExtent];
549
550 FormatString(description,"[%%s]*[%%s] Compute image difference using %s metric...",
551 MetricTypeToString(metric));
552
553 status=PixelIterateDualRead(call_back,
554 NULL,
555 description,
556 statistics, NULL,
557 reference_image->columns,reference_image->rows,
558 reference_image,0,0,
559 compare_image,0,0,
560 exception);
561 /*
562 Post-process statistics (as required)
563 */
564
565 number_channels=3.0 + (reference_image->matte ? 1.0 : 0.0);
566 number_pixels=(double) reference_image->columns*reference_image->rows;
567
568 if ((MeanAbsoluteErrorMetric == metric) ||
569 (MeanSquaredErrorMetric == metric) ||
570 (PeakSignalToNoiseRatioMetric == metric)||
571 (RootMeanSquaredErrorMetric == metric))
572 {
573 /*
574 Compute mean values.
575 */
576 statistics->combined=((statistics->red+statistics->green+
577 statistics->blue+
578 (reference_image->matte ? statistics->opacity : 0.0))/
579 (number_pixels*number_channels));
580 statistics->red /= number_pixels;
581 statistics->green /= number_pixels;
582 statistics->blue /= number_pixels;
583 statistics->opacity /= number_pixels;
584 }
585
586 if (PeakAbsoluteErrorMetric == metric)
587 {
588 /*
589 Determine peak channel value
590 */
591 if (statistics->red > statistics->combined)
592 statistics->combined=statistics->red;
593
594 if (statistics->green > statistics->combined)
595 statistics->combined=statistics->green;
596
597 if (statistics->blue > statistics->combined)
598 statistics->combined=statistics->blue;
599
600 if ((reference_image->matte) && (statistics->opacity > statistics->combined))
601 statistics->combined=statistics->opacity;
602 }
603
604 if (PeakSignalToNoiseRatioMetric == metric)
605 {
606 /*
607 Compute PSNR.
608 */
609 statistics->red=(20.0 * log10(1.0/sqrt(statistics->red)));
610 statistics->green=(20.0 * log10(1.0/sqrt(statistics->green)));
611 statistics->blue=(20.0 * log10(1.0/sqrt(statistics->blue)));
612 statistics->opacity=(20.0 * log10(1.0/sqrt(statistics->opacity)));
613 statistics->combined=(20.0 * log10(1.0/sqrt(statistics->combined)));
614 }
615
616 if (RootMeanSquaredErrorMetric == metric)
617 {
618 /*
619 Compute RMSE.
620 */
621 statistics->red=sqrt(statistics->red);
622 statistics->green=sqrt(statistics->green);
623 statistics->blue=sqrt(statistics->blue);
624 statistics->opacity=sqrt(statistics->opacity);
625 statistics->combined=sqrt(statistics->combined);
626 }
627 }
628
629 return status;
630 }
631
632 /*
633 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
634 % %
635 % %
636 % %
637 % G e t I m a g e C h a n n e l D i s t o r t i o n %
638 % %
639 % %
640 % %
641 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
642 %
643 % GetImageChannelDistortion() updates a distortion parameter with the
644 % distortion (error) computed according to the specified comparison metric.
645 % The value returned is only for the channel specified.
646 %
647 % The format of the GetImageChannelDistortion method is:
648 %
649 % MagickPassFail GetImageChannelDistortion(const Image *reference_image,
650 % const Image *compare_image,
651 % const ChannelType channel,
652 % const MetricType metric,
653 % double *distortion,
654 % ExceptionInfo *exception)
655 %
656 % A description of each parameter follows:
657 %
658 % o reference_image: the reference image.
659 %
660 % o compare_image: the comparison image.
661 %
662 % o channel: the channel to obtain error data for.
663 %
664 % o metric: metric to use when differencing.
665 %
666 % o distortion: updated with the computed distortion.
667 %
668 % o exception: Return any errors or warnings in this structure.
669 %
670 */
671 MagickExport MagickPassFail
GetImageChannelDistortion(const Image * reference_image,const Image * compare_image,const ChannelType channel,const MetricType metric,double * distortion,ExceptionInfo * exception)672 GetImageChannelDistortion(const Image *reference_image,
673 const Image *compare_image,
674 const ChannelType channel,
675 const MetricType metric,
676 double *distortion,
677 ExceptionInfo *exception)
678 {
679 DifferenceStatistics
680 statistics;
681
682 MagickPassFail
683 status;
684
685 assert(distortion != (double *) NULL);
686
687 *distortion=1.0;
688 status=GetImageChannelDifference(reference_image,compare_image,metric,
689 &statistics,exception);
690 switch (channel)
691 {
692 case RedChannel:
693 case CyanChannel:
694 *distortion=statistics.red;
695 break;
696 case GreenChannel:
697 case MagentaChannel:
698 *distortion=statistics.green;
699 break;
700 case BlueChannel:
701 case YellowChannel:
702 *distortion=statistics.blue;
703 break;
704 case BlackChannel:
705 case MatteChannel:
706 case OpacityChannel:
707 *distortion=statistics.opacity;
708 break;
709 case UndefinedChannel:
710 case AllChannels:
711 case GrayChannel:
712 *distortion=statistics.combined;
713 break;
714 }
715
716 return status;
717 }
718
719 /*
720 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
721 % %
722 % %
723 % %
724 % G e t I m a g e D i s t o r t i o n %
725 % %
726 % %
727 % %
728 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
729 %
730 % GetImageDistortion() updates a distortion parameter with the distortion
731 % (error) computed according to the specified comparison metric. The value
732 % returned reflects all enabled channels.
733 %
734 % The format of the GetImageDistortion method is:
735 %
736 % MagickPassFail GetImageDistortion(const Image *reference_image,
737 % const Image *compare_image,
738 % const MetricType metric,
739 % double *distortion,
740 % ExceptionInfo *exception)
741 %
742 % A description of each parameter follows:
743 %
744 % o reference_image: the reference image.
745 %
746 % o compare_image: the comparison image.
747 %
748 % o channel: the channel to obtain error data for.
749 %
750 % o metric: metric to use when differencing.
751 %
752 % o distortion: updated with the computed distortion.
753 %
754 % o exception: Return any errors or warnings in this structure.
755 %
756 */
757 MagickExport MagickPassFail
GetImageDistortion(const Image * reference_image,const Image * compare_image,const MetricType metric,double * distortion,ExceptionInfo * exception)758 GetImageDistortion(const Image *reference_image,
759 const Image *compare_image,
760 const MetricType metric,
761 double *distortion,
762 ExceptionInfo *exception)
763 {
764 return GetImageChannelDistortion(reference_image,compare_image,AllChannels,
765 metric,distortion,exception);
766 }
767
768 /*
769 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
770 % %
771 % %
772 % %
773 % I s I m a g e s E q u a l %
774 % %
775 % %
776 % %
777 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
778 %
779 % IsImagesEqual() measures the difference between colors at each pixel
780 % location of two images. A value other than 0 means the colors match
781 % exactly. Otherwise an error measure is computed by summing over all
782 % pixels in an image the distance squared in RGB space between each image
783 % pixel and its corresponding pixel in the reference image. The error
784 % measure is assigned to these image members:
785 %
786 % o mean_error_per_pixel: The mean error for any single pixel in
787 % the image.
788 %
789 % o normalized_mean_error: The normalized mean quantization error for
790 % any single pixel in the image. This distance measure is normalized to
791 % a range between 0 and 1. It is independent of the range of red, green,
792 % and blue values in the image.
793 %
794 % o normalized_maximum_error: The normalized maximum quantization
795 % error for any single pixel in the image. This distance measure is
796 % normalized to a range between 0 and 1. It is independent of the range
797 % of red, green, and blue values in your image.
798 %
799 % A small normalized mean square error, accessed as
800 % image->normalized_mean_error, suggests the images are very similiar in
801 % spatial layout and color.
802 %
803 % The format of the IsImagesEqual method is:
804 %
805 % MagickBool IsImagesEqual(Image *image,const Image *reference)
806 %
807 % A description of each parameter follows.
808 %
809 % o image: The image.
810 %
811 % o reference: The reference image.
812 %
813 */
814 typedef struct _ErrorStatistics {
815 double
816 maximum,
817 total;
818 } ErrorStatistics;
819
820 static MagickPassFail
ComputePixelError(void * mutable_data,const void * immutable_data,const Image * restrict first_image,const PixelPacket * restrict first_pixels,const IndexPacket * restrict first_indexes,const Image * restrict second_image,const PixelPacket * restrict second_pixels,const IndexPacket * restrict second_indexes,const long npixels,ExceptionInfo * exception)821 ComputePixelError(void *mutable_data,
822 const void *immutable_data,
823 const Image * restrict first_image,
824 const PixelPacket * restrict first_pixels,
825 const IndexPacket * restrict first_indexes,
826 const Image * restrict second_image,
827 const PixelPacket * restrict second_pixels,
828 const IndexPacket * restrict second_indexes,
829 const long npixels,
830 ExceptionInfo *exception)
831 {
832 ErrorStatistics
833 *stats = (ErrorStatistics *) mutable_data;
834
835 double
836 difference,
837 distance,
838 distance_squared,
839 stats_maximum,
840 stats_total;
841
842 register long
843 i;
844
845 ARG_NOT_USED(immutable_data);
846 ARG_NOT_USED(first_indexes);
847 ARG_NOT_USED(second_image);
848 ARG_NOT_USED(second_indexes);
849 ARG_NOT_USED(exception);
850
851 stats_maximum=0.0;
852 stats_total=0.0;
853
854 for (i=0; i < npixels; i++)
855 {
856 difference=(first_pixels[i].red-(double) second_pixels[i].red)/MaxRGBDouble;
857 distance_squared=(difference*difference);
858
859 difference=(first_pixels[i].green-(double) second_pixels[i].green)/MaxRGBDouble;
860 distance_squared+=(difference*difference);
861
862 difference=(first_pixels[i].blue-(double) second_pixels[i].blue)/MaxRGBDouble;
863 distance_squared+=(difference*difference);
864
865 if (first_image->matte)
866 {
867 difference=(first_pixels[i].opacity-(double) second_pixels[i].opacity)/MaxRGBDouble;
868 distance_squared+=(difference*difference);
869 }
870 distance=sqrt(distance_squared);
871
872 stats_total+=distance;
873 if (distance > stats_maximum)
874 stats_maximum=distance;
875 }
876
877 #if defined(HAVE_OPENMP)
878 # pragma omp critical (GM_ComputePixelError)
879 #endif
880 {
881 stats->total+=stats_total;
882
883 if (stats_maximum > stats->maximum)
884 stats->maximum=stats_maximum;
885 }
886 return (MagickPass);
887 }
888
889 MagickExport MagickBool
IsImagesEqual(Image * image,const Image * reference)890 IsImagesEqual(Image *image,const Image *reference)
891 {
892 ErrorStatistics
893 stats;
894
895 double
896 mean_error_per_pixel,
897 normalize,
898 number_pixels;
899
900 /*
901 Initialize measurement.
902 */
903 assert(image != (const Image *) NULL);
904 assert(image->signature == MagickSignature);
905 assert(reference != (const Image *) NULL);
906 assert(reference->signature == MagickSignature);
907 (void) memset(&image->error,0,sizeof(ErrorInfo));
908 if ((image->rows != reference->rows) ||
909 (image->columns != reference->columns))
910 ThrowBinaryException3(ImageError,UnableToCompareImages,
911 ImageSizeDiffers);
912 if ((image->colorspace != reference->colorspace) &&
913 (!IsRGBColorspace(image->colorspace) || !IsRGBColorspace(reference->colorspace)))
914 ThrowBinaryException3(ImageError,UnableToCompareImages,
915 ImageColorspaceDiffers);
916 if(image->matte != reference->matte)
917 ThrowBinaryException3(ImageError,UnableToCompareImages,
918 ImageOpacityDiffers);
919
920 /*
921 For each pixel, collect error statistics.
922 */
923 number_pixels=(double) image->columns*image->rows;
924
925 stats.maximum=0.0;
926 stats.total=0.0;
927
928 (void) PixelIterateDualRead(ComputePixelError,
929 NULL,
930 "[%s]*[%s] Compute pixel error ...",
931 &stats, NULL,
932 image->columns,image->rows,
933 image,0,0,
934 reference,0,0,
935 &image->exception);
936
937 /*
938 Compute final error statistics.
939 */
940
941 if (image->matte)
942 normalize = sqrt(4.0); /* sqrt(1.0*1.0+1.0*1.0+1.0*1.0+1.0*1.0) */
943 else
944 normalize = sqrt(3.0); /* sqrt(1.0*1.0+1.0*1.0+1.0*1.0) */
945 mean_error_per_pixel=stats.total/number_pixels;
946 image->error.mean_error_per_pixel=mean_error_per_pixel*MaxRGBDouble;
947 image->error.normalized_mean_error=mean_error_per_pixel/normalize;
948 image->error.normalized_maximum_error=stats.maximum/normalize;
949 return(image->error.normalized_mean_error == 0.0);
950 }
951
952 /*
953 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
954 % %
955 % %
956 % %
957 % I n i t i a l i z e D i f f e r e n c e I m a g e O p t i o n s %
958 % %
959 % %
960 % %
961 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
962 %
963 % InitializeDifferenceImageOptions() assigns default options to a user-provided
964 % DifferenceImageOptions structure. This function should always be used
965 % to initialize the DifferenceImageOptions structure prior to making any
966 % changes to it.
967 %
968 % The format of the InitializeDifferenceImageOptions method is:
969 %
970 % void InitializeDifferenceImageOptions(DifferenceImageOptions *options,
971 % ExceptionInfo *exception)
972 %
973 % A description of each parameter follows:
974 %
975 % o options: pointer to DifferenceImageOptions structure to initialize.
976 %
977 % o exception: Return any errors or warnings in this structure.
978 %
979 */
980 MagickExport void
InitializeDifferenceImageOptions(DifferenceImageOptions * options,ExceptionInfo * exception)981 InitializeDifferenceImageOptions(DifferenceImageOptions *options,
982 ExceptionInfo *exception)
983 {
984 assert(options != (DifferenceImageOptions *) NULL);
985 memset(options,0,sizeof(DifferenceImageOptions));
986 options->channel=AllChannels;
987 options->highlight_style=TintHighlightStyle;
988 (void) QueryColorDatabase(HighlightColor,&options->highlight_color,exception);
989 }
990
991
992 /*
993 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
994 % %
995 % %
996 % %
997 % I n i t i a l i z e D i f f e r e n c e S t a t i s t i c s %
998 % %
999 % %
1000 % %
1001 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1002 %
1003 % InitializeDifferenceStatistics() assigns default options to a user-provided
1004 % DifferenceStatistics structure.
1005 %
1006 % The format of the InitializeDifferenceStatistics method is:
1007 %
1008 % void InitializeDifferenceStatistics(DifferenceStatistics *options,
1009 % ExceptionInfo *exception)
1010 %
1011 % A description of each parameter follows:
1012 %
1013 % o options: pointer to DifferenceStatistics structure to initialize.
1014 %
1015 % o exception: Return any errors or warnings in this structure.
1016 %
1017 */
1018 MagickExport void
InitializeDifferenceStatistics(DifferenceStatistics * statistics,ExceptionInfo * exception)1019 InitializeDifferenceStatistics(DifferenceStatistics *statistics,
1020 ExceptionInfo *exception)
1021 {
1022 ARG_NOT_USED(exception);
1023 assert(statistics != (DifferenceStatistics *) NULL);
1024 statistics->red=0.0;
1025 statistics->green=0.0;
1026 statistics->blue=0.0;
1027 statistics->opacity=0.0;
1028 statistics->combined=0.0;
1029 }
1030