285 lines
11 KiB
C
285 lines
11 KiB
C
|
/*
|
|||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
% %
|
|||
|
% %
|
|||
|
% AAA N N AAA L Y Y ZZZZZ EEEEE %
|
|||
|
% A A NN N A A L Y Y ZZ E %
|
|||
|
% AAAAA N N N AAAAA L Y ZZZ EEE %
|
|||
|
% A A N NN A A L Y ZZ E %
|
|||
|
% A A N N A A LLLLL Y ZZZZZ EEEEE %
|
|||
|
% %
|
|||
|
% Analyze An Image %
|
|||
|
% %
|
|||
|
% Software Design %
|
|||
|
% Bill Corbis %
|
|||
|
% December 1998 %
|
|||
|
% %
|
|||
|
% %
|
|||
|
% Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization %
|
|||
|
% dedicated to making software imaging solutions freely available. %
|
|||
|
% %
|
|||
|
% You may not use this file except in compliance with the License. You may %
|
|||
|
% obtain a copy of the License at %
|
|||
|
% %
|
|||
|
% https://imagemagick.org/script/license.php %
|
|||
|
% %
|
|||
|
% Unless required by applicable law or agreed to in writing, software %
|
|||
|
% distributed under the License is distributed on an "AS IS" BASIS, %
|
|||
|
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
|
|||
|
% See the License for the specific language governing permissions and %
|
|||
|
% limitations under the License. %
|
|||
|
% %
|
|||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
%
|
|||
|
*/
|
|||
|
|
|||
|
/*
|
|||
|
Include declarations.
|
|||
|
*/
|
|||
|
#include <stdio.h>
|
|||
|
#include <stdlib.h>
|
|||
|
#include <string.h>
|
|||
|
#include <time.h>
|
|||
|
#include <assert.h>
|
|||
|
#include <math.h>
|
|||
|
#include "magick/studio.h"
|
|||
|
#include "magick/MagickCore.h"
|
|||
|
|
|||
|
/*
|
|||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
% %
|
|||
|
% %
|
|||
|
% %
|
|||
|
% a n a l y z e I m a g e %
|
|||
|
% %
|
|||
|
% %
|
|||
|
% %
|
|||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
%
|
|||
|
% analyzeImage() computes the brightness and saturation mean, standard
|
|||
|
% deviation, kurtosis and skewness and stores these values as attributes
|
|||
|
% of the image.
|
|||
|
%
|
|||
|
% The format of the analyzeImage method is:
|
|||
|
%
|
|||
|
% size_t analyzeImage(Image *images,const int argc,char **argv,
|
|||
|
% ExceptionInfo *exception)
|
|||
|
%
|
|||
|
% A description of each parameter follows:
|
|||
|
%
|
|||
|
% o image: the address of a structure of type Image.
|
|||
|
%
|
|||
|
% o argc: Specifies a pointer to an integer describing the number of
|
|||
|
% elements in the argument vector.
|
|||
|
%
|
|||
|
% o argv: Specifies a pointer to a text array containing the command line
|
|||
|
% arguments.
|
|||
|
%
|
|||
|
% o exception: return any errors or warnings in this structure.
|
|||
|
%
|
|||
|
*/
|
|||
|
|
|||
|
typedef struct _StatisticsInfo
|
|||
|
{
|
|||
|
double
|
|||
|
area,
|
|||
|
brightness,
|
|||
|
mean,
|
|||
|
standard_deviation,
|
|||
|
sum[5],
|
|||
|
kurtosis,
|
|||
|
skewness;
|
|||
|
} StatisticsInfo;
|
|||
|
|
|||
|
static inline int GetMagickNumberThreads(const Image *source,
|
|||
|
const Image *destination,const size_t chunk,int multithreaded)
|
|||
|
{
|
|||
|
#define MagickMax(x,y) (((x) > (y)) ? (x) : (y))
|
|||
|
#define MagickMin(x,y) (((x) < (y)) ? (x) : (y))
|
|||
|
|
|||
|
/*
|
|||
|
Number of threads bounded by the amount of work and any thread resource
|
|||
|
limit. The limit is 2 if the pixel cache type is not memory or
|
|||
|
memory-mapped.
|
|||
|
*/
|
|||
|
if (multithreaded == 0)
|
|||
|
return(1);
|
|||
|
if (((GetImagePixelCacheType(source) != MemoryCache) &&
|
|||
|
(GetImagePixelCacheType(source) != MapCache)) ||
|
|||
|
((GetImagePixelCacheType(destination) != MemoryCache) &&
|
|||
|
(GetImagePixelCacheType(destination) != MapCache)))
|
|||
|
return(MagickMax(MagickMin(GetMagickResourceLimit(ThreadResource),2),1));
|
|||
|
return(MagickMax(MagickMin((ssize_t) GetMagickResourceLimit(ThreadResource),
|
|||
|
(ssize_t) (chunk)/64),1));
|
|||
|
}
|
|||
|
|
|||
|
ModuleExport size_t analyzeImage(Image **images,const int argc,
|
|||
|
const char **argv,ExceptionInfo *exception)
|
|||
|
{
|
|||
|
#define AnalyzeImageFilterTag "Filter/Analyze"
|
|||
|
#define magick_number_threads(source,destination,chunk,multithreaded) \
|
|||
|
num_threads(GetMagickNumberThreads(source,destination,chunk,multithreaded))
|
|||
|
|
|||
|
char
|
|||
|
text[MagickPathExtent];
|
|||
|
|
|||
|
Image
|
|||
|
*image;
|
|||
|
|
|||
|
MagickBooleanType
|
|||
|
status;
|
|||
|
|
|||
|
MagickOffsetType
|
|||
|
progress;
|
|||
|
|
|||
|
assert(images != (Image **) NULL);
|
|||
|
assert(*images != (Image *) NULL);
|
|||
|
assert((*images)->signature == MagickCoreSignature);
|
|||
|
(void) argc;
|
|||
|
(void) argv;
|
|||
|
image=(*images);
|
|||
|
status=MagickTrue;
|
|||
|
progress=0;
|
|||
|
for ( ; image != (Image *) NULL; image=GetNextImageInList(image))
|
|||
|
{
|
|||
|
CacheView
|
|||
|
*image_view;
|
|||
|
|
|||
|
double
|
|||
|
area;
|
|||
|
|
|||
|
ssize_t
|
|||
|
y;
|
|||
|
|
|||
|
StatisticsInfo
|
|||
|
brightness,
|
|||
|
saturation;
|
|||
|
|
|||
|
if (status == MagickFalse)
|
|||
|
continue;
|
|||
|
(void) memset(&brightness,0,sizeof(brightness));
|
|||
|
(void) memset(&saturation,0,sizeof(saturation));
|
|||
|
status=MagickTrue;
|
|||
|
image_view=AcquireVirtualCacheView(image,exception);
|
|||
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|||
|
#pragma omp parallel for schedule(static) \
|
|||
|
shared(progress,status,brightness,saturation) \
|
|||
|
magick_number_threads(image,image,image->rows,1)
|
|||
|
#endif
|
|||
|
for (y=0; y < (ssize_t) image->rows; y++)
|
|||
|
{
|
|||
|
const PixelPacket
|
|||
|
*p;
|
|||
|
|
|||
|
ssize_t
|
|||
|
i,
|
|||
|
x;
|
|||
|
|
|||
|
StatisticsInfo
|
|||
|
local_brightness,
|
|||
|
local_saturation;
|
|||
|
|
|||
|
if (status == MagickFalse)
|
|||
|
continue;
|
|||
|
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
|
|||
|
if (p == (const PixelPacket *) NULL)
|
|||
|
{
|
|||
|
status=MagickFalse;
|
|||
|
continue;
|
|||
|
}
|
|||
|
(void) memset(&local_brightness,0,sizeof(local_brightness));
|
|||
|
(void) memset(&local_saturation,0,sizeof(local_saturation));
|
|||
|
for (x=0; x < (ssize_t) image->columns; x++)
|
|||
|
{
|
|||
|
double
|
|||
|
b,
|
|||
|
h,
|
|||
|
s;
|
|||
|
|
|||
|
ConvertRGBToHSL(GetPixelRed(p),GetPixelGreen(p),GetPixelBlue(p),
|
|||
|
&h,&s,&b);
|
|||
|
b*=QuantumRange;
|
|||
|
for (i=1; i <= 4; i++)
|
|||
|
local_brightness.sum[i]+=pow(b,(double) i);
|
|||
|
s*=QuantumRange;
|
|||
|
for (i=1; i <= 4; i++)
|
|||
|
local_saturation.sum[i]+=pow(s,(double) i);
|
|||
|
p++;
|
|||
|
}
|
|||
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|||
|
#pragma omp critical (analyzeImage)
|
|||
|
#endif
|
|||
|
for (i=1; i <= 4; i++)
|
|||
|
{
|
|||
|
brightness.sum[i]+=local_brightness.sum[i];
|
|||
|
saturation.sum[i]+=local_saturation.sum[i];
|
|||
|
}
|
|||
|
}
|
|||
|
image_view=DestroyCacheView(image_view);
|
|||
|
area=(double) image->columns*image->rows;
|
|||
|
brightness.mean=brightness.sum[1]/area;
|
|||
|
(void) FormatLocaleString(text,MagickPathExtent,"%g",brightness.mean);
|
|||
|
(void) SetImageProperty(image,"filter:brightness:mean",text);
|
|||
|
brightness.standard_deviation=sqrt(brightness.sum[2]/area-
|
|||
|
(brightness.sum[1]/area*brightness.sum[1]/area));
|
|||
|
(void) FormatLocaleString(text,MagickPathExtent,"%g",
|
|||
|
brightness.standard_deviation);
|
|||
|
(void) SetImageProperty(image,"filter:brightness:standard-deviation",text);
|
|||
|
if (fabs(brightness.standard_deviation) >= MagickEpsilon)
|
|||
|
brightness.kurtosis=(brightness.sum[4]/area-4.0*brightness.mean*
|
|||
|
brightness.sum[3]/area+6.0*brightness.mean*brightness.mean*
|
|||
|
brightness.sum[2]/area-3.0*brightness.mean*brightness.mean*
|
|||
|
brightness.mean*brightness.mean)/(brightness.standard_deviation*
|
|||
|
brightness.standard_deviation*brightness.standard_deviation*
|
|||
|
brightness.standard_deviation)-3.0;
|
|||
|
(void) FormatLocaleString(text,MagickPathExtent,"%g",brightness.kurtosis);
|
|||
|
(void) SetImageProperty(image,"filter:brightness:kurtosis",text);
|
|||
|
if (brightness.standard_deviation != 0)
|
|||
|
brightness.skewness=(brightness.sum[3]/area-3.0*brightness.mean*
|
|||
|
brightness.sum[2]/area+2.0*brightness.mean*brightness.mean*
|
|||
|
brightness.mean)/(brightness.standard_deviation*
|
|||
|
brightness.standard_deviation*brightness.standard_deviation);
|
|||
|
(void) FormatLocaleString(text,MagickPathExtent,"%g",brightness.skewness);
|
|||
|
(void) SetImageProperty(image,"filter:brightness:skewness",text);
|
|||
|
saturation.mean=saturation.sum[1]/area;
|
|||
|
(void) FormatLocaleString(text,MagickPathExtent,"%g",saturation.mean);
|
|||
|
(void) SetImageProperty(image,"filter:saturation:mean",text);
|
|||
|
saturation.standard_deviation=sqrt(saturation.sum[2]/area-
|
|||
|
(saturation.sum[1]/area*saturation.sum[1]/area));
|
|||
|
(void) FormatLocaleString(text,MagickPathExtent,"%g",
|
|||
|
saturation.standard_deviation);
|
|||
|
(void) SetImageProperty(image,"filter:saturation:standard-deviation",text);
|
|||
|
if (fabs(saturation.standard_deviation) >= MagickEpsilon)
|
|||
|
saturation.kurtosis=(saturation.sum[4]/area-4.0*saturation.mean*
|
|||
|
saturation.sum[3]/area+6.0*saturation.mean*saturation.mean*
|
|||
|
saturation.sum[2]/area-3.0*saturation.mean*saturation.mean*
|
|||
|
saturation.mean*saturation.mean)/(saturation.standard_deviation*
|
|||
|
saturation.standard_deviation*saturation.standard_deviation*
|
|||
|
saturation.standard_deviation)-3.0;
|
|||
|
(void) FormatLocaleString(text,MagickPathExtent,"%g",saturation.kurtosis);
|
|||
|
(void) SetImageProperty(image,"filter:saturation:kurtosis",text);
|
|||
|
if (fabs(saturation.standard_deviation) >= MagickEpsilon)
|
|||
|
saturation.skewness=(saturation.sum[3]/area-3.0*saturation.mean*
|
|||
|
saturation.sum[2]/area+2.0*saturation.mean*saturation.mean*
|
|||
|
saturation.mean)/(saturation.standard_deviation*
|
|||
|
saturation.standard_deviation*saturation.standard_deviation);
|
|||
|
(void) FormatLocaleString(text,MagickPathExtent,"%g",saturation.skewness);
|
|||
|
(void) SetImageProperty(image,"filter:saturation:skewness",text);
|
|||
|
if (image->progress_monitor != (MagickProgressMonitor) NULL)
|
|||
|
{
|
|||
|
MagickBooleanType
|
|||
|
proceed;
|
|||
|
|
|||
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|||
|
#pragma omp atomic
|
|||
|
#endif
|
|||
|
progress++;
|
|||
|
proceed=SetImageProgress(image,AnalyzeImageFilterTag,progress,
|
|||
|
GetImageListLength(image));
|
|||
|
if (proceed == MagickFalse)
|
|||
|
status=MagickFalse;
|
|||
|
}
|
|||
|
}
|
|||
|
return(MagickImageFilterSignature);
|
|||
|
}
|