static MagickRealType GetAbsoluteError(const Image *image, const Image *reconstruct_image,ExceptionInfo *exception) { long y; MagickPixelPacket image_pixel, reconstruct_pixel; MagickRealType distortion; register const IndexPacket *indexes, *reconstruct_indexes; register const PixelPacket *p, *q; register long x; ViewInfo *image_view, *reconstruct_view; /* Compute the absolute difference in pixels between two images. */ GetMagickPixelPacket(image,&image_pixel); GetMagickPixelPacket(reconstruct_image,&reconstruct_pixel); distortion=0.0; image_view=OpenCacheView(image); reconstruct_view=OpenCacheView(reconstruct_image); for (y=0; y < (long) image->rows; y++) { p=AcquireCacheViewPixels(image_view,0,y,image->columns,1,exception); q=AcquireCacheViewPixels(reconstruct_view,0,y,reconstruct_image->columns,1, exception); if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL)) break; indexes=AcquireCacheViewIndexes(image_view); reconstruct_indexes=AcquireCacheViewIndexes(reconstruct_view); for (x=0; x < (long) image->columns; x++) { SetMagickPixelPacket(image,p,indexes+x,&image_pixel); SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes+x, &reconstruct_pixel); if (IsMagickColorSimilar(&image_pixel,&reconstruct_pixel) == MagickFalse) distortion++; p++; q++; } } reconstruct_view=CloseCacheView(reconstruct_view); image_view=CloseCacheView(image_view); return(distortion); }
/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % A v e r a g e I m a g e s % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % The Average() method takes a set of images and averages them together. % Each image in the set must have the same width and height. Average() % returns a single image with each corresponding pixel component of % each image averaged. On failure, a NULL image is returned and % exception describes the reason for the failure. % % The format of the AverageImage method is: % % Image *AverageImages(Image *image,ExceptionInfo *exception) % % A description of each parameter follows: % % o image: The image sequence. % % o exception: Return any errors or warnings in this structure. % % */ MagickExport Image *AverageImages(const Image *image,ExceptionInfo *exception) { ThreadViewDataSet *pixels_sums; Image *average_image; const Image *last_image; long y; unsigned long row_count=0; double number_scenes; unsigned long number_pixels; MagickPassFail status=MagickPass; /* Ensure the image are the same size. */ assert(image != (Image *) NULL); assert(image->signature == MagickSignature); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); if (image->next == (Image *) NULL) ThrowImageException3(ImageError,ImageSequenceIsRequired, UnableToAverageImage); { const Image *next; for (next=image; next != (Image *) NULL; next=next->next) { if ((next->columns != image->columns) || (next->rows != image->rows)) ThrowImageException3(OptionError,UnableToAverageImageSequence, ImageWidthsOrHeightsDiffer); } } /* Allocate sum accumulation buffer. */ number_pixels=image->columns; pixels_sums=AllocateThreadViewDataArray(image,exception,number_pixels, sizeof(DoublePixelPacket)); if (pixels_sums == (ThreadViewDataSet *) NULL) ThrowImageException3(ResourceLimitError,MemoryAllocationFailed, UnableToAverageImageSequence); /* Initialize average next attributes. */ average_image=CloneImage(image,image->columns,image->rows,True,exception); if (average_image == (Image *) NULL) { DestroyThreadViewDataSet(pixels_sums); return((Image *) NULL); } average_image->storage_class=DirectClass; number_scenes=(double) GetImageListLength(image); last_image=GetLastImageInList(image); #if defined(HAVE_OPENMP) # pragma omp parallel for schedule(dynamic) shared(row_count, status) #endif for (y=0; y < (long) image->rows; y++) { register DoublePixelPacket *pixels_sum; const Image *next; register const PixelPacket *p; register long x; MagickBool thread_status; thread_status=status; if (thread_status == MagickFail) continue; pixels_sum=AccessThreadViewData(pixels_sums); /* Compute sum over each pixel color component. */ for (next=image; next != (Image *) NULL; next=next->next) { ViewInfo *next_view; next_view=OpenCacheView((Image *) next); if (next_view == (ViewInfo *) NULL) thread_status=MagickFail; if (next_view != (ViewInfo *) NULL) { p=AcquireCacheViewPixels(next_view,0,y,next->columns,1,exception); if (p == (const PixelPacket *) NULL) thread_status=MagickFail; if (p != (const PixelPacket *) NULL) { if (next == image) { for (x=0; x < (long) next->columns; x++) { pixels_sum[x].red=p[x].red; pixels_sum[x].green=p[x].green; pixels_sum[x].blue=p[x].blue; pixels_sum[x].opacity=p[x].opacity; } } else { for (x=0; x < (long) next->columns; x++) { pixels_sum[x].red+=p[x].red; pixels_sum[x].green+=p[x].green; pixels_sum[x].blue+=p[x].blue; pixels_sum[x].opacity+=p[x].opacity; } } } CloseCacheView(next_view); } } /* Average next pixels. */ if (thread_status != MagickFail) { register PixelPacket *q; q=SetImagePixelsEx(average_image,0,y,average_image->columns,1,exception); if (q == (PixelPacket *) NULL) thread_status=MagickFail; if (q != (PixelPacket *) NULL) { for (x=0; x < (long) average_image->columns; x++) { q[x].red=(Quantum) (pixels_sum[x].red/number_scenes+0.5); q[x].green=(Quantum) (pixels_sum[x].green/number_scenes+0.5); q[x].blue=(Quantum) (pixels_sum[x].blue/number_scenes+0.5); q[x].opacity=(Quantum) (pixels_sum[x].opacity/number_scenes+0.5); } if (!SyncImagePixelsEx(average_image,exception)) thread_status=MagickFail; } } #if defined(HAVE_OPENMP) # pragma omp critical (GM_AverageImages) #endif { row_count++; if (QuantumTick(row_count,average_image->rows)) if (!MagickMonitorFormatted(row_count,average_image->rows,exception, "[%s,...,%s] Average image sequence...", image->filename,last_image->filename)) thread_status=MagickFail; if (thread_status == MagickFail) status=MagickFail; } } DestroyThreadViewDataSet(pixels_sums); if (status == MagickFail) { DestroyImage(average_image); average_image=(Image *) NULL; } return(average_image); }
/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % I s I m a g e s E q u a l % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsImagesEqual() measures the difference between colors at each pixel % location of two images. A value other than 0 means the colors match % exactly. Otherwise an error measure is computed by summing over all % pixels in an image the distance squared in RGB space between each image % pixel and its corresponding pixel in the reconstruct image. The error % measure is assigned to these image members: % % o mean_error_per_pixel: The mean error for any single pixel in % the image. % % o normalized_mean_error: The normalized mean quantization error for % any single pixel in the image. This distance measure is normalized to % a range between 0 and 1. It is independent of the range of red, green, % and blue values in the image. % % o normalized_maximum_error: The normalized maximum quantization % error for any single pixel in the image. This distance measure is % normalized to a range between 0 and 1. It is independent of the range % of red, green, and blue values in your image. % % A small normalized mean square error, accessed as % image->normalized_mean_error, suggests the images are very similar in % spatial layout and color. % % The format of the IsImagesEqual method is: % % MagickBooleanType IsImagesEqual(Image *image, % const Image *reconstruct_image) % % A description of each parameter follows. % % o image: The image. % % o reconstruct_image: The reconstruct image. % */ MagickExport MagickBooleanType IsImagesEqual(Image *image, const Image *reconstruct_image) { long y; MagickBooleanType status; MagickRealType area, distance, maximum_error, mean_error, mean_error_per_pixel; register const IndexPacket *indexes, *reconstruct_indexes; register const PixelPacket *p, *q; register long x; ViewInfo *image_view, *reconstruct_view; assert(image != (Image *) NULL); assert(image->signature == MagickSignature); assert(reconstruct_image != (const Image *) NULL); assert(reconstruct_image->signature == MagickSignature); if ((reconstruct_image->columns != image->columns) || (reconstruct_image->rows != image->rows)) ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename); area=0.0; maximum_error=0.0; mean_error_per_pixel=0.0; mean_error=0.0; image_view=OpenCacheView(image); reconstruct_view=OpenCacheView(reconstruct_image); for (y=0; y < (long) image->rows; y++) { p=AcquireCacheViewPixels(image_view,0,y,image->columns,1,&image->exception); q=AcquireCacheViewPixels(reconstruct_view,0,y,reconstruct_image->columns,1, &image->exception); if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL)) break; indexes=AcquireCacheViewIndexes(image_view); reconstruct_indexes=AcquireCacheViewIndexes(reconstruct_view); for (x=0; x < (long) image->columns; x++) { distance=fabs(p->red-(double) q->red); mean_error_per_pixel+=distance; mean_error+=distance*distance; if (distance > maximum_error) maximum_error=distance; area++; distance=fabs(p->green-(double) q->green); mean_error_per_pixel+=distance; mean_error+=distance*distance; if (distance > maximum_error) maximum_error=distance; area++; distance=fabs(p->blue-(double) q->blue); mean_error_per_pixel+=distance; mean_error+=distance*distance; if (distance > maximum_error) maximum_error=distance; area++; distance=fabs(p->opacity-(double) q->opacity); mean_error_per_pixel+=distance; mean_error+=distance*distance; if (distance > maximum_error) maximum_error=distance; area++; if ((image->colorspace == CMYKColorspace) && (reconstruct_image->colorspace == CMYKColorspace)) { distance=fabs(indexes[x]-(double) reconstruct_indexes[x]); mean_error_per_pixel+=distance; mean_error+=distance*distance; if (distance > maximum_error) maximum_error=distance; area++; } p++; q++; } } reconstruct_view=CloseCacheView(reconstruct_view); image_view=CloseCacheView(image_view); image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area); image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale* mean_error/area); image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error); status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse; return(status); }
static MagickRealType GetPeakAbsoluteError(const Image *image, const Image *reconstruct_image,const ChannelType channel, ExceptionInfo *exception) { long y; MagickRealType distance, distortion; register const IndexPacket *indexes, *reconstruct_indexes; register const PixelPacket *p, *q; register long x; ViewInfo *image_view, *reconstruct_view; distortion=0.0; image_view=OpenCacheView(image); reconstruct_view=OpenCacheView(reconstruct_image); for (y=0; y < (long) image->rows; y++) { p=AcquireCacheViewPixels(image_view,0,y,image->columns,1,exception); q=AcquireCacheViewPixels(reconstruct_view,0,y,reconstruct_image->columns,1, exception); if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL)) break; indexes=AcquireCacheViewIndexes(image_view); reconstruct_indexes=AcquireCacheViewIndexes(reconstruct_view); for (x=0; x < (long) image->columns; x++) { if ((channel & RedChannel) != 0) { distance=fabs(p->red-(double) q->red); if (distance > distortion) distortion=distance; } if ((channel & GreenChannel) != 0) { distance=fabs(p->green-(double) q->green); if (distance > distortion) distortion=distance; } if ((channel & BlueChannel) != 0) { distance=fabs(p->blue-(double) q->blue); if (distance > distortion) distortion=distance; } if ((channel & OpacityChannel) != 0) { distance=fabs(p->opacity-(double) q->opacity); if (distance > distortion) distortion=distance; } if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace) && (reconstruct_image->colorspace == CMYKColorspace)) { distance=fabs(indexes[x]-(double) reconstruct_indexes[x]); if (distance > distortion) distortion=distance; } p++; q++; } } reconstruct_view=CloseCacheView(reconstruct_view); image_view=CloseCacheView(image_view); return(distortion); }
static MagickRealType GetMeanErrorPerPixel(Image *image, const Image *reconstruct_image,const ChannelType channel, ExceptionInfo *exception) { long y; MagickRealType alpha, area, beta, distance, maximum_error, mean_error, mean_error_per_pixel; register const IndexPacket *indexes, *reconstruct_indexes; register const PixelPacket *p, *q; register long x; ViewInfo *image_view, *reconstruct_view; alpha=1.0; beta=1.0; area=0.0; maximum_error=0.0; mean_error_per_pixel=0.0; mean_error=0.0; image_view=OpenCacheView(image); reconstruct_view=OpenCacheView(reconstruct_image); for (y=0; y < (long) image->rows; y++) { p=AcquireCacheViewPixels(image_view,0,y,image->columns,1,exception); q=AcquireCacheViewPixels(reconstruct_view,0,y,reconstruct_image->columns,1, exception); if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL)) break; indexes=AcquireCacheViewIndexes(image_view); reconstruct_indexes=AcquireCacheViewIndexes(reconstruct_view); for (x=0; x < (long) image->columns; x++) { if ((channel & OpacityChannel) != 0) { if (image->matte != MagickFalse) alpha=(MagickRealType) (QuantumScale*(QuantumRange-p->opacity)); if (reconstruct_image->matte != MagickFalse) beta=(MagickRealType) (QuantumScale*(QuantumRange-q->opacity)); } if ((channel & RedChannel) != 0) { distance=fabs(alpha*p->red-beta*q->red); mean_error_per_pixel+=distance; mean_error+=distance*distance; if (distance > maximum_error) maximum_error=distance; area++; } if ((channel & GreenChannel) != 0) { distance=fabs(alpha*p->green-beta*q->green); mean_error_per_pixel+=distance; mean_error+=distance*distance; if (distance > maximum_error) maximum_error=distance; area++; } if ((channel & BlueChannel) != 0) { distance=fabs(alpha*p->blue-beta*q->blue); mean_error_per_pixel+=distance; mean_error+=distance*distance; if (distance > maximum_error) maximum_error=distance; area++; } if ((channel & OpacityChannel) != 0) { distance=fabs(alpha*p->opacity-beta*q->opacity); mean_error_per_pixel+=distance; mean_error+=distance*distance; if (distance > maximum_error) maximum_error=distance; area++; } if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace) && (reconstruct_image->colorspace == CMYKColorspace)) { distance=fabs(alpha*indexes[x]-beta*reconstruct_indexes[x]); mean_error_per_pixel+=distance; mean_error+=distance*distance; if (distance > maximum_error) maximum_error=distance; area++; } p++; q++; } } reconstruct_view=CloseCacheView(reconstruct_view); image_view=CloseCacheView(image_view); image->error.mean_error_per_pixel=mean_error_per_pixel/area; image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area; image->error.normalized_maximum_error=QuantumScale*maximum_error; return((MagickRealType) image->error.mean_error_per_pixel); }
MagickExport Image *CompareImageChannels(Image *image, const Image *reconstruct_image,const ChannelType channel, const MetricType metric,double *distortion,ExceptionInfo *exception) { Image *difference_image; long y; MagickPixelPacket composite, red, source, white; MagickStatusType difference; register const IndexPacket *indexes, *reconstruct_indexes; register const PixelPacket *p, *q; register IndexPacket *difference_indexes; register long x; register PixelPacket *r; ViewInfo *difference_view, *image_view, *reconstruct_view; assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); assert(reconstruct_image != (const Image *) NULL); assert(reconstruct_image->signature == MagickSignature); assert(distortion != (double *) NULL); *distortion=0.0; if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); if ((reconstruct_image->columns != image->columns) || (reconstruct_image->rows != image->rows)) ThrowImageException(ImageError,"ImageSizeDiffers"); difference_image=CloneImage(image,image->columns,image->rows,MagickTrue, exception); if (difference_image == (Image *) NULL) return((Image *) NULL); if (SetImageStorageClass(difference_image,DirectClass) == MagickFalse) { InheritException(exception,&difference_image->exception); difference_image=DestroyImage(difference_image); return((Image *) NULL); } (void) QueryMagickColor("#f1001e",&red,exception); (void) QueryMagickColor("#ffffff",&white,exception); if (difference_image->colorspace == CMYKColorspace) { ConvertRGBToCMYK(&red); ConvertRGBToCMYK(&white); } /* Generate difference image. */ GetMagickPixelPacket(reconstruct_image,&source); GetMagickPixelPacket(difference_image,&composite); image_view=OpenCacheView(image); reconstruct_view=OpenCacheView(reconstruct_image); difference_view=OpenCacheView(difference_image); for (y=0; y < (long) image->rows; y++) { p=AcquireCacheViewPixels(image_view,0,y,image->columns,1,exception); q=AcquireCacheViewPixels(reconstruct_view,0,y,reconstruct_image->columns,1, exception); r=SetCacheView(difference_view,0,y,difference_image->columns,1); if ((p == (const PixelPacket *) NULL) || (q == (const PixelPacket *) NULL) || (r == (PixelPacket *) NULL)) break; indexes=AcquireCacheViewIndexes(image_view); reconstruct_indexes=AcquireCacheViewIndexes(reconstruct_view); difference_indexes=GetCacheViewIndexes(difference_view); for (x=0; x < (long) image->columns; x++) { difference=MagickFalse; if ((channel & RedChannel) != 0) if (p->red != q->red) difference=MagickTrue; if ((channel & GreenChannel) != 0) if (p->green != q->green) difference=MagickTrue; if ((channel & BlueChannel) != 0) if (p->blue != q->blue) difference=MagickTrue; if ((channel & OpacityChannel) != 0) if (p->opacity != q->opacity) difference=MagickTrue; if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace) && (reconstruct_image->colorspace == CMYKColorspace)) if (indexes[x] != reconstruct_indexes[x]) difference=MagickTrue; SetMagickPixelPacket(reconstruct_image,q,reconstruct_indexes+x,&source); if (difference != MagickFalse) MagickPixelCompositeOver(&source,7.5*QuantumRange/10.0,&red, (MagickRealType) red.opacity,&composite); else MagickPixelCompositeOver(&source,7.5*QuantumRange/10.0,&white, (MagickRealType) white.opacity,&composite); SetPixelPacket(difference_image,&composite,r,difference_indexes+x); p++; q++; r++; } if (SyncCacheView(difference_view) == MagickFalse) break; } difference_view=CloseCacheView(difference_view); reconstruct_view=CloseCacheView(reconstruct_view); image_view=CloseCacheView(image_view); (void) GetImageChannelDistortion(image,reconstruct_image,channel,metric, distortion,exception); return(difference_image); }